Última actividad 1776218155

admin revisó este gist 1776218154. Ir a la revisión

1 file changed, 768 insertions

111.js(archivo creado)

@@ -0,0 +1,768 @@
1 + // ==UserScript==
2 + // @name GLM Coding 抢购助手 (简化版)
3 + // @namespace http://tampermonkey.net/
4 + // @version 1.2
5 + // @description 准点自动点击指定套餐按钮,仅点击不处理后续流程。
6 + // @author Codex
7 + // @match *://bigmodel.cn/glm-coding*
8 + // @match *://bigmodel.cn/usercenter/glm-coding*
9 + // @grant none
10 + // @run-at document-start
11 + // ==/UserScript==
12 +
13 + (function () {
14 + 'use strict';
15 +
16 + if (window.__autoGlmSimpleInitialized) return;
17 + window.__autoGlmSimpleInitialized = true;
18 +
19 + // ==========================================
20 + // 网络拦截 - 绕过UI限制
21 + // ==========================================
22 + const originalJSONParse = JSON.parse;
23 + JSON.parse = function (text, reviver) {
24 + let result = originalJSONParse(text, reviver);
25 +
26 + function deepModify(obj) {
27 + if (!obj || typeof obj !== 'object') return;
28 +
29 + if (obj.isSoldOut === true) obj.isSoldOut = false;
30 + if (obj.soldOut === true) obj.soldOut = false;
31 + if (obj.disabled === true && (obj.price !== undefined || obj.productId || obj.title)) {
32 + obj.disabled = false;
33 + }
34 + if (obj.stock === 0) obj.stock = 999;
35 +
36 + for (let key in obj) {
37 + if (obj[key] && typeof obj[key] === 'object') {
38 + deepModify(obj[key]);
39 + }
40 + }
41 + }
42 +
43 + try {
44 + deepModify(result);
45 + } catch (e) {
46 + console.log('[Auto-GLM-Simple] JSON拦截异常:', e.message);
47 + }
48 + return result;
49 + };
50 +
51 + const originalFetch = window.fetch;
52 + window.fetch = async function (...args) {
53 + const response = await originalFetch.apply(this, args);
54 + const contentType = response.headers.get('content-type') || '';
55 + if (contentType.includes('application/json')) {
56 + const clone = response.clone();
57 + try {
58 + let text = await clone.text();
59 + if (text.includes('"isSoldOut":true') || text.includes('"disabled":true') || text.includes('"soldOut":true')) {
60 + console.log('[Auto-GLM-Simple] 拦截Fetch售罄数据:', args[0]);
61 + text = text.replace(/"isSoldOut":true/g, '"isSoldOut":false')
62 + .replace(/"disabled":true/g, '"disabled":false')
63 + .replace(/"soldOut":true/g, '"soldOut":false')
64 + .replace(/"stock":0/g, '"stock":999');
65 + return new Response(text, {
66 + status: response.status,
67 + statusText: response.statusText,
68 + headers: response.headers
69 + });
70 + }
71 + } catch (e) {
72 + console.log('[Auto-GLM-Simple] Fetch拦截异常:', e.message);
73 + }
74 + }
75 + return response;
76 + };
77 +
78 + const originalXHROpen = XMLHttpRequest.prototype.open;
79 + const originalXHRSend = XMLHttpRequest.prototype.send;
80 +
81 + XMLHttpRequest.prototype.open = function (method, url, ...rest) {
82 + this._reqUrl = url;
83 + return originalXHROpen.call(this, method, url, ...rest);
84 + };
85 +
86 + XMLHttpRequest.prototype.send = function (...args) {
87 + this.addEventListener('readystatechange', function () {
88 + if (this.readyState === 4 && this.status === 200) {
89 + const contentType = this.getResponseHeader('content-type') || '';
90 + if (contentType.includes('application/json')) {
91 + try {
92 + let text = this.responseText;
93 + if (text.includes('"isSoldOut":true') || text.includes('"disabled":true') || text.includes('"soldOut":true')) {
94 + console.log('[Auto-GLM-Simple] 拦截XHR售罄数据:', this._reqUrl);
95 + text = text.replace(/"isSoldOut":true/g, '"isSoldOut":false')
96 + .replace(/"disabled":true/g, '"disabled":false')
97 + .replace(/"soldOut":true/g, '"soldOut":false')
98 + .replace(/"stock":0/g, '"stock":999');
99 + Object.defineProperty(this, 'responseText', { get: function () { return text; } });
100 + Object.defineProperty(this, 'response', { get: function () { return JSON.parse(text); } });
101 + }
102 + } catch (e) {
103 + console.log('[Auto-GLM-Simple] XHR拦截异常:', e.message);
104 + }
105 + }
106 + }
107 + });
108 + originalXHRSend.apply(this, args);
109 + };
110 +
111 + console.log('[Auto-GLM-Simple] 网络拦截器已注册');
112 +
113 + // ==========================================
114 + // 简化配置
115 + // ==========================================
116 + const STORAGE_KEY = 'glm-simple-config';
117 + const WATCH_GRACE_MS = 5 * 60 * 1000;
118 + const CYCLE_SETTLE_MS = 350;
119 + const SECOND_CLICK_DELAY_MS = 90;
120 + const DIALOG_RETRY_BASE_DELAY_MS = 500;
121 + const DIALOG_RETRY_RANDOM_MS = 500;
122 + const PRODUCT_MAP = {
123 + Lite: { month: 'product-02434c', quarter: 'product-b8ea38', year: 'product-70a804' },
124 + Pro: { month: 'product-1df3e1', quarter: 'product-fef82f', year: 'product-5643e6' },
125 + Max: { month: 'product-2fc421', quarter: 'product-5d3a03', year: 'product-d46f8b' }
126 + };
127 + const CYCLE_LABELS = { month: '连续包月', quarter: '连续包季', year: '连续包年' };
128 +
129 + const DEFAULT_CONFIG = {
130 + targetPlan: 'Pro',
131 + billingCycle: 'quarter',
132 + targetHour: 10,
133 + targetMinute: 0,
134 + targetSecond: 0,
135 + autoStart: false
136 + };
137 +
138 + let config = loadConfig();
139 + let tickTimer = null;
140 + let isWatching = false;
141 + let hasClicked = false;
142 + let isClicking = false;
143 + let targetTimestamp = 0;
144 + let lastCycleSwitchAt = 0;
145 + let lastStatusText = '';
146 +
147 + function clampNumber(value, min, max, fallback) {
148 + const next = Number(value);
149 + if (!Number.isFinite(next)) return fallback;
150 + return Math.min(max, Math.max(min, Math.floor(next)));
151 + }
152 +
153 + function sanitizeConfig(raw = {}) {
154 + return {
155 + targetPlan: PRODUCT_MAP[raw.targetPlan] ? raw.targetPlan : DEFAULT_CONFIG.targetPlan,
156 + billingCycle: CYCLE_LABELS[raw.billingCycle] ? raw.billingCycle : DEFAULT_CONFIG.billingCycle,
157 + targetHour: clampNumber(raw.targetHour, 0, 23, DEFAULT_CONFIG.targetHour),
158 + targetMinute: clampNumber(raw.targetMinute, 0, 59, DEFAULT_CONFIG.targetMinute),
159 + targetSecond: clampNumber(raw.targetSecond, 0, 59, DEFAULT_CONFIG.targetSecond),
160 + autoStart: Boolean(raw.autoStart)
161 + };
162 + }
163 +
164 + function loadConfig() {
165 + try {
166 + const raw = localStorage.getItem(STORAGE_KEY);
167 + if (!raw) return { ...DEFAULT_CONFIG };
168 + return { ...DEFAULT_CONFIG, ...sanitizeConfig(JSON.parse(raw)) };
169 + } catch {
170 + return { ...DEFAULT_CONFIG };
171 + }
172 + }
173 +
174 + function saveConfig() {
175 + try {
176 + localStorage.setItem(STORAGE_KEY, JSON.stringify(config));
177 + } catch (e) {
178 + console.warn('[Auto-GLM-Simple] 保存配置失败:', e);
179 + }
180 + }
181 +
182 + function escapeHtml(text) {
183 + return String(text)
184 + .replaceAll('&', '&')
185 + .replaceAll('<', '&lt;')
186 + .replaceAll('>', '&gt;')
187 + .replaceAll('"', '&quot;')
188 + .replaceAll("'", '&#39;');
189 + }
190 +
191 + function log(msg) {
192 + console.log(`[Auto-GLM-Simple] ${msg}`);
193 + const logBox = document.getElementById('glm-simple-log');
194 + if (logBox) {
195 + const time = new Date().toLocaleTimeString();
196 + logBox.innerHTML = `<div>[${time}] ${escapeHtml(msg)}</div>` + logBox.innerHTML;
197 + if (logBox.children.length > 50) logBox.lastElementChild.remove();
198 + }
199 + }
200 +
201 + function updateStatus(text) {
202 + if (text === lastStatusText) return;
203 + lastStatusText = text;
204 + const statusEl = document.getElementById('glm-simple-status');
205 + if (statusEl) statusEl.textContent = text;
206 + }
207 +
208 + function normalizeText(text) {
209 + return String(text || '').replace(/\s+/g, '').trim();
210 + }
211 +
212 + function getTargetDate(now = new Date()) {
213 + return new Date(
214 + now.getFullYear(),
215 + now.getMonth(),
216 + now.getDate(),
217 + config.targetHour,
218 + config.targetMinute,
219 + config.targetSecond || 0,
220 + 0
221 + );
222 + }
223 +
224 + function refreshTargetTimestamp() {
225 + targetTimestamp = getTargetDate().getTime();
226 + }
227 +
228 + function sleep(ms) {
229 + return new Promise((resolve) => window.setTimeout(resolve, ms));
230 + }
231 +
232 + function isVisibleElement(node) {
233 + if (!node || !node.isConnected) return false;
234 + const rect = node.getBoundingClientRect();
235 + return rect.width > 0 && rect.height > 0;
236 + }
237 +
238 + // ==========================================
239 + // 核心逻辑:准点点击
240 + // ==========================================
241 + function findCycleTab(cycle) {
242 + const label = CYCLE_LABELS[cycle];
243 + if (!label) return null;
244 + return Array.from(document.querySelectorAll('.switch-tab-item'))
245 + .find((node) => normalizeText(node.textContent).includes(normalizeText(label))) || null;
246 + }
247 +
248 + function ensureBillingCycleSelected() {
249 + const tab = findCycleTab(config.billingCycle);
250 + if (!tab) return false;
251 + if (tab.classList.contains('active')) return true;
252 + if (Date.now() - lastCycleSwitchAt < CYCLE_SETTLE_MS) return false;
253 + lastCycleSwitchAt = Date.now();
254 + dispatchRealClick(tab.querySelector('.switch-tab-item-content') || tab);
255 + return false;
256 + }
257 +
258 + function findPlanCard(planName) {
259 + return Array.from(document.querySelectorAll('.package-card-box .package-card'))
260 + .filter(isVisibleElement)
261 + .find((card) => {
262 + const title = card.querySelector('.package-card-title .font-prompt');
263 + return title && normalizeText(title.textContent) === normalizeText(planName);
264 + }) || null;
265 + }
266 +
267 + function findBuyButton(card) {
268 + if (!card) return null;
269 + return Array.from(card.querySelectorAll('button.buy-btn, .package-card-btn-box button'))
270 + .find(isVisibleElement) || null;
271 + }
272 +
273 + function getButtonState(button) {
274 + if (!button) return { text: '', disabled: true };
275 + return {
276 + text: normalizeText(button.textContent),
277 + disabled: button.disabled
278 + || button.getAttribute('aria-disabled') === 'true'
279 + || button.classList.contains('is-disabled')
280 + || button.classList.contains('disabled')
281 + };
282 + }
283 +
284 + function temporarilyEnableButton(button) {
285 + if (!button) return () => {};
286 + const previous = {
287 + disabled: button.disabled,
288 + disabledAttr: button.getAttribute('disabled'),
289 + ariaDisabled: button.getAttribute('aria-disabled'),
290 + className: button.className
291 + };
292 + button.disabled = false;
293 + button.removeAttribute('disabled');
294 + button.setAttribute('aria-disabled', 'false');
295 + button.classList.remove('is-disabled', 'disabled');
296 + return () => {
297 + button.disabled = previous.disabled;
298 + if (previous.disabledAttr === null) {
299 + button.removeAttribute('disabled');
300 + } else {
301 + button.setAttribute('disabled', previous.disabledAttr);
302 + }
303 + if (previous.ariaDisabled === null) {
304 + button.removeAttribute('aria-disabled');
305 + } else {
306 + button.setAttribute('aria-disabled', previous.ariaDisabled);
307 + }
308 + button.className = previous.className;
309 + };
310 + }
311 +
312 + function findPayDialogRoot() {
313 + return document.querySelector('.white-mask-bg .pay-dialog, .white-mask-bg .scan-code-box, .confirm-pay-btn, .scan-qrcode-box');
314 + }
315 +
316 + function isRealPayDialog(root) {
317 + if (!root) return false;
318 + const priceEl = root.querySelector('.scan-qrcode-box .price-icon + span');
319 + if (priceEl) {
320 + const priceText = priceEl.textContent?.trim();
321 + return Boolean(priceText && priceText.length > 0);
322 + }
323 + return Boolean(root.querySelector('.confirm-pay-btn'));
324 + }
325 +
326 + function findFailureDialog() {
327 + const busyDialog = document.querySelector('.el-dialog__wrapper .empty-data-wrap');
328 + if (busyDialog?.textContent?.includes('购买人数较多')) {
329 + return { type: 'busy', closeBtn: busyDialog.closest('.el-dialog')?.querySelector('.el-dialog__headerbtn') };
330 + }
331 + const payRoot = document.querySelector('.white-mask-bg .pay-dialog, .white-mask-bg .scan-code-box');
332 + if (payRoot && !isRealPayDialog(payRoot)) {
333 + return { type: 'empty-price', closeBtn: payRoot.querySelector('.el-dialog__headerbtn, .confirm-pay-btn') };
334 + }
335 + return null;
336 + }
337 +
338 + function dispatchMouseLikeEvent(target, type, init) {
339 + const EventCtor = type.startsWith('pointer') && typeof PointerEvent === 'function' ? PointerEvent : MouseEvent;
340 + target.dispatchEvent(new EventCtor(type, init));
341 + }
342 +
343 + function dispatchRealClick(target) {
344 + if (!target || !target.isConnected) return false;
345 + try {
346 + target.scrollIntoView({ block: 'center', inline: 'center', behavior: 'auto' });
347 + } catch {}
348 + try {
349 + target.focus({ preventScroll: true });
350 + } catch {}
351 +
352 + const rect = target.getBoundingClientRect();
353 + const eventInit = {
354 + bubbles: true,
355 + cancelable: true,
356 + composed: true,
357 + view: window,
358 + clientX: rect.left + Math.max(1, rect.width / 2),
359 + clientY: rect.top + Math.max(1, rect.height / 2)
360 + };
361 + ['pointerdown', 'mousedown', 'pointerup', 'mouseup', 'click'].forEach((type) => {
362 + dispatchMouseLikeEvent(target, type, eventInit);
363 + });
364 + target.click();
365 + return true;
366 + }
367 +
368 + function getNextTickDelay(now = Date.now()) {
369 + const diff = targetTimestamp - now;
370 + if (diff > 60_000) return 1000;
371 + if (diff > 10_000) return 400;
372 + if (diff > 3_000) return 120;
373 + if (diff > 0) return 40;
374 + if (diff > -WATCH_GRACE_MS) return 60;
375 + return 250;
376 + }
377 +
378 + function scheduleNextTick(delay = getNextTickDelay()) {
379 + if (!isWatching) return;
380 + if (tickTimer) clearTimeout(tickTimer);
381 + tickTimer = window.setTimeout(() => {
382 + tickTimer = null;
383 + void tick();
384 + }, delay);
385 + }
386 +
387 + function isTargetWindowExpired(now = Date.now()) {
388 + return now > targetTimestamp + WATCH_GRACE_MS;
389 + }
390 +
391 + function getCountdown() {
392 + const diff = targetTimestamp - Date.now();
393 + if (diff <= 0) return null;
394 + const hours = Math.floor(diff / (1000 * 60 * 60));
395 + const minutes = Math.floor((diff % (1000 * 60 * 60)) / (1000 * 60));
396 + const seconds = Math.floor((diff % (1000 * 60)) / 1000);
397 + return `${String(hours).padStart(2, '0')}:${String(minutes).padStart(2, '0')}:${String(seconds).padStart(2, '0')}`;
398 + }
399 +
400 + async function triggerBuyButton(button) {
401 + if (!button || isClicking) return false;
402 + isClicking = true;
403 + let restoreButton = null;
404 + const beforeText = getButtonState(button).text;
405 +
406 + try {
407 + const { disabled } = getButtonState(button);
408 + if (disabled) {
409 + restoreButton = temporarilyEnableButton(button);
410 + log('到点后临时解除按钮禁用,准备触发购买按钮');
411 + }
412 +
413 + dispatchRealClick(button);
414 + await sleep(SECOND_CLICK_DELAY_MS);
415 +
416 + const dialogOpened = Boolean(findPayDialogRoot());
417 + const buttonChanged = !button.isConnected || getButtonState(button).text !== beforeText;
418 + if (!dialogOpened && !buttonChanged && button.isConnected) {
419 + dispatchRealClick(button);
420 + }
421 +
422 + return true;
423 + } finally {
424 + if (restoreButton) {
425 + window.setTimeout(() => {
426 + restoreButton();
427 + }, 1200);
428 + }
429 + isClicking = false;
430 + }
431 + }
432 +
433 + async function tick() {
434 + if (!isWatching || hasClicked) return;
435 +
436 + if (isTargetWindowExpired()) {
437 + stopWatching({ statusText: '已过时间', logMessage: '已超过目标时间窗口,监听结束' });
438 + return;
439 + }
440 +
441 + const countdown = getCountdown();
442 + if (countdown) {
443 + updateStatus(`倒计时 ${countdown}`);
444 + } else {
445 + updateStatus('已到点,等待按钮就绪');
446 + }
447 +
448 + const cycleReady = ensureBillingCycleSelected();
449 + if (!cycleReady) {
450 + scheduleNextTick();
451 + return;
452 + }
453 + if (Date.now() - lastCycleSwitchAt < CYCLE_SETTLE_MS) {
454 + scheduleNextTick();
455 + return;
456 + }
457 +
458 + const card = findPlanCard(config.targetPlan);
459 + const button = findBuyButton(card);
460 +
461 + if (!button) {
462 + if (Date.now() >= targetTimestamp) updateStatus('已到点,等待购买按钮出现');
463 + scheduleNextTick();
464 + return;
465 + }
466 +
467 + if (Date.now() < targetTimestamp) {
468 + scheduleNextTick();
469 + return;
470 + }
471 +
472 + const clicked = await triggerBuyButton(button);
473 + if (clicked) {
474 + const failureDialog = findFailureDialog();
475 + if (failureDialog) {
476 + log(`出现失败弹窗(${failureDialog.type}),正在关闭...`);
477 + dispatchRealClick(failureDialog.closeBtn);
478 + await sleep(getDialogRetryDelay());
479 + log(`重新点击购买按钮`);
480 + scheduleNextTick();
481 + return;
482 + }
483 + hasClicked = true;
484 + const timeStr = `${config.targetHour}:${String(config.targetMinute).padStart(2, '0')}:${String(config.targetSecond || 0).padStart(2, '0')}`;
485 + log(`[${timeStr}] 已点击 ${config.targetPlan} ${CYCLE_LABELS[config.billingCycle]} 购买按钮`);
486 + stopWatching({ statusText: '已点击,等待后续操作', logMessage: '' });
487 + return;
488 + }
489 +
490 + scheduleNextTick();
491 + }
492 +
493 + function stopWatching(options = {}) {
494 + const { statusText = '已停止', logMessage = '已停止' } = options;
495 + if (tickTimer) {
496 + clearTimeout(tickTimer);
497 + tickTimer = null;
498 + }
499 + isWatching = false;
500 + if (logMessage) log(logMessage);
501 + updateStatus(statusText);
502 + }
503 +
504 + function getDialogRetryDelay() {
505 + return DIALOG_RETRY_BASE_DELAY_MS + Math.floor(Math.random() * DIALOG_RETRY_RANDOM_MS);
506 + }
507 +
508 + function startWatching() {
509 + if (isWatching) return;
510 +
511 + refreshTargetTimestamp();
512 + if (isTargetWindowExpired()) {
513 + log('已超过目标时间,无法启动监听');
514 + updateStatus('已过时间');
515 + return;
516 + }
517 +
518 + isWatching = true;
519 + hasClicked = false;
520 + isClicking = false;
521 + lastCycleSwitchAt = 0;
522 + const timeStr = `${config.targetHour}:${String(config.targetMinute).padStart(2, '0')}:${String(config.targetSecond || 0).padStart(2, '0')}`;
523 + log(`开始监听,目标时间: ${timeStr}`);
524 + updateStatus(getCountdown() ? `倒计时 ${getCountdown()}` : '已到点,等待按钮就绪');
525 + scheduleNextTick(0);
526 + }
527 +
528 + function resetClicked() {
529 + hasClicked = false;
530 + isClicking = false;
531 + log(isWatching ? '已重置点击状态,继续监听' : '已重置点击状态');
532 + updateStatus(isWatching ? (getCountdown() ? `倒计时 ${getCountdown()}` : '已到点,等待按钮就绪') : '就绪');
533 + if (isWatching) scheduleNextTick(0);
534 + }
535 +
536 + function handleConfigChange() {
537 + saveConfig();
538 + if (!isWatching) return;
539 + refreshTargetTimestamp();
540 + hasClicked = false;
541 + isClicking = false;
542 + lastCycleSwitchAt = 0;
543 + log('配置已更新,监听目标时间已重新对齐');
544 + updateStatus(getCountdown() ? `倒计时 ${getCountdown()}` : '已到点,等待按钮就绪');
545 + scheduleNextTick(0);
546 + }
547 +
548 + // ==========================================
549 + // UI
550 + // ==========================================
551 + function injectStyles() {
552 + if (document.getElementById('glm-simple-style')) return;
553 + const style = document.createElement('style');
554 + style.id = 'glm-simple-style';
555 + style.textContent = `
556 + #glm-simple-panel {
557 + position: fixed;
558 + left: 20px;
559 + bottom: 20px;
560 + width: 300px;
561 + z-index: 999999;
562 + border-radius: 16px;
563 + overflow: hidden;
564 + background: linear-gradient(135deg, #10233f 0%, #1d4ed8 64%, #38bdf8 100%);
565 + box-shadow: 0 24px 64px -28px rgba(16, 35, 63, 0.45);
566 + font-family: "SF Pro Display", "PingFang SC", "Segoe UI", sans-serif;
567 + color: #eff6ff;
568 + }
569 + #glm-simple-panel * { box-sizing: border-box; }
570 + .glm-simple-head {
571 + padding: 14px 16px;
572 + }
573 + .glm-simple-title {
574 + font-size: 14px;
575 + font-weight: 700;
576 + }
577 + .glm-simple-body {
578 + padding: 12px 14px;
579 + background: rgba(255,255,255,0.95);
580 + color: #1e293b;
581 + }
582 + .glm-simple-row {
583 + display: flex;
584 + gap: 8px;
585 + margin-bottom: 10px;
586 + }
587 + .glm-simple-field {
588 + flex: 1;
589 + }
590 + .glm-simple-field label {
591 + display: block;
592 + font-size: 11px;
593 + font-weight: 600;
594 + color: #475569;
595 + margin-bottom: 4px;
596 + }
597 + .glm-simple-field select,
598 + .glm-simple-field input {
599 + width: 100%;
600 + padding: 6px 8px;
601 + border: 1px solid #cbd5e1;
602 + border-radius: 8px;
603 + font-size: 13px;
604 + background: #f8fafc;
605 + }
606 + .glm-simple-time {
607 + display: flex;
608 + align-items: center;
609 + gap: 4px;
610 + }
611 + .glm-simple-time input {
612 + width: 50px;
613 + text-align: center;
614 + }
615 + .glm-simple-time span {
616 + font-size: 12px;
617 + color: #64748b;
618 + }
619 + .glm-simple-status {
620 + font-size: 13px;
621 + margin-bottom: 10px;
622 + padding: 8px;
623 + background: #f1f5f9;
624 + border-radius: 8px;
625 + text-align: center;
626 + }
627 + .glm-simple-actions {
628 + display: flex;
629 + gap: 8px;
630 + }
631 + .glm-simple-btn {
632 + flex: 1;
633 + padding: 8px 12px;
634 + border: none;
635 + border-radius: 10px;
636 + font-size: 13px;
637 + font-weight: 600;
638 + cursor: pointer;
639 + color: white;
640 + background: linear-gradient(135deg, #1d4ed8, #0ea5e9);
641 + }
642 + .glm-simple-btn.secondary {
643 + color: #475569;
644 + background: #e2e8f0;
645 + }
646 + .glm-simple-log {
647 + margin-top: 10px;
648 + max-height: 100px;
649 + overflow: auto;
650 + font-size: 11px;
651 + color: #334155;
652 + background: #f8fafc;
653 + border-radius: 8px;
654 + padding: 6px 8px;
655 + }
656 + `;
657 + document.head.appendChild(style);
658 + }
659 +
660 + function buildPanel() {
661 + if (document.getElementById('glm-simple-panel')) return;
662 +
663 + const panel = document.createElement('div');
664 + panel.id = 'glm-simple-panel';
665 + panel.innerHTML = `
666 + <div class="glm-simple-head">
667 + <div class="glm-simple-title">GLM 准点点击助手</div>
668 + </div>
669 + <div class="glm-simple-body">
670 + <div class="glm-simple-row">
671 + <div class="glm-simple-field">
672 + <label>套餐</label>
673 + <select id="glm-simple-plan">
674 + <option value="Lite">Lite</option>
675 + <option value="Pro">Pro</option>
676 + <option value="Max">Max</option>
677 + </select>
678 + </div>
679 + <div class="glm-simple-field">
680 + <label>周期</label>
681 + <select id="glm-simple-cycle">
682 + <option value="month">连续包月</option>
683 + <option value="quarter">连续包季</option>
684 + <option value="year">连续包年</option>
685 + </select>
686 + </div>
687 + </div>
688 + <div class="glm-simple-row glm-simple-time">
689 + <div class="glm-simple-field">
690 + <label>时</label>
691 + <input id="glm-simple-hour" type="number" min="0" max="23" />
692 + </div>
693 + <span>:</span>
694 + <div class="glm-simple-field">
695 + <label>分</label>
696 + <input id="glm-simple-minute" type="number" min="0" max="59" />
697 + </div>
698 + <span>:</span>
699 + <div class="glm-simple-field">
700 + <label>秒</label>
701 + <input id="glm-simple-second" type="number" min="0" max="59" />
702 + </div>
703 + </div>
704 + <div class="glm-simple-status" id="glm-simple-status">就绪</div>
705 + <div class="glm-simple-actions">
706 + <button class="glm-simple-btn" id="glm-simple-start" type="button">开始监听</button>
707 + <button class="glm-simple-btn secondary" id="glm-simple-stop" type="button">停止</button>
708 + <button class="glm-simple-btn secondary" id="glm-simple-reset" type="button">重置</button>
709 + </div>
710 + <div class="glm-simple-log" id="glm-simple-log"></div>
711 + </div>
712 + `;
713 + document.body.appendChild(panel);
714 +
715 + // 绑定事件
716 + const planEl = document.getElementById('glm-simple-plan');
717 + const cycleEl = document.getElementById('glm-simple-cycle');
718 + const hourEl = document.getElementById('glm-simple-hour');
719 + const minEl = document.getElementById('glm-simple-minute');
720 + const secEl = document.getElementById('glm-simple-second');
721 +
722 + planEl.value = config.targetPlan;
723 + cycleEl.value = config.billingCycle;
724 + hourEl.value = config.targetHour;
725 + minEl.value = config.targetMinute;
726 + secEl.value = config.targetSecond || 0;
727 +
728 + planEl.addEventListener('change', () => {
729 + config.targetPlan = planEl.value;
730 + handleConfigChange();
731 + });
732 + cycleEl.addEventListener('change', () => {
733 + config.billingCycle = cycleEl.value;
734 + handleConfigChange();
735 + });
736 + hourEl.addEventListener('change', () => {
737 + config.targetHour = Math.max(0, Math.min(23, Number(hourEl.value) || 0));
738 + hourEl.value = config.targetHour;
739 + handleConfigChange();
740 + });
741 + minEl.addEventListener('change', () => {
742 + config.targetMinute = Math.max(0, Math.min(59, Number(minEl.value) || 0));
743 + minEl.value = config.targetMinute;
744 + handleConfigChange();
745 + });
746 + secEl.addEventListener('change', () => {
747 + config.targetSecond = Math.max(0, Math.min(59, Number(secEl.value) || 0));
748 + secEl.value = config.targetSecond;
749 + handleConfigChange();
750 + });
751 +
752 + document.getElementById('glm-simple-start').addEventListener('click', startWatching);
753 + document.getElementById('glm-simple-stop').addEventListener('click', stopWatching);
754 + document.getElementById('glm-simple-reset').addEventListener('click', resetClicked);
755 + }
756 +
757 + function bootstrap() {
758 + injectStyles();
759 + buildPanel();
760 + log('脚本已加载');
761 + }
762 +
763 + if (document.readyState === 'loading') {
764 + document.addEventListener('DOMContentLoaded', bootstrap);
765 + } else {
766 + bootstrap();
767 + }
768 + })();
Siguiente Anterior