Последняя активность 1775530113

admin ревизий этого фрагмента 1775530112. К ревизии

1 file changed, 647 insertions

glm.js(файл создан)

@@ -0,0 +1,647 @@
1 + // ==UserScript==
2 + // @name 智谱 GLM Coding 终极抢购助手 (全自动版)
3 + // @namespace http://tampermonkey.net/
4 + // @version 3.2
5 + // @description 全自动抢购:自动重试 preview + check 双重校验 + 错误弹窗自动恢复 + 主动模式 + 定时触发
6 + // @author Assistant
7 + // @match *://www.bigmodel.cn/*
8 + // @run-at document-start
9 + // @grant none
10 + // ==/UserScript==
11 +
12 + (function () {
13 + 'use strict';
14 +
15 + // ======================== 配置 ========================
16 + const CFG = {
17 + delay: 100,
18 + maxRetry: 300,
19 + PREVIEW: '/api/biz/pay/preview',
20 + CHECK: '/api/biz/pay/check',
21 + };
22 +
23 + // ======================== 全局状态 ========================
24 + const S = {
25 + status: 'idle',
26 + count: 0,
27 + bizId: null,
28 + captured: null,
29 + cache: null,
30 + lastSuccess: null, // 最近一次成功的响应,用于弹窗恢复
31 + proactive: false,
32 + timerId: null,
33 + logs: [],
34 + };
35 +
36 + let stopRequested = false;
37 + let recovering = false;
38 + let recoveryAttempts = 0;
39 +
40 + // ======================== 工具函数 ========================
41 + const sleep = ms => new Promise(r => setTimeout(r, ms));
42 + const ts = () => new Date().toLocaleTimeString('zh-CN', { hour12: false });
43 +
44 + function log(msg) {
45 + S.logs.push(`${ts()} ${msg}`);
46 + if (S.logs.length > 80) S.logs.shift();
47 + console.log(`[GLM抢购] ${msg}`);
48 + refreshLog();
49 + }
50 +
51 + function extractHeaders(h) {
52 + const o = {};
53 + if (!h) return o;
54 + if (h instanceof Headers) h.forEach((v, k) => (o[k] = v));
55 + else if (Array.isArray(h)) h.forEach(([k, v]) => (o[k] = v));
56 + else Object.entries(h).forEach(([k, v]) => (o[k] = v));
57 + return o;
58 + }
59 +
60 + // ======================== 一、JSON.parse 深层篡改 (UI 补丁) ========================
61 + const _parse = JSON.parse;
62 + JSON.parse = function (text, reviver) {
63 + let result = _parse(text, reviver);
64 + try {
65 + (function fix(obj) {
66 + if (!obj || typeof obj !== 'object') return;
67 + if (obj.isSoldOut === true) obj.isSoldOut = false;
68 + if (obj.soldOut === true) obj.soldOut = false;
69 + if (obj.disabled === true && (obj.price !== undefined || obj.productId || obj.title)) obj.disabled = false;
70 + if (obj.stock === 0) obj.stock = 999;
71 + for (let k in obj) if (obj[k] && typeof obj[k] === 'object') fix(obj[k]);
72 + })(result);
73 + } catch (e) {}
74 + return result;
75 + };
76 +
77 + // ======================== 二、核心重试引擎 ========================
78 + const _fetch = window.fetch;
79 + let _retryPromise = null;
80 +
81 + async function retry(url, opts) {
82 + if (_retryPromise) {
83 + log('⏳ 合并到当前重试…');
84 + return _retryPromise;
85 + }
86 +
87 + stopRequested = false;
88 +
89 + _retryPromise = (async () => {
90 + S.status = 'retrying';
91 + S.count = 0;
92 + refreshUI();
93 +
94 + // 剥离 AbortSignal,防止前端超时中断我们的重试
95 + const { signal, ...cleanOpts } = opts || {};
96 +
97 + for (let i = 1; i <= CFG.maxRetry; i++) {
98 + if (stopRequested) {
99 + log('⏹ 重试被手动停止');
100 + break;
101 + }
102 +
103 + S.count = i;
104 + refreshUI();
105 +
106 + try {
107 + const resp = await _fetch(url, { ...cleanOpts, credentials: 'include' });
108 + const text = await resp.text();
109 + let data;
110 + try { data = _parse(text); } catch { data = null; }
111 +
112 + if (data && data.code === 200 && data.data && data.data.bizId) {
113 + const bizId = data.data.bizId;
114 + log(`🔑 获取到 bizId=${bizId},正在校验 check 接口…`);
115 +
116 + // 关键校验:调用 check 接口确认 bizId 是否有效
117 + try {
118 + const checkUrl = `${location.origin}${CFG.CHECK}?bizId=${bizId}`;
119 + const checkResp = await _fetch(checkUrl, { credentials: 'include' });
120 + const checkText = await checkResp.text();
121 + let checkData;
122 + try { checkData = _parse(checkText); } catch { checkData = null; }
123 +
124 + if (checkData && checkData.data === 'EXPIRE') {
125 + // bizId 已过期,继续重试 preview
126 + log(`#${i} bizId 已过期(EXPIRE),继续重试…`);
127 + await sleep(CFG.delay);
128 + continue;
129 + }
130 +
131 + // check 返回非 EXPIRE → 真正成功!
132 + S.status = 'success';
133 + S.bizId = bizId;
134 + S.lastSuccess = { text, data };
135 + log(`✅ 抢购成功! bizId=${bizId}, check 校验通过 (第${i}次)`);
136 + refreshUI();
137 + recoveryAttempts = 0;
138 + setTimeout(autoRecover, 600);
139 + return { ok: true, text, data, status: resp.status };
140 + } catch (checkErr) {
141 + // check 接口本身出错,也继续重试
142 + log(`#${i} check 校验异常: ${checkErr.message},继续重试…`);
143 + await sleep(CFG.delay);
144 + continue;
145 + }
146 + }
147 +
148 + const why = !data ? '非JSON响应'
149 + : data.code === 555 ? '系统繁忙(555)'
150 + : (data.data && data.data.bizId === null) ? '售罄(bizId=null)'
151 + : `未知(code=${data.code})`;
152 + if (i <= 5 || i % 20 === 0) log(`#${i} ${why}`);
153 + } catch (e) {
154 + if (i <= 3 || i % 20 === 0) log(`#${i} 网络错误: ${e.message}`);
155 + }
156 +
157 + await sleep(CFG.delay);
158 + }
159 +
160 + if (!stopRequested) {
161 + S.status = 'failed';
162 + log(`❌ 达到上限 ${CFG.maxRetry} 次`);
163 + } else {
164 + S.status = 'idle';
165 + }
166 + refreshUI();
167 + return { ok: false };
168 + })();
169 +
170 + try { return await _retryPromise; }
171 + finally { _retryPromise = null; }
172 + }
173 +
174 + // ======================== 三、错误弹窗自动恢复 ========================
175 +
176 + /** 查找页面上可见的错误弹窗 */
177 + function findErrorDialog() {
178 + const selectors = [
179 + '.el-dialog', '.el-message-box', '.el-dialog__wrapper',
180 + '.ant-modal', '.ant-modal-wrap',
181 + '[class*="modal"]', '[class*="dialog"]', '[class*="popup"]',
182 + '[role="dialog"]',
183 + ];
184 + for (const sel of selectors) {
185 + for (const el of document.querySelectorAll(sel)) {
186 + // 必须可见
187 + const style = window.getComputedStyle(el);
188 + if (style.display === 'none' || style.visibility === 'hidden' || style.opacity === '0') continue;
189 + if (!el.offsetParent && style.position !== 'fixed') continue;
190 +
191 + const text = el.textContent || '';
192 + if (/购买人数过多|系统繁忙|稍后再试|请重试|繁忙|失败|出错|异常/.test(text)) {
193 + return el;
194 + }
195 + }
196 + }
197 + return null;
198 + }
199 +
200 + /** 关闭弹窗 */
201 + function dismissDialog(dialog) {
202 + // 策略 1:找关闭按钮
203 + const closeSelectors = [
204 + '.el-dialog__headerbtn', '.el-message-box__headerbtn',
205 + '.el-dialog__close', '.ant-modal-close',
206 + '[class*="close-btn"]', '[class*="closeBtn"]',
207 + '[aria-label="Close"]', '[aria-label="close"]',
208 + ];
209 + for (const sel of closeSelectors) {
210 + const btn = dialog.querySelector(sel) || document.querySelector(sel);
211 + if (btn && btn.offsetParent !== null) {
212 + btn.click();
213 + log('🔄 点击关闭按钮');
214 + return true;
215 + }
216 + }
217 +
218 + // 策略 2:找弹窗内的 确定/取消/关闭 按钮
219 + const btns = dialog.querySelectorAll('button, [role="button"]');
220 + for (const btn of btns) {
221 + const t = (btn.textContent || '').trim();
222 + if (/关闭|确定|取消|知道了|OK|Cancel|Close|确认/.test(t) && t.length < 10) {
223 + btn.click();
224 + log(`🔄 点击 [${t}] 按钮`);
225 + return true;
226 + }
227 + }
228 +
229 + // 策略 3:按 Escape
230 + document.dispatchEvent(new KeyboardEvent('keydown', { key: 'Escape', keyCode: 27, bubbles: true }));
231 + document.dispatchEvent(new KeyboardEvent('keyup', { key: 'Escape', keyCode: 27, bubbles: true }));
232 + log('🔄 发送 Escape 键');
233 +
234 + // 策略 4:点击遮罩层
235 + const masks = document.querySelectorAll('.el-overlay, .v-modal, .el-overlay-dialog, [class*="overlay"], [class*="mask"]');
236 + for (const mask of masks) {
237 + if (mask.offsetParent !== null || window.getComputedStyle(mask).position === 'fixed') {
238 + mask.click();
239 + log('🔄 点击遮罩层');
240 + return true;
241 + }
242 + }
243 +
244 + // 策略 5:强制隐藏
245 + dialog.style.display = 'none';
246 + const overlays = document.querySelectorAll('.el-overlay, .v-modal');
247 + overlays.forEach(o => (o.style.display = 'none'));
248 + log('🔄 强制隐藏弹窗');
249 + return true;
250 + }
251 +
252 + /** 自动恢复:关闭错误弹窗 → 重新触发购买 */
253 + async function autoRecover() {
254 + if (recovering || recoveryAttempts >= 3) return;
255 + if (!S.lastSuccess) return;
256 +
257 + const dialog = findErrorDialog();
258 + if (!dialog) return; // 没有错误弹窗,说明前端已正常处理,无需恢复
259 +
260 + recovering = true;
261 + recoveryAttempts++;
262 +
263 + try {
264 + log('🔄 检测到错误弹窗,启动自动恢复…');
265 +
266 + // 把成功响应放入缓存,供拦截器下一次返回
267 + S.cache = S.lastSuccess;
268 +
269 + // 关闭错误弹窗
270 + dismissDialog(dialog);
271 + await sleep(500);
272 +
273 + // 再次确认弹窗关了(有些弹窗有动画延迟)
274 + const stillThere = findErrorDialog();
275 + if (stillThere) {
276 + dismissDialog(stillThere);
277 + await sleep(300);
278 + }
279 +
280 + // 重新触发购买按钮 → 拦截器返回缓存响应 → 前端弹出支付二维码
281 + const btn = findBuyButton();
282 + if (btn) {
283 + btn.click();
284 + log('🖱 已自动重新点击购买按钮');
285 + } else {
286 + log('⚠️ 未找到购买按钮,请手动点击');
287 + alert('已获取到商品!请立即手动点击购买按钮!');
288 + }
289 + } finally {
290 + recovering = false;
291 + }
292 + }
293 +
294 + /** 持续监控:每 500ms 检查一次是否有需要恢复的错误弹窗 */
295 + function setupDialogWatcher() {
296 + setInterval(() => {
297 + if (S.lastSuccess && !recovering && recoveryAttempts < 3) {
298 + const dialog = findErrorDialog();
299 + if (dialog) autoRecover();
300 + }
301 + }, 500);
302 + }
303 +
304 + // ======================== 四、Fetch 拦截器 ========================
305 + window.fetch = async function (input, init) {
306 + const url = typeof input === 'string' ? input : input?.url;
307 +
308 + if (url && url.includes(CFG.PREVIEW)) {
309 + S.captured = {
310 + url,
311 + method: init?.method || 'POST',
312 + body: init?.body,
313 + headers: extractHeaders(init?.headers),
314 + };
315 + log('🎯 捕获 preview 请求 (Fetch)');
316 + refreshUI();
317 +
318 + // 有缓存 → 直接返回(来自主动模式或弹窗恢复)
319 + if (S.cache) {
320 + log('📦 返回缓存的成功响应');
321 + const c = S.cache;
322 + S.cache = null;
323 + recoveryAttempts = 0; // 重置恢复计数
324 + return new Response(c.text, {
325 + status: 200,
326 + headers: { 'Content-Type': 'application/json' },
327 + });
328 + }
329 +
330 + const result = await retry(url, {
331 + method: init?.method || 'POST',
332 + body: init?.body,
333 + headers: extractHeaders(init?.headers),
334 + signal: init?.signal, // 传入以便 retry 内部剥离
335 + });
336 +
337 + if (result.ok) {
338 + return new Response(result.text, {
339 + status: result.status,
340 + headers: { 'Content-Type': 'application/json' },
341 + });
342 + }
343 + return _fetch.apply(this, [input, init]);
344 + }
345 +
346 + if (url && url.includes(CFG.CHECK) && url.includes('bizId=null')) {
347 + log('🚫 拦截 check(bizId=null)');
348 + return new Response(JSON.stringify({ code: -1, msg: '等待有效bizId' }), {
349 + status: 200, headers: { 'Content-Type': 'application/json' },
350 + });
351 + }
352 +
353 + return _fetch.apply(this, [input, init]);
354 + };
355 +
356 + // ======================== 五、XHR 拦截器 ========================
357 + const _xhrOpen = XMLHttpRequest.prototype.open;
358 + const _xhrSend = XMLHttpRequest.prototype.send;
359 + const _xhrSetHeader = XMLHttpRequest.prototype.setRequestHeader;
360 +
361 + XMLHttpRequest.prototype.setRequestHeader = function (k, v) {
362 + (this._h || (this._h = {}))[k] = v;
363 + return _xhrSetHeader.call(this, k, v);
364 + };
365 + XMLHttpRequest.prototype.open = function (method, url) {
366 + this._m = method;
367 + this._u = url;
368 + return _xhrOpen.apply(this, arguments);
369 + };
370 + XMLHttpRequest.prototype.send = function (body) {
371 + const url = this._u;
372 +
373 + if (typeof url === 'string' && url.includes(CFG.PREVIEW)) {
374 + const self = this;
375 + S.captured = { url, method: this._m, body, headers: this._h || {} };
376 + log('🎯 捕获 preview 请求 (XHR)');
377 + refreshUI();
378 +
379 + if (S.cache) {
380 + log('📦 返回缓存的成功响应 (XHR)');
381 + const c = S.cache; S.cache = null;
382 + recoveryAttempts = 0;
383 + fakeXHR(self, c.text);
384 + return;
385 + }
386 +
387 + retry(url, { method: this._m, body, headers: this._h || {} }).then(result => {
388 + fakeXHR(self, result.ok ? result.text : '{"code":-1,"msg":"重试失败"}');
389 + });
390 + return;
391 + }
392 +
393 + if (typeof url === 'string' && url.includes(CFG.CHECK) && url.includes('bizId=null')) {
394 + log('🚫 拦截 check(bizId=null) (XHR)');
395 + fakeXHR(this, '{"code":-1,"msg":"等待有效bizId"}');
396 + return;
397 + }
398 +
399 + return _xhrSend.call(this, body);
400 + };
401 +
402 + function fakeXHR(xhr, text) {
403 + setTimeout(() => {
404 + const dp = (k, v) => Object.defineProperty(xhr, k, { value: v, configurable: true });
405 + dp('readyState', 4); dp('status', 200); dp('statusText', 'OK');
406 + dp('responseText', text); dp('response', text);
407 + const rsc = new Event('readystatechange');
408 + if (typeof xhr.onreadystatechange === 'function') xhr.onreadystatechange(rsc);
409 + xhr.dispatchEvent(rsc);
410 + const load = new ProgressEvent('load');
411 + if (typeof xhr.onload === 'function') xhr.onload(load);
412 + xhr.dispatchEvent(load);
413 + xhr.dispatchEvent(new ProgressEvent('loadend'));
414 + }, 0);
415 + }
416 +
417 + // ======================== 六、主动抢购模式 ========================
418 + async function startProactive() {
419 + if (!S.captured) {
420 + log('⚠️ 请先手动点一次购买/订阅按钮');
421 + alert('请先手动点一次购买/订阅按钮,让脚本捕获请求参数');
422 + return;
423 + }
424 +
425 + S.proactive = true;
426 + log('🚀 主动抢购模式启动');
427 +
428 + const { url, method, body, headers } = S.captured;
429 + const result = await retry(url, { method, body, headers });
430 +
431 + S.proactive = false;
432 +
433 + if (result.ok) {
434 + S.cache = { text: result.text, data: result.data };
435 + log('🎉 主动模式成功! 触发购买流程…');
436 +
437 + // 先关可能存在的弹窗
438 + const errDlg = findErrorDialog();
439 + if (errDlg) {
440 + dismissDialog(errDlg);
441 + await sleep(300);
442 + }
443 +
444 + const btn = findBuyButton();
445 + if (btn) {
446 + btn.click();
447 + log('🖱 已自动点击购买按钮');
448 + } else {
449 + log('⚠️ 未找到按钮,请手动点击');
450 + alert('已获取到商品!请立即点击购买按钮!');
451 + }
452 + }
453 + }
454 +
455 + function stopAll() {
456 + stopRequested = true;
457 + S.proactive = false;
458 + S.status = 'idle';
459 + S.count = 0;
460 + if (S.timerId) { clearTimeout(S.timerId); S.timerId = null; }
461 + log('⏹ 已停止');
462 + refreshUI();
463 + }
464 +
465 + function findBuyButton() {
466 + for (const el of document.querySelectorAll('button, a, [role="button"], div[class*="btn"], span[class*="btn"]')) {
467 + const t = el.textContent.trim();
468 + if (/购买|抢购|立即|下单|订阅/.test(t) && t.length < 20 && el.offsetParent !== null) {
469 + return el;
470 + }
471 + }
472 + return null;
473 + }
474 +
475 + // ======================== 七、定时触发 ========================
476 + function scheduleAt(timeStr) {
477 + if (S.timerId) { clearTimeout(S.timerId); S.timerId = null; }
478 + const parts = timeStr.split(':').map(Number);
479 + const now = new Date();
480 + const target = new Date(now.getFullYear(), now.getMonth(), now.getDate(), parts[0], parts[1], parts[2] || 0);
481 + if (target <= now) { log('⚠️ 目标时间已过'); return; }
482 + const ms = target - now;
483 + log(`⏰ 已设定: ${timeStr} (${Math.ceil(ms / 1000)}秒后)`);
484 + S.timerId = setTimeout(() => {
485 + S.timerId = null;
486 + log('⏰ 时间到! 启动抢购!');
487 + if (S.captured) {
488 + startProactive();
489 + } else {
490 + const btn = findBuyButton();
491 + if (btn) { btn.click(); log('🖱 定时: 已点击购买按钮'); }
492 + else { log('⚠️ 未找到按钮'); alert('定时到了!请手动点击购买!'); }
493 + }
494 + }, ms - 50);
495 + refreshUI();
496 + }
497 +
498 + // ======================== 八、浮动控制面板 ========================
499 + function createPanel() {
500 + const panel = document.createElement('div');
501 + panel.id = 'glm-rush';
502 + panel.innerHTML = `
503 + <style>
504 + #glm-rush{position:fixed;top:10px;right:10px;width:340px;background:#1a1a2e;color:#e0e0e0;
505 + border-radius:12px;box-shadow:0 4px 24px rgba(0,0,0,.6);z-index:999999;
506 + font:13px/1.5 Consolas,'Courier New',monospace;user-select:none}
507 + #glm-rush *{box-sizing:border-box;margin:0;padding:0}
508 + .glm-hd{background:linear-gradient(135deg,#0f3460,#16213e);padding:9px 14px;
509 + border-radius:12px 12px 0 0;display:flex;justify-content:space-between;align-items:center;cursor:move}
510 + .glm-hd b{font-size:14px;letter-spacing:.5px}
511 + .glm-mn{background:none;border:none;color:#aaa;cursor:pointer;font-size:20px;line-height:1;padding:0 4px}
512 + .glm-mn:hover{color:#fff}
513 + .glm-bd{padding:12px 14px 14px}
514 + .glm-st{padding:8px;border-radius:8px;text-align:center;font-weight:700;margin-bottom:10px;transition:background .3s}
515 + .glm-st-idle{background:#2d3436}
516 + .glm-st-retrying{background:#e17055;animation:glm-pulse 1s infinite}
517 + .glm-st-success{background:#00b894}
518 + .glm-st-failed{background:#d63031}
519 + @keyframes glm-pulse{50%{opacity:.7}}
520 + .glm-cap{font-size:11px;padding:5px 8px;background:#2d3436;border-radius:6px;margin-bottom:10px;
521 + white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
522 + .glm-row{display:flex;align-items:center;gap:6px;margin-bottom:8px;font-size:12px;flex-wrap:wrap}
523 + .glm-row input[type=number],.glm-row input[type=time]{
524 + width:72px;padding:4px 6px;border:1px solid #444;border-radius:4px;
525 + background:#2d3436;color:#fff;text-align:center;font-size:12px}
526 + .glm-btns{display:flex;gap:8px;margin-bottom:10px}
527 + .glm-btns button{flex:1;padding:8px;border:none;border-radius:6px;cursor:pointer;
528 + font-weight:700;font-size:12px;color:#fff;transition:opacity .2s}
529 + .glm-btns button:hover{opacity:.85}
530 + .glm-b-go{background:#0984e3}
531 + .glm-b-stop{background:#d63031}
532 + .glm-b-time{background:#6c5ce7;flex:0 0 auto !important;padding:4px 10px !important}
533 + .glm-logs{max-height:170px;overflow-y:auto;background:#0d1117;border-radius:6px;
534 + padding:6px 8px;font-size:11px;line-height:1.7}
535 + .glm-logs div{white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
536 + .glm-logs::-webkit-scrollbar{width:4px}
537 + .glm-logs::-webkit-scrollbar-thumb{background:#444;border-radius:2px}
538 + </style>
539 + <div class="glm-hd" id="glm-drag">
540 + <b>🎯 GLM 抢购助手 v3.2</b>
541 + <button class="glm-mn" id="glm-min">−</button>
542 + </div>
543 + <div class="glm-bd" id="glm-bd">
544 + <div class="glm-st glm-st-idle" id="glm-st">⏳ 等待中</div>
545 + <div class="glm-cap" id="glm-cap">📡 请求: 未捕获 — 请先点一次购买按钮</div>
546 + <div class="glm-row">
547 + <span>间隔</span><input type="number" id="glm-delay" value="${CFG.delay}" min="50" max="5000" step="50"><span>ms</span>
548 + <span style="margin-left:6px">上限</span><input type="number" id="glm-max" value="${CFG.maxRetry}" min="10" max="9999" step="10"><span>次</span>
549 + </div>
550 + <div class="glm-row">
551 + <span>定时</span><input type="time" id="glm-time" step="1">
552 + <button class="glm-b-time" id="glm-time-set">设定</button>
553 + <span id="glm-timer-info" style="color:#6c5ce7;font-size:11px"></span>
554 + </div>
555 + <div class="glm-btns">
556 + <button class="glm-b-go" id="glm-go">▶ 主动抢购</button>
557 + <button class="glm-b-stop" id="glm-stop" style="display:none">■ 停止</button>
558 + </div>
559 + <div class="glm-logs" id="glm-logs"></div>
560 + </div>`;
561 + document.body.appendChild(panel);
562 +
563 + const $ = id => document.getElementById(id);
564 + $('glm-go').onclick = startProactive;
565 + $('glm-stop').onclick = stopAll;
566 + $('glm-delay').onchange = function () { CFG.delay = Math.max(50, +this.value || 100); };
567 + $('glm-max').onchange = function () { CFG.maxRetry = Math.max(10, +this.value || 300); };
568 + $('glm-time-set').onclick = function () { const v = $('glm-time').value; if (v) scheduleAt(v); };
569 +
570 + $('glm-min').onclick = function () {
571 + const bd = $('glm-bd');
572 + const hidden = bd.style.display === 'none';
573 + bd.style.display = hidden ? '' : 'none';
574 + this.textContent = hidden ? '−' : '+';
575 + };
576 +
577 + // 拖拽
578 + let sx, sy, sl, st;
579 + $('glm-drag').onmousedown = function (e) {
580 + sx = e.clientX; sy = e.clientY;
581 + const rect = panel.getBoundingClientRect();
582 + sl = rect.left; st = rect.top;
583 + const onMove = function (e) {
584 + panel.style.left = (sl + e.clientX - sx) + 'px';
585 + panel.style.top = (st + e.clientY - sy) + 'px';
586 + panel.style.right = 'auto';
587 + };
588 + const onUp = function () {
589 + document.removeEventListener('mousemove', onMove);
590 + document.removeEventListener('mouseup', onUp);
591 + };
592 + document.addEventListener('mousemove', onMove);
593 + document.addEventListener('mouseup', onUp);
594 + };
595 +
596 + log('🚀 v3.2 已加载 (preview+check 双重校验)');
597 +
598 + // 启动错误弹窗监控
599 + setupDialogWatcher();
600 + }
601 +
602 + function refreshUI() {
603 + const stEl = document.getElementById('glm-st');
604 + if (!stEl) return;
605 + stEl.className = 'glm-st glm-st-' + S.status;
606 + stEl.textContent = S.status === 'idle' ? '⏳ 等待中'
607 + : S.status === 'retrying' ? `🔄 重试中… ${S.count}/${CFG.maxRetry}`
608 + : S.status === 'success' ? `✅ 成功! bizId=${S.bizId}`
609 + : `❌ 失败 (${S.count}次)`;
610 +
611 + const capEl = document.getElementById('glm-cap');
612 + if (capEl) {
613 + capEl.textContent = S.captured
614 + ? `📡 已捕获: ${S.captured.method} …${S.captured.url.split('?')[0].slice(-30)}`
615 + : '📡 请求: 未捕获 — 请先点一次购买按钮';
616 + }
617 +
618 + const goBtn = document.getElementById('glm-go');
619 + const stopBtn = document.getElementById('glm-stop');
620 + if (goBtn && stopBtn) {
621 + goBtn.style.display = S.status === 'retrying' ? 'none' : '';
622 + stopBtn.style.display = S.status === 'retrying' ? '' : 'none';
623 + }
624 + }
625 +
626 + function refreshLog() {
627 + const el = document.getElementById('glm-logs');
628 + if (!el) return;
629 + const last = S.logs[S.logs.length - 1];
630 + if (last) {
631 + const div = document.createElement('div');
632 + div.textContent = last;
633 + el.appendChild(div);
634 + while (el.children.length > 80) el.removeChild(el.firstChild);
635 + el.scrollTop = el.scrollHeight;
636 + }
637 + }
638 +
639 + // ======================== 启动 ========================
640 + console.log('[GLM抢购] 🚀 v3.2 全自动版 (preview+check 双重校验) 已注入');
641 +
642 + if (document.readyState === 'loading') {
643 + document.addEventListener('DOMContentLoaded', createPanel);
644 + } else {
645 + createPanel();
646 + }
647 + })();
Новее Позже