Greasy Fork

Greasy Fork is available in English.

GLM Coding Plan抢购助手

智能监控,支持配置多级备选抢购;自动穿透限流弹窗;默认使用拼团折扣码更优惠,介意误用

当前为 2026-04-03 提交的版本,查看 最新版本

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Greasemonkey 油猴子Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Userscripts ,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==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);
 
})();