Files
yml2213 d21b06a311 新增商品类目(限量返场/精英专享)支持与版本号 0.2.1
10.15 抓包确认:积分中心商城 PlayMallUI.queryAllocation 的商品与 webActUI.getActPrizeList(sid=2277) pid 完全一致,属于同一批商品,因此列表无需接商城链路;真号实测 sid=2277 返回 30 项(限量返场 99 共 12 项 + 精英专享 101 共 18 项),sid=2203 已返回空。\n\n- huya:解析商品 tag23/25/26/27/28(发布时间、上架/下架时间、每日时段),新增 PrizeInfo.CategoryIDs/InCategory/StartsAt/NotYetOnSale 与响应级 CategoryStats/CategorySummary/FilterByCategory,列表日志带类目统计\n- sniper:Config.Category 限定自动选品范围,未命中提示带类目\n- CLI:新增 -category / -category-only\n- GUI:Config.Category、ListPrizes 回传类目字典与统计;界面商品列表按类目分页签、每行标注类目、未上架显示上架时间与倒计时,所选类目随配置提交给监控\n- 测试:新增 internal/huya/category_test.go 与 sniper 类目用例;README/WSS_NOTES 补记结论
2026-10-15 18:23:38 +08:00

9.0 KiB
Raw Permalink Blame History

WSS 协议要点

本文件记录 2026-07-06 手动浏览器 HAR、页面 JS 和 Go 版本当前实现的复核结论。

抓包结论

手动浏览器链路是 HTTP + WSS 混合:

  • 页面初始化、商品信息、HttpDNS 等有多条 https://cdnws.api.huya.com/ POST。
  • 浏览器同时建立了 3 条 wss://77bc035c-ws.va.huya.com/ 连接。
  • scoreExchangePrize 出现在 WSS 连接数据和静态 JS 中,没有出现在 cdnws POST 请求中。
  • 当前 HTTP 版本可用,是因为 webActUI 的 WUP over HTTP 编码已经对齐。
  • Go 版 WSS 已改为浏览器同款 WebSocketCommand 外层,仍需真实 Cookie 联调确认服务端返回。

连接

  • WebSocket URL:wss://<host>/?baseinfo=<base64 taf>
  • 手动 HAR host:77bc035c-ws.va.huya.com
  • host 来自 launch.queryHttpDns 或浏览器抓包,不应长期写死。
  • 手动 HAR 中 baseinfo.cookie 与 traceId 为空,cookie 通过后续 VerifyCookie 命令发送。
  • 握手启用 permessage-deflate。
  • 浏览器请求扩展:permessage-deflate; client_max_window_bits
  • 服务端返回扩展:permessage-deflate;server_no_context_takeover;client_no_context_takeover

正确外层结构

浏览器 JS 不是 1B version + 1B command + 4B sequence + body。

页面实际构造 TAF/JCE WebSocketCommand:

WebSocketCommand {
  0: iCmdType int32
  1: vData bytes
  2: lRequestId int64
  3: traceId string
  4: iEncryptType int32
  5: lTime int64
  6: sMD5 string
}

WUP 请求发送逻辑:

Wup.setServant(servant)
Wup.setFunc(func)
Wup.setRequestId(requestId)
Wup.writeStruct("tReq", req)

WebSocketCommand.iCmdType = EWSCmd_WupReq(3)
WebSocketCommand.vData = Wup.encode()
WebSocketCommand.traceId = "<trace>:<trace>:0:<enableTrace>"
ws.send(WebSocketCommand.writeTo(JceOutputStream))

服务端返回 EWSCmd_WupRsp(4),内部 vData 才是 WUP 响应。

命令枚举

页面 JS 中可见的关键枚举:

  • 3:EWSCmd_WupReq
  • 4:EWSCmd_WupRsp
  • 5:EWSCmdC2S_HeartBeat
  • 6:EWSCmdS2C_HeartBeatAck
  • 10:EWSCmdC2S_VerifyCookieReq
  • 11:EWSCmdS2C_VerifyCookieRsp
  • 16:EWSCmdC2S_RegisterGroupReq
  • 17:EWSCmdS2C_RegisterGroupRsp
  • 20:EWSCmdC2S_HeartBeatReq
  • 21:EWSCmdS2C_HeartBeatRsp
  • 23:EWSCmdC2S_UpdateUserExpsReq
  • 24:EWSCmdS2C_UpdateUserExpsRsp
  • 33:EWSCmdC2S_UpdateUserInfoReq
  • 34:EWSCmdS2C_UpdateUserInfoRsp

手动 HAR 解出的服务端 WSS 回包包括:

  • EWSCmdS2C_HeartBeatRsp
  • EWSCmd_WupRsp launch.wsLaunch
  • EWSCmd_WupRsp mobileui.getConfig
  • EWSCmd_WupRsp webActUI.getActPrizeList
  • EWSCmd_WupRsp webActUI.getUserScore
  • EWSCmd_WupRsp webActUI.scoreExchangePrize

初始化流程

浏览器 WSS session 的关键流程:

  1. 连接 wss://<host>/?baseinfo=<base64 taf>。
  2. 发送 EWSCmdC2S_RegisterGroupReq(16),默认 group 为 live:0。
  3. 通过 WSS 调用 launch.wsLaunch,手动 HAR 中 requestId 为 -1。
  4. 发送 EWSCmdC2S_VerifyCookieReq(10),cookie 在这里进入 WSS 通道。
  5. 通过 WSS 调用 mobileui.getConfig。
  6. 发送 EWSCmdC2S_UpdateUserExpsReq(23),请求体是 getConfig 下发的版本 map(实测 {"58664":"107263"},Go 版已按此回传)。
  7. 再发送一次 EWSCmdC2S_RegisterGroupReq(16)。
  8. 发送 EWSCmdC2S_HeartBeatReq(20),后续维持心跳。
  9. launch.queryHttpDns 仍通过 HTTP cdnws 调用。
  10. 后续 WUP 业务在连接可用时走 signalSdk.sendWup,连接不可用时可 fallback 到 sendWupHttp。

握手 baseinfo(11 个字段)实测带 exp(tag5),值就是 getConfig 的版本串;cookie(tag8)/traceId(tag9) 为空,tag10 是空的 customHeaders map。

响应应按 WUP iRequestId 匹配回调,不能只用 FIFO。

活动接口

当前 Go HTTP 版本已实现:

  • webActUI.getActPrizeList
  • webActUI.getUserScore
  • webActUI.getUserPrizeRecords
  • webActUI.scoreExchangePrize

WSS 版本仍是实验状态,但外层 WebSocketCommand、初始化帧顺序和 requestId 匹配已按手动 HAR 修正。

抢购兑换请求结构:

tReq.user
tReq.sid
tReq.pid
tReq.ip = ""
tReq.clientEnv = {}
tReq.source = ""
tReq.isRole = 1

浏览器手动抢购小黄鸭成功回包确认:

  • webActUI.scoreExchangePrize
  • sid=2203
  • pid=12889
  • status=200
  • msg=请求成功
  • exchangeInfo.pid=12889
  • exchangeInfo.exchangeScore=988
  • exchangeInfo.desc=兑换奖品:套装-潮萌小黄鸭

launch.queryHttpDns(已实现)

2026-10-13「精英宝典」抓包确认:m-shop.huya.com 页面用 HTTP cdnws 调它换取 WSS host,iRequestId 固定 -1。

请求 tReq:

tReq.0 uid        int64
tReq.1 ua         string        # webh5&0.0.1&websocket&&h5_/points.html
tReq.2 domains    list<string>  # ["cdnws.api.huya.com"]
tReq.3 appSrc     string        # HUYA&ZH&2052
tReq.4 ""         string
tReq.5 0          int32
tReq.6 1          int32

响应 tRsp:

tRsp.0 map<domain, {0: list<string> hosts, 1: int32 ttl秒, 2: list<string>}>  # 实测 ttl=1296000
tRsp.1 int32

Go 版:HTTPClient.QueryHttpDns + WSSClient.resolveHost(TTL 缓存、多 host 轮询、失败回退默认 host)。

launch.pingSignal(已实现)

页面在 HTTP cdnws 上调用它做链路探测,iRequestId=0:

tReq.0 "launch"     tReq.1 "pingSignal"     tReq.2 map<string,string> 空
tRsp.0 "launch"     tRsp.1 "pingSignal"     tRsp.2 map<string,int64>{signal_ap_start, signal_ap_end}

