# glm-rush **Repository Path**: debuger123/glm-rush ## Basic Information - **Project Name**: glm-rush - **Description**: No description available - **Primary Language**: Unknown - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 1 - **Forks**: 0 - **Created**: 2026-04-23 - **Last Updated**: 2026-05-13 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # GLM Coding 抢购助手 v4.12-debug 智谱 GLM Coding Plan 限时抢购自动化脚本(Tampermonkey 油猴脚本) ## 功能特点 - **极速并发引擎** — 双模式并发:极速模式 10 路 + 普通模式 5 路,任一成功立即取消其余 - **自适应间隔** — 前 20 次零延迟爆发 → 30ms 快速重试 → 100ms 随机间隔,带 ±30% 抖动 - **preview + check 双重校验** — 获取 bizId 后调用 check 确认有效,EXPIRE 立即重试 - **4 层支付恢复** — 暴力清弹窗 → 缓存重点击 → 直接获取支付链接 → 兜底提醒 - **反检测** — 请求指纹随机化(X-Request-Id / X-Timestamp / Accept-Language)、JSON.parse 定向拦截、fetch/XHR toString 伪装、Shadow DOM 面板隔离 - **高精度定时** — requestAnimationFrame + performance.now,精度 ±2ms,5 次采样取中位数 + 定时前重校准 - **配置持久化** — localStorage 保存所有配置,sessionStorage 保存捕获的请求,刷新不丢失 - **弹窗自动恢复** — MutationObserver 监控弹窗,自动关闭并重新触发,最多 3 次 - **多标签页协同** — BroadcastChannel 跨标签页通信,一页抢到其他页自动停止 - **AbortController** — 成功后立即取消在飞请求,释放连接池 - **keepalive** — 请求在页面卸载后仍能发出 - **快捷键** — `Alt+S` 开始 / `Alt+X` 停止 / `Alt+H` 隐藏面板 ## 安装 ### 方式 1:从 GitHub Raw 安装(推荐) 1. 安装 [Tampermonkey](https://www.tampermonkey.net/) 浏览器扩展 2. 点击安装:[glm-rush-v4.user.js](https://raw.githubusercontent.com/qtaxm/glm-rush/master/glm-rush-v4.user.js) 3. Tampermonkey 自动弹出安装页面,点击 **安装** ### 方式 2:手动安装 1. 复制 `glm-rush-v4.user.js` 的内容 2. 打开 Tampermonkey → 添加新脚本 → 粘贴 → 保存 ## 快速开始 1. 打开 [bigmodel.cn](https://bigmodel.cn) 并登录账号 2. 进入商品购买页面,右上角出现控制面板 3. **手动点击一次"购买"按钮** — 脚本自动捕获请求参数(面板显示"已捕获") 4. 脚本自动设定定时(默认 10:00:00),到点自动开抢 5. 抢购成功后自动弹出支付页面,扫码完成支付 ## 使用方式 ### 方式一:自动定时(推荐) 手动点一次购买后,脚本自动设定定时并在整点开抢。提前 30 秒重新校准时间,提前 3 秒预热 TCP 连接。 ### 方式二:多标签页(v4.10 新增) 多开几个 bigmodel.cn 标签页,每个都完成"点一次购买"。到整点时每个标签页各自 10 路并发抢购,任一页抢到后通过 BroadcastChannel 自动通知其他页停止。总并发 = 标签页数 × 10。 ### 方式三:手动触发 - 点击面板 **"▶ 主动抢购"** 按钮 - 或按快捷键 **Alt+S** ### 方式三:自定义定时 1. 在面板"定时"输入框中设置时间(格式 HH:MM:SS) 2. 点击"设定" 3. 脚本在指定时间自动开抢 ## 面板说明 右上角浮动面板(Shadow DOM 隔离,不影响页面样式): | 区域 | 说明 | |------|------| | 状态栏 | 实时显示当前状态(等待中 / 极速重试中 / 成功 / 失败) | | 捕获信息 | 显示已捕获的请求方法与 URL | | 统计区 | 实时显示重试次数、成功次数、错误次数 | | 参数区 | 可调整并发数、极速并发数、重试上限 | | 定时区 | 设定抢购时间,显示倒计时 | | 操作按钮 | 主动抢购 / 停止 / 预热 | | 日志区 | 彩色日志(绿色成功、黄色警告、红色错误、灰色信息) | ### 按钮功能 | 按钮 | 作用 | |------|------| | ▶ 主动抢购 | 立即开始并发抢购 | | ■ 停止 | 终止当前抢购 | | 预热 | 手动触发 TCP 连接预热 | | 定时 → 设定 | 自定义抢购时间 | ### 快捷键 | 快捷键 | 功能 | |--------|------| | `Alt + S` | 启动主动抢购 | | `Alt + X` | 停止所有操作 | | `Alt + H` | 隐藏/显示面板 | ## 配置参数 | 参数 | 默认值 | 说明 | |------|--------|------| | 并发路数 | 5 | 普通模式同时发起的请求数 | | 极速并发 | 10 | 前 5 秒的高并发路数 | | 极速时长 | 5s | 高并发持续多久 | | 最大重试 | 2000 | 达到上限后停止 | | 爆发次数 | 20 | 前 N 次零延迟 | | 快速间隔 | 30ms | 爆发后的重试间隔 | | 慢速间隔 | 100ms | 后期重试间隔中值 | | 抖动 | ±30% | 间隔随机化幅度 | | 抢购时间 | 10:00:00 | 每天定时触发时间 | | 弹窗恢复上限 | 3 | 支付恢复最大尝试次数 | | 日志上限 | 100 | 面板保留的最大日志条数 | 参数修改后自动持久化到 localStorage,刷新不丢失。 ## 核心机制详解 ### 1. 请求拦截与重放 脚本 hook 了 `window.fetch` 和 `XMLHttpRequest.prototype.send`。当用户手动点击购买按钮时,浏览器发出的 `/api/biz/pay/preview` 请求会被拦截,完整请求参数(URL、HTTP 方法、请求体、请求头)被捕获并存储到 sessionStorage。 **捕获时自动清理一次性凭据**:如果用户点击购买时触发了腾讯验证码,请求 body 中会包含 `ticket` 和 `randstr`。这些是一次性凭据,用过即失效。脚本在存储前自动删除这两个字段,确保重放时使用干净的 body。 之后的重试完全基于这份捕获的请求进行重放,确保请求格式与用户手动操作完全一致,不会因为字段遗漏或格式变化而失败。 ### 2. 反售罄 Patch 全局 hook `JSON.parse`,在返回解析结果前递归遍历对象,自动修改以下字段: | 原始值 | 修改后 | 说明 | |--------|--------|------| | `isSoldOut: true` | `false` | 解除售罄标记 | | `soldOut: true` | `false` | 解除售罄标记 | | `isServerBusy: true` | `false` | 解除繁忙标记 | | `stock: 0` | `999` | 伪造库存 | | `disabled: true`(商品对象) | `false` | 解除按钮禁用 | 效果:页面永远显示商品可购买,购买按钮永远可点击。 伪装方式:`JSON.parse.toString()` 返回 `'function parse() { [native code] }'`,防止网站检测到 hook。 使用 `WeakSet` 防止循环引用导致的无限递归,并跳过 `__proto__`、`constructor`、`prototype` 防止原型链污染。 ### 3. 并发重试引擎(v4.9 重构) v4.8 及之前采用"批次模式":发一批 N 个请求 → 等全部返回 → 分析 → 发下一批。这有两个问题:(1) 批次之间有空隙,浪费时间;(2) 所有请求完全相同,容易触发限流。 v4.9 改为**持续补充模式**: ``` 传统批次模式: [发50个] [等待全部返回] [分析] [发50个] [等待全部返回] ... ↑ 空隙 ↑ ↑ 空隙 ↑ 持续补充模式: [发50个] → 第1个返回失败 → 立即补发1个 → 第2个返回失败 → 立即补发1个 → ... 始终保持50个请求在飞,零空隙 ``` **核心机制**: - 初始发射 N 个请求(`maxInFlight` = 极速并发数) - 每个请求返回后立即补发一个新请求,始终保持 N 个在飞 - 任一成功立即标记 `settled`,停止补充 - 极速窗口内每个请求错开 0~10ms 随机间隔,避免瞬时爆发触发 WAF **自适应并发**: - 初始并发 = 配置值 - 收到 429 限流 → 并发 -5 - 收到售罄/繁忙(服务器正常响应)→ 并发 +1 - 大量需要验证码 → 并发降至一半 - 范围:2 ~ maxInFlight **请求优先级**: - 所有 fetch 请求添加 `priority: 'high'`,告诉浏览器优先处理 ### 4. Check 校验 获取 `bizId` 后不会直接使用,而是调用 `/api/biz/pay/check?bizId=xxx` 验证: | check 结果 | 处理 | |------------|------| | 通过 | 返回成功,进入支付流程 | | `EXPIRE` | bizId 已过期,立即重试,不等待 | | 网络异常 | 仍然认为成功,保留 bizId(不因网络波动丢弃到手的 bizId) | 避免拿着过期 bizId 走后续流程浪费时间。 ### 5. 自适应异常处理 | 异常情况 | 处理方式 | |----------|----------| | 连续大量网络错误 | 暂停 3 秒后恢复 | | 会话过期(HTTP 401/403) | 立即停止,提示重新登录 | | 限流(HTTP 429) | 指数退避 + 自适应并发 -5 | | 全部 EXPIRE | 立即重试,不等待 | | >50% 需要验证码 | 后台弹腾讯验证码(不阻塞)+ 并发降至一半 | | 售罄/繁忙(正常响应) | 自适应并发 +1 | | 连续大量售罄(超过 20s) | 降速到 2 秒间隔 | ### 6. 服务器时间同步 通过读取服务器响应头 `Date` 字段计算本地时钟偏移量: ``` serverTimeOffset = 服务器时间 - (本地发送时间 + RTT/2) ``` v4.10 改进:连续 5 次采样取偏移量和 RTT 的**中位数**,消除单次网络抖动的影响。同时记录实测 RTT 用于提前量计算。定时前 30 秒自动重新校准,消除时钟漂移。 所有定时判断基于 `getServerNow()`(本地时间 + 偏移量),避免本地时钟偏差导致错过整点。 备用方案:使用 worldtimeapi.org/api/timezone/Asia/Shanghai 同步。 定时精度:10ms 间隔 setInterval 轮询,到时间立即启动。 ### 7. 弹窗恢复(4 层策略) 抢购成功后页面可能弹出错误弹窗("系统繁忙"、"购买人数过多"等),导致支付窗口出不来。自动执行恢复: ``` 第 1 层:检测并关闭错误弹窗 ├─ 查找 .el-dialog / .ant-modal / [role="dialog"] 等 ├─ 匹配文本:"繁忙/失败/重试/出错/异常" └─ 点击关闭按钮或直接 display:none 第 2 层:缓存成功响应 + 重新点击购买按钮 ├─ 将成功响应写入 state.cache ├─ findBuyButton() 找到购买按钮并点击 └─ 页面重新发请求 → fetch hook 返回缓存响应 → 正常弹出支付 第 3 层:直接获取支付链接 ├─ 调用 /biz/pay/check?bizId=xxx ├─ 检查响应中的支付 URL / payUrl / qrCode └─ 有链接则直接跳转,有二维码则页面内显示 第 4 层:兜底提醒 └─ alert 弹窗提示用户手动操作(刷新后点击购买) ``` - 最多自动恢复 3 次 - MutationObserver 持续监控 DOM 变化,有新弹窗立即处理 - 检测到支付相关弹窗(qrcode/wechat/alipay)时跳过恢复,不干扰支付流程 ### 8. 请求指纹随机化 每次请求添加随机字段,降低被识别为自动化脚本的概率: | 字段 | 值 | 作用 | |------|-----|------| | `X-Request-Id` | 随机字符串 | 每次请求唯一标识 | | `X-Timestamp` | 当前毫秒时间戳 | 时间唯一性 | | `Accept-Language` | 随机权重值 `zh-CN,zh;q=0.7,en;q=0.5` | 请求指纹差异化 | ### 9. 验证码处理(v4.8 重构) #### 验证码队列 新增 `captchaQueue` 数组,存储已解的腾讯验证码凭据。每个凭据记录时间戳,4 分钟后自动过期(腾讯 ticket 有效期 5 分钟,预留 1 分钟余量)。 `singleAttempt` 发请求前优先从队列取 ticket 带上。队列为空时发不带 ticket 的请求。 #### 后台弹验证码(不阻塞重试循环) v4.7 中弹验证码会阻塞整个重试引擎,用户点验证码的几秒内完全停止重试。v4.8 改为非阻塞模式: ``` 重试引擎正常运行(不带 ticket) │ ├─ 发现 >50% 请求需要验证码 │ └─ 触发 backgroundSolveCaptcha()(异步,不 await) │ └─ 弹出验证码,用户完成后 ticket 入队 │ ├─ 重试引擎不等待,继续并发重试(不带 ticket) │ ├─ 下一批请求自动从队列取 ticket 带上 │ └─ 循环:需要验证码 → 后台弹 → 继续重试 → ticket 入队 → 自动使用 ``` - 阈值从"全部需要"(100%)降低到"超过半数"(>50%) - 同一时间只弹一个验证码(`captchaSolving` 标志防止重复弹出) - 验证码 SDK 按需加载,首次需要时才注入 `turing.captcha.gtimg.com/turing.js` #### 预热验证码 抢购前自动批量弹验证码,攒 ticket 备用: | 时间 | 动作 | |------|------| | 定时前 5 分钟 | 启动验证码预热 | | 每 30 秒 | 弹一次验证码(队列满 3 个则暂停) | | 抢购开始时 | 队列中已有预解好的 ticket | | 抢购结束 | 自动停止预热(成功/失败/超时均停止) | 预热机制确保 10:00 开抢的瞬间,脚本手里已经有有效的 ticket,不用等用户点验证码。 ### 10. Vue 组件兜底 针对 Vue 驱动的页面,两层兜底: **patchVueServerBusy()**: - 每 500ms 遍历 Vue 组件树(最深 8 层) - 找到 `isServerBusy === true` 的组件,强制设为 `false` - 解决某些页面的繁忙状态存在 Vue data 中而非 API 响应中的问题 - 最多尝试 30 次(15 秒)后放弃 **forcePayDialog(responseData)**: - 抢购成功 1.5 秒后若支付弹窗仍未出现 - 遍历 Vue 组件树找到含 `payDialogVisible` 的支付组件 - 直接设置 `priceData = data` + `payDialogVisible = true` - 相当于直接命令 Vue "弹窗给我打开" ### 10. TCP 预热(v4.9 增强) 定时触发前 3 秒自动执行: ``` 请求 1-10: GET /api/biz/pay/check?bizId=preheat_0~9 (并发) 请求 11-13: HEAD /api/biz/pay/preview (并发) ``` 并发建立 13 个连接(v4.8 之前只有 4 个串行连接),确保极速模式下每个并发都有现成连接可用。所有请求添加 `priority: 'high'`。 ### 11. 提前量计算(v4.9 新增,v4.10 修正) v4.8 及之前在整点 10:00:00.000 才发第一个请求,但请求到达服务端需要网络延迟(RTT)。v4.9 引入提前量概念但公式有误(用了时钟偏差而非 RTT)。v4.10 使用实测 RTT 中位数,提前 `measuredRTT / 2` 启动抢购,使第一个请求恰好在整点到达服务端: ``` v4.8: 10:00:00.000 发请求 → 10:00:00.050 到达服务端(晚了50ms) v4.10: 09:59:59.950 发请求 → 10:00:00.000 到达服务端(刚好) ``` 提前量最小为 50ms,确保即使 RTT 很小也有补偿效果。 ### 12. AbortController 取消在飞请求(v4.10 新增) 浏览器对同一域名的并发连接有上限(HTTP/1.1 约 6 个 TCP 连接,HTTP/2 受 stream limit)。任一请求成功后,其余在飞请求仍占用连接池,延迟新的有效请求。 v4.10 在 `retry()` 中创建共享 `AbortController`,成功后立即 `abort()` 所有在飞请求,释放连接给后续操作(check 校验、支付流程等)。会话过期时同样 abort。`singleAttempt` 的 `catch` 已处理 `AbortError`。 ### 13. 多标签页协同(v4.10 新增) 通过 `BroadcastChannel('glm-rush')` 实现跨标签页通信。每个标签页生成随机 `tabId`,任一标签页抢购成功后广播 `{ type: 'success', tabId, bizId }`,其他标签页收到后自动停止抢购和验证码预热。 效果:多开标签页可线性提升总并发(N 个标签页 × 10 路 = 10N 路总并发),且不会重复抢购。不支持 `BroadcastChannel` 的浏览器静默降级为单标签页模式。 ### 14. fetch keepalive(v4.10 新增) 所有抢购请求添加 `keepalive: true`,确保即使浏览器触发页面卸载(GC、导航等),请求也能发出不会被取消。同源请求下 `keepalive` 与 `credentials: 'include'` 兼容。 ## 完整执行流程 ``` 页面加载 │ ├─ hook JSON.parse(反售罄 patch) ├─ hook window.fetch(请求拦截) ├─ hook XMLHttpRequest(请求拦截) ├─ 自动同步服务器时间 ├─ 自动设定定时(如有已捕获的请求参数) ← v4.9.1 新增 │ ▼ 用户手动点击"购买"按钮 │ ├─ 脚本捕获完整请求参数 → 存储 sessionStorage ├─ 自动同步服务器时间(读响应头 Date) ├─ 自动设定定时(默认 10:00:00) │ ▼ 定时前 5 分钟 —— 验证码预热 │ ├─ 每 30 秒弹验证码,攒 ticket 到队列(最多 3 个) │ ▼ 定时前 30 秒 —— 重新校准时间 ← v4.10 新增 │ ├─ 再次 5 次采样取中位数,消除时钟漂移 │ ▼ 定时前 3 秒 —— TCP 预热 │ ├─ 并发 13 个请求,暖好连接池 │ ▼ 整点到达(提前 measuredRTT/2 启动,补偿网络延迟) │ ├─ 启动重试引擎(持续补充模式 + AbortController + keepalive) │ │ │ ├─ 初始发射 N 个请求(极速窗口内错开 0~10ms) │ ├─ 每个请求返回失败 → 立即补发一个(零空隙) │ ├─ 始终保持 N 个请求在飞 │ ├─ 成功后 abort 其余在飞请求,释放连接 ← v4.10 新增 │ │ │ ├─ 自适应并发: │ │ ├─ 429 限流 → 并发 -5 │ │ ├─ 售罄/繁忙 → 并发 +1 │ │ ├─ 大量验证码 → 并发降至一半 │ │ └─ 连续售罄 → 降速 2s │ │ │ ├─ 自适应策略: │ │ ├─ 连续网络错误 → 暂停 3s │ │ ├─ 429 限流 → 指数退避 (2s~16s) │ │ ├─ >50% 需要验证码 → 后台弹验证码(不阻塞重试) │ │ └─ 有缓存的 ticket → 请求自动带上 │ │ │ └─ check 校验 → 过滤过期 bizId(异常时保留 bizId) │ ▼ 获取有效 bizId ✓ │ ├─ abort 其余在飞请求(释放连接池) ├─ 广播成功消息给其他标签页 ← v4.10 新增 ├─ 返回成功响应给页面 ├─ 发送浏览器通知 ├─ 自动点击购买按钮触发正常支付流程 │ ▼ 支付弹窗异常? │ ├─ [策略1] 自动关闭错误弹窗 ├─ [策略2] 缓存响应 + 重新点击购买 ├─ [策略3] 调 check 接口获取支付链接 / 二维码 ├─ [策略4] 直接操作 Vue 组件强制弹出支付窗口 └─ [兜底] alert 提示用户手动操作 │ ▼ 用户扫码支付 → 完成 ✓ ``` ## 数据持久化 | 存储位置 | 键 | 内容 | 生命周期 | |----------|-----|------|----------| | localStorage | `glm_rush_cfg` | 并发数、上限等配置 | 永久(手动清除) | | sessionStorage | `glm_rush_captured` | 捕获的请求参数 | 标签页关闭前 | ## 安全机制 - **离开保护**:抢购进行中关闭或刷新页面会弹出浏览器确认框 - **伪装 hook**:`JSON.parse` 和 `window.fetch` 的 `toString()` 返回原生代码签名 - **Shadow DOM**:面板使用 closed mode,页面 JS 无法访问面板内部元素 - **原型链保护**:遍历对象时跳过 `__proto__`、`constructor`、`prototype` ## 注意事项 1. **必须先手动点一次购买**:脚本依赖捕获真实请求参数,不会自行构造请求 2. **保持登录状态**:不要在抢购过程中清除 Cookie 或退出登录 3. **Token 过期**:面板会提示"会话过期",重新登录后刷新页面即可 4. **只抢一次**:同一商品抢到后,后续请求自动返回缓存响应,不会重复抢购 5. **提前准备**:建议提前 5 分钟完成登录和首次点击购买;多开标签页可提升成功率 6. **浏览器通知**:首次使用时会请求通知权限,建议允许以便抢到后及时提醒 ## 调试模式说明 (v4.12-debug) ### 概述 v4.12-debug 在 v4.11 基础上新增详细调试日志,用于诊断抢购失败原因。调试日志同时输出到: - **F12 控制台**:黄色 `%c[GLM-DBG]` 前缀,方便筛选 - **浮动面板日志区**:标记为 warn 颜色(黄色) ### 采样策略 为避免刷屏影响性能,调试日志采用智能采样: | 条件 | 详细程度 | |------|---------| | 第 1~5 次请求 | 每次都打完整日志 | | 第 6 次起 | 每 10 次采样一次 | | 成功(拿到 bizId) | 始终记录 | | check 返回 EXPIRE | 始终记录 | | 批次统计(每 20 个请求) | 始终记录 | ### 调试日志覆盖的关键节点 #### 1. 请求拦截捕获(Fetch / XHR) 首次捕获购买请求时,详细记录: ``` ━━━ Fetch拦截: 捕获preview请求 ━━━ URL: /api/biz/pay/preview?productId=xxx Method: POST Body: {"productId":"xxx","ticket":"xxx","randstr":"xxx"} Body字段: [productId, ticket, randstr] .productId = xxx .ticket = xxx (一次性凭据,捕获时已清理) .randstr = xxx Headers(5): [Content-Type, Authorization, ...] ``` **排查重点**:Body 字段中是否有疑似一次性凭据的字段名(包含 `token`、`sign`、`nonce`、`timestamp`、`csrf`、`_t` 等)。如果有,说明重放时这些值已过期,需要每次重新获取。 #### 2. 重试参数确认(startProactive) 抢购启动时记录使用的参数,和捕获时对比: ``` 重试参数 url=/api/biz/pay/preview?productId=xxx method=POST 重试body: {"productId":"xxx"} 重试body字段: [productId] .productId = xxx ``` **排查重点**:如果这里的 body 和捕获时的 body 字段不同(少了某些字段),说明清理逻辑误删了必要字段。 #### 3. 单次请求详情(singleAttempt) 每个采样请求记录完整链路: ``` #1 原始body字段: [productId] #1 无可用验证码ticket (队列空:0) #1 发送请求 → /api/biz/pay/preview?productId=xxx #1 最终body: {"productId":"xxx"} #1 ← HTTP 200 (52ms) 响应: {"code":555,"msg":"系统繁忙","data":null} #1 555 系统繁忙 msg=系统繁忙 ``` ``` #3 ⚠️ 疑似一次性字段: timestamp, sign #3 .timestamp = 1714003200000 #3 .sign = a3f2b8c1d4e5... #3 装入验证码ticket=xxx... 队列剩余:2 #3 ← HTTP 200 (48ms) 响应: {"code":200,"data":{"bizId":"ORDER_12345"}} #3 🎯 拿到bizId=ORDER_12345! 即将check校验... #3 check响应 (35ms): {"code":200,"data":"EXPIRE"} #3 ❌ bizId=ORDER_12345 已EXPIRE! (preview→check间隔35ms) ``` **排查重点**: - **`⚠️ 疑似一次性字段`**:出现这个警告说明 body 中有时间戳/签名类字段,每次重放都一样,可能被服务端校验拒绝 - **响应内容**:看 `code` 和 `msg` 到底是什么,确认服务端拒绝的真实原因 - **EXPIRE**:看 preview→check 间隔多少毫秒,如果很短就 EXPIRE 说明 bizId 有效期极短 #### 4. 验证码队列状态 ``` 取出ticket (过期丢弃:0 队列剩余:2) ⚠️ 丢弃了3个过期ticket, 队列已空 ``` **排查重点**:如果大量出现"队列已空",说明验证码供给不足。 #### 5. 批次失败原因统计 每 20 个请求汇总一次: ``` #200 批次统计(20个): 售罄×15, 需要验证码×3, 429 限流×2 ``` **排查重点**:看哪种失败原因占主导。 ### 如何使用调试日志排查问题 #### 场景 1:一直返回"售罄" 看批次统计是否全部为"售罄",以及响应中是否有额外信息(如 `nextSaleTime`、`stock` 等)。 #### 场景 2:一直返回"需要验证码" 检查验证码队列状态,确认是否有足够的 ticket 供给。如果队列始终为空,说明: - 验证码弹出后用户没来得及完成 - 预热机制没启动 - 腾讯验证码 SDK 加载失败 #### 场景 3:拿到 bizId 但 check 返回 EXPIRE 看 preview→check 的间隔毫秒数。如果间隔很短就 EXPIRE,说明服务端给 bizId 的有效期极短,需要优化 check 流程。 #### 场景 4:疑似一次性凭据过期 如果日志出现 `⚠️ 疑似一次性字段`,说明 body 中有 `timestamp`/`sign`/`nonce` 类字段。这些字段每次重放值相同,服务端校验不通过。需要改为每次重试前重新生成这些值。 #### 场景 5:HTTP 401/403/429 - **401/403**:Cookie 过期,需要重新登录 - **429**:限流,降低并发数 ### 快捷键 | 快捷键 | 功能 | |--------|------| | `Alt + S` | 启动主动抢购 | | `Alt + X` | 停止所有操作 | | `Alt + H` | 隐藏/显示面板 | | `Alt + D` | 提示导出调试日志 | ### 关闭调试模式 如果调试完成不需要详细日志,修改脚本顶部的 `DEBUG` 常量: ```javascript const DEBUG = false; // 改为 false 关闭调试日志 ``` 或者替换回 v4.11 正式版脚本。 --- ## 更新日志 ### v4.12-debug (2026-05-13) **调试日志系统** v4.11 在多次使用中未能成功抢购。v4.12-debug 在所有关键节点新增详细调试日志,用于定位失败原因。 新增内容: - `DEBUG` 全局开关 + `dbgLog()` 调试日志函数 - `singleAttempt` 中记录:请求 body 字段名、疑似一次性凭据检测、验证码 ticket 使用状态、HTTP 状态码、完整响应内容、失败原因分类 - Fetch/XHR 拦截时记录:捕获的完整 URL、Method、Body、Headers、每个 Body 字段的值 - `startProactive` 时记录:重试使用的参数,和捕获时对比 - 验证码队列:取出 ticket 时记录队列剩余和过期丢弃数量 - check 校验:记录 check 耗时、完整响应、EXPIRE 时的间隔毫秒数 - 批次统计:每 20 个请求汇总失败原因分布 - 智能采样:前 5 次每次都记,之后每 10 次采样,关键事件始终记录 ### v4.11 - 新增状态变化声音提示(Web Audio API) - 新增页面顶部状态横幅 - 新增浏览器标签标题动态更新 - 新增日志区域成功时闪绿动画 ### v4.10 (2026-04-27) **修复 RTT 提前量计算** v4.9 的提前量公式 `Math.abs(serverTimeOffset) * 2` 把时钟偏差当网络延迟用了,导致提前量几乎总是 50ms 的最小值。v4.10 新增 `measuredRTT` 变量,在时间同步阶段实测 RTT 并保存,提前量改为 `measuredRTT / 2`(最小 50ms),让请求真正踩点到达服务器。 **多次时间同步取中位数** v4.9 只同步一次时间,单次测量受网络抖动影响可能偏差几十毫秒。v4.10 改为连续 5 次采样,取偏移量和 RTT 的中位数,消除抖动。同时在定时前 30 秒自动重新校准,消除长时间运行的时钟漂移。 **AbortController 取消在飞请求** v4.9 中任一请求成功后,其余在飞请求仍跑完,浪费浏览器连接池(同域名并发有上限)。v4.10 在 `retry()` 中创建共享 `AbortController`,成功后立即 `abort()` 所有在飞请求,释放连接。会话过期时同样 abort。 **fetch keepalive** 所有抢购请求添加 `keepalive: true`,确保即使浏览器触发页面卸载(GC、导航等),请求也能发出不会被取消。 **多标签页协同** 通过 `BroadcastChannel('glm-rush')` 实现多标签页协同:任一标签页抢购成功后广播 `{ type: 'success', bizId }`,其他标签页收到后自动停止抢购和验证码预热。多开标签页可以线性提升总并发(每个标签页各自 10 路),且抢到后不会重复操作。不支持 BroadcastChannel 的浏览器静默降级为单标签页模式。 ### v4.9.1 (2026-04-27) **修复定时未自动启动** v4.9 中 `autoScheduleIfNeeded()` 只在捕获请求时被调用,页面加载初始化时不会触发。如果用户打开页面后没有手动点购买按钮,定时器就永远不会被设定。修复为 `syncServerTime()` 完成后自动调用 `autoScheduleIfNeeded()`,页面加载即自动设定定时(前提:`sessionStorage` 中有之前捕获的请求参数)。 **修复验证码预热不会停止** `startCaptchaPreheat()` 启动后是 `setInterval` 每 30 秒弹验证码,但 `stopCaptchaPreheat()` 之前只在抢购成功时调用。定时到期触发、时间已过判定、抢购失败/达到上限等路径都没停预热,导致验证码一直弹出。修复为在所有结束路径都调用 `stopCaptchaPreheat()`: - 定时到期触发 `startProactive()` 时 - `autoScheduleIfNeeded()` 判定时间已过时 - 抢购失败或达到重试上限时 ### v4.9 (2026-04-23) **持续补充模式(重构重试引擎)** v4.8 采用"批次模式":发一批 N 个请求 → 等全部返回 → 分析 → 发下一批。批次之间存在空隙,浪费时间。v4.9 改为"持续补充模式":始终保持 N 个请求在飞,一个返回失败就立即补发一个,零空隙。极速窗口内每个请求错开 0~10ms 随机间隔,避免瞬时爆发触发 WAF。 **自适应并发** 并发数不再固定,根据服务端反馈动态调整:收到 429 限流 → 并发 -5;收到售罄/繁忙(正常响应)→ 并发 +1;大量需要验证码 → 并发降至一半。范围 2 ~ maxInFlight。 **提前量计算** 利用时间同步阶段测量的 RTT,提前 RTT/2 启动抢购,使第一个请求恰好在整点到达服务端,不再慢半拍。 **请求优先级** 所有 fetch 请求添加 `priority: 'high'`,告诉浏览器优先调度抢购请求。 **增强预热** 从 4 个串行连接增加到 13 个并发连接(10 个 check + 3 个 preview),确保极速模式下每个并发都有现成连接。 ### v4.8 (2026-04-23) **验证码队列 + 后台处理 + 预热** v4.7 的验证码处理有三个短板:(1) 弹验证码时整个重试引擎停住,浪费几秒窗口;(2) 每个 ticket 只用一次就没了,又要等整批失败才再弹;(3) 没有 10:00 前的预热机制,开抢时手里没有 ticket。 v4.8 完全重构验证码模块: - **验证码队列**:新增 `captchaQueue` 数组,每个 ticket 记录时间戳,4 分钟后自动过期(腾讯 ticket 有效期 5 分钟,预留 1 分钟余量)。`singleAttempt` 发请求前优先从队列取 ticket 带上。 - **后台弹验证码**:新增 `backgroundSolveCaptcha()`,异步弹验证码但不 `await`。重试引擎发现超过半数请求需要验证码时触发,用户点验证码的同时重试引擎继续不带 ticket 并发重试。验证码完成后 ticket 自动入队,下一批请求自动使用。 - **阈值降低**:从"全部需要"(100%)降低到"超过半数"(>50%)。 - **预热验证码**:抢购前 5 分钟自动启动,每 30 秒弹一次验证码(队列满 3 个暂停)。抢购成功后自动停止。确保 10:00 开抢时手中已有有效 ticket。 ### v4.7 (2026-04-23) **新增验证码自动处理** v4.0~v4.6 没有验证码处理能力,当服务端返回要求验证码时,`singleAttempt` 只标记失败继续无验证码重试,永远不会成功。v4.7 新增腾讯验证码(TencentCaptcha)支持: - `singleAttempt` 检测到服务端响应包含"验证"/"安全"/"captcha"/"ticket"等关键词时,标记 `needCaptcha: true` - `retry` 引擎发现整批请求都需要验证码时,自动弹出腾讯验证码让用户完成 - 拿到新的 `ticket`/`randstr` 后立即带验证码重试一批请求 - 用户未完成验证码时不会卡住,继续无验证码重试 - 验证码 SDK(`turing.captcha.gtimg.com/turing.js`)按需加载,不增加初始负担 **捕获时清理一次性凭据** 用户首次点击购买时,请求 body 中包含腾讯验证码的 `ticket` 和 `randstr`。这些凭据是一次性的,用过即失效。之前的版本直接存储并重放原始 body,导致每次重放都带着过期的验证码凭据,可能被服务端拒绝。 v4.7 在两处清理: 1. **fetch hook 捕获时**:存储到 `sessionStorage` 前删除 body 中的 `ticket`/`randstr` 2. **singleAttempt 发送前**:每次请求前检查 body,如果存在 `ticket`/`randstr` 就删除 **check 校验失败不再丢弃 bizId** v4.0~v4.6 中,拿到 bizId 后的 check 校验如果网络异常,会返回失败并丢弃已拿到的 bizId。v4.7 改为:check 异常时仍然认为成功,把 bizId 交回给后续流程处理。理由是拿到 bizId 本身就意味着 preview 成功,check 只是额外验证,不应该让到手的 bizId 因网络波动而丢失。 ### v4.6 (2026-04-10) - **修复** 支付弹窗不弹出 — 根因: 前端 `payComponent.isServerBusy=true` 阻止 `payPreviewFn` 发请求 - **新增** `patchSoldOut` 增加 `isServerBusy` 拦截(JSON.parse 层) - **新增** `patchVueServerBusy` 兜底:定时扫描 Vue 组件树,直接 patch `isServerBusy=false` - **新增** `forcePayDialog` 兜底:抢购成功 1.5s 后若弹窗未出现,直接设置 Vue `payDialogVisible=true` ### v4.5 (2026-04-10) - **修复** `findBuyButton` 找错按钮(匹配到"即刻订阅"导航按钮),优先找 `buy-btn` 类按钮 ### v4.4 (2026-04-09) - **新增** 极速模式:前 5 秒 10 路并发,之后降为 5 路 - **新增** 请求指纹随机化(X-Request-Id / X-Timestamp / Accept-Language 权重随机) - **新增** 余额支付方式支持 - **优化** 并发数从 3 路提升到 5 路(普通模式) - **优化** 最大重试从 500 提升到 2000 - **优化** 爆发次数从 10 提升到 20,快速间隔从 50ms 降到 30ms - **优化** 连续售罄 / 限流智能退避 ### v4.1 (2026-04-08) - **修复** 售罄状态下按钮不可点击的问题(恢复全局 JSON.parse patch) - **修复** 支付弹窗不弹出的问题(4 层恢复策略) - **修复** `@match` 规则不匹配 `bigmodel.cn`(无 www) - **修复** 原型链污染风险(Object.keys + WeakSet) - **修复** HTTP 401/403 会话过期检测 - **修复** 限流退避使用错误的计数器 - **修复** stats.errors 永远显示 0 - **修复** Alt+H 快捷键在 Shadow DOM 中失效 ### v4.0 (2026-04-08) - 并发重试(Promise.race 变体) - 自适应间隔(爆发→快速→随机抖动) - 反检测(定向拦截、toString 伪装、Shadow DOM) - 高精度定时(rAF + performance.now) - 配置/请求持久化 - MutationObserver 弹窗监控 - 快捷键、离开保护 ### v3.2 (原版) - 单线程串行重试 - preview + check 双重校验 - 错误弹窗自动恢复 - 浮动控制面板 ## License MIT