# 虎牙精英宝典 HAR 重新分析(Python) ## 1. 样本与结论 | HAR | SHA-256 | entries | 结论 | |---|---|---:|---| | `手机扫码登录-pc直播间-1.har` | `bcb1e830bfc368c9029f85f90458b708dcf086993a79b833e8e544c65e5c679f` | 1947 | 扫码登录、PC 直播间初始化、登录态建立 | | `pc直播间-精英宝典1.har` | `f8d13a837c477aa4dc55b27df6828ffb7840bbdcccf7c078f840164d62a00e07` | 580 | 精英宝典初始化、绑定、任务、奖品、积分、记录、商城前置 | 页面地址为 `https://zt.huya.com/b02faae1/pc/index.html`。活动固定业务参数为 `sid=2203`、`actId=25135`、绑定活动 `bActId=9271`、任务模块 `moduleId=20051`、游戏 `cjm`、组件 `gid/componentId=3203`。 核心结论:协议是 WSS 二进制通道承载的 WUP/TAF RPC,HTTP `cdnws.api.huya.com` 可作为现有 Python 客户端的兜底。样本没有捕获积分兑换成功请求、兑换详情响应或支付完成闭环,因此这些字段不能从 HAR 推测。 ## 2. 传输层与帧解码 - 活动页主通道:`wss://wsapi.huya.com/?baseinfo=<动态值>`。 - 直播间/绑定入口还使用 `wss://d35bf35c-ws.va.huya.com/?baseinfo=<动态值>`。 - HTTP 兜底:`POST https://cdnws.api.huya.com/?baseinfo=<动态值>`,请求体为 WUP 包。 - HAR 的业务消息在 entry 的 `_webSocketMessages`,不是普通 HTTP entry body。 - WebSocket 帧前 6 字节是通道头(command、sequence),后续 body 再提取 WUP;WUP body 从 `0x10 0x03` 字段开始。仓库的 `WssMessage.decode()`、`frame_decoder._extract_wup()` 和 `WupResponse.decode()` 可以复用。 - `launch.wsLaunch`、`mobileui.getConfig` 完成连接初始化;业务 WUP `requestId` 在同一会话中递增,不能写死 HAR 值。 ## 3. 请求链 精英宝典 WebSocket entry 75 的可复现顺序如下: 1. `launch.wsLaunch` -> `mobileui.getConfig`。 2. `GameBizUI.getQiWeiRelationByUid`(页面辅助信息)。 3. `webActUI.checkUserBindGameAccount(bActId=9271)`,返回和平精英、微信/QQ 平台、角色绑定状态。 4. 并行查询:`getActPrizeList(sid=2203)`、`getActInfo(actId=25135)`、`getActTaskDetail(actId=25135)`。 5. `getActUserTaskDetail(userId, actId=25135)`、`getUserScore(sid=2203)`。 6. 绑定流程:再次检查绑定 -> `huyauserui.getUserProfileBatch` -> `getLiveLinkParam(bActId=9271)` -> `confirmBindActAccount(bActId=9271)` -> 再次检查绑定。 7. 记录页:`getUserPrizeRecords(sid=2203)` -> `getEntityPrizeFieldMap` -> `getModuleAddress(moduleId=20051)`。 8. 商城前置:`shopMiddleUI.getGoodsInfoV5`;确认页还调用 `getSupplierInfoV5`、`listPayChannelV5`、`checkHyProtocolV5`、`PlayMallUI.getMyPromotion`、`calcOrderPromotion`。 9. 页面会重复刷新 `getActPrizeList` 与 `getUserScore`;轮询间隔由工作台自行配置,建议 10-30 秒,写操作后只做一次短轮询。 第一份 HAR 的扫码登录链为: ```text GET /web/middle/2.6/.../https/... POST /qrLgn/getQrId GET /qrLgn/getQrImg?k=&appId=5002 POST /qrLgn/tryQrLogin # stage=0 -> stage=1 -> success POST /web/cookie/verify ``` 成功登录后得到的 UID、Cookie、`baseinfo` 必须传入同一个 WSS/HTTP 会话。二维码 ID、requestId、context、biztoken、guid、`udb_cred`、`sdid`、时间戳和签名均为动态值,报告不固化样本机密值。 ### 参数分类 | 参数 | 类型 | Python 处理 | |---|---|---| | `sid/actId/bActId/moduleId/gid` | 业务参数 | 配置项,默认值只对应本次页面 | | `uid`、游戏账号/角色 | 用户输入或前序响应 | 从当前登录态或绑定响应读取 | | Cookie、`baseinfo`、WSS session | Token/Session | `HuyaHttpClient` 运行时构造并保持 | | `qrId/requestId/context/traceId` | 随机/请求序号 | 登录和 RPC 运行时生成 | | `t/code/sig`、`biztoken`、`udb_cred`、`sdid` | 动态签名/认证 | 只转发前序响应或 Cookie;算法未闭合,不重算 | | `startTime/endTime/leftNum/isCanExchange` | 服务端状态 | 每次查询覆盖本地快照,不写死 | HTTP 请求保留 `User-Agent`、`Origin`、`Referer`、`Content-Type: application/octet-stream` 和 `Accept-Language`;`Host`、`Content-Length`、`Connection` 由 HTTP 库生成。WSS 的 `baseinfo` query 和二进制 body 由会话客户端生成,不复制浏览器历史头。 ## 4. 业务响应复核 ### 活动信息 `getActInfo`:`status=200`,`actId=25135`,名称“活动+购买任务”,时间戳 `1782662400..1790783999`,任务模块 `20051`,外部任务活动 `17096`,游戏 `cjm`,组件 `3203`。`17096` 与绑定用的 `9271` 是不同字段,代码中必须分开命名。 ### 奖品列表 `getActPrizeList` 在 entry 75 的多次响应均返回 **29 项**、2 个分类:`91=限量返场`(11 项)和 `83=精英专享`(18 项)。示例: | pid | 名称 | 积分 | 分类 | |---:|---|---:|---:| | 13161 | 破片手榴弹-粉粉猫爪大礼包 | 3600 | 91 | | 13163 | 烟雾弹-猫咪绅士大礼包 | 3600 | 91 | | 13167 | 套装-幸运萌趣趣 | 1980 | 91 | | 13165 | 套装-奇妙萌趣趣 | 688 | 91 | | 13169 | 套装-幸福萌趣趣 | 1680 | 91 | | 12873 | 套装-青涩年华 | 488 | 83 | | 12867 | 套装-雪山精英 | 600 | 83 | | 12879 | 套装-浪漫波比 | 1280 | 83 | | 12865 | 幸运币礼包(大) | 900 | 83 | 完整结构还包含 `id/sid/name/score/type/img/sort/frequency/frequencyLimit/num/status/percent/newScore/tags/leftNum/isShowNum/usedNum/goodType/detailImg/detailDesc/whiteUser/commonPrizeId/updateTime/isCanExchange/exchangeStartTime/exchangeEndTime/startTimeSlot/endTimeSlot/isTodayLimit/isUserLimit`。库存、排序和可兑换状态在重复轮询中变化,不能硬编码;工作台应以最新响应覆盖快照。 ### 任务与用户进度 `getActTaskDetail` 返回 6 个任务: - 购买精英令 *100,奖励 10000 分,SPU `hy-5874270`。 - 购买精英令 *10,奖励 1000 分,SPU `hy-5874269`。 - 购买精英令 *1,奖励 100 分,SPU `hy-5879346`。 - 购买精英徽章,奖励 10 分,SPU `hy-5872824`。 - 每天观看和平精英直播 10 分钟,奖励 2 分,任务类型 56。 - 订阅和平精英福利提醒,奖励 5 分,任务类型 68。 `getActUserTaskDetail` 的每项至少有 `actId/taskId/taskStatus/prizeStatus/taskValue/taskCount/prizeCount/subTaskDetail`,它是展示任务进度、完成和领取状态的来源。 ### 积分 响应字段顺序为 `status/msg/gainScore/usedScore/newGainScore/newUsedScore/score/isOpenExchangeTip`。样本值为 `4200,4200,4200,4200,0,0`;页面逻辑优先使用 `max(0, newGainScore-newUsedScore)`,所以当时可用积分为 **0**,不能把任意一个累计字段直接当余额。 ### 绑定、记录与地址 - 绑定响应显示游戏“和平精英”,账号平台可为微信或 QQ,`isBindAccount`、`isBindRole`、`isNeedActCheck` 和 `changeBindTime` 随状态变化。 - `getLiveLinkParam` 返回 `actId=17096`、`gameIdList=cjm`、`livePlatId=huya`、动态 `t/code/sig`。样本不足以闭合 `code/sig` 算法,不实现猜测签名。 - `confirmBindActAccount` 样本为 `status=200,msg=请求成功`。 - `getUserPrizeRecords` 返回 2 条历史记录,字段包括 `id/name/score/createTime/status/type/isReal/newScore/account/code/commonPrizeType/commonPrizeStatus/orderId/img/goodType/prizeId/recordId/commonPrizeExchangeStatus/commonPrizeId/prizeExtraParam/ymd/commonPrizeMsg`。 - `getEntityPrizeFieldMap` 给出实体奖品字段映射:模块 1 为姓名/手机/地址,模块 38 为 QQ,模块 39 为姓名/手机;`getModuleAddress(moduleId=20051)` 返回 `status=201,msg=找不到地址`。 ## 5. Python 实现边界 仓库现有 `core/huya/http_client.py` 已实现 WUP/TAF、会话 Cookie、积分、奖品列表、记录、任务、绑定、直播链接和商城方法;新增示例见 [`scripts/huya_elite_client.py`](../scripts/huya_elite_client.py)。示例不读取 HAR,运行时从环境变量读取 UID/Cookie,调用顺序为:活动信息/任务/用户任务/绑定/奖品/积分/记录,写操作前重新查询奖品详情。 推荐状态机: ```text 登录态 -> getActInfo/getActTaskDetail -> checkBind/getActPrizeList/getActUserTaskDetail/getUserScore -> (用户确认后) getActPrizeDetail -> scoreExchangePrize -> getUserScore/getActPrizeList/getUserPrizeRecords 回查 ``` 兑换必须二次校验 `isCanExchange`、活动时间、库存、频次、今日/用户限制和可用积分。`getActPrizeDetail`、`scoreExchangePrize` 成功响应以及地址写入尚未出现在样本中,客户端应把它们作为显式待补接口,而不是猜测字段。 ## 6. 尚缺证据与补抓清单 当前 HAR 未闭合:`getActPrizeDetail` 响应、`scoreExchangePrize` 成功请求/响应、`addModuleAddress`、`createOrderV5`、`payOrderSubmitV5`、支付完成后的订单状态和宝典开通字段。需要重新抓“点击兑换”和“点击开通宝典至支付完成”的完整操作,并保留 Initiator、页面 JavaScript 和同一登录态。