From 3b88653e5d22321f12451eef43dd69e52cb5f55c Mon Sep 17 00:00:00 2001 From: yml2213 Date: Sat, 4 Jul 2026 17:57:33 +0800 Subject: [PATCH] =?UTF-8?q?=E5=AE=8C=E5=96=84=E8=99=8E=E7=89=99=E7=A7=AF?= =?UTF-8?q?=E5=88=86=E6=9F=A5=E8=AF=A2=E6=97=A5=E5=BF=97?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- core/huya/activity_structs.py | 2 +- core/huya/http_client.py | 62 ++++++++++++++++++++++------- web/backend/services/huya_runner.py | 6 ++- 3 files changed, 54 insertions(+), 16 deletions(-) diff --git a/core/huya/activity_structs.py b/core/huya/activity_structs.py index b3bccbf..eb2a278 100644 --- a/core/huya/activity_structs.py +++ b/core/huya/activity_structs.py @@ -10,7 +10,7 @@ class ActivityUserId(TafStruct): self.lUid: int = 0 self.sGuid: str = "" self.sToken: str = "" - self.sHuYaUA: str = "web&1.0&huya" + self.sHuYaUA: str = "webh5&0.0.1&websocket&&diypc_52775" self.sCookie: str = "" self.iTokenType: int = 0 self.sDeviceInfo: str = "" diff --git a/core/huya/http_client.py b/core/huya/http_client.py index 46e5d9e..3443384 100644 --- a/core/huya/http_client.py +++ b/core/huya/http_client.py @@ -42,7 +42,7 @@ class WSConnectParaInfo(TafStruct): def __init__(self): self.lUid: int = 0 self.sGuid: str = "" - self.sUA: str = "webh5&0.0.1&websocket&&diypc_52775" + self.sUA: str = HTTP_HUYA_UA self.sAppSrc: str = "HUYA&ZH&2052" self.sMid: str = "" self.sExp: str = "" @@ -53,7 +53,7 @@ class WSConnectParaInfo(TafStruct): self.mCustomHeaders: dict = {} def write_to(self, os: TafOutputStream): - # 与浏览器一致: 空字符串也写 (HAR 实证 tag4/5/7/8 都写了空串) + # 与浏览器一致: 可为空的字段也显式写入。 if self.lUid: os.write_int64(0, self.lUid) else: @@ -70,7 +70,17 @@ class WSConnectParaInfo(TafStruct): os.write_map(10, self.mCustomHeaders) def read_from(self, ins: TafInputStream): - pass + self.lUid = ins.read_int64(0, default=self.lUid) + self.sGuid = ins.read_string(1, default=self.sGuid) + self.sUA = ins.read_string(2, default=self.sUA) + self.sAppSrc = ins.read_string(3, default=self.sAppSrc) + self.sMid = ins.read_string(4, default=self.sMid) + self.sExp = ins.read_string(5, default=self.sExp) + self.iTokenType = ins.read_int32(6, default=self.iTokenType) + self.sToken = ins.read_string(7, default=self.sToken) + self.sCookie = ins.read_string(8, default=self.sCookie) + self.sTraceId = ins.read_string(9, default=self.sTraceId) + self.mCustomHeaders = ins.read_map(10) def _gen_trace_id() -> str: @@ -84,20 +94,16 @@ def generate_http_baseinfo(uid: int, guid: str, cookie: str, """ 构造 HTTP POST 的 baseinfo URL 参数 - HAR 实证: getGoodsInfoV5 的 baseinfo lUid=0, sCookie="" (身份靠 Wup body 的 userId.sCookie) + HAR 实证: 当前 cdnws HTTP RPC 的 baseinfo 会带 lUid 和完整 Cookie。 """ info = WSConnectParaInfo() - info.lUid = 0 # HAR 实证: baseinfo 里 lUid=0 + info.lUid = int(uid or 0) info.sGuid = guid - info.sUA = "webh5&0.0.1&websocket&&diypc_52775" + info.sUA = HTTP_HUYA_UA info.sAppSrc = "HUYA&ZH&2052" - info.sCookie = "" # HAR 实证: baseinfo 不带 cookie, cookie 在 Wup body + info.sCookie = cookie or "" info.sTraceId = trace_id or _gen_trace_id() - os = TafOutputStream() - os.write_struct_begin(0) # WSConnectParaInfo 是顶层 struct? 实际 baseinfo 是裸 struct 字段 - # 实际 HAR baseinfo 解码: 顶层直接是字段(tag0 lUid...), 不包 STRUCT_BEGIN - # 重置, 不写 STRUCT_BEGIN os = TafOutputStream() info.write_to(os) raw = os.get_bytes() @@ -168,7 +174,7 @@ class HuyaHttpClient: from .activity_structs import ActivityUserId user = ActivityUserId() user.lUid = uid - user.sHuYaUA = "web&1.0&huya" + user.sHuYaUA = HTTP_HUYA_UA user.sCookie = cookie or "" return user @@ -184,7 +190,7 @@ class HuyaHttpClient: method: 方法名 (getGoodsInfoV5) req_struct: 请求结构体 (TafStruct) rsp_class: 响应类 - uid/guid/cookie: 用于构造 baseinfo (cookie 主要放 Wup body) + uid/guid/cookie: 用于构造 baseinfo,业务请求体也会按各接口需要携带 """ # 1. 构造 Wup 请求体 wup = WupRequest() @@ -199,7 +205,7 @@ class HuyaHttpClient: url = f"https://{CDNWS_HOST}/?baseinfo={baseinfo}" self.logger(f"[HTTP] POST {service}.{method}") - self.logger(f"[HTTP] URL: {url[:100]}...") + self.logger(f"[HTTP] URL: https://{CDNWS_HOST}/?baseinfo=") self.logger(f"[HTTP] 发送 body: {len(wup_data)} 字节, hex前60: {wup_data[:60].hex()}") # 3. POST @@ -239,6 +245,9 @@ class HuyaHttpClient: result = wup_resp.readStruct("tRsp", rsp_class) if result is None: result = wup_resp.readStruct("tResp", rsp_class) + if result is not None and hasattr(result, "to_dict"): + decoded = json.dumps(result.to_dict(), ensure_ascii=False, separators=(",", ":")) + self.logger(f"[HTTP] 解码 tRsp: {decoded}") return result # ---------- 业务便捷方法 ---------- @@ -381,3 +390,28 @@ class HuyaHttpClient: if result is None: result = wup_resp.readStruct("tResp", PayOrderRes) return result + + +def _self_check(): + uid = 1199647239697 + cookie = "yyuid=1199647239697; udb_passport=test" + trace_id = "0123456789abcdef:0123456789abcdef:0:0" + + encoded = generate_http_baseinfo(uid, "", cookie, trace_id=trace_id) + raw = base64.b64decode(urllib.parse.unquote(encoded)) + + parsed = WSConnectParaInfo() + parsed.read_from(TafInputStream(raw)) + + assert parsed.lUid == uid + assert parsed.sUA == HTTP_HUYA_UA + assert parsed.sCookie == cookie + assert parsed.sTraceId == trace_id + + user = HuyaHttpClient._build_activity_user(uid, cookie) + assert user.sHuYaUA == HTTP_HUYA_UA + assert user.sCookie == cookie + + +if __name__ == "__main__": + _self_check() diff --git a/web/backend/services/huya_runner.py b/web/backend/services/huya_runner.py index c59cfc2..586db20 100644 --- a/web/backend/services/huya_runner.py +++ b/web/backend/services/huya_runner.py @@ -6,6 +6,7 @@ from concurrent.futures import ThreadPoolExecutor, as_completed from datetime import datetime, timezone from typing import Optional +from loguru import logger from sqlalchemy.orm import Session, joinedload from core.huya import HuyaHttpClient @@ -42,6 +43,9 @@ class HuyaBatchRunner: self._stop.set() def _push_log(self, level: str, message: str): + if level != "result" and message: + log_func = getattr(logger, level, logger.info) + log_func(f"[huya] {message}") if self.log_queue and self.loop: asyncio.run_coroutine_threadsafe( self.log_queue.put({"level": level, "message": message}), @@ -113,7 +117,7 @@ class HuyaBatchRunner: self._mark_task(worker_db, task, "failed", "账号 Cookie 为空") return - client = HuyaHttpClient(logger=lambda _msg: None) + client = HuyaHttpClient(logger=lambda msg: self._push_log("info", f"[{uid}] {msg}")) response = client.query_user_score(uid=uid, cookie=cookie, sid=sid_int) if response is None: self._mark_task(worker_db, task, "error", "虎牙积分接口无响应")