虎牙网页Cookie来源驱动合并与WSS传输门禁
This commit is contained in:
+173
-5
@@ -37,7 +37,7 @@ import requests
|
||||
from loguru import logger
|
||||
|
||||
from .cert_forge import build_p1, forge_cert
|
||||
from .cookie_utils import normalize_huya_cookie
|
||||
from .cookie_utils import cookie_pairs, normalize_cookie_pairs
|
||||
from .device_fingerprint import account_state_dir, get_huya_sdid, reset_account_state
|
||||
from .device_profile import get_profile, mobile_user_agent
|
||||
from .dfp_register import DfpRegistrationError, register_device
|
||||
@@ -62,6 +62,18 @@ APP_UA_MOBILE = (
|
||||
"Chrome/149.0.7827.159 Mobile Safari/537.36 huya adr/13.4.22/xiaomi/30"
|
||||
)
|
||||
|
||||
# 9.1 网页入口请求中稳定出现的设备态字段。它们必须来自真实 WebView/浏览器
|
||||
# 运行态或本次请求的 Set-Cookie,不能由 App 凭据推导或随机生成。
|
||||
WEB_DEVICE_COOKIE_KEYS = (
|
||||
"guid",
|
||||
"udb_guiddata",
|
||||
"udb_deviceid",
|
||||
"game_did",
|
||||
"_qimei_uuid42",
|
||||
"udb_anobiztoken",
|
||||
"__yamid_new",
|
||||
)
|
||||
|
||||
SessionAssets = tuple[dict, str, str]
|
||||
|
||||
RISK_URL_RE = re.compile(rb"https://aq\.huya\.com/p/safe_auth/[^\x00-\x20\"'\\<>]+")
|
||||
@@ -457,6 +469,110 @@ class HuyaAppPasswordLogin:
|
||||
logger.debug(f"登录元数据记录失败: {exc}")
|
||||
return result
|
||||
|
||||
@staticmethod
|
||||
def _missing_web_device_cookie(cookie: str) -> tuple[str, ...]:
|
||||
"""返回网页设备态缺失字段名,不暴露任何 Cookie 值。"""
|
||||
present = {key for key, value in cookie_pairs(cookie) if value}
|
||||
return tuple(key for key in WEB_DEVICE_COOKIE_KEYS if key not in present)
|
||||
|
||||
@staticmethod
|
||||
def _merge_web_cookie(
|
||||
session: requests.Session,
|
||||
biztoken: str,
|
||||
sdid: str,
|
||||
hdid: str,
|
||||
device_info: Mapping[str, object],
|
||||
*,
|
||||
cred: bytes | str = b"",
|
||||
uid: int = 0,
|
||||
username: str = "",
|
||||
include_transient: bool = True,
|
||||
) -> str:
|
||||
"""合并真实登录凭据与已有网页 Cookie。
|
||||
|
||||
抓包中的设备/统计 Cookie 由浏览器或设备指纹链产生,不能用随机值
|
||||
冒充。认证字段来自本次登录或指纹响应并覆盖旧值;其他字段只有在
|
||||
Session 或 ``device_info`` 明确提供时才保留,缺失就不写入。
|
||||
"""
|
||||
jar = session.cookies
|
||||
if hasattr(jar, "items"):
|
||||
pairs = [
|
||||
(str(key), "" if value is None else str(value))
|
||||
for key, value in jar.items()
|
||||
]
|
||||
else:
|
||||
pairs = [
|
||||
(str(getattr(cookie, "name", "")), str(getattr(cookie, "value", "")))
|
||||
for cookie in jar
|
||||
if getattr(cookie, "name", "")
|
||||
]
|
||||
|
||||
def set_value(key: str, value: object, *, overwrite: bool = False):
|
||||
value = "" if value is None else str(value).strip()
|
||||
if not value:
|
||||
return
|
||||
if overwrite:
|
||||
pairs[:] = [
|
||||
(item_key, item_value)
|
||||
for item_key, item_value in pairs
|
||||
if item_key != key
|
||||
]
|
||||
if not any(item_key == key for item_key, _item_value in pairs):
|
||||
pairs.append((key, value))
|
||||
|
||||
raw_cred = (
|
||||
base64.urlsafe_b64encode(cred).decode("ascii").rstrip("=")
|
||||
if isinstance(cred, bytes)
|
||||
else str(cred or "")
|
||||
)
|
||||
# 本次登录/指纹响应明确返回的字段,必须覆盖旧 Session 值。
|
||||
set_value("udb_cred", raw_cred, overwrite=True)
|
||||
if uid:
|
||||
set_value("udb_uid", int(uid), overwrite=True)
|
||||
set_value("yyuid", int(uid), overwrite=True)
|
||||
set_value("udb_biztoken", biztoken, overwrite=True)
|
||||
set_value("udb_version", "1.0", overwrite=True)
|
||||
set_value("udb_origin", "0", overwrite=True)
|
||||
set_value("udb_status", "1", overwrite=True)
|
||||
set_value("sdid", sdid, overwrite=True)
|
||||
set_value("hdid", hdid, overwrite=True)
|
||||
|
||||
# 手机号不是网页 passport。只有明确的 hy_账号或 Session 现有值可用。
|
||||
if username.startswith("hy_"):
|
||||
set_value("udb_passport", username, overwrite=True)
|
||||
set_value("username", username, overwrite=True)
|
||||
|
||||
# 设备/统计字段不推导、不随机生成;仅转发已知真实值。
|
||||
optional_keys = (
|
||||
"guid", "udb_guiddata", "__yamid_new",
|
||||
"game_did", "_qimei_uuid42", "_qimei_fingerprint", "_qimei_h38",
|
||||
"udb_anouid", "udb_anobiztoken", "__yasmid", "__yamid_tt1",
|
||||
"SoundValue", "alphaValue", "isInLiveRoom", "udb_deviceid",
|
||||
"udb_passdata", "_rep_cnt", "Hm_lvt_51700b6c722f5bb4cf39906a596ea41f",
|
||||
"HMACCOUNT", "udb_appid", "rep_cnt", "udb_accdata", "h_unt",
|
||||
"__yaoldyyuid", "_yasids", "huya_flash_rep_cnt", "huyasp_rep_cnt",
|
||||
"huya_hd_rep_cnt", "Hm_lpvt_51700b6c722f5bb4cf39906a596ea41f",
|
||||
"huya_web_rep_cnt",
|
||||
)
|
||||
for key in optional_keys:
|
||||
explicit = device_info.get(key)
|
||||
set_value(key, explicit, overwrite=explicit not in (None, ""))
|
||||
web_guiddata = str(
|
||||
device_info.get("web_guiddata") or device_info.get("udb_guiddata") or ""
|
||||
)
|
||||
if web_guiddata:
|
||||
set_value("udb_guiddata", web_guiddata, overwrite=True)
|
||||
web_device_id = str(device_info.get("web_device_id") or "")
|
||||
if re.fullmatch(r"w_\d{19}", web_device_id):
|
||||
set_value("udb_deviceid", web_device_id, overwrite=True)
|
||||
if not include_transient:
|
||||
pairs = [
|
||||
(key, value)
|
||||
for key, value in pairs
|
||||
if key != "web_qrlogin_confirm_id"
|
||||
]
|
||||
return normalize_cookie_pairs(pairs)
|
||||
|
||||
def _login_impl(self) -> HuyaLoginResult:
|
||||
"""登录主体 (原 login)。"""
|
||||
acct = self.username
|
||||
@@ -536,6 +652,21 @@ class HuyaAppPasswordLogin:
|
||||
f"账号设备指纹获取失败(必须为 hydevice): {detail}"
|
||||
)
|
||||
sdid = sdid_obj.sdid
|
||||
# hydevice may write additional browser cookies through its
|
||||
# document.cookie bridge. Forward only the values actually
|
||||
# observed from the runner; never synthesize missing fields.
|
||||
observed_cookie_keys = sorted(sdid_obj.cookies or {})
|
||||
logger.info(
|
||||
"[huya-app] hydevice 实际 Cookie 输出字段: {}",
|
||||
", ".join(observed_cookie_keys) if observed_cookie_keys else "无",
|
||||
)
|
||||
merge_device_info = dict(self.device_info)
|
||||
for key, value in (sdid_obj.cookies or {}).items():
|
||||
if key in WEB_DEVICE_COOKIE_KEYS or key in {
|
||||
"udb_appid", "__yasmid", "__yamid_tt1", "udb_anouid",
|
||||
"udb_anobiztoken", "udb_deviceid",
|
||||
}:
|
||||
merge_device_info.setdefault(key, value)
|
||||
logger.info("[huya-app] cred 已获取,开始二维码绑定流程")
|
||||
pc = QrRole(pc=True, sdid=sdid, proxies=self.proxies)
|
||||
ph = QrRole(
|
||||
@@ -621,7 +752,21 @@ class HuyaAppPasswordLogin:
|
||||
code="BIZTOKEN_TIMEOUT",
|
||||
)
|
||||
|
||||
# 5) POST /web/cookie/verify 兑换 Cookie
|
||||
# 5) 先把扫码返回的网页 token 和设备态写回 verify Session。
|
||||
# verify 响应本身没有 Set-Cookie;浏览器是在请求前就已准备好这些字段。
|
||||
merged_before_verify = self._merge_web_cookie(
|
||||
pc.s,
|
||||
biztoken,
|
||||
sdid,
|
||||
sdid_obj.hdid,
|
||||
merge_device_info,
|
||||
cred=cred,
|
||||
uid=uid,
|
||||
username=acct,
|
||||
)
|
||||
pc.s.cookies.clear()
|
||||
for key, value in cookie_pairs(merged_before_verify):
|
||||
pc.s.cookies.set(key, value)
|
||||
verify_resp = pc.s.post(
|
||||
"https://udblgn.huya.com/web/cookie/verify",
|
||||
json={"appId": 5002},
|
||||
@@ -634,8 +779,31 @@ class HuyaAppPasswordLogin:
|
||||
code="VERIFY_FAILED",
|
||||
)
|
||||
|
||||
cookie_str = normalize_huya_cookie(pc.s.cookies)
|
||||
if "udb_cred" not in cookie_str and "yyuid" not in cookie_str:
|
||||
cookie_str = self._merge_web_cookie(
|
||||
pc.s,
|
||||
biztoken,
|
||||
sdid,
|
||||
sdid_obj.hdid,
|
||||
merge_device_info,
|
||||
cred=cred,
|
||||
uid=uid,
|
||||
username=acct,
|
||||
include_transient=False,
|
||||
)
|
||||
missing_web = self._missing_web_device_cookie(cookie_str)
|
||||
if missing_web:
|
||||
missing_text = ", ".join(missing_web)
|
||||
logger.error(
|
||||
f"[huya-app] 账号 {acct} 登录凭据有效,但网页设备态不完整,缺少: {missing_text}"
|
||||
)
|
||||
return HuyaLoginResult(
|
||||
success=False,
|
||||
message=f"COOKIE_INCOMPLETE: 缺少网页设备态({missing_text}),请在真实虎牙网页/WebView完成一次访问后重试",
|
||||
code="COOKIE_INCOMPLETE",
|
||||
sdid=sdid,
|
||||
context=pc.context,
|
||||
)
|
||||
if "udb_cred" not in cookie_str or "yyuid" not in cookie_str:
|
||||
return HuyaLoginResult(
|
||||
success=False,
|
||||
message="Cookie 兑换完成但缺失关键凭据 (udb_cred/yyuid)",
|
||||
@@ -643,7 +811,7 @@ class HuyaAppPasswordLogin:
|
||||
)
|
||||
|
||||
logger.info(
|
||||
f"[huya-app] 账号 {acct} 登录成功,获取完整 Cookie ({len(cookie_str)}B)"
|
||||
f"[huya-app] 账号 {acct} 登录成功,获取网页 Cookie ({len(cookie_str)}B)"
|
||||
)
|
||||
return HuyaLoginResult(
|
||||
success=True,
|
||||
|
||||
Reference in New Issue
Block a user