重构虎牙精英宝典协议与支付状态链路

This commit is contained in:
yml2213
2026-09-01 11:31:15 +08:00
parent 955ba45488
commit 684e16a07a
12 changed files with 937 additions and 51 deletions
+340
View File
@@ -107,6 +107,84 @@ class GetUserScoreResp(TafStruct):
}
class GetActInfoReq(TafStruct):
"""webActUI.getActInfo 请求。"""
def __init__(self):
self.actId = 0
self.actUuid = ""
self.appSource = ""
def write_to(self, os: TafOutputStream):
os.write_int64(0, self.actId)
os.write_string(1, self.actUuid)
os.write_string(2, self.appSource)
def read_from(self, ins: TafInputStream):
self.actId = ins.read_int64(0, default=self.actId)
self.actUuid = ins.read_string(1, default=self.actUuid)
self.appSource = ins.read_string(2, default=self.appSource)
class ActInfoItem(TafStruct):
def __init__(self):
self.actId = 0
self.name = ""
self.startTime = 0
self.endTime = 0
self.moduleId = 0
self.outerActId = ""
self.gameIdList = ""
self.gid = 0
def read_from(self, ins: TafInputStream):
for tag, attr, reader in (
(0, "actId", ins.read_int64), (1, "name", ins.read_string),
(2, "startTime", ins.read_int64), (3, "endTime", ins.read_int64),
(4, "moduleId", ins.read_int64), (5, "outerActId", ins.read_string),
(6, "gameIdList", ins.read_string), (8, "gid", ins.read_int64),
):
setattr(self, attr, reader(tag, default=getattr(self, attr)))
def write_to(self, os: TafOutputStream):
os.write_int64(0, self.actId)
os.write_string(1, self.name)
os.write_int64(2, self.startTime)
os.write_int64(3, self.endTime)
os.write_int64(4, self.moduleId)
os.write_string(5, self.outerActId)
os.write_string(6, self.gameIdList)
os.write_int64(8, self.gid)
def to_dict(self) -> dict:
return {"act_id": self.actId, "name": self.name, "start_time": self.startTime,
"end_time": self.endTime, "module_id": self.moduleId,
"outer_act_id": self.outerActId, "game_id_list": self.gameIdList,
"gid": self.gid}
class GetActInfoResp(TafStruct):
def __init__(self):
self.status = 0
self.msg = ""
self.info: ActInfoItem | None = None
def read_from(self, ins: TafInputStream):
self.status = ins.read_int32(0, default=self.status)
self.msg = ins.read_string(1, default=self.msg)
self.info = ins.read_struct(2, ActInfoItem)
def write_to(self, os: TafOutputStream):
os.write_int32(0, self.status)
os.write_string(1, self.msg)
if self.info:
os.write_struct(2, self.info)
def to_dict(self) -> dict:
return {"status": self.status, "msg": self.msg,
"info": self.info.to_dict() if self.info else None}
class GetActPrizeListReq(TafStruct):
"""webActUI.getActPrizeList 请求。"""
@@ -170,6 +248,180 @@ class ScoreExchangePrizeReq(TafStruct):
self.isRole = ins.read_int32(6, default=self.isRole)
class GetActPrizeDetailReq(TafStruct):
"""webActUI.getActPrizeDetail 请求。"""
def __init__(self):
self.userId = ActivityUserId()
self.sid: int = 0
self.pid: int = 0
def write_to(self, os: TafOutputStream):
os.write_struct(0, self.userId)
os.write_int32(1, self.sid)
os.write_int32(2, self.pid)
def read_from(self, ins: TafInputStream):
self.userId = ins.read_struct(0, ActivityUserId) or self.userId
self.sid = ins.read_int32(1, default=self.sid)
self.pid = ins.read_int32(2, default=self.pid)
class ActPrizeDetailItem(TafStruct):
"""兑换详情中的奖品和服务端限制字段。"""
def __init__(self):
self.prizeId = 0
self.name = ""
self.score = 0
self.icon = ""
self.frequency = 0
self.frequencyLimit = 0
self.num = 0
self.status = 0
self.leftNum = 0
self.isShowNum = 0
self.percent = ""
self.newScore = 0
self.categoryId = ""
self.usedNum = 0
self.goodType = 0
self.commonPrizeId = 0
self.updateTime = 0
self.isCanExchange = 0
self.exchangeStartTime = 0
self.exchangeEndTime = 0
self.startTimeSlot = ""
self.endTimeSlot = ""
self.isTodayLimit = 0
self.isUserLimit = 0
def read_from(self, ins: TafInputStream):
readers = {
0: ("prizeId", ins.read_int64), 2: ("name", ins.read_string),
3: ("score", ins.read_int64), 5: ("icon", ins.read_string),
6: ("frequency", ins.read_int32), 7: ("frequencyLimit", ins.read_int32),
8: ("num", ins.read_int64), 9: ("status", ins.read_int32),
10: ("leftNum", ins.read_int64), 11: ("isShowNum", ins.read_int32),
12: ("percent", ins.read_string), 13: ("newScore", ins.read_int64),
14: ("categoryId", ins.read_string), 17: ("usedNum", ins.read_int64),
18: ("goodType", ins.read_int32), 22: ("commonPrizeId", ins.read_int64),
23: ("updateTime", ins.read_int64), 24: ("isCanExchange", ins.read_int32),
25: ("exchangeStartTime", ins.read_int64), 26: ("exchangeEndTime", ins.read_int64),
27: ("startTimeSlot", ins.read_string), 28: ("endTimeSlot", ins.read_string),
29: ("isTodayLimit", ins.read_int32), 30: ("isUserLimit", ins.read_int32),
}
while True:
pos = ins.buf.tell()
tag, dtype = ins.read_head()
if dtype == TafType.STRUCT_END:
ins.buf.seek(pos)
return
item = readers.get(tag)
if item is None:
ins.skip_field(dtype)
continue
name, reader = item
ins.buf.seek(pos)
setattr(self, name, reader(tag, default=getattr(self, name)))
def write_to(self, os: TafOutputStream):
os.write_int64(0, self.prizeId)
os.write_string(2, self.name)
os.write_int64(3, self.score)
os.write_string(5, self.icon)
os.write_int32(6, self.frequency)
os.write_int32(7, self.frequencyLimit)
os.write_int64(8, self.num)
os.write_int32(9, self.status)
os.write_int64(10, self.leftNum)
os.write_int32(11, self.isShowNum)
os.write_string(12, self.percent)
os.write_int64(13, self.newScore)
os.write_string(14, self.categoryId)
os.write_int64(17, self.usedNum)
os.write_int32(18, self.goodType)
os.write_int64(22, self.commonPrizeId)
os.write_int64(23, self.updateTime)
os.write_int32(24, self.isCanExchange)
os.write_int64(25, self.exchangeStartTime)
os.write_int64(26, self.exchangeEndTime)
os.write_string(27, self.startTimeSlot)
os.write_string(28, self.endTimeSlot)
os.write_int32(29, self.isTodayLimit)
os.write_int32(30, self.isUserLimit)
def to_dict(self) -> dict:
return {
"product_id": str(self.prizeId), "name": self.name, "score": self.score,
"icon": self.icon, "frequency": self.frequency,
"frequency_limit": self.frequencyLimit, "num": self.num,
"status": self.status, "left_num": self.leftNum,
"is_show_num": self.isShowNum, "percent": self.percent,
"new_score": self.newScore, "category_id": self.categoryId,
"used_num": self.usedNum, "good_type": self.goodType,
"common_prize_id": self.commonPrizeId, "update_time": self.updateTime,
"is_can_exchange": self.isCanExchange,
"exchange_start_time": self.exchangeStartTime,
"exchange_end_time": self.exchangeEndTime,
"start_time_slot": self.startTimeSlot, "end_time_slot": self.endTimeSlot,
"is_today_limit": self.isTodayLimit, "is_user_limit": self.isUserLimit,
}
class GetActPrizeDetailResp(TafStruct):
"""webActUI.getActPrizeDetail 响应。"""
def __init__(self):
self.status = 0
self.msg = ""
self.prize: ActPrizeDetailItem | None = None
self.detailStatus = 0
def read_from(self, ins: TafInputStream):
self.status = ins.read_int32(0, default=self.status)
self.msg = ins.read_string(1, default=self.msg)
outer = ins.read_struct(2, _ActPrizeDetailEnvelope)
if outer:
self.prize = outer.prize
self.detailStatus = outer.detailStatus
def write_to(self, os: TafOutputStream):
os.write_int32(0, self.status)
os.write_string(1, self.msg)
envelope = _ActPrizeDetailEnvelope()
envelope.prize = self.prize
envelope.detailStatus = self.detailStatus
os.write_struct(2, envelope)
def to_dict(self) -> dict:
return {"status": self.status, "msg": self.msg,
"prize": self.prize.to_dict() if self.prize else None,
"detail_status": self.detailStatus}
class _ActPrizeDetailEnvelope(TafStruct):
def __init__(self):
self.prize: ActPrizeDetailItem | None = None
self.detailStatus = 0
def read_from(self, ins: TafInputStream):
self.prize = ins.read_struct(0, ActPrizeDetailItem)
self.detailStatus = ins.read_int32(1, default=self.detailStatus)
while True:
pos = ins.buf.tell()
_tag, dtype = ins.read_head()
if dtype == TafType.STRUCT_END:
ins.buf.seek(pos)
return
ins.skip_field(dtype)
def write_to(self, os: TafOutputStream):
if self.prize:
os.write_struct(0, self.prize)
os.write_int32(1, self.detailStatus)
class ExchangeInfo(TafStruct):
"""积分兑换结果信息。"""
@@ -384,6 +636,94 @@ class GetActTaskDetailReq(TafStruct):
self.actId = ins.read_int32(1, default=self.actId)
class GetActUserTaskDetailReq(TafStruct):
"""webActUI.getActUserTaskDetail 请求。"""
def __init__(self):
self.userId = ActivityUserId()
self.actId = 0
def write_to(self, os: TafOutputStream):
os.write_struct(0, self.userId)
os.write_int64(1, self.actId)
def read_from(self, ins: TafInputStream):
self.userId = ins.read_struct(0, ActivityUserId) or self.userId
self.actId = ins.read_int64(1, default=self.actId)
class UserActTaskItem(TafStruct):
def __init__(self):
self.actId = 0
self.taskId = 0
self.taskStatus = 0
self.prizeStatus = 0
self.taskValue = ""
self.taskCount = 0
self.prizeCount = 0
self.subTaskDetail = 0
self.extraStatus = 0
def read_from(self, ins: TafInputStream):
for tag, attr, reader in (
(0, "actId", ins.read_int64), (1, "taskId", ins.read_int64),
(2, "taskStatus", ins.read_int32), (3, "prizeStatus", ins.read_int32),
(4, "taskValue", ins.read_string), (5, "taskCount", ins.read_int64),
(6, "prizeCount", ins.read_int64), (8, "subTaskDetail", ins.read_int64),
(9, "extraStatus", ins.read_int64),
):
setattr(self, attr, reader(tag, default=getattr(self, attr)))
def write_to(self, os: TafOutputStream):
os.write_int64(0, self.actId)
os.write_int64(1, self.taskId)
os.write_int32(2, self.taskStatus)
os.write_int32(3, self.prizeStatus)
os.write_string(4, self.taskValue)
os.write_int64(5, self.taskCount)
os.write_int64(6, self.prizeCount)
os.write_int64(8, self.subTaskDetail)
os.write_int64(9, self.extraStatus)
@staticmethod
def read_list_item(ins: TafInputStream, _tag: int):
item = UserActTaskItem()
item.read_from(ins)
_end, dtype = ins.read_head()
if dtype != TafType.STRUCT_END:
raise ValueError(f"期望用户任务 STRUCT_END,实际 0x{dtype:02x}")
return item
def to_dict(self) -> dict:
return {"act_id": self.actId, "task_id": self.taskId,
"task_status": self.taskStatus, "prize_status": self.prizeStatus,
"task_value": self.taskValue, "task_count": self.taskCount,
"prize_count": self.prizeCount, "sub_task_detail": self.subTaskDetail,
"extra_status": self.extraStatus}
class GetActUserTaskDetailResp(TafStruct):
def __init__(self):
self.status = 0
self.msg = ""
self.tasks: list[UserActTaskItem] = []
def read_from(self, ins: TafInputStream):
self.status = ins.read_int32(0, default=self.status)
self.msg = ins.read_string(1, default=self.msg)
self.tasks = ins.read_list(2, item_reader=UserActTaskItem.read_list_item)
def write_to(self, os: TafOutputStream):
os.write_int32(0, self.status)
os.write_string(1, self.msg)
os.write_list(2, self.tasks)
def to_dict(self) -> dict:
return {"status": self.status, "msg": self.msg,
"task_count": len(self.tasks),
"tasks": [item.to_dict() for item in self.tasks]}
class ActTaskPrizeInfo(TafStruct):
"""活动任务奖励项,刷新充值商品时只取展示需要字段。"""
+88 -4
View File
@@ -28,6 +28,7 @@ PC_UA = (
"Chrome/149.0.0.0 Safari/537.36"
)
HTTP_HUYA_UA = "webh5&0.0.1&websocket&&diypc_52775"
SHOP_BIZ_UA = "web&1.0.0&huya"
class WSConnectParaInfo(TafStruct):
@@ -200,6 +201,22 @@ class HuyaHttpClient:
user.sQIMEI = ""
return user
@staticmethod
def _build_shop_user(uid: int, cookie: str):
"""构造商城业务 UserId;与 9.1 商城 WSS 帧保持一致。"""
from .shop_structs import UserId
user = UserId()
user.lUid = int(uid or 0)
user.sGuid = ""
user.sToken = ""
user.sHuYaUA = SHOP_BIZ_UA
user.sCookie = HuyaHttpClient._normalize_cookie(cookie)
user.iTokenType = 0
user.sDeviceInfo = ""
user.sQIMEI = ""
return user
@staticmethod
def _build_shop_app(source_id: str = "yellowcarlist", scene: int = 7):
"""构造 HTTP 业务 ShopAppInfo。"""
@@ -331,6 +348,32 @@ class HuyaHttpClient:
# ---------- 业务便捷方法 ----------
def get_act_info(
self, act_id: int, act_uuid: str = "", app_source: str = "", timeout: float = 15.0
):
"""读取活动主信息,用于驱动当前活动配置。"""
from .activity_structs import GetActInfoReq, GetActInfoResp
req = GetActInfoReq()
req.actId = int(act_id or 0)
req.actUuid = act_uuid
req.appSource = app_source
return self.call_rpc("webActUI", "getActInfo", req, GetActInfoResp, timeout=timeout)
def get_act_user_task_detail(
self, uid: int, cookie: str, act_id: int, timeout: float = 15.0
):
"""读取当前用户任务状态,而不是只读取任务定义。"""
from .activity_structs import GetActUserTaskDetailReq, GetActUserTaskDetailResp
req = GetActUserTaskDetailReq()
req.userId = self._build_activity_user(uid, cookie)
req.actId = int(act_id or 0)
return self.call_rpc(
"webActUI", "getActUserTaskDetail", req, GetActUserTaskDetailResp,
uid=uid, cookie=cookie, timeout=timeout,
)
def query_user_score(self, uid: int, cookie: str, sid: int, timeout: float = 15.0):
"""查询活动可用积分。"""
from .activity_structs import GetUserScoreReq, GetUserScoreResp
@@ -406,6 +449,26 @@ class HuyaHttpClient:
timeout=timeout,
)
def get_act_prize_detail(
self, uid: int, cookie: str, sid: int, pid: int, timeout: float = 15.0
):
"""查询单个兑换奖品详情和服务端限制。"""
from .activity_structs import GetActPrizeDetailReq, GetActPrizeDetailResp
req = GetActPrizeDetailReq()
req.userId = self._build_activity_user(uid, cookie)
req.sid = int(sid or 0)
req.pid = int(pid or 0)
return self.call_rpc(
"webActUI",
"getActPrizeDetail",
req,
GetActPrizeDetailResp,
uid=uid,
cookie=cookie,
timeout=timeout,
)
def get_act_task_detail(
self, uid: int, cookie: str, act_id: int, timeout: float = 15.0
):
@@ -778,7 +841,7 @@ class HuyaHttpClient:
from .shop_structs import GetGoodsInfoReqV5, GoodsInfoRsp
req = GetGoodsInfoReqV5()
req.userId = self._build_user(uid, guid, cookie)
req.userId = self._build_shop_user(uid, cookie)
req.shopAppInfo = self._build_shop_app(source_id, scene)
req.pid = pid
req.spuId = spu_id
@@ -803,7 +866,7 @@ class HuyaHttpClient:
from .shop_structs import QueryUserOrderListReq, QueryUserOrderListRsp
req = QueryUserOrderListReq()
req.userId = self._build_user(uid, guid, cookie)
req.userId = self._build_shop_user(uid, cookie)
req.offset = int(offset or 0)
req.orderType = int(order_type or 1)
req.pageSize = int(page_size or 10)
@@ -833,6 +896,27 @@ class HuyaHttpClient:
)
return fallback if fallback is not None else result
def order_detail(
self, uid: int, guid: str, cookie: str, order_id: int, timeout: float = 15.0
):
"""按订单号查询商城订单详情(最新商城支付链路)。"""
from .shop_structs import OrderDetailReq, OrderDetailRsp
req = OrderDetailReq()
req.userId = self._build_shop_user(uid, cookie)
req.shopAppInfo = self._build_shop_app("yellowcarlist", 4)
req.orderId = int(order_id or 0)
return self.call_rpc(
"shopMiddleUI",
"orderDetailV5",
req,
OrderDetailRsp,
uid,
guid,
cookie,
timeout=timeout,
)
def create_order(
self,
uid,
@@ -862,7 +946,7 @@ class HuyaHttpClient:
last_result = None
for ot in types_to_try:
req = CreateOrderReqV5()
req.userId = self._build_user(uid, guid, cookie)
req.userId = self._build_shop_user(uid, cookie)
req.shopAppInfo = self._build_shop_app(source_id, scene)
req.receiveId = 0
req.pid = pid
@@ -926,7 +1010,7 @@ class HuyaHttpClient:
os = TafOutputStream()
os.write_struct_begin(0)
os.write_struct(0, self._build_user(uid, guid, cookie))
os.write_struct(0, self._build_shop_user(uid, cookie))
os.write_struct(1, self._build_shop_app(source_id, scene))
os.write_int64(2, order_id)
os.write_string(3, self._normalize_pay_channel(pay_type))
+124
View File
@@ -639,6 +639,130 @@ class QueryUserOrderListRsp(TafStruct):
}
class OrderDetailOrder(TafStruct):
"""orderDetailV5 中的订单摘要;保留支付轮询所需字段。"""
def __init__(self):
self.orderId: int = 0
self.orderStatus: int = 0
self.createTime: int = 0
self.payTime: int = 0
self.totalPrice: int = 0
def read_from(self, ins: TafInputStream):
while True:
pos = ins.buf.tell()
tag, dtype = ins.read_head()
if dtype == TafType.STRUCT_END:
ins.buf.seek(pos)
return
if tag == 0:
ins.buf.seek(pos)
self.orderId = ins.read_int64(tag, default=self.orderId)
elif tag == 3:
ins.buf.seek(pos)
self.orderStatus = ins.read_int32(tag, default=self.orderStatus)
elif tag == 7:
ins.buf.seek(pos)
self.totalPrice = ins.read_int64(tag, default=self.totalPrice)
elif tag == 13:
ins.buf.seek(pos)
self.createTime = ins.read_int64(tag, default=self.createTime)
elif tag == 14:
ins.buf.seek(pos)
self.payTime = ins.read_int64(tag, default=self.payTime)
else:
ins.skip_field(dtype)
def write_to(self, os: TafOutputStream):
os.write_int64(0, self.orderId)
os.write_int32(3, self.orderStatus)
os.write_int64(7, self.totalPrice)
os.write_int64(13, self.createTime)
os.write_int64(14, self.payTime)
def to_dict(self) -> dict:
return {
"order_id": self.orderId,
"order_status": self.orderStatus,
"create_time": self.createTime,
"pay_time": self.payTime,
"total_price": self.totalPrice,
}
class OrderDetailReq(TafStruct):
"""shopMiddleUI.orderDetailV5 请求。"""
def __init__(self):
self.userId = UserId()
self.shopAppInfo = ShopAppInfo()
self.orderId: int = 0
def write_to(self, os: TafOutputStream):
os.write_struct(0, self.userId)
os.write_struct(1, self.shopAppInfo)
os.write_int64(2, self.orderId)
def read_from(self, ins: TafInputStream):
self.userId = ins.read_struct(0, UserId) or self.userId
self.shopAppInfo = ins.read_struct(1, ShopAppInfo) or self.shopAppInfo
self.orderId = ins.read_int64(2, default=self.orderId)
class OrderDetailData(TafStruct):
def __init__(self):
self.order: OrderDetailOrder | None = None
def read_from(self, ins: TafInputStream):
while True:
pos = ins.buf.tell()
tag, dtype = ins.read_head()
if dtype == TafType.STRUCT_END:
ins.buf.seek(pos)
return
if tag == 1:
ins.buf.seek(pos)
self.order = ins.read_struct(1, OrderDetailOrder)
else:
ins.skip_field(dtype)
def write_to(self, os: TafOutputStream):
if self.order:
os.write_struct(1, self.order)
class OrderDetailRsp(TafStruct):
"""shopMiddleUI.orderDetailV5 响应。"""
def __init__(self):
self.code = 0
self.message = ""
self.data: OrderDetailData | None = None
def read_from(self, ins: TafInputStream):
self.code = ins.read_int32(0, default=self.code)
self.message = ins.read_string(1, default=self.message)
self.data = ins.read_struct(2, OrderDetailData)
@property
def order(self) -> OrderDetailOrder | None:
return self.data.order if self.data else None
def write_to(self, os: TafOutputStream):
os.write_int32(0, self.code)
os.write_string(1, self.message)
if self.data:
os.write_struct(2, self.data)
def to_dict(self) -> dict:
return {
"code": self.code,
"message": self.message,
"order": self.order.to_dict() if self.order else None,
}
# ============================================================
# 下单
# ============================================================
+20
View File
@@ -683,6 +683,26 @@ class HuyaWssClient:
timeout=15.0,
)
async def order_detail(
self,
uid: int,
guid: str,
cookie: str,
order_id: int,
source_id: str = "yellowcarlist",
scene: int = WSS_SHOP_SCENE,
):
"""按订单号查询商城订单详情,匹配 9.1 支付后轮询。"""
from .shop_structs import OrderDetailReq, OrderDetailRsp
req = OrderDetailReq()
req.userId = self._build_biz_user(uid, cookie)
req.shopAppInfo = self._build_shop_app(source_id, scene)
req.orderId = int(order_id or 0)
return await self.call_rpc(
"shopMiddleUI", "orderDetailV5", req, OrderDetailRsp, timeout=15.0
)
async def create_order(
self,
uid: int,