// ==UserScript== // @name GLM Coding 抢购助手 (简化版) // @namespace http://tampermonkey.net/ // @version 1.2 // @description 准点自动点击指定套餐按钮,仅点击不处理后续流程。 // @author Codex // @match *://bigmodel.cn/glm-coding* // @match *://bigmodel.cn/usercenter/glm-coding* // @grant none // @run-at document-start // ==/UserScript== (function () { 'use strict'; if (window.__autoGlmSimpleInitialized) return; window.__autoGlmSimpleInitialized = true; // ========================================== // 网络拦截 - 绕过UI限制 // ========================================== const originalJSONParse = JSON.parse; JSON.parse = function (text, reviver) { let result = originalJSONParse(text, reviver); function deepModify(obj) { if (!obj || typeof obj !== 'object') return; if (obj.isSoldOut === true) obj.isSoldOut = false; if (obj.soldOut === true) obj.soldOut = false; if (obj.disabled === true && (obj.price !== undefined || obj.productId || obj.title)) { obj.disabled = false; } if (obj.stock === 0) obj.stock = 999; for (let key in obj) { if (obj[key] && typeof obj[key] === 'object') { deepModify(obj[key]); } } } try { deepModify(result); } catch (e) { console.log('[Auto-GLM-Simple] JSON拦截异常:', e.message); } return result; }; const originalFetch = window.fetch; window.fetch = async function (...args) { const response = await originalFetch.apply(this, args); const contentType = response.headers.get('content-type') || ''; if (contentType.includes('application/json')) { const clone = response.clone(); try { let text = await clone.text(); if (text.includes('"isSoldOut":true') || text.includes('"disabled":true') || text.includes('"soldOut":true')) { console.log('[Auto-GLM-Simple] 拦截Fetch售罄数据:', args[0]); text = 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 new Response(text, { status: response.status, statusText: response.statusText, headers: response.headers }); } } catch (e) { console.log('[Auto-GLM-Simple] Fetch拦截异常:', e.message); } } return response; }; const originalXHROpen = XMLHttpRequest.prototype.open; const originalXHRSend = XMLHttpRequest.prototype.send; XMLHttpRequest.prototype.open = function (method, url, ...rest) { this._reqUrl = url; return originalXHROpen.call(this, method, url, ...rest); }; XMLHttpRequest.prototype.send = function (...args) { this.addEventListener('readystatechange', function () { if (this.readyState === 4 && this.status === 200) { const contentType = this.getResponseHeader('content-type') || ''; if (contentType.includes('application/json')) { try { let text = this.responseText; if (text.includes('"isSoldOut":true') || text.includes('"disabled":true') || text.includes('"soldOut":true')) { console.log('[Auto-GLM-Simple] 拦截XHR售罄数据:', this._reqUrl); text = 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'); Object.defineProperty(this, 'responseText', { get: function () { return text; } }); Object.defineProperty(this, 'response', { get: function () { return JSON.parse(text); } }); } } catch (e) { console.log('[Auto-GLM-Simple] XHR拦截异常:', e.message); } } } }); originalXHRSend.apply(this, args); }; console.log('[Auto-GLM-Simple] 网络拦截器已注册'); // ========================================== // 简化配置 // ========================================== const STORAGE_KEY = 'glm-simple-config'; const WATCH_GRACE_MS = 5 * 60 * 1000; const CYCLE_SETTLE_MS = 350; const SECOND_CLICK_DELAY_MS = 90; const DIALOG_RETRY_BASE_DELAY_MS = 500; const DIALOG_RETRY_RANDOM_MS = 500; const PRODUCT_MAP = { Lite: { month: 'product-02434c', quarter: 'product-b8ea38', year: 'product-70a804' }, Pro: { month: 'product-1df3e1', quarter: 'product-fef82f', year: 'product-5643e6' }, Max: { month: 'product-2fc421', quarter: 'product-5d3a03', year: 'product-d46f8b' } }; const CYCLE_LABELS = { month: '连续包月', quarter: '连续包季', year: '连续包年' }; const DEFAULT_CONFIG = { targetPlan: 'Pro', billingCycle: 'quarter', targetHour: 10, targetMinute: 0, targetSecond: 0, autoStart: false }; let config = loadConfig(); let tickTimer = null; let isWatching = false; let hasClicked = false; let isClicking = false; let targetTimestamp = 0; let lastCycleSwitchAt = 0; let lastStatusText = ''; function clampNumber(value, min, max, fallback) { const next = Number(value); if (!Number.isFinite(next)) return fallback; return Math.min(max, Math.max(min, Math.floor(next))); } function sanitizeConfig(raw = {}) { return { targetPlan: PRODUCT_MAP[raw.targetPlan] ? raw.targetPlan : DEFAULT_CONFIG.targetPlan, billingCycle: CYCLE_LABELS[raw.billingCycle] ? raw.billingCycle : DEFAULT_CONFIG.billingCycle, targetHour: clampNumber(raw.targetHour, 0, 23, DEFAULT_CONFIG.targetHour), targetMinute: clampNumber(raw.targetMinute, 0, 59, DEFAULT_CONFIG.targetMinute), targetSecond: clampNumber(raw.targetSecond, 0, 59, DEFAULT_CONFIG.targetSecond), autoStart: Boolean(raw.autoStart) }; } function loadConfig() { try { const raw = localStorage.getItem(STORAGE_KEY); if (!raw) return { ...DEFAULT_CONFIG }; return { ...DEFAULT_CONFIG, ...sanitizeConfig(JSON.parse(raw)) }; } catch { return { ...DEFAULT_CONFIG }; } } function saveConfig() { try { localStorage.setItem(STORAGE_KEY, JSON.stringify(config)); } catch (e) { console.warn('[Auto-GLM-Simple] 保存配置失败:', e); } } function escapeHtml(text) { return String(text) .replaceAll('&', '&') .replaceAll('<', '<') .replaceAll('>', '>') .replaceAll('"', '"') .replaceAll("'", '''); } function log(msg) { console.log(`[Auto-GLM-Simple] ${msg}`); const logBox = document.getElementById('glm-simple-log'); if (logBox) { const time = new Date().toLocaleTimeString(); logBox.innerHTML = `
[${time}] ${escapeHtml(msg)}
` + logBox.innerHTML; if (logBox.children.length > 50) logBox.lastElementChild.remove(); } } function updateStatus(text) { if (text === lastStatusText) return; lastStatusText = text; const statusEl = document.getElementById('glm-simple-status'); if (statusEl) statusEl.textContent = text; } function normalizeText(text) { return String(text || '').replace(/\s+/g, '').trim(); } function getTargetDate(now = new Date()) { return new Date( now.getFullYear(), now.getMonth(), now.getDate(), config.targetHour, config.targetMinute, config.targetSecond || 0, 0 ); } function refreshTargetTimestamp() { targetTimestamp = getTargetDate().getTime(); } function sleep(ms) { return new Promise((resolve) => window.setTimeout(resolve, ms)); } function isVisibleElement(node) { if (!node || !node.isConnected) return false; const rect = node.getBoundingClientRect(); return rect.width > 0 && rect.height > 0; } // ========================================== // 核心逻辑:准点点击 // ========================================== function findCycleTab(cycle) { const label = CYCLE_LABELS[cycle]; if (!label) return null; return Array.from(document.querySelectorAll('.switch-tab-item')) .find((node) => normalizeText(node.textContent).includes(normalizeText(label))) || null; } function ensureBillingCycleSelected() { const tab = findCycleTab(config.billingCycle); if (!tab) return false; if (tab.classList.contains('active')) return true; if (Date.now() - lastCycleSwitchAt < CYCLE_SETTLE_MS) return false; lastCycleSwitchAt = Date.now(); dispatchRealClick(tab.querySelector('.switch-tab-item-content') || tab); return false; } function findPlanCard(planName) { return Array.from(document.querySelectorAll('.package-card-box .package-card')) .filter(isVisibleElement) .find((card) => { const title = card.querySelector('.package-card-title .font-prompt'); return title && normalizeText(title.textContent) === normalizeText(planName); }) || null; } function findBuyButton(card) { if (!card) return null; return Array.from(card.querySelectorAll('button.buy-btn, .package-card-btn-box button')) .find(isVisibleElement) || null; } 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 findPayDialogRoot() { return document.querySelector('.white-mask-bg .pay-dialog, .white-mask-bg .scan-code-box, .confirm-pay-btn, .scan-qrcode-box'); } function isRealPayDialog(root) { if (!root) return false; const priceEl = root.querySelector('.scan-qrcode-box .price-icon + span'); if (priceEl) { const priceText = priceEl.textContent?.trim(); return Boolean(priceText && priceText.length > 0); } return Boolean(root.querySelector('.confirm-pay-btn')); } function findFailureDialog() { const busyDialog = document.querySelector('.el-dialog__wrapper .empty-data-wrap'); if (busyDialog?.textContent?.includes('购买人数较多')) { return { type: 'busy', closeBtn: busyDialog.closest('.el-dialog')?.querySelector('.el-dialog__headerbtn') }; } const payRoot = document.querySelector('.white-mask-bg .pay-dialog, .white-mask-bg .scan-code-box'); if (payRoot && !isRealPayDialog(payRoot)) { return { type: 'empty-price', closeBtn: payRoot.querySelector('.el-dialog__headerbtn, .confirm-pay-btn') }; } return null; } function dispatchMouseLikeEvent(target, type, init) { const EventCtor = type.startsWith('pointer') && typeof PointerEvent === 'function' ? PointerEvent : MouseEvent; target.dispatchEvent(new EventCtor(type, init)); } function dispatchRealClick(target) { if (!target || !target.isConnected) return false; try { target.scrollIntoView({ block: 'center', inline: 'center', behavior: 'auto' }); } catch {} try { target.focus({ preventScroll: true }); } catch {} const rect = target.getBoundingClientRect(); const eventInit = { bubbles: true, cancelable: true, composed: true, view: window, clientX: rect.left + Math.max(1, rect.width / 2), clientY: rect.top + Math.max(1, rect.height / 2) }; ['pointerdown', 'mousedown', 'pointerup', 'mouseup', 'click'].forEach((type) => { dispatchMouseLikeEvent(target, type, eventInit); }); target.click(); return true; } function getNextTickDelay(now = Date.now()) { const diff = targetTimestamp - now; if (diff > 60_000) return 1000; if (diff > 10_000) return 400; if (diff > 3_000) return 120; if (diff > 0) return 40; if (diff > -WATCH_GRACE_MS) return 60; return 250; } function scheduleNextTick(delay = getNextTickDelay()) { if (!isWatching) return; if (tickTimer) clearTimeout(tickTimer); tickTimer = window.setTimeout(() => { tickTimer = null; void tick(); }, delay); } function isTargetWindowExpired(now = Date.now()) { return now > targetTimestamp + WATCH_GRACE_MS; } function getCountdown() { const diff = targetTimestamp - Date.now(); if (diff <= 0) return null; const hours = Math.floor(diff / (1000 * 60 * 60)); const minutes = Math.floor((diff % (1000 * 60 * 60)) / (1000 * 60)); const seconds = Math.floor((diff % (1000 * 60)) / 1000); return `${String(hours).padStart(2, '0')}:${String(minutes).padStart(2, '0')}:${String(seconds).padStart(2, '0')}`; } async function triggerBuyButton(button) { if (!button || isClicking) return false; isClicking = true; let restoreButton = null; const beforeText = getButtonState(button).text; try { const { disabled } = getButtonState(button); if (disabled) { restoreButton = temporarilyEnableButton(button); log('到点后临时解除按钮禁用,准备触发购买按钮'); } dispatchRealClick(button); await sleep(SECOND_CLICK_DELAY_MS); const dialogOpened = Boolean(findPayDialogRoot()); const buttonChanged = !button.isConnected || getButtonState(button).text !== beforeText; if (!dialogOpened && !buttonChanged && button.isConnected) { dispatchRealClick(button); } return true; } finally { if (restoreButton) { window.setTimeout(() => { restoreButton(); }, 1200); } isClicking = false; } } async function tick() { if (!isWatching || hasClicked) return; if (isTargetWindowExpired()) { stopWatching({ statusText: '已过时间', logMessage: '已超过目标时间窗口,监听结束' }); return; } const countdown = getCountdown(); if (countdown) { updateStatus(`倒计时 ${countdown}`); } else { updateStatus('已到点,等待按钮就绪'); } const cycleReady = ensureBillingCycleSelected(); if (!cycleReady) { scheduleNextTick(); return; } if (Date.now() - lastCycleSwitchAt < CYCLE_SETTLE_MS) { scheduleNextTick(); return; } const card = findPlanCard(config.targetPlan); const button = findBuyButton(card); if (!button) { if (Date.now() >= targetTimestamp) updateStatus('已到点,等待购买按钮出现'); scheduleNextTick(); return; } if (Date.now() < targetTimestamp) { scheduleNextTick(); return; } const clicked = await triggerBuyButton(button); if (clicked) { const failureDialog = findFailureDialog(); if (failureDialog) { log(`出现失败弹窗(${failureDialog.type}),正在关闭...`); dispatchRealClick(failureDialog.closeBtn); await sleep(getDialogRetryDelay()); log(`重新点击购买按钮`); scheduleNextTick(); return; } hasClicked = true; const timeStr = `${config.targetHour}:${String(config.targetMinute).padStart(2, '0')}:${String(config.targetSecond || 0).padStart(2, '0')}`; log(`[${timeStr}] 已点击 ${config.targetPlan} ${CYCLE_LABELS[config.billingCycle]} 购买按钮`); stopWatching({ statusText: '已点击,等待后续操作', logMessage: '' }); return; } scheduleNextTick(); } function stopWatching(options = {}) { const { statusText = '已停止', logMessage = '已停止' } = options; if (tickTimer) { clearTimeout(tickTimer); tickTimer = null; } isWatching = false; if (logMessage) log(logMessage); updateStatus(statusText); } function getDialogRetryDelay() { return DIALOG_RETRY_BASE_DELAY_MS + Math.floor(Math.random() * DIALOG_RETRY_RANDOM_MS); } function startWatching() { if (isWatching) return; refreshTargetTimestamp(); if (isTargetWindowExpired()) { log('已超过目标时间,无法启动监听'); updateStatus('已过时间'); return; } isWatching = true; hasClicked = false; isClicking = false; lastCycleSwitchAt = 0; const timeStr = `${config.targetHour}:${String(config.targetMinute).padStart(2, '0')}:${String(config.targetSecond || 0).padStart(2, '0')}`; log(`开始监听,目标时间: ${timeStr}`); updateStatus(getCountdown() ? `倒计时 ${getCountdown()}` : '已到点,等待按钮就绪'); scheduleNextTick(0); } function resetClicked() { hasClicked = false; isClicking = false; log(isWatching ? '已重置点击状态,继续监听' : '已重置点击状态'); updateStatus(isWatching ? (getCountdown() ? `倒计时 ${getCountdown()}` : '已到点,等待按钮就绪') : '就绪'); if (isWatching) scheduleNextTick(0); } function handleConfigChange() { saveConfig(); if (!isWatching) return; refreshTargetTimestamp(); hasClicked = false; isClicking = false; lastCycleSwitchAt = 0; log('配置已更新,监听目标时间已重新对齐'); updateStatus(getCountdown() ? `倒计时 ${getCountdown()}` : '已到点,等待按钮就绪'); scheduleNextTick(0); } // ========================================== // UI // ========================================== function injectStyles() { if (document.getElementById('glm-simple-style')) return; const style = document.createElement('style'); style.id = 'glm-simple-style'; style.textContent = ` #glm-simple-panel { position: fixed; left: 20px; bottom: 20px; width: 300px; z-index: 999999; border-radius: 16px; overflow: hidden; background: linear-gradient(135deg, #10233f 0%, #1d4ed8 64%, #38bdf8 100%); box-shadow: 0 24px 64px -28px rgba(16, 35, 63, 0.45); font-family: "SF Pro Display", "PingFang SC", "Segoe UI", sans-serif; color: #eff6ff; } #glm-simple-panel * { box-sizing: border-box; } .glm-simple-head { padding: 14px 16px; } .glm-simple-title { font-size: 14px; font-weight: 700; } .glm-simple-body { padding: 12px 14px; background: rgba(255,255,255,0.95); color: #1e293b; } .glm-simple-row { display: flex; gap: 8px; margin-bottom: 10px; } .glm-simple-field { flex: 1; } .glm-simple-field label { display: block; font-size: 11px; font-weight: 600; color: #475569; margin-bottom: 4px; } .glm-simple-field select, .glm-simple-field input { width: 100%; padding: 6px 8px; border: 1px solid #cbd5e1; border-radius: 8px; font-size: 13px; background: #f8fafc; } .glm-simple-time { display: flex; align-items: center; gap: 4px; } .glm-simple-time input { width: 50px; text-align: center; } .glm-simple-time span { font-size: 12px; color: #64748b; } .glm-simple-status { font-size: 13px; margin-bottom: 10px; padding: 8px; background: #f1f5f9; border-radius: 8px; text-align: center; } .glm-simple-actions { display: flex; gap: 8px; } .glm-simple-btn { flex: 1; padding: 8px 12px; border: none; border-radius: 10px; font-size: 13px; font-weight: 600; cursor: pointer; color: white; background: linear-gradient(135deg, #1d4ed8, #0ea5e9); } .glm-simple-btn.secondary { color: #475569; background: #e2e8f0; } .glm-simple-log { margin-top: 10px; max-height: 100px; overflow: auto; font-size: 11px; color: #334155; background: #f8fafc; border-radius: 8px; padding: 6px 8px; } `; document.head.appendChild(style); } function buildPanel() { if (document.getElementById('glm-simple-panel')) return; const panel = document.createElement('div'); panel.id = 'glm-simple-panel'; panel.innerHTML = `
GLM 准点点击助手
:
:
就绪
`; document.body.appendChild(panel); // 绑定事件 const planEl = document.getElementById('glm-simple-plan'); const cycleEl = document.getElementById('glm-simple-cycle'); const hourEl = document.getElementById('glm-simple-hour'); const minEl = document.getElementById('glm-simple-minute'); const secEl = document.getElementById('glm-simple-second'); planEl.value = config.targetPlan; cycleEl.value = config.billingCycle; hourEl.value = config.targetHour; minEl.value = config.targetMinute; secEl.value = config.targetSecond || 0; planEl.addEventListener('change', () => { config.targetPlan = planEl.value; handleConfigChange(); }); cycleEl.addEventListener('change', () => { config.billingCycle = cycleEl.value; handleConfigChange(); }); hourEl.addEventListener('change', () => { config.targetHour = Math.max(0, Math.min(23, Number(hourEl.value) || 0)); hourEl.value = config.targetHour; handleConfigChange(); }); minEl.addEventListener('change', () => { config.targetMinute = Math.max(0, Math.min(59, Number(minEl.value) || 0)); minEl.value = config.targetMinute; handleConfigChange(); }); secEl.addEventListener('change', () => { config.targetSecond = Math.max(0, Math.min(59, Number(secEl.value) || 0)); secEl.value = config.targetSecond; handleConfigChange(); }); document.getElementById('glm-simple-start').addEventListener('click', startWatching); document.getElementById('glm-simple-stop').addEventListener('click', stopWatching); document.getElementById('glm-simple-reset').addEventListener('click', resetClicked); } function bootstrap() { injectStyles(); buildPanel(); log('脚本已加载'); } if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', bootstrap); } else { bootstrap(); } })();