""" 虎牙充值相关 JCE 结构定义 字段顺序 = TAF tag 顺序(从 JS 逆向所得) 来源: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 class OrderType: HUYA_PHYSICAL = 1 TENCENT_VIRTUAL = 2 MAGIC_BOX = 3 MAGIC_BOX_DELIVERY = 4 PAID_COURSE = 5 HUYA_VIRTUAL = 6 LOTTERY_PICKUP = 7 DELIVERY_WORKBENCH = 11 GAME_ACCOUNT = 12 SHELF_GOODS = 17 class OrderStatus: DEPOSIT_WAIT_PAY = 10 DEPOSIT_PAID = 20 WAIT_DELIVER = 30 WAIT_RECEIVE = 40 FINISHED = 50 FINISHED_CLOSED = 60 CANCELLED = 90 BALANCE_WAIT_PAY = 1000 CANCELLED_BALANCE_EXPIRED = 1001 def _opt_str(os: TafOutputStream, tag: int, val: str): """非空才写字符串(与浏览器一致,省默认值)""" if val: os.write_string(tag, val) def _opt_int(os: TafOutputStream, tag: int, val): """非 0 才写整数""" if val: os.write_int64(tag, val) def _opt_struct(os: TafOutputStream, tag: int, val): """非 None 才写结构体""" if val is not None: os.write_struct(tag, val) def _opt_list(os: TafOutputStream, tag: int, val): """非空才写 list""" if val: os.write_list(tag, val) def _opt_map(os: TafOutputStream, tag: int, val): """非空才写 map""" if val: os.write_map(tag, val) def _skip_to_struct_end(ins: TafInputStream): """跳过当前结构里未解析的尾部字段,停在 STRUCT_END 前。""" 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) # ============================================================ # 基础结构 # ============================================================ 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 def write_to(self, os: TafOutputStream): # HAR2 实证: 浏览器不优化空串/0值, 全写字段 os.write_int64(0, self.lUid) os.write_string(1, self.sGuid) os.write_string(2, self.sToken) os.write_string(3, self.sHuYaUA) os.write_string(4, self.sCookie) os.write_int32(5, self.iTokenType) os.write_string(6, self.sDeviceInfo) os.write_string(7, self.sQIMEI) def read_from(self, ins: TafInputStream): self.lUid = ins.read_int64(0, default=self.lUid) self.sGuid = ins.read_string(1, default=self.sGuid) self.sToken = ins.read_string(2, default=self.sToken) self.sHuYaUA = ins.read_string(3, default=self.sHuYaUA) self.sCookie = ins.read_string(4, default=self.sCookie) self.iTokenType = ins.read_int32(5, default=self.iTokenType) self.sDeviceInfo = ins.read_string(6, default=self.sDeviceInfo) self.sQIMEI = ins.read_string(7, default=self.sQIMEI) 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 def write_to(self, os: TafOutputStream): # HAR2 实证: 全写字段(含空串/0) os.write_string(0, self.sAppId) os.write_string(1, self.sBizType) os.write_int32(4, self.scene) os.write_string(5, self.sourceId) def read_from(self, ins: TafInputStream): self.sAppId = ins.read_string(0, default=self.sAppId) self.sBizType = ins.read_string(1, default=self.sBizType) self.scene = ins.read_int32(4, default=self.scene) self.sourceId = ins.read_string(5, default=self.sourceId) # ============================================================ # 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 def write_to(self, os: TafOutputStream): # 浏览器写空字符串(STRING1 length0),这里强制写以精确匹配 os.write_string(0, self.s0) os.write_string(1, self.s1) os.write_string(2, self.s2) os.write_string(3, self.s3) os.write_string(4, self.s4) def read_from(self, ins: TafInputStream): pass class WsLaunchReq(TafStruct): """launch.wsLaunch 请求(tReq 是 STRUCT,不是 list) 从 HAR 逆向: tag0: lUid (int64) tag1: string "" tag2: sHuYaUA "webh5&1.0.0&huya" tag3: appSrc "HUYA&ZH&2052" tag4: 子struct (5个空字符串) """ def __init__(self): 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.sub: WsLaunchSubStruct = WsLaunchSubStruct() # tag 4 def write_to(self, os: TafOutputStream): # 注意: 浏览器不优化空字符串,tag1 强制写(HAR 实证) _opt_int(os, 0, self.lUid) os.write_string(1, self.s1) # 强制写空字符串 _opt_str(os, 2, self.sHuYaUA) _opt_str(os, 3, self.appSrc) _opt_struct(os, 4, self.sub) def read_from(self, ins: TafInputStream): pass # ============================================================ # 商品查询 # ============================================================ class GetGoodsInfoReqV5(TafStruct): """商品查询请求 (shopMiddleUI.getGoodsInfoV5)""" def __init__(self): 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 def write_to(self, os: TafOutputStream): # HAR2 实证: 全写字段 os.write_struct(0, self.userId) os.write_struct(1, self.shopAppInfo) os.write_int64(2, self.pid) os.write_string(3, self.gameId) os.write_string(4, self.spuId) os.write_string(5, self.channelStockCode) os.write_int64(6, self.skuId) os.write_int64(7, self.inviterUid) os.write_int64(8, self.userModifyPriceId) def read_from(self, ins: TafInputStream): # 请求结构体一般不需要读,但保留 pass class GoodsInfoRsp(TafStruct): """商品查询响应(简化:只取关键字段)""" def __init__(self): self.code: int = 0 # tag 0 self.message: str = "" # tag 1 # tag 2 是 goodsInfo 结构,简化跳过 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) self.message = ins.read_string(1, default=self.message) # goodsInfo (tag 2) 跳过 self.selfGoods = ins.read_int32(3, default=self.selfGoods) self.marketStatus = ins.read_int32(5, default=self.marketStatus) self.timestamp = ins.read_int64(6, default=self.timestamp) def write_to(self, os: TafOutputStream): pass # ============================================================ # 订单历史 # ============================================================ class OrderListShopInfo(TafStruct): """订单明细里的店铺信息(只取展示需要的字段)""" def __init__(self): self.shopName: str = "" # tag 0 def read_from(self, ins: TafInputStream): self.shopName = ins.read_string(0, default=self.shopName) _skip_to_struct_end(ins) def write_to(self, os: TafOutputStream): pass 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.shopInfo: Optional[OrderListShopInfo] = None # tag 21 self.points: int = 0 # tag 23 def read_from(self, ins: TafInputStream): self.spuId = ins.read_string(4, default=self.spuId) self.skuId = ins.read_int64(16, default=self.skuId) self.buyerUid = ins.read_int64(18, default=self.buyerUid) self.virtualType = ins.read_int32(19, default=self.virtualType) self.quantity = ins.read_int64(20, default=self.quantity) self.shopInfo = ins.read_struct(21, OrderListShopInfo) self.points = ins.read_int64(23, default=self.points) _skip_to_struct_end(ins) def write_to(self, os: TafOutputStream): pass 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.goodsDetail: Optional[OrderListGoodsDetail] = None # tag 16 def read_from(self, ins: TafInputStream): self.bizOrderId = ins.read_string(0, default=self.bizOrderId) self.appId = ins.read_string(1, default=self.appId) self.orderId = ins.read_string(2, default=self.orderId) self.pid = ins.read_int64(3, default=self.pid) self.shopName = ins.read_string(4, default=self.shopName) self.orderStatus = ins.read_int32(5, default=self.orderStatus) self.itemName = ins.read_string(8, default=self.itemName) self.unitPrice = ins.read_int64(9, default=self.unitPrice) self.quantity = ins.read_int64(10, default=self.quantity) self.totalPrice = ins.read_int64(12, default=self.totalPrice) self.createTime = ins.read_int64(14, default=self.createTime) self.payTime = ins.read_int64(15, default=self.payTime) self.goodsDetail = ins.read_struct(16, OrderListGoodsDetail) _skip_to_struct_end(ins) def write_to(self, os: TafOutputStream): pass 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 def write_to(self, os: TafOutputStream): os.write_struct(0, self.userId) os.write_int32(1, self.offset) os.write_int32(2, self.orderType) os.write_int32(3, self.pageSize) os.write_int32(4, self.status) def read_from(self, ins: TafInputStream): pass class QueryUserOrderListRsp(TafStruct): """购买历史订单响应""" def __init__(self): self.code: int = 0 self.message: str = "" self.totalCount: int = 0 self.orders: List[OrderListItem] = [] @staticmethod def _read_order_item(ins: TafInputStream, _tag: int): item = OrderListItem() item.read_from(ins) _end_tag, dtype = ins.read_head() if dtype != TafType.STRUCT_END: raise ValueError(f"期望订单 STRUCT_END,实际 0x{dtype:02x}") return item 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.totalCount = ins.read_int64(2, default=self.totalCount) self.orders = ins.read_list(3, item_reader=self._read_order_item) def write_to(self, os: TafOutputStream): pass # ============================================================ # 下单 # ============================================================ 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 def write_to(self, os: TafOutputStream): # HAR 实证:下单 extraParam 会强制写默认 0/空串字段 os.write_int64(0, self.freight) os.write_string(1, self.channelStockType) os.write_string(2, self.channelStockCode) os.write_string(3, self.relatedBizId) os.write_string(4, self.bizParams) os.write_string(5, self.popupTraceId) os.write_int64(6, self.supplierUid) if self.categoryId: os.write_string(7, self.categoryId) os.write_string(8, self.ext) def read_from(self, ins: TafInputStream): pass class CreateOrderPromotionParam(TafStruct): def __init__(self): 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 os.write_int64(0, self.yxjDeductPrice) os.write_int64(1, self.userModifyPriceId) os.write_int32(2, self.enablePromotion) def read_from(self, ins: TafInputStream): pass class CreateOrderAccountParam(TafStruct): def __init__(self): self.payoutTypeList: List[int] = [] # tag 0 Vector 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 值也会写出 os.write_list(0, self.payoutTypeList) os.write_int64(1, self.payoutChargeAmount) os.write_list(2, self.cancelPayoutTypeList) os.write_int64(3, self.recycleSupplierId) os.write_int64(4, self.claimPrice) def read_from(self, ins: TafInputStream): pass class PromotionItem(TafStruct): def __init__(self): self.promotionId: int = 0 # tag 0 self.promotionType: int = 0 # tag 1 def write_to(self, os: TafOutputStream): _opt_int(os, 0, self.promotionId) _opt_int(os, 1, self.promotionType) def read_from(self, ins: TafInputStream): self.promotionId = ins.read_int64(0, default=self.promotionId) self.promotionType = ins.read_int32(1, default=self.promotionType) 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 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 self.sourceId: str = "" # tag 16 self.env: Dict[str, str] = {} # tag 17 Map 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 def write_to(self, os: TafOutputStream): # HAR 实证:createOrderV5 会写出完整字段,即使值为 0/空串/空 list os.write_struct(0, self.userId) os.write_struct(1, self.shopAppInfo) os.write_int64(2, self.receiveId) os.write_int64(3, self.pid) os.write_int64(4, self.skuId) os.write_int64(5, self.itemCount) os.write_string(6, self.remark) os.write_string(7, self.spuId) os.write_string(8, self.gameId) os.write_int64(9, self.orderId) os.write_int32(10, self.src) os.write_list(11, self.couponUserIds) os.write_int32(12, self.orderType) os.write_struct(13, self.extraParam or CreateOrderExtraParam()) os.write_int32(14, self.scene) os.write_list(15, self.promotionItems) os.write_string(16, self.sourceId) os.write_map(17, self.env) os.write_int32(18, self.orderScene) os.write_string(19, self.watchWord) os.write_string(20, self.marketingChannel) os.write_struct(21, self.promotionParam or CreateOrderPromotionParam()) os.write_string(22, self.externalTraceKey) os.write_int64(23, self.kefuUid) os.write_struct(24, self.accountParam or CreateOrderAccountParam()) os.write_int64(25, self.parentOrderId) os.write_string(26, self.vendorAccountType) os.write_string(27, self.vendorAccountVal) os.write_string(28, self.vendorSubAccountVal) os.write_string(29, self.vendorSubAccountType) os.write_int32(30, self.bizType) os.write_int32(31, self.gameCategoryId) os.write_string(32, self.ext) def read_from(self, ins: TafInputStream): pass 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时可能有) 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.orderId = ins.read_int64(2, default=self.orderId) self.subOrderId = ins.read_int64(4, default=self.subOrderId) self.orderStatus = ins.read_int32(5, default=self.orderStatus) self.riskUrl = ins.read_string(8, default=self.riskUrl) def write_to(self, os: TafOutputStream): pass # ============================================================ # 支付(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 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.orderId = ins.read_int64(2, default=self.orderId) self.appOrderId = ins.read_string(3, default=self.appOrderId) self.payOrderId = ins.read_string(4, default=self.payOrderId) self.payUrl = ins.read_string(5, default=self.payUrl) self.amount = ins.read_int64(6, default=self.amount) def write_to(self, os: TafOutputStream): pass