"""虎牙 App 密码登录 WUP(TAF) 请求帧编码器。 纯 Python 构造符合 TAF/WUP 协议的请求二进制帧。 """ from __future__ import annotations import json import random import struct import time as _time from typing import Any, Dict # TAF 类型标签 INT8, INT16, INT32, INT64 = 0x00, 0x01, 0x02, 0x03 STRING1, STRING4 = 0x06, 0x07 MAP, LIST = 0x08, 0x09 STRUCT_BEGIN, STRUCT_END = 0x0A, 0x0B ZERO, SIMPLE_LIST = 0x0C, 0x0D class _Writer: """TAF 输出流。""" def __init__(self) -> None: self.buf = bytearray() def get(self) -> bytes: return bytes(self.buf) def head(self, tag: int, dtype: int) -> None: if tag < 15: self.buf.append((tag << 4) | dtype) else: self.buf.append(0xF0 | dtype) self.buf.append(tag) def int8(self, tag: int, v: int) -> None: if v == 0: self.head(tag, ZERO) else: self.head(tag, INT8) self.buf += struct.pack("b", v) def int16(self, tag: int, v: int) -> None: if -128 <= v <= 127: self.int8(tag, v) else: self.head(tag, INT16) self.buf += struct.pack(">h", v) def int32(self, tag: int, v: int) -> None: if -32768 <= v <= 32767: self.int16(tag, v) else: self.head(tag, INT32) self.buf += struct.pack(">i", v) def int64(self, tag: int, v: int) -> None: if -2147483648 <= v <= 2147483647: self.int32(tag, v) else: self.head(tag, INT64) self.buf += struct.pack(">q", v) def string(self, tag: int, s: str) -> None: b = s.encode("utf-8") if len(b) > 255: self.head(tag, STRING4) self.buf += struct.pack(">I", len(b)) else: self.head(tag, STRING1) self.buf += struct.pack("B", len(b)) self.buf += b def bytes(self, tag: int, b: bytes) -> None: """SIMPLE_LIST (byte[])。""" self.head(tag, SIMPLE_LIST) self.head(0, INT8) self.int32(0, len(b)) self.buf += b def struct_begin(self, tag: int) -> None: self.head(tag, STRUCT_BEGIN) def struct_end(self) -> None: self.head(0, STRUCT_END) def list_begin(self, tag: int, n: int) -> None: self.head(tag, LIST) self.int32(0, n) def map_begin(self, tag: int, n: int) -> None: self.head(tag, MAP) self.int32(0, n) def _build_meta_json(session: int, trace_id: str) -> str: """构造 _wup_data.t0.t2 元数据 JSON。""" meta: Dict[str, Any] = { "associationId": 8193, "funcName": "hypasswordLogin", "group": 1, "id": 4097, "session": session, "step": 1, "stillLogin": False, "traceId": trace_id, "type": 3, "uid": 0, "userContext": "", } return json.dumps(meta, ensure_ascii=False, separators=(",", ":")) def _make_name(uid_str: str) -> str: """登录名 = "hy_" + 虎牙号。""" if uid_str.startswith("hy_"): return uid_str return "hy_" + uid_str def make_user_action(now_ms: int | None = None) -> str: """生成一条随机的 userAction 风控行为 JSON。""" if now_ms is None: now_ms = int(_time.time() * 1000) t1 = now_ms t2 = now_ms + random.randint(200, 900) return json.dumps( { "curl": "登录页", "furl": "我的", "latitude": "-1.0", "longitude": "-1.0", "ssid": "", "user_action": [ {"id": "24", "time": str(t1), "x": str(random.randint(150, 900)), "y": str(random.randint(800, 1600))}, {"id": "11", "time": str(t2), "x": str(random.randint(150, 900)), "y": str(random.randint(800, 1600))}, ], }, ensure_ascii=False, separators=(",", ":"), ) def make_trace_id(pid: int = 0) -> str: """生成 traceId,格式 `--<毫秒时间戳>`。""" return f"{random.getrandbits(64):016x}-{pid}-{int(_time.time() * 1000)}" def _build_wup_data(w: _Writer, uid_str: str, sha1_password: str, safedeviceid: str, hdid: str, session: int, trace_id: str, user_action_json: str, device_info: Dict[str, str]) -> None: """编码 _wup_data struct。""" meta_json = _build_meta_json(session, trace_id) name = _make_name(uid_str) w.struct_begin(0) # -- t0: 请求头 struct -- w.struct_begin(0) w.int8(0, 0) w.string(1, "1.0") w.string(2, meta_json) w.string(3, "5008") w.int8(4, 3) w.string(5, safedeviceid) w.string(6, "") w.string(7, "") w.string(8, user_action_json) w.string(9, "") w.struct_end() # -- t1: 设备信息 struct -- di = device_info w.struct_begin(1) w.string(0, hdid) w.string(1, di.get("app_version", "13.4.22")) w.string(2, di.get("sdk_version", "1.0.80138")) w.string(3, "") w.string(4, di.get("ip", "127.0.0.1")) w.string(5, di.get("vendor", "xiaomi")) w.string(6, "") w.struct_end() # -- t2: 屏幕/设备特征 struct -- w.struct_begin(2) w.int8(0, 1) w.string(1, di.get("model", "M2102J2SC")) w.string(2, di.get("fingerprint", "")) w.string(3, di.get("os", "android")) w.string(4, di.get("screen", "M2102J2SC,30,11")) w.string(6, str(di.get("width", "1080"))) w.string(7, str(di.get("height", "2120"))) w.string(8, di.get("device_id", "")) w.struct_end() # -- 登录字段 -- w.string(3, name) w.string(4, sha1_password) w.list_begin(5, 1) w.string(0, "5008") w.int8(6, 1) w.map_begin(7, 0) w.bytes(8, b"") w.struct_end() def build_password_login_wup( uid_str: str, sha1_password: str, safedeviceid: str, hdid: str, session: int, trace_id: str, user_action_json: str, device_info: Dict[str, str], ) -> bytes: """构造密码登录的 WUP TAF 请求体。""" wd = _Writer() _build_wup_data(wd, uid_str, sha1_password, safedeviceid, hdid, session, trace_id, user_action_json, device_info) wup_data = wd.get() req = _Writer() req.int32(0, session) wupdbreq_v0 = req.get() sb = _Writer() sb.map_begin(0, 2) sb.string(0, "_wup_data") sb.bytes(1, wup_data) sb.string(0, "wupudbrequest_v0") sb.bytes(1, wupdbreq_v0) s_buffer = sb.get() w = _Writer() w.int16(1, 3) w.int8(2, 0) w.int8(3, 0) w.int32(4, session) w.string(5, "huyaudbwebui") w.string(6, "hypasswordLogin") w.bytes(7, s_buffer) w.int32(8, 0) w.map_begin(9, 0) w.map_begin(10, 0) wup_body = w.get() return struct.pack(">I", 4 + len(wup_body)) + wup_body