真实浏览器抓包与匿名设备态协议生成器
This commit is contained in:
@@ -0,0 +1,177 @@
|
||||
"""虎牙网页匿名设备态协议生成器(纯 HTTP,middle + anonymousLogin)。
|
||||
|
||||
生成 4 个网页设备态字段(来源见 docs/HUYA_COOKIE合并审计与来源矩阵.md):
|
||||
|
||||
- ``udb_deviceid`` <- GET /web/middle/2.4/{rand8}/https/{hex32} 的 Set-Cookie
|
||||
- ``udb_guiddata`` <- middle URL 第三段 hex32(客户端自选,服务端不校验)
|
||||
- ``udb_anobiztoken``<- POST /web/anonymousLogin 响应 data.biztoken(344 位)
|
||||
- ``udb_anouid`` <- anonymousLogin 响应 data.uid
|
||||
|
||||
用法::
|
||||
|
||||
python -m core.huya.anon_device # 打印 4 字段 JSON
|
||||
python -m core.huya.anon_device --json # 同上,紧凑 JSON(供脚本消费)
|
||||
|
||||
注意: 200 / returnCode=0 只证明接口受理,不证明风控认可这些值的真实性。
|
||||
``guid`` / ``_qimei_uuid42`` / ``__yamid_new`` / ``game_did`` 仍无服务端
|
||||
签发接口,本模块不生成、不伪造它们。
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import json
|
||||
import random
|
||||
import re
|
||||
import secrets
|
||||
import sys
|
||||
from collections.abc import Mapping
|
||||
|
||||
import requests
|
||||
from loguru import logger
|
||||
|
||||
MIDDLE_URL = "https://udblgn.huya.com/web/middle/2.4/{rand8}/https/{guiddata}"
|
||||
ANON_LOGIN_URL = "https://udblgn.huya.com/web/anonymousLogin"
|
||||
|
||||
DESKTOP_UA = (
|
||||
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 "
|
||||
"(KHTML, like Gecko) Chrome/149.0.0.0 Safari/537.36"
|
||||
)
|
||||
|
||||
UDBN_DEVICEID_RE = re.compile(r"udb_deviceid=([^;]+)")
|
||||
|
||||
|
||||
class AnonymousDeviceError(RuntimeError):
|
||||
"""匿名设备态获取失败。"""
|
||||
|
||||
|
||||
def _new_session(*, proxies: Mapping[str, str] | None = None) -> requests.Session:
|
||||
session = requests.Session()
|
||||
session.trust_env = False
|
||||
if proxies:
|
||||
session.proxies.update(dict(proxies))
|
||||
return session
|
||||
|
||||
|
||||
def _parse_set_cookie_deviceid(set_cookie: str) -> str:
|
||||
match = UDBN_DEVICEID_RE.search(set_cookie or "")
|
||||
if not match or not re.fullmatch(r"w_\d{19}", match.group(1)):
|
||||
raise AnonymousDeviceError("middle 响应未下发合法 udb_deviceid")
|
||||
return match.group(1)
|
||||
|
||||
|
||||
def fetch_anonymous_device_state(
|
||||
*,
|
||||
proxies: Mapping[str, str] | None = None,
|
||||
timeout: float = 15.0,
|
||||
session: requests.Session | None = None,
|
||||
) -> dict[str, str]:
|
||||
"""换取匿名设备态四字段:udb_deviceid / udb_guiddata / udb_anobiztoken / udb_anouid。
|
||||
|
||||
与网页 SDK 相同的两步:middle 引导页签发 deviceid,anonymousLogin 换取
|
||||
biztoken/uid。所有参数客户端随机生成,无需任何既有 Cookie。
|
||||
"""
|
||||
own_session = session is None
|
||||
s = session or _new_session(proxies=proxies)
|
||||
try:
|
||||
guiddata = secrets.token_hex(16)
|
||||
rand8 = str(random.randint(10_000_000, 99_999_999))
|
||||
middle_url = MIDDLE_URL.format(rand8=rand8, guiddata=guiddata)
|
||||
|
||||
# 1) middle:签发 udb_deviceid
|
||||
resp = s.get(
|
||||
middle_url,
|
||||
headers={"User-Agent": DESKTOP_UA, "Referer": "https://www.huya.com/"},
|
||||
timeout=timeout,
|
||||
allow_redirects=False,
|
||||
)
|
||||
if resp.status_code != 200:
|
||||
raise AnonymousDeviceError(f"middle 返回 {resp.status_code}")
|
||||
deviceid = _parse_set_cookie_deviceid(resp.headers.get("Set-Cookie", ""))
|
||||
|
||||
# 2) anonymousLogin:发放匿名 uid + biztoken(必须带 middle Referer)
|
||||
context = (
|
||||
"WB-"
|
||||
+ secrets.token_hex(16)
|
||||
+ "-" + secrets.token_hex(16)
|
||||
+ "-" + secrets.token_hex(16)
|
||||
)
|
||||
payload = {
|
||||
"uri": "10013",
|
||||
"version": "2.4",
|
||||
"context": context,
|
||||
"appId": 5002,
|
||||
"sdid": "csid_" + secrets.token_hex(16),
|
||||
"lcid": 2052,
|
||||
"byPass": 3,
|
||||
"requestId": random.randint(10_000_000, 99_999_999),
|
||||
"authId": "",
|
||||
"data": {"domainList": ""},
|
||||
}
|
||||
anon = s.post(
|
||||
ANON_LOGIN_URL,
|
||||
headers={
|
||||
"User-Agent": DESKTOP_UA,
|
||||
"Referer": middle_url,
|
||||
"Content-Type": "application/json;charset=UTF-8",
|
||||
},
|
||||
data=json.dumps(payload),
|
||||
timeout=timeout,
|
||||
allow_redirects=False,
|
||||
)
|
||||
if anon.status_code != 200:
|
||||
raise AnonymousDeviceError(f"anonymousLogin 返回 {anon.status_code}")
|
||||
try:
|
||||
body = anon.json()
|
||||
except ValueError as exc:
|
||||
raise AnonymousDeviceError(
|
||||
f"anonymousLogin 响应非 JSON: {anon.text[:80]!r}"
|
||||
) from exc
|
||||
if body.get("returnCode") != 0:
|
||||
raise AnonymousDeviceError(
|
||||
f"anonymousLogin returnCode={body.get('returnCode')} "
|
||||
f"message={body.get('message')!r}"
|
||||
)
|
||||
data = body.get("data") or {}
|
||||
anouid = str(data.get("uid") or "")
|
||||
biztoken = str(data.get("biztoken") or "")
|
||||
if not anouid or not biztoken:
|
||||
raise AnonymousDeviceError("anonymousLogin 响应缺少 uid/biztoken")
|
||||
if not 300 <= len(biztoken) <= 400:
|
||||
logger.warning("[huya-anon] biztoken 长度异常: {}", len(biztoken))
|
||||
|
||||
logger.info(
|
||||
"[huya-anon] 匿名设备态 OK: deviceid={}... guiddata={}... "
|
||||
"anouid={} biztoken={}...",
|
||||
deviceid[:14], guiddata[:8], anouid, biztoken[:10],
|
||||
)
|
||||
return {
|
||||
"udb_deviceid": deviceid,
|
||||
"udb_guiddata": guiddata,
|
||||
"udb_anobiztoken": biztoken,
|
||||
"udb_anouid": anouid,
|
||||
}
|
||||
finally:
|
||||
if own_session:
|
||||
s.close()
|
||||
|
||||
|
||||
def main() -> int:
|
||||
ap = argparse.ArgumentParser(description="虎牙匿名设备态四字段生成(纯协议)")
|
||||
ap.add_argument("--json", action="store_true", help="输出紧凑 JSON")
|
||||
args = ap.parse_args()
|
||||
try:
|
||||
state = fetch_anonymous_device_state()
|
||||
except AnonymousDeviceError as exc:
|
||||
print(f"❌ {exc}", file=sys.stderr)
|
||||
return 2
|
||||
if args.json:
|
||||
print(json.dumps(state, ensure_ascii=False))
|
||||
else:
|
||||
for key, value in state.items():
|
||||
print(f"{key} = {value}")
|
||||
print("\n(4/7 字段;guid/_qimei_uuid42/__yamid_new/game_did 无服务端签发,不生成)")
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
Reference in New Issue
Block a user