Files
douyin/douyin-im-collector/credential_normalize.py
T
2026-07-17 09:24:47 +08:00

101 lines
3.4 KiB
Python

"""IM 凭证规范化(独立版,不依赖客服系统 backend)。"""
from __future__ import annotations
import json
import re
from urllib.parse import parse_qs, unquote, urlparse
def _parse_tea_from_ls(origins: list) -> tuple[str, str]:
my_uid = ""
web_id = ""
ordered = sorted(origins or [], key=lambda o: 0 if "www.douyin.com" in (o.get("origin") or "") else 1)
for origin in ordered:
for entry in origin.get("localStorage") or []:
name = entry.get("name") or ""
if "tea_cache" not in name.lower():
continue
try:
parsed = json.loads(entry.get("value") or "{}")
except Exception:
continue
uid = str(parsed.get("user_unique_id") or "").strip()
wid = str(parsed.get("web_id") or "").strip()
if uid and wid and uid == wid and len(uid) > 12:
continue
if uid and uid.isdigit() and not my_uid:
my_uid = uid
if wid and wid.isdigit() and not web_id:
web_id = wid
if my_uid and web_id:
return my_uid, web_id or my_uid
return my_uid, web_id or my_uid
def _ws_device_id(url: str) -> str:
m = re.search(r"[?&]device_id=([^&\s]+)", url or "")
return unquote(m.group(1)) if m else ""
def normalize_storage_state_for_im(data: dict) -> dict:
if not isinstance(data, dict) or not isinstance(data.get("cookies"), list):
return data
data = dict(data)
origins = data.get("origins") or []
tea_uid, _tea_web_id = _parse_tea_from_ls(origins)
ws_url = str(data.get("frontier_ws_url") or "")
is_creator_ws = "aid=2906" in ws_url and "sdk_cert=" in ws_url
if is_creator_ws:
ws_dev = _ws_device_id(ws_url)
if ws_dev and ws_dev.isdigit():
data["my_uid"] = int(ws_dev)
my_uid = ws_dev
else:
my_uid = str(data.get("my_uid") or tea_uid or "")
if my_uid.isdigit():
data["my_uid"] = int(my_uid)
else:
my_uid = ""
elif tea_uid:
data["my_uid"] = int(tea_uid)
my_uid = tea_uid
else:
my_uid = str(data.get("my_uid") or "")
if my_uid.isdigit():
data["my_uid"] = int(my_uid)
else:
my_uid = ""
if ws_url and my_uid and not is_creator_ws:
ws_dev = _ws_device_id(ws_url)
token = ""
try:
token = parse_qs(urlparse(ws_url).query).get("token", [""])[0]
except Exception:
pass
looks_built = bool(token) and len(token) < 40
if ws_dev and ws_dev.isdigit() and ws_dev != my_uid and looks_built:
data.pop("frontier_ws_url", None)
data.pop("sdk_cert", None)
data.pop("ts_sign", None)
data.pop("frontier_ws_built", None)
for origin in origins:
ls = origin.get("localStorage") or []
fixed = False
for entry in ls:
if entry.get("name") == "web_runtime_security_uid":
fixed = True
val = str(entry.get("value") or "")
if not val.isdigit() and my_uid:
entry["value"] = my_uid
break
if not fixed and my_uid:
ls.append({"name": "web_runtime_security_uid", "value": my_uid})
origin["localStorage"] = ls
data["origins"] = origins
return data