type: 收敛测试 schemas 与协议层类型
This commit is contained in:
+39
-2
@@ -1,8 +1,37 @@
|
||||
"""虎牙协议与业务客户端。"""
|
||||
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from .http_client import HuyaHttpClient
|
||||
from .wss_client import HuyaWssClient
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from .activity_structs import GetUserScoreReq, GetUserScoreResp
|
||||
from .app_login import (
|
||||
HuyaAppLoginError,
|
||||
HuyaAppPasswordLogin,
|
||||
HuyaAppQrAuthRequiredError,
|
||||
login_huya_app_password,
|
||||
)
|
||||
from .login import (
|
||||
HuyaCredentialError,
|
||||
HuyaLoginError,
|
||||
HuyaLoginResult,
|
||||
HuyaPasswordLogin,
|
||||
login_huya_password,
|
||||
)
|
||||
from .sms_login import (
|
||||
HuyaSmsCodeResult,
|
||||
HuyaSmsLogin,
|
||||
login_huya_sms,
|
||||
send_huya_sms_code,
|
||||
)
|
||||
from .verification import (
|
||||
HuyaVerificationError,
|
||||
HuyaVerificationSolver,
|
||||
solve_huya_verification,
|
||||
)
|
||||
|
||||
__all__ = [
|
||||
"HuyaHttpClient",
|
||||
"HuyaWssClient",
|
||||
@@ -103,8 +132,16 @@ def __getattr__(name: str):
|
||||
}
|
||||
globals().update(values)
|
||||
return values[name]
|
||||
if name in {"HuyaVerificationError", "HuyaVerificationSolver", "solve_huya_verification"}:
|
||||
from .verification import HuyaVerificationError, HuyaVerificationSolver, solve_huya_verification
|
||||
if name in {
|
||||
"HuyaVerificationError",
|
||||
"HuyaVerificationSolver",
|
||||
"solve_huya_verification",
|
||||
}:
|
||||
from .verification import (
|
||||
HuyaVerificationError,
|
||||
HuyaVerificationSolver,
|
||||
solve_huya_verification,
|
||||
)
|
||||
|
||||
values = {
|
||||
"HuyaVerificationError": HuyaVerificationError,
|
||||
|
||||
+44
-19
@@ -18,6 +18,7 @@
|
||||
- 滑块 UA / session / traceId 用金样本常量 -> 与随机机型画像不自洽;
|
||||
- qr_auth(扫码)/dx_auth(短信) 无自动闭环 -> 碰上直接失败(QR_AUTH_REQUIRED)。
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import base64
|
||||
@@ -176,13 +177,13 @@ def parse_cred(resp: bytes) -> bytes | None:
|
||||
e = resp.find(b"_wup_header")
|
||||
if s < 0 or e < 0:
|
||||
return None
|
||||
d = resp[s:e - 6]
|
||||
d = resp[s : e - 6]
|
||||
m = re.search(rb"\x3d\x00([\x00-\x03])(.)", d)
|
||||
if not m:
|
||||
return None
|
||||
ln = m.group(2)[0]
|
||||
st = m.start() + 4
|
||||
cred = d[st:st + ln]
|
||||
cred = d[st : st + ln]
|
||||
if len(cred) == 114 and cred[:1] == b"\x0a":
|
||||
return cred
|
||||
return None
|
||||
@@ -209,7 +210,10 @@ def solve_safe_auth(risk_url: str, proxies=None, max_retry: int = 3) -> dict:
|
||||
HuyaVerificationSolver,
|
||||
)
|
||||
|
||||
q = {k: v[0] for k, v in parse_qs(urlparse(risk_url).query, keep_blank_values=True).items()}
|
||||
q = {
|
||||
k: v[0]
|
||||
for k, v in parse_qs(urlparse(risk_url).query, keep_blank_values=True).items()
|
||||
}
|
||||
app_id = str(q.get("appId") or "5002")
|
||||
last_err: Exception | None = None
|
||||
for attempt in range(max_retry):
|
||||
@@ -287,7 +291,11 @@ def login_cred_with_flow(
|
||||
return cred, uid
|
||||
risk_url = parse_risk_url(resp)
|
||||
if risk_url:
|
||||
kind = "pt_auth(滑块)" if "pt_auth" in risk_url else ("qr_auth(扫码)" if "qr_auth" in risk_url else "未知")
|
||||
kind = (
|
||||
"pt_auth(滑块)"
|
||||
if "pt_auth" in risk_url
|
||||
else ("qr_auth(扫码)" if "qr_auth" in risk_url else "未知")
|
||||
)
|
||||
logger.info(f"[huya-app] 第 {rnd + 1} 轮触发安全验证: {kind}")
|
||||
if "qr_auth" in risk_url:
|
||||
raise HuyaAppQrAuthRequiredError(
|
||||
@@ -317,12 +325,14 @@ class QrRole:
|
||||
self.context = f"{prefix}-{ctx_hex}-{tail}"
|
||||
self.page_id = random.randint(40_000_000, 41_000_000)
|
||||
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,
|
||||
"Origin": UDB_BASE,
|
||||
"content-type": "application/json;charset=UTF-8",
|
||||
"Accept": "*/*",
|
||||
})
|
||||
self.s.headers.update(
|
||||
{
|
||||
"User-Agent": UA_PC if pc else APP_UA_MOBILE,
|
||||
"Origin": UDB_BASE,
|
||||
"content-type": "application/json;charset=UTF-8",
|
||||
"Accept": "*/*",
|
||||
}
|
||||
)
|
||||
|
||||
def _headers(self, uri: str) -> dict:
|
||||
mid = "2.6" if self.pc else "2.5"
|
||||
@@ -334,7 +344,9 @@ class QrRole:
|
||||
"Referer": f"{UDB_BASE}/web/middle/{mid}/{self.page_id}/https/{self.context.split('-')[1]}",
|
||||
}
|
||||
|
||||
def call(self, path: str, uri: str, data: dict, cookies: dict | None = None) -> dict:
|
||||
def call(
|
||||
self, path: str, uri: str, data: dict, cookies: dict | None = None
|
||||
) -> dict:
|
||||
envelope = {
|
||||
"uri": uri,
|
||||
"version": "2.6" if self.pc else "2.5",
|
||||
@@ -389,13 +401,16 @@ class HuyaAppPasswordLogin:
|
||||
self.proxies = dict(proxies) if proxies else None
|
||||
self.timeout = timeout or (10.0, 25.0)
|
||||
self.force_new_device = force_new_device
|
||||
self.device_info = device_info or get_profile(self.username, force_new=force_new_device)
|
||||
self.device_info = device_info or get_profile(
|
||||
self.username, force_new=force_new_device
|
||||
)
|
||||
|
||||
def login(self) -> HuyaLoginResult:
|
||||
"""执行完整 App 登录获取 Cookie 流程 (成功/失败均记录登录时间 → 设备绑定页)。"""
|
||||
result = self._login_impl()
|
||||
try:
|
||||
from .device_profile import record_login
|
||||
|
||||
record_login(self.username, result.success, result.message)
|
||||
except Exception: # 元数据记录失败不影响登录结果
|
||||
pass
|
||||
@@ -404,7 +419,9 @@ class HuyaAppPasswordLogin:
|
||||
def _login_impl(self) -> HuyaLoginResult:
|
||||
"""登录主体 (原 login)。"""
|
||||
acct = self.username
|
||||
logger.info(f"[huya-app] 开始登录账号 {acct} (机型: {self.device_info.get('model')})...")
|
||||
logger.info(
|
||||
f"[huya-app] 开始登录账号 {acct} (机型: {self.device_info.get('model')})..."
|
||||
)
|
||||
|
||||
# 1) 获取新鲜 cred 与 真实 uid (自动过 safe_auth 滑块)
|
||||
try:
|
||||
@@ -442,7 +459,9 @@ class HuyaAppPasswordLogin:
|
||||
|
||||
# 3) 信封补丁
|
||||
raw = bytearray(env.raw)
|
||||
raw[env.cert_off:env.cert_off + env.cert_len] = cert.encode("ascii")
|
||||
if env.cert_off is None or env.uid_off is None:
|
||||
raise ValueError("信封缺少证书或 uid 偏移")
|
||||
raw[env.cert_off : env.cert_off + env.cert_len] = cert.encode("ascii")
|
||||
if env.uid != uid:
|
||||
struct.pack_into(">Q", raw, env.uid_off, uid)
|
||||
wup = base64.b64encode(bytes(raw)).decode("ascii")
|
||||
@@ -457,9 +476,11 @@ class HuyaAppPasswordLogin:
|
||||
try:
|
||||
# 一号一设备: sdid 状态按账号隔离 (默认全局目录会让所有账号共享
|
||||
# 同一份 hydevice 设备状态, 服务端可跨账号关联 — 见 R40 缺口修复)
|
||||
sdid_obj = get_huya_sdid(allow_fallback=True,
|
||||
state_dir=account_state_dir(self.username),
|
||||
device_hint=self.device_info)
|
||||
sdid_obj = get_huya_sdid(
|
||||
allow_fallback=True,
|
||||
state_dir=account_state_dir(self.username),
|
||||
device_hint=self.device_info,
|
||||
)
|
||||
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)
|
||||
@@ -502,7 +523,9 @@ class HuyaAppPasswordLogin:
|
||||
},
|
||||
)
|
||||
if r2.get("returnCode") not in (0, "0", None) and r2.get("returnCode") != 0:
|
||||
logger.warning(f"[huya-app] bind 返回码: {r2.get('returnCode')} msg: {r2.get('message')}")
|
||||
logger.warning(
|
||||
f"[huya-app] bind 返回码: {r2.get('returnCode')} msg: {r2.get('message')}"
|
||||
)
|
||||
|
||||
# 4.3 轮询 tryQrLogin
|
||||
biztoken = None
|
||||
@@ -552,7 +575,9 @@ class HuyaAppPasswordLogin:
|
||||
code="COOKIE_INCOMPLETE",
|
||||
)
|
||||
|
||||
logger.info(f"[huya-app] 账号 {acct} 登录成功,获取完整 Cookie ({len(cookie_str)}B)")
|
||||
logger.info(
|
||||
f"[huya-app] 账号 {acct} 登录成功,获取完整 Cookie ({len(cookie_str)}B)"
|
||||
)
|
||||
return HuyaLoginResult(
|
||||
success=True,
|
||||
cookie=cookie_str,
|
||||
|
||||
@@ -75,7 +75,9 @@ def generate_huya_password(prefix: str = "hy", random_length: int = 8) -> str:
|
||||
"""生成适合虎牙账号使用的随机密码。"""
|
||||
clean_prefix = "".join(ch for ch in str(prefix or "hy") if ch.isalnum())[:8] or "hy"
|
||||
alphabet = string.ascii_lowercase + string.digits
|
||||
suffix = "".join(random.SystemRandom().choice(alphabet) for _ in range(max(6, random_length)))
|
||||
suffix = "".join(
|
||||
random.SystemRandom().choice(alphabet) for _ in range(max(6, random_length))
|
||||
)
|
||||
return f"{clean_prefix}{suffix}"
|
||||
|
||||
|
||||
@@ -86,13 +88,15 @@ def encode_change_password_behavior(stage: str) -> str:
|
||||
actions: list[dict] = []
|
||||
|
||||
if stage in {"send", "submit"}:
|
||||
actions.append({
|
||||
"id": "pmodify.btn.getsms",
|
||||
"x": random.randint(470, 520),
|
||||
"y": random.randint(120, 145),
|
||||
"d": elapsed,
|
||||
"time": now,
|
||||
})
|
||||
actions.append(
|
||||
{
|
||||
"id": "pmodify.btn.getsms",
|
||||
"x": random.randint(470, 520),
|
||||
"y": random.randint(120, 145),
|
||||
"d": elapsed,
|
||||
"time": now,
|
||||
}
|
||||
)
|
||||
if stage == "submit":
|
||||
for action_id in ("pmodify.input.sms", "pmodify.input.pw", "pmodify.input.sms"):
|
||||
now += random.randint(600, 3200)
|
||||
@@ -100,20 +104,24 @@ def encode_change_password_behavior(stage: str) -> str:
|
||||
actions.append({"id": action_id, "d": elapsed, "time": now})
|
||||
now += random.randint(800, 2400)
|
||||
elapsed += random.randint(800, 2400)
|
||||
actions.append({
|
||||
"id": "pmodify.btn.submit",
|
||||
"x": random.randint(395, 445),
|
||||
"y": random.randint(220, 245),
|
||||
"d": elapsed,
|
||||
"time": now,
|
||||
})
|
||||
actions.append(
|
||||
{
|
||||
"id": "pmodify.btn.submit",
|
||||
"x": random.randint(395, 445),
|
||||
"y": random.randint(220, 245),
|
||||
"d": elapsed,
|
||||
"time": now,
|
||||
}
|
||||
)
|
||||
|
||||
value = {
|
||||
"furl": CHANGE_PASSWORD_FROM_URL,
|
||||
"curl": CHANGE_PASSWORD_PAGE_URL,
|
||||
"user_action": actions,
|
||||
}
|
||||
return quote(json.dumps(value, separators=(",", ":"), ensure_ascii=False), safe="~()*!.'")
|
||||
return quote(
|
||||
json.dumps(value, separators=(",", ":"), ensure_ascii=False), safe="~()*!.'"
|
||||
)
|
||||
|
||||
|
||||
def _sleep_or_stop(stop_event: threading.Event | None, seconds: float) -> bool:
|
||||
@@ -132,7 +140,13 @@ def _payload_data(payload: dict) -> dict:
|
||||
|
||||
def _session_data(payload: dict, fallback: str = "") -> str:
|
||||
data = _payload_data(payload)
|
||||
return str(data.get("sessionData") or data.get("sessiondata") or payload.get("sessionData") or fallback or "")
|
||||
return str(
|
||||
data.get("sessionData")
|
||||
or data.get("sessiondata")
|
||||
or payload.get("sessionData")
|
||||
or fallback
|
||||
or ""
|
||||
)
|
||||
|
||||
|
||||
class HuyaPasswordChanger:
|
||||
@@ -174,23 +188,25 @@ class HuyaPasswordChanger:
|
||||
self._setup_headers()
|
||||
|
||||
def _setup_headers(self) -> None:
|
||||
self.session.headers.update({
|
||||
"Accept": "application/json, text/plain, */*",
|
||||
"Accept-Language": "zh-CN,zh;q=0.9",
|
||||
"Cache-Control": "no-cache",
|
||||
"Connection": "keep-alive",
|
||||
"Content-Type": "application/json;charset=UTF-8",
|
||||
"Origin": "https://udbreg.huya.com",
|
||||
"Pragma": "no-cache",
|
||||
"Referer": self.middle_url,
|
||||
"Sec-Fetch-Dest": "empty",
|
||||
"Sec-Fetch-Mode": "cors",
|
||||
"Sec-Fetch-Site": "same-origin",
|
||||
"User-Agent": self.ua,
|
||||
"sec-ch-ua": '"Not;A=Brand";v="8", "Chromium";v="150", "Google Chrome";v="150"',
|
||||
"sec-ch-ua-mobile": "?0",
|
||||
"sec-ch-ua-platform": '"macOS"',
|
||||
})
|
||||
self.session.headers.update(
|
||||
{
|
||||
"Accept": "application/json, text/plain, */*",
|
||||
"Accept-Language": "zh-CN,zh;q=0.9",
|
||||
"Cache-Control": "no-cache",
|
||||
"Connection": "keep-alive",
|
||||
"Content-Type": "application/json;charset=UTF-8",
|
||||
"Origin": "https://udbreg.huya.com",
|
||||
"Pragma": "no-cache",
|
||||
"Referer": self.middle_url,
|
||||
"Sec-Fetch-Dest": "empty",
|
||||
"Sec-Fetch-Mode": "cors",
|
||||
"Sec-Fetch-Site": "same-origin",
|
||||
"User-Agent": self.ua,
|
||||
"sec-ch-ua": '"Not;A=Brand";v="8", "Chromium";v="150", "Google Chrome";v="150"',
|
||||
"sec-ch-ua-mobile": "?0",
|
||||
"sec-ch-ua-platform": '"macOS"',
|
||||
}
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def _safe_url(url: str) -> str:
|
||||
@@ -199,7 +215,9 @@ class HuyaPasswordChanger:
|
||||
|
||||
def _request_json(self, method: str, url: str, source: str, **kwargs) -> dict:
|
||||
response = self.session.request(method, url, timeout=self.timeout, **kwargs)
|
||||
logger.debug(f"{method.upper()} {self._safe_url(url)} -> {response.status_code}")
|
||||
logger.debug(
|
||||
f"{method.upper()} {self._safe_url(url)} -> {response.status_code}"
|
||||
)
|
||||
response.raise_for_status()
|
||||
try:
|
||||
return response.json()
|
||||
@@ -216,20 +234,26 @@ class HuyaPasswordChanger:
|
||||
logger.debug(f"GET {self._safe_url(url)} -> {response.status_code}")
|
||||
except requests.RequestException as exc:
|
||||
logger.debug(f"虎牙改密 middle 初始化失败: {host}: {exc}")
|
||||
self.session.headers.update({
|
||||
"Origin": "https://udbreg.huya.com",
|
||||
"Referer": self.middle_url,
|
||||
})
|
||||
self.session.headers.update(
|
||||
{
|
||||
"Origin": "https://udbreg.huya.com",
|
||||
"Referer": self.middle_url,
|
||||
}
|
||||
)
|
||||
|
||||
def prepare_device(self) -> str:
|
||||
"""获取虎牙风控 sdid。"""
|
||||
token_payload = {"encryptVersion": "1.0.1", "fingerprintVersion": "1.2.41"}
|
||||
token_res = self._request_json("post", DF_TOKEN_URL, "获取虎牙 df token", json=token_payload)
|
||||
token_res = self._request_json(
|
||||
"post", DF_TOKEN_URL, "获取虎牙 df token", json=token_payload
|
||||
)
|
||||
token = token_res.get("data", {}).get("token")
|
||||
if not token:
|
||||
raise HuyaLoginError(f"获取虎牙 df token 失败: {token_res}")
|
||||
|
||||
collect_res = self._request_json("post", DF_COLLECT_URL, "获取虎牙 sdid", json={"token": token})
|
||||
collect_res = self._request_json(
|
||||
"post", DF_COLLECT_URL, "获取虎牙 sdid", json={"token": token}
|
||||
)
|
||||
self.sdid = collect_res.get("data", {}).get("sdid", "")
|
||||
if not self.sdid:
|
||||
raise HuyaLoginError(f"获取虎牙 sdid 失败: {collect_res}")
|
||||
@@ -240,7 +264,11 @@ class HuyaPasswordChanger:
|
||||
from .verification import HuyaVerificationSolver
|
||||
|
||||
solver = HuyaVerificationSolver(
|
||||
cookie=self.session.cookies.get_dict(),
|
||||
cookie={
|
||||
key: value
|
||||
for key, value in self.session.cookies.get_dict().items()
|
||||
if value is not None
|
||||
},
|
||||
ua=self.ua,
|
||||
sdid=self.sdid,
|
||||
session=self.session,
|
||||
@@ -402,12 +430,16 @@ class HuyaPasswordChanger:
|
||||
request_id=self.request_id,
|
||||
)
|
||||
|
||||
def submit_code(self, password: str, sms_code: str, session_data: str) -> HuyaChangePasswordResult:
|
||||
def submit_code(
|
||||
self, password: str, sms_code: str, session_data: str
|
||||
) -> HuyaChangePasswordResult:
|
||||
"""提交改密短信验证码。"""
|
||||
if not session_data:
|
||||
raise HuyaLoginError("缺少改密 sessionData,请先发送改密短信")
|
||||
|
||||
payload = self._submit_once(password=password, sms_code=sms_code, session_data=session_data)
|
||||
payload = self._submit_once(
|
||||
password=password, sms_code=sms_code, session_data=session_data
|
||||
)
|
||||
for index in range(3):
|
||||
return_code = int(payload.get("returnCode") or 0)
|
||||
if return_code == 0:
|
||||
@@ -436,7 +468,9 @@ class HuyaPasswordChanger:
|
||||
logger.info(f"虎牙短信改密触发风控: {return_code} ({index + 1}/3)")
|
||||
self._solve_verification(payload)
|
||||
time.sleep(0.5)
|
||||
payload = self._submit_once(password=password, sms_code=sms_code, session_data=session_data)
|
||||
payload = self._submit_once(
|
||||
password=password, sms_code=sms_code, session_data=session_data
|
||||
)
|
||||
|
||||
return HuyaChangePasswordResult(
|
||||
success=False,
|
||||
|
||||
+36
-21
@@ -1,14 +1,23 @@
|
||||
"""
|
||||
TAF/WUP 帧解码器 — 将二进制帧转为可读摘要,用于日志输出
|
||||
"""
|
||||
|
||||
from typing import Any, cast
|
||||
from .taf_protocol import TafInputStream, TafType
|
||||
from .wup_protocol import normalize_wup_payload
|
||||
|
||||
# cmd 编号 → 名称
|
||||
CMD_NAMES = {
|
||||
0x03: "RPC_REQ", 0x04: "RPC_RSP", 0x0a: "AUTH",
|
||||
0x0b: "PUSH1", 0x10: "HB_SEND", 0x11: "HB_RECV",
|
||||
0x17: "CONFIRM", 0x18: "PUSH2", 0x21: "REGISTER", 0x22: "CONFIRM_RSP",
|
||||
0x03: "RPC_REQ",
|
||||
0x04: "RPC_RSP",
|
||||
0x0A: "AUTH",
|
||||
0x0B: "PUSH1",
|
||||
0x10: "HB_SEND",
|
||||
0x11: "HB_RECV",
|
||||
0x17: "CONFIRM",
|
||||
0x18: "PUSH2",
|
||||
0x21: "REGISTER",
|
||||
0x22: "CONFIRM_RSP",
|
||||
}
|
||||
|
||||
# TAIL_BYTES = 2c36004c5c6600
|
||||
@@ -18,10 +27,14 @@ _TAIL = bytes.fromhex("2c36004c5c6600")
|
||||
def _strip_tail(body: bytes) -> bytes:
|
||||
"""裁掉 body 末尾的 TAIL_BYTES"""
|
||||
if body.endswith(_TAIL):
|
||||
return body[:-len(_TAIL)]
|
||||
return body[: -len(_TAIL)]
|
||||
# 有时 TAIL 前还有 0c (ZERO tag)
|
||||
if len(body) > 1 and body[-len(_TAIL)-1:-len(_TAIL)] == b'\x0c' and body.endswith(_TAIL):
|
||||
return body[:-len(_TAIL)-1]
|
||||
if (
|
||||
len(body) > 1
|
||||
and body[-len(_TAIL) - 1 : -len(_TAIL)] == b"\x0c"
|
||||
and body.endswith(_TAIL)
|
||||
):
|
||||
return body[: -len(_TAIL) - 1]
|
||||
return body
|
||||
|
||||
|
||||
@@ -33,16 +46,18 @@ def _decode_taf_value(ins: TafInputStream, dtype: int, depth: int = 0) -> object
|
||||
return ins._read_int_value(dtype)
|
||||
if dtype in (TafType.FLOAT, TafType.DOUBLE):
|
||||
import struct as _s
|
||||
|
||||
if dtype == TafType.FLOAT:
|
||||
return round(_s.unpack('>f', ins.buf.read(4))[0], 4)
|
||||
return round(_s.unpack('>d', ins.buf.read(8))[0], 6)
|
||||
return round(_s.unpack(">f", ins.buf.read(4))[0], 4)
|
||||
return round(_s.unpack(">d", ins.buf.read(8))[0], 6)
|
||||
if dtype == TafType.STRING1:
|
||||
ln = ins.buf.read(1)[0]
|
||||
return ins.buf.read(ln).decode('utf-8', errors='replace')
|
||||
return ins.buf.read(ln).decode("utf-8", errors="replace")
|
||||
if dtype == TafType.STRING4:
|
||||
import struct as _s
|
||||
ln = _s.unpack('>I', ins.buf.read(4))[0]
|
||||
return ins.buf.read(ln).decode('utf-8', errors='replace')
|
||||
|
||||
ln = _s.unpack(">I", ins.buf.read(4))[0]
|
||||
return ins.buf.read(ln).decode("utf-8", errors="replace")
|
||||
if dtype == TafType.MAP:
|
||||
cnt = ins._read_int_len()
|
||||
m = {}
|
||||
@@ -106,14 +121,14 @@ def _extract_wup(body: bytes) -> bytes:
|
||||
return body
|
||||
# 大包格式: [1B prefix][4B wup_len][wup_body][tail]
|
||||
# prefix 可能是 0x00,必须优先于 4B total_len 判断。
|
||||
wup_len = int.from_bytes(body[1:5], 'big')
|
||||
if 5 + wup_len <= len(body) and body[5:7] == b'\x10\x03':
|
||||
return body[5:5 + wup_len]
|
||||
total_len = int.from_bytes(body[0:4], 'big')
|
||||
if 8 <= total_len <= len(body) and body[4:6] == b'\x10\x03':
|
||||
wup_len = int.from_bytes(body[1:5], "big")
|
||||
if 5 + wup_len <= len(body) and body[5:7] == b"\x10\x03":
|
||||
return body[5 : 5 + wup_len]
|
||||
total_len = int.from_bytes(body[0:4], "big")
|
||||
if 8 <= total_len <= len(body) and body[4:6] == b"\x10\x03":
|
||||
return body[:total_len]
|
||||
if 5 + wup_len <= len(body):
|
||||
return body[5:5 + wup_len]
|
||||
return body[5 : 5 + wup_len]
|
||||
return body
|
||||
|
||||
|
||||
@@ -127,7 +142,7 @@ def _decode_wup_body(body: bytes) -> dict:
|
||||
ins = TafInputStream(wup)
|
||||
|
||||
# 读 WUP 字段 tag1~tag10,读到 tag10 后停止(忽略尾部垃圾)
|
||||
sBuffer = b''
|
||||
sBuffer = b""
|
||||
while True:
|
||||
try:
|
||||
tag, dtype = ins.peek_head()
|
||||
@@ -267,8 +282,8 @@ def format_wss_log(body: bytes, cmd: int, seq: int, direction: str) -> str:
|
||||
return f"{prefix} {label}"
|
||||
|
||||
# AUTH
|
||||
if cmd == 0x0a:
|
||||
text = _strip_tail(body).decode('utf-8', errors='replace')
|
||||
if cmd == 0x0A:
|
||||
text = _strip_tail(body).decode("utf-8", errors="replace")
|
||||
return f"{prefix} AUTH {text[:100]}{'...' if len(text) > 100 else ''}"
|
||||
|
||||
# REGISTER / CONFIRM / PUSH 等
|
||||
@@ -302,7 +317,7 @@ def format_wss_log(body: bytes, cmd: int, seq: int, direction: str) -> str:
|
||||
fields = {}
|
||||
cmd_name = CMD_NAMES.get(cmd, f"0x{cmd:02x}")
|
||||
if fields:
|
||||
return f"{prefix} {cmd_name} {_fmt_fields(_truncate(fields))}"
|
||||
return f"{prefix} {cmd_name} {_fmt_fields(cast(dict[str, Any], _truncate(fields)))}"
|
||||
return f"{prefix} {cmd_name}"
|
||||
except Exception:
|
||||
cmd_name = CMD_NAMES.get(cmd, f"0x{cmd:02x}")
|
||||
|
||||
Reference in New Issue
Block a user