From 58dc81449d87d2938d8d4d4d242d03778e2d2254 Mon Sep 17 00:00:00 2001 From: chendelian <116870791@qq.com> Date: Tue, 31 Mar 2026 18:19:37 +0800 Subject: [PATCH] chore: auto release commit (2026-03-31 18:19:36) --- scripts/__pycache__/account.cpython-312.pyc | Bin 25619 -> 25173 bytes scripts/account.py | 240 ++++++++++---------- 2 files changed, 120 insertions(+), 120 deletions(-) diff --git a/scripts/__pycache__/account.cpython-312.pyc b/scripts/__pycache__/account.cpython-312.pyc index dbd65246c40184270cea8e8902d87ec20b6b2fa1..4aef0a751a512ab5f334c1bbe02a6901ed7231cc 100644 GIT binary patch delta 10277 zcmb6<2~<>9mjBnUeW6&Y*uf8iQtU{?l_&}-f}n!N5KUR~KUi2)kzc=5?4paLHIb+R z^GsZmHr;lj(I|;oj7GDWWnxa+g!GB2HZ$po#nLmUCCi-2$xP3gd;hNjw9RzS)4KmH z_ucp2efQmW?|qOD=|5biqpp}tdJ58%r>{7lUA#9ckzTuZYIPY$ag0mbu4~fK6i>BT z5argyDL3U%l&~H!X_ukNprCjIVQCA|(+PyHv81Uu^(oCsM()K^c2lH(1XBrw)5)Jq zafqIBJGwc7&MOEz&7x?VO!{am$(R;5AsgS89YGua@^R!7Iw@e`v`tn{*A&m``Ggh) zXV|W6O5}{Zj!#GeWT@5gNtkO$gA=>YS& zGJpl#41k3a_DrCv=Zk>iETCA-Rm_G89BK~y=7t;fTsbtBaCbwuY1}+OS8(&8rj);n ztHAy2qlFh>f2PCMxX5}{ieteh1?+M%)Xm@)o}^_i(-f4cwvKV0M{-={I1bW?)Q^9; zO5}Odtcjzns@k*`Mw#?8Jr+p&wMddT6F-Yj*-QyK=s%U8H9lG=9d1dVNI7qT)a#De z++wM1!$kaSc_z#LubH&@$XohkebbzY9UGQNOECXT$0}Tco92p}=0YzUKZl=t5*Uk# zrf}+`fzr~yr4$&a zH0>Xy1f$O2YQ|>?<-aRRopdK{s~bUf)!FEvmUD=_=xpbWSg7w1ogTNtMVi!e13uI^ z`l4JmbnH;*^|PVg{^6IO4fVb@{LJy2ZydUD?y2Vh4{tGJNJ?oawp8LJ#j4x_FvhUR%+)sD9-# z`>K_-4J^#y#<}j{M_!gkVZEOGjXs(7Mvt492+Q<(&x~}3S3cOYYw*Z|m9Sl&R;SzU6&<3_%U0Jl2C5cUtsGlCTUoWJVpVM;TP#g`@W7c+ z-?O3DUXr&2_Pm&d3burW7U2ERz88nL9|9p5Jo?PY^SeWRz@~#Rcw}lWhgY=ABRWJJ z4F{7cAL$-`b1yJ&?%=6s!W+J`FZAfI2lu}^vLE@m%I)0B4n6lcyQ*>F%{LAY9(i6y z8-DYV8|ThTTcsy^<1G>c2YXJyriWhGKY02Tc4owU|wfd|p%?BipPEmWGs@acXK zw9wAOp$q5lIIhg!4m==tq=KSSDpMIe(hbWWJpA(Dll^z5Wt*c9ssŌu^Vo}v&L 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 delta 9939 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 diff --git a/scripts/account.py b/scripts/account.py index 0e36a16..a979e45 100644 --- a/scripts/account.py +++ b/scripts/account.py @@ -6,6 +6,7 @@ import subprocess import tempfile import time from datetime import datetime +from typing import Optional # Windows GBK 编码兼容修复 if sys.platform == "win32": @@ -15,6 +16,40 @@ if sys.platform == "win32": BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) SKILL_SLUG = "account-manager" +# SQLite 无独立 DATETIME 类型:时间统一存 INTEGER Unix 秒(UTC),查询/JSON 再转 ISO8601。 +# 下列 DDL 含注释,会原样写入 sqlite_master,便于 Navicat / DBeaver 等查看建表语句。 +ACCOUNTS_TABLE_SQL = """ +/* 多平台账号表:与本地 Chromium/Edge 用户数据目录(profile)绑定,供发布类技能读取登录态 */ +CREATE TABLE accounts ( + id INTEGER PRIMARY KEY AUTOINCREMENT, -- 账号主键(自增) + name TEXT NOT NULL, -- 展示名称,如「搜狐1号」 + platform TEXT NOT NULL, -- 平台标识,与内置 PLATFORM_URLS 键一致,如 sohu + phone TEXT, -- 可选绑定手机号 + profile_dir TEXT, -- Playwright 持久化用户数据目录(绝对路径) + url TEXT, -- 平台入口或登录页 URL + login_status INTEGER NOT NULL DEFAULT 0, -- 是否已登录:0 否 1 是(由脚本校验后写入) + last_login_at INTEGER, -- 最近一次登录成功时间,Unix 秒 UTC;未登录过为 NULL + extra_json TEXT, -- 扩展字段 JSON + created_at INTEGER NOT NULL, -- 记录创建时间,Unix 秒 UTC + updated_at INTEGER NOT NULL -- 记录最后更新时间,Unix 秒 UTC +); +""" + + +def _now_unix() -> int: + return int(time.time()) + + +def _unix_to_iso_local(ts: Optional[int]) -> Optional[str]: + """对外 JSON:本地时区 ISO8601 字符串,便于人读。""" + if ts is None: + return None + try: + return datetime.fromtimestamp(int(ts)).isoformat(timespec="seconds") + except (ValueError, OSError, OverflowError): + return None + + PLATFORM_URLS = { "sohu": "https://mp.sohu.com", "zhihu": "https://www.zhihu.com", @@ -75,107 +110,13 @@ def get_conn(): return sqlite3.connect(get_db_path()) -def _ensure_column(cur, table_name, col_name, ddl): - cur.execute(f"PRAGMA table_info({table_name})") - cols = [row[1] for row in cur.fetchall()] - if col_name not in cols: - cur.execute(f"ALTER TABLE {table_name} ADD COLUMN {col_name} {ddl}") - - -def _has_text_primary_id(cur): - cur.execute("PRAGMA table_info(accounts)") - for row in cur.fetchall(): - if row[1] == "id": - t = (row[2] or "").upper() - return "TEXT" in t or "CHAR" in t - return False - - -def _migrate_text_id_to_integer(conn): - cur = conn.cursor() - cur.execute( - """ - CREATE TABLE accounts_new ( - id INTEGER PRIMARY KEY AUTOINCREMENT, - name TEXT NOT NULL, - platform TEXT NOT NULL, - phone TEXT, - profile_dir TEXT, - url TEXT, - login_status INTEGER DEFAULT 0, - last_login_at TEXT, - extra_json TEXT, - created_at TEXT NOT NULL, - updated_at TEXT NOT NULL - ) - """ - ) - cur.execute( - "SELECT name, platform, phone, profile_dir, login_status, last_login_at, extra_json, created_at, updated_at FROM accounts ORDER BY rowid" - ) - rows = cur.fetchall() - for row in rows: - name, platform, phone, profile_dir, login_status, last_login_at, extra_json, created_at, updated_at = row - plat = (platform or "").strip().lower() - url = PLATFORM_URLS.get(plat, "") - cur.execute( - """ - INSERT INTO accounts_new - (name, platform, phone, profile_dir, url, login_status, last_login_at, extra_json, created_at, updated_at) - VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?) - """, - ( - name, - plat, - phone or "", - profile_dir or "", - url, - int(login_status or 0), - last_login_at, - extra_json, - created_at, - updated_at, - ), - ) - cur.execute("DROP TABLE accounts") - cur.execute("ALTER TABLE accounts_new RENAME TO accounts") - - def init_db(): conn = get_conn() try: cur = conn.cursor() cur.execute("SELECT name FROM sqlite_master WHERE type='table' AND name='accounts'") if not cur.fetchone(): - cur.execute( - """ - CREATE TABLE accounts ( - id INTEGER PRIMARY KEY AUTOINCREMENT, - name TEXT NOT NULL, - platform TEXT NOT NULL, - phone TEXT, - profile_dir TEXT, - url TEXT, - login_status INTEGER DEFAULT 0, - last_login_at TEXT, - extra_json TEXT, - created_at TEXT NOT NULL, - updated_at TEXT NOT NULL - ) - """ - ) - else: - if _has_text_primary_id(cur): - _migrate_text_id_to_integer(conn) - cur = conn.cursor() - _ensure_column(cur, "accounts", "url", "TEXT") - cur.execute( - "SELECT id, platform FROM accounts WHERE url IS NULL OR TRIM(COALESCE(url,'')) = ''" - ) - for rid, plat in cur.fetchall(): - pu = PLATFORM_URLS.get((plat or "").strip().lower(), "") - if pu: - cur.execute("UPDATE accounts SET url = ? WHERE id = ?", (pu, rid)) + cur.executescript(ACCOUNTS_TABLE_SQL) conn.commit() finally: conn.close() @@ -197,7 +138,11 @@ def get_account_by_id(account_id): try: cur = conn.cursor() cur.execute( - "SELECT id, name, platform, phone, profile_dir, url, login_status, last_login_at, extra_json FROM accounts WHERE id = ?", + """ + SELECT id, name, platform, phone, profile_dir, url, login_status, last_login_at, extra_json, + created_at, updated_at + FROM accounts WHERE id = ? + """, (aid,), ) row = cur.fetchone() @@ -211,7 +156,9 @@ def get_account_by_id(account_id): "profile_dir": row[4] or get_default_profile_dir(row[0]), "url": row[5] or PLATFORM_URLS.get(row[2], ""), "login_status": int(row[6] or 0), - "last_login_at": row[7], + "last_login_at": _unix_to_iso_local(row[7]), + "created_at": _unix_to_iso_local(row[9]), + "updated_at": _unix_to_iso_local(row[10]), } if row[8]: try: @@ -279,7 +226,7 @@ def cmd_add(platform: str, phone: str = ""): phone = (phone or "").strip() url = PLATFORM_URLS[platform] - now = datetime.now().isoformat(timespec="seconds") + now = _now_unix() conn = get_conn() try: @@ -298,7 +245,7 @@ def cmd_add(platform: str, phone: str = ""): profile_dir = get_default_profile_dir(new_id) cur.execute( "UPDATE accounts SET profile_dir = ?, updated_at = ? WHERE id = ?", - (profile_dir, now, new_id), + (profile_dir, _now_unix(), new_id), ) conn.commit() finally: @@ -363,6 +310,32 @@ def _login_timeout_seconds(): return 300 +def _login_poll_interval_seconds(): + """交互窗口内轮询地址栏的间隔,默认 1.5s,便于登录成功后尽快收尾。""" + try: + v = float((os.getenv("JIANGCHANG_LOGIN_POLL_INTERVAL_SECONDS") or "1.5").strip()) + return max(0.5, min(v, 10.0)) + except ValueError: + return 1.5 + + +def _url_looks_logged_in(platform: str, page_url: str) -> bool: + """根据当前 URL 判断是否已进入后台(与交互窗口、无头校验共用逻辑)。""" + u = (page_url or "").lower() + if not u: + return False + if platform == "sohu": + if "passport" in u or "/login" in u: + return False + # 登录成功后常见:…/mpfe/v4/contentManagement/… + if "mpfe" in u or "contentmanagement" in u: + return True + return False + if "login" in u or "signin" in u or "passport" in u: + return False + return True + + def _verify_login(profile_dir: str, platform: str, start_url: str, channel: str) -> bool: try: from playwright.sync_api import sync_playwright @@ -381,15 +354,12 @@ def _verify_login(profile_dir: str, platform: str, start_url: str, channel: str) try: page = ctx.pages[0] if ctx.pages else ctx.new_page() page.goto(start_url, wait_until="domcontentloaded", timeout=45000) - time.sleep(2) - u = (page.url or "").lower() - if platform == "sohu": - if "passport" in u or "/login" in u: - return False - return True - if "login" in u or "signin" in u or "passport" in u: - return False - return True + # 无头启动后可能仍在跳转,短轮询几次再判定 + for _ in range(20): + if _url_looks_logged_in(platform, page.url): + return True + time.sleep(1.0) + return _url_looks_logged_in(platform, page.url) finally: ctx.close() except Exception: @@ -418,18 +388,35 @@ def cmd_login(account_id): url = (target.get("url") or "").strip() or PLATFORM_URLS.get(target["platform"], "https://www.google.com") platform = (target.get("platform") or "").strip().lower() timeout_sec = _login_timeout_seconds() + poll_sec = _login_poll_interval_seconds() browser_name = "Google Chrome" if channel == "chrome" else "Microsoft Edge" print(f"正在为账号 [{target['name']}] 打开 {browser_name} …") print(f"访问地址:{url}") - print("请在窗口内完成登录(扫码/输入密码)。到达等待时间后将自动关闭窗口,您也可提前手动关闭。") - print(f"最长等待:{timeout_sec} 秒") + print( + "请在窗口内完成登录。系统会轮询地址栏,进入后台后将自动关闭窗口并写入状态(无需手动关浏览器)。" + ) + print(f"轮询间隔约 {poll_sec} 秒;全程最长 {timeout_sec} 秒。") - # Playwright 同步 API 宜在独立子进程中跑浏览器,避免线程与事件循环问题 - runner = r"""import json, sys + # 子进程:轮询 URL,已登录则立即关窗;勿仅用 wait_for_event(close),否则用户不关窗口会卡满整段超时。 + runner = r"""import json, sys, time from playwright.sync_api import sync_playwright + +def looks_sohu(u): + u = (u or "").lower() + if not u: + return False + if "passport" in u or "/login" in u: + return False + if "mpfe" in u or "contentmanagement" in u: + return True + return False + with open(sys.argv[1], encoding="utf-8") as f: c = json.load(f) +platform = (c.get("platform") or "").lower() +poll = float(c.get("poll_interval", 1.5)) +deadline = time.time() + float(c["timeout_sec"]) with sync_playwright() as p: ctx = p.chromium.launch_persistent_context( user_data_dir=c["profile_dir"], @@ -441,23 +428,36 @@ with sync_playwright() as p: try: page = ctx.pages[0] if ctx.pages else ctx.new_page() page.goto(c["url"], wait_until="domcontentloaded", timeout=60000) - try: - ctx.wait_for_event("close", timeout=c["timeout_sec"] * 1000) - except Exception: - pass + if platform == "sohu": + while time.time() < deadline: + try: + if looks_sohu(page.url): + break + except Exception: + break + time.sleep(poll) + else: + rem = max(0.0, deadline - time.time()) + try: + ctx.wait_for_event("close", timeout=rem * 1000) + except Exception: + pass + except Exception as e: + print(e, file=sys.stderr) + raise + finally: try: ctx.close() except Exception: pass - except Exception as e: - print(e, file=sys.stderr) - raise """ cfg = { "channel": channel, "profile_dir": profile_dir, "url": url, "timeout_sec": timeout_sec, + "platform": platform, + "poll_interval": poll_sec, } with tempfile.NamedTemporaryFile( mode="w", suffix=".json", delete=False, encoding="utf-8" @@ -499,7 +499,7 @@ with sync_playwright() as p: def _mark_login_status(account_id, success: bool): - now = datetime.now().isoformat(timespec="seconds") + now = _now_unix() conn = get_conn() try: cur = conn.cursor()