#!/usr/bin/env python3 """通过当前登录的抖音浏览器会话调用个人信息接口并保存 JSON。""" import argparse import json import sys from datetime import datetime from pathlib import Path from typing import Any, cast from account_log import visible from cdp_explicit import ( # pyright: ignore[reportMissingImports] add_cdp_args, configure, evaluate_script, ) API_PATH = "/aweme/v1/web/user/profile/self/?device_platform=webapp&aid=6383" OUTPUT = Path(__file__).with_name("current_user.json") BROWSER_SCRIPT = f"""(async () => {{ if (location.origin !== 'https://www.douyin.com') return JSON.stringify({{status: 0, body: '{{}}'}}); const response = await fetch({json.dumps(API_PATH)}, {{credentials: "include", signal: AbortSignal.timeout(15000)}}); return JSON.stringify({{status: response.status, body: await response.text()}}); }})()""" def get_user_from_browser() -> dict: """在当前抖音页面内使用登录态调用 self profile API。""" completed = evaluate_script(BROWSER_SCRIPT) if completed.returncode: raise RuntimeError("CDP 调用失败") return parse_user_response(completed.stdout) def parse_user_response(value) -> dict: """Shared self-profile parsing for CLI and asynchronous desktop enrollment.""" try: response = json.loads(value) if isinstance(value, str) else value payload = json.loads(response["body"]) if not isinstance(payload, dict): raise TypeError("invalid profile payload") except (IndexError, KeyError, TypeError, json.JSONDecodeError) as exc: raise RuntimeError("无法解析接口响应") from exc if ( response.get("status") != 200 or payload.get("status_code") != 0 or not payload.get("user") ): raise RuntimeError("接口返回异常,请手动检查登录态") user = payload["user"] if not isinstance(user, dict): raise RuntimeError("个人信息无效,请手动检查登录状态") uid = user.get("uid") if ( type(uid) not in (str, int) or not str(uid).isascii() or not str(uid).isdecimal() or str(uid).startswith("0") or len(str(uid)) > 20 ): raise RuntimeError("尚未登录,请在打开的浏览器中手动登录") return {**user, "uid": str(uid)} def business_user(user: dict) -> dict: """Return full platform business data while retaining credential protection.""" result = cast(dict[str, Any], visible(user)) avatar = result.get("avatar_300x300") or result.get("avatar_thumb") or {} avatars = avatar.get("url_list") if isinstance(avatar, dict) else [] result["douyin_id"] = result.get("short_id") or result.get("unique_id") or "" result["avatar_url"] = avatars[0] if avatars else result.get("avatar_url") or "" return result def main() -> int: parser = argparse.ArgumentParser(description=__doc__) parser.add_argument("--output", type=Path, default=Path("current_user.json")) add_cdp_args(parser) args = parser.parse_args() configure(args) try: user = business_user(get_user_from_browser()) args.output.write_text( json.dumps( {"fetched_at": datetime.now().astimezone().isoformat(), "user": user}, ensure_ascii=False, indent=2, ) + "\n", encoding="utf-8", ) except (OSError, RuntimeError) as exc: print(f"获取失败:{exc}", file=sys.stderr) print( "请核对显式 CDP 地址和期望 UID;登录失效请手动登录。", file=sys.stderr, ) return 1 print(f"已保存:{args.output}") print(json.dumps(user, ensure_ascii=False, indent=2)) return 0 if __name__ == "__main__": raise SystemExit(main())