From ba1c21c0b649e5ae329ee540fd6fc76d4a36d477 Mon Sep 17 00:00:00 2001 From: chendelian <116870791@qq.com> Date: Tue, 31 Mar 2026 18:37:04 +0800 Subject: [PATCH] chore: auto release commit (2026-03-31 18:37:04) --- SKILL.md | 12 +- scripts/__pycache__/account.cpython-312.pyc | Bin 25173 -> 32840 bytes scripts/account.py | 214 ++++++++++++++++++-- 3 files changed, 206 insertions(+), 20 deletions(-) diff --git a/SKILL.md b/SKILL.md index a3225d7..c821d85 100644 --- a/SKILL.md +++ b/SKILL.md @@ -1,7 +1,7 @@ --- name: 账号管理 description: 多平台多账号管理。管理各平台账号与Chrome Profile的对应关系,供publisher类Skill调用获取账号信息。 -version: 1.0.2 +version: 1.0.3 author: 深圳匠厂科技有限公司 metadata: openclaw: @@ -29,6 +29,16 @@ python3 {baseDir}/scripts/account.py add sohu python3 {baseDir}/scripts/account.py add sohu 189xxxxxxxx ``` +### 仅打开浏览器核对是否已登录(不写数据库) +```bash +python3 {baseDir}/scripts/account.py open +``` + +### 登录并自动检测、写入数据库 +```bash +python3 {baseDir}/scripts/account.py login +``` + ### 列出某平台所有账号 ```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 4aef0a751a512ab5f334c1bbe02a6901ed7231cc..2acca904c278b3523adfb94c8b353533889da4d3 100644 GIT binary patch delta 10870 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 delta 5200 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 bool: + """ + 通用:是否「明显仍在登录/认证页」。 + 用路径段判断,避免 mpfe/v4/login 因含 mpfe 被误判为已登录。 + """ + if not page_url or not page_url.strip(): + return True + try: + p = urlparse(page_url.strip()) + except Exception: + return True + host = (p.netloc or "").lower() + path = (p.path or "").lower() + query = (p.query or "").lower() + if "passport" in host or "login." in host or "signin." in host: + return True + seg = [s for s in path.split("/") if s] + login_segments = ( + "login", + "signin", + "signup", + "register", + "authorize", + "authentication", + ) + if any(s in login_segments for s in seg): + return True + if re.search(r"/(login|signin)(/|$|\?)", path): + return True + if "redirect_uri" in query and ("login" in query or "passport" in query): + return True + return False + + +def _sohu_url_logged_in(page_url: str) -> bool: + """ + 搜狐:仅白名单路径视为已进入后台(不用裸 mpfe,避免 /mpfe/v4/login 误判)。 + """ + if _definitely_logged_out_url(page_url): + return False + try: + path = (urlparse(page_url).path or "").lower() + except Exception: + return False + if "contentmanagement" in path: + return True + if "/mpfe/v4/main" in path or path.rstrip("/").endswith("/main"): + return True + if "/mpfe/v4/publish" in path or "/mpfe/v4/data" in path: + return True + return False + + +def _sohu_page_shows_login_form(page) -> bool: + """搜狐登录页常见密码框;可见则认为未登录(与 URL 交叉验证)。""" + try: + loc = page.locator('input[type="password"]') + n = loc.count() + if n == 0: + return False + return loc.first.is_visible(timeout=800) + except Exception: + return False + + def _url_looks_logged_in(platform: str, page_url: str) -> bool: - """根据当前 URL 判断是否已进入后台(与交互窗口、无头校验共用逻辑)。""" - u = (page_url or "").lower() - if not u: + """是否判定为已登录:搜狐用白名单+负例;其它平台仅用「非明确登录页」宽松策略。""" + if not page_url or not page_url.strip(): 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 _sohu_url_logged_in(page_url) + if _definitely_logged_out_url(page_url): return False - if "login" in u or "signin" in u or "passport" in u: + u = page_url.lower() + if "passport" in u or "signin" in u: return False return True +def _verify_logged_in_page(platform: str, page) -> bool: + """结合 URL 与页面(搜狐加验登录表单)。""" + url = page.url or "" + if platform == "sohu": + if _sohu_page_shows_login_form(page): + return False + return _sohu_url_logged_in(url) + return _url_looks_logged_in(platform, url) + + def _verify_login(profile_dir: str, platform: str, start_url: str, channel: str) -> bool: try: from playwright.sync_api import sync_playwright @@ -354,12 +428,11 @@ 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) - # 无头启动后可能仍在跳转,短轮询几次再判定 for _ in range(20): - if _url_looks_logged_in(platform, page.url): + if _verify_logged_in_page(platform, page): return True time.sleep(1.0) - return _url_looks_logged_in(platform, page.url) + return _verify_logged_in_page(platform, page) finally: ctx.close() except Exception: @@ -398,20 +471,43 @@ def cmd_login(account_id): ) print(f"轮询间隔约 {poll_sec} 秒;全程最长 {timeout_sec} 秒。") - # 子进程:轮询 URL,已登录则立即关窗;勿仅用 wait_for_event(close),否则用户不关窗口会卡满整段超时。 + # 子进程:搜狐轮询 URL+页面;勿用「含 mpfe 即登录」,/mpfe/v4/login 会误判。 runner = r"""import json, sys, time +from urllib.parse import urlparse from playwright.sync_api import sync_playwright -def looks_sohu(u): - u = (u or "").lower() +def sohu_logged_in(url): + u = (url or "").strip() if not u: return False - if "passport" in u or "/login" in u: + try: + p = urlparse(u) + except Exception: return False - if "mpfe" in u or "contentmanagement" in u: + path = (p.path or "").lower() + host = (p.netloc or "").lower() + if "passport" in host: + return False + segs = [s for s in path.split("/") if s] + if any(s in ("login", "signin", "signup", "register", "authorize") for s in segs): + return False + if "contentmanagement" in path: + return True + if "/mpfe/v4/main" in path or path.rstrip("/").endswith("/main"): + return True + if "/mpfe/v4/publish" in path or "/mpfe/v4/data" in path: return True return False +def sohu_not_login_page(page): + try: + loc = page.locator('input[type="password"]') + if loc.count() == 0: + return False + return loc.first.is_visible(timeout=600) + except Exception: + return False + with open(sys.argv[1], encoding="utf-8") as f: c = json.load(f) platform = (c.get("platform") or "").lower() @@ -431,7 +527,7 @@ with sync_playwright() as p: if platform == "sohu": while time.time() < deadline: try: - if looks_sohu(page.url): + if sohu_logged_in(page.url) and not sohu_not_login_page(page): break except Exception: break @@ -498,6 +594,84 @@ with sync_playwright() as p: print("⚠️ 未检测到有效登录,状态为未登录。请关闭其他占用该用户目录的浏览器后重试,或延长 JIANGCHANG_LOGIN_TIMEOUT_SECONDS 后再登录。") +def cmd_open(account_id): + """打开该账号的持久化浏览器,仅用于肉眼确认是否已登录;不写数据库。""" + 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 = (target.get("url") or "").strip() or PLATFORM_URLS.get( + target["platform"], "https://www.google.com" + ) + browser_name = "Google Chrome" if channel == "chrome" else "Microsoft Edge" + print(f"正在打开 [{target['name']}] 的 {browser_name}(仅查看,不写入数据库)") + print(f"地址:{url}") + print("请在本窗口中自行确认登录态。关闭浏览器后命令结束。") + print("需要自动检测并写入数据库时,请执行:python account.py login ") + + runner_open = 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) + ctx.wait_for_event("close", timeout=86400000) + except Exception as e: + print(e, file=sys.stderr) + raise + finally: + try: + ctx.close() + except Exception: + pass +""" + cfg = {"channel": channel, "profile_dir": profile_dir, "url": url} + 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_open) + py_path = pf.name + try: + subprocess.run([sys.executable, py_path, cfg_path]) + finally: + try: + os.unlink(cfg_path) + except OSError: + pass + try: + os.unlink(py_path) + except OSError: + pass + + def _mark_login_status(account_id, success: bool): now = _now_unix() conn = get_conn() @@ -521,7 +695,7 @@ def _mark_login_status(account_id, success: bool): if __name__ == "__main__": if len(sys.argv) < 2: print( - "用法:python account.py add [phone] | list [platform] | get | login " + "用法:python account.py add [phone] | list [platform] | get | open | login " ) sys.exit(1) @@ -538,6 +712,8 @@ if __name__ == "__main__": 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]) + elif cmd == "open" and len(sys.argv) >= 3: + cmd_open(sys.argv[2]) elif cmd == "login" and len(sys.argv) >= 3: cmd_login(sys.argv[2]) else: