- 协议事实确认:launch.pingSignal 的 signal_ap_start/signal_ap_end 是 Unix 毫秒墙钟时间戳。真实夹具 signal_ap_start=1791891743288 对应北京时间 2026-10-13 19:42:23,与同批抓包订单号里的秒级时间戳 1791891953 同日;start 是服务端收到请求的时刻、end 是写出响应的时刻,实测相差 13ms - 新增 internal/huya/timecal.go:TimeSample/TimeCalibration/OffsetHistory/RunTimeCalibration/AlignedTriggerTime/TryCalibrateServerTime。偏移 = signal_ap_start − (SentAt + RTT/2),误差上界取 RTT/2;默认 3 轮采样取「误差上界最小、其次 RTT 最低」的样本,再对最近 3 次偏移取中位数平滑;ServerTimestamp 丢弃解析后不在 2020-2100 的时间戳,避免字段单位变化时按错误偏移挪走抢购窗口 - HTTPClient 与 WSSClient 各自实现 TimeCalibrator(PingSignalSample 共用 pingSignalSampleWith 采样逻辑),各自持有 OffsetHistory - launch.go:ServerElapsed 改用 PingSignalStartKey/PingSignalEndKey 常量 - CLI:新增 -time-sync(默认开),-at 按服务端时间理解,触发时刻 = 目标 − lead − 偏移;校准日志输出服务端时间、本机偏移、误差上界、RTT 与样本数 - GUI:Config.TimeSync(默认 true)+ 对齐触发与校准日志;前端设置面板新增「时间校准」开关并纳入配置读写 - 测试:新增 internal/huya/timecal_test.go,覆盖真实夹具时间戳换算、秒级/异常值拒绝、RTT/2 中点公式、多轮取优、中位数滤跳变、全失败降级与部分失败容错、跨钟差的触发时刻换算;README 补「时间校准」一节 - 验证:gofmt 干净、go build ./...、go test ./...、go test -race(huya/sniper/gui)、前端 npm run typecheck/build 全部通过
205 lines
12 KiB
Markdown
205 lines
12 KiB
Markdown
# huya-go
|
||
|
||
虎牙活动积分兑换/抢购的 Go 复刻版。支持两种传输层,CLI 与 GUI 默认都走 `wss`:
|
||
|
||
- `wss`(默认):浏览器实际使用的通道。先调用 `launch.queryHttpDns` 解析可用 host,再走 TAF `WebSocketCommand` 外层与 WSS 初始化流程(`launch.wsLaunch` → `mobileui.getConfig`)。host 留空即自动解析,解析失败会回退内置默认 host。
|
||
- `http`:走 `https://cdnws.api.huya.com/`,作为可回退通道;CLI 用 `-transport http`,GUI 在设置里切换。
|
||
|
||
## 构建
|
||
|
||
CLI:
|
||
|
||
```bash
|
||
go test ./...
|
||
go build -o huya-go ./cmd/huya-go
|
||
```
|
||
|
||
GUI:
|
||
|
||
```bash
|
||
cd frontend
|
||
npm install
|
||
npm run typecheck
|
||
cd ..
|
||
wails build -debug -nopackage
|
||
```
|
||
|
||
GUI 二进制输出到 `build/bin/huya-gui`。开发时可以使用 `wails dev` 启动 Wails + React + Ant Design 界面。
|
||
|
||
Windows exe(文件名带版本号):
|
||
|
||
```bash
|
||
./scripts/build-windows.sh # 默认 windows/amd64
|
||
./scripts/build-windows.sh arm64 # 如需 windows/arm64
|
||
```
|
||
|
||
产物为 `build/bin/huya-gui-v<版本>.exe`(PE32+ GUI)。版本号的单一来源是 `internal/version/version.go`:脚本从这里读取版本号,既拼进 exe 文件名,也通过 `-ldflags "-X huya-go/internal/version.Version=..."` 编译进程序,界面左下角的版本标签来自同一个值。发布新版本时改这一处,并同步 `wails.json` 的 `info.productVersion`(只影响 exe 文件属性)。在 macOS 上交叉编译不会生成 NSIS 安装包(`-nopackage`),只产出 exe。
|
||
|
||
## Cookie
|
||
|
||
推荐把账号 Cookie 放到本地文件,一行一个账号:
|
||
|
||
```text
|
||
yyuid=...; udb_uid=...; guid=...; ...
|
||
```
|
||
|
||
不要把 Cookie、token、JWT 提交到仓库或写进文档。
|
||
|
||
## 常用命令
|
||
|
||
GUI 界面提供账号预览、奖品列表、积分查询、兑换记录、直接兑换、监控抢购、实时日志。
|
||
|
||
CLI 示例:
|
||
|
||
查询奖品列表:
|
||
|
||
```bash
|
||
./huya-go -cmd list -sid 2203 -cookie-file cookie.txt
|
||
```
|
||
|
||
查询积分:
|
||
|
||
```bash
|
||
./huya-go -cmd score -sid 2203 -cookie-file cookie.txt
|
||
```
|
||
|
||
直接兑换指定 PID:
|
||
|
||
```bash
|
||
./huya-go -cmd exchange -sid 2203 -pid 12889 -cookie-file cookie.txt
|
||
```
|
||
|
||
按奖品名轮询并抢购:
|
||
|
||
```bash
|
||
./huya-go -cmd monitor -sid 2203 -name 小黄鸭 -cookie-file cookie.txt -loops 20 -delay 120 -concurrency 5
|
||
```
|
||
|
||
定时开始:
|
||
|
||
```bash
|
||
./huya-go -cmd monitor -sid 2203 -pid 12889 -cookie-file cookie.txt -at "2026-07-06 20:00:00"
|
||
```
|
||
|
||
以上默认都走 `wss`。需要回退到 http 传输时,给任意命令加上 `-transport http`。
|
||
|
||
## 多开(多实例)
|
||
|
||
同一台机器可以同时启动多个实例,互不干扰:
|
||
|
||
- 启动时按顺序占用「实例槽位」并加独占锁:第 1 个进程是**默认实例**(沿用历史数据目录),第 2 个及以后分别是 `实例 2`、`实例 3`…,数据目录为 `<数据目录>/instances/instance-N`。
|
||
- 每个实例独占自己的日志目录与 WebView2 用户数据目录,所以多开时日志不会交错,Windows 上也不会出现「第二个窗口开不出来」。
|
||
- 界面本地存储(账号、设置、商品缓存)按实例分片,每个实例各存各的账号;标题栏会显示当前实例名。
|
||
- 槽位锁在进程退出时自动释放;若锁目录不可写,会退回到「进程唯一」的临时实例目录(日志降级为仅内存),**绝不退回默认目录**,以免两个进程互相覆盖。
|
||
- 环境变量:`HUYA_DATA_DIR` 覆盖数据根目录,`HUYA_LOG_DIR` 覆盖日志目录,`HUYA_LOG_LEVEL` 覆盖默认日志等级。
|
||
|
||
## 日志
|
||
|
||
GUI 与 CLI 共用同一套结构化日志(分类 + 等级 + 账号标签),同时输出到界面与磁盘:
|
||
|
||
- 路径:`<数据目录>/logs/huya-go_<实例标识>_<时间戳>.log`。macOS 数据目录为 `~/Library/Application Support/huya-go`,Windows 为 `%AppData%\huya-go`。
|
||
- 每次启动创建独立文件(`O_EXCL` 抢占,重名自动加 `-2` 后缀),所以多开或同秒重启都不会写进同一个文件;文件开头写明实例名、数据目录与格式说明。
|
||
- 单文件上限 5MB,超出后滚动为 `.1` ~ `.5`,最旧的被丢弃。
|
||
- 每行格式:`时间 [分类][等级][账号] 正文`。逐条网络请求是 `debug` 级,默认不记录,可在界面「日志」页签打开「调试」开关,或 CLI 使用 `-log-level debug`。
|
||
- 写盘是异步且队列有界:队列满时丢弃日志并在文件尾部与界面提示丢弃条数,避免磁盘 I/O 拖慢抢购链路。
|
||
- 界面「日志」页签支持按等级筛选、按 ID 增量拉取、一键打开日志目录或复制路径。
|
||
- GUI 启动失败(例如 WebView2 建不出环境)时,`<用户缓存目录>/huya-go/startup.log` 会记录原因。
|
||
|
||
## 参数说明
|
||
|
||
- `-cmd`:`list`、`score`、`records`、`exchange`、`monitor`。
|
||
- `-sid`:活动 SID,必填。
|
||
- `-pid`:奖品 PID。`exchange` 必填,`monitor` 可用 `-pid` 或 `-name`。
|
||
- `-max-price`:目标奖品最高积分,`0` 表示不限制(CLI 与 GUI 设置都支持)。
|
||
- `-min-left`:目标奖品最少剩余库存,`0` 表示不限制(CLI 与 GUI 设置都支持)。
|
||
- `-category`:类目过滤,支持类目名(如 `限量返场`、`精英专享`)或类目 id(如 `99`);留空表示不限。
|
||
- `-category-only`:`list` 只输出该类目的商品(配合 `-category` 使用)。
|
||
- `-cookie`:单账号 Cookie。
|
||
- `-cookie-file`:多账号 Cookie 文件。
|
||
- `-transport`:`wss` 或 `http`,默认 `wss`。
|
||
- `-wss-host`:WebSocket host。默认留空,自动调用 `launch.queryHttpDns` 解析(带 TTL 缓存、多 host 轮询),解析失败时回退内置默认 host;需要固定时可显式指定。
|
||
- `-timeout`:单次调用超时秒数。
|
||
- `-loops`、`-delay`、`-concurrency`:轮询次数、轮询间隔、并发额度。`-concurrency` 限制的是**同时在途的请求数**(不是同时运行的账号数),账号在退避等待期间不占用额度。
|
||
- `-stop-after-success`:`monitor` 中任一账号成功后是否停止。
|
||
- `-at`:定时开始时间,支持 `RFC3339`、`2006-01-02 15:04:05`、`HH:MM:SS`;开启时间校准时按**服务端时间**理解。
|
||
- `-lead`:相对 `-at` 的提前启动量,默认 `2500ms`(用于覆盖网络往返,让请求在开抢瞬间已经在途)。
|
||
- `-time-sync`:默认开启。定时前用 `launch.pingSignal` 校准服务端与本机的钟差,并把触发时刻对齐到服务端时钟(详见「时间校准」)。
|
||
- `-log-dir`:日志目录,默认使用应用数据目录下的 `logs`。
|
||
- `-log-level`:日志最低等级,`debug`/`info`/`warn`/`error`,默认 `info`。
|
||
- `-v`:输出调试日志(等价于 `-log-level debug`,并把网络日志同时打到 stderr)。
|
||
|
||
## 抢购调度与退避(2026-10-16 调整)
|
||
|
||
参考 `dygou_go` 的抢购调度经验(该项目的实测复盘结论)做了以下调整:
|
||
|
||
- **并发额度按单次请求计**:额度只覆盖一次在途请求,账号在退避等待期间释放额度。旧实现把额度给整个账号循环,退避中的账号会一直霸占额度,账号多时互相饿死。
|
||
- **售罄不再默认终止**:服务端回「已抢完 / 库存不足」时继续按退避节奏探测到轮询结束——别人锁单未支付或取消会让库存回流。需要「售罄即停」时显式设置 `Config.StopOnSoldOut`。
|
||
- **下架仍然终止**:`已下架 / 奖品不存在` 视为本窗口内的终态,直接停止该奖品(与售罄区别对待)。
|
||
- **积分不足仍然终止**:积分不会自己变多,默认 `StopOnNotEnough`,避免无意义的重复请求。
|
||
- **退避带抖动**:所有退避(限频 / 失败 / 售罄探测)在配置值基础上叠加 0-25% 随机抖动,避免多个账号在同一时刻整批重试形成周期性尖峰;配置值始终是退避下限。
|
||
- **定时精确触发**:定时开抢改用一次性定时器(旧实现按 200ms 轮询,醒来时刻会随机晚 0-200ms,正好错过开抢窗口)。`-lead` 仍用于按 RTT 提前起跑。
|
||
- **任务结束汇总**:每次监控结束输出一行汇总(轮次 / 账号 / 目标 / 成功账号 / 耗时 / 请求次数)与按次数排序的结局分布(`success`、`sold_out`、`too_frequent`、`not_found` 等),便于复盘。
|
||
- **HTTP 传输连接池**:`http` 传输补上 `MaxConnsPerHost=64`、`MaxIdleConnsPerHost=64`(Go 默认每主机只有 2 个空闲连接),多账号并发时不再反复重建连接。
|
||
|
||
## 时间校准(2026-10-16 新增)
|
||
|
||
定时抢购默认先做一次服务端时间校准(CLI `-time-sync`,GUI 设置里的「时间校准」开关,默认开):
|
||
|
||
- **数据来源**:`launch.pingSignal` 响应里的 `signal_ap_start` / `signal_ap_end`。2026-10-13 抓包确认这两个字段是 Unix 毫秒时间戳(`start` 是服务端收到请求的时刻,`end` 是写出响应的时刻,实测相差 13ms);真实字节夹具见 `internal/huya/launch_test.go` 的 `realPingSignalRspHex`。
|
||
- **钟差估计**:`偏移 = signal_ap_start − (发送时刻 + RTT/2)`,误差上界取 `RTT/2`——服务端打时间戳的具体位置落在发送与接收之间,无法再精确,按最坏情况估计。
|
||
- **多轮取优**:默认 3 轮采样,取「误差上界最小、其次 RTT 最低」的样本;再对最近 3 次偏移取中位数平滑,压掉单轮网络抖动。
|
||
- **生效方式**:`-at` / `startAt` 按服务端时间理解,触发时刻 = `目标时刻 − 提前量 − 偏移`。本机时钟比服务端慢 300ms 就自动提前 300ms,反之推后。
|
||
- **失败降级**:一次样本都拿不到时只告警,仍按本机时钟触发;时间戳明显不合理(解析出来不是这个世纪的毫秒值)会被丢弃,避免按错误偏移整体挪走抢购窗口。
|
||
- **日志**:`时间校准完成: 服务端=2026-10-16 20:00:01.234 本机偏移=+123ms 误差<=45ms RTT=90ms 样本=3 已平滑`。
|
||
|
||
校准每次只多花几百毫秒(3 轮 pingSignal),发生在预热之后、等待定时之前,因此不会挤占开抢窗口。
|
||
|
||
## 已确认的小黄鸭参数
|
||
|
||
2026-07-06 浏览器手动抢购抓包确认:
|
||
|
||
- `sid=2203`
|
||
- `pid=12889`
|
||
- 奖品名:`套装-潮萌小黄鸭`
|
||
- 消耗积分:`988`
|
||
|
||
远端配置里的子奖品代码不能直接当兑换 PID,实际兑换以 `scoreExchangePrize` 请求里的 `pid` 为准。
|
||
|
||
## 精英宝典/幸运币(2026-10-13 抓包)
|
||
|
||
`m-shop.huya.com` 积分中心的兑换记录里确认到:
|
||
|
||
- `sid=2277`
|
||
- `pid=13409`
|
||
- 奖品名:`幸运币礼包(小)`
|
||
- 消耗积分:`20`
|
||
- 订单号格式:`sOrderId_2277_13409_<uid>_<时间戳>_<随机>`
|
||
|
||
该活动的列表/积分/兑换接口与 `sid=2203` 走同一套 `webActUI`,可直接用 `-cmd list -sid 2277` 验证;同一次抓包里「开通精英宝典 / 购买精英币」属于商城付费链路(`shopMiddleUI.*` + 支付宝),本项目未实现,详见 `WSS_NOTES.md`。
|
||
|
||
## 商品类目:限量返场 / 精英专享(2026-10-15 抓包)
|
||
|
||
`webActUI.getActPrizeList` 的响应除商品外还下发类目字典(`tRsp.4`),每件商品用 `tags`(TAF tag14)
|
||
标注归属:`101=精英专享`、`99=限量返场`。2026-10-15 真号实测 `sid=2277`:
|
||
|
||
- 共 30 件商品:限量返场(99) 12 件(怪诞乔克服装礼包 8000 积分、天网终结者大礼包 4800 积分、潮萌小黄鸭系列…)+ 精英专享(101) 18 件(幸运币/荣耀勋章礼包、套装…)。
|
||
- `sid=2203`(上一期活动)已停止下发商品,列表为空,需要把 SID 切到 `2277`。
|
||
- 限量返场是「预告上架」类商品:`start_time`(TAF tag25,实测 2026-10-16 17:00:00)与 `end_time`(tag26)决定
|
||
可兑换时间窗,页面上的「xx:xx:xx 后上架」倒计时就来自它;`daily_start/daily_end`(tag27/28)是每日可兑时段。
|
||
|
||
用法:
|
||
|
||
```bash
|
||
# 全量列表(日志里会打出「类目=精英专享(101)=18项/限量返场(99)=12项」)
|
||
./huya-go -cmd list -sid 2277 -cookie-file cookies.txt
|
||
|
||
# 只看限量返场(也支持类目 id 或模糊名,如 -category 99 / -category 返场)
|
||
./huya-go -cmd list -sid 2277 -cookie-file cookies.txt -category 限量返场 -category-only
|
||
|
||
# 抢购时限定只在该类目里选品(配合 -auto)
|
||
./huya-go -cmd monitor -sid 2277 -cookie-file cookies.txt -category 限量返场 -auto
|
||
```
|
||
|
||
界面「商品列表」同样按类目分了页签(全部 / 限量返场 / 精英专享,带商品数),每行标注类目,
|
||
未上架的商品显示上架时间与倒计时;当前选中的类目会一起提交给监控,用作自动选品的范围。
|