Skip to content

Instantly share code, notes, and snippets.

@LessUp
Last active June 17, 2026 03:57
Show Gist options
  • Select an option

  • Save LessUp/e609e779fb7773cf279942f57a65719a to your computer and use it in GitHub Desktop.

Select an option

Save LessUp/e609e779fb7773cf279942f57a65719a to your computer and use it in GitHub Desktop.
⚡ GLM Coding Rush — 智谱编程助手一键抢购脚本 | Auto-Purchase Userscript for GLM Coding | 自动解锁售罄 · 高速重试 · 定时触发 · 支付保护 · 中英双语面板 | Auto-unlock sold-out · High-speed retry · Scheduled trigger · Payment guard · Bilingual panel | Tampermonkey/Violentmonkey | 点击 Raw 安装 · Click Raw to install
// ==UserScript==
// @name GLM Coding Rush - 智谱编程助手抢购脚本
// @namespace https://gist.github.com/LessUp
// @version 1.1.0
// @description 智谱 GLM Coding 一键抢购脚本 — 自动解锁售罄按钮 / 高速重试引擎 / bizId 双重校验 / 错误弹窗自动恢复 / 支付弹窗保护 / 秒级定时触发 / 可拖拽浮动面板
// @author LessUp
// @match *://www.bigmodel.cn/*
// @match https://bigmodel.cn/glm-coding*
// @run-at document-start
// @grant none
// @license MIT
// @homepageURL https://gist.github.com/LessUp/e609e779fb7773cf279942f57a65719a
// @supportURL https://gist.github.com/LessUp/e609e779fb7773cf279942f57a65719a
// ==/UserScript==
/**
* ═══════════════════════════════════════════════════════════════
* GLM Coding Rush v1.1.0 — 智谱编程助手抢购脚本
* ═══════════════════════════════════════════════════════════════
*
* 项目地址 : https://gist.github.com/LessUp (Star ⭐ 收藏)
* 邀请链接 : https://www.bigmodel.cn/glm-coding?ic=XCGTIPDIID
* (通过邀请链接新购可立减 5%)
*
* ─── 核心功能 ─────────────────────────────────────────────────
* 1. 自动解锁 — 移除 disabled / 售罄状态,提前激活购买入口
* 2. 高速重试 — 极速阶段 30ms 间隔,自适应退避策略
* 3. 双重校验 — preview + check 两步验证,过滤无效订单
* 4. 弹窗恢复 — 错误弹窗自动关闭并恢复购买流程
* 5. 支付保护 — 检测到支付界面时自动暂停一切操作
* 6. 定时触发 — 精确到秒级,后台标签页防漂移轮询
* 7. 控制面板 — 可拖拽浮窗,实时日志 / 参数调节 / 一键启停
*
* ─── 安装方式 ─────────────────────────────────────────────────
* 1. 安装 Tampermonkey / Violentmonkey 浏览器扩展
* 2. 点击 Gist「Raw」按钮 → 弹出安装提示 → 确认安装
* 3. 访问 https://www.bigmodel.cn/glm-coding 即生效
*
* ─── 使用流程 ─────────────────────────────────────────────────
* Step 1 打开 GLM Coding 购买页,脚本自动加载
* Step 2 手动点击一次「购买/订阅」按钮,让脚本捕获请求参数
* Step 3 点击面板「⚡ 立即抢购」或「⏰ 定时抢购」自动触发
* Step 4 抢购成功后弹出支付二维码,完成付款即可
*
* ─── 重要提示 ─────────────────────────────────────────────────
* 本脚本会自动携带开发者邀请码 (ic=XCGTIPDIID),通过该邀请码
* 购买可享受新用户 5% 优惠。如不需要可在脚本中搜索 INVITE_CODE
* 并修改或清空。
*
* ─── 免责声明 ─────────────────────────────────────────────────
* 本脚本仅供学习交流,请遵守平台使用规则。
* 因使用本脚本产生的任何后果由使用者自行承担。
*
* ─── 反馈交流 ─────────────────────────────────────────────────
* 欢迎在 Gist 评论区留言反馈 Bug 或功能建议!
* 觉得好用请 Star ⭐ 并分享给需要的朋友 🙏
*
* ─── 更新日志 ─────────────────────────────────────────────────
* v1.1.0 UI优化:“主动抢购”→“立即抢购”+“定时抢购”双按钮布局
* 逻辑增强:GLM专用支付弹窗检测 / 空价弹窗恢复 / 繁忙弹窗检测
* 重试优化:随机延迟抖动避免请求模式检测
* 新增:配置持久化 (localStorage) / 邀请码友好提示
* 修复:去除重复 @match / 清理冗余 CSS
* v1.0.0 全面重构:代码风格优化 / UI 全新设计 / 注释规范化
* 功能合并:UI 补丁 + 全自动引擎 + 支付弹窗保护
* 性能优化:UI 节流 / 自适应退避 / 极速阶段
* 新增:邀请码自动注入 + 使用提示
*
* ═══════════════════════════════════════════════════════════════
*/
(function () {
'use strict';
/* ────────────────────────────────────────────
* 常量 & 配置
* ──────────────────────────────────────────── */
const VERSION = '1.1.0';
const SCRIPT_NAME = 'GLM Coding Rush';
const INVITE_CODE = 'XCGTIPDIID';
const CYCLE_LABELS = {
month : '连续包月',
quarter : '连续包季',
year : '连续包年',
};
const CYCLE_ALIASES = {
month : ['连续包月', '包月', 'monthly', 'month'],
quarter : ['连续包季', '包季', 'quarterly', 'quarter'],
year : ['连续包年', '包年', 'yearly', 'annual', 'year'],
};
const PLAN_ALIASES = {
Lite : ['lite', '轻量', '基础'],
Pro : ['pro', '专业'],
Max : ['max', '旗舰', '尊享'],
};
const BUY_TEXT_RE = /立即购买|立即抢购|立刻购买|去支付|去购买|购买|抢购|订阅|下单|buy\s*now|purchase|subscribe|checkout|pay\s*now|order|buy|pay/i;
const SOLD_OUT_TEXT_RE = /售罄|缺货|已抢完|敬请期待|sold\s*out|out\s*of\s*stock|coming\s*soon/i;
const I18N = {
zh: {
mode_extreme: '极限模式',
mode_steady: '稳健模式',
mode_custom: '自定义',
mode_short_extreme: '极限',
mode_short_steady: '稳健',
mode_short_custom: '自定义',
cycle_month: '连续包月',
cycle_quarter: '连续包季',
cycle_year: '连续包年',
cycle_short_month: '包月',
cycle_short_quarter: '包季',
cycle_short_year: '包年',
label_mode: '模式',
label_plan: '套餐',
label_cycle: '周期',
label_time: '定时',
label_early: '提前',
label_window: '窗口',
label_delay: '间隔',
label_max: '上限',
label_burst: 'Burst',
label_burst_delay: '速点',
label_backoff: '退避',
btn_rush_now: '立即抢购',
btn_schedule: '定时抢购',
btn_stop: '停止',
log_welcome: '感谢使用!本脚本通过作者邀请码 (ic={code}) 为您自动享受新用户优惠。',
tip_recommended: '推荐配置:{mode} · {plan} · {time} (UTC+8) · 提前 {early}s · 窗口 {window}s',
capture_idle: '请求未捕获 — 当前目标 {target} · {mode}',
capture_ready: '已捕获 {method} · {target} · …{tail}',
status_idle: '等待中',
status_retrying: '重试中… {count}/{max}',
status_success: '成功!bizId={bizId}',
status_failed: '失败 ({count} 次)',
timer_default: '默认 {time} (UTC+8) · 提前 {early}s · 窗口 {window}s',
timer_before_start: '{time} (UTC+8) 预启动倒计时 {countdown}',
timer_before_open: '预启动中 · 距开售 {countdown}',
timer_in_window: '开售窗口中 · 剩余 {countdown}',
timer_ended: '时间窗口结束',
note_custom: '自定义模式:你可以手动调整所有重试参数。',
note_locked: '{mode}已锁定推荐参数:{delay}ms / {max}次 / Burst {burst}',
alert_need_capture: '请先点击目标套餐购买按钮,或确认页面已切到目标套餐/周期。',
alert_need_buy_click: '请先手动点一次“购买/订阅”按钮,让脚本捕获请求参数',
alert_manual_recover: '已获取到商品!请立即手动点击购买按钮!',
header_lang: 'EN',
header_lang_title: '切换到 English',
header_min_title: '最小化',
panel_loaded: '已加载',
},
en: {
mode_extreme: 'Extreme',
mode_steady: 'Steady',
mode_custom: 'Custom',
mode_short_extreme: 'Extreme',
mode_short_steady: 'Steady',
mode_short_custom: 'Custom',
cycle_month: 'Monthly',
cycle_quarter: 'Quarterly',
cycle_year: 'Yearly',
cycle_short_month: 'Month',
cycle_short_quarter: 'Quarter',
cycle_short_year: 'Year',
label_mode: 'Mode',
label_plan: 'Plan',
label_cycle: 'Cycle',
label_time: 'Time',
label_early: 'Early',
label_window: 'Window',
label_delay: 'Delay',
label_max: 'Max',
label_burst: 'Burst',
label_burst_delay: 'Burst Delay',
label_backoff: 'Backoff',
btn_rush_now: 'Rush Now',
btn_schedule: 'Schedule',
btn_stop: 'Stop',
log_welcome: 'Thanks for using this script! Invite code (ic={code}) is applied for a 5% new-user discount.',
tip_recommended: 'Recommended: {mode} · {plan} · {time} (UTC+8) · Early {early}s · Window {window}s',
capture_idle: 'Request not captured — Target {target} · {mode}',
capture_ready: 'Captured {method} · {target} · …{tail}',
status_idle: 'Idle',
status_retrying: 'Retrying… {count}/{max}',
status_success: 'Success! bizId={bizId}',
status_failed: 'Failed ({count})',
timer_default: 'Default {time} (UTC+8) · Early {early}s · Window {window}s',
timer_before_start: '{time} (UTC+8) pre-start in {countdown}',
timer_before_open: 'Pre-start active · opens in {countdown}',
timer_in_window: 'Rush window active · left {countdown}',
timer_ended: 'Rush window ended',
note_custom: 'Custom mode: you can adjust all retry parameters.',
note_locked: '{mode} uses locked presets: {delay}ms / {max} tries / Burst {burst}',
alert_need_capture: 'Click the target plan button first, or make sure the page is on the correct plan and billing cycle.',
alert_need_buy_click: 'Please click the buy/subscribe button once so the script can capture the request.',
alert_manual_recover: 'The item is available. Please click the purchase button manually right now.',
header_lang: '中',
header_lang_title: 'Switch to 中文',
header_min_title: 'Minimize',
panel_loaded: 'loaded',
},
};
const MODE_PRESETS = {
extreme : { retryDelay: 80, maxRetries: 1600, burstCount: 40, burstDelay: 20, backoffDelay: 160 },
steady : { retryDelay: 120, maxRetries: 900, burstCount: 18, burstDelay: 32, backoffDelay: 280 },
};
let customTuning = {
retryDelay : 100,
maxRetries : 300,
burstCount : 15,
burstDelay : 30,
backoffDelay : 300,
};
/** 运行参数(可通过面板实时调整) */
const Config = {
locale : 'zh',
mode : 'extreme',
targetPlan : 'Pro',
billingCycle : 'month',
targetTime : '10:00:00',
earlyStartSeconds : 8,
watchWindowSeconds: 180,
retryDelay : MODE_PRESETS.extreme.retryDelay,
maxRetries : MODE_PRESETS.extreme.maxRetries,
burstCount : MODE_PRESETS.extreme.burstCount,
burstDelay : MODE_PRESETS.extreme.burstDelay,
backoffDelay : MODE_PRESETS.extreme.backoffDelay,
uiThrottle : 500, // UI 刷新最小间隔 (ms)
cacheTTL : 12000, // 缓存有效期 (ms)
cacheReplayCount: 2, // 缓存重放次数
previewAPI : '/api/biz/pay/preview',
checkAPI : '/api/biz/pay/check',
};
/* ────────────────────────────────────────────-
* 全局运行时状态
* ──────────────────────────────────────────── */
const State = {
phase : 'idle', // idle | retrying | success | failed
retryCount : 0,
bizId : null,
captured : null, // 捕获到的请求参数
cache : null, // 缓存的成功响应
lastSuccess: null,
lastCaptureAt: 0,
proactive : false,
timerId : null,
scheduleTargetAt : 0,
scheduleStartAt : 0,
scheduleEndAt : 0,
scheduleStarted : false,
paymentVisible : false,
paymentWaitSource: '',
panelMinimized : false,
panelPosition : null,
logs : [],
};
let stopRequested = false;
let isRecovering = false;
let recoveryAttempts = 0;
let hasDataPatched = false;
let lastUITimestamp = 0;
let preferredBuyButton = null;
let dialogObserver = null;
let dialogCheckTimer = null;
let lastCycleSwitchAt = 0;
let scheduleLaunching = false;
/* ────────────────────────────────────────────
* 工具函数
* ──────────────────────────────────────────── */
const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
const timestamp = () => new Date().toLocaleTimeString('zh-CN', { hour12: false });
function normalizeText(text) {
return String(text || '').replace(/\s+/g, '');
}
function currentMessages() {
return I18N[Config.locale] || I18N.zh;
}
function template(text, params = {}) {
return String(text || '').replace(/\{(\w+)\}/g, (_, key) => params[key] ?? '');
}
function t(key, params) {
const messages = currentMessages();
const fallback = I18N.zh[key] || key;
return template(messages[key] || fallback, params);
}
function currentCycleLabel(short = false) {
const suffix = short ? 'short_' : '';
return t(`cycle_${suffix}${Config.billingCycle}`);
}
function currentTargetLabel() {
return `${Config.targetPlan} ${currentCycleLabel(Config.locale !== 'zh')}`;
}
function modeLabel(mode = Config.mode) {
return t(`mode_${mode}`);
}
function modeShortLabel(mode = Config.mode) {
return t(`mode_short_${mode}`);
}
function syncCustomTuningFromConfig() {
customTuning = {
retryDelay : Config.retryDelay,
maxRetries : Config.maxRetries,
burstCount : Config.burstCount,
burstDelay : Config.burstDelay,
backoffDelay : Config.backoffDelay,
};
}
function applyModePreset(mode) {
if (mode === Config.mode) return;
if (Config.mode === 'custom' && mode !== 'custom') syncCustomTuningFromConfig();
Config.mode = mode;
if (mode === 'custom') {
Object.assign(Config, customTuning);
return;
}
Object.assign(Config, MODE_PRESETS[mode] || MODE_PRESETS.extreme);
}
/* ── 配置持久化 (localStorage) ── */
const CONFIG_STORAGE_KEY = 'glm-rush-config';
function loadSavedConfig() {
try {
const raw = localStorage.getItem(CONFIG_STORAGE_KEY);
if (!raw) return;
const saved = JSON.parse(raw);
if (saved.locale === 'zh' || saved.locale === 'en') Config.locale = saved.locale;
if (saved.mode && (MODE_PRESETS[saved.mode] || saved.mode === 'custom')) {
if (saved.mode !== Config.mode) applyModePreset(saved.mode);
else if (saved.mode === 'custom') Config.mode = 'custom';
}
if (saved.targetPlan && PLAN_ALIASES[saved.targetPlan]) Config.targetPlan = saved.targetPlan;
if (saved.billingCycle && CYCLE_LABELS[saved.billingCycle]) Config.billingCycle = saved.billingCycle;
if (saved.targetTime) Config.targetTime = saved.targetTime;
if (saved.earlyStartSeconds) Config.earlyStartSeconds = Math.max(1, Math.min(120, +saved.earlyStartSeconds));
if (saved.watchWindowSeconds) Config.watchWindowSeconds = Math.max(30, Math.min(600, +saved.watchWindowSeconds));
if (saved.mode === 'custom' && saved.customTuning) {
Object.assign(customTuning, saved.customTuning);
Object.assign(Config, customTuning);
}
} catch (_) { /* ignore corrupted data */ }
}
function saveCurrentConfig() {
try {
localStorage.setItem(CONFIG_STORAGE_KEY, JSON.stringify({
locale: Config.locale,
mode: Config.mode,
targetPlan: Config.targetPlan,
billingCycle: Config.billingCycle,
targetTime: Config.targetTime,
earlyStartSeconds: Config.earlyStartSeconds,
watchWindowSeconds: Config.watchWindowSeconds,
customTuning: Config.mode === 'custom' ? {
retryDelay: Config.retryDelay,
maxRetries: Config.maxRetries,
burstCount: Config.burstCount,
burstDelay: Config.burstDelay,
backoffDelay: Config.backoffDelay,
} : undefined,
}));
} catch (_) { /* quota exceeded etc. */ }
}
function rewriteAvailabilityText(text) {
if (typeof text !== 'string') return { text, changed: false };
const next = text
.replace(/"isSoldOut":true/g, '"isSoldOut":false')
.replace(/"disabled":true/g, '"disabled":false')
.replace(/"soldOut":true/g, '"soldOut":false')
.replace(/"stock":0/g, '"stock":999');
return { text: next, changed: next !== text };
}
function markPatchedData() {
hasDataPatched = true;
setTimeout(markActivatedButtons, 500);
setTimeout(markActivatedButtons, 1500);
}
async function patchFetchAvailabilityResponse(response) {
const contentType = response?.headers?.get?.('content-type') || '';
if (!contentType.includes('application/json')) return response;
try {
const originalText = await response.clone().text();
const rewritten = rewriteAvailabilityText(originalText);
if (!rewritten.changed) return response;
markPatchedData();
return new Response(rewritten.text, {
status : response.status,
statusText : response.statusText,
headers : response.headers,
});
} catch (_) {
return response;
}
}
function patchXhrAvailabilityResponse(xhr) {
try {
const contentType = xhr.getResponseHeader('content-type') || '';
if (!contentType.includes('application/json')) return;
const rewritten = rewriteAvailabilityText(xhr.responseText);
if (!rewritten.changed) return;
Object.defineProperty(xhr, 'responseText', { get: () => rewritten.text, configurable: true });
Object.defineProperty(xhr, 'response', { get: () => rewritten.text, configurable: true });
markPatchedData();
} catch (_) { }
}
function getTargetTimestampUtc8(timeStr) {
const [hour, minute, second = 0] = timeStr.split(':').map((item) => Number(item) || 0);
const now = Date.now();
const beijingNow = new Date(now + 8 * 60 * 60 * 1000);
let target = Date.UTC(
beijingNow.getUTCFullYear(),
beijingNow.getUTCMonth(),
beijingNow.getUTCDate(),
hour,
minute,
second,
0
) - 8 * 60 * 60 * 1000;
if (target <= now) target += 24 * 60 * 60 * 1000;
return target;
}
function formatCountdown(ms) {
const totalSeconds = Math.max(0, Math.ceil(ms / 1000));
const hours = Math.floor(totalSeconds / 3600);
const minutes = Math.floor((totalSeconds % 3600) / 60);
const seconds = totalSeconds % 60;
return `${String(hours).padStart(2, '0')}:${String(minutes).padStart(2, '0')}:${String(seconds).padStart(2, '0')}`;
}
function getScheduleTickDelay(remaining) {
if (remaining > 60_000) return 1000;
if (remaining > 10_000) return 300;
if (remaining > 3_000) return 100;
if (remaining > 0) return 40;
return 150;
}
function getButtonState(button) {
if (!button) return { text: '', disabled: true };
return {
text : normalizeText(button.textContent),
disabled : button.disabled
|| button.getAttribute('aria-disabled') === 'true'
|| button.classList.contains('is-disabled')
|| button.classList.contains('disabled'),
};
}
function temporarilyEnableButton(button) {
if (!button) return () => { };
const previous = {
disabled : button.disabled,
disabledAttr : button.getAttribute('disabled'),
ariaDisabled : button.getAttribute('aria-disabled'),
className : button.className,
};
button.disabled = false;
button.removeAttribute('disabled');
button.setAttribute('aria-disabled', 'false');
button.classList.remove('is-disabled', 'disabled');
return () => {
button.disabled = previous.disabled;
if (previous.disabledAttr === null) button.removeAttribute('disabled');
else button.setAttribute('disabled', previous.disabledAttr);
if (previous.ariaDisabled === null) button.removeAttribute('aria-disabled');
else button.setAttribute('aria-disabled', previous.ariaDisabled);
button.className = previous.className;
};
}
/** 追加日志并同步输出到控制台 */
function log(message) {
State.logs.push(`${timestamp()} ${message}`);
if (State.logs.length > 80) State.logs.shift();
console.log(`[${SCRIPT_NAME}] ${message}`);
refreshLogPanel();
}
/** 将各种格式的 headers 统一转换为普通对象 */
function normalizeHeaders(raw) {
const result = {};
if (!raw) return result;
if (raw instanceof Headers) { raw.forEach((v, k) => (result[k] = v)); }
else if (Array.isArray(raw)) { raw.forEach(([k, v]) => (result[k] = v)); }
else { Object.entries(raw).forEach(([k, v]) => (result[k] = v)); }
return result;
}
function primeSuccessCache(payload) {
if (!payload?.text) return;
State.cache = {
text : payload.text,
data : payload.data,
expiresAt : Date.now() + Config.cacheTTL,
remainingUses: Config.cacheReplayCount,
};
}
function consumeSuccessCache() {
const cached = State.cache;
if (!cached) return null;
if (cached.expiresAt <= Date.now() || cached.remainingUses <= 0) {
State.cache = null;
return null;
}
cached.remainingUses -= 1;
if (cached.remainingUses <= 0) {
State.cache = null;
}
recoveryAttempts = 0;
return {
text : cached.text,
data : cached.data,
remainingUses: cached.remainingUses,
};
}
function getElementTextSnapshot(el) {
if (!el) return '';
const parts = [
el.textContent,
el.getAttribute?.('title'),
el.getAttribute?.('aria-label'),
el.getAttribute?.('placeholder'),
el.getAttribute?.('data-title'),
el.getAttribute?.('data-name'),
el.getAttribute?.('data-testid'),
].filter(Boolean);
return normalizeText(parts.join(' '));
}
function matchesAliases(text, aliases = []) {
const haystack = normalizeText(text).toLowerCase();
return aliases.some((alias) => haystack.includes(normalizeText(alias).toLowerCase()));
}
function isSelectedNode(node) {
if (!node) return false;
return node.classList.contains('active')
|| node.classList.contains('is-active')
|| node.classList.contains('selected')
|| node.classList.contains('current')
|| node.classList.contains('ant-segmented-item-selected')
|| node.getAttribute('aria-selected') === 'true'
|| node.getAttribute('data-state') === 'active';
}
function findCycleTab(cycle) {
const aliases = CYCLE_ALIASES[cycle] || [CYCLE_LABELS[cycle] || cycle];
return Array.from(document.querySelectorAll('.switch-tab-item, [class*="switch-tab-item"], [role="tab"], .ant-segmented-item, [class*="segment"]')).find((node) => {
return isVisibleElement(node) && matchesAliases(getElementTextSnapshot(node), aliases);
}) || null;
}
function ensureBillingCycleSelected() {
const tab = findCycleTab(Config.billingCycle);
if (!tab) return false;
if (isSelectedNode(tab) || isSelectedNode(tab.parentElement)) return true;
if (Date.now() - lastCycleSwitchAt < 320) return false;
lastCycleSwitchAt = Date.now();
const target = tab.querySelector('.switch-tab-item-content') || tab;
log(`🧭 切换周期到 ${currentCycleLabel()}`);
try { target.focus({ preventScroll: true }); } catch (_) { }
try { target.scrollIntoView({ block: 'center', inline: 'center', behavior: 'auto' }); } catch (_) { }
try { target.click(); } catch (_) { }
return false;
}
function findPlanCard(planName) {
const aliases = PLAN_ALIASES[planName] || [planName];
const cards = document.querySelectorAll('.package-card-box .package-card, .package-card, [class*="package-card"], [class*="plan-card"]');
let bestCard = null;
let bestScore = -Infinity;
for (const card of cards) {
if (!isVisibleElement(card)) continue;
const title = card.querySelector('.package-card-title .font-prompt, .package-card-title, [class*="card-title"], [class*="title"], [class*="name"]');
const titleText = getElementTextSnapshot(title || card);
if (!matchesAliases(titleText, aliases) && !matchesAliases(getElementTextSnapshot(card), aliases)) continue;
let score = 0;
if (title && matchesAliases(getElementTextSnapshot(title), aliases)) score += 60;
if (matchesAliases(getElementTextSnapshot(card), aliases)) score += 20;
if (isSelectedNode(card)) score += 18;
if (matchesAliases(getElementTextSnapshot(card), CYCLE_ALIASES[Config.billingCycle] || [])) score += 10;
if (score > bestScore) {
bestCard = card;
bestScore = score;
}
}
return bestCard;
}
function findTargetBuyButton() {
const card = findPlanCard(Config.targetPlan);
if (!card) return null;
const buttons = card.querySelectorAll('button.buy-btn, .package-card-btn-box button, button, a, [role="button"], div[class*="btn"], span[class*="btn"]');
let bestButton = null;
let bestScore = -Infinity;
for (const button of buttons) {
const rect = button.getBoundingClientRect();
const text = getElementTextSnapshot(button);
if (rect.width <= 0 || rect.height <= 0) continue;
if (!BUY_TEXT_RE.test(text) || SOLD_OUT_TEXT_RE.test(text)) continue;
const state = getButtonState(button);
let score = 80;
if (!state.disabled) score += 20;
if (/立即购买|立即抢购|buynow|purchase|checkout|paynow/i.test(text)) score += 30;
if (matchesAliases(text, CYCLE_ALIASES[Config.billingCycle] || [])) score += 15;
if (button.tagName === 'BUTTON') score += 10;
if (score > bestScore) {
bestButton = button;
bestScore = score;
}
}
return bestButton;
}
function isVisibleElement(el) {
if (!el || !document.contains(el)) return false;
const cs = window.getComputedStyle(el);
if (cs.display === 'none' || cs.visibility === 'hidden' || cs.opacity === '0' || cs.pointerEvents === 'none') return false;
if (!el.offsetParent && cs.position !== 'fixed') return false;
return true;
}
function isBuyCandidate(el) {
const panel = document.getElementById('glm-rush');
const text = getElementTextSnapshot(el);
if (!el || panel?.contains(el)) return false;
if (!BUY_TEXT_RE.test(text) || text.length >= 48) return false;
if (SOLD_OUT_TEXT_RE.test(text)) return false;
return isVisibleElement(el);
}
function rememberBuyButton(el) {
if (!isBuyCandidate(el)) return false;
preferredBuyButton = el;
return true;
}
function scoreBuyButton(el) {
if (!isBuyCandidate(el)) return -Infinity;
const text = getElementTextSnapshot(el);
const rect = el.getBoundingClientRect();
let score = 0;
const card = el.closest('.package-card-box .package-card, .package-card, [class*="package-card"], [class*="plan-card"]');
if (el === preferredBuyButton) score += 200;
if (/立即购买|立即抢购|立刻购买|去支付|去购买|buy\s*now|purchase|checkout|pay\s*now/i.test(text)) score += 60;
if (BUY_TEXT_RE.test(text)) score += 40;
if (card && card === findPlanCard(Config.targetPlan)) score += 90;
if (card && matchesAliases(getElementTextSnapshot(card), CYCLE_ALIASES[Config.billingCycle] || [])) score += 18;
if (el.tagName === 'BUTTON') score += 18;
if (el.getAttribute('role') === 'button') score += 12;
if (rect.width >= 64) score += 10;
if (rect.height >= 28) score += 8;
if (rect.top >= 0 && rect.top < window.innerHeight) score += 6;
if (rect.left >= 0 && rect.left < window.innerWidth) score += 4;
return score;
}
function dispatchBuyGesture(el) {
const rect = el.getBoundingClientRect();
const clientX = rect.left + Math.max(rect.width / 2, 1);
const clientY = rect.top + Math.max(rect.height / 2, 1);
const base = {
bubbles : true,
cancelable: true,
composed : true,
view : window,
clientX,
clientY,
button : 0,
};
if (typeof PointerEvent === 'function') {
el.dispatchEvent(new PointerEvent('pointerdown', { ...base, buttons: 1, pointerId: 1, pointerType: 'mouse', isPrimary: true }));
}
el.dispatchEvent(new MouseEvent('mousedown', { ...base, buttons: 1 }));
if (typeof PointerEvent === 'function') {
el.dispatchEvent(new PointerEvent('pointerup', { ...base, buttons: 0, pointerId: 1, pointerType: 'mouse', isPrimary: true }));
}
el.dispatchEvent(new MouseEvent('mouseup', { ...base, buttons: 0 }));
el.dispatchEvent(new MouseEvent('click', { ...base, buttons: 0, detail: 1 }));
}
async function triggerBuyButton(reason) {
let btn = findBuyButton();
if (!btn) {
ensureBillingCycleSelected();
await sleep(140);
btn = findBuyButton();
}
if (!btn) return false;
rememberBuyButton(btn);
const previousCaptureAt = State.lastCaptureAt;
const beforeText = getButtonState(btn).text;
let restoreButton = null;
const state = getButtonState(btn);
log(`🎯 ${reason}:定位按钮「${(getElementTextSnapshot(btn) || 'button').slice(0, 24)}」${state.disabled ? ' (原为禁用)' : ''}`);
if (state.disabled) {
restoreButton = temporarilyEnableButton(btn);
log(`⚡ ${reason}:已临时解除按钮禁用`);
}
try { btn.focus({ preventScroll: true }); }
catch (_) {
try { btn.focus(); } catch (_) { }
}
try { btn.scrollIntoView({ block: 'center', inline: 'center', behavior: 'auto' }); } catch (_) { }
try {
try { dispatchBuyGesture(btn); } catch (_) { }
await sleep(60);
if (State.lastCaptureAt > previousCaptureAt || isPaymentDialogVisible()) {
log(`🖱 ${reason}:已触发购买按钮`);
return true;
}
try { btn.click(); } catch (_) { }
await sleep(90);
let triggered = State.lastCaptureAt > previousCaptureAt || isPaymentDialogVisible() || !btn.isConnected || getButtonState(btn).text !== beforeText;
if (!triggered && btn.isConnected) {
try { dispatchBuyGesture(btn); } catch (_) { }
await sleep(70);
triggered = State.lastCaptureAt > previousCaptureAt || isPaymentDialogVisible() || !btn.isConnected || getButtonState(btn).text !== beforeText;
}
log(triggered ? `🖱 ${reason}:已自动点击购买按钮` : `⚠️ ${reason}:点击后未观察到 preview 请求`);
return triggered;
} finally {
if (restoreButton) {
setTimeout(() => {
try { restoreButton(); } catch (_) { }
}, 1200);
}
}
}
function queueDialogRecoveryCheck() {
if (dialogCheckTimer) return;
dialogCheckTimer = setTimeout(() => {
dialogCheckTimer = null;
if (isPaymentDialogVisible()) return;
if (State.lastSuccess && !isRecovering && recoveryAttempts < 3) {
if (findErrorDialog()) autoRecover();
}
}, 80);
}
function trackBuyButton(event) {
const target = event.target;
if (!(target instanceof Element)) return;
const candidate = target.closest('button, a, [role="button"], div[class*="btn"], span[class*="btn"]');
if (candidate) rememberBuyButton(candidate);
}
document.addEventListener('pointerdown', trackBuyButton, true);
document.addEventListener('click', trackBuyButton, true);
/* ────────────────────────────────────────────
* 邀请码自动注入
* ──────────────────────────────────────────── */
(function injectInviteCode() {
try {
const { pathname, search, origin, hash } = window.location;
if (!pathname.startsWith('/glm-coding')) return;
const params = new URLSearchParams(search);
if (params.get('ic') === INVITE_CODE) return;
params.set('ic', INVITE_CODE);
const targetUrl = `${origin}${pathname}?${params.toString()}${hash}`;
window.history.replaceState(null, '', targetUrl);
if (document.readyState === 'complete' && !sessionStorage.getItem('glm_ic_injected')) {
sessionStorage.setItem('glm_ic_injected', '1');
location.replace(targetUrl);
}
} catch (_) { /* 静默失败,不影响主流程 */ }
})();
/* ────────────────────────────────────────────
* JSON.parse 深层补丁 — 解锁售罄 / disabled 状态
* ──────────────────────────────────────────── */
const nativeParse = JSON.parse;
JSON.parse = function (text, reviver) {
const result = nativeParse(text, reviver);
try {
let patched = false;
(function traverse(obj) {
if (!obj || typeof obj !== 'object') return;
if (obj.isSoldOut === true) { obj.isSoldOut = false; patched = true; }
if (obj.soldOut === true) { obj.soldOut = false; patched = true; }
if (obj.disabled === true && (obj.price !== undefined || obj.productId || obj.title)) {
obj.disabled = false;
patched = true;
}
if (obj.stock === 0) { obj.stock = 999; patched = true; }
for (const key in obj) {
if (obj[key] && typeof obj[key] === 'object') traverse(obj[key]);
}
})(result);
if (patched) {
markPatchedData();
}
} catch (_) { /* 忽略解析异常 */ }
return result;
};
/* ────────────────────────────────────────────
* 按钮激活标记
* ──────────────────────────────────────────── */
/** 在被脚本激活的购买按钮旁添加提示角标 */
function markActivatedButtons() {
const panel = document.getElementById('glm-rush');
const candidates = document.querySelectorAll(
'button, a, [role="button"], div[class*="btn"], span[class*="btn"]'
);
for (const el of candidates) {
if (panel && panel.contains(el)) continue;
const text = (el.textContent || '').trim();
if (!/购买|抢购|立即|下单|订阅/.test(text) || text.length >= 20) continue;
if (el.offsetParent === null || el.dataset.glmMarked) continue;
el.dataset.glmMarked = '1';
el.style.position = 'relative';
const badge = document.createElement('span');
badge.className = 'glm-badge';
badge.textContent = '⚡ 脚本已激活';
badge.title = '此按钮由脚本提前解锁,官方尚未开放抢购';
el.parentElement.style.position = 'relative';
el.parentElement.appendChild(badge);
}
}
/** 注入角标样式 */
function injectBadgeStyles() {
if (document.getElementById('glm-badge-css')) return;
const style = document.createElement('style');
style.id = 'glm-badge-css';
style.textContent = `
.glm-badge {
position: absolute;
top: -10px; right: -10px;
background: linear-gradient(135deg, #f39c12, #e74c3c);
color: #fff;
font-size: 10px;
padding: 2px 8px;
border-radius: 10px;
white-space: nowrap;
pointer-events: none;
z-index: 10;
animation: glm-badge-pulse 2s ease-in-out infinite;
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.35);
font-family: system-ui, sans-serif;
}
@keyframes glm-badge-pulse {
0%, 100% { opacity: 1; transform: scale(1); }
50% { opacity: 0.7; transform: scale(0.96); }
}
`;
document.head.appendChild(style);
}
/* ────────────────────────────────────────────
* 核心:高速重试引擎
* ──────────────────────────────────────────── */
const nativeFetch = window.fetch;
let activeRetryJob = null;
/**
* 对指定 URL 发起高速重试请求,直到获取有效 bizId 或达到上限。
* 包含极速阶段 → 常规阶段 → 限流退避三级策略。
*/
async function executeRetry(url, opts) {
if (activeRetryJob) {
log('⏳ 已有重试任务运行中,合并请求…');
return activeRetryJob;
}
stopRequested = false;
activeRetryJob = (async () => {
State.phase = 'retrying';
State.retryCount = 0;
refreshStatusPanel();
const { signal, ...cleanOpts } = opts || {};
for (let i = 1; i <= Config.maxRetries; i++) {
if (stopRequested) { log('⏹ 重试已被手动停止'); break; }
State.retryCount = i;
const now = Date.now();
if (now - lastUITimestamp >= Config.uiThrottle || i === 1) {
lastUITimestamp = now;
refreshStatusPanel();
}
try {
const resp = await nativeFetch(url, { ...cleanOpts, credentials: 'include' });
const text = await resp.text();
let data;
try { data = nativeParse(text); } catch { data = null; }
if (data?.code === 200 && data?.data?.bizId) {
const bizId = data.data.bizId;
log(`🔑 获取 bizId=${bizId},校验 check 接口…`);
try {
const checkUrl = `${location.origin}${Config.checkAPI}?bizId=${bizId}`;
const checkResp = await nativeFetch(checkUrl, { credentials: 'include' });
const checkText = await checkResp.text();
let checkData;
try { checkData = nativeParse(checkText); } catch { checkData = null; }
if (checkData?.data === 'EXPIRE') {
log(`#${i} bizId 已过期 (EXPIRE),继续…`);
await sleep(Config.retryDelay);
continue;
}
if (!checkData || (checkData.code && checkData.code !== 200)) {
log(`#${i} check 失败 (code=${checkData?.code}),继续…`);
await sleep(Config.retryDelay);
continue;
}
State.phase = 'success';
State.bizId = bizId;
State.lastSuccess = { text, data };
recoveryAttempts = 0;
log(`✅ 抢购成功!bizId=${bizId},check 校验通过 (第 ${i} 次)`);
log('💳 已获取有效订单,准备触发支付弹窗流程…');
refreshStatusPanel();
setTimeout(() => {
if (!isPaymentDialogVisible()) autoRecover(true);
}, 900);
return { ok: true, text, data, status: resp.status };
} catch (err) {
log(`#${i} check 异常: ${err.message},继续…`);
await sleep(Config.retryDelay);
continue;
}
}
const isRateLimit = data?.code === 555;
const reason = !data ? '非 JSON 响应'
: isRateLimit ? '系统繁忙 (555)'
: data?.data?.bizId === null ? '售罄 (bizId=null)'
: `未知 (code=${data.code})`;
if (i <= 5 || i % 20 === 0) log(`#${i} ${reason}`);
const baseDelay = isRateLimit ? Config.backoffDelay
: i <= Config.burstCount ? Config.burstDelay
: Config.retryDelay;
await sleep(baseDelay + Math.floor(Math.random() * (baseDelay * 0.3)));
} catch (err) {
if (i <= 3 || i % 20 === 0) log(`#${i} 网络错误: ${err.message}`);
const d = i <= Config.burstCount ? Config.burstDelay : Config.retryDelay;
await sleep(d + Math.floor(Math.random() * (d * 0.3)));
}
}
if (!stopRequested) {
State.phase = 'failed';
log(`❌ 已达上限 ${Config.maxRetries} 次`);
} else {
State.phase = 'idle';
}
refreshStatusPanel();
return { ok: false };
})();
try { return await activeRetryJob; }
finally { activeRetryJob = null; }
}
/* ────────────────────────────────────────────
* 支付弹窗检测
* ──────────────────────────────────────────── */
const DIALOG_SELECTORS = [
'.el-dialog', '.el-message-box', '.el-dialog__wrapper',
'.ant-modal', '.ant-modal-wrap',
'[class*="modal"]', '[class*="dialog"]', '[class*="popup"]',
'[role="dialog"]',
].join(', ');
const PAYMENT_KEYWORDS = /二维码|扫码|支付|QRCode|qrcode|微信|支付宝|付款|扫一扫|wechat|alipay/i;
const QR_SELECTORS = 'canvas, img[src*="qr"], img[src*="QR"], [class*="qr"], [class*="QR"]';
/** GLM-specific pay dialog selectors (more reliable than generic keyword matching) */
const GLM_PAY_SELECTORS = '.white-mask-bg .pay-dialog, .white-mask-bg .scan-code-box, .confirm-pay-btn, .scan-qrcode-box';
function findPaymentDialog() {
/* ── 1. GLM-specific pay dialog (highest priority) ── */
const glmRoot = document.querySelector(GLM_PAY_SELECTORS);
if (glmRoot) {
const priceEl = glmRoot.querySelector('.scan-qrcode-box .price-icon + span');
if ((priceEl && priceEl.textContent?.trim()) || glmRoot.querySelector('.confirm-pay-btn')) {
return glmRoot;
}
}
/* ── 2. Generic dialog keyword/QR fallback ── */
for (const el of document.querySelectorAll(DIALOG_SELECTORS)) {
const cs = window.getComputedStyle(el);
if (cs.display === 'none' || cs.visibility === 'hidden' || cs.opacity === '0') continue;
if (!el.offsetParent && cs.position !== 'fixed') continue;
if (PAYMENT_KEYWORDS.test(el.textContent || '')) return el;
if (el.querySelector(QR_SELECTORS)) return el;
}
return null;
}
function syncPaymentDialogState(source = 'watcher') {
const dialog = findPaymentDialog();
const visible = !!dialog;
if (visible && !State.paymentVisible) {
State.paymentVisible = true;
State.paymentWaitSource = '';
log(`💳 支付弹窗已出现 (${source})`);
} else if (!visible && State.paymentVisible) {
State.paymentVisible = false;
log(`💳 支付弹窗已关闭或离开视野 (${source})`);
}
return dialog;
}
async function waitForPaymentDialog(reason, timeoutMs = 2600) {
State.paymentWaitSource = reason;
log(`💳 ${reason}:等待支付弹窗…`);
const startAt = Date.now();
while (Date.now() - startAt < timeoutMs) {
if (syncPaymentDialogState(reason)) return true;
await sleep(120);
}
if (!syncPaymentDialogState(reason)) {
log(`⚠️ ${reason}:${Math.ceil(timeoutMs / 1000)} 秒内未检测到支付弹窗`);
}
return false;
}
/** 检测页面上是否存在可见的支付弹窗 */
function isPaymentDialogVisible() {
return !!findPaymentDialog();
}
/* ────────────────────────────────────────────
* 错误弹窗自动恢复
* ──────────────────────────────────────────── */
const ERROR_KEYWORDS = /购买人数过多|系统繁忙|稍后再试|请重试|繁忙|失败|出错|异常/;
/** 查找页面上可见的错误弹窗(排除支付弹窗) */
function findErrorDialog() {
/* ── GLM-specific: empty-price pay dialog = failure ── */
const payRoot = document.querySelector('.white-mask-bg .pay-dialog, .white-mask-bg .scan-code-box');
if (payRoot) {
const cs = window.getComputedStyle(payRoot);
if (cs.display !== 'none' && cs.visibility !== 'hidden') {
const priceEl = payRoot.querySelector('.scan-qrcode-box .price-icon + span');
if (!priceEl || !priceEl.textContent?.trim()) {
return payRoot; // pay dialog opened but no price = failure
}
}
}
/* ── GLM-specific: 购买人数较多 busy dialog ── */
const busyWrap = document.querySelector('.el-dialog__wrapper .empty-data-wrap');
if (busyWrap?.textContent?.includes('购买人数较多')) {
return busyWrap.closest('.el-dialog') || busyWrap;
}
/* ── Generic error dialog detection ── */
for (const el of document.querySelectorAll(DIALOG_SELECTORS)) {
const cs = window.getComputedStyle(el);
if (cs.display === 'none' || cs.visibility === 'hidden' || cs.opacity === '0') continue;
if (!el.offsetParent && cs.position !== 'fixed') continue;
const text = el.textContent || '';
if (PAYMENT_KEYWORDS.test(text)) continue;
if (ERROR_KEYWORDS.test(text)) return el;
}
return null;
}
/** 尝试关闭指定弹窗,成功返回 true */
function dismissDialog(dialog) {
if (isPaymentDialogVisible()) {
log('💳 支付弹窗可见,跳过关闭操作');
return false;
}
const closeSelectors = [
'.el-dialog__headerbtn', '.el-message-box__headerbtn',
'.el-dialog__close', '.ant-modal-close',
'[class*="close-btn"]', '[class*="closeBtn"]',
'[aria-label="Close"]', '[aria-label="close"]',
];
for (const sel of closeSelectors) {
const btn = dialog.querySelector(sel) || document.querySelector(sel);
if (btn?.offsetParent) { btn.click(); log('🔄 关闭弹窗 (关闭按钮)'); return true; }
}
for (const btn of dialog.querySelectorAll('button, [role="button"]')) {
const t = (btn.textContent || '').trim();
if (/关闭|确定|取消|知道了|OK|Cancel|Close|确认/.test(t) && t.length < 10) {
btn.click();
log(`🔄 关闭弹窗 (点击「${t}」)`);
return true;
}
}
document.dispatchEvent(new KeyboardEvent('keydown', { key: 'Escape', keyCode: 27, bubbles: true }));
document.dispatchEvent(new KeyboardEvent('keyup', { key: 'Escape', keyCode: 27, bubbles: true }));
log('🔄 尝试通过 Escape 关闭弹窗');
const masks = document.querySelectorAll('.el-overlay, .v-modal, .el-overlay-dialog, [class*="overlay"], [class*="mask"]');
for (const mask of masks) {
const cs = window.getComputedStyle(mask);
if (mask.offsetParent !== null || cs.position === 'fixed') {
mask.click();
log('🔄 尝试点击遮罩层关闭弹窗');
return true;
}
}
dialog.style.display = 'none';
document.querySelectorAll('.el-overlay, .v-modal').forEach((el) => (el.style.display = 'none'));
log('🔄 强制隐藏错误弹窗');
return true;
}
/** 自动恢复:关闭错误弹窗 → 重新触发购买流程 */
async function autoRecover(force = false) {
if (isRecovering || recoveryAttempts >= 3 || !State.lastSuccess) return;
if (isPaymentDialogVisible()) { log('💳 支付弹窗已出现,无需恢复'); return; }
const dialog = findErrorDialog();
if (!dialog && !force) return;
isRecovering = true;
recoveryAttempts++;
log(
force && !dialog
? `🔄 自动恢复 #${recoveryAttempts}:未检测到支付弹窗,直接重放成功响应…`
: `🔄 自动恢复 #${recoveryAttempts}:关闭错误弹窗并重放成功响应…`
);
try {
primeSuccessCache(State.lastSuccess);
if (dialog) {
if (!dismissDialog(dialog)) return;
await sleep(320);
if (isPaymentDialogVisible()) { log('💳 支付弹窗已出现,停止恢复'); return; }
const stillThere = findErrorDialog();
if (stillThere) {
if (!dismissDialog(stillThere)) return;
await sleep(180);
}
}
if (isPaymentDialogVisible()) { log('💳 支付弹窗已出现,停止恢复'); return; }
const triggered = await triggerBuyButton('自动恢复');
if (!triggered) {
log('⚠️ 恢复失败:未找到购买按钮,请立即手动点击');
alert(t('alert_manual_recover'));
} else {
await waitForPaymentDialog('自动恢复', 2800);
}
} finally {
isRecovering = false;
}
}
/** 启动弹窗监视定时器 */
function startDialogWatcher() {
setInterval(() => {
syncPaymentDialogState('watcher');
if (State.lastSuccess && !isRecovering && recoveryAttempts < 3) {
queueDialogRecoveryCheck();
}
}, 250);
const observerTarget = document.documentElement;
if (!dialogObserver && observerTarget) {
dialogObserver = new MutationObserver(() => {
syncPaymentDialogState('mutation');
if (State.lastSuccess && !isRecovering && recoveryAttempts < 3) {
queueDialogRecoveryCheck();
}
});
dialogObserver.observe(observerTarget, { childList: true, subtree: true });
}
}
/* ────────────────────────────────────────────
* Fetch 拦截器
* ──────────────────────────────────────────── */
window.fetch = async function (input, init) {
const url = typeof input === 'string' ? input : input?.url;
if (url?.includes(Config.previewAPI)) {
State.captured = {
url,
method : init?.method || 'POST',
body : init?.body,
headers : normalizeHeaders(init?.headers),
};
State.lastCaptureAt = Date.now();
log('🎯 捕获 preview 请求 (Fetch)');
refreshStatusPanel();
const cached = consumeSuccessCache();
if (cached) {
log(cached.remainingUses > 0 ? `📦 返回缓存响应 (剩余 ${cached.remainingUses} 次)` : '📦 返回缓存响应');
return new Response(cached.text, {
status: 200,
headers: { 'Content-Type': 'application/json' },
});
}
const result = await executeRetry(url, {
method : init?.method || 'POST',
body : init?.body,
headers: normalizeHeaders(init?.headers),
signal : init?.signal,
});
if (result.ok) {
return new Response(result.text, {
status: result.status,
headers: { 'Content-Type': 'application/json' },
});
}
return nativeFetch.apply(this, [input, init]);
}
if (url?.includes(Config.checkAPI) && url.includes('bizId=null')) {
log('🚫 拦截无效请求: check(bizId=null)');
return new Response(JSON.stringify({ code: -1, msg: '等待有效 bizId' }), {
status: 200,
headers: { 'Content-Type': 'application/json' },
});
}
const response = await nativeFetch.apply(this, [input, init]);
return patchFetchAvailabilityResponse(response);
};
/* ────────────────────────────────────────────
* XHR 拦截器
* ──────────────────────────────────────────── */
const nativeXhrOpen = XMLHttpRequest.prototype.open;
const nativeXhrSend = XMLHttpRequest.prototype.send;
const nativeXhrSetHeader = XMLHttpRequest.prototype.setRequestHeader;
XMLHttpRequest.prototype.setRequestHeader = function (key, value) {
(this._glmHeaders || (this._glmHeaders = {}))[key] = value;
return nativeXhrSetHeader.call(this, key, value);
};
XMLHttpRequest.prototype.open = function (method, url) {
this._glmMethod = method;
this._glmUrl = url;
this._glmPatchedAvailability = false;
return nativeXhrOpen.apply(this, arguments);
};
XMLHttpRequest.prototype.send = function (body) {
const url = this._glmUrl;
if (!this._glmPatchedAvailability) {
this._glmPatchedAvailability = true;
this.addEventListener('readystatechange', function () {
if (this.readyState === 4 && this.status === 200) {
patchXhrAvailabilityResponse(this);
}
});
}
if (typeof url === 'string' && url.includes(Config.previewAPI)) {
const self = this;
State.captured = {
url,
method : this._glmMethod,
body,
headers : this._glmHeaders || {},
};
State.lastCaptureAt = Date.now();
log('🎯 捕获 preview 请求 (XHR)');
refreshStatusPanel();
const cached = consumeSuccessCache();
if (cached) {
log(cached.remainingUses > 0 ? `📦 返回缓存响应 (XHR, 剩余 ${cached.remainingUses} 次)` : '📦 返回缓存响应 (XHR)');
emulateXhrResponse(self, cached.text);
return;
}
executeRetry(url, {
method : this._glmMethod,
body,
headers : this._glmHeaders || {},
}).then((result) => {
emulateXhrResponse(self, result.ok ? result.text : '{"code":-1,"msg":"重试失败"}');
});
return;
}
if (typeof url === 'string' && url.includes(Config.checkAPI) && url.includes('bizId=null')) {
log('🚫 拦截无效请求: check(bizId=null) (XHR)');
emulateXhrResponse(this, '{"code":-1,"msg":"等待有效 bizId"}');
return;
}
return nativeXhrSend.call(this, body);
};
/** 模拟 XHR 成功响应 */
function emulateXhrResponse(xhr, text) {
setTimeout(() => {
const define = (prop, val) =>
Object.defineProperty(xhr, prop, { value: val, configurable: true });
define('readyState', 4);
define('status', 200);
define('statusText', 'OK');
define('responseText', text);
define('response', text);
const rscEvent = new Event('readystatechange');
if (typeof xhr.onreadystatechange === 'function') xhr.onreadystatechange(rscEvent);
xhr.dispatchEvent(rscEvent);
const loadEvent = new ProgressEvent('load');
if (typeof xhr.onload === 'function') xhr.onload(loadEvent);
xhr.dispatchEvent(loadEvent);
xhr.dispatchEvent(new ProgressEvent('loadend'));
}, 0);
}
/* ────────────────────────────────────────────
* 主动抢购 / 停止 / 查找按钮
* ──────────────────────────────────────────── */
/** 查找页面上的购买按钮(排除脚本面板内的按钮) */
function findBuyButton() {
const targeted = findTargetBuyButton();
if (rememberBuyButton(targeted)) return targeted;
if (rememberBuyButton(preferredBuyButton)) return preferredBuyButton;
let bestButton = null;
let bestScore = -Infinity;
for (const el of document.querySelectorAll(
'button, a, [role="button"], div[class*="btn"], span[class*="btn"]'
)) {
const score = scoreBuyButton(el);
if (score > bestScore) {
bestButton = el;
bestScore = score;
}
}
if (bestButton) rememberBuyButton(bestButton);
return bestButton;
}
/** 启动主动抢购模式 */
async function startProactive() {
if (!State.captured) {
log('⚠️ 请先手动点一次「购买/订阅」按钮');
alert(t('alert_need_buy_click'));
return;
}
ensureBillingCycleSelected();
State.proactive = true;
log(`🚀 主动抢购模式启动 (${currentTargetLabel()} · ${modeLabel()})`);
const { url, method, body, headers } = State.captured;
const result = await executeRetry(url, { method, body, headers });
State.proactive = false;
if (result.ok) {
primeSuccessCache(result);
log('🎉 主动模式成功!正在触发购买流程…');
const errDlg = findErrorDialog();
if (errDlg) { dismissDialog(errDlg); await sleep(300); }
const triggered = await triggerBuyButton('主动模式');
if (!triggered) {
log('⚠️ 未找到购买按钮,请手动点击');
alert(t('alert_manual_recover'));
} else {
await waitForPaymentDialog('主动模式', 2800);
}
}
}
/** 停止所有抢购活动 */
function stopAll() {
stopRequested = true;
State.proactive = false;
State.phase = 'idle';
State.retryCount = 0;
State.bizId = null;
State.cache = null;
State.lastSuccess = null;
State.scheduleTargetAt = 0;
State.scheduleStartAt = 0;
State.scheduleEndAt = 0;
State.scheduleStarted = false;
State.paymentVisible = false;
State.paymentWaitSource = '';
scheduleLaunching = false;
if (State.timerId) { clearTimeout(State.timerId); State.timerId = null; }
if (dialogCheckTimer) { clearTimeout(dialogCheckTimer); dialogCheckTimer = null; }
log('⏹ 已停止所有任务');
refreshStatusPanel();
}
/* ────────────────────────────────────────────
* 定时触发
* ──────────────────────────────────────────── */
async function launchScheduledRush() {
if (scheduleLaunching || State.phase === 'retrying' || State.phase === 'success' || State.proactive || stopRequested) return;
scheduleLaunching = true;
try {
const cycleReady = ensureBillingCycleSelected();
if (!cycleReady) return;
const triggered = await triggerBuyButton(State.scheduleStarted ? '定时窗口补点' : '定时预启动');
State.scheduleStarted = true;
if (!triggered && State.captured) {
await startProactive();
}
} finally {
scheduleLaunching = false;
}
}
/** 设置定时抢购,精确到秒 */
function scheduleAt(timeStr) {
if (State.timerId) { clearTimeout(State.timerId); State.timerId = null; }
Config.targetTime = timeStr;
const targetAt = getTargetTimestampUtc8(timeStr);
State.scheduleTargetAt = targetAt;
State.scheduleStartAt = targetAt - Config.earlyStartSeconds * 1000;
State.scheduleEndAt = targetAt + Config.watchWindowSeconds * 1000;
State.scheduleStarted = false;
stopRequested = false;
log(`⏰ 定时已设定: ${timeStr} (UTC+8) · 提前 ${Config.earlyStartSeconds}s · 窗口 ${Config.watchWindowSeconds}s`);
const tick = async () => {
const now = Date.now();
const timerEl = document.getElementById('glm-timer-info');
if (now >= State.scheduleEndAt) {
if (timerEl) timerEl.textContent = '🛑 时间窗口结束';
stopAll();
return;
}
if (timerEl) {
if (now < State.scheduleStartAt) timerEl.textContent = `⏳ ${formatCountdown(State.scheduleStartAt - now)} 后预启动`;
else if (now < State.scheduleTargetAt) timerEl.textContent = `⚡ 预启动中 · 距开售 ${formatCountdown(State.scheduleTargetAt - now)}`;
else timerEl.textContent = `🔥 开售窗口中 · 剩余 ${formatCountdown(State.scheduleEndAt - now)}`;
}
if (now >= State.scheduleStartAt) {
await launchScheduledRush();
}
const delay = getScheduleTickDelay(Math.max(0, State.scheduleStartAt - now));
State.timerId = setTimeout(() => {
State.timerId = null;
void tick();
}, delay);
};
void tick();
refreshStatusPanel();
}
/* ────────────────────────────────────────────
* 浮动控制面板 — UI 构建
* ──────────────────────────────────────────── */
function createPanel() {
const panel = document.createElement('div');
panel.id = 'glm-rush';
panel.innerHTML = `
<style>
/* ── 面板容器 ── */
#glm-rush {
position: fixed; top: 16px; right: 16px; width: 348px;
background: #f6fbff;
color: #17324d;
border: 1px solid #c9daef;
border-radius: 16px;
box-shadow: 0 14px 36px rgba(43, 87, 152, 0.16), 0 0 0 1px rgba(255, 255, 255, 0.65) inset;
z-index: 999999;
font: 13px/1.6 'Inter', 'Segoe UI', system-ui, -apple-system, sans-serif;
user-select: none;
backdrop-filter: blur(8px);
}
#glm-rush[data-lang="en"] { width: 388px; }
#glm-rush * { box-sizing: border-box; margin: 0; padding: 0; }
/* ── 头部 ── */
.glm-header {
background: linear-gradient(180deg, #eef6ff 0%, #deebff 100%);
padding: 11px 14px;
border-radius: 16px 16px 0 0;
border-bottom: 1px solid #d6e4f6;
display: flex; justify-content: space-between; align-items: center;
cursor: move;
}
.glm-header-title {
font-size: 14px; font-weight: 800;
letter-spacing: 0.2px;
color: #114b90;
}
.glm-header-ver {
font-size: 10px; font-weight: 700;
color: rgba(17, 75, 144, 0.62); margin-left: 6px;
}
.glm-header-actions {
display: flex;
align-items: center;
gap: 6px;
}
.glm-btn-min,
.glm-btn-lang {
background: #ffffff;
border: 1px solid #d3e2f5;
border-radius: 8px;
color: #184883; cursor: pointer;
line-height: 1;
display: flex; align-items: center; justify-content: center;
transition: all 0.2s;
}
.glm-btn-min {
font-size: 16px;
width: 28px; height: 28px;
}
.glm-btn-lang {
min-width: 34px;
height: 28px;
padding: 0 8px;
font-size: 11px;
font-weight: 800;
}
.glm-btn-min:hover,
.glm-btn-lang:hover { background: #f2f8ff; color: #123e74; }
/* ── 主体 ── */
.glm-body { padding: 10px 12px 12px; }
/* ── 邀请码提示 ── */
.glm-invite-notice {
background: #fbfdff;
border: 1px solid #d8e6f6;
border-radius: 12px;
padding: 8px 10px;
margin-bottom: 8px;
font-size: 11px;
color: #2a4f7f;
line-height: 1.45;
}
/* ── 状态指示器 ── */
.glm-status {
padding: 9px 10px;
border-radius: 12px;
text-align: center;
font-weight: 800; font-size: 13px;
margin-bottom: 8px;
transition: all 0.3s ease;
letter-spacing: 0.2px;
}
.glm-status-idle { background: #ffffff; color: #245089; border: 1px solid #d8e6f6; }
.glm-status-retrying { background: #eef5ff; color: #0f4fcb; border: 1px solid #bfd8ff; animation: glm-glow 1.5s ease-in-out infinite; }
.glm-status-success { background: #f1fbf5; color: #0f8c54; border: 1px solid #cdeed8; }
.glm-status-failed { background: #fff4f7; color: #c93b66; border: 1px solid #f3ccd8; }
@keyframes glm-glow {
0%, 100% { box-shadow: 0 0 0 rgba(57, 107, 255, 0); }
50% { box-shadow: 0 0 14px rgba(57, 107, 255, 0.18); }
}
/* ── 捕获信息 ── */
.glm-capture {
font-size: 11px;
padding: 6px 10px;
background: #ffffff;
border: 1px solid #d8e6f6;
border-radius: 10px;
margin-bottom: 8px;
white-space: normal; overflow: hidden; text-overflow: initial;
line-height: 1.45;
color: #234f86;
}
/* ── 参数行 ── */
.glm-param-row {
display: flex; align-items: center; gap: 8px;
margin-bottom: 8px; font-size: 12px; flex-wrap: wrap;
background: #ffffff;
border: 1px solid #d8e6f6;
border-radius: 12px;
padding: 8px 10px;
}
.glm-param-row label { color: #234f86; font-weight: 700; min-width: 34px; }
#glm-rush[data-lang="en"] .glm-param-row label { min-width: 54px; }
.glm-param-row input[type=number],
.glm-param-row input[type=time],
.glm-param-row select {
width: 88px; padding: 5px 8px;
border: 1px solid #c8daf1;
border-radius: 9px;
background: #ffffff;
color: #16325c; text-align: center;
font-size: 12px; font-family: inherit;
transition: border-color 0.2s;
}
.glm-param-row input[type=time] { width: 126px; }
#glm-rush[data-lang="en"] .glm-param-row input[type=time] { width: 146px; }
.glm-param-row input:focus {
outline: none;
border-color: #6da6ff;
box-shadow: 0 0 0 2px rgba(70, 132, 255, 0.12);
}
.glm-param-unit { color: #5074a3; font-size: 11px; }
/* ── 操作按钮 ── */
.glm-actions { display: flex; gap: 8px; margin-bottom: 8px; }
.glm-actions button {
flex: 1;
min-height: 38px; padding: 9px 12px;
border: none; border-radius: 10px; cursor: pointer;
font-weight: 800; font-size: 12px; color: #fff;
transition: all 0.2s; letter-spacing: 0.3px;
}
.glm-actions button:hover { transform: translateY(-1px); box-shadow: 0 8px 18px rgba(37, 82, 145, 0.18); }
.glm-actions button:active { transform: translateY(0); }
.glm-btn-start { background: linear-gradient(135deg, #3b73ff, #4d8eff); }
.glm-btn-schedule { background: linear-gradient(135deg, #5a96ff, #4ba9ff); }
.glm-btn-stop { background: linear-gradient(135deg, #ff6b8f, #ff7d70); }
/* ── 日志区域 ── */
.glm-logs {
max-height: 108px; overflow-y: auto;
background: #ffffff;
border: 1px solid #d8e6f6;
border-radius: 10px;
padding: 8px 10px;
font-size: 11px; line-height: 1.8;
font-family: 'JetBrains Mono', 'Fira Code', Consolas, monospace;
color: #17324d;
}
.glm-logs div {
white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
padding: 1px 0;
border-bottom: 1px solid rgba(108, 133, 171, 0.1);
}
.glm-logs div:last-child { border-bottom: none; }
.glm-logs::-webkit-scrollbar { width: 4px; }
.glm-logs::-webkit-scrollbar-track { background: transparent; }
.glm-logs::-webkit-scrollbar-thumb { background: rgba(67, 110, 177, 0.28); border-radius: 2px; }
</style>
<div class="glm-header" id="glm-drag">
<div>
<span class="glm-header-title">⚡ ${SCRIPT_NAME}</span>
<span class="glm-header-ver">v${VERSION}</span>
</div>
<div class="glm-header-actions">
<button class="glm-btn-lang" id="glm-lang" title="切换到 English">EN</button>
<button class="glm-btn-min" id="glm-min" title="最小化">−</button>
</div>
</div>
<div class="glm-body" id="glm-body">
<div class="glm-invite-notice" id="glm-tip-copy">
💡 推荐配置:<b>极限模式</b> · <b>Pro 连续包月</b> · <b>10:00:00 (UTC+8)</b> · 提前 <b>${Config.earlyStartSeconds}s</b>。
</div>
<div class="glm-status glm-status-idle" id="glm-status">⏳ 等待中</div>
<div class="glm-capture" id="glm-capture">📡 请求未捕获 — 请先点一次目标套餐购买按钮</div>
<div class="glm-param-row">
<label id="glm-label-mode">模式</label>
<select id="glm-mode"><option id="glm-mode-extreme" value="extreme">极限</option><option id="glm-mode-steady" value="steady">稳健</option><option id="glm-mode-custom" value="custom">自定义</option></select>
<label style="margin-left:8px" id="glm-label-plan">套餐</label>
<select id="glm-plan"><option value="Lite">Lite</option><option value="Pro">Pro</option><option value="Max">Max</option></select>
</div>
<div class="glm-param-row">
<label id="glm-label-cycle">周期</label>
<select id="glm-cycle"><option id="glm-cycle-month" value="month">包月</option><option id="glm-cycle-quarter" value="quarter">包季</option><option id="glm-cycle-year" value="year">包年</option></select>
<label style="margin-left:8px" id="glm-label-time">定时</label>
<input type="time" id="glm-time" value="${Config.targetTime}" step="1">
</div>
<div class="glm-param-row">
<label id="glm-label-early">提前</label>
<input type="number" id="glm-early" value="${Config.earlyStartSeconds}" min="1" max="120" step="1">
<span class="glm-param-unit">s</span>
<label style="margin-left:8px" id="glm-label-window">窗口</label>
<input type="number" id="glm-window" value="${Config.watchWindowSeconds}" min="30" max="600" step="5">
<span class="glm-param-unit">s</span>
</div>
<div class="glm-capture" id="glm-lock-note">极限模式已锁定推荐参数;切到自定义后可手动调节。</div>
<div class="glm-param-row" id="glm-manual-basic">
<label id="glm-label-delay">间隔</label>
<input type="number" id="glm-delay" value="${Config.retryDelay}" min="50" max="5000" step="50">
<span class="glm-param-unit">ms</span>
<label style="margin-left:8px" id="glm-label-max">上限</label>
<input type="number" id="glm-max" value="${Config.maxRetries}" min="10" max="9999" step="10">
<span class="glm-param-unit">次</span>
</div>
<div class="glm-param-row" id="glm-manual-advanced">
<label id="glm-label-burst">Burst</label>
<input type="number" id="glm-burst" value="${Config.burstCount}" min="1" max="200" step="1">
<span class="glm-param-unit">次</span>
<label style="margin-left:8px" id="glm-label-burst-delay">速点</label>
<input type="number" id="glm-burst-delay" value="${Config.burstDelay}" min="10" max="1000" step="5">
<span class="glm-param-unit">ms</span>
<label style="margin-left:8px" id="glm-label-backoff">退避</label>
<input type="number" id="glm-backoff" value="${Config.backoffDelay}" min="20" max="2000" step="10">
<span class="glm-param-unit">ms</span>
</div>
<div class="glm-capture" id="glm-timer-info">⌚ 默认每日 10:00:00 (UTC+8)</div>
<div class="glm-actions" id="glm-actions">
<button class="glm-btn-start" id="glm-rush-now">⚡ 立即抢购</button>
<button class="glm-btn-schedule" id="glm-schedule">⏰ 定时抢购</button>
<button class="glm-btn-stop" id="glm-stop" style="display:none">■ 停止</button>
</div>
<div class="glm-logs" id="glm-logs"></div>
</div>`;
document.body.appendChild(panel);
// ── 事件绑定 ──
const $ = (id) => document.getElementById(id);
$('glm-mode').value = Config.mode;
$('glm-plan').value = Config.targetPlan;
$('glm-cycle').value = Config.billingCycle;
$('glm-lang').onclick = function (event) {
event.stopPropagation();
Config.locale = Config.locale === 'zh' ? 'en' : 'zh';
saveCurrentConfig();
refreshStatusPanel();
};
$('glm-rush-now').onclick = async function () {
if (State.phase === 'retrying' || State.proactive) return;
ensureBillingCycleSelected();
if (State.captured) {
await startProactive();
return;
}
const triggered = await triggerBuyButton('手动启动');
if (!triggered) {
log('⚠️ 手动启动失败,请先点击目标套餐购买按钮');
alert(t('alert_need_capture'));
}
};
$('glm-schedule').onclick = function () {
const v = $('glm-time').value || Config.targetTime;
if (v) scheduleAt(v);
};
$('glm-stop').onclick = stopAll;
$('glm-mode').onchange = function () {
applyModePreset(this.value);
saveCurrentConfig();
syncPanelControls();
refreshStatusPanel();
};
$('glm-plan').onchange = function () {
Config.targetPlan = this.value;
preferredBuyButton = null;
saveCurrentConfig();
refreshStatusPanel();
};
$('glm-cycle').onchange = function () {
Config.billingCycle = this.value;
lastCycleSwitchAt = 0;
preferredBuyButton = null;
saveCurrentConfig();
refreshStatusPanel();
};
$('glm-time').onchange = function () {
Config.targetTime = this.value || '10:00:00';
saveCurrentConfig();
refreshStatusPanel();
};
$('glm-early').onchange = function () {
Config.earlyStartSeconds = Math.max(1, Math.min(120, +this.value || 8));
this.value = Config.earlyStartSeconds;
saveCurrentConfig();
refreshStatusPanel();
};
$('glm-window').onchange = function () {
Config.watchWindowSeconds = Math.max(30, Math.min(600, +this.value || 180));
this.value = Config.watchWindowSeconds;
saveCurrentConfig();
refreshStatusPanel();
};
$('glm-delay').onchange = function () {
Config.retryDelay = Math.max(20, +this.value || 100);
syncCustomTuningFromConfig();
saveCurrentConfig();
refreshStatusPanel();
};
$('glm-max').onchange = function () {
Config.maxRetries = Math.max(10, +this.value || 300);
syncCustomTuningFromConfig();
saveCurrentConfig();
refreshStatusPanel();
};
$('glm-burst').onchange = function () {
Config.burstCount = Math.max(1, +this.value || 6);
syncCustomTuningFromConfig();
saveCurrentConfig();
refreshStatusPanel();
};
$('glm-burst-delay').onchange = function () {
Config.burstDelay = Math.max(10, +this.value || 25);
syncCustomTuningFromConfig();
saveCurrentConfig();
refreshStatusPanel();
};
$('glm-backoff').onchange = function () {
Config.backoffDelay = Math.max(20, +this.value || 150);
syncCustomTuningFromConfig();
saveCurrentConfig();
refreshStatusPanel();
};
$('glm-min').onclick = function () {
const body = $('glm-body');
const hidden = body.style.display === 'none';
body.style.display = hidden ? '' : 'none';
State.panelMinimized = !hidden;
this.textContent = hidden ? '−' : '+';
refreshStatusPanel();
};
// ── 拖拽 ──
let startX, startY, startLeft, startTop;
$('glm-drag').onmousedown = function (e) {
if (e.target.closest('#glm-lang, #glm-min')) return;
startX = e.clientX;
startY = e.clientY;
const rect = panel.getBoundingClientRect();
startLeft = rect.left;
startTop = rect.top;
const onMove = (ev) => {
panel.style.left = (startLeft + ev.clientX - startX) + 'px';
panel.style.top = (startTop + ev.clientY - startY) + 'px';
panel.style.right = 'auto';
};
const onUp = () => {
document.removeEventListener('mousemove', onMove);
document.removeEventListener('mouseup', onUp);
};
document.addEventListener('mousemove', onMove);
document.addEventListener('mouseup', onUp);
};
loadSavedConfig();
syncPanelControls();
injectBadgeStyles();
log(`🚀 ${SCRIPT_NAME} v${VERSION} ${t('panel_loaded')}`);
log(t('log_welcome', { code: INVITE_CODE }));
startDialogWatcher();
refreshStatusPanel();
}
function syncPanelControls() {
const panelEl = document.getElementById('glm-rush');
const bodyEl = document.getElementById('glm-body');
const langEl = document.getElementById('glm-lang');
const modeEl = document.getElementById('glm-mode');
const planEl = document.getElementById('glm-plan');
const cycleEl = document.getElementById('glm-cycle');
const timeEl = document.getElementById('glm-time');
const earlyEl = document.getElementById('glm-early');
const windowEl = document.getElementById('glm-window');
const delayEl = document.getElementById('glm-delay');
const maxEl = document.getElementById('glm-max');
const burstEl = document.getElementById('glm-burst');
const burstDelayEl = document.getElementById('glm-burst-delay');
const backoffEl = document.getElementById('glm-backoff');
const lockNoteEl = document.getElementById('glm-lock-note');
const manualBasicEl = document.getElementById('glm-manual-basic');
const manualAdvancedEl = document.getElementById('glm-manual-advanced');
const tipEl = document.getElementById('glm-tip-copy');
const labelModeEl = document.getElementById('glm-label-mode');
const labelPlanEl = document.getElementById('glm-label-plan');
const labelCycleEl = document.getElementById('glm-label-cycle');
const labelTimeEl = document.getElementById('glm-label-time');
const labelEarlyEl = document.getElementById('glm-label-early');
const labelWindowEl = document.getElementById('glm-label-window');
const labelDelayEl = document.getElementById('glm-label-delay');
const labelMaxEl = document.getElementById('glm-label-max');
const labelBurstEl = document.getElementById('glm-label-burst');
const labelBurstDelayEl = document.getElementById('glm-label-burst-delay');
const labelBackoffEl = document.getElementById('glm-label-backoff');
const modeExtremeEl = document.getElementById('glm-mode-extreme');
const modeSteadyEl = document.getElementById('glm-mode-steady');
const modeCustomEl = document.getElementById('glm-mode-custom');
const cycleMonthEl = document.getElementById('glm-cycle-month');
const cycleQuarterEl = document.getElementById('glm-cycle-quarter');
const cycleYearEl = document.getElementById('glm-cycle-year');
const rushNowEl = document.getElementById('glm-rush-now');
const scheduleEl = document.getElementById('glm-schedule');
const stopEl = document.getElementById('glm-stop');
const minEl = document.getElementById('glm-min');
const manualVisible = Config.mode === 'custom';
if (panelEl) panelEl.dataset.lang = Config.locale;
if (bodyEl) bodyEl.dataset.minimized = State.panelMinimized ? 'true' : 'false';
if (langEl) {
langEl.textContent = t('header_lang');
langEl.title = t('header_lang_title');
}
if (minEl) minEl.title = t('header_min_title');
if (tipEl) {
tipEl.innerHTML = `💡 ${t('tip_recommended', {
mode : modeLabel(),
plan : currentTargetLabel(),
time : Config.targetTime,
early : Config.earlyStartSeconds,
window : Config.watchWindowSeconds,
})}`;
}
if (labelModeEl) labelModeEl.textContent = t('label_mode');
if (labelPlanEl) labelPlanEl.textContent = t('label_plan');
if (labelCycleEl) labelCycleEl.textContent = t('label_cycle');
if (labelTimeEl) labelTimeEl.textContent = t('label_time');
if (labelEarlyEl) labelEarlyEl.textContent = t('label_early');
if (labelWindowEl) labelWindowEl.textContent = t('label_window');
if (labelDelayEl) labelDelayEl.textContent = t('label_delay');
if (labelMaxEl) labelMaxEl.textContent = t('label_max');
if (labelBurstEl) labelBurstEl.textContent = t('label_burst');
if (labelBurstDelayEl) labelBurstDelayEl.textContent = t('label_burst_delay');
if (labelBackoffEl) labelBackoffEl.textContent = t('label_backoff');
if (modeExtremeEl) modeExtremeEl.textContent = modeShortLabel('extreme');
if (modeSteadyEl) modeSteadyEl.textContent = modeShortLabel('steady');
if (modeCustomEl) modeCustomEl.textContent = modeShortLabel('custom');
if (cycleMonthEl) cycleMonthEl.textContent = t(`cycle_short_month`);
if (cycleQuarterEl) cycleQuarterEl.textContent = t(`cycle_short_quarter`);
if (cycleYearEl) cycleYearEl.textContent = t(`cycle_short_year`);
if (rushNowEl) rushNowEl.textContent = `⚡ ${t('btn_rush_now')}`;
if (scheduleEl) scheduleEl.textContent = `⏰ ${t('btn_schedule')}`;
if (stopEl) stopEl.textContent = `■ ${t('btn_stop')}`;
if (modeEl) modeEl.value = Config.mode;
if (planEl) planEl.value = Config.targetPlan;
if (cycleEl) cycleEl.value = Config.billingCycle;
if (timeEl) timeEl.value = Config.targetTime;
if (earlyEl) earlyEl.value = Config.earlyStartSeconds;
if (windowEl) windowEl.value = Config.watchWindowSeconds;
if (delayEl) delayEl.value = Config.retryDelay;
if (maxEl) maxEl.value = Config.maxRetries;
if (burstEl) burstEl.value = Config.burstCount;
if (burstDelayEl) burstDelayEl.value = Config.burstDelay;
if (backoffEl) backoffEl.value = Config.backoffDelay;
if (lockNoteEl) {
lockNoteEl.textContent = Config.mode === 'custom'
? t('note_custom')
: t('note_locked', {
mode : modeLabel(),
delay : Config.retryDelay,
max : Config.maxRetries,
burst : Config.burstCount,
});
}
if (manualBasicEl) manualBasicEl.style.display = manualVisible ? '' : 'none';
if (manualAdvancedEl) manualAdvancedEl.style.display = manualVisible ? '' : 'none';
}
/* ────────────────────────────────────────────
* UI 刷新
* ──────────────────────────────────────────── */
/** 刷新状态栏 & 捕获信息 & 按钮可见性 */
function refreshStatusPanel() {
const statusEl = document.getElementById('glm-status');
if (!statusEl) return;
syncPanelControls();
statusEl.className = 'glm-status glm-status-' + State.phase;
statusEl.textContent =
State.phase === 'idle' ? `⏳ ${t('status_idle')}` :
State.phase === 'retrying' ? `🔄 ${t('status_retrying', { count: State.retryCount, max: Config.maxRetries })}` :
State.phase === 'success' ? `✅ ${t('status_success', { bizId: State.bizId })}` :
`❌ ${t('status_failed', { count: State.retryCount })}`;
const captureEl = document.getElementById('glm-capture');
if (captureEl) {
captureEl.textContent = State.captured
? `📡 ${t('capture_ready', { method: State.captured.method, target: currentTargetLabel(), tail: State.captured.url.split('?')[0].slice(-28) })}`
: `📡 ${t('capture_idle', { target: currentTargetLabel(), mode: modeLabel() })}`;
}
const timerEl = document.getElementById('glm-timer-info');
if (timerEl) {
if (State.scheduleTargetAt) {
const now = Date.now();
if (now < State.scheduleStartAt) {
timerEl.textContent = `⌚ ${t('timer_before_start', { time: Config.targetTime, countdown: formatCountdown(State.scheduleStartAt - now) })}`;
} else if (now < State.scheduleTargetAt) {
timerEl.textContent = `⚡ ${t('timer_before_open', { countdown: formatCountdown(State.scheduleTargetAt - now) })}`;
} else if (now < State.scheduleEndAt) {
timerEl.textContent = `🔥 ${t('timer_in_window', { countdown: formatCountdown(State.scheduleEndAt - now) })}`;
} else {
timerEl.textContent = `🛑 ${t('timer_ended')}`;
}
} else {
timerEl.textContent = `⌚ ${t('timer_default', { time: Config.targetTime, early: Config.earlyStartSeconds, window: Config.watchWindowSeconds })}`;
}
}
const rushBtn = document.getElementById('glm-rush-now');
const scheduleBtn = document.getElementById('glm-schedule');
const stopBtn = document.getElementById('glm-stop');
if (rushBtn && scheduleBtn && stopBtn) {
const busy = State.phase === 'retrying' || !!State.scheduleTargetAt;
rushBtn.style.display = busy ? 'none' : '';
scheduleBtn.style.display = busy ? 'none' : '';
stopBtn.style.display = busy ? '' : 'none';
}
}
/** 增量追加最新一条日志到面板 */
function refreshLogPanel() {
const el = document.getElementById('glm-logs');
if (!el) return;
while (el.childNodes.length >= State.logs.length && el.firstChild) {
el.removeChild(el.firstChild);
}
const latest = State.logs[State.logs.length - 1];
if (latest) {
const div = document.createElement('div');
div.textContent = latest;
el.appendChild(div);
el.scrollTop = el.scrollHeight;
}
}
/* ────────────────────────────────────────────
* 启动入口
* ──────────────────────────────────────────── */
if (document.body) {
createPanel();
} else {
document.addEventListener('DOMContentLoaded', createPanel);
}
})();
@unixzilla

Copy link
Copy Markdown

我懷疑每日只有十個名額放售。 根本搶不到,浪費時間。

@964097833

Copy link
Copy Markdown

用这个能抢到吗

@what0o0

what0o0 commented Jun 6, 2026

Copy link
Copy Markdown

现在验证码只能用一次,后面的请求preview接口都会提示tick已被使用

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment