Greasy Fork is available in English.
智能监控,支持配置多级备选抢购;自动穿透限流弹窗;默认使用拼团折扣码更优惠,介意误用
当前为
// ==UserScript==
// @name GLM Coding Plan抢购助手
// @namespace http://tampermonkey.net/
// @version 5.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,2,3",
PACKAGES_PRIORITY: "2,3,1",
CHECK_INTERVAL: 100,
SMART_REFRESH: true,
};
function loadConfig() {
const stored = GM_getValue(STORAGE_KEY, null);
if (stored) {
try { return { ...DEFAULT_CONFIG, ...JSON.parse(stored) }; } catch (e) {}
}
return { ...DEFAULT_CONFIG };
}
function saveConfig(config) { GM_setValue(STORAGE_KEY, JSON.stringify(config)); }
const CONFIG = loadConfig();
// ==================== 状态机与队列初始化 ====================
let state = 'SCANNING';
let qIdx = 0;
let sweepRestocks = [];
let mainTimer = null;
let notificationShown = false;
const tabs = String(CONFIG.TABS_PRIORITY).split(',').map(Number).filter(n => !isNaN(n));
const pkgs = String(CONFIG.PACKAGES_PRIORITY).split(',').map(Number).filter(n => !isNaN(n));
const scanQueue = [];
for (let t of tabs) {
for (let p of pkgs) {
scanQueue.push({ tab: t, pkg: p });
}
}
if (window.location.href.includes('rate-limit.html')) {
window.location = 'https://www.bigmodel.cn/glm-coding?ic=UMYTQHW8I2&closedialog=true';
return;
}
// ==================== 构建独立穿梭框组件 ====================
function buildTransferBox(containerElement, dataMap, selectedStr, title) {
const selectedArr = selectedStr.split(',').filter(x => x);
const allKeys = Object.keys(dataMap);
const availArr = allKeys.filter(k => !selectedArr.includes(k));
let html = `
<div style="font-size: 13px; font-weight: bold; margin-bottom: 8px; color: #444;">${title}</div>
<div class="tf-container" 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-list tf-left" style="list-style: none; padding: 5px; margin: 0; flex: 1; overflow-y: auto;">
${availArr.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-to-right">▶</button>
<button type="button" class="tf-btn tf-to-left">◀</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-list tf-right" style="list-style: none; padding: 5px; margin: 0; flex: 1; overflow-y: auto;">
${selectedArr.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-down">▼</button>
</div>
</div>
`;
containerElement.innerHTML = html;
// 事件绑定逻辑
const leftList = containerElement.querySelector('.tf-left');
const rightList = containerElement.querySelector('.tf-right');
// 选中高亮互斥
containerElement.querySelectorAll('.tf-list').forEach(list => {
list.addEventListener('click', (e) => {
if (e.target.tagName === 'LI') {
// 移除两侧所有的 active
containerElement.querySelectorAll('.tf-item').forEach(el => el.classList.remove('active'));
e.target.classList.add('active');
}
});
});
// 按钮功能
containerElement.querySelector('.tf-to-right').onclick = () => {
const active = leftList.querySelector('.active');
if (active) { rightList.appendChild(active); active.classList.remove('active'); }
};
containerElement.querySelector('.tf-to-left').onclick = () => {
const active = rightList.querySelector('.active');
if (active) { leftList.appendChild(active); active.classList.remove('active'); }
};
containerElement.querySelector('.tf-up').onclick = () => {
const active = rightList.querySelector('.active');
if (active && active.previousElementSibling) {
rightList.insertBefore(active, active.previousElementSibling);
}
};
containerElement.querySelector('.tf-down').onclick = () => {
const active = rightList.querySelector('.active');
if (active && active.nextElementSibling) {
rightList.insertBefore(active, active.nextElementSibling.nextElementSibling);
}
};
// 返回获取当前排序结果的方法
return function getResult() {
const items = rightList.querySelectorAll('.tf-item');
return Array.from(items).map(el => el.dataset.val).join(',');
};
}
// ==================== 配置管理界面 ====================
function openConfigPanel() {
const existing = document.getElementById('glm-config-panel');
if (existing) existing.remove();
// 注入组件 CSS
if (!document.getElementById('glm-tf-style')) {
const style = document.createElement('style');
style.id = 'glm-tf-style';
style.textContent = `
.tf-item { padding: 6px 10px; margin-bottom: 4px; border-radius: 4px; cursor: pointer; font-size: 13px; color: #333; transition: all 0.2s; border: 1px solid transparent; }
.tf-item:hover { background: #f5f5f5; }
.tf-item.active { background: #e6f7ff; border-color: #91d5ff; color: #1890ff; font-weight: bold; }
.tf-btn { padding: 4px 8px; font-size: 10px; cursor: pointer; border: 1px solid #d9d9d9; border-radius: 4px; background: #fff; color: #555; transition: 0.2s; display: flex; align-items: center; justify-content: center; height: 28px;}
.tf-btn:hover { border-color: #40a9ff; color: #40a9ff; }
.tf-btn:active { background: #f0f0f0; }
`;
document.head.appendChild(style);
}
const overlay = document.createElement('div');
overlay.id = 'glm-config-panel';
overlay.style.cssText = `
position: fixed; top: 0; left: 0; width: 100%; height: 100%;
background: rgba(0,0,0,0.6); z-index: 99999;
display: flex; align-items: center; justify-content: center;
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
backdrop-filter: blur(2px);
`;
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,0.3);
`;
panel.innerHTML = `
<h3 style="margin: 0 0 20px 0; font-size: 18px; color: #1a1a1a;">⚙️ 抢购助手配置</h3>
<div id="wrap-packages"></div>
<div id="wrap-tabs"></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="cfg-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="cfg-cancel" style="padding: 8px 16px; border: 1px solid #ddd; background: #f5f5f5; border-radius: 6px; cursor: pointer; color: #666;">取消</button>
<button id="cfg-save" style="padding: 8px 20px; border: none; background: #1890ff; color: white; border-radius: 6px; cursor: pointer; font-weight: bold; box-shadow: 0 2px 5px rgba(24,144,255,0.3);">保存并刷新</button>
</div>
`;
overlay.appendChild(panel);
document.body.appendChild(overlay);
// 初始化穿梭框并获取返回的取值函数
const getPkgPriority = buildTransferBox(document.getElementById('wrap-packages'), PKGS_MAP, CONFIG.PACKAGES_PRIORITY, "套餐优先级");
const getTabPriority = buildTransferBox(document.getElementById('wrap-tabs'), TABS_MAP, CONFIG.TABS_PRIORITY, "订阅周期优先级");
// 按钮事件
panel.querySelector('#cfg-cancel').onclick = () => overlay.remove();
panel.querySelector('#cfg-save').onclick = () => {
const newPkgs = getPkgPriority();
const newTabs = getTabPriority();
if (!newPkgs || !newTabs) {
alert("请至少在右侧选择一个套餐和一个周期!");
return;
}
saveConfig({
TABS_PRIORITY: newTabs,
PACKAGES_PRIORITY: newPkgs,
SMART_REFRESH: panel.querySelector('#cfg-smart').checked,
CHECK_INTERVAL: CONFIG.CHECK_INTERVAL
});
overlay.remove();
alert('配置已更新!脚本将基于新排列的优先级扫描队列。');
location.reload();
};
overlay.onclick = (e) => { if (e.target === overlay) overlay.remove(); };
}
GM_registerMenuCommand('⚙️ 打开配置面板 (高级排序)', openConfigPanel);
// ==================== 工具函数 ====================
function getTodayStr() {
const now = new Date();
return `${now.getMonth() + 1}月${now.getDate()}日`;
}
function parseRestockDateTime(text) {
const match = text.match(/0?(\d{1,2})月0?(\d{1,2})日\s*(\d{1,2}):0?(\d{1,2})/);
if (!match) return null;
const now = new Date();
const dateObj = new Date(now.getFullYear(), parseInt(match[1]) - 1, parseInt(match[2]), parseInt(match[3]), parseInt(match[4]), 0);
return {
dateStr: `${parseInt(match[1])}月${parseInt(match[2])}日`,
timeObj: dateObj,
msUntil: dateObj.getTime() - now.getTime()
};
}
function showAutoDismissNotification(message, duration = 5000) {
const existing = document.getElementById('glm-notification');
if (existing) existing.remove();
const notif = document.createElement('div');
notif.id = 'glm-notification';
notif.style.cssText = `
position: fixed; top: 20px; right: 20px; z-index: 99999;
background: linear-gradient(135deg, #1890ff 0%, #0050b3 100%);
color: white; padding: 16px 20px; border-radius: 8px;
box-shadow: 0 4px 20px rgba(0,0,0,0.3); max-width: 320px;
font-family: -apple-system, sans-serif; font-size: 14px; line-height: 1.5;
transition: all 0.3s;
`;
notif.innerHTML = `<div style="font-weight: 600; margin-bottom: 4px;">🤖 抢购助手运行中</div><div style="opacity: 0.95;">${message}</div>`;
document.body.appendChild(notif);
if (duration > 0) {
setTimeout(() => {
notif.style.opacity = '0';
notif.style.transform = 'translateY(-20px)';
setTimeout(() => notif.remove(), 300);
}, duration);
}
}
// ==================== 推广引导弹窗(多平台版) ====================
function triggerMiniMaxPromo() {
const endDate = new Date('2026-04-30T23:59:59').getTime();
if (Date.now() > endDate) {
showAutoDismissNotification('所有备选套餐今日均已售罄,脚本停止运行。', 0);
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 overlay = document.createElement('div');
overlay.style.cssText = `
position: fixed; top: 0; left: 0; width: 100%; height: 100%;
background: rgba(0,0,0,0.75); z-index: 999999;
display: flex; align-items: center; justify-content: center;
backdrop-filter: blur(5px);
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
`;
const card = document.createElement('div');
card.style.cssText = `
background: #fff; width: 520px; border-radius: 16px; overflow: hidden;
box-shadow: 0 25px 50px -12px rgba(0,0,0,0.5);
max-height: 90vh; display: flex; flex-direction: column;
`;
const providerRows = 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; transition: all 0.2s;
background: #fafafa;
"
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="
display:inline-block; width:8px; height:8px; border-radius:50%;
background:${p.color}; 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('');
card.innerHTML = `
<div style="background: linear-gradient(135deg, #1e3c72 0%, #2a5298 100%); padding: 24px 24px 20px; color: white; flex-shrink:0;">
<h2 style="margin: 0 0 6px 0; font-size: 20px;">GLM Coding Plan 全部售罄 🫠</h2>
<p style="margin: 0; opacity: 0.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;">
${providerRows}
</div>
</div>
<div style="padding: 14px 20px; border-top: 1px solid #f0f0f0; flex-shrink:0; text-align:right;">
<button id="promo-close" style="
background: none; border: 1px solid #ddd; color: #888;
padding: 7px 18px; border-radius: 6px; cursor: pointer; font-size: 13px;
">关闭并停止脚本</button>
</div>
`;
overlay.appendChild(card);
document.body.appendChild(overlay);
card.querySelector('#promo-close').onclick = () => overlay.remove();
overlay.onclick = (e) => { if (e.target === overlay) overlay.remove(); };
}
// ==================== 核心主循环 ====================
function mainLoop() {
if (state === 'WAITING' || state === 'DONE') return;
if (state === 'BUYING') {
const modalWrappers = document.querySelectorAll('.el-dialog__wrapper');
let foundRateLimit = false;
modalWrappers.forEach(wrap => {
if (window.getComputedStyle(wrap).display !== 'none' && wrap.innerText.includes('当前购买人数较多')) {
foundRateLimit = true;
const closeBtn = wrap.querySelector('.el-dialog__close');
if (closeBtn) closeBtn.click();
}
});
if (foundRateLimit) {
state = 'WAITING';
showAutoDismissNotification("⚠️ 触发API限流,正在自动重试...", 2000);
setTimeout(() => {
qIdx = 0;
state = 'SCANNING';
}, 1500);
}
return;
}
if (state === 'SCANNING') {
if (scanQueue.length === 0) return;
const target = scanQueue[qIdx];
const tabSelector = `#switchTabBox > div > div.switch-tab-item:nth-child(${target.tab + 1})`;
const tabEl = document.querySelector(tabSelector);
if (!tabEl) return;
if (!tabEl.classList.contains('active')) {
tabEl.click();
return;
}
const btnSelector = `#app > div.claude-code-box > div.claude-code-content > div.claude-code-package-box.media-container.module-container-common > div.package-list.glm-coding-package-list > div:nth-child(${target.pkg}) > div > div.package-card-btn-box > button`;
const btn = document.querySelector(btnSelector);
if (btn) {
const isDisabled = btn.disabled || btn.classList.contains('is-disabled');
const btnText = btn.innerText || '';
if (!isDisabled && btnText.includes('特惠订阅')) {
btn.click();
state = 'BUYING';
setTimeout(() => { if(state === 'BUYING') state = 'DONE'; }, 5000);
return;
}
const restockInfo = parseRestockDateTime(btnText);
if (restockInfo && restockInfo.dateStr === getTodayStr() && restockInfo.msUntil > 0) {
sweepRestocks.push(restockInfo);
}
}
qIdx++;
if (qIdx >= scanQueue.length) {
if (sweepRestocks.length > 0) {
sweepRestocks.sort((a, b) => a.msUntil - b.msUntil);
const nearest = sweepRestocks[0];
if (CONFIG.SMART_REFRESH) {
state = 'WAITING';
let waitMs = nearest.msUntil;
if (waitMs > 300000) waitMs = 300000;
else if (waitMs > 120000) waitMs = 120000;
else if (waitMs <= 10000) waitMs = 0;
const minutesUntil = Math.floor(nearest.msUntil / 60000);
const totalDesc = minutesUntil > 0
? `${minutesUntil}分${Math.floor((nearest.msUntil % 60000) / 1000)}秒`
: `${Math.floor(nearest.msUntil / 1000)}秒`;
if (!notificationShown) {
showAutoDismissNotification(`已扫描全部备选队列。<br>等待最近补货: <b>${totalDesc}</b> 后。<br>倒计时结束将自动刷新页面。`, 8000);
notificationShown = true;
}
setTimeout(() => {
window.location = 'https://www.bigmodel.cn/glm-coding?ic=UMYTQHW8I2&closedialog=true';
}, waitMs);
} else {
qIdx = 0;
sweepRestocks = [];
}
} else {
state = 'DONE';
triggerMiniMaxPromo();
}
}
}
}
mainTimer = setInterval(mainLoop, CONFIG.CHECK_INTERVAL);
})();