Files
douyin-pc/src/get_current_user.py
T

108 lines
3.8 KiB
Python

#!/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())