- df/collect 响应含40hex hdid(每次新值)但 WUP 登录拒绝(652B) -> 非同一体系 - runner.js 输出 __HDID__, get_huya_sdid 一并返回 - 文档: hdid 来源/可铸造性/共用风险评估 + 设备池方案
142 lines
5.0 KiB
Python
142 lines
5.0 KiB
Python
"""虎牙设备指纹:通过 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 = ""
|
||
hdid: str = "" # df/collect 同响应下发的 40hex 设备ID; 与 WUP 登录的 32hex hdid 非同一体系
|
||
source: str = ""
|
||
message: str = ""
|
||
|
||
|
||
HDID_PREFIX = "__HDID__"
|
||
|
||
|
||
def _run_node_runner(state_dir: Path, app_id: str, timeout: tuple[float, float]) -> tuple[str, str]:
|
||
"""调用 node runner,返回 (sdid, hdid)。"""
|
||
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
|
||
|
||
sdid, hdid = "", ""
|
||
for line in (proc.stdout or "").splitlines():
|
||
line = line.strip()
|
||
if line.startswith(SDID_PREFIX) and len(line) > len(SDID_PREFIX) + 20:
|
||
sdid = line[len(SDID_PREFIX):]
|
||
if line.startswith(HDID_PREFIX) and len(line) > len(HDID_PREFIX) + 20:
|
||
hdid = line[len(HDID_PREFIX):]
|
||
if sdid:
|
||
return sdid, hdid
|
||
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: 虎牙应用 id,App 场景 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, hdid = _run_node_runner(state_dir, app_id, timeout)
|
||
if sdid:
|
||
logger.debug("虎牙设备指纹成功(node): sdid={}... hdid={}...",
|
||
sdid[:24], hdid[:10])
|
||
return HuyaSdidResult(sdid=sdid, hdid=hdid, 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")
|