接入匿名设备态协议补齐Cookie字段并放宽保存

This commit is contained in:
yml2213
2026-09-01 19:29:56 +08:00
parent 8447ca82ed
commit 727c32a5f5
6 changed files with 113 additions and 23 deletions
+32 -9
View File
@@ -475,6 +475,30 @@ class HuyaAppPasswordLogin:
present = {key for key, value in cookie_pairs(cookie) if value}
return tuple(key for key in WEB_DEVICE_COOKIE_KEYS if key not in present)
@staticmethod
def _supplement_anonymous_device(
device_info: Mapping[str, object],
*,
proxies: Mapping[str, str] | None = None,
) -> dict:
"""用匿名设备态协议尽力补齐 4 个字段(udb_deviceid/guiddata/biztoken/anouid)。
只 ``setdefault``,不覆盖 hydevice 或显式传入的真实值;获取失败只
告警,不阻断登录(字段不全仍保存,风控风险由调用方承担)。
"""
from .anon_device import AnonymousDeviceError, fetch_anonymous_device_state
merged = dict(device_info)
try:
anon = fetch_anonymous_device_state(proxies=proxies)
except AnonymousDeviceError as exc:
logger.warning("[huya-app] 匿名设备态获取失败(不阻断登录): {}", exc)
return merged
for key, value in anon.items():
merged.setdefault(key, value)
logger.info("[huya-app] 匿名设备态协议补齐字段: {}", ", ".join(sorted(anon)))
return merged
@staticmethod
def _merge_web_cookie(
session: requests.Session,
@@ -667,6 +691,11 @@ class HuyaAppPasswordLogin:
"udb_anobiztoken", "udb_deviceid",
}:
merge_device_info.setdefault(key, value)
# 匿名设备态协议补齐(middle + anonymousLogin 签发 4 字段),
# 只 setdefault 不覆盖真实值;失败不阻断。
merge_device_info = self._supplement_anonymous_device(
merge_device_info, proxies=self.proxies
)
logger.info("[huya-app] cred 已获取,开始二维码绑定流程")
pc = QrRole(pc=True, sdid=sdid, proxies=self.proxies)
ph = QrRole(
@@ -793,15 +822,9 @@ class HuyaAppPasswordLogin:
missing_web = self._missing_web_device_cookie(cookie_str)
if missing_web:
missing_text = ", ".join(missing_web)
logger.error(
f"[huya-app] 账号 {acct} 登录凭据有效,但网页设备态不完整,缺少: {missing_text}"
)
return HuyaLoginResult(
success=False,
message=f"COOKIE_INCOMPLETE: 缺少网页设备态({missing_text}),请在真实虎牙网页/WebView完成一次访问后重试",
code="COOKIE_INCOMPLETE",
sdid=sdid,
context=pc.context,
logger.warning(
f"[huya-app] 账号 {acct} 登录凭据有效,但网页设备态不完整,"
f"缺少: {missing_text}(字段不全仍保存,风控风险自担)"
)
if "udb_cred" not in cookie_str or "yyuid" not in cookie_str:
return HuyaLoginResult(
@@ -56,7 +56,11 @@
因此 App 的 `/web/cookie/verify` 不能兑换出完整网页 CK。网页设备字段来自浏览器此前
持久化的 Cookie/Storage 和页面脚本运行态;当前 Python App 登录链不应声称能凭空补齐它们,
也不在脚本中外挂浏览器。缺失时返回 `COOKIE_INCOMPLETE`,精英宝典任务不再回退 HTTP。
也不在脚本中外挂浏览器。2026-09-01 起接入匿名设备态协议
`core/huya/anon_device.py`middle + anonymousLogin)补 `udb_deviceid`/
`udb_guiddata`/`udb_anobiztoken`/`udb_anouid` 四个字段;其余脚本字段(`guid`/
`game_did`/`_qimei_uuid42`/`__yamid_new`)缺失时不再拒绝登录,改为警告并保存
(风控风险自担);精英宝典任务不再回退 HTTP。
### 运行时复核(2026-09-01
@@ -67,7 +71,8 @@
- `__SDID__``__HDID__` 正常返回;
- Cookie 桥只观察到脚本自身的 `udb_appid`/临时 `sdid`,没有生成 `guid`、QIMEI、
`udb_guiddata``udb_deviceid``udb_anobiztoken`
- 因此这次改动不会改变 `COOKIE_INCOMPLETE` 判定,也不会用随机值补齐字段
- 因此这次改动此前不会改变 `COOKIE_INCOMPLETE` 判定,也不会用随机值补齐字段
2026-09-01 登录链接入匿名设备态协议后可补齐 4 个服务端签发字段(见下节)。
从线上下载的 `hydevice-prod-1.2.51-min.js` 在现有 Node 仿真环境中还依赖未实现的
浏览器 API(首个错误为 `window[t(...)] is not a function`),没有将其替换进生产链。
@@ -82,8 +87,8 @@
成功响应;`df/collect` 只下发 `sdid``hdid`
- `7.5-短信登录-滑块.har``web/cookie/verify` 前后的请求也没有新增这 7 个字段。
因此当前日志中的缺失列表是协议事实,不是 Cookie 合并阶段丢失;下一步只有取得
同一网页会话的真实持久化设备态,或找到服务端正式签发接口,才能继续补齐
因此当前日志中的缺失列表是协议事实,不是 Cookie 合并阶段丢失;服务端正式签发
接口已找到并接入登录链(middle + anonymousLogin,见“匿名流程解密”节)
### 真实浏览器实测(2026-09-01,未登录)
+10 -5
View File
@@ -17,8 +17,10 @@
旧登录结果中的 Cookie 缺少网页设备态,已不再作为成功登录或任务链路使用。
纯 App/HTTP 登录不会由 `web/cookie/verify` 下发网页设备 Cookie。浏览器只用于
人工调试和抓取来源,未集成到脚本;当前 App 登录缺少这些字段时直接返回
`COOKIE_INCOMPLETE`,不会伪造或自动启动浏览器。
人工调试和抓取来源,未集成到脚本;登录链已接入匿名设备态协议
`core/huya/anon_device.py`)补 `udb_deviceid`/`udb_guiddata`/`udb_anobiztoken`/
`udb_anouid`,剩余脚本字段(`guid`/`game_did`/`_qimei_uuid42`/`__yamid_new`
缺失时警告并保存,不伪造、不启动浏览器。
## 推荐联调顺序
@@ -38,16 +40,19 @@
执行器只检查字段是否存在,不记录字段值:
```text
必要字段:udb_cred、udb_biztoken、udb_uid、yyuid、sdid、guid、udb_guiddata、udb_deviceid、game_did、_qimei_uuid42、udb_anobiztoken、__yamid_new
必要字段:udb_cred、udb_biztoken、udb_uid、yyuid、sdid、udb_deviceid、udb_guiddata、udb_anobiztoken
```
- 字段齐全:尝试活动或商城 WSS。
- 缺少字段:App 登录返回 `COOKIE_INCOMPLETE`任务日志列出缺失字段并停止,不创建 HTTP 客户端,例如:
- 缺少字段:任务日志列出缺失字段并停止,不创建 HTTP 客户端,例如:
```text
Cookie 缺少网页设备态(guid, udb_guiddata),已停止活动 HTTP 回退
Cookie 缺少网页设备态(udb_deviceid, udb_guiddata, udb_anobiztoken),已停止活动 HTTP 回退
```
`guid`/`game_did`/`_qimei_uuid42`/`__yamid_new` 四个脚本字段无服务端签发接口,
登录时缺失只警告不阻断保存,WSS 判定也不再要求(风控风险自担)。
- WSS 握手、`wsLaunch``getConfig` 或业务 RPC 任一步失败:任务直接失败并记录具体阶段,便于修复 Cookie 或协议参数。
## WSS 专项验收
+40
View File
@@ -147,6 +147,46 @@ def test_app_login_cookie_contains_web_session_shape():
assert expected <= keys
def test_supplement_anonymous_device_adds_four_fields(monkeypatch):
anon = {
"udb_deviceid": "w_1147246591246680065",
"udb_guiddata": "a" * 32,
"udb_anobiztoken": "B" * 344,
"udb_anouid": "1471255709907",
}
monkeypatch.setattr(
"core.huya.anon_device.fetch_anonymous_device_state", lambda **kw: anon
)
merged = HuyaAppPasswordLogin._supplement_anonymous_device({"guid": "g" * 32})
assert merged["udb_deviceid"] == anon["udb_deviceid"]
assert merged["udb_guiddata"] == anon["udb_guiddata"]
assert merged["udb_anobiztoken"] == anon["udb_anobiztoken"]
assert merged["udb_anouid"] == anon["udb_anouid"]
assert merged["guid"] == "g" * 32 # 已有显式值不被覆盖
def test_supplement_anonymous_device_keeps_explicit_values(monkeypatch):
anon = {"udb_deviceid": "w_9999999999999999999", "udb_anobiztoken": "B" * 344}
monkeypatch.setattr(
"core.huya.anon_device.fetch_anonymous_device_state", lambda **kw: anon
)
merged = HuyaAppPasswordLogin._supplement_anonymous_device(
{"udb_deviceid": "w_1111111111111111111"}
)
assert merged["udb_deviceid"] == "w_1111111111111111111" # setdefault 语义
def test_supplement_anonymous_device_failure_does_not_raise(monkeypatch):
def boom(**kw):
from core.huya.anon_device import AnonymousDeviceError
raise AnonymousDeviceError("network down")
monkeypatch.setattr("core.huya.anon_device.fetch_anonymous_device_state", boom)
merged = HuyaAppPasswordLogin._supplement_anonymous_device({"guid": "g"})
assert merged == {"guid": "g"}
class TestHuyaAppLogin:
def setup_method(self):
self.engine = create_engine("sqlite://")
+14 -2
View File
@@ -10,8 +10,7 @@ def test_minimal_app_cookie_reports_only_missing_web_guid():
)
assert HuyaBatchRunnerCore._web_wss_cookie_missing(cookie) == (
"guid", "udb_guiddata", "udb_deviceid", "game_did",
"_qimei_uuid42", "udb_anobiztoken", "__yamid_new",
"udb_deviceid", "udb_guiddata", "udb_anobiztoken",
)
assert not HuyaBatchRunnerCore._has_web_wss_cookie(cookie)
@@ -26,3 +25,16 @@ def test_complete_web_cookie_enables_wss_without_logging_values():
assert HuyaBatchRunnerCore._web_wss_cookie_missing(cookie) == ()
assert HuyaBatchRunnerCore._has_web_wss_cookie(cookie)
def test_script_only_fields_do_not_block_wss():
"""guid/_qimei_uuid42/__yamid_new/game_did 无服务端签发时不再阻断 WSS。"""
cookie = (
"udb_cred=cred; udb_biztoken=biz; udb_uid=1199659425268; "
"yyuid=1199659425268; sdid=sdid; "
"udb_deviceid=w_1234567890123456789; udb_guiddata=guiddata; "
"udb_anobiztoken=anobiz"
)
assert HuyaBatchRunnerCore._web_wss_cookie_missing(cookie) == ()
assert HuyaBatchRunnerCore._has_web_wss_cookie(cookie)
+8 -3
View File
@@ -84,11 +84,16 @@ class HuyaBatchRunnerCore:
@staticmethod
def _web_wss_cookie_missing(cookie: str) -> tuple[str, ...]:
"""返回 WSS 前置字段中缺失的名称,不输出任何 Cookie 值。"""
"""返回 WSS 前置字段中缺失的名称,不输出任何 Cookie 值。
必填集 = 认证字段 + sdid + 匿名设备态协议可签发的字段
udb_deviceid/udb_guiddata/udb_anobiztoken,见 core/huya/anon_device.py)。
guid/_qimei_uuid42/__yamid_new/game_did 目前无服务端签发接口,
不阻断 WSS(风控风险自担)。
"""
required = (
"udb_cred", "udb_biztoken", "udb_uid", "yyuid", "sdid",
"guid", "udb_guiddata", "udb_deviceid", "game_did",
"_qimei_uuid42", "udb_anobiztoken", "__yamid_new",
"udb_deviceid", "udb_guiddata", "udb_anobiztoken",
)
return tuple(key for key in required if not cookie_value(cookie, key))