Files
huya-go/README.md
T
yml2213 828236eb1d 新增服务端时间校准,定时抢购按服务端时钟对齐触发
- 协议事实确认: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 全部通过
2026-10-16 16:13:29 +08:00

205 lines
12 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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
```
界面「商品列表」同样按类目分了页签(全部 / 限量返场 / 精英专享,带商品数),每行标注类目,
未上架的商品显示上架时间与倒计时;当前选中的类目会一起提交给监控,用作自动选品的范围。