style: 统一 Ruff 代码格式
This commit is contained in:
+29
-13
@@ -26,6 +26,7 @@
|
||||
python -m core.huya.account_env <账号> <密码> --new-device # 抛弃旧环境, 换新设备
|
||||
python -m core.huya.account_env <账号> --show # 只查看该账号绑定
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import hashlib
|
||||
@@ -35,10 +36,10 @@ import sys
|
||||
import time
|
||||
|
||||
from .device_profile import (
|
||||
HDID32, # 登录帧 t1.t0 32hex 常量 (R15 公式结果, k1 来源见 R39; device_profile.py)
|
||||
HDID32, # 登录帧 t1.t0 32hex 常量 (R15 公式结果, k1 来源见 R39; device_profile.py)
|
||||
_load_db,
|
||||
_save_db,
|
||||
get_profile, # 幂等画像: 同账号永远复用同一套 (data/huya_device_profiles.json)
|
||||
get_profile, # 幂等画像: 同账号永远复用同一套 (data/huya_device_profiles.json)
|
||||
)
|
||||
from .app_login import HuyaAppPasswordLogin
|
||||
|
||||
@@ -77,7 +78,9 @@ def get_or_create_env(account: str, force_new: bool = False) -> dict:
|
||||
record["guid32"] = _rand_hex(16, b"guid")
|
||||
changed = True
|
||||
if "hebe" not in record:
|
||||
record["hebe"] = {f"Hebe_D{i}": _rand_hex(16, f"hebe{i}".encode()) for i in range(1, 6)}
|
||||
record["hebe"] = {
|
||||
f"Hebe_D{i}": _rand_hex(16, f"hebe{i}".encode()) for i in range(1, 6)
|
||||
}
|
||||
changed = True
|
||||
if changed:
|
||||
db[account] = record
|
||||
@@ -88,8 +91,12 @@ def get_or_create_env(account: str, force_new: bool = False) -> dict:
|
||||
# ---------------------------------------------------------------------------
|
||||
# 绑定 + 登录: 主流程
|
||||
# ---------------------------------------------------------------------------
|
||||
def bind_and_login(account: str, password: str,
|
||||
force_new_device: bool = False, proxies: dict | None = None) -> dict:
|
||||
def bind_and_login(
|
||||
account: str,
|
||||
password: str,
|
||||
force_new_device: bool = False,
|
||||
proxies: dict | None = None,
|
||||
) -> dict:
|
||||
"""账号 ↔ 环境绑定并登录。
|
||||
|
||||
注册链在 login_cred_with_flow 内部执行一次 (register_device 用的 fingerprint
|
||||
@@ -97,9 +104,11 @@ def bind_and_login(account: str, password: str,
|
||||
沿用同一组设备字段 (app_login.login_cred_with_flow)。
|
||||
"""
|
||||
env = get_or_create_env(account, force_new=force_new_device)
|
||||
print(f"[env] {account} ↔ {env.get('vendor')}/{env.get('model')} "
|
||||
f"fp40={env.get('fingerprint', '')[:12]}... guid32={env.get('guid32', '')[:12]}... "
|
||||
f"t1.t0={env.get('hdid', '')[:12]}...")
|
||||
print(
|
||||
f"[env] {account} ↔ {env.get('vendor')}/{env.get('model')} "
|
||||
f"fp40={env.get('fingerprint', '')[:12]}... guid32={env.get('guid32', '')[:12]}... "
|
||||
f"t1.t0={env.get('hdid', '')[:12]}..."
|
||||
)
|
||||
|
||||
# device_info 显式传入本环境 (否则 HuyaAppPasswordLogin 内部会再走一次
|
||||
# get_profile —— 结果相同, 但显式传入让"环境→注册→登录"的数据流向可读);
|
||||
@@ -108,13 +117,16 @@ def bind_and_login(account: str, password: str,
|
||||
login_env.setdefault("device_id", env.get("device_id"))
|
||||
|
||||
result = HuyaAppPasswordLogin(
|
||||
account, password, proxies=proxies, device_info=login_env,
|
||||
account,
|
||||
password,
|
||||
proxies=proxies,
|
||||
device_info=login_env,
|
||||
).login()
|
||||
|
||||
# ---- 绑定元数据回写 (令牌不入库: t2/t5 每次登录实时签发, 环境才是长期身份) ----
|
||||
db = _load_db()
|
||||
record = dict(db.get(account) or env)
|
||||
record.setdefault("bound_at", int(time.time())) # 首次绑定时间
|
||||
record.setdefault("bound_at", int(time.time())) # 首次绑定时间
|
||||
record["last_login"] = {
|
||||
"ok": result.success,
|
||||
"msg": result.message[:120],
|
||||
@@ -125,9 +137,13 @@ def bind_and_login(account: str, password: str,
|
||||
|
||||
return {
|
||||
"account": account,
|
||||
"env": {"model": env.get("model"), "vendor": env.get("vendor"),
|
||||
"fingerprint": env.get("fingerprint"), "guid32": env.get("guid32"),
|
||||
"hdid_t1t0": env.get("hdid")},
|
||||
"env": {
|
||||
"model": env.get("model"),
|
||||
"vendor": env.get("vendor"),
|
||||
"fingerprint": env.get("fingerprint"),
|
||||
"guid32": env.get("guid32"),
|
||||
"hdid_t1t0": env.get("hdid"),
|
||||
},
|
||||
"login_success": result.success,
|
||||
"login_message": result.message,
|
||||
"code": getattr(result, "code", None),
|
||||
|
||||
@@ -245,7 +245,9 @@ class ScoreExchangePrizeResp(TafStruct):
|
||||
self.msg = ins.read_string(1, default=self.msg)
|
||||
self.orderId = ins.read_string(3, default=self.orderId)
|
||||
self.exchangeInfo = ins.read_struct(4, ExchangeInfo) or self.exchangeInfo
|
||||
self.actPreCondition = ins.read_struct(5, ExchangeActPreCondition) or self.actPreCondition
|
||||
self.actPreCondition = (
|
||||
ins.read_struct(5, ExchangeActPreCondition) or self.actPreCondition
|
||||
)
|
||||
|
||||
def write_to(self, os: TafOutputStream):
|
||||
os.write_int32(0, self.status)
|
||||
@@ -512,7 +514,9 @@ class ActTaskDetailItem(TafStruct):
|
||||
|
||||
@property
|
||||
def spu_id(self) -> str:
|
||||
return self._extract_spu_id(self.taskUrl) or self._extract_spu_id(self.taskParams)
|
||||
return self._extract_spu_id(self.taskUrl) or self._extract_spu_id(
|
||||
self.taskParams
|
||||
)
|
||||
|
||||
def to_dict(self) -> dict:
|
||||
return {
|
||||
|
||||
@@ -172,7 +172,11 @@ def register_huya_with_sms_line(
|
||||
if login_result.success and login_result.cookie:
|
||||
cookie = login_result.cookie
|
||||
uid = cookie_value(cookie, "udb_uid") or cookie_value(cookie, "yyuid")
|
||||
username = cookie_value(cookie, "udb_passport") or cookie_value(cookie, "username") or uid
|
||||
username = (
|
||||
cookie_value(cookie, "udb_passport")
|
||||
or cookie_value(cookie, "username")
|
||||
or uid
|
||||
)
|
||||
if not change_password:
|
||||
return HuyaAutoRegisterResult(
|
||||
phone=phone,
|
||||
@@ -207,7 +211,9 @@ def register_huya_with_sms_line(
|
||||
attempts=attempts,
|
||||
)
|
||||
|
||||
password = fixed_password.strip() or generate_huya_password(password_prefix)
|
||||
password = fixed_password.strip() or generate_huya_password(
|
||||
password_prefix
|
||||
)
|
||||
change_result = change_huya_password_with_sms_line(
|
||||
uid=uid,
|
||||
cookie=cookie,
|
||||
@@ -220,7 +226,9 @@ def register_huya_with_sms_line(
|
||||
stop_event=stop_event,
|
||||
)
|
||||
if not change_result.success:
|
||||
failed_status = "stopped" if change_result.message == "已停止" else "error"
|
||||
failed_status = (
|
||||
"stopped" if change_result.message == "已停止" else "error"
|
||||
)
|
||||
return HuyaAutoRegisterResult(
|
||||
phone=phone,
|
||||
provider=item.provider,
|
||||
@@ -230,7 +238,9 @@ def register_huya_with_sms_line(
|
||||
cookie=cookie,
|
||||
code=poll_result.code,
|
||||
change_code=change_result.code,
|
||||
sdid=change_result.sdid or login_result.sdid or code_result.sdid,
|
||||
sdid=change_result.sdid
|
||||
or login_result.sdid
|
||||
or code_result.sdid,
|
||||
normalized_phone=normalized_phone,
|
||||
username=username,
|
||||
uid=uid,
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
证书格式:base64( [0x0c][key_idx][AES-128-ECB(key16, zeropad(P1))] )
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import base64
|
||||
@@ -48,7 +49,7 @@ def parse_p1(data: bytes) -> dict:
|
||||
|
||||
def tk(n: int) -> bytes:
|
||||
nonlocal o
|
||||
b = data[o:o + n]
|
||||
b = data[o : o + n]
|
||||
o += n
|
||||
return b
|
||||
|
||||
|
||||
@@ -29,9 +29,12 @@ FP_STATE_ROOT = Path(__file__).resolve().parents[2] / "data" / "huya_fp_states"
|
||||
|
||||
def account_state_dir(account: str) -> Path:
|
||||
"""账号专属指纹状态目录 (持久化, 保证同一账号多次登录是同一台'设备')。"""
|
||||
safe = "".join(c if c.isalnum() or c in "-_." else "_" for c in (account or "anon"))[:64]
|
||||
safe = "".join(
|
||||
c if c.isalnum() or c in "-_." else "_" for c in (account or "anon")
|
||||
)[:64]
|
||||
return FP_STATE_ROOT / safe
|
||||
|
||||
|
||||
DEFAULT_TIMEOUT = (8, 40)
|
||||
|
||||
|
||||
@@ -52,7 +55,9 @@ class HuyaSdidResult:
|
||||
HDID_PREFIX = "__HDID__"
|
||||
|
||||
|
||||
def _run_node_runner(state_dir: Path, app_id: str, timeout: tuple[float, float]) -> tuple[str, str]:
|
||||
def _run_node_runner(
|
||||
state_dir: Path, app_id: str, timeout: tuple[float, float]
|
||||
) -> tuple[str, str]:
|
||||
"""调用 node runner,返回 (sdid, hdid)。
|
||||
|
||||
若 state_dir/device.json 存在 (账号画像派生的设备覆盖参数), runner 会以该
|
||||
@@ -79,9 +84,9 @@ def _run_node_runner(state_dir: Path, app_id: str, timeout: tuple[float, float])
|
||||
for line in (proc.stdout or "").splitlines():
|
||||
line = line.strip()
|
||||
if line.startswith(SDID_PREFIX) and len(line) > len(SDID_PREFIX) + 20:
|
||||
sdid = line[len(SDID_PREFIX):]
|
||||
sdid = line[len(SDID_PREFIX) :]
|
||||
if line.startswith(HDID_PREFIX) and len(line) > len(HDID_PREFIX) + 20:
|
||||
hdid = line[len(HDID_PREFIX):]
|
||||
hdid = line[len(HDID_PREFIX) :]
|
||||
if sdid:
|
||||
return sdid, hdid
|
||||
stderr_tail = (proc.stderr or "").strip().splitlines()
|
||||
@@ -168,8 +173,9 @@ def get_huya_sdid(
|
||||
try:
|
||||
sdid, hdid = _run_node_runner(state_dir, app_id, timeout)
|
||||
if sdid:
|
||||
logger.debug("虎牙设备指纹成功(node): sdid={}... hdid={}...",
|
||||
sdid[:24], hdid[:10])
|
||||
logger.debug(
|
||||
"虎牙设备指纹成功(node): sdid={}... hdid={}...", sdid[:24], hdid[:10]
|
||||
)
|
||||
return HuyaSdidResult(sdid=sdid, hdid=hdid, source="fingerprint")
|
||||
except HuyaFingerprintError as exc:
|
||||
logger.warning("虎牙 hydevice 指纹失败: {}", exc)
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
- 固定: hdid(32hex 硬锚,全账号同一) / app_version / sdk_version;
|
||||
- 动态签发: safedeviceid、登录帧 device_id。
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import hashlib
|
||||
@@ -85,6 +86,7 @@ def record_login(account: str, ok: bool, message: str = "") -> None:
|
||||
供 app_login 登录流程调用, GUI 设备绑定页读取展示。
|
||||
"""
|
||||
import time as _time
|
||||
|
||||
db = _load_db()
|
||||
rec = db.get(account)
|
||||
if rec is None:
|
||||
@@ -112,7 +114,9 @@ def _load_db() -> dict:
|
||||
def _save_db(db: dict) -> None:
|
||||
try:
|
||||
PRIMARY_PROFILE_DB.parent.mkdir(parents=True, exist_ok=True)
|
||||
PRIMARY_PROFILE_DB.write_text(json.dumps(db, indent=2, ensure_ascii=False), encoding="utf-8")
|
||||
PRIMARY_PROFILE_DB.write_text(
|
||||
json.dumps(db, indent=2, ensure_ascii=False), encoding="utf-8"
|
||||
)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
@@ -130,8 +134,12 @@ def _enrich_profile(profile: dict) -> tuple[dict, bool]:
|
||||
out["guid32"] = hashlib.sha256(os.urandom(16) + b"guid").hexdigest()
|
||||
changed = True
|
||||
if len(out.get("hebe") or {}) < 5:
|
||||
out["hebe"] = {f"Hebe_D{i}": hashlib.sha256(os.urandom(16) + f"hebe{i}".encode()).hexdigest()
|
||||
for i in range(1, 6)}
|
||||
out["hebe"] = {
|
||||
f"Hebe_D{i}": hashlib.sha256(
|
||||
os.urandom(16) + f"hebe{i}".encode()
|
||||
).hexdigest()
|
||||
for i in range(1, 6)
|
||||
}
|
||||
changed = True
|
||||
return out, changed
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
基于 XXTEA 算法与 uid + k1 派生密钥。
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import hashlib
|
||||
@@ -27,13 +28,23 @@ def _xxtea_encrypt_words(v: list[int], k: list[int]) -> list[int]:
|
||||
p = 0
|
||||
while p < n - 1:
|
||||
y = v[p + 1]
|
||||
z = (v[p] + ((((z >> 5) ^ (y << 2)) + ((y >> 3) ^ (z << 4)))
|
||||
^ ((s ^ y) + (k[(p & 3) ^ e] ^ z)))) & 0xFFFFFFFF
|
||||
z = (
|
||||
v[p]
|
||||
+ (
|
||||
(((z >> 5) ^ (y << 2)) + ((y >> 3) ^ (z << 4)))
|
||||
^ ((s ^ y) + (k[(p & 3) ^ e] ^ z))
|
||||
)
|
||||
) & 0xFFFFFFFF
|
||||
v[p] = z
|
||||
p += 1
|
||||
y = v[0]
|
||||
z = (v[n - 1] + ((((z >> 5) ^ (y << 2)) + ((y >> 3) ^ (z << 4)))
|
||||
^ ((s ^ y) + (k[((n - 1) & 3) ^ e] ^ z)))) & 0xFFFFFFFF
|
||||
z = (
|
||||
v[n - 1]
|
||||
+ (
|
||||
(((z >> 5) ^ (y << 2)) + ((y >> 3) ^ (z << 4)))
|
||||
^ ((s ^ y) + (k[((n - 1) & 3) ^ e] ^ z))
|
||||
)
|
||||
) & 0xFFFFFFFF
|
||||
v[n - 1] = z
|
||||
q -= 1
|
||||
return v
|
||||
@@ -45,9 +56,9 @@ def xxtea_encrypt(data: bytes, key16: bytes) -> bytes:
|
||||
nwords = (n // 4) + 1
|
||||
v = [0] * nwords
|
||||
for i in range(n // 4):
|
||||
v[i] = struct.unpack("<I", data[i * 4:i * 4 + 4])[0]
|
||||
v[i] = struct.unpack("<I", data[i * 4 : i * 4 + 4])[0]
|
||||
v[nwords - 1] = n
|
||||
k = [struct.unpack("<I", key16[i * 4:i * 4 + 4])[0] for i in range(4)]
|
||||
k = [struct.unpack("<I", key16[i * 4 : i * 4 + 4])[0] for i in range(4)]
|
||||
_xxtea_encrypt_words(v, k)
|
||||
return b"".join(struct.pack("<I", w & 0xFFFFFFFF) for w in v)
|
||||
|
||||
|
||||
+145
-120
@@ -4,6 +4,7 @@
|
||||
|
||||
来源:m-shop.yaoguo.com 的 jce/ShopFacade.js、api/orderui.ts、api/mall/PlayMallNewHome.ts
|
||||
"""
|
||||
|
||||
from typing import List, Dict, Optional
|
||||
from .taf_protocol import TafOutputStream, TafInputStream, TafStruct, TafType
|
||||
|
||||
@@ -78,17 +79,19 @@ def _skip_to_struct_end(ins: TafInputStream):
|
||||
# 基础结构
|
||||
# ============================================================
|
||||
|
||||
|
||||
class UserId(TafStruct):
|
||||
"""用户标识(cookie 在这里)"""
|
||||
|
||||
def __init__(self):
|
||||
self.lUid: int = 0 # tag 0
|
||||
self.sGuid: str = "" # tag 1
|
||||
self.sToken: str = "" # tag 2
|
||||
self.sHuYaUA: str = "" # tag 3 "webh5&0.0.1&websocket&&diypc_52775"
|
||||
self.sCookie: str = "" # tag 4 完整 cookie
|
||||
self.iTokenType: int = 0 # tag 5
|
||||
self.sDeviceInfo: str = "" # tag 6
|
||||
self.sQIMEI: str = "" # tag 7
|
||||
self.lUid: int = 0 # tag 0
|
||||
self.sGuid: str = "" # tag 1
|
||||
self.sToken: str = "" # tag 2
|
||||
self.sHuYaUA: str = "" # tag 3 "webh5&0.0.1&websocket&&diypc_52775"
|
||||
self.sCookie: str = "" # tag 4 完整 cookie
|
||||
self.iTokenType: int = 0 # tag 5
|
||||
self.sDeviceInfo: str = "" # tag 6
|
||||
self.sQIMEI: str = "" # tag 7
|
||||
|
||||
def write_to(self, os: TafOutputStream):
|
||||
# HAR2 实证: 浏览器不优化空串/0值, 全写字段
|
||||
@@ -114,11 +117,12 @@ class UserId(TafStruct):
|
||||
|
||||
class ShopAppInfo(TafStruct):
|
||||
"""应用信息(HAR2 实证字段顺序: tag0 sAppId, tag1 sBizType, tag4 scene, tag5 sourceId)"""
|
||||
|
||||
def __init__(self):
|
||||
self.sAppId: str = "huya" # tag 0
|
||||
self.sBizType: str = "" # tag 1
|
||||
self.scene: int = 0 # tag 4
|
||||
self.sourceId: str = "" # tag 5
|
||||
self.sAppId: str = "huya" # tag 0
|
||||
self.sBizType: str = "" # tag 1
|
||||
self.scene: int = 0 # tag 4
|
||||
self.sourceId: str = "" # tag 5
|
||||
|
||||
def write_to(self, os: TafOutputStream):
|
||||
# HAR2 实证: 全写字段(含空串/0)
|
||||
@@ -138,14 +142,16 @@ class ShopAppInfo(TafStruct):
|
||||
# wsLaunch 初始化
|
||||
# ============================================================
|
||||
|
||||
|
||||
class WsLaunchSubStruct(TafStruct):
|
||||
"""wsLaunch tag4 子结构(5个空字符串字段,浏览器强制写)"""
|
||||
|
||||
def __init__(self):
|
||||
self.s0: str = "" # tag 0
|
||||
self.s1: str = "" # tag 1
|
||||
self.s2: str = "" # tag 2
|
||||
self.s3: str = "" # tag 3
|
||||
self.s4: str = "" # tag 4
|
||||
self.s0: str = "" # tag 0
|
||||
self.s1: str = "" # tag 1
|
||||
self.s2: str = "" # tag 2
|
||||
self.s3: str = "" # tag 3
|
||||
self.s4: str = "" # tag 4
|
||||
|
||||
def write_to(self, os: TafOutputStream):
|
||||
# 浏览器写空字符串(STRING1 length0),这里强制写以精确匹配
|
||||
@@ -169,11 +175,12 @@ class WsLaunchReq(TafStruct):
|
||||
tag3: appSrc "HUYA&ZH&2052"
|
||||
tag4: 子struct (5个空字符串)
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
self.lUid: int = 0 # tag 0
|
||||
self.s1: str = "" # tag 1
|
||||
self.lUid: int = 0 # tag 0
|
||||
self.s1: str = "" # tag 1
|
||||
self.sHuYaUA: str = "webh5&1.0.0&huya" # tag 2
|
||||
self.appSrc: str = "HUYA&ZH&2052" # tag 3
|
||||
self.appSrc: str = "HUYA&ZH&2052" # tag 3
|
||||
self.sub: WsLaunchSubStruct = WsLaunchSubStruct() # tag 4
|
||||
|
||||
def write_to(self, os: TafOutputStream):
|
||||
@@ -192,18 +199,20 @@ class WsLaunchReq(TafStruct):
|
||||
# 商品查询
|
||||
# ============================================================
|
||||
|
||||
|
||||
class GetGoodsInfoReqV5(TafStruct):
|
||||
"""商品查询请求 (shopMiddleUI.getGoodsInfoV5)"""
|
||||
|
||||
def __init__(self):
|
||||
self.userId = UserId() # tag 0
|
||||
self.userId = UserId() # tag 0
|
||||
self.shopAppInfo = ShopAppInfo() # tag 1
|
||||
self.pid: int = 0 # tag 2
|
||||
self.gameId: str = "" # tag 3
|
||||
self.spuId: str = "" # tag 4
|
||||
self.channelStockCode: str = "" # tag 5
|
||||
self.skuId: int = 0 # tag 6
|
||||
self.inviterUid: int = 0 # tag 7
|
||||
self.userModifyPriceId: int = 0 # tag 8
|
||||
self.pid: int = 0 # tag 2
|
||||
self.gameId: str = "" # tag 3
|
||||
self.spuId: str = "" # tag 4
|
||||
self.channelStockCode: str = "" # tag 5
|
||||
self.skuId: int = 0 # tag 6
|
||||
self.inviterUid: int = 0 # tag 7
|
||||
self.userModifyPriceId: int = 0 # tag 8
|
||||
|
||||
def write_to(self, os: TafOutputStream):
|
||||
# HAR2 实证: 全写字段
|
||||
@@ -224,13 +233,14 @@ class GetGoodsInfoReqV5(TafStruct):
|
||||
|
||||
class GoodsInfoRsp(TafStruct):
|
||||
"""商品查询响应。"""
|
||||
|
||||
def __init__(self):
|
||||
self.code: int = 0 # tag 0
|
||||
self.message: str = "" # tag 1
|
||||
self.goodsInfo = None # tag 2
|
||||
self.selfGoods: int = 0 # tag 3
|
||||
self.marketStatus: int = 0 # tag 5
|
||||
self.timestamp: int = 0 # tag 6
|
||||
self.code: int = 0 # tag 0
|
||||
self.message: str = "" # tag 1
|
||||
self.goodsInfo = None # tag 2
|
||||
self.selfGoods: int = 0 # tag 3
|
||||
self.marketStatus: int = 0 # tag 5
|
||||
self.timestamp: int = 0 # tag 6
|
||||
|
||||
def read_from(self, ins: TafInputStream):
|
||||
self.code = ins.read_int32(0, default=self.code)
|
||||
@@ -293,6 +303,7 @@ class GoodsInfoRsp(TafStruct):
|
||||
|
||||
class GoodsBaseInfo(TafStruct):
|
||||
"""商品基础信息(getGoodsInfoV5 tag2.tag0)。"""
|
||||
|
||||
def __init__(self):
|
||||
self.spuId: str = ""
|
||||
self.appId: str = ""
|
||||
@@ -328,6 +339,7 @@ class GoodsBaseInfo(TafStruct):
|
||||
|
||||
class GoodsSkuItem(TafStruct):
|
||||
"""商品 SKU 信息(getGoodsInfoV5 tag2.tag4.tag3 map value)。"""
|
||||
|
||||
def __init__(self):
|
||||
self.skuId: int = 0
|
||||
self.spuId: str = ""
|
||||
@@ -370,6 +382,7 @@ class GoodsSkuItem(TafStruct):
|
||||
|
||||
class GoodsPriceInfo(TafStruct):
|
||||
"""商品价格与 SKU 信息(getGoodsInfoV5 tag2.tag4)。"""
|
||||
|
||||
def __init__(self):
|
||||
self.spuId: str = ""
|
||||
self.minPrice: int = 0
|
||||
@@ -435,6 +448,7 @@ class GoodsPriceInfo(TafStruct):
|
||||
|
||||
class GoodsInfoDetail(TafStruct):
|
||||
"""getGoodsInfoV5 响应里的 goodsInfo 主体。"""
|
||||
|
||||
def __init__(self):
|
||||
self.baseInfo = GoodsBaseInfo()
|
||||
self.priceInfo = GoodsPriceInfo()
|
||||
@@ -457,8 +471,10 @@ class GoodsInfoDetail(TafStruct):
|
||||
# 订单历史
|
||||
# ============================================================
|
||||
|
||||
|
||||
class OrderListShopInfo(TafStruct):
|
||||
"""订单明细里的店铺信息(只取展示需要的字段)"""
|
||||
|
||||
def __init__(self):
|
||||
self.shopName: str = "" # tag 0
|
||||
|
||||
@@ -477,14 +493,15 @@ class OrderListShopInfo(TafStruct):
|
||||
|
||||
class OrderListGoodsDetail(TafStruct):
|
||||
"""订单明细(queryUserOrderList 响应 tag16)"""
|
||||
|
||||
def __init__(self):
|
||||
self.spuId: str = "" # tag 4
|
||||
self.skuId: int = 0 # tag 16
|
||||
self.buyerUid: int = 0 # tag 18
|
||||
self.virtualType: int = 0 # tag 19
|
||||
self.quantity: int = 0 # tag 20
|
||||
self.spuId: str = "" # tag 4
|
||||
self.skuId: int = 0 # tag 16
|
||||
self.buyerUid: int = 0 # tag 18
|
||||
self.virtualType: int = 0 # tag 19
|
||||
self.quantity: int = 0 # tag 20
|
||||
self.shopInfo: Optional[OrderListShopInfo] = None # tag 21
|
||||
self.points: int = 0 # tag 23
|
||||
self.points: int = 0 # tag 23
|
||||
|
||||
def read_from(self, ins: TafInputStream):
|
||||
self.spuId = ins.read_string(4, default=self.spuId)
|
||||
@@ -513,19 +530,20 @@ class OrderListGoodsDetail(TafStruct):
|
||||
|
||||
class OrderListItem(TafStruct):
|
||||
"""订单列表项(queryUserOrderList 响应 tag3 的 list 元素)"""
|
||||
|
||||
def __init__(self):
|
||||
self.bizOrderId: str = "" # tag 0 shop10148750
|
||||
self.appId: str = "" # tag 1 shop
|
||||
self.orderId: str = "" # tag 2
|
||||
self.pid: int = 0 # tag 3
|
||||
self.shopName: str = "" # tag 4
|
||||
self.orderStatus: int = 0 # tag 5
|
||||
self.itemName: str = "" # tag 8
|
||||
self.unitPrice: int = 0 # tag 9 分
|
||||
self.quantity: int = 0 # tag 10
|
||||
self.totalPrice: int = 0 # tag 12 分
|
||||
self.createTime: int = 0 # tag 14 毫秒时间戳
|
||||
self.payTime: int = 0 # tag 15 毫秒时间戳
|
||||
self.bizOrderId: str = "" # tag 0 shop10148750
|
||||
self.appId: str = "" # tag 1 shop
|
||||
self.orderId: str = "" # tag 2
|
||||
self.pid: int = 0 # tag 3
|
||||
self.shopName: str = "" # tag 4
|
||||
self.orderStatus: int = 0 # tag 5
|
||||
self.itemName: str = "" # tag 8
|
||||
self.unitPrice: int = 0 # tag 9 分
|
||||
self.quantity: int = 0 # tag 10
|
||||
self.totalPrice: int = 0 # tag 12 分
|
||||
self.createTime: int = 0 # tag 14 毫秒时间戳
|
||||
self.payTime: int = 0 # tag 15 毫秒时间戳
|
||||
self.goodsDetail: Optional[OrderListGoodsDetail] = None # tag 16
|
||||
|
||||
def read_from(self, ins: TafInputStream):
|
||||
@@ -567,12 +585,13 @@ class OrderListItem(TafStruct):
|
||||
|
||||
class QueryUserOrderListReq(TafStruct):
|
||||
"""查询购买历史订单 (revenueWebUI.queryUserOrderList)"""
|
||||
|
||||
def __init__(self):
|
||||
self.userId = UserId() # tag 0
|
||||
self.offset: int = 0 # tag 1
|
||||
self.orderType: int = 1 # tag 2
|
||||
self.pageSize: int = 10 # tag 3
|
||||
self.status: int = 0 # tag 4
|
||||
self.userId = UserId() # tag 0
|
||||
self.offset: int = 0 # tag 1
|
||||
self.orderType: int = 1 # tag 2
|
||||
self.pageSize: int = 10 # tag 3
|
||||
self.status: int = 0 # tag 4
|
||||
|
||||
def write_to(self, os: TafOutputStream):
|
||||
os.write_struct(0, self.userId)
|
||||
@@ -587,6 +606,7 @@ class QueryUserOrderListReq(TafStruct):
|
||||
|
||||
class QueryUserOrderListRsp(TafStruct):
|
||||
"""购买历史订单响应"""
|
||||
|
||||
def __init__(self):
|
||||
self.code: int = 0
|
||||
self.message: str = ""
|
||||
@@ -624,17 +644,18 @@ class QueryUserOrderListRsp(TafStruct):
|
||||
# 下单
|
||||
# ============================================================
|
||||
|
||||
|
||||
class CreateOrderExtraParam(TafStruct):
|
||||
def __init__(self):
|
||||
self.freight: int = 0 # tag 0
|
||||
self.channelStockType: str = "" # tag 1
|
||||
self.channelStockCode: str = "" # tag 2
|
||||
self.relatedBizId: str = "" # tag 3
|
||||
self.bizParams: str = "" # tag 4
|
||||
self.popupTraceId: str = "" # tag 5
|
||||
self.supplierUid: int = 0 # tag 6
|
||||
self.categoryId: str = "" # tag 7
|
||||
self.ext: str = "" # tag 8
|
||||
self.freight: int = 0 # tag 0
|
||||
self.channelStockType: str = "" # tag 1
|
||||
self.channelStockCode: str = "" # tag 2
|
||||
self.relatedBizId: str = "" # tag 3
|
||||
self.bizParams: str = "" # tag 4
|
||||
self.popupTraceId: str = "" # tag 5
|
||||
self.supplierUid: int = 0 # tag 6
|
||||
self.categoryId: str = "" # tag 7
|
||||
self.ext: str = "" # tag 8
|
||||
|
||||
def write_to(self, os: TafOutputStream):
|
||||
# HAR 实证:下单 extraParam 会强制写默认 0/空串字段
|
||||
@@ -655,9 +676,9 @@ class CreateOrderExtraParam(TafStruct):
|
||||
|
||||
class CreateOrderPromotionParam(TafStruct):
|
||||
def __init__(self):
|
||||
self.yxjDeductPrice: int = 0 # tag 0
|
||||
self.userModifyPriceId: int = 0 # tag 1
|
||||
self.enablePromotion: int = 1 # tag 2
|
||||
self.yxjDeductPrice: int = 0 # tag 0
|
||||
self.userModifyPriceId: int = 0 # tag 1
|
||||
self.enablePromotion: int = 1 # tag 2
|
||||
|
||||
def write_to(self, os: TafOutputStream):
|
||||
# HAR 实证:tag0/tag1 为 0,tag2 为 1
|
||||
@@ -671,11 +692,11 @@ class CreateOrderPromotionParam(TafStruct):
|
||||
|
||||
class CreateOrderAccountParam(TafStruct):
|
||||
def __init__(self):
|
||||
self.payoutTypeList: List[int] = [] # tag 0 Vector<INT32>
|
||||
self.payoutChargeAmount: int = 0 # tag 1
|
||||
self.cancelPayoutTypeList: List[int] = [] # tag 2
|
||||
self.recycleSupplierId: int = 0 # tag 3
|
||||
self.claimPrice: int = 0 # tag 4
|
||||
self.payoutTypeList: List[int] = [] # tag 0 Vector<INT32>
|
||||
self.payoutChargeAmount: int = 0 # tag 1
|
||||
self.cancelPayoutTypeList: List[int] = [] # tag 2
|
||||
self.recycleSupplierId: int = 0 # tag 3
|
||||
self.claimPrice: int = 0 # tag 4
|
||||
|
||||
def write_to(self, os: TafOutputStream):
|
||||
# HAR 实证:空 list/0 值也会写出
|
||||
@@ -691,8 +712,8 @@ class CreateOrderAccountParam(TafStruct):
|
||||
|
||||
class PromotionItem(TafStruct):
|
||||
def __init__(self):
|
||||
self.promotionId: int = 0 # tag 0
|
||||
self.promotionType: int = 0 # tag 1
|
||||
self.promotionId: int = 0 # tag 0
|
||||
self.promotionType: int = 0 # tag 1
|
||||
|
||||
def write_to(self, os: TafOutputStream):
|
||||
_opt_int(os, 0, self.promotionId)
|
||||
@@ -705,40 +726,41 @@ class PromotionItem(TafStruct):
|
||||
|
||||
class CreateOrderReqV5(TafStruct):
|
||||
"""下单请求 (shopMiddleUI.createOrderV5)"""
|
||||
|
||||
def __init__(self):
|
||||
self.userId = UserId() # tag 0
|
||||
self.shopAppInfo = ShopAppInfo() # tag 1
|
||||
self.receiveId: int = 0 # tag 2
|
||||
self.pid: int = 0 # tag 3
|
||||
self.skuId: int = 0 # tag 4
|
||||
self.itemCount: int = 1 # tag 5
|
||||
self.remark: str = "" # tag 6
|
||||
self.spuId: str = "" # tag 7
|
||||
self.gameId: str = "" # tag 8
|
||||
self.orderId: int = 0 # tag 9
|
||||
self.src: int = 0 # tag 10
|
||||
self.couponUserIds: List[int] = [] # tag 11 Vector<INT64>
|
||||
self.orderType: int = 0 # tag 12
|
||||
self.userId = UserId() # tag 0
|
||||
self.shopAppInfo = ShopAppInfo() # tag 1
|
||||
self.receiveId: int = 0 # tag 2
|
||||
self.pid: int = 0 # tag 3
|
||||
self.skuId: int = 0 # tag 4
|
||||
self.itemCount: int = 1 # tag 5
|
||||
self.remark: str = "" # tag 6
|
||||
self.spuId: str = "" # tag 7
|
||||
self.gameId: str = "" # tag 8
|
||||
self.orderId: int = 0 # tag 9
|
||||
self.src: int = 0 # tag 10
|
||||
self.couponUserIds: List[int] = [] # tag 11 Vector<INT64>
|
||||
self.orderType: int = 0 # tag 12
|
||||
self.extraParam: Optional[CreateOrderExtraParam] = None # tag 13
|
||||
self.scene: int = 0 # tag 14
|
||||
self.promotionItems: List = [] # tag 15 Vector<PromotionItem>
|
||||
self.sourceId: str = "" # tag 16
|
||||
self.env: Dict[str, str] = {} # tag 17 Map<STRING,STRING>
|
||||
self.orderScene: int = 0 # tag 18
|
||||
self.watchWord: str = "" # tag 19
|
||||
self.marketingChannel: str = "" # tag 20
|
||||
self.scene: int = 0 # tag 14
|
||||
self.promotionItems: List = [] # tag 15 Vector<PromotionItem>
|
||||
self.sourceId: str = "" # tag 16
|
||||
self.env: Dict[str, str] = {} # tag 17 Map<STRING,STRING>
|
||||
self.orderScene: int = 0 # tag 18
|
||||
self.watchWord: str = "" # tag 19
|
||||
self.marketingChannel: str = "" # tag 20
|
||||
self.promotionParam: Optional[CreateOrderPromotionParam] = None # tag 21
|
||||
self.externalTraceKey: str = "" # tag 22
|
||||
self.kefuUid: int = 0 # tag 23
|
||||
self.accountParam: Optional[CreateOrderAccountParam] = None # tag 24
|
||||
self.parentOrderId: int = 0 # tag 25
|
||||
self.vendorAccountType: str = "" # tag 26
|
||||
self.vendorAccountVal: str = "" # tag 27
|
||||
self.vendorSubAccountVal: str = "" # tag 28
|
||||
self.vendorSubAccountType: str = "" # tag 29
|
||||
self.bizType: int = 0 # tag 30
|
||||
self.gameCategoryId: int = 0 # tag 31
|
||||
self.ext: str = "" # tag 32
|
||||
self.externalTraceKey: str = "" # tag 22
|
||||
self.kefuUid: int = 0 # tag 23
|
||||
self.accountParam: Optional[CreateOrderAccountParam] = None # tag 24
|
||||
self.parentOrderId: int = 0 # tag 25
|
||||
self.vendorAccountType: str = "" # tag 26
|
||||
self.vendorAccountVal: str = "" # tag 27
|
||||
self.vendorSubAccountVal: str = "" # tag 28
|
||||
self.vendorSubAccountType: str = "" # tag 29
|
||||
self.bizType: int = 0 # tag 30
|
||||
self.gameCategoryId: int = 0 # tag 31
|
||||
self.ext: str = "" # tag 32
|
||||
|
||||
def write_to(self, os: TafOutputStream):
|
||||
# HAR 实证:createOrderV5 会写出完整字段,即使值为 0/空串/空 list
|
||||
@@ -782,13 +804,14 @@ class CreateOrderReqV5(TafStruct):
|
||||
|
||||
class CreateOrderRsp(TafStruct):
|
||||
"""下单响应 (shopMiddleUI.createOrderV5)"""
|
||||
|
||||
def __init__(self):
|
||||
self.code: int = 0 # tag 0
|
||||
self.message: str = "" # tag 1
|
||||
self.orderId: int = 0 # tag 2 虎牙订单号
|
||||
self.subOrderId: int = 0 # tag 4
|
||||
self.orderStatus: int = 0 # tag 5
|
||||
self.riskUrl: str = "" # tag 8 风控跳转URL(code!=200时可能有)
|
||||
self.code: int = 0 # tag 0
|
||||
self.message: str = "" # tag 1
|
||||
self.orderId: int = 0 # tag 2 虎牙订单号
|
||||
self.subOrderId: int = 0 # tag 4
|
||||
self.orderStatus: int = 0 # tag 5
|
||||
self.riskUrl: str = "" # tag 8 风控跳转URL(code!=200时可能有)
|
||||
|
||||
def read_from(self, ins: TafInputStream):
|
||||
self.code = ins.read_int32(0, default=self.code)
|
||||
@@ -806,19 +829,21 @@ class CreateOrderRsp(TafStruct):
|
||||
# 支付(payOrderSubmitV5)
|
||||
# ============================================================
|
||||
|
||||
|
||||
class PayOrderRes(TafStruct):
|
||||
"""
|
||||
发起支付响应 (shopMiddleUI.payOrderSubmitV5)
|
||||
结构从 state_shop_ts.js 的 payOrderRes 推断,tag 顺序按出现顺序
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
self.code: int = 0 # tag 0
|
||||
self.message: str = "" # tag 1
|
||||
self.orderId: int = 0 # tag 2 虎牙订单号
|
||||
self.appOrderId: str = "" # tag 3
|
||||
self.payOrderId: str = "" # tag 4 支付宝 payOrderId
|
||||
self.payUrl: str = "" # tag 5 支付宝下单URL(含sign)
|
||||
self.amount: int = 0 # tag 6
|
||||
self.code: int = 0 # tag 0
|
||||
self.message: str = "" # tag 1
|
||||
self.orderId: int = 0 # tag 2 虎牙订单号
|
||||
self.appOrderId: str = "" # tag 3
|
||||
self.payOrderId: str = "" # tag 4 支付宝 payOrderId
|
||||
self.payUrl: str = "" # tag 5 支付宝下单URL(含sign)
|
||||
self.amount: int = 0 # tag 6
|
||||
|
||||
def read_from(self, ins: TafInputStream):
|
||||
self.code = ins.read_int32(0, default=self.code)
|
||||
|
||||
+69
-52
@@ -8,6 +8,7 @@
|
||||
0x08 MAP 0x09 LIST 0x0a STRUCT_BEGIN 0x0b STRUCT_END
|
||||
0x0c ZERO 0x0d SIMPLE_LIST
|
||||
"""
|
||||
|
||||
import struct
|
||||
import io
|
||||
from typing import Any, Dict, List, Optional, Tuple
|
||||
@@ -24,16 +25,17 @@ class TafType:
|
||||
STRING4 = 0x07
|
||||
MAP = 0x08
|
||||
LIST = 0x09
|
||||
STRUCT_BEGIN = 0x0a
|
||||
STRUCT_END = 0x0b
|
||||
ZERO = 0x0c
|
||||
SIMPLE_LIST = 0x0d
|
||||
STRUCT_BEGIN = 0x0A
|
||||
STRUCT_END = 0x0B
|
||||
ZERO = 0x0C
|
||||
SIMPLE_LIST = 0x0D
|
||||
|
||||
|
||||
# ============================================================
|
||||
# 输出流(编码)
|
||||
# ============================================================
|
||||
|
||||
|
||||
class TafOutputStream:
|
||||
"""TAF 编码输出流"""
|
||||
|
||||
@@ -46,9 +48,9 @@ class TafOutputStream:
|
||||
# ---- head ----
|
||||
def write_head(self, tag: int, data_type: int):
|
||||
if tag < 15:
|
||||
self.buf.write(struct.pack('B', (tag << 4) | data_type))
|
||||
self.buf.write(struct.pack("B", (tag << 4) | data_type))
|
||||
else:
|
||||
self.buf.write(struct.pack('BB', 0xF0 | data_type, tag))
|
||||
self.buf.write(struct.pack("BB", 0xF0 | data_type, tag))
|
||||
|
||||
# ---- 整数(带自动优化) ----
|
||||
def write_int8(self, tag: int, value: int):
|
||||
@@ -56,28 +58,28 @@ class TafOutputStream:
|
||||
self.write_head(tag, TafType.ZERO)
|
||||
else:
|
||||
self.write_head(tag, TafType.INT8)
|
||||
self.buf.write(struct.pack('b', value))
|
||||
self.buf.write(struct.pack("b", value))
|
||||
|
||||
def write_int16(self, tag: int, value: int):
|
||||
if -128 <= value <= 127:
|
||||
self.write_int8(tag, value)
|
||||
else:
|
||||
self.write_head(tag, TafType.INT16)
|
||||
self.buf.write(struct.pack('>h', value))
|
||||
self.buf.write(struct.pack(">h", value))
|
||||
|
||||
def write_int32(self, tag: int, value: int):
|
||||
if -32768 <= value <= 32767:
|
||||
self.write_int16(tag, value)
|
||||
else:
|
||||
self.write_head(tag, TafType.INT32)
|
||||
self.buf.write(struct.pack('>i', value))
|
||||
self.buf.write(struct.pack(">i", value))
|
||||
|
||||
def write_int64(self, tag: int, value: int):
|
||||
if -2147483648 <= value <= 2147483647:
|
||||
self.write_int32(tag, value)
|
||||
else:
|
||||
self.write_head(tag, TafType.INT64)
|
||||
self.buf.write(struct.pack('>q', value))
|
||||
self.buf.write(struct.pack(">q", value))
|
||||
|
||||
def write_uint64(self, tag: int, value: int):
|
||||
"""uint64:超过 int32 范围用 INT64"""
|
||||
@@ -85,27 +87,27 @@ class TafOutputStream:
|
||||
self.write_int32(tag, value)
|
||||
else:
|
||||
self.write_head(tag, TafType.INT64)
|
||||
self.buf.write(struct.pack('>Q', value))
|
||||
self.buf.write(struct.pack(">Q", value))
|
||||
|
||||
# ---- 浮点 ----
|
||||
def write_float(self, tag: int, value: float):
|
||||
self.write_head(tag, TafType.FLOAT)
|
||||
self.buf.write(struct.pack('>f', value))
|
||||
self.buf.write(struct.pack(">f", value))
|
||||
|
||||
def write_double(self, tag: int, value: float):
|
||||
self.write_head(tag, TafType.DOUBLE)
|
||||
self.buf.write(struct.pack('>d', value))
|
||||
self.buf.write(struct.pack(">d", value))
|
||||
|
||||
# ---- 字符串 ----
|
||||
def write_string(self, tag: int, value: str):
|
||||
encoded = value.encode('utf-8')
|
||||
encoded = value.encode("utf-8")
|
||||
length = len(encoded)
|
||||
if length > 255:
|
||||
self.write_head(tag, TafType.STRING4)
|
||||
self.buf.write(struct.pack('>I', length))
|
||||
self.buf.write(struct.pack(">I", length))
|
||||
else:
|
||||
self.write_head(tag, TafType.STRING1)
|
||||
self.buf.write(struct.pack('B', length))
|
||||
self.buf.write(struct.pack("B", length))
|
||||
self.buf.write(encoded)
|
||||
|
||||
# ---- 字节数组 ----
|
||||
@@ -133,8 +135,9 @@ class TafOutputStream:
|
||||
self.write_struct_end()
|
||||
|
||||
# ---- Map ----
|
||||
def write_map(self, tag: int, value: Dict[Any, Any],
|
||||
key_writer=None, val_writer=None):
|
||||
def write_map(
|
||||
self, tag: int, value: Dict[Any, Any], key_writer=None, val_writer=None
|
||||
):
|
||||
self.write_head(tag, TafType.MAP)
|
||||
self.write_int32(0, len(value))
|
||||
for k, v in value.items():
|
||||
@@ -172,7 +175,7 @@ class TafOutputStream:
|
||||
self.write_map(tag, value)
|
||||
elif isinstance(value, (list, tuple)):
|
||||
self.write_list(tag, list(value))
|
||||
elif hasattr(value, 'write_to'):
|
||||
elif hasattr(value, "write_to"):
|
||||
self.write_struct(tag, value)
|
||||
else:
|
||||
raise TypeError(f"不支持的类型: {type(value)}")
|
||||
@@ -182,6 +185,7 @@ class TafOutputStream:
|
||||
# 输入流(解码)—— 完整实现,支持所有类型
|
||||
# ============================================================
|
||||
|
||||
|
||||
class TafInputStream:
|
||||
"""TAF 解码输入流"""
|
||||
|
||||
@@ -201,14 +205,14 @@ class TafInputStream:
|
||||
data = self.buf.read(1)
|
||||
if not data:
|
||||
raise EOFError("读取到文件末尾")
|
||||
b = struct.unpack('B', data)[0]
|
||||
b = struct.unpack("B", data)[0]
|
||||
tag = (b >> 4) & 0x0F
|
||||
data_type = b & 0x0F
|
||||
if tag == 15:
|
||||
data = self.buf.read(1)
|
||||
if not data:
|
||||
raise EOFError("读取 tag 扩展字节失败")
|
||||
tag = struct.unpack('B', data)[0]
|
||||
tag = struct.unpack("B", data)[0]
|
||||
return tag, data_type
|
||||
|
||||
# ---- 跳过 ----
|
||||
@@ -228,10 +232,10 @@ class TafInputStream:
|
||||
elif data_type == TafType.DOUBLE:
|
||||
self.buf.read(8)
|
||||
elif data_type == TafType.STRING1:
|
||||
length = struct.unpack('B', self.buf.read(1))[0]
|
||||
length = struct.unpack("B", self.buf.read(1))[0]
|
||||
self.buf.read(length)
|
||||
elif data_type == TafType.STRING4:
|
||||
length = struct.unpack('>I', self.buf.read(4))[0]
|
||||
length = struct.unpack(">I", self.buf.read(4))[0]
|
||||
self.buf.read(length)
|
||||
elif data_type == TafType.MAP:
|
||||
self._skip_map()
|
||||
@@ -258,13 +262,13 @@ class TafInputStream:
|
||||
if dtype == TafType.ZERO:
|
||||
return 0
|
||||
if dtype == TafType.INT8:
|
||||
return struct.unpack('b', self.buf.read(1))[0]
|
||||
return struct.unpack("b", self.buf.read(1))[0]
|
||||
if dtype == TafType.INT16:
|
||||
return struct.unpack('>h', self.buf.read(2))[0]
|
||||
return struct.unpack(">h", self.buf.read(2))[0]
|
||||
if dtype == TafType.INT32:
|
||||
return struct.unpack('>i', self.buf.read(4))[0]
|
||||
return struct.unpack(">i", self.buf.read(4))[0]
|
||||
if dtype == TafType.INT64:
|
||||
return struct.unpack('>q', self.buf.read(8))[0]
|
||||
return struct.unpack(">q", self.buf.read(8))[0]
|
||||
raise ValueError(f"期望整数, 实际 0x{dtype:02x}")
|
||||
|
||||
def _skip_struct(self):
|
||||
@@ -335,19 +339,23 @@ class TafInputStream:
|
||||
if dtype == TafType.ZERO:
|
||||
return 0
|
||||
if dtype == TafType.INT8:
|
||||
return struct.unpack('B', self.buf.read(1))[0]
|
||||
return struct.unpack("B", self.buf.read(1))[0]
|
||||
if dtype == TafType.INT16:
|
||||
return struct.unpack('>H', self.buf.read(2))[0]
|
||||
return struct.unpack(">H", self.buf.read(2))[0]
|
||||
if dtype == TafType.INT32:
|
||||
return struct.unpack('>I', self.buf.read(4))[0]
|
||||
return struct.unpack(">I", self.buf.read(4))[0]
|
||||
if dtype == TafType.INT64:
|
||||
return struct.unpack('>Q', self.buf.read(8))[0]
|
||||
return struct.unpack(">Q", self.buf.read(8))[0]
|
||||
raise ValueError(f"期望 uint, 实际 0x{dtype:02x}")
|
||||
|
||||
def read_boolean(self, tag: int, required: bool = False, default: bool = False) -> bool:
|
||||
def read_boolean(
|
||||
self, tag: int, required: bool = False, default: bool = False
|
||||
) -> bool:
|
||||
return bool(self.read_int8(tag, required, 1 if default else 0))
|
||||
|
||||
def read_float(self, tag: int, required: bool = False, default: float = 0.0) -> float:
|
||||
def read_float(
|
||||
self, tag: int, required: bool = False, default: float = 0.0
|
||||
) -> float:
|
||||
found = self._find_tag(tag, required)
|
||||
if not found:
|
||||
return default
|
||||
@@ -355,12 +363,14 @@ class TafInputStream:
|
||||
if dtype == TafType.ZERO:
|
||||
return 0.0
|
||||
if dtype == TafType.FLOAT:
|
||||
return struct.unpack('>f', self.buf.read(4))[0]
|
||||
return struct.unpack(">f", self.buf.read(4))[0]
|
||||
if dtype == TafType.DOUBLE:
|
||||
return struct.unpack('>d', self.buf.read(8))[0]
|
||||
return struct.unpack(">d", self.buf.read(8))[0]
|
||||
return float(self._read_int_value(dtype))
|
||||
|
||||
def read_double(self, tag: int, required: bool = False, default: float = 0.0) -> float:
|
||||
def read_double(
|
||||
self, tag: int, required: bool = False, default: float = 0.0
|
||||
) -> float:
|
||||
return self.read_float(tag, required, default)
|
||||
|
||||
def read_string(self, tag: int, required: bool = False, default: str = "") -> str:
|
||||
@@ -369,14 +379,16 @@ class TafInputStream:
|
||||
return default
|
||||
dtype = found[1]
|
||||
if dtype == TafType.STRING1:
|
||||
length = struct.unpack('B', self.buf.read(1))[0]
|
||||
length = struct.unpack("B", self.buf.read(1))[0]
|
||||
elif dtype == TafType.STRING4:
|
||||
length = struct.unpack('>I', self.buf.read(4))[0]
|
||||
length = struct.unpack(">I", self.buf.read(4))[0]
|
||||
else:
|
||||
raise ValueError(f"期望 string, 实际 0x{dtype:02x}")
|
||||
return self.buf.read(length).decode('utf-8', errors='replace')
|
||||
return self.buf.read(length).decode("utf-8", errors="replace")
|
||||
|
||||
def read_bytes(self, tag: int, required: bool = False, default: bytes = b'') -> bytes:
|
||||
def read_bytes(
|
||||
self, tag: int, required: bool = False, default: bytes = b""
|
||||
) -> bytes:
|
||||
found = self._find_tag(tag, required)
|
||||
if not found:
|
||||
return default
|
||||
@@ -388,8 +400,9 @@ class TafInputStream:
|
||||
return self.buf.read(length)
|
||||
|
||||
# ---- 复合类型 ----
|
||||
def read_map(self, tag: int, required: bool = False,
|
||||
key_reader=None, val_reader=None) -> Dict:
|
||||
def read_map(
|
||||
self, tag: int, required: bool = False, key_reader=None, val_reader=None
|
||||
) -> Dict:
|
||||
found = self._find_tag(tag, required)
|
||||
if not found:
|
||||
return {}
|
||||
@@ -405,8 +418,7 @@ class TafInputStream:
|
||||
result[k] = v
|
||||
return result
|
||||
|
||||
def read_list(self, tag: int, required: bool = False,
|
||||
item_reader=None) -> List:
|
||||
def read_list(self, tag: int, required: bool = False, item_reader=None) -> List:
|
||||
found = self._find_tag(tag, required)
|
||||
if not found:
|
||||
return []
|
||||
@@ -439,13 +451,18 @@ class TafInputStream:
|
||||
return reader(self, 0)
|
||||
# 自动推断
|
||||
if dtype == TafType.STRING1:
|
||||
length = struct.unpack('B', self.buf.read(1))[0]
|
||||
return self.buf.read(length).decode('utf-8', errors='replace')
|
||||
length = struct.unpack("B", self.buf.read(1))[0]
|
||||
return self.buf.read(length).decode("utf-8", errors="replace")
|
||||
if dtype == TafType.STRING4:
|
||||
length = struct.unpack('>I', self.buf.read(4))[0]
|
||||
return self.buf.read(length).decode('utf-8', errors='replace')
|
||||
if dtype in (TafType.ZERO, TafType.INT8, TafType.INT16,
|
||||
TafType.INT32, TafType.INT64):
|
||||
length = struct.unpack(">I", self.buf.read(4))[0]
|
||||
return self.buf.read(length).decode("utf-8", errors="replace")
|
||||
if dtype in (
|
||||
TafType.ZERO,
|
||||
TafType.INT8,
|
||||
TafType.INT16,
|
||||
TafType.INT32,
|
||||
TafType.INT64,
|
||||
):
|
||||
return self._read_int_value(dtype)
|
||||
if dtype == TafType.STRUCT_BEGIN:
|
||||
# 未知 struct,跳过
|
||||
@@ -459,6 +476,7 @@ class TafInputStream:
|
||||
# 结构体基类
|
||||
# ============================================================
|
||||
|
||||
|
||||
class TafStruct:
|
||||
"""TAF 结构体基类:子类实现 write_to / read_from"""
|
||||
|
||||
@@ -470,8 +488,7 @@ class TafStruct:
|
||||
|
||||
def to_dict(self) -> Dict[str, Any]:
|
||||
"""调试用:转字典"""
|
||||
return {k: v for k, v in self.__dict__.items()
|
||||
if not k.startswith('_')}
|
||||
return {k: v for k, v in self.__dict__.items() if not k.startswith("_")}
|
||||
|
||||
def __repr__(self):
|
||||
return f"{self.__class__.__name__}({self.to_dict()})"
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
基于 AES-128-ECB 与 0 填充。
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from Crypto.Cipher import AES
|
||||
|
||||
+37
-13
@@ -134,12 +134,18 @@ def make_user_action(now_ms: int | None = None) -> str:
|
||||
"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))},
|
||||
{
|
||||
"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,
|
||||
@@ -152,10 +158,17 @@ def make_trace_id(pid: int = 0) -> str:
|
||||
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:
|
||||
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)
|
||||
@@ -179,7 +192,9 @@ def _build_wup_data(w: _Writer, uid_str: str, sha1_password: str,
|
||||
# -- t1: 设备信息 struct --
|
||||
di = device_info
|
||||
w.struct_begin(1)
|
||||
w.string(0, hdid) # t1.t0 = HDID32 (登录帧32hex设备证书, libhydeviceid.so 硬锚, 换→APP_SIGN_NOT_MATCH)
|
||||
w.string(
|
||||
0, hdid
|
||||
) # t1.t0 = HDID32 (登录帧32hex设备证书, libhydeviceid.so 硬锚, 换→APP_SIGN_NOT_MATCH)
|
||||
w.string(1, di.get("app_version", "13.4.22"))
|
||||
w.string(2, di.get("sdk_version", "1.0.80138"))
|
||||
w.string(3, "")
|
||||
@@ -224,8 +239,17 @@ def build_password_login_wup(
|
||||
) -> bytes:
|
||||
"""构造密码登录的 WUP TAF 请求体。"""
|
||||
wd = _Writer()
|
||||
_build_wup_data(wd, uid_str, sha1_password, safedeviceid, hdid,
|
||||
session, trace_id, user_action_json, device_info)
|
||||
_build_wup_data(
|
||||
wd,
|
||||
uid_str,
|
||||
sha1_password,
|
||||
safedeviceid,
|
||||
hdid,
|
||||
session,
|
||||
trace_id,
|
||||
user_action_json,
|
||||
device_info,
|
||||
)
|
||||
wup_data = wd.get()
|
||||
|
||||
req = _Writer()
|
||||
|
||||
+26
-20
@@ -9,6 +9,7 @@ Wup 包结构:
|
||||
tag7:sBuffer(bytes), tag8:iTimeout, tag9:context(map), tag10:status(map)
|
||||
sBuffer = Map<"tReq", 编码后的请求结构体>
|
||||
"""
|
||||
|
||||
import struct
|
||||
from typing import Any, Dict, Optional
|
||||
from .taf_protocol import TafOutputStream, TafInputStream, TafType, TafStruct
|
||||
@@ -18,16 +19,16 @@ class WupRequest:
|
||||
"""Wup 请求对象"""
|
||||
|
||||
def __init__(self):
|
||||
self.iVersion: int = 3 # tag 1
|
||||
self.cPacketType: int = 0 # tag 2
|
||||
self.iMessageType: int = 0 # tag 3
|
||||
self.iRequestId: int = 0 # tag 4
|
||||
self.sServantName: str = "" # tag 5
|
||||
self.sFuncName: str = "" # tag 6
|
||||
self.sBuffer: bytes = b'' # tag 7
|
||||
self.iTimeout: int = 3000 # tag 8
|
||||
self.context: Dict[str, str] = {} # tag 9
|
||||
self.status: Dict[str, str] = {} # tag 10
|
||||
self.iVersion: int = 3 # tag 1
|
||||
self.cPacketType: int = 0 # tag 2
|
||||
self.iMessageType: int = 0 # tag 3
|
||||
self.iRequestId: int = 0 # tag 4
|
||||
self.sServantName: str = "" # tag 5
|
||||
self.sFuncName: str = "" # tag 6
|
||||
self.sBuffer: bytes = b"" # tag 7
|
||||
self.iTimeout: int = 3000 # tag 8
|
||||
self.context: Dict[str, str] = {} # tag 9
|
||||
self.status: Dict[str, str] = {} # tag 10
|
||||
self.newdata: Dict[str, bytes] = {}
|
||||
|
||||
def setServant(self, name: str):
|
||||
@@ -49,7 +50,7 @@ class WupRequest:
|
||||
"""
|
||||
os = TafOutputStream()
|
||||
|
||||
if isinstance(struct_data, TafStruct) or hasattr(struct_data, 'write_to'):
|
||||
if isinstance(struct_data, TafStruct) or hasattr(struct_data, "write_to"):
|
||||
# 结构体对象:STRUCT_BEGIN + 内容 + STRUCT_END
|
||||
os.write_struct(0, struct_data)
|
||||
elif isinstance(struct_data, dict):
|
||||
@@ -83,7 +84,7 @@ class WupRequest:
|
||||
os.write_map(tag, value)
|
||||
elif isinstance(value, (list, tuple)):
|
||||
os.write_list(tag, list(value))
|
||||
elif hasattr(value, 'write_to'):
|
||||
elif hasattr(value, "write_to"):
|
||||
os.write_struct(tag, value)
|
||||
else:
|
||||
raise TypeError(f"不支持的字段类型: {type(value)}")
|
||||
@@ -115,14 +116,14 @@ class WupRequest:
|
||||
|
||||
# 3. 长度前缀
|
||||
length = 4 + len(wup_body)
|
||||
return struct.pack('>I', length) + wup_body
|
||||
return struct.pack(">I", length) + wup_body
|
||||
|
||||
|
||||
def normalize_wup_payload(data: bytes) -> bytes:
|
||||
"""去掉可选的 4 字节 WUP 长度前缀,返回裸 WUP body"""
|
||||
if len(data) < 4:
|
||||
return data
|
||||
declared_len = struct.unpack('>I', data[0:4])[0]
|
||||
declared_len = struct.unpack(">I", data[0:4])[0]
|
||||
if declared_len == len(data) or declared_len + 4 == len(data):
|
||||
return data[4:]
|
||||
return data
|
||||
@@ -138,7 +139,7 @@ class WupResponse:
|
||||
self.iRequestId: int = 0
|
||||
self.sServantName: str = ""
|
||||
self.sFuncName: str = ""
|
||||
self.sBuffer: bytes = b''
|
||||
self.sBuffer: bytes = b""
|
||||
self.iTimeout: int = 0
|
||||
self.context: Dict[str, str] = {}
|
||||
self.status: Dict[str, str] = {}
|
||||
@@ -249,19 +250,20 @@ class WupResponse:
|
||||
# 辅助:按已知 dtype 读取值
|
||||
# ============================================================
|
||||
|
||||
|
||||
def _read_string_value(ins: TafInputStream, dtype: int) -> str:
|
||||
if dtype == TafType.STRING1:
|
||||
length = struct.unpack('B', ins.buf.read(1))[0]
|
||||
length = struct.unpack("B", ins.buf.read(1))[0]
|
||||
elif dtype == TafType.STRING4:
|
||||
length = struct.unpack('>I', ins.buf.read(4))[0]
|
||||
length = struct.unpack(">I", ins.buf.read(4))[0]
|
||||
else:
|
||||
return ""
|
||||
return ins.buf.read(length).decode('utf-8', errors='replace')
|
||||
return ins.buf.read(length).decode("utf-8", errors="replace")
|
||||
|
||||
|
||||
def _read_bytes_value(ins: TafInputStream, dtype: int) -> bytes:
|
||||
if dtype != TafType.SIMPLE_LIST:
|
||||
return b''
|
||||
return b""
|
||||
ins.read_head() # 元素类型 INT8
|
||||
length = ins._read_int_len()
|
||||
return ins.buf.read(length)
|
||||
@@ -276,6 +278,10 @@ def _read_map_value(ins: TafInputStream, dtype: int) -> Dict:
|
||||
_, kt = ins.read_head()
|
||||
k = _read_string_value(ins, kt)
|
||||
_, vt = ins.read_head()
|
||||
v = _read_string_value(ins, vt) if vt in (TafType.STRING1, TafType.STRING4) else ""
|
||||
v = (
|
||||
_read_string_value(ins, vt)
|
||||
if vt in (TafType.STRING1, TafType.STRING4)
|
||||
else ""
|
||||
)
|
||||
result[k] = v
|
||||
return result
|
||||
|
||||
Reference in New Issue
Block a user