Go 版:HTTPClient.PingSignal(HTTP 预热)与 WSSClient.PingSignal(WSS 预热后探测业务通道),预热结果只记日志、不影响抢购。

商城链路(仅记录,未实现)

同一抓包中「开通精英宝典 / 购买幸运币」属于商城付费链路,全部走 WSS 上的 shopMiddleUI.*:

getGoodsInfoV5 → getSupplierInfoV5 → listPayChannelV5 → checkHyProtocolV5 → acceptHyProtocolV5
→ calcOrderPromotion → checkUserBuyAuth → createOrderV5 → payOrderSubmitV5(支付宝二维码) → orderDetailV5(轮询)
另有 createOrderV6(另一条下单通道)与 revenueWebUI.queryUserOrderList

注意函数名带版本后缀(V5/V6)。webActUI 在 WSS 上同样可用(实测 checkUserBindGameAccount、getUserPrizeRecords)。

积分中心商城的商品列表(PlayMallUI.queryAllocation)

2026-10-15 HAR(m-shop.huya.com/points.html?pagecode=heping_baodian_credit_center_page)确认:

tReq.0 user        {uid, guid, token, ua=web&1.0.0&huya, cookie, tokenType, deviceInfo}
tReq.1 {0:"huya", 1:1001, 3:"", 5:7, 6:"yellowcarlist", 7:""}
tReq.2 pageId      string  # "heping_baodian_credit_center_page"
tReq.3 ""          tReq.4 0   tReq.5 1   tReq.6 24
tReq.7 extraJson   string  # 切换类目时是 {"jumpTabId":433}
tReq.11 list<int>  # [6, 19]
tRsp.0 {0:200(status), 1:"成功"}
tRsp.1 {0:"和平精英宝典积分中心", 1:pageId}
tRsp.2 list<tab>{0:tabId(343/345), 4:int, 5:bytes}   # 5 是内嵌 TAF 负载
tRsp.3 list<string>  # CDN 域名

内嵌负载解码后是页面区块(tabId 433=限量返场、431=精英专享),每个区块的 6: list<商品> 里带 tag9=2277(sid)、tag10="13433"(pid)、价格是活动积分的 10 倍。

结论:商城 grid 与 webActUI.getActPrizeList(sid=2277) 是同一批商品(pid 完全一致), 本项目不需要走商城链路取列表;10.13 该负载只有 431(精英专享) 一个区块,10.15 新增 433(限量返场)。

商品类目(限量返场 / 精英专享)

webActUI.getActPrizeList 响应除商品外还下发类目字典,商品侧用 tag14 标注归属:

tRsp.3 moduleId  int32
tRsp.4 list<PrizeTag>{0:id, 1:name, 2:weight}   # 2026-10-15 实测:101=精英专享、99=限量返场
PrizeInfo.14 tags string                        # 类目 id,多个用逗号分隔

2026-10-15 实测 sid=2277:30 项商品 = 限量返场(99) 12 项(怪诞乔克服装礼包 8000 积分等, 页面显示「xx:xx:xx 后上架」)+ 精英专享(101) 18 项。sid=2203 的旧活动已停止返回内容。 Go 版:GetActPrizeListResp.CategorySummary/CategoryStats/FilterByCategory 与 PrizeInfo.CategoryIDs/InCategory/StartsAt/NotYetOnSale,CLI -category / -category-only, 界面商品列表按类目筛选并标注类目。

商品侧还有一组时间字段(此前未解析):

PrizeInfo.23 publish_time int64   # 商品发布时间(实测 2026-10-15 11:04:57)
PrizeInfo.25 start_time   int64   # 上架时间,页面倒计时来源(实测 2026-10-16 17:00:00)
PrizeInfo.26 end_time     int64   # 下架时间(实测 2026-10-23 23:59:59)
PrizeInfo.27/28 daily_start/daily_end string  # 每日可兑时段 "00:00:00"/"23:59:59"

待完善

  • 使用真实 Cookie 联调 list/score/exchange,确认服务端不再超时。
  • WSS 侧对 launch.pingSignal 的实际可用性需真号验证(失败已降级为日志)。
  • 补更多基于真实抓包摘要的 WSS 编解码单测。
  • 限量返场商品是「预告上架」,兑换时的服务端文案(如「未开放」)与开抢瞬间的表现需真号验证。