虎牙登录: 接入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:
@@ -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: 虎牙应用 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 = _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")
|
||||
Reference in New Issue
Block a user