From ae1da709ef20d34b3cdafc1cf6737b95edd7e955 Mon Sep 17 00:00:00 2001 From: chendelian <116870791@qq.com> Date: Tue, 31 Mar 2026 17:00:42 +0800 Subject: [PATCH] chore: auto release commit (2026-03-31 17:00:42) --- SKILL.md | 8 +- scripts/__pycache__/account.cpython-312.pyc | Bin 6363 -> 25619 bytes scripts/account.py | 479 +++++++++++++++----- 3 files changed, 366 insertions(+), 121 deletions(-) diff --git a/SKILL.md b/SKILL.md index 9e5ad1b..a3225d7 100644 --- a/SKILL.md +++ b/SKILL.md @@ -1,7 +1,7 @@ --- name: 账号管理 description: 多平台多账号管理。管理各平台账号与Chrome Profile的对应关系,供publisher类Skill调用获取账号信息。 -version: 1.0.1 +version: 1.0.2 author: 深圳匠厂科技有限公司 metadata: openclaw: @@ -23,6 +23,12 @@ allowed-tools: ## 执行步骤 +### 添加账号(仅需平台,可选手机号;ID 数据库自增,名称自动生成如「搜狐1号」) +```bash +python3 {baseDir}/scripts/account.py add sohu +python3 {baseDir}/scripts/account.py add sohu 189xxxxxxxx +``` + ### 列出某平台所有账号 ```bash python3 {baseDir}/scripts/account.py list \ No newline at end of file diff --git a/scripts/__pycache__/account.cpython-312.pyc b/scripts/__pycache__/account.cpython-312.pyc index 741a4cd9f572356855e9a20c90dee817f8ee000e..dbd65246c40184270cea8e8902d87ec20b6b2fa1 100644 GIT binary patch literal 25619 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 diff --git a/scripts/account.py b/scripts/account.py index d25318f..0e36a16 100644 --- a/scripts/account.py +++ b/scripts/account.py @@ -2,29 +2,42 @@ import sys import json import os import sqlite3 +import subprocess +import tempfile +import time from datetime import datetime # Windows GBK 编码兼容修复 if sys.platform == "win32": import io - sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8', errors='replace') - sys.stderr = io.TextIOWrapper(sys.stderr.buffer, encoding='utf-8', errors='replace') + sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding="utf-8", errors="replace") + sys.stderr = io.TextIOWrapper(sys.stderr.buffer, encoding="utf-8", errors="replace") BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) SKILL_SLUG = "account-manager" PLATFORM_URLS = { - # 自媒体/图文平台 "sohu": "https://mp.sohu.com", "zhihu": "https://www.zhihu.com", "wechat": "https://mp.weixin.qq.com", - - # 大模型平台 "kimi": "https://kimi.moonshot.cn", "deepseek": "https://chat.deepseek.com", "doubao": "https://www.doubao.com", "qianwen": "https://tongyi.aliyun.com", "yiyan": "https://yiyan.baidu.com", - "yuanbao": "https://yuanbao.tencent.com" + "yuanbao": "https://yuanbao.tencent.com", +} + +# 新账号默认名称:搜狐1号 / sohu_2 等同平台序号 +_PLATFORM_NAME_CN = { + "sohu": "搜狐", + "zhihu": "知乎", + "wechat": "微信", + "kimi": "Kimi", + "deepseek": "DeepSeek", + "doubao": "豆包", + "qianwen": "通义", + "yiyan": "文心", + "yuanbao": "元宝", } @@ -53,7 +66,7 @@ def get_db_path(): def get_default_profile_dir(account_id): - path = os.path.join(get_skill_data_dir(), "profiles", account_id) + path = os.path.join(get_skill_data_dir(), "profiles", str(account_id)) os.makedirs(path, exist_ok=True) return path @@ -62,33 +75,6 @@ def get_conn(): return sqlite3.connect(get_db_path()) -def init_db(): - conn = get_conn() - try: - cur = conn.cursor() - cur.execute( - """ - CREATE TABLE IF NOT EXISTS accounts ( - id TEXT PRIMARY KEY, - name TEXT NOT NULL, - platform TEXT NOT NULL, - phone TEXT, - profile_dir TEXT, - login_status INTEGER DEFAULT 0, - last_login_at TEXT, - extra_json TEXT, - created_at TEXT NOT NULL, - updated_at TEXT NOT NULL - ) - """ - ) - _ensure_column(cur, "accounts", "login_status", "INTEGER DEFAULT 0") - _ensure_column(cur, "accounts", "last_login_at", "TEXT") - conn.commit() - finally: - conn.close() - - 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()] @@ -96,14 +82,123 @@ def _ensure_column(cur, table_name, col_name, ddl): 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)) + conn.commit() + finally: + conn.close() + + +def _normalize_account_id(account_id): + if account_id is None: + return None + s = str(account_id).strip() + if s.isdigit(): + return int(s) + return s + + def get_account_by_id(account_id): init_db() + aid = _normalize_account_id(account_id) conn = get_conn() try: cur = conn.cursor() cur.execute( - "SELECT id, name, platform, phone, profile_dir, login_status, last_login_at, extra_json FROM accounts WHERE id = ?", - (account_id,), + "SELECT id, name, platform, phone, profile_dir, url, login_status, last_login_at, extra_json FROM accounts WHERE id = ?", + (aid,), ) row = cur.fetchone() if not row: @@ -114,12 +209,13 @@ def get_account_by_id(account_id): "platform": row[2], "phone": row[3] or "", "profile_dir": row[4] or get_default_profile_dir(row[0]), - "login_status": int(row[5] or 0), - "last_login_at": row[6], + "url": row[5] or PLATFORM_URLS.get(row[2], ""), + "login_status": int(row[6] or 0), + "last_login_at": row[7], } - if row[7]: + if row[8]: try: - extra = json.loads(row[7]) + extra = json.loads(row[8]) if isinstance(extra, dict): acc.update(extra) except Exception: @@ -128,14 +224,17 @@ def get_account_by_id(account_id): finally: conn.close() + +def _default_name_for_platform(platform: str, index: int) -> str: + cn = _PLATFORM_NAME_CN.get(platform) + if cn: + return f"{cn}{index}号" + return f"{platform}_{index}" + + def cmd_list(platform="all"): - """ - 修改后的 list 功能,能直观打出带着【手机号】的详细台账供AI查询匹配! - 支持查全部(all),或指定(sohu) - """ init_db() conn = get_conn() - found = False try: cur = conn.cursor() if platform == "all": @@ -159,7 +258,8 @@ def cmd_list(platform="all"): found = True if not found: - print(f"ERROR:NO_ACCOUNTS_FOUND") + print("ERROR:NO_ACCOUNTS_FOUND") + def cmd_get(account_id): acc = get_account_by_id(account_id) @@ -169,133 +269,272 @@ def cmd_get(account_id): print("ERROR:ACCOUNT_NOT_FOUND") -def cmd_add(account_id, name, platform, phone="", profile_dir=""): +def cmd_add(platform: str, phone: str = ""): + """添加账号:仅平台 + 可选手机号;ID 自增,名称按平台序号自动生成。""" init_db() platform = (platform or "").strip().lower() if platform not in PLATFORM_URLS: print(f"ERROR:INVALID_PLATFORM (支持: {', '.join(sorted(PLATFORM_URLS.keys()))})") return - account_id = (account_id or "").strip() - name = (name or "").strip() or account_id - if not account_id: - print("ERROR:INVALID_ACCOUNT_ID") - return - - profile_dir = (profile_dir or "").strip() or get_default_profile_dir(account_id) - os.makedirs(profile_dir, exist_ok=True) + phone = (phone or "").strip() + url = PLATFORM_URLS[platform] now = datetime.now().isoformat(timespec="seconds") conn = get_conn() try: cur = conn.cursor() + cur.execute("SELECT COUNT(*) FROM accounts WHERE platform = ?", (platform,)) + next_idx = cur.fetchone()[0] + 1 + name = _default_name_for_platform(platform, next_idx) cur.execute( """ - INSERT INTO accounts (id, name, platform, phone, profile_dir, login_status, last_login_at, extra_json, created_at, updated_at) - VALUES (?, ?, ?, ?, ?, 0, NULL, NULL, ?, ?) - ON CONFLICT(id) DO UPDATE SET - name = excluded.name, - platform = excluded.platform, - phone = excluded.phone, - profile_dir = excluded.profile_dir, - updated_at = excluded.updated_at + INSERT INTO accounts (name, platform, phone, profile_dir, url, login_status, last_login_at, extra_json, created_at, updated_at) + VALUES (?, ?, ?, '', ?, 0, NULL, NULL, ?, ?) """, - (account_id, name, platform, phone, profile_dir, now, now), + (name, platform, phone, url, now, now), + ) + new_id = cur.lastrowid + profile_dir = get_default_profile_dir(new_id) + cur.execute( + "UPDATE accounts SET profile_dir = ?, updated_at = ? WHERE id = ?", + (profile_dir, now, new_id), ) conn.commit() finally: conn.close() - print(f"✅ 已保存账号:{account_id}({platform})") - print("ℹ️ 请继续执行登录:python account.py login <账号ID>") + print(f"✅ 已保存账号:ID {new_id} | {name} | {platform}") + print(f"ℹ️ 登录请执行:python account.py login {new_id}") + + +def _win_find_exe(candidates): + for p in candidates: + if p and os.path.isfile(p): + return p + return None + + +def resolve_chromium_channel(): + """ + Playwright channel: 'chrome' | 'msedge' | None + Windows 优先 Chrome,其次 Edge;其它系统默认 chrome(由 Playwright 解析 PATH)。 + """ + if sys.platform != "win32": + return "chrome" + + pf = os.environ.get("ProgramFiles", r"C:\Program Files") + pfx86 = os.environ.get("ProgramFiles(x86)", r"C:\Program Files (x86)") + local = os.environ.get("LocalAppData", "") + + chrome = _win_find_exe( + [ + os.path.join(pf, "Google", "Chrome", "Application", "chrome.exe"), + os.path.join(pfx86, "Google", "Chrome", "Application", "chrome.exe"), + os.path.join(local, "Google", "Chrome", "Application", "chrome.exe") if local else "", + ] + ) + if chrome: + return "chrome" + + edge = _win_find_exe( + [ + os.path.join(pfx86, "Microsoft", "Edge", "Application", "msedge.exe"), + os.path.join(pf, "Microsoft", "Edge", "Application", "msedge.exe"), + os.path.join(local, "Microsoft", "Edge", "Application", "msedge.exe") if local else "", + ] + ) + if edge: + return "msedge" + + return None + + +def _print_browser_install_hint(): + print("❌ 未检测到 Google Chrome 或 Microsoft Edge,请先安装后再登录:") + print(" • Chrome: https://www.google.com/chrome/") + print(" • Edge: https://www.microsoft.com/zh-cn/edge") + + +def _login_timeout_seconds(): + try: + return max(60, int((os.getenv("JIANGCHANG_LOGIN_TIMEOUT_SECONDS") or "300").strip())) + except ValueError: + return 300 + + +def _verify_login(profile_dir: str, platform: str, start_url: str, channel: str) -> bool: + try: + from playwright.sync_api import sync_playwright + except ImportError: + print("⚠️ 未安装 playwright,跳过登录校验(请: pip install playwright && playwright install)") + return False + + try: + with sync_playwright() as p: + ctx = p.chromium.launch_persistent_context( + user_data_dir=profile_dir, + headless=True, + channel=channel, + args=["--disable-blink-features=AutomationControlled"], + ) + 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 + finally: + ctx.close() + except Exception: + return False def cmd_login(account_id): - """首次登录:打开浏览器,用户手动登录,登录态自动保存到Profile目录""" target = get_account_by_id(account_id) - if not target: print("ERROR:ACCOUNT_NOT_FOUND") return + channel = resolve_chromium_channel() + if not channel: + _print_browser_install_hint() + return + + try: + import playwright # noqa: F401 + except ImportError: + print("ERROR:需要 playwright:pip install playwright && playwright install chromium") + return + profile_dir = target["profile_dir"] os.makedirs(profile_dir, exist_ok=True) - url = PLATFORM_URLS.get(target["platform"], "https://www.google.com") + 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() - print(f"正在为账号 [{target['name']}] 打开浏览器...") - print(f"请在浏览器中完成登录,登录后直接关闭浏览器窗口即可。") - print(f"登录态将自动保存到:{profile_dir}") + browser_name = "Google Chrome" if channel == "chrome" else "Microsoft Edge" + print(f"正在为账号 [{target['name']}] 打开 {browser_name} …") + print(f"访问地址:{url}") + print("请在窗口内完成登录(扫码/输入密码)。到达等待时间后将自动关闭窗口,您也可提前手动关闭。") + print(f"最长等待:{timeout_sec} 秒") - # 用Playwright打开持久化浏览器,等待用户手动登录 - login_script = f""" -import asyncio -from playwright.async_api import async_playwright -async def main(): - async with async_playwright() as p: - browser = await p.chromium.launch_persistent_context( - user_data_dir=r"{profile_dir}", - headless=False, - channel="chrome", # 【修改处】也加上这行 - no_viewport=True, # 确保浏览器内容视口也最大化 - args=["--start-maximized"] - ) - page = browser.pages[0] if browser.pages else await browser.new_page() - await page.goto("{url}") - print("浏览器已打开,请完成登录后关闭窗口...") - # 移除自动超时,无限等待直到用户扫码完手动关闭 - await browser.wait_for_event("close", timeout=0) - print("登录态已保存!") -asyncio.run(main()) + # Playwright 同步 API 宜在独立子进程中跑浏览器,避免线程与事件循环问题 + runner = r"""import json, sys +from playwright.sync_api import sync_playwright +with open(sys.argv[1], encoding="utf-8") as f: + c = json.load(f) +with sync_playwright() as p: + ctx = p.chromium.launch_persistent_context( + user_data_dir=c["profile_dir"], + headless=False, + channel=c["channel"], + no_viewport=True, + args=["--start-maximized"], + ) + 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 + try: + ctx.close() + except Exception: + pass + except Exception as e: + print(e, file=sys.stderr) + raise """ - import tempfile - with tempfile.NamedTemporaryFile(mode='w', suffix='.py', - delete=False, encoding='utf-8') as f: - f.write(login_script) - tmp_path = f.name + cfg = { + "channel": channel, + "profile_dir": profile_dir, + "url": url, + "timeout_sec": timeout_sec, + } + with tempfile.NamedTemporaryFile( + mode="w", suffix=".json", delete=False, encoding="utf-8" + ) as jf: + json.dump(cfg, jf, ensure_ascii=False) + cfg_path = jf.name + with tempfile.NamedTemporaryFile( + mode="w", suffix=".py", delete=False, encoding="utf-8" + ) as pf: + pf.write(runner) + py_path = pf.name + try: + r = subprocess.run( + [sys.executable, py_path, cfg_path], + timeout=timeout_sec + 180, + ) + if r.returncode != 0: + print("⚠️ 浏览器进程异常退出,仍将尝试检测登录状态") + except subprocess.TimeoutExpired: + print("⚠️ 等待浏览器超时,仍将尝试检测登录状态") + finally: + try: + os.unlink(cfg_path) + except OSError: + pass + try: + os.unlink(py_path) + except OSError: + pass - exit_code = os.system(f'python "{tmp_path}"') - os.unlink(tmp_path) - if exit_code == 0: - _mark_login_success(account_id) - print("✅ 登录状态已标记为已登录") + time.sleep(1.5) + print("正在检测登录状态…") + ok = _verify_login(profile_dir, platform, url, channel) + _mark_login_status(target["id"], ok) + if ok: + print("✅ 检测到已登录,状态已写入数据库") else: - print("⚠️ 浏览器登录流程异常退出,登录状态未更新") + print("⚠️ 未检测到有效登录,状态为未登录。请关闭其他占用该用户目录的浏览器后重试,或延长 JIANGCHANG_LOGIN_TIMEOUT_SECONDS 后再登录。") -def _mark_login_success(account_id): +def _mark_login_status(account_id, success: bool): now = datetime.now().isoformat(timespec="seconds") conn = get_conn() try: cur = conn.cursor() - cur.execute( - "UPDATE accounts SET login_status = 1, last_login_at = ?, updated_at = ? WHERE id = ?", - (now, now, account_id), - ) + if success: + cur.execute( + "UPDATE accounts SET login_status = 1, last_login_at = ?, updated_at = ? WHERE id = ?", + (now, now, account_id), + ) + else: + cur.execute( + "UPDATE accounts SET login_status = 0, updated_at = ? WHERE id = ?", + (now, account_id), + ) conn.commit() finally: conn.close() + if __name__ == "__main__": if len(sys.argv) < 2: - print("用法:python account.py add [phone] [profile_dir] | list [platform] | get | login ") + print( + "用法:python account.py add [phone] | list [platform] | get | login " + ) sys.exit(1) cmd = sys.argv[1] - # 兼容写反顺序:python account.py sohu list if len(sys.argv) >= 3 and sys.argv[2] == "list" and cmd in PLATFORM_URLS: cmd_list(cmd) elif cmd in PLATFORM_URLS and len(sys.argv) == 2: cmd_list(cmd) - elif cmd == "add" and len(sys.argv) >= 5: - cmd_add( - sys.argv[2], - sys.argv[3], - sys.argv[4], - sys.argv[5] if len(sys.argv) >= 6 else "", - sys.argv[6] if len(sys.argv) >= 7 else "", - ) + elif cmd == "add" and len(sys.argv) >= 3: + plat = sys.argv[2].lower() + phone_arg = sys.argv[3] if len(sys.argv) >= 4 else "" + cmd_add(plat, phone_arg) elif cmd == "list": - # 默认不传 platform 时打出 all cmd_list(sys.argv[2] if len(sys.argv) >= 3 else "all") elif cmd == "get" and len(sys.argv) >= 3: cmd_get(sys.argv[2]) @@ -303,4 +542,4 @@ if __name__ == "__main__": cmd_login(sys.argv[2]) else: print("参数错误") - sys.exit(1) \ No newline at end of file + sys.exit(1)