fix: v4.5 修复支付弹窗不弹出 — 改用先抢再喂策略
- startProactive 改为直接 retry 抢 bizId,成功后缓存响应再点按钮 - findBuyButton 按优先级排序,排除导航按钮 - clickButton 强制解除 disabled/is-disabled - 拦截器去掉 proactive 分支,只做 cache 返回
This commit is contained in:
104
README.md
104
README.md
@@ -1,18 +1,17 @@
|
|||||||
# GLM Coding 抢购助手 v4.0
|
# GLM Coding 抢购助手 v4.5
|
||||||
|
|
||||||
智谱 GLM Coding Plan 限时抢购自动化脚本(Tampermonkey 油猴脚本)
|
智谱 GLM Coding Plan 限时抢购自动化脚本(Tampermonkey 油猴脚本)
|
||||||
|
|
||||||
## 功能特点
|
## 功能特点
|
||||||
|
|
||||||
- **并发重试** — 3 路并发请求,任一成功立即返回(比单线程快 3x)
|
- **极速并发引擎** — 双模式并发:极速模式 10 路 + 普通模式 5 路,任一成功立即取消其余
|
||||||
- **自适应间隔** — 前 10 次零延迟爆发 → 快速重试 → 随机间隔,带 ±30% 抖动
|
- **自适应间隔** — 前 20 次零延迟爆发 → 30ms 快速重试 → 100ms 随机间隔,带 ±30% 抖动
|
||||||
- **preview + check 双重校验** — 获取 bizId 后调用 check 确认有效,EXPIRE 立即重试
|
- **preview + check 双重校验** — 获取 bizId 后调用 check 确认有效,EXPIRE 立即重试
|
||||||
- **4 层支付恢复** — 暴力清弹窗 → 缓存重点击 → 直接获取支付链接 → 兜底提醒
|
- **4 层支付恢复** — 暴力清弹窗 → 缓存重点击 → 直接获取支付链接 → 兜底提醒
|
||||||
- **反检测** — JSON.parse 定向拦截(不污染全局)、fetch/XHR toString 伪装、Shadow DOM 面板隔离
|
- **反检测** — 请求指纹随机化(X-Request-Id / X-Timestamp / Accept-Language)、JSON.parse 定向拦截、fetch/XHR toString 伪装、Shadow DOM 面板隔离
|
||||||
- **高精度定时** — requestAnimationFrame + performance.now,精度 ±2ms
|
- **高精度定时** — requestAnimationFrame + performance.now,精度 ±2ms
|
||||||
- **配置持久化** — localStorage 保存并发数/上限等配置,sessionStorage 保存捕获的请求
|
- **配置持久化** — localStorage 保存所有配置,sessionStorage 保存捕获的请求,刷新不丢失
|
||||||
- **错误弹窗自动恢复** — MutationObserver 监控弹窗出现,自动关闭并重新触发购买
|
- **弹窗自动恢复** — MutationObserver 监控弹窗,自动关闭并重新触发,最多 3 次
|
||||||
- **TCP 预热** — 提前建立连接,减少首次请求延迟
|
|
||||||
- **快捷键** — `Alt+S` 开始 / `Alt+X` 停止 / `Alt+H` 隐藏面板
|
- **快捷键** — `Alt+S` 开始 / `Alt+X` 停止 / `Alt+H` 隐藏面板
|
||||||
|
|
||||||
## 安装
|
## 安装
|
||||||
@@ -31,47 +30,26 @@
|
|||||||
## 使用方法
|
## 使用方法
|
||||||
|
|
||||||
1. 打开 [GLM Coding 页面](https://bigmodel.cn/glm-coding)
|
1. 打开 [GLM Coding 页面](https://bigmodel.cn/glm-coding)
|
||||||
2. 右上角出现 **GLM v4.0** 控制面板
|
2. 右上角出现控制面板
|
||||||
3. **手动点一次购买按钮** — 脚本捕获请求参数(面板显示"已捕获")
|
3. **手动点一次购买按钮** — 脚本捕获请求参数(面板显示"已捕获")
|
||||||
4. 选择触发方式:
|
4. 选择触发方式:
|
||||||
- **主动抢购**:立即开始并发重试
|
- **主动抢购**:立即开始并发重试
|
||||||
- **定时触发**:设定时间,到点自动开始
|
- **定时触发**:设定时间(默认 10:00:00),到点自动开始
|
||||||
- **预热**:提前建立 TCP 连接
|
|
||||||
5. 抢购成功后自动弹出支付页面
|
5. 抢购成功后自动弹出支付页面
|
||||||
|
|
||||||
## 控制面板
|
|
||||||
|
|
||||||
```
|
|
||||||
┌─────────────────────────┐
|
|
||||||
│ GLM v4.0 [-] │
|
|
||||||
├─────────────────────────┤
|
|
||||||
│ ● 重试中... 45/500 │
|
|
||||||
│ 已捕获: POST .../preview│
|
|
||||||
│ │
|
|
||||||
│ [重试:45] [成功:0] [错误:3] │
|
|
||||||
│ │
|
|
||||||
│ 并发 [3] 上限 [500] │
|
|
||||||
│ 定时 [--:--] [设定] │
|
|
||||||
│ │
|
|
||||||
│ [▶ 主动抢购] [停止] [预热]│
|
|
||||||
│ │
|
|
||||||
│ 10:00:01 捕获 preview │
|
|
||||||
│ 10:00:01 #3 系统繁忙 │
|
|
||||||
│ 10:00:02 #15 售罄 │
|
|
||||||
│ 10:00:03 成功! bizId=xx │
|
|
||||||
└─────────────────────────┘
|
|
||||||
```
|
|
||||||
|
|
||||||
## 配置参数
|
## 配置参数
|
||||||
|
|
||||||
| 参数 | 默认值 | 说明 |
|
| 参数 | 默认值 | 说明 |
|
||||||
|------|--------|------|
|
|------|--------|------|
|
||||||
| 并发数 | 3 | 同时发起的请求数 |
|
| 并发路数 | 5 | 普通模式同时发起的请求数 |
|
||||||
| 最大重试 | 500 | 达到上限后停止 |
|
| 极速并发 | 10 | 前 5 秒的高并发路数 |
|
||||||
| 爆发次数 | 10 | 前 N 次零延迟 |
|
| 极速时长 | 5s | 高并发持续多久 |
|
||||||
| 快速间隔 | 50ms | 爆发后的重试间隔 |
|
| 最大重试 | 2000 | 达到上限后停止 |
|
||||||
| 慢速间隔 | 150ms | 后期重试间隔中值 |
|
| 爆发次数 | 20 | 前 N 次零延迟 |
|
||||||
|
| 快速间隔 | 30ms | 爆发后的重试间隔 |
|
||||||
|
| 慢速间隔 | 100ms | 后期重试间隔中值 |
|
||||||
| 抖动 | ±30% | 间隔随机化幅度 |
|
| 抖动 | ±30% | 间隔随机化幅度 |
|
||||||
|
| 抢购时间 | 10:00:00 | 每天定时触发时间 |
|
||||||
|
|
||||||
## 快捷键
|
## 快捷键
|
||||||
|
|
||||||
@@ -86,9 +64,15 @@
|
|||||||
```
|
```
|
||||||
用户点击购买 → 脚本捕获 preview 请求
|
用户点击购买 → 脚本捕获 preview 请求
|
||||||
↓
|
↓
|
||||||
┌── 并发路1 ──┐
|
┌── 极速模式 (前5秒) ──┐
|
||||||
├── 并发路2 ──┤ → 任一获取 bizId
|
│ 10路并发 × 零延迟 │
|
||||||
└── 并发路3 ──┘
|
└──────────────────────┘
|
||||||
|
↓
|
||||||
|
┌── 普通模式 ──────────┐
|
||||||
|
│ 5路并发 × 自适应间隔 │
|
||||||
|
└──────────────────────┘
|
||||||
|
↓
|
||||||
|
任一获取 bizId
|
||||||
↓
|
↓
|
||||||
check 校验 bizId
|
check 校验 bizId
|
||||||
├── EXPIRE → 立即重试
|
├── EXPIRE → 立即重试
|
||||||
@@ -101,34 +85,42 @@
|
|||||||
└── 兜底提醒
|
└── 兜底提醒
|
||||||
```
|
```
|
||||||
|
|
||||||
## 注意事项
|
|
||||||
|
|
||||||
- 需要先登录智谱账号
|
|
||||||
- 抢购前建议先点一次购买按钮让脚本捕获请求参数
|
|
||||||
- 建议在抢购开始前 3 秒点击 **预热** 按钮
|
|
||||||
- 如果支付弹窗未出现,脚本会自动尝试多种恢复策略
|
|
||||||
|
|
||||||
## 更新日志
|
## 更新日志
|
||||||
|
|
||||||
|
### v4.5 (2026-04-10)
|
||||||
|
- **修复** 支付弹窗不弹出的核心问题:改用"先抢再喂"策略,retry 独立抢到 bizId 后缓存响应,再点击按钮让前端正常处理
|
||||||
|
- **修复** `findBuyButton` 找错按钮(匹配到"即刻订阅"导航按钮),现在按优先级排序,优先找特惠/购买按钮
|
||||||
|
- **修复** disabled 按钮点击无效:`clickButton` 强制解除 disabled 和 is-disabled class
|
||||||
|
- **优化** 拦截器简化:去掉 proactive 分支,只做 cache 返回 + 普通捕获,逻辑更清晰
|
||||||
|
- **优化** 按钮排除"即刻订阅"、"暂不"、"拼好模"等非购买按钮
|
||||||
|
|
||||||
|
### 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)
|
### v4.1 (2026-04-08)
|
||||||
- **修复** 售罄状态下按钮不可点击的问题(恢复全局 JSON.parse patch)
|
- **修复** 售罄状态下按钮不可点击的问题(恢复全局 JSON.parse patch)
|
||||||
- **修复** 支付弹窗不弹出的问题(4 层恢复策略:清弹窗→缓存重点击→获取支付链接→兜底提醒)
|
- **修复** 支付弹窗不弹出的问题(4 层恢复策略)
|
||||||
- **修复** `@match` 规则不匹配 `bigmodel.cn`(无 www)的问题
|
- **修复** `@match` 规则不匹配 `bigmodel.cn`(无 www)
|
||||||
- **修复** 原型链污染风险(Object.keys + WeakSet 循环引用保护)
|
- **修复** 原型链污染风险(Object.keys + WeakSet)
|
||||||
- **修复** HTTP 401/403 会话过期检测(之前永远不会触发)
|
- **修复** HTTP 401/403 会话过期检测
|
||||||
- **修复** 限流退避使用错误的计数器
|
- **修复** 限流退避使用错误的计数器
|
||||||
- **修复** stats.errors 永远显示 0
|
- **修复** stats.errors 永远显示 0
|
||||||
- **修复** Alt+H 快捷键在 Shadow DOM 中失效
|
- **修复** Alt+H 快捷键在 Shadow DOM 中失效
|
||||||
- **修复** `_glmShadow` 暴露在全局作用域
|
|
||||||
|
|
||||||
### v4.0 (2026-04-08)
|
### v4.0 (2026-04-08)
|
||||||
- 并发重试(3 路 Promise.race)
|
- 并发重试(Promise.race 变体)
|
||||||
- 自适应间隔(爆发→快速→随机抖动)
|
- 自适应间隔(爆发→快速→随机抖动)
|
||||||
- 反检测(定向拦截、toString 伪装、Shadow DOM)
|
- 反检测(定向拦截、toString 伪装、Shadow DOM)
|
||||||
- 高精度定时(rAF + performance.now)
|
- 高精度定时(rAF + performance.now)
|
||||||
- 配置/请求持久化(localStorage + sessionStorage)
|
- 配置/请求持久化
|
||||||
- MutationObserver 弹窗监控
|
- MutationObserver 弹窗监控
|
||||||
- TCP 预热、快捷键、离开保护
|
- 快捷键、离开保护
|
||||||
|
|
||||||
### v3.2 (原版)
|
### v3.2 (原版)
|
||||||
- 单线程串行重试
|
- 单线程串行重试
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
// ==UserScript==
|
// ==UserScript==
|
||||||
// @name 智谱 GLM Coding 抢购助手 v4.0
|
// @name 智谱 GLM Coding 抢购助手 v4.0
|
||||||
// @namespace http://tampermonkey.net/
|
// @namespace http://tampermonkey.net/
|
||||||
// @version 4.4
|
// @version 4.5
|
||||||
// @description 并发重试 + 自适应间隔 + 反检测 + check校验 + 弹窗恢复 + 定时触发 + 配置持久化
|
// @description 并发重试 + 自适应间隔 + 反检测 + check校验 + 弹窗恢复 + 定时触发 + 配置持久化
|
||||||
// @author Assistant
|
// @author Assistant
|
||||||
// @match *://www.bigmodel.cn/*
|
// @match *://www.bigmodel.cn/*
|
||||||
@@ -362,37 +362,20 @@
|
|||||||
setState({ captured });
|
setState({ captured });
|
||||||
try { sessionStorage.setItem('glm_rush_captured', JSON.stringify(captured)); } catch {}
|
try { sessionStorage.setItem('glm_rush_captured', JSON.stringify(captured)); } catch {}
|
||||||
|
|
||||||
// 已经成功过 → 直接返回缓存
|
// 有缓存 → 返回给前端(来自主动模式抢到后的 cache)
|
||||||
if (state.status === 'success' && state.lastSuccess) {
|
// 这是支付弹窗能弹出的关键: 前端发 preview → 拦截器返回缓存的成功响应 → 前端正常处理
|
||||||
log('已抢到, 返回成功响应');
|
|
||||||
return new Response(state.lastSuccess.text, { status: 200, headers: { 'Content-Type': 'application/json' } });
|
|
||||||
}
|
|
||||||
|
|
||||||
// 有缓存 → 返回(来自主动模式成功后的恢复)
|
|
||||||
if (state.cache) {
|
if (state.cache) {
|
||||||
log('返回缓存响应');
|
log('返回缓存的成功响应给前端');
|
||||||
const c = state.cache;
|
const c = state.cache;
|
||||||
setState({ cache: null });
|
setState({ cache: null });
|
||||||
recoveryAttempts = 0;
|
recoveryAttempts = 0;
|
||||||
return new Response(c.text, { status: 200, headers: { 'Content-Type': 'application/json' } });
|
return new Response(c.text, { status: 200, headers: { 'Content-Type': 'application/json' } });
|
||||||
}
|
}
|
||||||
|
|
||||||
// 主动模式/正在抢购 → 进入重试引擎
|
// 已经成功过,再次点击也返回成功响应
|
||||||
if (state.proactive || state.status === 'retrying') {
|
if (state.status === 'success' && state.lastSuccess) {
|
||||||
log('抢购中, 启动重试...');
|
log('已抢到, 返回成功响应');
|
||||||
const result = await retry(url, {
|
return new Response(state.lastSuccess.text, { status: 200, headers: { 'Content-Type': 'application/json' } });
|
||||||
method: init?.method || 'POST',
|
|
||||||
body: init?.body,
|
|
||||||
headers: extractHeaders(init?.headers),
|
|
||||||
});
|
|
||||||
setState({ proactive: false });
|
|
||||||
if (result.ok) {
|
|
||||||
log('拦截器内抢购成功! 返回响应给前端...');
|
|
||||||
try { new Notification('GLM 抢购成功!', { body: `bizId=${state.bizId}` }); } catch {}
|
|
||||||
// 直接返回给前端的 fetch 调用 → 前端会正常弹出支付窗口
|
|
||||||
return new Response(result.text, { status: result.status, headers: { 'Content-Type': 'application/json' } });
|
|
||||||
}
|
|
||||||
return _fetch.apply(this, [input, init]);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 普通捕获 → 只记录参数,放行原始请求,自动设定定时
|
// 普通捕获 → 只记录参数,放行原始请求,自动设定定时
|
||||||
@@ -444,25 +427,19 @@
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 有缓存 → 返回成功响应给前端
|
||||||
if (state.cache) {
|
if (state.cache) {
|
||||||
log('返回缓存响应 (XHR)');
|
log('返回缓存的成功响应给前端 (XHR)');
|
||||||
const c = state.cache; setState({ cache: null });
|
const c = state.cache; setState({ cache: null });
|
||||||
recoveryAttempts = 0;
|
recoveryAttempts = 0;
|
||||||
fakeXHR(self, c.text);
|
fakeXHR(self, c.text);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 主动模式/正在抢购 → 重试
|
// 已成功过 → 返回成功响应
|
||||||
if (state.proactive || state.status === 'retrying') {
|
if (state.status === 'success' && state.lastSuccess) {
|
||||||
log('抢购中, 启动重试 (XHR)...');
|
log('已抢到, 返回成功响应 (XHR)');
|
||||||
retry(url, { method: this._m, body, headers: this._h || {} }).then(result => {
|
fakeXHR(self, state.lastSuccess.text);
|
||||||
setState({ proactive: false });
|
|
||||||
if (result.ok) {
|
|
||||||
log('XHR拦截器内抢购成功! 返回响应给前端...');
|
|
||||||
try { new Notification('GLM 抢购成功!', { body: `bizId=${state.bizId}` }); } catch {}
|
|
||||||
}
|
|
||||||
fakeXHR(self, result.ok ? result.text : '{"code":-1,"msg":"重试失败"}');
|
|
||||||
});
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -644,28 +621,47 @@
|
|||||||
function findBuyButton() {
|
function findBuyButton() {
|
||||||
// 优先返回用户上次点击的同一个按钮
|
// 优先返回用户上次点击的同一个按钮
|
||||||
if (_lastClickedBtn && _lastClickedBtn.offsetParent !== null) return _lastClickedBtn;
|
if (_lastClickedBtn && _lastClickedBtn.offsetParent !== null) return _lastClickedBtn;
|
||||||
for (const el of document.querySelectorAll('button, a, [role="button"], div[class*="btn"], span[class*="btn"]')) {
|
|
||||||
|
// 按优先级查找: 特惠订阅 > 订阅升级 > 其他购买按钮
|
||||||
|
// 注意: 特惠按钮可能是 disabled 的,但我们会在 clickButton 里强制解除
|
||||||
|
const priority = ['特惠', '购买', '抢购', '下单'];
|
||||||
|
const candidates = [];
|
||||||
|
for (const el of document.querySelectorAll('button.buy-btn, button[class*="buy"], button')) {
|
||||||
const t = el.textContent.trim();
|
const t = el.textContent.trim();
|
||||||
if (/购买|抢购|立即|下单|订阅/.test(t) && t.length < 20 && el.offsetParent !== null) return el;
|
if (/购买|抢购|立即|下单|特惠|订阅/.test(t) && t.length < 20 && el.offsetParent !== null) {
|
||||||
|
// 排除"即刻订阅"这类导航按钮和非购买按钮
|
||||||
|
if (/即刻订阅|暂不|取消|拼好模/.test(t)) continue;
|
||||||
|
const pIdx = priority.findIndex(p => t.includes(p));
|
||||||
|
candidates.push({ el, priority: pIdx >= 0 ? pIdx : 99 });
|
||||||
}
|
}
|
||||||
return null;
|
}
|
||||||
|
candidates.sort((a, b) => a.priority - b.priority);
|
||||||
|
return candidates.length > 0 ? candidates[0].el : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 监听用户点击,记住是哪个按钮
|
// 监听用户点击,记住是哪个按钮
|
||||||
document.addEventListener('click', e => {
|
document.addEventListener('click', e => {
|
||||||
const t = (e.target.textContent || '').trim();
|
const t = (e.target.textContent || '').trim();
|
||||||
if (/购买|抢购|立即|下单|订阅/.test(t) && t.length < 20) {
|
if (/购买|抢购|立即|下单|特惠|订阅/.test(t) && t.length < 20) {
|
||||||
_lastClickedBtn = e.target.closest('button') || e.target;
|
_lastClickedBtn = e.target.closest('button') || e.target;
|
||||||
log('记住按钮: ' + t);
|
log('记住按钮: ' + t);
|
||||||
}
|
}
|
||||||
}, true);
|
}, true);
|
||||||
|
|
||||||
function clickButton(btn) {
|
function clickButton(btn) {
|
||||||
// 多种方式触发点击,确保前端框架能响应
|
// 强制解除 disabled 状态(售罄按钮需要解锁才能触发事件)
|
||||||
|
if (btn.disabled) {
|
||||||
|
btn.disabled = false;
|
||||||
|
btn.classList.remove('is-disabled', 'disabled');
|
||||||
|
log('已解除按钮 disabled 状态');
|
||||||
|
}
|
||||||
|
// 确保 pointer-events 可交互
|
||||||
|
btn.style.pointerEvents = 'auto';
|
||||||
|
// 多种方式触发点击,确保前端框架(Vue/Element UI)能响应
|
||||||
btn.focus();
|
btn.focus();
|
||||||
btn.dispatchEvent(new MouseEvent('mousedown', { bubbles: true }));
|
btn.dispatchEvent(new MouseEvent('mousedown', { bubbles: true, cancelable: true }));
|
||||||
btn.dispatchEvent(new MouseEvent('mouseup', { bubbles: true }));
|
btn.dispatchEvent(new MouseEvent('mouseup', { bubbles: true, cancelable: true }));
|
||||||
btn.dispatchEvent(new MouseEvent('click', { bubbles: true }));
|
btn.dispatchEvent(new MouseEvent('click', { bubbles: true, cancelable: true }));
|
||||||
btn.click();
|
btn.click();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -680,30 +676,38 @@
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 核心策略: 设置 proactive=true,然后点击按钮
|
// 核心策略(与旧版一致,更可靠):
|
||||||
// 让前端自己发 fetch → 拦截器检测到 proactive → 启动重试
|
// 1. 直接调 retry 抢 bizId(不依赖按钮点击)
|
||||||
// 响应直接返回给前端的 fetch 调用 → 前端正常弹出支付窗口
|
// 2. 成功后把响应存入 cache
|
||||||
|
// 3. 点击购买按钮 → 前端发 preview → 拦截器返回 cache → 前端弹支付窗口
|
||||||
setState({ proactive: true });
|
setState({ proactive: true });
|
||||||
log(`极速抢购启动! 点击按钮触发前端请求...`);
|
log('极速抢购启动! 直接请求模式...');
|
||||||
|
|
||||||
const btn = findBuyButton();
|
|
||||||
if (btn) {
|
|
||||||
clickButton(btn);
|
|
||||||
log('已点击购买按钮, 等待拦截器重试...');
|
|
||||||
// 拦截器会在 fetch/XHR 中自动处理重试
|
|
||||||
// proactive 会在拦截器成功后由 retry 结束时保持
|
|
||||||
} else {
|
|
||||||
// 找不到按钮 → 降级为直接调用方式
|
|
||||||
log('未找到按钮, 降级为直接请求模式...');
|
|
||||||
const { url, method, body, headers } = state.captured;
|
const { url, method, body, headers } = state.captured;
|
||||||
const result = await retry(url, { method, body, headers });
|
const result = await retry(url, { method, body, headers });
|
||||||
setState({ proactive: false });
|
setState({ proactive: false });
|
||||||
|
|
||||||
if (result.ok) {
|
if (result.ok) {
|
||||||
|
// 存入 cache,等前端下一次 preview 请求时返回
|
||||||
setState({ cache: { text: result.text, data: result.data } });
|
setState({ cache: { text: result.text, data: result.data } });
|
||||||
log('抢购成功! 请立即手动点击购买按钮!');
|
log('抢购成功! 触发购买流程...');
|
||||||
try { new Notification('GLM 抢购成功!', { body: `bizId=${state.bizId}` }); } catch {}
|
try { new Notification('GLM 抢购成功!', { body: `bizId=${state.bizId}` }); } catch {}
|
||||||
alert('已抢到! 请立即手动点击「特惠订阅」按钮完成支付!');
|
|
||||||
|
// 清理可能存在的错误弹窗
|
||||||
|
const errDlg = findErrorDialog();
|
||||||
|
if (errDlg) {
|
||||||
|
dismissDialog(errDlg);
|
||||||
|
await sleep(300);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 点击购买按钮 → 前端发 preview → 拦截器返回 cache → 前端弹支付窗口
|
||||||
|
const btn = findBuyButton();
|
||||||
|
if (btn) {
|
||||||
|
clickButton(btn);
|
||||||
|
log('已自动点击购买按钮, 等待支付窗口...');
|
||||||
|
} else {
|
||||||
|
log('未找到购买按钮, 请手动点击!');
|
||||||
|
alert('已抢到! 请立即点击「特惠订阅」按钮完成支付!');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user