更新
This commit is contained in:
@@ -14,9 +14,21 @@ def is_frontier_ws_url(url: str) -> bool:
|
||||
真实抓包里 host 可能是 frontier-im.douyin.com,也可能是
|
||||
frontierNN-normal.zijieapi.com 这类内部别名,二者都要认。
|
||||
"""
|
||||
if not url or "token=" not in url:
|
||||
if not url:
|
||||
return False
|
||||
return "frontier-im.douyin.com" in url or ("frontier" in url and "zijieapi.com" in url)
|
||||
parsed = urlparse(url)
|
||||
host = (parsed.hostname or "").lower()
|
||||
query = parse_qs(parsed.query)
|
||||
fpid = (query.get("fpid") or [""])[0]
|
||||
legacy = host == "frontier-im.douyin.com" and "token" in query and fpid == "9"
|
||||
browser_frontier = (
|
||||
"frontier" in host
|
||||
and host.endswith("zijieapi.com")
|
||||
and "access_key" in query
|
||||
and "device_id" in query
|
||||
and fpid == "9"
|
||||
)
|
||||
return legacy or browser_frontier
|
||||
|
||||
|
||||
@dataclass
|
||||
@@ -34,7 +46,8 @@ class DouyinImSession:
|
||||
keys_str: str = ""
|
||||
web_protect_str: str = ""
|
||||
my_uid: int = 0
|
||||
# my_uid 是否已用 query/user 接口核验过(采集端推断的 my_uid 可能取错 tea_cache id)
|
||||
# my_uid 是否已由账号资料 UID 等可靠来源核验。query/user 的 user_uid
|
||||
# 不是所有账号的 IM UID,不能据此覆盖已采集/已同步的 my_uid。
|
||||
uid_verified: bool = False
|
||||
conv_meta: dict = field(default_factory=dict)
|
||||
# 方案 A:直接复用浏览器抓到的真实 frontier 连接凭证(绕开我们自己推导 token/access_key 不准的问题)
|
||||
@@ -96,6 +109,14 @@ class DouyinImSession:
|
||||
my_uid = _as_uid(extra.get("my_uid")) or _as_uid(data.get("my_uid"))
|
||||
user_agent = str(extra.get("user_agent") or data.get("user_agent") or "").strip()
|
||||
|
||||
# 先整段扫描 localStorage,收集字段(避免遍历顺序导致取值不确定)。
|
||||
# 关键背景:新版抖音 web 端 __tea_cache_tokens_6383 的 user_unique_id 实际存的是
|
||||
# web_id(如 7678646545793812008),并非账号 UID;而 web_runtime_security_uid
|
||||
# 才是账号真实 UID(如 2609567359568155)。混合登录态下若把 tea 的 user_unique_id
|
||||
# 当 my_uid,会导致 device_id != my_uid,IM 发送被安全网关 KICK。
|
||||
ls_sec_uid = "" # web_runtime_security_uid(最可靠的账号 UID 来源)
|
||||
ls_web_id = "" # 第一个 tea 条目的 web_id/user_unique_id
|
||||
ls_tea_pairs = [] # [(user_unique_id, web_id), ...] 按出现顺序
|
||||
if not device_id or not web_id or not keys_str or not web_protect_str or not my_uid:
|
||||
for origin in data.get("origins", []):
|
||||
for entry in origin.get("localStorage", []):
|
||||
@@ -107,27 +128,42 @@ class DouyinImSession:
|
||||
keys_str = value
|
||||
if name == "security-sdk/s_sdk_sign_data_key/web_protect" and not web_protect_str:
|
||||
web_protect_str = value
|
||||
if "tea_cache_tokens" in name and not web_id:
|
||||
if "tea_cache_tokens" in name:
|
||||
try:
|
||||
parsed = json.loads(value)
|
||||
web_id = str(
|
||||
parsed.get("web_id")
|
||||
or parsed.get("user_unique_id")
|
||||
or ""
|
||||
)
|
||||
except Exception:
|
||||
pass
|
||||
if name == "web_runtime_security_uid" and not device_id:
|
||||
if str(value or "").isdigit():
|
||||
device_id = value
|
||||
if "tea_cache_tokens" in name and not my_uid:
|
||||
try:
|
||||
parsed = json.loads(value)
|
||||
uid = parsed.get("user_unique_id")
|
||||
if uid and str(uid).isdigit():
|
||||
my_uid = int(uid)
|
||||
if isinstance(parsed, dict):
|
||||
wid = str(parsed.get("web_id") or "")
|
||||
uid = str(parsed.get("user_unique_id") or "")
|
||||
if not ls_web_id:
|
||||
ls_web_id = wid or uid
|
||||
ls_tea_pairs.append((uid, wid))
|
||||
except Exception:
|
||||
pass
|
||||
if name == "web_runtime_security_uid":
|
||||
v = str(value or "")
|
||||
if v.isdigit() and not ls_sec_uid:
|
||||
ls_sec_uid = v
|
||||
|
||||
# web_id:extra 显式值 > localStorage tea
|
||||
if not web_id:
|
||||
web_id = ls_web_id
|
||||
|
||||
# my_uid 优先级:extra/顶层 > web_runtime_security_uid(真实账号 UID)>
|
||||
# tea 的 user_unique_id(仅当与自身 web_id 不同才可信,避免误取 web_id)
|
||||
if not my_uid and ls_sec_uid:
|
||||
my_uid = int(ls_sec_uid)
|
||||
if not my_uid:
|
||||
for uid, wid in ls_tea_pairs:
|
||||
if uid.isdigit() and not (wid and uid == wid):
|
||||
my_uid = int(uid)
|
||||
break
|
||||
|
||||
# device_id 优先级:extra/cookies > web_runtime_security_uid(与账号 UID 绑定)
|
||||
if not device_id:
|
||||
if ls_sec_uid:
|
||||
device_id = ls_sec_uid
|
||||
elif my_uid:
|
||||
device_id = str(my_uid)
|
||||
|
||||
if not my_uid:
|
||||
for item in data.get("cookies", []):
|
||||
@@ -144,6 +180,14 @@ class DouyinImSession:
|
||||
elif not device_id and web_id:
|
||||
device_id = web_id
|
||||
|
||||
# 最终一致性收敛:protobuf/frontier 的 device_id 优先取 session.device_id
|
||||
# (resolve_proto_device_id),若凭证里残留旧设备号(如 www 域
|
||||
# web_runtime_security_uid),发送时 device_id != my_uid 会被安全网关
|
||||
# 判为设备指纹异常 -> decision=KICK。my_uid 此时已是权威账号 UID,
|
||||
# 不一致时以 my_uid 收敛 device_id。
|
||||
if my_uid and device_id and str(device_id) != str(my_uid):
|
||||
device_id = str(my_uid)
|
||||
|
||||
ws_urls = list(extra.get("ws_urls") or [])
|
||||
|
||||
# 方案 A:凭证采集工具可携带浏览器抓到的真实 frontier 连接(含 token/sdk_cert/ts_sign)。
|
||||
@@ -193,6 +237,7 @@ class DouyinImSession:
|
||||
"keys_str": self.keys_str,
|
||||
"web_protect_str": self.web_protect_str,
|
||||
"my_uid": self.my_uid,
|
||||
"uid_verified": self.uid_verified,
|
||||
"conv_meta": self.conv_meta,
|
||||
"sdk_cert": self.sdk_cert,
|
||||
"frontier_ts_sign": self.frontier_ts_sign,
|
||||
@@ -212,6 +257,7 @@ class DouyinImSession:
|
||||
keys_str=str(data.get("keys_str") or ""),
|
||||
web_protect_str=str(data.get("web_protect_str") or ""),
|
||||
my_uid=int(data.get("my_uid") or 0),
|
||||
uid_verified=bool(data.get("uid_verified", False)),
|
||||
conv_meta=dict(data.get("conv_meta") or {}),
|
||||
sdk_cert=str(data.get("sdk_cert") or ""),
|
||||
frontier_ts_sign=str(data.get("frontier_ts_sign") or ""),
|
||||
|
||||
Reference in New Issue
Block a user