Greasy Fork is available in English.
智能监控,支持配置多级备选抢购;自动穿透限流弹窗;默认使用拼团折扣码更优惠,介意误用
当前为
// ==UserScript==
// @name GLM Coding Plan抢购助手
// @namespace http://tampermonkey.net/
// @version 6.1
// @description 智能监控,支持配置多级备选抢购;自动穿透限流弹窗;默认使用拼团折扣码更优惠,介意误用
// @author mumumi
// @include https://*bigmodel.cn/glm-coding*
// @include https://*bigmodel.cn/html/rate-limit.html*
// @icon https://www.google.com/s2/favicons?sz=64&domain=bigmodel.cn
// @grant GM_setValue
// @grant GM_getValue
// @grant GM_registerMenuCommand
// @run-at document-start
// @license GNU GPLv3
// ==/UserScript==
(function () {
'use strict';
// ======================== 数据字典 ========================
const TABS_MAP = { 1: '连续包月', 2: '连续包季', 3: '连续包年' };
const PKGS_MAP = { 1: 'Lite', 2: 'Pro', 3: 'Max' };
const STORAGE_KEY = 'glm_coding_config_v5';
// ======================== 配置 ========================
const DEFAULT_CONFIG = {
TABS_PRIORITY: '1',
PACKAGES_PRIORITY: '2,3,1',
CHECK_INTERVAL: 100,
SMART_REFRESH: true,
};
function loadConfig() {
try {
const s = GM_getValue(STORAGE_KEY, null);
return s ? { ...DEFAULT_CONFIG, ...JSON.parse(s) } : { ...DEFAULT_CONFIG };
} catch { return { ...DEFAULT_CONFIG }; }
}
function saveConfig(cfg) { GM_setValue(STORAGE_KEY, JSON.stringify(cfg)); }
const CONFIG = loadConfig();
// ======================== 限流页跳回 ========================
if (location.href.includes('rate-limit.html')) {
location.replace(RETURN_URL);
return;
}
// ======================== 扫描队列构建 ========================
// 按用户配置:先遍历每个 tab,在 tab 内按套餐优先级扫描
const tabs = String(CONFIG.TABS_PRIORITY).split(',').map(Number).filter(Boolean);
const pkgs = String(CONFIG.PACKAGES_PRIORITY).split(',').map(Number).filter(Boolean);
const scanQueue = tabs.flatMap(t => pkgs.map(p => ({ tab: t, pkg: p })));
// ======================== 状态机变量 ========================
// 主状态: SCANNING | TASK_UNIT | SLEEPING | DONE
let state = 'SCANNING';
// SCANNING
let qIdx = 0;
let sweepRestocks = []; // 本轮扫到的补货时间
let lastTabSwitch = 0; // 上次切 tab 的时间戳,用于等 Vue 重绘
// TASK_UNIT
let taskTarget = null; // { tab, pkg }
let taskPhase = 'IDLE'; // IDLE | WAITING
let taskClickTime = 0;
let taskRLCount = 0; // 本任务单元内连续限流次数
const MAX_RL = 3; // 连续限流几次后刷新页面
const MODAL_TIMEOUT = 5000; // 点击后等待弹窗的超时时间(ms)
// SLEEPING
let sleepUntil = 0;
// ======================== 工具函数 ========================
function parseRestock(text) {
const m = (text || '').match(/0?(\d{1,2})月0?(\d{1,2})日\s*(\d{1,2}):0?(\d{1,2})/);
if (!m) return null;
const now = new Date();
const t = new Date(now.getFullYear(), +m[1] - 1, +m[2], +m[3], +m[4]);
const dateStr = `${+m[1]}月${+m[2]}日`;
return { dateStr, msUntil: t - now };
}
function todayStr() {
const d = new Date();
return `${d.getMonth() + 1}月${d.getDate()}日`;
}
function fmt(ms) {
if (ms <= 0) return '0秒';
const s = Math.floor(ms / 1000), m = Math.floor(s / 60), h = Math.floor(m / 60);
if (h > 0) return `${h}h${m % 60}m`;
if (m > 0) return `${m}分${s % 60}秒`;
return `${s}秒`;
}
/**
* 梯度休眠时长计算(距补货时间 → 本次页面刷新间隔)
*
* > 60min → 4min (防浏览器休眠选 4min 而非更长)
* > 30min → 3min
* > 15min → 2min
* > 5min → 1min
* > 2min → 30s
* > 1min → 10s
* > 10s → 3s (极短刷新,精确卡点)
* ≤ 10s → 0 (不刷新,原地高频轮询)
*/
function calcSleepMs(msUntil) {
if (msUntil > 3600000) return 240000;
if (msUntil > 1800000) return 180000;
if (msUntil > 900000) return 120000;
if (msUntil > 300000) return 60000;
if (msUntil > 120000) return 30000;
if (msUntil > 60000) return 10000;
if (msUntil > 10000) return 3000;
return 0;
}
// ======================== DOM 访问 ========================
const tabEl = (n) =>
document.querySelector(`#switchTabBox > div > .switch-tab-item:nth-child(${n + 1})`);
const btnEl = (n) =>
document.querySelector(`.glm-coding-package-list > div:nth-child(${n}) > div > .package-card-btn-box > button`);
// 按钮是否可以立即购买
const canBuy = (b) =>
b && !b.disabled && !b.classList.contains('is-disabled') && (b.innerText || '').includes('特惠订阅');
// 按钮是否明确显示售罄/补货
const isSoldOut = (b) =>
/售罄|补货|暂时/.test((b?.innerText) || '');
// ======================== 弹窗检测 ========================
function findRLModal() {
for (const w of document.querySelectorAll('.el-dialog__wrapper')) {
if (getComputedStyle(w).display !== 'none' &&
(w.innerText || '').includes('当前购买人数较多')) {
return w;
}
}
return null;
}
function isSuccessDialog() {
const d = document.querySelector('.pay-success-dialog-box');
if (!d) return false;
const w = d.closest('.el-dialog__wrapper');
return w ? getComputedStyle(w).display !== 'none' : false;
}
function closeModal(w) { w?.querySelector('.el-dialog__close')?.click(); }
// ======================== 底部状态栏 ========================
let _bar = null;
function setBar(html, bg = '#1677ff') {
if (!_bar) {
_bar = document.createElement('div');
_bar.id = 'glm-status-bar';
_bar.style.cssText = `
position:fixed;bottom:0;left:0;right:0;z-index:2147483647;
padding:7px 16px;font:13px/1.5 system-ui,sans-serif;color:#fff;
display:flex;align-items:center;justify-content:space-between;
box-shadow:0 -2px 8px rgba(0,0,0,.25);transition:background .4s;`;
const close = document.createElement('button');
close.textContent = '×';
close.style.cssText = `background:rgba(255,255,255,.2);border:none;color:#fff;
width:22px;height:22px;border-radius:4px;cursor:pointer;font-size:16px;
line-height:1;flex-shrink:0;`;
close.onclick = () => { _bar.remove(); _bar = null; };
const msg = document.createElement('span');
_bar.appendChild(msg);
_bar.appendChild(close);
document.body.appendChild(_bar);
}
_bar.style.background = bg;
_bar.firstElementChild.innerHTML = `🤖 <b>抢购助手</b> | ${html}`;
}
// ======================== 推广弹窗(多平台版) ========================
function triggerPromo() {
if (Date.now() > new Date('2026-04-30T23:59:59').getTime()) {
setBar('所有套餐今日售罄,脚本停止。', '#434343');
return;
}
const PROVIDERS = [
{ name: 'MiniMax', desc: '¥29起,赠视频/语音/音乐额度', color: '#4CAF50', url: 'https://platform.minimaxi.com/subscribe/token-plan?code=FoGlERWIF3&source=link' },
{ name: '字节·方舟', desc: '首月低至¥8.9,多模型切换', color: '#FF6B35', url: 'https://volcengine.com/L/YIeVPueJ2O4/' },
{ name: '讯飞星辰', desc: '¥19起,首月¥3.9最低门槛', color: '#00BFFF', url: 'https://maas.xfyun.cn/packageSubscription?inviteCode=MAAS-C6BE3A3B' },
{ name: '无问芯穹', desc: '多模型聚合,首月¥19.9', color: '#7B68EE', url: 'https://cloud.infini-ai.com/login?redirect=/genstudio/invitation&invite_code=IyveoKRS' },
];
const rows = PROVIDERS.map(p => `
<a href="${p.url}" target="_blank" style="display:flex;align-items:center;
justify-content:space-between;padding:10px 14px;border-radius:8px;
text-decoration:none;border:1px solid #eee;background:#fafafa;transition:all .2s;"
onmouseover="this.style.background='#f0f7ff';this.style.borderColor='#91d5ff';"
onmouseout="this.style.background='#fafafa';this.style.borderColor='#eee';">
<div style="display:flex;align-items:center;gap:10px;">
<span style="width:8px;height:8px;border-radius:50%;background:${p.color};
display:inline-block;flex-shrink:0;"></span>
<span style="font-weight:600;font-size:14px;color:#111;">${p.name}</span>
<span style="font-size:12px;color:#888;">${p.desc}</span>
</div>
<span style="font-size:12px;color:${p.color};font-weight:500;flex-shrink:0;">立即开通 →</span>
</a>`).join('');
const ov = document.createElement('div');
ov.style.cssText = `position:fixed;inset:0;background:rgba(0,0,0,.75);z-index:2147483646;
display:flex;align-items:center;justify-content:center;
backdrop-filter:blur(5px);font-family:system-ui,sans-serif;`;
ov.innerHTML = `
<div style="background:#fff;width:520px;border-radius:16px;overflow:hidden;
box-shadow:0 25px 50px -12px rgba(0,0,0,.5);max-height:90vh;
display:flex;flex-direction:column;">
<div style="background:linear-gradient(135deg,#1e3c72,#2a5298);
padding:24px 24px 20px;color:#fff;flex-shrink:0;">
<h2 style="margin:0 0 6px;font-size:20px;">GLM Coding Plan 全部售罄 🫠</h2>
<p style="margin:0;opacity:.85;font-size:14px;">配置的所有套餐今日已售罄,补货后脚本将继续监控</p>
</div>
<div style="padding:16px 20px;overflow-y:auto;flex:1;">
<div style="font-size:13px;color:#888;margin-bottom:12px;">
👇 以下平台也有编程套餐和折扣链接,点击直达</div>
<div style="display:flex;flex-direction:column;gap:8px;">${rows}</div>
</div>
<div style="padding:14px 20px;border-top:1px solid #f0f0f0;flex-shrink:0;text-align:right;">
<button id="promo-x" style="background:none;border:1px solid #ddd;color:#888;
padding:7px 18px;border-radius:6px;cursor:pointer;font-size:13px;">
关闭并停止脚本</button>
</div>
</div>`;
document.body.appendChild(ov);
ov.querySelector('#promo-x').onclick = () => ov.remove();
ov.onclick = (e) => { if (e.target === ov) ov.remove(); };
}
// ======================== 配置面板 ========================
function buildTransferBox(container, dataMap, selectedStr, title) {
const sel = selectedStr.split(',').filter(Boolean);
const avail = Object.keys(dataMap).filter(k => !sel.includes(k));
container.innerHTML = `
<div style="font-size:13px;font-weight:bold;margin-bottom:8px;color:#444;">${title}</div>
<div style="display:flex;align-items:stretch;gap:10px;margin-bottom:20px;height:140px;">
<div style="flex:1;border:1px solid #ddd;border-radius:6px;display:flex;
flex-direction:column;background:#fafafa;">
<div style="padding:6px 10px;border-bottom:1px solid #ddd;font-size:12px;
color:#666;background:#f0f0f0;border-radius:6px 6px 0 0;">备选池</div>
<ul class="tf-left" style="list-style:none;padding:5px;margin:0;flex:1;overflow-y:auto;">
${avail.map(k => `<li data-val="${k}" class="tf-item">${dataMap[k]}</li>`).join('')}
</ul>
</div>
<div style="display:flex;flex-direction:column;justify-content:center;gap:8px;">
<button type="button" class="tf-btn tf-r">▶</button>
<button type="button" class="tf-btn tf-l">◀</button>
</div>
<div style="flex:1;border:1px solid #ddd;border-radius:6px;display:flex;
flex-direction:column;background:#fff;">
<div style="padding:6px 10px;border-bottom:1px solid #ddd;font-size:12px;
color:#666;background:#e6f7ff;border-radius:6px 6px 0 0;">选中且排序(自上而下)</div>
<ul class="tf-right" style="list-style:none;padding:5px;margin:0;flex:1;overflow-y:auto;">
${sel.map(k => `<li data-val="${k}" class="tf-item">${dataMap[k]}</li>`).join('')}
</ul>
</div>
<div style="display:flex;flex-direction:column;justify-content:center;gap:8px;">
<button type="button" class="tf-btn tf-up">▲</button>
<button type="button" class="tf-btn tf-dn">▼</button>
</div>
</div>`;
const L = container.querySelector('.tf-left');
const R = container.querySelector('.tf-right');
container.querySelectorAll('ul').forEach(ul => ul.addEventListener('click', e => {
if (e.target.tagName === 'LI') {
container.querySelectorAll('.tf-item').forEach(i => i.classList.remove('active'));
e.target.classList.add('active');
}
}));
container.querySelector('.tf-r').onclick = () => {
const a = L.querySelector('.active');
if (a) { R.appendChild(a); a.classList.remove('active'); }
};
container.querySelector('.tf-l').onclick = () => {
const a = R.querySelector('.active');
if (a) { L.appendChild(a); a.classList.remove('active'); }
};
container.querySelector('.tf-up').onclick = () => {
const a = R.querySelector('.active');
if (a?.previousElementSibling) R.insertBefore(a, a.previousElementSibling);
};
container.querySelector('.tf-dn').onclick = () => {
const a = R.querySelector('.active');
if (a?.nextElementSibling) R.insertBefore(a.nextElementSibling, a);
};
return () => [...R.querySelectorAll('.tf-item')].map(i => i.dataset.val).join(',');
}
function openConfigPanel() {
document.getElementById('glm-cfg-overlay')?.remove();
if (!document.getElementById('glm-tf-style')) {
const s = document.createElement('style');
s.id = 'glm-tf-style';
s.textContent = `
.tf-item{padding:6px 10px;margin-bottom:4px;border-radius:4px;cursor:pointer;
font-size:13px;color:#333;border:1px solid transparent;transition:all .15s}
.tf-item:hover{background:#f5f5f5}
.tf-item.active{background:#e6f7ff;border-color:#91d5ff;color:#1890ff;font-weight:700}
.tf-btn{padding:4px 8px;font-size:10px;cursor:pointer;border:1px solid #d9d9d9;
border-radius:4px;background:#fff;color:#555;height:28px;transition:.2s}
.tf-btn:hover{border-color:#40a9ff;color:#40a9ff}`;
document.head.appendChild(s);
}
const ov = document.createElement('div');
ov.id = 'glm-cfg-overlay';
ov.style.cssText = `position:fixed;inset:0;background:rgba(0,0,0,.6);z-index:2147483646;
display:flex;align-items:center;justify-content:center;
backdrop-filter:blur(2px);font-family:system-ui,sans-serif;`;
const panel = document.createElement('div');
panel.style.cssText = `background:#fff;color:#333;width:540px;padding:24px;
border-radius:12px;box-shadow:0 20px 60px rgba(0,0,0,.3);`;
panel.innerHTML = `
<h3 style="margin:0 0 20px;font-size:18px;color:#1a1a1a;">⚙️ 抢购助手配置</h3>
<div id="glm-wrap-pkg"></div>
<div id="glm-wrap-tab"></div>
<div style="margin-bottom:20px;padding-top:10px;border-top:1px dashed #eee;">
<label style="display:flex;align-items:center;cursor:pointer;">
<input type="checkbox" id="glm-smart" ${CONFIG.SMART_REFRESH ? 'checked' : ''}
style="margin-right:8px;">
<span style="font-size:14px;color:#555;">
启用智能刷新(梯度嗅探补货时间,防止黑IP)</span>
</label>
</div>
<div style="display:flex;justify-content:flex-end;gap:10px;">
<button id="glm-cancel" style="padding:8px 16px;border:1px solid #ddd;
background:#f5f5f5;border-radius:6px;cursor:pointer;color:#666;">取消</button>
<button id="glm-save" style="padding:8px 20px;border:none;background:#1890ff;
color:#fff;border-radius:6px;cursor:pointer;font-weight:700;
box-shadow:0 2px 5px rgba(24,144,255,.3);">保存并刷新</button>
</div>`;
ov.appendChild(panel);
document.body.appendChild(ov);
const getPkgs = buildTransferBox(document.getElementById('glm-wrap-pkg'), PKGS_MAP, CONFIG.PACKAGES_PRIORITY, '套餐优先级');
const getTabs = buildTransferBox(document.getElementById('glm-wrap-tab'), TABS_MAP, CONFIG.TABS_PRIORITY, '订阅周期优先级');
panel.querySelector('#glm-cancel').onclick = () => ov.remove();
panel.querySelector('#glm-save').onclick = () => {
const p = getPkgs(), t = getTabs();
if (!p || !t) { alert('请至少各选一个套餐和一个周期!'); return; }
saveConfig({
TABS_PRIORITY: t,
PACKAGES_PRIORITY: p,
SMART_REFRESH: panel.querySelector('#glm-smart').checked,
CHECK_INTERVAL: CONFIG.CHECK_INTERVAL,
});
ov.remove();
alert('配置已更新!脚本将基于新优先级重新扫描。');
location.reload();
};
ov.onclick = (e) => { if (e.target === ov) ov.remove(); };
}
GM_registerMenuCommand('⚙️ 打开配置面板(高级排序)', openConfigPanel);
// ======================== 主循环 ========================
function tick() {
if (state === 'DONE') return;
if (state === 'SLEEPING') {
const rem = sleepUntil - Date.now();
if (rem <= 0) {
location.replace('https://www.bigmodel.cn/glm-coding?ic=UMYTQHW8I2&closedialog=true');
} else {
setBar(`💤 休眠中,<b>${fmt(rem)}</b> 后刷新页面`, '#434343');
}
return;
}
if (state === 'TASK_UNIT') { doTaskUnit(); return; }
if (state === 'SCANNING') { doScan(); }
}
// ──────────────────── SCANNING ────────────────────
function doScan() {
if (qIdx >= scanQueue.length) {
onSweepDone();
return;
}
const item = scanQueue[qIdx];
// 确保目标 tab 激活
const te = tabEl(item.tab);
if (!te) return; // DOM 未就绪
if (!te.classList.contains('active')) {
te.click();
lastTabSwitch = Date.now();
te.scrollIntoView({ behavior: 'auto', block: 'center' });
setBar(`🔄 切换到 ${TABS_MAP[item.tab]}...`);
return;
}
// 等 Vue 重绘(切 tab 后至少 400ms)
if (Date.now() - lastTabSwitch < 400) return;
const b = btnEl(item.pkg);
if (canBuy(b)) {
// 找到可购买按钮 → 进入任务单元
taskTarget = { ...item };
taskPhase = 'IDLE';
taskRLCount = 0;
state = 'TASK_UNIT';
setBar(`🎯 发现可购!${TABS_MAP[item.tab]} · ${PKGS_MAP[item.pkg]},即将点击...`, '#389e0d');
return;
}
// 收集今日补货时间
const ri = b && parseRestock(b.innerText);
if (ri && ri.dateStr === todayStr() && ri.msUntil > 0) {
sweepRestocks.push(ri);
}
setBar(`🔍 扫描 ${TABS_MAP[item.tab]} · ${PKGS_MAP[item.pkg]} (${qIdx + 1}/${scanQueue.length})`);
qIdx++;
}
function onSweepDone() {
if (!sweepRestocks.length) {
// 今天全部套餐无货且无补货预告 → 显示推广
state = 'DONE';
setBar('📭 今日全部售罄,脚本停止。', '#434343');
triggerPromo();
return;
}
sweepRestocks.sort((a, b) => a.msUntil - b.msUntil);
const nearest = sweepRestocks[0];
const sleep = calcSleepMs(nearest.msUntil);
if (sleep === 0) {
// ≤10s,不刷新,原地高频轮询
setBar(`⚡ 补货倒计时 <b>${fmt(nearest.msUntil)}</b>,高频监控中!`, '#d4380d');
qIdx = 0;
sweepRestocks = [];
return;
}
if (CONFIG.SMART_REFRESH) {
state = 'SLEEPING';
sleepUntil = Date.now() + sleep;
setBar(
`💤 补货还需 <b>${fmt(nearest.msUntil)}</b>,` +
`<b>${fmt(sleep)}</b> 后刷新页面`, '#434343'
);
} else {
// 关闭智能刷新时持续轮询
qIdx = 0;
sweepRestocks = [];
}
}
// ──────────────────── TASK_UNIT ────────────────────
function doTaskUnit() {
const { tab, pkg } = taskTarget;
// 确保正确 tab 处于激活状态
const te = tabEl(tab);
if (!te) return;
if (!te.classList.contains('active')) { te.click(); return; }
const b = btnEl(pkg);
// ── IDLE:尝试点击 ──
if (taskPhase === 'IDLE') {
if (!canBuy(b)) {
// 按钮不可用(防抖结束后变售罄,或被其他人抢走)
exitTask();
return;
}
b.click();
taskClickTime = Date.now();
taskPhase = 'WAITING';
setBar(
`⏳ 已点击 ${TABS_MAP[tab]} · ${PKGS_MAP[pkg]},等待弹窗...` +
`(限流 ${taskRLCount}/${MAX_RL})`, '#d46b08'
);
return;
}
// ── WAITING:等待弹窗 ──
if (taskPhase === 'WAITING') {
// 1. 检测限流弹窗
const rlw = findRLModal();
if (rlw) {
closeModal(rlw);
taskRLCount++;
if (taskRLCount >= MAX_RL) {
setBar(`🔁 连续 ${MAX_RL} 次限流,即将刷新页面...`, '#cf1322');
setTimeout(() => location.replace('https://www.bigmodel.cn/glm-coding?ic=UMYTQHW8I2&closedialog=true'), 50);
return;
}
setBar(
`⚠️ 限流 ${taskRLCount}/${MAX_RL},弹窗已关闭,稍后重试...`,
'#d46b08'
);
taskPhase = 'IDLE'; // 回到 IDLE 重新点击
return;
}
// 2. 检测购买成功弹窗
if (isSuccessDialog()) {
state = 'DONE';
setBar('🎉 订阅成功!恭喜!', '#237804');
return;
}
// 3. 超时处理
if (Date.now() - taskClickTime > MODAL_TIMEOUT) {
if (isSoldOut(b)) {
// 按钮已变售罄(被人抢走了)
exitTask();
} else {
// 按钮仍在(可能是防抖期结束,按钮恢复可点),重试
taskPhase = 'IDLE';
}
}
}
}
/**
* 退出任务单元:当前套餐售罄,向后推进 qIdx,继续扫描
* (同 tab 内的其他套餐自然排在队列后面,会继续检查)
*/
function exitTask() {
setBar(
`📦 ${TABS_MAP[taskTarget.tab]} · ${PKGS_MAP[taskTarget.pkg]} 售罄,继续扫下一个...`
);
qIdx++; // 跳过当前已售罄的项
taskTarget = null;
taskPhase = 'IDLE';
taskRLCount = 0;
state = 'SCANNING';
}
// ======================== 启动 ========================
setInterval(tick, CONFIG.CHECK_INTERVAL);
})();