#!/usr/bin/env python3 """ 小龙 sheet update: match phones via XXTEA encryption, fill H (UID), D (trial count), I (reg date), J (channel) """ import sys sys.path.insert(0, '/root/.openclaw/workspace/scripts') from phone_encrypt import encrypt_phone import psycopg2 import json import requests import time FEISHU_TOKEN = "t-g10464c0UK5L67JVXSDDT3EWM4DPLSDY5C7R7NS6" SPREADSHEET_TOKEN = "NoZqsFi47hIOHEt9j8WcfRtbnug" SHEET_ID = "qJF4I" # PostgreSQL connection PG_CONFIG = { "host": "bj-postgres-16pob4sg.sql.tencentcdb.com", "port": 28591, "user": "ai_member", "password": "LdfjdjL83h3h3^$&**YGG*", "database": "vala_bi", } def get_sheet_data(): """Read all data from the sheet""" url = f"https://open.feishu.cn/open-apis/sheets/v2/spreadsheets/{SPREADSHEET_TOKEN}/values/{SHEET_ID}!A3:J2502?valueRenderOption=ToString" headers = {"Authorization": f"Bearer {FEISHU_TOKEN}"} r = requests.get(url, headers=headers) data = r.json() values = data.get("data", {}).get("valueRange", {}).get("values", []) return values def write_cell_range(range_str, values_list): """Write values to a range""" url = f"https://open.feishu.cn/open-apis/sheets/v2/spreadsheets/{SPREADSHEET_TOKEN}/values" headers = { "Authorization": f"Bearer {FEISHU_TOKEN}", "Content-Type": "application/json" } body = { "valueRange": { "range": f"{SHEET_ID}!{range_str}", "values": values_list } } r = requests.put(url, headers=headers, json=body) resp = r.json() if resp.get("code") != 0: print(f" ERROR writing {range_str}: {resp}") else: print(f" OK: {range_str}") return resp def main(): # Step 1: Read sheet data print("Reading sheet data...") rows = get_sheet_data() print(f"Got {len(rows)} rows") # Parse rows with phones phone_rows = [] # (row_num, phone, existing_uid, existing_d, existing_i, existing_j) for i, row in enumerate(rows): row_num = i + 3 if len(row) < 5: continue phone = row[4].strip() if row[4] else "" if not phone or len(phone) != 11 or not phone.isdigit(): continue uid = row[7].strip() if len(row) > 7 and row[7] else "" d_val = row[3] if len(row) > 3 and row[3] else "" i_val = row[8].strip() if len(row) > 8 and row[8] else "" j_val = row[9].strip() if len(row) > 9 and row[9] else "" phone_rows.append((row_num, phone, uid, d_val, i_val, j_val)) print(f"Found {len(phone_rows)} rows with valid phones") # Separate into two groups: # Group A: H is "未注册" or empty → need to match and fill H, D, I, J # Group B: H has valid numeric UID but D is empty → need to fill D, I, J group_a = [] # need phone matching group_b = [] # already have UID, need D/I/J for row_num, phone, uid, d_val, i_val, j_val in phone_rows: if not uid or uid == "未注册": group_a.append((row_num, phone, uid, d_val, i_val, j_val)) elif uid.isdigit(): if not d_val or not i_val or not j_val: group_b.append((row_num, phone, uid, d_val, i_val, j_val)) print(f"Group A (need phone match): {len(group_a)}") print(f"Group B (have UID, need D/I/J): {len(group_b)}") # Step 2: Encrypt all phones and query PostgreSQL all_phones_a = [p[1] for p in group_a] all_uids_b = [p[2] for p in group_b] conn = psycopg2.connect(**PG_CONFIG) cur = conn.cursor() # Map: encrypted_phone -> account_id enc_to_uid = {} if all_phones_a: enc_list = [encrypt_phone(p) for p in all_phones_a] phone_to_enc = dict(zip(all_phones_a, enc_list)) # Query accounts placeholders = ",".join(["%s"] * len(enc_list)) cur.execute( f"SELECT id, tel_encrypt FROM bi_vala_app_account WHERE tel_encrypt IN ({placeholders}) AND status=1 AND deleted_at IS NULL", enc_list ) for row in cur.fetchall(): uid, tel_enc = row enc_to_uid[tel_enc] = uid print(f"Matched {len(enc_to_uid)} phones in bi_vala_app_account") # For matched accounts, get registration date and download channel matched_uids = list(enc_to_uid.values()) uid_to_info = {} if matched_uids: placeholders2 = ",".join(["%s"] * len(matched_uids)) cur.execute( f"SELECT id, created_at::date, download_channel FROM bi_vala_app_account WHERE id IN ({placeholders2}) AND status=1 AND deleted_at IS NULL", matched_uids ) for row in cur.fetchall(): uid_to_info[row[0]] = (str(row[1]) if row[1] else "", row[2] or "") # Step 3: Get trial lesson counts for ALL accounts (both groups) all_uids = list(set( [enc_to_uid[encrypt_phone(p[1])] for p in group_a if encrypt_phone(p[1]) in enc_to_uid] + [p[2] for p in group_b] )) uid_to_trial_count = {} if all_uids: placeholders3 = ",".join(["%s"] * len(all_uids)) cur.execute( f"SELECT account_id, COUNT(*) FROM bi_user_course_detail WHERE account_id IN ({placeholders3}) AND expire_time IS NULL AND deleted_at IS NULL GROUP BY account_id", all_uids ) for row in cur.fetchall(): uid_to_trial_count[row[0]] = row[1] cur.close() conn.close() print(f"Trial counts: {len(uid_to_trial_count)} accounts have trials") print(f"Account info: {len(uid_to_info)} accounts have reg date/channel") # Step 4: Build write batches # For Group A: write H (UID), D (trial count), I (reg date), J (channel) # For Group B: write D (trial count), I (reg date), J (channel) writes_h = [] # (row, value) writes_d = [] # (row, value) writes_i = [] # (row, value) writes_j = [] # (row, value) matched_count = 0 for row_num, phone, old_uid, old_d, old_i, old_j in group_a: enc = encrypt_phone(phone) uid = enc_to_uid.get(enc) if uid: matched_count += 1 uid_str = str(uid) # H column: UID writes_h.append((row_num, uid_str)) # D column: trial count trial = uid_to_trial_count.get(uid, 0) writes_d.append((row_num, str(trial) if trial else "")) # I column: reg date info = uid_to_info.get(uid, ("", "")) writes_i.append((row_num, info[0])) # J column: channel writes_j.append((row_num, info[1])) # If not matched, skip - don't write anything for row_num, phone, uid_str, old_d, old_i, old_j in group_b: uid = int(uid_str) # D column: trial count trial = uid_to_trial_count.get(uid, 0) writes_d.append((row_num, str(trial) if trial else "")) # I column: reg date info = uid_to_info.get(uid, ("", "")) writes_i.append((row_num, info[0])) # J column: channel writes_j.append((row_num, info[1])) print(f"\nMatched {matched_count} new phones") print(f"Total H writes: {len(writes_h)}") print(f"Total D writes: {len(writes_d)}") print(f"Total I writes: {len(writes_i)}") print(f"Total J writes: {len(writes_j)}") # Step 5: Write in batches (consecutive rows per column) def write_batch(writes, col_letter): if not writes: return # Sort by row number writes.sort(key=lambda x: x[0]) # Group consecutive rows i = 0 while i < len(writes): start = writes[i][0] vals = [] j = i while j < len(writes) and writes[j][0] == start + (j - i): vals.append([writes[j][1]]) j += 1 end = start + len(vals) - 1 range_str = f"{col_letter}{start}:{col_letter}{end}" write_cell_range(range_str, vals) i = j time.sleep(0.05) print("\nWriting H column...") write_batch(writes_h, "H") print("\nWriting D column...") write_batch(writes_d, "D") print("\nWriting I column...") write_batch(writes_i, "I") print("\nWriting J column...") write_batch(writes_j, "J") print("\n=== SUMMARY ===") print(f"Phones matched: {matched_count}") print(f"H (UID) written: {len(writes_h)}") print(f"D (trial count) written: {len(writes_d)}") print(f"I (reg date) written: {len(writes_i)}") print(f"J (channel) written: {len(writes_j)}") if __name__ == "__main__": main()