统一虎牙 App 登录设备画像

This commit is contained in:
yml2213
2026-08-30 23:04:28 +08:00
parent bc02018719
commit f4ed518b50
6 changed files with 281 additions and 119 deletions
+44 -26
View File
@@ -15,7 +15,7 @@
⚠ 风险与边界(详见 ``docs/HUYA_APP_OVERVIEW.md``):
- hdid 全账号固定共享 -> 服务端可设备维度关联账号;
- 滑块 UA / session / traceId 用金样本常量 -> 与随机机型画像不自洽
- 滑块 UA / session / traceId / userAction 均按当前画像和本次请求动态生成
- qr_auth(扫码)/dx_auth(短信) 无自动闭环 -> 碰上直接失败(QR_AUTH_REQUIRED)。
"""
@@ -29,7 +29,7 @@ import re
import struct
import time
import uuid
from typing import Mapping
from collections.abc import Mapping
from urllib.parse import parse_qs, quote, urlparse
import requests
@@ -38,10 +38,10 @@ from loguru import logger
from .cert_forge import build_p1, decrypt_cert, forge_cert, parse_p1
from .cookie_utils import normalize_huya_cookie
from .device_fingerprint import account_state_dir, get_huya_sdid
from .device_profile import get_profile
from .device_profile import get_profile, mobile_user_agent
from .dfp_register import DfpRegistrationError, register_device
from .envelope_forge import Envelope
from .login import HuyaCredentialError, HuyaLoginError, HuyaLoginResult
from .login import HuyaLoginError, HuyaLoginResult
from .nonce_forge import K1_DEFAULT, gen_nonce
from .wup_encoder import build_password_login_wup
@@ -91,30 +91,28 @@ class HuyaAppQrAuthRequiredError(HuyaAppLoginError):
def _golden_session_assets() -> tuple[dict, str, str]:
"""生成本次登录的会话元数据和行为轨迹。"""
from .wup_encoder import make_trace_id, make_user_action
mj = {
"associationId": 8193,
"funcName": "hypasswordLogin",
"group": 1,
"id": 4097,
"session": 3251699,
"session": random.randint(1_000_000, 9_999_999),
"step": 1,
"stillLogin": False,
"traceId": "0b8f098ff64a5bdc-23473-94783833787595172451",
"traceId": make_trace_id(),
"type": 3,
"uid": 0,
"userContext": "",
}
ua = (
'{"curl":"登录页","furl":"我的","latitude":"-1.0","longitude":"-1.0","ssid":"",'
'"user_action":[{"id":"24","time":"1787595171950","x":"277","y":"1057"},'
'{"id":"11","time":"1787595172444","x":"296","y":"952"}]}'
)
sd = (
"PQwemAN9NHkZKoMqVTFUZBIypqMTaQEOrmXr37xQVhQZqrL/gUKEQ11xvE0ju48V8O/"
"t9UBGSp27m4+6bP4IiAEnpaR5Rj1kHEfN2SPLPqYZW9vroxUSoAvjJn6ezTP9jWGxxlRDCbt"
"Py4Rd6MencYT/pNImVIWK+YbNKZt1O05bHUFhqHf3"
)
return mj, ua, sd
return mj, make_user_action(), ""
def _android_version(device_info: Mapping[str, object]) -> str:
parts = str(device_info.get("screen") or "").split(",")
return parts[2] if len(parts) >= 3 and parts[2] else "11"
def wup_password_login_raw(
@@ -133,14 +131,15 @@ def wup_password_login_raw(
注册链失败抛 ``HuyaAppLoginError``,绝不静默回退旧固定值。
"""
uid_str = account[3:] if account.startswith("hy_") else account
dev = dict(device_info) if device_info is not None else get_profile(account)
mj, ua, _old_sd = _golden_session_assets()
dev = dict(device_info or DEFAULT_GOLDEN_DEV)
if not safedeviceid:
try:
_t1, safedeviceid, registered_device_id = register_device(
fingerprint=dev.get("fingerprint"),
proxies=proxies,
timeout=timeout,
device_info=dev,
)
except DfpRegistrationError as exc:
raise HuyaAppLoginError(f"新设备注册失败: {exc}") from exc
@@ -160,7 +159,7 @@ def wup_password_login_raw(
data=pkt,
headers={
"Content-Type": "application/multipart-formdata; charset=UTF-8",
"User-Agent": "Dalvik/2.1.0 (Linux; U; Android 11)",
"User-Agent": f"Dalvik/2.1.0 (Linux; U; Android {_android_version(dev)})",
"Accept-Encoding": "gzip",
},
proxies=proxies,
@@ -203,7 +202,12 @@ def parse_risk_url(resp: bytes) -> str | None:
return (pt or urls)[0]
def solve_safe_auth(risk_url: str, proxies=None, max_retry: int = 3) -> dict:
def solve_safe_auth(
risk_url: str,
proxies=None,
max_retry: int = 3,
device_info: Mapping[str, object] | None = None,
) -> dict:
"""解 safe_auth 风控滑块。"""
from .verification.solver import (
HuyaQrAuthRequiredError,
@@ -218,7 +222,7 @@ def solve_safe_auth(risk_url: str, proxies=None, max_retry: int = 3) -> dict:
last_err: Exception | None = None
for attempt in range(max_retry):
solver = HuyaVerificationSolver(
ua=APP_UA_MOBILE,
ua=mobile_user_agent(device_info or DEFAULT_GOLDEN_DEV),
proxies=proxies,
app_id=app_id,
page_url=risk_url,
@@ -268,11 +272,12 @@ def login_cred_with_flow(
注册只执行一次;safe_auth 通过后的重发继续使用同一组设备字段。
"""
dev = dict(device_info or DEFAULT_GOLDEN_DEV)
dev = dict(device_info) if device_info is not None else get_profile(account)
try:
_t1, safedeviceid, registered_device_id = register_device(
fingerprint=dev.get("fingerprint"),
proxies=proxies,
device_info=dev,
)
except DfpRegistrationError as exc:
raise HuyaAppLoginError(f"新设备注册失败: {exc}") from exc
@@ -301,7 +306,7 @@ def login_cred_with_flow(
raise HuyaAppQrAuthRequiredError(
f"该账号 App 渠道要求扫码验证(qr_auth),请先在手机虎牙 App 上正常登录一次建立设备信任。"
)
solve_safe_auth(risk_url, proxies=proxies)
solve_safe_auth(risk_url, proxies=proxies, device_info=dev)
logger.info("[huya-app] safe_auth 滑块过验成功,重发 WUP 登录...")
continue
raise HuyaAppLoginError("登录未返回凭据也无风控URL(密码错误或账号状态异常)")
@@ -311,7 +316,13 @@ def login_cred_with_flow(
class QrRole:
"""udblgn.huya.com/qrLgn/* 的 JSON 协议封装。"""
def __init__(self, pc: bool, sdid: str, proxies: dict | None = None):
def __init__(
self,
pc: bool,
sdid: str,
proxies: dict | None = None,
device_info: Mapping[str, object] | None = None,
):
self.pc = pc
self.sdid = sdid
self.s = requests.Session()
@@ -327,7 +338,9 @@ class QrRole:
self.req_counter = random.randint(40_000_000, 41_000_000)
self.s.headers.update(
{
"User-Agent": UA_PC if pc else APP_UA_MOBILE,
"User-Agent": UA_PC
if pc
else mobile_user_agent(device_info or DEFAULT_GOLDEN_DEV),
"Origin": UDB_BASE,
"content-type": "application/json;charset=UTF-8",
"Accept": "*/*",
@@ -483,7 +496,12 @@ class HuyaAppPasswordLogin:
)
sdid = sdid_obj.sdid if sdid_obj else ""
pc = QrRole(pc=True, sdid=sdid, proxies=self.proxies)
ph = QrRole(pc=False, sdid=sdid, proxies=self.proxies)
ph = QrRole(
pc=False,
sdid=sdid,
proxies=self.proxies,
device_info=self.device_info,
)
beh, page = web_behavior()
# 4.1 获取 qrId