虎牙登录: 接入hydevice高信任设备指纹与App场景验证支持

- 新增 fingerprint/: node仿真Android WebView运行官方hydevice.js(含WASM加密), 产出与真机同构的高信任sdid
- 新增 device_fingerprint.py: py编排node runner获取sdid, 失败自动降级旧无指纹流程
- login.py: prepare_device优先高信任指纹; 增加App场景常量(appId=5008, appSign=md5(appId+verCode+appKey)[:8], 逆向自APK UdbNetHelper)
- solver: 支持appId/page_url/use_touch_events参数化(App模式); get3使用csid会话id; 识别qr_auth扫码(冷却)与dx_auth短信策略并抛专用异常
This commit is contained in:
yml2213
2026-08-23 17:32:34 +08:00
parent bf2f78aa70
commit 06a0a1e7d3
8 changed files with 438 additions and 16 deletions
+131
View File
@@ -0,0 +1,131 @@
"""虎牙设备指纹:通过 node 运行 hydevice.js 生成高信任 sdid。
流程与真机 App WebView 一致:
df/token -> hydevice 采集+WASM 加密 -> df/collect -> sdid
"""
from __future__ import annotations
import shutil
import subprocess
import tempfile
from collections.abc import Mapping
from dataclasses import dataclass
from pathlib import Path
import requests
from loguru import logger
FINGERPRINT_DIR = Path(__file__).parent / "fingerprint"
RUNNER_JS = FINGERPRINT_DIR / "runner.js"
SDID_PREFIX = "__SDID__"
DEFAULT_TIMEOUT = (8, 40)
class HuyaFingerprintError(RuntimeError):
"""虎牙设备指纹生成失败。"""
@dataclass
class HuyaSdidResult:
"""sdid 获取结果。"""
sdid: str = ""
source: str = ""
message: str = ""
def _run_node_runner(state_dir: Path, app_id: str, timeout: tuple[float, float]) -> str:
"""调用 node runner,返回 sdid。"""
node_bin = shutil.which("node")
if not node_bin:
raise HuyaFingerprintError("未找到 node 可执行文件(hydevice.js 需要 JS 引擎)")
connect_timeout, read_timeout = timeout
total_timeout = int(connect_timeout + read_timeout + 10)
try:
proc = subprocess.run(
[node_bin, str(RUNNER_JS), str(state_dir), app_id],
capture_output=True,
text=True,
timeout=total_timeout,
cwd=str(FINGERPRINT_DIR),
)
except subprocess.TimeoutExpired as exc:
raise HuyaFingerprintError(f"hydevice runner 超时({total_timeout}s)") from exc
for line in (proc.stdout or "").splitlines():
line = line.strip()
if line.startswith(SDID_PREFIX) and len(line) > len(SDID_PREFIX) + 20:
return line[len(SDID_PREFIX):]
stderr_tail = (proc.stderr or "").strip().splitlines()
detail = stderr_tail[-1][:120] if stderr_tail else f"exit={proc.returncode}"
raise HuyaFingerprintError(f"hydevice runner 未返回 sdid: {detail}")
def _fallback_sdid(session: requests.Session, timeout) -> str:
"""旧版降级:仅 token+collect,无指纹(低信任)。"""
token_url = "https://df.huya.com/web/df/token"
collect_url = "https://df.huya.com/web/df/collect"
token_res = session.post(
token_url,
json={"encryptVersion": "1.0.1", "fingerprintVersion": "1.2.45"},
timeout=timeout,
)
token_res.raise_for_status()
token = token_res.json().get("data", {}).get("token", "")
if not token:
raise HuyaFingerprintError(f"获取虎牙 df token 失败: {token_res.text[:120]}")
collect_res = session.post(collect_url, json={"token": token}, timeout=timeout)
collect_res.raise_for_status()
return collect_res.json().get("data", {}).get("sdid", "")
def get_huya_sdid(
app_id: str = "5008",
state_dir: str | Path | None = None,
session: requests.Session | None = None,
proxies: Mapping | None = None,
timeout: tuple[float, float] = DEFAULT_TIMEOUT,
allow_fallback: bool = True,
) -> HuyaSdidResult:
"""获取虎牙高信任 sdid;node 链路失败时可选降级为低信任指纹。
Args:
app_id: 虎牙应用 idApp 场景 5008。
state_dir: localStorage 持久化目录(保持设备一致性),None 用临时目录。
allow_fallback: node 失败时是否降级为旧版无指纹方式。
Returns:
HuyaSdidResult(sdid, source, message)source 为 fingerprint/fallback/none。
"""
del session, proxies # node runner 自行发请求;保留参数便于未来代理透传
if state_dir is None:
state_dir = Path(tempfile.gettempdir()) / "huya_fp_state"
state_dir = Path(state_dir)
try:
sdid = _run_node_runner(state_dir, app_id, timeout)
if sdid:
logger.debug("虎牙设备指纹成功(node): sdid={}...", sdid[:24])
return HuyaSdidResult(sdid=sdid, source="fingerprint")
except HuyaFingerprintError as exc:
logger.warning("虎牙 hydevice 指纹失败: {}", exc)
if not allow_fallback:
raise
if allow_fallback:
try:
with requests.Session() as s:
s.trust_env = False
sdid = _fallback_sdid(s, timeout)
if sdid:
logger.debug("虎牙设备指纹降级(无指纹): sdid={}...", sdid[:24])
return HuyaSdidResult(
sdid=sdid,
source="fallback",
message="低信任指纹(降级)",
)
except Exception as exc: # noqa: BLE001
logger.warning("虎牙降级指纹也失败: {}", exc)
return HuyaSdidResult(source="none", message="未能获取 sdid")