Files
live-hub-py/core/huya/device_fingerprint.py
T
yml2213 06a0a1e7d3 虎牙登录: 接入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短信策略并抛专用异常
2026-08-23 17:32:34 +08:00

132 lines
4.6 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""虎牙设备指纹:通过 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")