└── cursor-auto-fill.user.js
/cursor-auto-fill.user.js:
--------------------------------------------------------------------------------
1 | // ==UserScript==
2 | // @name Cursor试用生成绑卡自动填写
3 | // @namespace http://tampermonkey.net/
4 | // @version 2.6.0
5 | // @description 自动填写 Cursor 试用页面的支付信息,支付失败自动重试
6 | // @author Yan
7 | // @match https://checkout.stripe.com/c/pay/*
8 | // @match https://www.google.com/*
9 | // @match https://www.baidu.com/*
10 | // @grant GM_setValue
11 | // @grant GM_getValue
12 | // @grant GM_addStyle
13 | // @grant GM_registerMenuCommand
14 | // @run-at document-end
15 | // ==/UserScript==
16 |
17 | (function() {
18 | 'use strict';
19 |
20 | // ===== 全局状态控制 =====
21 | let isRunning = false; // 是否正在执行
22 | let shouldStop = false; // 是否应该停止
23 |
24 | // ===== 添加CSS样式 =====
25 | const style = document.createElement('style');
26 | style.textContent = `
27 | @keyframes cursor-pulse {
28 | 0%, 100% { opacity: 1; }
29 | 50% { opacity: 0.5; }
30 | }
31 |
32 | @keyframes cursor-spin {
33 | from { transform: rotate(0deg); }
34 | to { transform: rotate(360deg); }
35 | }
36 |
37 | @keyframes cursor-fade-in {
38 | from { opacity: 0; transform: translateY(10px); }
39 | to { opacity: 1; transform: translateY(0); }
40 | }
41 |
42 | #cursor-auto-fill-container * {
43 | box-sizing: border-box !important;
44 | }
45 |
46 | #cursor-auto-fill-panel {
47 | position: fixed !important;
48 | top: 24px !important;
49 | right: 24px !important;
50 | max-width: 420px !important;
51 | min-width: 360px !important;
52 | width: auto !important;
53 | background: #ffffff !important;
54 | border-radius: 20px !important;
55 | box-shadow: 0 24px 48px rgba(0, 0, 0, 0.1), 0 0 0 1px rgba(0, 0, 0, 0.05) !important;
56 | z-index: 2147483647 !important;
57 | font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif !important;
58 | overflow: visible !important;
59 | animation: cursor-fade-in 0.4s ease !important;
60 | }
61 |
62 | #cursor-auto-fill-header {
63 | background: #ffffff !important;
64 | padding: 24px !important;
65 | color: #000000 !important;
66 | border-bottom: 1px solid #f0f0f0 !important;
67 | }
68 |
69 | #cursor-auto-fill-header-row {
70 | display: flex !important;
71 | justify-content: space-between !important;
72 | align-items: center !important;·
73 | }
74 |
75 | #cursor-auto-fill-header-title {
76 | font-size: 20px !important;
77 | font-weight: 700 !important;
78 | color: #000000 !important;
79 | letter-spacing: -0.5px !important;
80 | }
81 |
82 | #cursor-auto-fill-toggle {
83 | background: #f5f5f5 !important;
84 | border: none !important;
85 | color: #666 !important;
86 | width: 36px !important;
87 | height: 36px !important;
88 | border-radius: 10px !important;
89 | cursor: pointer !important;
90 | font-size: 22px !important;
91 | line-height: 1 !important;
92 | transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1) !important;
93 | }
94 |
95 | #cursor-auto-fill-toggle:hover {
96 | background: #e8e8e8 !important;
97 | transform: scale(1.05) !important;
98 | }
99 |
100 | #cursor-auto-fill-content {
101 | padding: 20px 24px 24px !important;
102 | background: #fafafa !important;
103 | }
104 |
105 | .cursor-section {
106 | background: #ffffff !important;
107 | border-radius: 16px !important;
108 | padding: 20px !important;
109 | margin-bottom: 16px !important;
110 | border: 1px solid #e8e8e8 !important;
111 | transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1) !important;
112 | }
113 |
114 | .cursor-section:hover {
115 | border-color: #d0d0d0 !important;
116 | box-shadow: 0 8px 16px rgba(0, 0, 0, 0.06) !important;
117 | transform: translateY(-2px) !important;
118 | }
119 |
120 | .cursor-section-title {
121 | color: #000000 !important;
122 | font-size: 15px !important;
123 | font-weight: 600 !important;
124 | margin-bottom: 16px !important;
125 | letter-spacing: -0.3px !important;
126 | padding-bottom: 12px !important;
127 | border-bottom: 1px solid #f5f5f5 !important;
128 | }
129 |
130 | .cursor-config-row {
131 | display: flex !important;
132 | justify-content: space-between !important;
133 | align-items: center !important;
134 | margin-bottom: 12px !important;
135 | }
136 |
137 | .cursor-config-row:last-child {
138 | margin-bottom: 0 !important;
139 | }
140 |
141 | .cursor-config-label {
142 | color: #333 !important;
143 | font-size: 14px !important;
144 | font-weight: 500 !important;
145 | }
146 |
147 | .cursor-toggle-switch {
148 | position: relative !important;
149 | display: inline-block !important;
150 | width: 50px !important;
151 | height: 28px !important;
152 | }
153 |
154 | .cursor-toggle-switch input {
155 | opacity: 0 !important;
156 | width: 0 !important;
157 | height: 0 !important;
158 | position: absolute !important;
159 | pointer-events: none !important;
160 | }
161 |
162 | .cursor-toggle-slider {
163 | position: absolute !important;
164 | cursor: pointer !important;
165 | top: 0 !important;
166 | left: 0 !important;
167 | right: 0 !important;
168 | bottom: 0 !important;
169 | background-color: #e0e0e0 !important;
170 | transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1) !important;
171 | border-radius: 28px !important;
172 | }
173 |
174 | .cursor-toggle-dot {
175 | position: absolute !important;
176 | height: 22px !important;
177 | width: 22px !important;
178 | left: 3px !important;
179 | bottom: 3px !important;
180 | background-color: white !important;
181 | border-radius: 50% !important;
182 | transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1) !important;
183 | box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1) !important;
184 | }
185 |
186 | #cursor-bin-input {
187 | width: 100% !important;
188 | padding: 14px 16px !important;
189 | border: 1.5px solid #e0e0e0 !important;
190 | border-radius: 12px !important;
191 | font-size: 14px !important;
192 | font-family: 'SF Mono', Monaco, Consolas, monospace !important;
193 | background: #ffffff !important;
194 | color: #000000 !important;
195 | transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1) !important;
196 | }
197 |
198 | #cursor-bin-input:focus {
199 | outline: none !important;
200 | border-color: #000000 !important;
201 | box-shadow: 0 0 0 4px rgba(0, 0, 0, 0.05) !important;
202 | }
203 |
204 | .cursor-button-group {
205 | display: flex !important;
206 | gap: 12px !important;
207 | margin-top: 20px !important;
208 | }
209 |
210 | .cursor-btn {
211 | flex: 1 !important;
212 | padding: 14px 20px !important;
213 | border: none !important;
214 | border-radius: 12px !important;
215 | font-size: 15px !important;
216 | font-weight: 600 !important;
217 | cursor: pointer !important;
218 | transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1) !important;
219 | letter-spacing: -0.2px !important;
220 | }
221 |
222 | .cursor-btn-start {
223 | background: #000000 !important;
224 | color: white !important;
225 | box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15) !important;
226 | border: none !important;
227 | }
228 |
229 | .cursor-btn-start:hover {
230 | background: #1a1a1a !important;
231 | transform: translateY(-3px) !important;
232 | box-shadow: 0 8px 20px rgba(0, 0, 0, 0.25) !important;
233 | }
234 |
235 | .cursor-btn-start:active {
236 | transform: translateY(-1px) !important;
237 | }
238 |
239 | .cursor-btn-running {
240 | background: #007AFF !important;
241 | color: white !important;
242 | box-shadow: 0 4px 12px rgba(0, 122, 255, 0.3) !important;
243 | border: none !important;
244 | }
245 |
246 | .cursor-btn-running:hover {
247 | background: #0066CC !important;
248 | transform: scale(1.02) !important;
249 | box-shadow: 0 6px 16px rgba(0, 122, 255, 0.4) !important;
250 | }
251 |
252 | .cursor-btn-stop {
253 | background: #ffffff !important;
254 | color: #ff3b30 !important;
255 | border: 2px solid #ff3b30 !important;
256 | box-shadow: 0 2px 8px rgba(255, 59, 48, 0.15) !important;
257 | }
258 |
259 | .cursor-btn-stop:hover {
260 | background: #fff5f5 !important;
261 | transform: translateY(-3px) !important;
262 | box-shadow: 0 6px 16px rgba(255, 59, 48, 0.3) !important;
263 | }
264 |
265 | .cursor-btn-stop:active {
266 | transform: translateY(-1px) !important;
267 | }
268 |
269 | .cursor-btn-icon {
270 | display: inline-block !important;
271 | margin-right: 6px !important;
272 | }
273 |
274 | .cursor-btn-icon.spinning {
275 | animation: cursor-spin 1s linear infinite !important;
276 | }
277 |
278 | #cursor-log-container {
279 | background: #ffffff !important;
280 | border-radius: 16px !important;
281 | padding: 20px !important;
282 | max-height: 260px !important;
283 | overflow-y: auto !important;
284 | border: 1px solid #e8e8e8 !important;
285 | }
286 |
287 | #cursor-log-title {
288 | color: #000000 !important;
289 | font-size: 15px !important;
290 | font-weight: 600 !important;
291 | margin-bottom: 16px !important;
292 | letter-spacing: -0.3px !important;
293 | padding-bottom: 12px !important;
294 | border-bottom: 1px solid #f5f5f5 !important;
295 | }
296 |
297 | #cursor-auto-fill-logs {
298 | font-family: 'SF Mono', 'Monaco', 'Menlo', 'Consolas', monospace !important;
299 | font-size: 12px !important;
300 | line-height: 2 !important;
301 | }
302 |
303 | #cursor-auto-fill-logs::-webkit-scrollbar {
304 | width: 6px !important;
305 | }
306 |
307 | #cursor-auto-fill-logs::-webkit-scrollbar-track {
308 | background: transparent !important;
309 | }
310 |
311 | #cursor-auto-fill-logs::-webkit-scrollbar-thumb {
312 | background: #d8d8d8 !important;
313 | border-radius: 10px !important;
314 | transition: background 0.2s !important;
315 | }
316 |
317 | #cursor-auto-fill-logs::-webkit-scrollbar-thumb:hover {
318 | background: #b8b8b8 !important;
319 | }
320 |
321 | .cursor-log-item {
322 | margin: 8px 0 !important;
323 | padding: 0 !important;
324 | animation: cursor-fade-in 0.3s ease !important;
325 | }
326 |
327 | .cursor-log-timestamp {
328 | color: #999 !important;
329 | font-weight: 500 !important;
330 | margin-right: 8px !important;
331 | }
332 |
333 | .cursor-log-info .cursor-log-text {
334 | color: #007AFF !important;
335 | }
336 |
337 | .cursor-log-success .cursor-log-text {
338 | color: #34C759 !important;
339 | font-weight: 600 !important;
340 | }
341 |
342 | .cursor-log-error .cursor-log-text {
343 | color: #FF3B30 !important;
344 | font-weight: 600 !important;
345 | }
346 |
347 | .cursor-log-warning .cursor-log-text {
348 | color: #FF9500 !important;
349 | font-weight: 600 !important;
350 | }
351 |
352 | .cursor-status-badge {
353 | display: inline-block !important;
354 | padding: 6px 12px !important;
355 | border-radius: 8px !important;
356 | font-size: 13px !important;
357 | font-weight: 600 !important;
358 | margin-top: 12px !important;
359 | letter-spacing: -0.2px !important;
360 | }
361 |
362 | .cursor-status-idle {
363 | background: #f5f5f5 !important;
364 | color: #666 !important;
365 | }
366 |
367 | .cursor-status-running {
368 | background: #e3f2fd !important;
369 | color: #007AFF !important;
370 | animation: cursor-pulse 2s ease-in-out infinite !important;
371 | }
372 |
373 | .cursor-status-stopped {
374 | background: #ffebee !important;
375 | color: #ff3b30 !important;
376 | }
377 | `;
378 | document.head.appendChild(style);
379 |
380 | // ===== 配置管理 =====
381 | const CONFIG_KEY = 'cursor_auto_fill_config';
382 | const QUEUE_KEY = 'cursor_url_queue';
383 | const defaultConfig = {
384 | autoFill: true,
385 | autoSubmit: true,
386 | bin: '379240xxxxxxxxx',
387 | batchMode: false,
388 | maxConcurrent: 1 // 单页面模式
389 | };
390 |
391 | function getConfig() {
392 | const saved = GM_getValue(CONFIG_KEY);
393 | return saved ? JSON.parse(saved) : defaultConfig;
394 | }
395 |
396 | function saveConfig(config) {
397 | GM_setValue(CONFIG_KEY, JSON.stringify(config));
398 | }
399 |
400 | let config = getConfig();
401 |
402 | // ===== URL队列管理 =====
403 | class URLQueue {
404 | constructor() {
405 | this.queue = this.load();
406 | }
407 |
408 | load() {
409 | const saved = GM_getValue(QUEUE_KEY);
410 | return saved ? JSON.parse(saved) : { urls: [], current: 0, status: 'idle' };
411 | }
412 |
413 | save() {
414 | GM_setValue(QUEUE_KEY, JSON.stringify(this.queue));
415 | }
416 |
417 | addURLs(urlText) {
418 | const urls = urlText.split('\n')
419 | .map(url => url.trim())
420 | .filter(url => url && url.startsWith('http'));
421 |
422 | this.queue = {
423 | urls: urls.map(url => ({ url, status: 'pending', timestamp: Date.now() })),
424 | current: 0,
425 | status: 'idle'
426 | };
427 | this.save();
428 | logger.log(`已添加 ${urls.length} 个URL到队列`, 'success');
429 | return urls.length;
430 | }
431 |
432 | getNext(count = 5) {
433 | const pending = this.queue.urls.filter(item => item.status === 'pending');
434 | const next = pending.slice(0, count);
435 | next.forEach(item => item.status = 'processing');
436 | this.save();
437 | return next.map(item => item.url);
438 | }
439 |
440 | markCompleted(url) {
441 | const item = this.queue.urls.find(item => item.url === url);
442 | if (item) {
443 | item.status = 'completed';
444 | this.queue.current++;
445 | this.save();
446 | }
447 | }
448 |
449 | getStats() {
450 | const total = this.queue.urls.length;
451 | const completed = this.queue.urls.filter(item => item.status === 'completed').length;
452 | const processing = this.queue.urls.filter(item => item.status === 'processing').length;
453 | const pending = this.queue.urls.filter(item => item.status === 'pending').length;
454 | return { total, completed, processing, pending };
455 | }
456 |
457 | clear() {
458 | this.queue = { urls: [], current: 0, status: 'idle' };
459 | this.save();
460 | }
461 |
462 | isComplete() {
463 | return this.queue.urls.length > 0 &&
464 | this.queue.urls.every(item => item.status === 'completed');
465 | }
466 | }
467 |
468 | const urlQueue = new URLQueue();
469 |
470 | // ===== 日志系统 =====
471 | class Logger {
472 | constructor() {
473 | this.logs = [];
474 | this.maxLogs = 100;
475 | }
476 |
477 | log(message, type = 'info') {
478 | const timestamp = new Date().toLocaleTimeString();
479 | const logEntry = { timestamp, message, type };
480 | this.logs.push(logEntry);
481 | if (this.logs.length > this.maxLogs) {
482 | this.logs.shift();
483 | }
484 | console.log(`[Cursor Auto Fill] ${timestamp} [${type.toUpperCase()}] ${message}`);
485 | this.updateLogDisplay();
486 | this.autoScroll();
487 | }
488 |
489 | updateLogDisplay() {
490 | const logContainer = document.getElementById('cursor-auto-fill-logs');
491 | if (logContainer) {
492 | logContainer.innerHTML = this.logs.map(log =>
493 | `
494 | ${log.timestamp}
495 | ${log.message}
496 |
`
497 | ).join('');
498 | }
499 | }
500 |
501 | autoScroll() {
502 | const logContainer = document.getElementById('cursor-log-container');
503 | if (logContainer) {
504 | // 丝滑滚动到底部
505 | logContainer.scrollTo({
506 | top: logContainer.scrollHeight,
507 | behavior: 'smooth'
508 | });
509 | }
510 | }
511 | }
512 |
513 | const logger = new Logger();
514 |
515 | // ===== 更新UI状态 =====
516 | function updateUIState(state) {
517 | const actionBtn = document.getElementById('cursor-action-btn');
518 | const btnIcon = document.getElementById('cursor-btn-icon');
519 | const btnText = document.getElementById('cursor-btn-text');
520 | const statusBadge = document.getElementById('cursor-status-badge');
521 |
522 | if (state === 'running') {
523 | // 执行中状态
524 | if (actionBtn) {
525 | actionBtn.className = 'cursor-btn cursor-btn-running';
526 | }
527 | if (btnIcon) {
528 | btnIcon.className = 'cursor-btn-icon spinning';
529 | btnIcon.textContent = '⏸';
530 | }
531 | if (btnText) btnText.textContent = '执行中...';
532 | if (statusBadge) {
533 | statusBadge.className = 'cursor-status-badge cursor-status-running';
534 | statusBadge.textContent = '● 执行中';
535 | statusBadge.style.setProperty('background', '#e3f2fd', 'important');
536 | statusBadge.style.setProperty('color', '#007AFF', 'important');
537 | }
538 | } else if (state === 'stopped') {
539 | // 已停止状态
540 | if (actionBtn) {
541 | actionBtn.className = 'cursor-btn cursor-btn-stop';
542 | }
543 | if (btnIcon) {
544 | btnIcon.className = 'cursor-btn-icon';
545 | btnIcon.textContent = '■';
546 | }
547 | if (btnText) btnText.textContent = '已停止';
548 | if (statusBadge) {
549 | statusBadge.className = 'cursor-status-badge cursor-status-stopped';
550 | statusBadge.textContent = '● 已停止';
551 | statusBadge.style.setProperty('background', '#ffebee', 'important');
552 | statusBadge.style.setProperty('color', '#ff3b30', 'important');
553 | }
554 | } else {
555 | // 待机状态
556 | if (actionBtn) {
557 | actionBtn.className = 'cursor-btn cursor-btn-start';
558 | }
559 | if (btnIcon) {
560 | btnIcon.className = 'cursor-btn-icon';
561 | btnIcon.textContent = '▶';
562 | }
563 | if (btnText) btnText.textContent = '开始填写';
564 | if (statusBadge) {
565 | statusBadge.className = 'cursor-status-badge cursor-status-idle';
566 | statusBadge.textContent = '○ 待机中';
567 | statusBadge.style.setProperty('background', '#f5f5f5', 'important');
568 | statusBadge.style.setProperty('color', '#666', 'important');
569 | }
570 | }
571 | }
572 |
573 | // ===== Luhn算法生成卡号 =====
574 | function generateCardNumber(bin) {
575 | logger.log('开始生成卡号...', 'info');
576 |
577 | let cardNumber = '';
578 | for (let char of bin) {
579 | if (char.toLowerCase() === 'x') {
580 | cardNumber += Math.floor(Math.random() * 10);
581 | } else {
582 | cardNumber += char;
583 | }
584 | }
585 |
586 | cardNumber = cardNumber.slice(0, -1);
587 |
588 | let sum = 0;
589 | let shouldDouble = true;
590 |
591 | for (let i = cardNumber.length - 1; i >= 0; i--) {
592 | let digit = parseInt(cardNumber[i]);
593 |
594 | if (shouldDouble) {
595 | digit *= 2;
596 | if (digit > 9) {
597 | digit -= 9;
598 | }
599 | }
600 |
601 | sum += digit;
602 | shouldDouble = !shouldDouble;
603 | }
604 |
605 | const checkDigit = (10 - (sum % 10)) % 10;
606 | cardNumber += checkDigit;
607 |
608 | logger.log(`卡号生成成功: ${cardNumber.replace(/(\d{4})/g, '$1 ').trim()}`, 'success');
609 | return cardNumber;
610 | }
611 |
612 | // ===== 生成随机卡片信息 =====
613 | function generateCardInfo() {
614 | // 每次都随机生成379240开头的卡号
615 | const cardNumber = generateCardNumber(config.bin);
616 |
617 | const now = new Date();
618 | const month = String(Math.floor(Math.random() * 12) + 1).padStart(2, '0');
619 | const year = String(now.getFullYear() + Math.floor(Math.random() * 2) + 1).slice(-2);
620 |
621 | // AmEx卡(34/37开头)CVV是4位,其他卡是3位
622 | const cardPrefix = cardNumber.substring(0, 2);
623 | const isAmex = cardPrefix === '34' || cardPrefix === '37';
624 |
625 | logger.log(`🔍 卡号前缀: ${cardPrefix}, 是否AmEx: ${isAmex}`, 'info');
626 |
627 | const cvv = isAmex
628 | ? String(Math.floor(Math.random() * 9000) + 1000) // 4位:1000-9999
629 | : String(Math.floor(Math.random() * 900) + 100); // 3位:100-999
630 |
631 | logger.log(`✅ 生成到期日: ${month}/${year}, CVV: ${cvv} (${isAmex ? 'AmEx-4位' : '普通-3位'})`, 'success');
632 |
633 | return { cardNumber, month, year, cvv };
634 | }
635 |
636 | // ===== 生成随机美国地址 =====
637 | function generateUSAddress() {
638 | logger.log('生成随机美国地址...', 'info');
639 |
640 | const firstNames = ['James', 'John', 'Robert', 'Michael', 'William', 'David', 'Richard', 'Joseph', 'Thomas', 'Charles'];
641 | const lastNames = ['Smith', 'Johnson', 'Williams', 'Brown', 'Jones', 'Garcia', 'Miller', 'Davis', 'Rodriguez', 'Martinez'];
642 |
643 | const streets = ['Main St', 'Oak Ave', 'Pine St', 'Maple Ave', 'Cedar St', 'Elm St', 'Washington St', 'Lake St', 'Hill St', 'Park Ave'];
644 | const cities = [
645 | { name: 'New York', state: 'New York', zip: '10001' },
646 | { name: 'Los Angeles', state: 'California', zip: '90001' },
647 | { name: 'Chicago', state: 'Illinois', zip: '60601' },
648 | { name: 'Houston', state: 'Texas', zip: '77001' },
649 | { name: 'Phoenix', state: 'Arizona', zip: '85001' },
650 | { name: 'Philadelphia', state: 'Pennsylvania', zip: '19101' },
651 | { name: 'San Antonio', state: 'Texas', zip: '78201' },
652 | { name: 'San Diego', state: 'California', zip: '92101' },
653 | { name: 'Dallas', state: 'Texas', zip: '75201' },
654 | { name: 'San Jose', state: 'California', zip: '95101' }
655 | ];
656 |
657 | const firstName = firstNames[Math.floor(Math.random() * firstNames.length)];
658 | const lastName = lastNames[Math.floor(Math.random() * lastNames.length)];
659 | const fullName = `${firstName} ${lastName}`;
660 |
661 | const streetNumber = Math.floor(Math.random() * 9000) + 1000;
662 | const street = streets[Math.floor(Math.random() * streets.length)];
663 | const address1 = `${streetNumber} ${street}`;
664 |
665 | const cityInfo = cities[Math.floor(Math.random() * cities.length)];
666 |
667 | logger.log(`地址生成成功: ${fullName}, ${address1}, ${cityInfo.name}, ${cityInfo.state}`, 'success');
668 |
669 | return {
670 | name: fullName,
671 | address1: address1,
672 | city: cityInfo.name,
673 | state: cityInfo.state,
674 | zip: cityInfo.zip
675 | };
676 | }
677 |
678 | // ===== 等待元素出现 =====
679 | function waitForElement(selector, timeout = 10000) {
680 | return new Promise((resolve, reject) => {
681 | if (shouldStop) {
682 | reject(new Error('用户停止执行'));
683 | return;
684 | }
685 |
686 | const element = document.querySelector(selector);
687 | if (element) {
688 | return resolve(element);
689 | }
690 |
691 | const observer = new MutationObserver(() => {
692 | if (shouldStop) {
693 | observer.disconnect();
694 | reject(new Error('用户停止执行'));
695 | return;
696 | }
697 |
698 | const element = document.querySelector(selector);
699 | if (element) {
700 | observer.disconnect();
701 | resolve(element);
702 | }
703 | });
704 |
705 | observer.observe(document.body, {
706 | childList: true,
707 | subtree: true
708 | });
709 |
710 | setTimeout(() => {
711 | observer.disconnect();
712 | reject(new Error(`等待元素超时: ${selector}`));
713 | }, timeout);
714 | });
715 | }
716 |
717 | // ===== 等待一段时间 =====
718 | function sleep(ms) {
719 | return new Promise(resolve => {
720 | if (shouldStop) {
721 | resolve();
722 | return;
723 | }
724 | setTimeout(resolve, ms);
725 | });
726 | }
727 |
728 | // ===== 填充输入框(使用setter触发React)=====
729 | async function fillInput(selector, value, label) {
730 | if (shouldStop) {
731 | throw new Error('用户停止执行');
732 | }
733 |
734 | try {
735 | logger.log(`填写${label}...`, 'info');
736 | const input = await waitForElement(selector, 8000);
737 |
738 | // 后台模式下减少延迟
739 | const isBackground = document.hidden;
740 | const delay = isBackground ? 5 : 15; // 后台模式下加快速度
741 |
742 | input.focus();
743 | await sleep(isBackground ? 30 : 100);
744 |
745 | const nativeInputValueSetter = Object.getOwnPropertyDescriptor(
746 | window.HTMLInputElement.prototype,
747 | 'value'
748 | ).set;
749 |
750 | nativeInputValueSetter.call(input, '');
751 | input.dispatchEvent(new Event('input', { bubbles: true }));
752 | await sleep(isBackground ? 20 : 50);
753 |
754 | for (let i = 0; i < value.length; i++) {
755 | if (shouldStop) throw new Error('用户停止执行');
756 |
757 | const currentValue = value.substring(0, i + 1);
758 | nativeInputValueSetter.call(input, currentValue);
759 |
760 | input.dispatchEvent(new KeyboardEvent('keydown', {
761 | bubbles: true,
762 | cancelable: true,
763 | key: value[i],
764 | code: `Key${value[i].toUpperCase()}`
765 | }));
766 |
767 | input.dispatchEvent(new Event('input', {
768 | bubbles: true,
769 | cancelable: true
770 | }));
771 |
772 | input.dispatchEvent(new KeyboardEvent('keyup', {
773 | bubbles: true,
774 | key: value[i],
775 | code: `Key${value[i].toUpperCase()}`
776 | }));
777 |
778 | await sleep(delay);
779 | }
780 |
781 | await sleep(isBackground ? 50 : 100);
782 | input.dispatchEvent(new Event('change', { bubbles: true }));
783 | await sleep(isBackground ? 50 : 100);
784 |
785 | if (input.value !== value) {
786 | nativeInputValueSetter.call(input, value);
787 | input.dispatchEvent(new Event('input', { bubbles: true }));
788 | await sleep(isBackground ? 50 : 100);
789 | }
790 |
791 | input.blur();
792 | await sleep(isBackground ? 200 : 400);
793 |
794 | const actualValue = input.value;
795 | const normalizedActual = actualValue.replace(/[\s\/\-]/g, '');
796 | const normalizedExpected = value.replace(/[\s\/\-]/g, '');
797 |
798 | if (normalizedActual === normalizedExpected || actualValue === value) {
799 | logger.log(`✓ ${label}填写完成: ${actualValue}`, 'success');
800 | } else {
801 | logger.log(`⚠ ${label}值不匹配 (期望:${value}, 实际:${actualValue})`, 'warning');
802 | }
803 |
804 | return true;
805 | } catch (error) {
806 | if (error.message === '用户停止执行') {
807 | throw error;
808 | }
809 | logger.log(`❌ 填写${label}失败: ${error.message}`, 'error');
810 | return false;
811 | }
812 | }
813 |
814 | // ===== 填写表单 =====
815 | async function fillForm() {
816 | // 防止重复执行
817 | if (isRunning) {
818 | logger.log('⚠ 正在执行中,请勿重复点击', 'warning');
819 | return;
820 | }
821 |
822 | isRunning = true;
823 | shouldStop = false;
824 | updateUIState('running');
825 |
826 | try {
827 | logger.log('========== 开始自动填写 ==========', 'info');
828 |
829 | await sleep(500);
830 |
831 | // 1. 检查并展开银行卡区域
832 | logger.log('检查银行卡区域状态...', 'info');
833 |
834 | const cardRadio = document.querySelector('input[type="radio"][value="card"]');
835 | const isAlreadyExpanded = cardRadio && cardRadio.checked;
836 |
837 | if (isAlreadyExpanded) {
838 | logger.log('✓ 银行卡区域已展开', 'success');
839 | await sleep(200);
840 | } else {
841 | logger.log('点击展开银行卡区域...', 'info');
842 | const cardButton = document.querySelector('[data-testid="card-accordion-item-button"]');
843 |
844 | if (cardButton) {
845 | cardButton.click();
846 | await sleep(800);
847 |
848 | const radioAfterClick = document.querySelector('input[type="radio"][value="card"]');
849 | if (radioAfterClick && radioAfterClick.checked) {
850 | logger.log('✓ 银行卡区域已展开', 'success');
851 | } else {
852 | throw new Error('银行卡区域展开失败');
853 | }
854 | } else {
855 | throw new Error('未找到银行卡展开按钮');
856 | }
857 | }
858 |
859 | if (shouldStop) throw new Error('用户停止执行');
860 |
861 | // 等待输入框渲染
862 | logger.log('等待输入框渲染...', 'info');
863 | await waitForElement('input[name="number"], input[placeholder*="卡号"], input[autocomplete="cc-number"]', 5000);
864 | logger.log('✓ 输入框已就绪', 'success');
865 |
866 | // 生成信息
867 | const cardInfo = generateCardInfo();
868 | const address = generateUSAddress();
869 |
870 | // 2. 填写卡号
871 | await fillInput(
872 | 'input[name="number"], input[placeholder*="卡号"], input[autocomplete="cc-number"]',
873 | cardInfo.cardNumber,
874 | '卡号'
875 | );
876 |
877 | // 3. 填写到期日
878 | await fillInput(
879 | 'input[name="expiry"], input[placeholder*="到期"], input[autocomplete="cc-exp"]',
880 | `${cardInfo.month}${cardInfo.year}`,
881 | '到期日'
882 | );
883 |
884 | // 4. 填写CVV
885 | await fillInput(
886 | 'input[name="cvc"], input[placeholder*="CVC"], input[placeholder*="安全码"], input[autocomplete="cc-csc"]',
887 | cardInfo.cvv,
888 | 'CVV'
889 | );
890 |
891 | // 5. 填写持卡人姓名
892 | await fillInput(
893 | 'input[name="name"], input[placeholder*="姓名"], input[autocomplete="cc-name"]',
894 | address.name,
895 | '持卡人姓名'
896 | );
897 |
898 | if (shouldStop) throw new Error('用户停止执行');
899 |
900 | // 6. 点击"手动输入地址"按钮(如果存在)
901 | logger.log('查找"手动输入地址"按钮...', 'info');
902 | const manualAddressButton = Array.from(document.querySelectorAll('button')).find(btn =>
903 | btn.textContent.includes('手动输入地址') ||
904 | btn.textContent.includes('Enter address manually')
905 | );
906 |
907 | if (manualAddressButton) {
908 | logger.log('点击"手动输入地址"...', 'info');
909 | manualAddressButton.click();
910 | await sleep(500);
911 | logger.log('✓ 已展开手动输入', 'success');
912 | }
913 |
914 | // 7. 选择国家 - 美国
915 | logger.log('选择国家:美国...', 'info');
916 | const allSelects = document.querySelectorAll('select');
917 | const countrySelect = document.querySelector('select[name="billingCountry"]') || allSelects[0];
918 |
919 | if (countrySelect) {
920 | logger.log(`当前国家: ${countrySelect.value}`, 'info');
921 |
922 | let usOption = null;
923 | for (let option of countrySelect.options) {
924 | if (option.value === 'US' || option.textContent.trim() === '美国') {
925 | usOption = option;
926 | break;
927 | }
928 | }
929 |
930 | if (usOption) {
931 | countrySelect.value = usOption.value;
932 | countrySelect.dispatchEvent(new Event('input', { bubbles: true }));
933 | countrySelect.dispatchEvent(new Event('change', { bubbles: true }));
934 | await sleep(1500);
935 | logger.log('✓ 已选择美国', 'success');
936 | }
937 | }
938 |
939 | if (shouldStop) throw new Error('用户停止执行');
940 |
941 | // 8. 填写地址
942 | await fillInput(
943 | 'input[name="line1"], input[placeholder*="地址"]',
944 | address.address1,
945 | '地址'
946 | );
947 |
948 | // 9. 填写城市
949 | await fillInput(
950 | 'input[name="city"], input[placeholder*="城市"]',
951 | address.city,
952 | '城市'
953 | );
954 |
955 | // 10. 填写邮编
956 | await fillInput(
957 | 'input[name="zip"], input[placeholder*="邮编"]',
958 | address.zip,
959 | '邮编'
960 | );
961 |
962 | await sleep(800);
963 |
964 | // 11. 检查州
965 | const allSelectsAfter = document.querySelectorAll('select');
966 | const stateSelect = allSelectsAfter.length > 1 ? allSelectsAfter[1] : null;
967 |
968 | if (stateSelect && stateSelect.value) {
969 | logger.log(`✓ 州已自动选择: ${stateSelect.value}`, 'success');
970 | }
971 |
972 | logger.log('========== 所有字段填写完成 ==========', 'success');
973 |
974 | if (shouldStop) throw new Error('用户停止执行');
975 |
976 | // 检查并提交,等待跳转完成
977 | const submitted = await checkAndSubmit();
978 | return submitted;
979 |
980 | } catch (error) {
981 | if (error.message === '用户停止执行') {
982 | logger.log('========== 执行已停止 ==========', 'warning');
983 | updateUIState('stopped');
984 | } else {
985 | logger.log(`❌ 错误: ${error.message}`, 'error');
986 | updateUIState('idle');
987 | }
988 | return false;
989 | } finally {
990 | isRunning = false;
991 | }
992 | }
993 |
994 | // ===== 此函数已废弃,不再使用 =====
995 | // function waitForNavigation() - 已移除,点击提交后直接2秒关闭
996 |
997 | // ===== 检查并提交 =====
998 | async function checkAndSubmit() {
999 | try {
1000 | logger.log('检查提交按钮状态...', 'info');
1001 |
1002 | const submitButton = document.querySelector('button[type="submit"]');
1003 | if (!submitButton) {
1004 | logger.log('未找到提交按钮', 'warning');
1005 | updateUIState('idle');
1006 | return false;
1007 | }
1008 |
1009 | const checkButtonReady = () => {
1010 | const isBasicReady = !submitButton.disabled &&
1011 | !submitButton.hasAttribute('disabled') &&
1012 | submitButton.offsetParent !== null;
1013 |
1014 | const processingText = submitButton.querySelector('[class*="processing"]');
1015 | const isProcessing = processingText && processingText.offsetParent !== null;
1016 |
1017 | const classList = submitButton.className;
1018 | const hasDisabledClass = classList.includes('disabled') || classList.includes('Disabled');
1019 |
1020 | return isBasicReady && !isProcessing && !hasDisabledClass;
1021 | };
1022 |
1023 | logger.log('快速检测按钮状态(最多5秒)...', 'info');
1024 |
1025 | // 等待按钮就绪 - 优化:每次只等0.2秒
1026 | let attempts = 0;
1027 | const maxAttempts = 25; // 25次 × 0.2秒 = 5秒
1028 |
1029 | while (attempts < maxAttempts && !checkButtonReady()) {
1030 | if (shouldStop) {
1031 | updateUIState('stopped');
1032 | return false;
1033 | }
1034 | await sleep(200); // 每0.2秒检测一次,提速5倍
1035 | attempts++;
1036 | }
1037 |
1038 | if (!checkButtonReady()) {
1039 | logger.log('⚠ 按钮未就绪,但继续尝试提交', 'warning');
1040 | } else {
1041 | logger.log(`✓ 提交按钮已就绪!(用时 ${(attempts * 0.2).toFixed(1)}秒)`, 'success');
1042 | }
1043 |
1044 | if (config.autoSubmit) {
1045 | logger.log('立即提交...', 'info');
1046 |
1047 | if (!shouldStop) {
1048 | submitButton.click();
1049 | logger.log('✓ 已点击提交按钮!', 'success');
1050 |
1051 | // 等待3秒检测支付结果
1052 | await sleep(3000);
1053 |
1054 | // 检测是否支付失败
1055 | if (isPaymentFailed()) {
1056 | logger.log('❌❌❌ 支付失败,需要重试!', 'error');
1057 | updateUIState('idle');
1058 | return false; // 返回false表示失败,需要重试
1059 | }
1060 |
1061 | logger.log('✅ 提交完成!', 'success');
1062 | updateUIState('idle');
1063 |
1064 | // 标记当前URL完成并跳转到下一个
1065 | await jumpToNextURL();
1066 |
1067 | return true;
1068 | }
1069 | } else {
1070 | logger.log('自动提交未启用,请手动点击"开始试用"', 'info');
1071 | updateUIState('idle');
1072 | return false;
1073 | }
1074 |
1075 | } catch (error) {
1076 | logger.log(`检查提交按钮时出错: ${error.message}`, 'error');
1077 | updateUIState('idle');
1078 | return false;
1079 | }
1080 | }
1081 |
1082 | // ===== 停止执行 =====
1083 | function stopExecution() {
1084 | shouldStop = true;
1085 | logger.log('正在停止执行...', 'warning');
1086 | updateUIState('stopped');
1087 |
1088 | setTimeout(() => {
1089 | shouldStop = false;
1090 | updateUIState('idle');
1091 | }, 2000);
1092 | }
1093 |
1094 | // ===== 检测支付是否失败 =====
1095 | function isPaymentFailed() {
1096 | const pageText = document.body.textContent || document.body.innerText || '';
1097 | const pageHTML = document.body.innerHTML || '';
1098 |
1099 | // 检测"支付失败"相关文本
1100 | const failedIndicators = [
1101 | '您的卡被拒绝',
1102 | '卡被拒绝',
1103 | '支付失败',
1104 | '付款失败',
1105 | 'card was declined',
1106 | 'payment failed',
1107 | 'card declined',
1108 | 'declined',
1109 | 'was declined',
1110 | '交易失败',
1111 | '无法处理',
1112 | 'cannot process',
1113 | 'unable to process'
1114 | ];
1115 |
1116 | for (const indicator of failedIndicators) {
1117 | if (pageText.includes(indicator) || pageHTML.includes(indicator)) {
1118 | logger.log(`❌ 检测到支付失败: "${indicator}"`, 'error');
1119 | return true;
1120 | }
1121 | }
1122 |
1123 | return false;
1124 | }
1125 |
1126 | // ===== 检测页面是否已完成支付 =====
1127 | function isAlreadyCompleted() {
1128 | const pageText = document.body.textContent || document.body.innerText || '';
1129 | const pageHTML = document.body.innerHTML || '';
1130 |
1131 | logger.log('检测页面是否已被使用...', 'info');
1132 |
1133 | // 检测"已完成"相关文本(中英文)
1134 | const completedIndicators = [
1135 | '您已全部完成',
1136 | '您已经完成付款',
1137 | '本结账会话已超时',
1138 | '结账会话已超时',
1139 | 'already completed',
1140 | 'payment completed',
1141 | 'session expired',
1142 | 'checkout session has expired',
1143 | 'session has expired',
1144 | '会话已超时',
1145 | '已超时'
1146 | ];
1147 |
1148 | for (const indicator of completedIndicators) {
1149 | if (pageText.includes(indicator) || pageHTML.includes(indicator)) {
1150 | logger.log(`✓✓✓ 检测到已使用标志: "${indicator}"`, 'success');
1151 | return true;
1152 | }
1153 | }
1154 |
1155 | // 检查是否没有输入框(说明不是正常的支付页面)
1156 | const hasInputs = document.querySelectorAll('input[type="text"], input[autocomplete]').length > 0;
1157 | const hasSubmitButton = document.querySelector('button[type="submit"]') !== null;
1158 |
1159 | if (!hasInputs && !hasSubmitButton) {
1160 | logger.log('✓ 页面没有输入框和提交按钮,可能已使用', 'info');
1161 | return true;
1162 | }
1163 |
1164 | logger.log('页面正常,未检测到已使用标志', 'info');
1165 | return false;
1166 | }
1167 |
1168 | // ===== 检测Cursor试用页面 =====
1169 | function isCursorTrialPage() {
1170 | if (!window.location.href.includes('checkout.stripe.com')) {
1171 | return false;
1172 | }
1173 |
1174 | const title = document.title;
1175 | if (title.includes('Cursor')) {
1176 | logger.log('✓ 检测到 Cursor 试用页面', 'success');
1177 | return true;
1178 | }
1179 |
1180 | const pageText = document.body.textContent;
1181 | if (pageText.includes('Cursor Ultra') || pageText.includes('试用 Cursor')) {
1182 | logger.log('✓ 检测到 Cursor 试用页面', 'success');
1183 | return true;
1184 | }
1185 |
1186 | return false;
1187 | }
1188 |
1189 | // ===== 等待页面加载 =====
1190 | function waitForPageLoad() {
1191 | return new Promise((resolve) => {
1192 | let attempts = 0;
1193 | const maxAttempts = 10; // 改为5秒超时
1194 |
1195 | const checkInterval = setInterval(() => {
1196 | attempts++;
1197 |
1198 | if (isCursorTrialPage()) {
1199 | clearInterval(checkInterval);
1200 | logger.log(`✓ 检测到Cursor页面 (用时${attempts}秒)`, 'success');
1201 | resolve(true);
1202 | return;
1203 | }
1204 |
1205 | if (attempts >= maxAttempts) {
1206 | clearInterval(checkInterval);
1207 | logger.log('✓ 页面检测完成,继续执行', 'info');
1208 | resolve(true); // 改为总是返回true,不阻塞
1209 | }
1210 | }, 1000); // 每秒检测一次
1211 | });
1212 | }
1213 |
1214 | // ===== 创建UI界面 =====
1215 | function createUI() {
1216 | const oldUI = document.getElementById('cursor-auto-fill-container');
1217 | if (oldUI) {
1218 | oldUI.remove();
1219 | }
1220 |
1221 | const container = document.createElement('div');
1222 | container.id = 'cursor-auto-fill-container';
1223 |
1224 | container.innerHTML = `
1225 |
1226 |
1233 |
1234 |
1235 |
功能设置
1236 |
1237 |
1238 |
1244 |
1245 |
1246 |
1247 |
1253 |
1254 |
1255 |
1256 |
1257 |
BIN 配置
1258 |
1259 |
1260 |
1261 |
1262 |
批量处理
1263 |
1264 |
1265 |
1268 |
1269 |
1270 |
1271 |
1272 |
1273 |
1274 |
总数: 0 | 完成: 0 | 处理中: 0 | 待处理: 0
1275 |
1276 |
1277 |
1278 |
1279 |
1283 |
1284 |
1285 |
1286 |
执行日志
1287 |
1288 |
1289 |
1290 |
1291 | `;
1292 |
1293 | document.body.appendChild(container);
1294 |
1295 | // JavaScript强制设置所有样式
1296 | const panel = document.getElementById('cursor-auto-fill-panel');
1297 | if (panel) {
1298 | panel.style.setProperty('position', 'fixed', 'important');
1299 | panel.style.setProperty('top', '24px', 'important');
1300 | panel.style.setProperty('right', '24px', 'important');
1301 | panel.style.setProperty('max-width', '420px', 'important');
1302 | panel.style.setProperty('min-width', '360px', 'important');
1303 | panel.style.setProperty('width', 'auto', 'important');
1304 | panel.style.setProperty('background', '#ffffff', 'important');
1305 | panel.style.setProperty('border-radius', '20px', 'important');
1306 | panel.style.setProperty('box-shadow', '0 24px 48px rgba(0, 0, 0, 0.1), 0 0 0 1px rgba(0, 0, 0, 0.05)', 'important');
1307 | panel.style.setProperty('z-index', '2147483647', 'important');
1308 | panel.style.setProperty('overflow', 'visible', 'important');
1309 | panel.style.setProperty('font-family', '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif', 'important');
1310 | }
1311 |
1312 | const header = document.getElementById('cursor-auto-fill-header');
1313 | if (header) {
1314 | header.style.setProperty('background', '#ffffff', 'important');
1315 | header.style.setProperty('padding', '24px', 'important');
1316 | header.style.setProperty('border-bottom', '1px solid #f0f0f0', 'important');
1317 | }
1318 |
1319 | const headerRow = document.getElementById('cursor-auto-fill-header-row');
1320 | if (headerRow) {
1321 | headerRow.style.setProperty('display', 'flex', 'important');
1322 | headerRow.style.setProperty('justify-content', 'space-between', 'important');
1323 | headerRow.style.setProperty('align-items', 'center', 'important');
1324 | headerRow.style.setProperty('margin-bottom', '12px', 'important');
1325 | }
1326 |
1327 | const headerTitle = document.getElementById('cursor-auto-fill-header-title');
1328 | if (headerTitle) {
1329 | headerTitle.style.setProperty('font-size', '20px', 'important');
1330 | headerTitle.style.setProperty('font-weight', '700', 'important');
1331 | headerTitle.style.setProperty('color', '#000000', 'important');
1332 | headerTitle.style.setProperty('letter-spacing', '-0.5px', 'important');
1333 | }
1334 |
1335 | const toggleHeaderBtn = document.getElementById('cursor-auto-fill-toggle');
1336 | if (toggleHeaderBtn) {
1337 | toggleHeaderBtn.style.setProperty('background', '#f5f5f5', 'important');
1338 | toggleHeaderBtn.style.setProperty('border', 'none', 'important');
1339 | toggleHeaderBtn.style.setProperty('color', '#666', 'important');
1340 | toggleHeaderBtn.style.setProperty('width', '36px', 'important');
1341 | toggleHeaderBtn.style.setProperty('height', '36px', 'important');
1342 | toggleHeaderBtn.style.setProperty('border-radius', '10px', 'important');
1343 | toggleHeaderBtn.style.setProperty('cursor', 'pointer', 'important');
1344 | toggleHeaderBtn.style.setProperty('font-size', '22px', 'important');
1345 | toggleHeaderBtn.style.setProperty('line-height', '1', 'important');
1346 | }
1347 |
1348 | const statusBadge = document.getElementById('cursor-status-badge');
1349 | if (statusBadge) {
1350 | statusBadge.style.setProperty('display', 'inline-block', 'important');
1351 | statusBadge.style.setProperty('padding', '6px 12px', 'important');
1352 | statusBadge.style.setProperty('border-radius', '8px', 'important');
1353 | statusBadge.style.setProperty('font-size', '13px', 'important');
1354 | statusBadge.style.setProperty('font-weight', '600', 'important');
1355 | }
1356 |
1357 | const content = document.getElementById('cursor-auto-fill-content');
1358 | if (content) {
1359 | content.style.setProperty('padding', '20px 24px 24px', 'important');
1360 | content.style.setProperty('background', '#fafafa', 'important');
1361 | }
1362 |
1363 | const actionBtn = document.getElementById('cursor-action-btn');
1364 | if (actionBtn) {
1365 | actionBtn.style.setProperty('background', '#000000', 'important');
1366 | actionBtn.style.setProperty('color', 'white', 'important');
1367 | actionBtn.style.setProperty('border', 'none', 'important');
1368 | actionBtn.style.setProperty('box-shadow', '0 4px 12px rgba(0, 0, 0, 0.15)', 'important');
1369 | }
1370 |
1371 | const btnIcon = document.getElementById('cursor-btn-icon');
1372 | if (btnIcon) {
1373 | btnIcon.style.setProperty('display', 'inline-block', 'important');
1374 | btnIcon.style.setProperty('margin-right', '6px', 'important');
1375 | }
1376 |
1377 | const btnText = document.getElementById('cursor-btn-text');
1378 | if (btnText) {
1379 | btnText.style.setProperty('display', 'inline-block', 'important');
1380 | }
1381 |
1382 | // 设置所有section样式
1383 | const sections = document.querySelectorAll('.cursor-section');
1384 | sections.forEach(section => {
1385 | section.style.setProperty('background', '#ffffff', 'important');
1386 | section.style.setProperty('border-radius', '16px', 'important');
1387 | section.style.setProperty('padding', '20px', 'important');
1388 | section.style.setProperty('margin-bottom', '16px', 'important');
1389 | section.style.setProperty('border', '1px solid #e8e8e8', 'important');
1390 | });
1391 |
1392 | // 设置section标题
1393 | const sectionTitles = document.querySelectorAll('.cursor-section-title');
1394 | sectionTitles.forEach(title => {
1395 | title.style.setProperty('color', '#000000', 'important');
1396 | title.style.setProperty('font-size', '15px', 'important');
1397 | title.style.setProperty('font-weight', '600', 'important');
1398 | title.style.setProperty('margin-bottom', '16px', 'important');
1399 | title.style.setProperty('padding-bottom', '12px', 'important');
1400 | title.style.setProperty('border-bottom', '1px solid #f5f5f5', 'important');
1401 | });
1402 |
1403 | // 设置config rows
1404 | const configRows = document.querySelectorAll('.cursor-config-row');
1405 | configRows.forEach(row => {
1406 | row.style.setProperty('display', 'flex', 'important');
1407 | row.style.setProperty('justify-content', 'space-between', 'important');
1408 | row.style.setProperty('align-items', 'center', 'important');
1409 | row.style.setProperty('margin-bottom', '12px', 'important');
1410 | });
1411 |
1412 | // 设置config labels
1413 | const configLabels = document.querySelectorAll('.cursor-config-label');
1414 | configLabels.forEach(label => {
1415 | label.style.setProperty('color', '#333', 'important');
1416 | label.style.setProperty('font-size', '14px', 'important');
1417 | label.style.setProperty('font-weight', '500', 'important');
1418 | });
1419 |
1420 | // 设置toggle switches
1421 | const toggleSwitches = document.querySelectorAll('.cursor-toggle-switch');
1422 | toggleSwitches.forEach(toggle => {
1423 | toggle.style.setProperty('position', 'relative', 'important');
1424 | toggle.style.setProperty('display', 'inline-block', 'important');
1425 | toggle.style.setProperty('width', '50px', 'important');
1426 | toggle.style.setProperty('height', '28px', 'important');
1427 | });
1428 |
1429 | // 强制隐藏所有复选框input
1430 | const toggleInputs = document.querySelectorAll('.cursor-toggle-switch input');
1431 | toggleInputs.forEach(input => {
1432 | input.style.setProperty('opacity', '0', 'important');
1433 | input.style.setProperty('width', '0', 'important');
1434 | input.style.setProperty('height', '0', 'important');
1435 | input.style.setProperty('position', 'absolute', 'important');
1436 | input.style.setProperty('pointer-events', 'none', 'important');
1437 | input.style.setProperty('visibility', 'hidden', 'important');
1438 | input.style.setProperty('display', 'none', 'important');
1439 | });
1440 |
1441 | // 设置toggle sliders
1442 | const toggleSliders = document.querySelectorAll('.cursor-toggle-slider');
1443 | toggleSliders.forEach(slider => {
1444 | slider.style.setProperty('position', 'absolute', 'important');
1445 | slider.style.setProperty('cursor', 'pointer', 'important');
1446 | slider.style.setProperty('top', '0', 'important');
1447 | slider.style.setProperty('left', '0', 'important');
1448 | slider.style.setProperty('right', '0', 'important');
1449 | slider.style.setProperty('bottom', '0', 'important');
1450 | slider.style.setProperty('background-color', '#e0e0e0', 'important');
1451 | slider.style.setProperty('border-radius', '28px', 'important');
1452 | slider.style.setProperty('transition', 'all 0.3s cubic-bezier(0.4, 0, 0.2, 1)', 'important');
1453 | });
1454 |
1455 | // 设置toggle dots(开关圆点)
1456 | const toggleDots = document.querySelectorAll('.cursor-toggle-dot');
1457 | toggleDots.forEach(dot => {
1458 | dot.style.setProperty('position', 'absolute', 'important');
1459 | dot.style.setProperty('height', '22px', 'important');
1460 | dot.style.setProperty('width', '22px', 'important');
1461 | dot.style.setProperty('left', '3px', 'important');
1462 | dot.style.setProperty('bottom', '3px', 'important');
1463 | dot.style.setProperty('background-color', 'white', 'important');
1464 | dot.style.setProperty('border-radius', '50%', 'important');
1465 | dot.style.setProperty('transition', 'all 0.3s cubic-bezier(0.4, 0, 0.2, 1)', 'important');
1466 | dot.style.setProperty('box-shadow', '0 2px 4px rgba(0, 0, 0, 0.1)', 'important');
1467 | });
1468 |
1469 | // 处理开关选中状态
1470 | const autoFillInput = document.getElementById('cursor-auto-fill-toggle-input');
1471 | const autoSubmitInput = document.getElementById('cursor-auto-submit-toggle');
1472 |
1473 | function updateToggleState(input) {
1474 | const slider = input.nextElementSibling;
1475 | const dot = slider ? slider.querySelector('.cursor-toggle-dot') : null;
1476 |
1477 | if (input.checked) {
1478 | if (slider) slider.style.setProperty('background-color', '#000000', 'important');
1479 | if (dot) dot.style.setProperty('transform', 'translateX(22px)', 'important');
1480 | } else {
1481 | if (slider) slider.style.setProperty('background-color', '#e0e0e0', 'important');
1482 | if (dot) dot.style.setProperty('transform', 'translateX(0)', 'important');
1483 | }
1484 | }
1485 |
1486 | if (autoFillInput) updateToggleState(autoFillInput);
1487 | if (autoSubmitInput) updateToggleState(autoSubmitInput);
1488 |
1489 | // 设置BIN输入框
1490 | const binInput = document.getElementById('cursor-bin-input');
1491 | if (binInput) {
1492 | binInput.style.setProperty('width', '100%', 'important');
1493 | binInput.style.setProperty('padding', '14px 16px', 'important');
1494 | binInput.style.setProperty('border', '1.5px solid #e0e0e0', 'important');
1495 | binInput.style.setProperty('border-radius', '12px', 'important');
1496 | binInput.style.setProperty('font-size', '14px', 'important');
1497 | binInput.style.setProperty('background', '#ffffff', 'important');
1498 | binInput.style.setProperty('color', '#000000', 'important');
1499 | binInput.style.setProperty('font-family', '"SF Mono", Monaco, Consolas, monospace', 'important');
1500 | }
1501 |
1502 | // 设置按钮组
1503 | const btnGroup = document.querySelector('.cursor-button-group');
1504 | if (btnGroup) {
1505 | btnGroup.style.setProperty('margin-top', '20px', 'important');
1506 | btnGroup.style.setProperty('margin-bottom', '20px', 'important');
1507 | }
1508 |
1509 | // 设置所有按钮通用样式
1510 | const allBtns = document.querySelectorAll('.cursor-btn');
1511 | allBtns.forEach(btn => {
1512 | btn.style.setProperty('width', '100%', 'important');
1513 | btn.style.setProperty('padding', '14px 20px', 'important');
1514 | btn.style.setProperty('border-radius', '12px', 'important');
1515 | btn.style.setProperty('font-size', '15px', 'important');
1516 | btn.style.setProperty('font-weight', '600', 'important');
1517 | btn.style.setProperty('cursor', 'pointer', 'important');
1518 | btn.style.setProperty('transition', 'all 0.3s cubic-bezier(0.4, 0, 0.2, 1)', 'important');
1519 | btn.style.setProperty('letter-spacing', '-0.2px', 'important');
1520 | });
1521 |
1522 | // 设置日志容器
1523 | const logContainer = document.getElementById('cursor-log-container');
1524 | if (logContainer) {
1525 | logContainer.style.setProperty('background', '#ffffff', 'important');
1526 | logContainer.style.setProperty('border-radius', '16px', 'important');
1527 | logContainer.style.setProperty('padding', '20px', 'important');
1528 | logContainer.style.setProperty('max-height', '260px', 'important');
1529 | logContainer.style.setProperty('overflow-y', 'auto', 'important');
1530 | logContainer.style.setProperty('border', '1px solid #e8e8e8', 'important');
1531 | }
1532 |
1533 | // 设置日志标题
1534 | const logTitle = document.getElementById('cursor-log-title');
1535 | if (logTitle) {
1536 | logTitle.style.setProperty('color', '#000000', 'important');
1537 | logTitle.style.setProperty('font-size', '15px', 'important');
1538 | logTitle.style.setProperty('font-weight', '600', 'important');
1539 | logTitle.style.setProperty('margin-bottom', '16px', 'important');
1540 | logTitle.style.setProperty('padding-bottom', '12px', 'important');
1541 | logTitle.style.setProperty('border-bottom', '1px solid #f5f5f5', 'important');
1542 | }
1543 |
1544 | // 设置日志区域
1545 | const logs = document.getElementById('cursor-auto-fill-logs');
1546 | if (logs) {
1547 | logs.style.setProperty('font-family', '"SF Mono", Monaco, Consolas, monospace', 'important');
1548 | logs.style.setProperty('font-size', '12px', 'important');
1549 | logs.style.setProperty('line-height', '2', 'important');
1550 | }
1551 |
1552 | // 定时检查并设置日志项颜色(因为日志是动态添加的)
1553 | setInterval(() => {
1554 | const logItems = document.querySelectorAll('.cursor-log-item');
1555 | logItems.forEach(item => {
1556 | const timestamp = item.querySelector('.cursor-log-timestamp');
1557 | const text = item.querySelector('.cursor-log-text');
1558 |
1559 | if (timestamp) {
1560 | timestamp.style.setProperty('color', '#a0a0a0', 'important');
1561 | timestamp.style.setProperty('font-weight', '500', 'important');
1562 | }
1563 |
1564 | if (text) {
1565 | if (item.classList.contains('cursor-log-success')) {
1566 | text.style.setProperty('color', '#34C759', 'important');
1567 | text.style.setProperty('font-weight', '600', 'important');
1568 | } else if (item.classList.contains('cursor-log-error')) {
1569 | text.style.setProperty('color', '#FF3B30', 'important');
1570 | text.style.setProperty('font-weight', '600', 'important');
1571 | } else if (item.classList.contains('cursor-log-warning')) {
1572 | text.style.setProperty('color', '#FF9500', 'important');
1573 | text.style.setProperty('font-weight', '600', 'important');
1574 | } else if (item.classList.contains('cursor-log-info')) {
1575 | text.style.setProperty('color', '#007AFF', 'important');
1576 | }
1577 | }
1578 | });
1579 | }, 100); // 每100ms检查一次
1580 |
1581 | logger.log('✓ UI界面已创建', 'success');
1582 |
1583 | setupUIEvents();
1584 | makeDraggable();
1585 | logger.updateLogDisplay();
1586 | }
1587 |
1588 | // ===== 设置UI事件 =====
1589 | function setupUIEvents() {
1590 | // 折叠按钮
1591 | const toggleBtn = document.getElementById('cursor-auto-fill-toggle');
1592 | const content = document.getElementById('cursor-auto-fill-content');
1593 | let isCollapsed = false;
1594 |
1595 | toggleBtn.addEventListener('click', (e) => {
1596 | e.stopPropagation();
1597 | isCollapsed = !isCollapsed;
1598 | content.style.display = isCollapsed ? 'none' : 'block';
1599 | toggleBtn.textContent = isCollapsed ? '+' : '−';
1600 | });
1601 |
1602 | // 自动填写开关
1603 | const autoFillInput = document.getElementById('cursor-auto-fill-toggle-input');
1604 | autoFillInput.addEventListener('change', (e) => {
1605 | config.autoFill = e.target.checked;
1606 | saveConfig(config);
1607 | logger.log(`自动填写已${config.autoFill ? '开启' : '关闭'}`, 'info');
1608 | updateToggleVisual(e.target);
1609 | });
1610 |
1611 | // 自动提交开关
1612 | const autoSubmitInput = document.getElementById('cursor-auto-submit-toggle');
1613 | autoSubmitInput.addEventListener('change', (e) => {
1614 | config.autoSubmit = e.target.checked;
1615 | saveConfig(config);
1616 | logger.log(`自动提交已${config.autoSubmit ? '开启' : '关闭'}`, 'info');
1617 | updateToggleVisual(e.target);
1618 | });
1619 |
1620 | // 更新开关视觉状态的函数
1621 | function updateToggleVisual(input) {
1622 | const slider = input.nextElementSibling;
1623 | const dot = slider ? slider.querySelector('.cursor-toggle-dot') : null;
1624 |
1625 | if (input.checked) {
1626 | if (slider) slider.style.setProperty('background-color', '#000000', 'important');
1627 | if (dot) dot.style.setProperty('transform', 'translateX(22px)', 'important');
1628 | } else {
1629 | if (slider) slider.style.setProperty('background-color', '#e0e0e0', 'important');
1630 | if (dot) dot.style.setProperty('transform', 'translateX(0)', 'important');
1631 | }
1632 | }
1633 |
1634 | // BIN输入
1635 | document.getElementById('cursor-bin-input').addEventListener('change', (e) => {
1636 | config.bin = e.target.value;
1637 | saveConfig(config);
1638 | logger.log(`BIN已更新: ${config.bin}`, 'info');
1639 | });
1640 |
1641 | // 批量处理按钮
1642 | document.getElementById('cursor-batch-start').addEventListener('click', (e) => {
1643 | e.stopPropagation();
1644 | const urlText = document.getElementById('cursor-url-batch').value;
1645 | if (!urlText.trim()) {
1646 | logger.log('请输入URL列表', 'warning');
1647 | return;
1648 | }
1649 |
1650 | const count = urlQueue.addURLs(urlText);
1651 | if (count > 0) {
1652 | document.getElementById('cursor-batch-stats').style.display = 'block';
1653 | updateBatchStats();
1654 | startBatchProcessing();
1655 | }
1656 | });
1657 |
1658 | // 清空队列按钮
1659 | document.getElementById('cursor-batch-clear').addEventListener('click', (e) => {
1660 | e.stopPropagation();
1661 | urlQueue.clear();
1662 | document.getElementById('cursor-url-batch').value = '';
1663 | document.getElementById('cursor-batch-stats').style.display = 'none';
1664 | logger.log('队列已清空', 'info');
1665 | });
1666 |
1667 | // 动作按钮(开始/停止切换)
1668 | document.getElementById('cursor-action-btn').addEventListener('click', (e) => {
1669 | e.stopPropagation();
1670 |
1671 | if (isRunning) {
1672 | // 当前正在执行,点击停止
1673 | logger.log('========== 用户停止执行 ==========', 'warning');
1674 | stopExecution();
1675 | } else {
1676 | // 当前待机,点击开始
1677 | logger.log('========== 手动触发填写 ==========', 'info');
1678 | fillForm();
1679 | }
1680 | });
1681 | }
1682 |
1683 | // ===== 使面板可拖拽 =====
1684 | function makeDraggable() {
1685 | const panel = document.getElementById('cursor-auto-fill-panel');
1686 | const header = document.getElementById('cursor-auto-fill-header');
1687 | let isDragging = false;
1688 | let currentX, currentY, initialX, initialY;
1689 |
1690 | header.addEventListener('mousedown', (e) => {
1691 | if (e.target.id === 'cursor-auto-fill-toggle') return;
1692 | isDragging = true;
1693 | initialX = e.clientX - panel.offsetLeft;
1694 | initialY = e.clientY - panel.offsetTop;
1695 | });
1696 |
1697 | document.addEventListener('mousemove', (e) => {
1698 | if (isDragging) {
1699 | e.preventDefault();
1700 | currentX = e.clientX - initialX;
1701 | currentY = e.clientY - initialY;
1702 |
1703 | panel.style.left = currentX + 'px';
1704 | panel.style.top = currentY + 'px';
1705 | panel.style.right = 'auto';
1706 | }
1707 | });
1708 |
1709 | document.addEventListener('mouseup', () => {
1710 | isDragging = false;
1711 | });
1712 | }
1713 |
1714 | // ===== 批量处理逻辑 =====
1715 | let batchMonitorInterval = null; // 全局监控interval
1716 |
1717 | function updateBatchStats() {
1718 | const stats = urlQueue.getStats();
1719 | const totalEl = document.getElementById('stat-total');
1720 | const completedEl = document.getElementById('stat-completed');
1721 | const processingEl = document.getElementById('stat-processing');
1722 | const pendingEl = document.getElementById('stat-pending');
1723 |
1724 | if (totalEl) totalEl.textContent = stats.total;
1725 | if (completedEl) completedEl.textContent = stats.completed;
1726 | if (processingEl) processingEl.textContent = stats.processing;
1727 | if (pendingEl) pendingEl.textContent = stats.pending;
1728 | }
1729 |
1730 | // 从文本框中删除已完成的所有URL
1731 | function updateTextareaWithPendingURLs() {
1732 | const textarea = document.getElementById('cursor-url-batch');
1733 | if (!textarea) return;
1734 |
1735 | // 获取队列中所有completed的URL
1736 | const completedURLs = urlQueue.queue.urls
1737 | .filter(item => item.status === 'completed')
1738 | .map(item => item.url);
1739 |
1740 | if (completedURLs.length === 0) return;
1741 |
1742 | const currentText = textarea.value;
1743 | const lines = currentText.split('\n');
1744 |
1745 | // 过滤掉所有已完成的URL
1746 | const completedIDs = completedURLs.map(url => {
1747 | const match = url.match(/\/pay\/([^\/\?]+)/);
1748 | return match ? match[1] : null;
1749 | }).filter(id => id);
1750 |
1751 | const filteredLines = lines.filter(line => {
1752 | const lineMatch = line.trim().match(/\/pay\/([^\/\?]+)/);
1753 | if (lineMatch && completedIDs.includes(lineMatch[1])) {
1754 | return false; // 过滤掉已完成的
1755 | }
1756 | // 如果不是URL格式的行(如空行),保留
1757 | return line.trim().length === 0 || !line.includes('checkout.stripe.com');
1758 | });
1759 |
1760 | const newText = filteredLines.join('\n').trim();
1761 | if (newText !== currentText.trim()) {
1762 | textarea.value = newText;
1763 | logger.log(`✓ 已从列表中移除 ${completedURLs.length} 个完成的URL`, 'info');
1764 | }
1765 | }
1766 |
1767 | // ===== 单页面批量处理 =====
1768 | function startBatchProcessing() {
1769 | const stats = urlQueue.getStats();
1770 | logger.log(`========== 单页面批量处理 v2.4.0 ==========`, 'success');
1771 | logger.log(`共 ${stats.total} 个URL,当前页面逐个处理`, 'info');
1772 | updateBatchStats();
1773 |
1774 | // 只获取1个URL,在当前标签页打开
1775 | const nextURLs = urlQueue.getNext(1);
1776 | if (nextURLs.length > 0) {
1777 | const nextURL = nextURLs[0];
1778 | logger.log(`🚀 1秒后跳转到第1个URL...`, 'success');
1779 | logger.log(`URL: ${nextURL.substring(0, 60)}...`, 'info');
1780 | setTimeout(() => {
1781 | window.location.href = nextURL;
1782 | }, 1000);
1783 | } else {
1784 | logger.log('❌ 没有待处理的URL', 'warning');
1785 | }
1786 | }
1787 |
1788 | function isBatchMode() {
1789 | // 检查当前URL是否在队列中
1790 | const currentURL = window.location.href;
1791 |
1792 | // 必须是Stripe支付页面
1793 | if (!currentURL.includes('checkout.stripe.com/c/pay/')) {
1794 | return false;
1795 | }
1796 |
1797 | // 检查队列中是否有匹配的processing状态URL
1798 | const inQueue = urlQueue.queue.urls.some(item => {
1799 | // 提取pay/后面的ID进行匹配
1800 | const itemMatch = item.url.match(/\/pay\/([^\/\?]+)/);
1801 | const currentMatch = currentURL.match(/\/pay\/([^\/\?]+)/);
1802 |
1803 | if (itemMatch && currentMatch) {
1804 | return itemMatch[1] === currentMatch[1] && item.status === 'processing';
1805 | }
1806 | return false;
1807 | });
1808 |
1809 | return inQueue;
1810 | }
1811 |
1812 | // ===== 跳转到下一个URL =====
1813 | async function jumpToNextURL() {
1814 | // 1. 标记当前URL完成
1815 | const currentURL = window.location.href;
1816 | const currentMatch = currentURL.match(/\/pay\/([^\/\?]+)/);
1817 |
1818 | if (currentMatch) {
1819 | const targetItem = urlQueue.queue.urls.find(item => {
1820 | const itemMatch = item.url.match(/\/pay\/([^\/\?]+)/);
1821 | return itemMatch && itemMatch[1] === currentMatch[1];
1822 | });
1823 |
1824 | if (targetItem) {
1825 | targetItem.status = 'completed';
1826 | urlQueue.save();
1827 | logger.log(`✓ URL已标记为完成: ${targetItem.url.substring(0, 50)}...`, 'success');
1828 |
1829 | // 从文本框删除
1830 | updateTextareaWithPendingURLs();
1831 | }
1832 | }
1833 |
1834 | // 2. 获取下一个URL
1835 | const stats = urlQueue.getStats();
1836 | logger.log(`进度: ${stats.completed}/${stats.total} 完成`, 'info');
1837 |
1838 | const nextURLs = urlQueue.getNext(1);
1839 |
1840 | if (nextURLs.length > 0) {
1841 | const nextURL = nextURLs[0];
1842 | logger.log(`⏭️ 立即跳转到下一个URL...`, 'success');
1843 | logger.log(`下一个: ${nextURL.substring(0, 60)}...`, 'info');
1844 |
1845 | // 直接跳转(不关闭标签页)
1846 | window.location.href = nextURL;
1847 | } else {
1848 | // 全部完成
1849 | logger.log('========== 🎉 全部URL处理完成! ==========', 'success');
1850 | logger.log(`✅ 共完成 ${stats.completed} 个URL`, 'success');
1851 | }
1852 | }
1853 |
1854 | // ===== 后台执行保活机制 =====
1855 | function keepAlive() {
1856 | // 防止后台标签页被浏览器暂停
1857 | // 使用多种策略保持脚本活跃
1858 |
1859 | // 1. 定时发送console日志(轻量级)
1860 | setInterval(() => {
1861 | console.log('[Cursor Auto Fill] Keepalive ping:', new Date().toLocaleTimeString());
1862 | }, 10000);
1863 |
1864 | // 2. 使用requestAnimationFrame保持活跃
1865 | function rafKeepAlive() {
1866 | requestAnimationFrame(rafKeepAlive);
1867 | }
1868 | rafKeepAlive();
1869 |
1870 | // 3. 监听visibilitychange事件
1871 | document.addEventListener('visibilitychange', () => {
1872 | if (document.hidden) {
1873 | console.log('[Cursor Auto Fill] 标签页进入后台,继续执行...');
1874 | } else {
1875 | console.log('[Cursor Auto Fill] 标签页回到前台');
1876 | }
1877 | });
1878 |
1879 | logger.log('✓ 后台保活机制已启动', 'info');
1880 | }
1881 |
1882 | // ===== 检测页面类型 =====
1883 | function getPageType() {
1884 | const url = window.location.href;
1885 | if (url.includes('checkout.stripe.com/c/pay/')) {
1886 | return 'stripe';
1887 | } else if (url.includes('cursor.com') || url.includes('google.com') || url.includes('baidu.com')) {
1888 | return 'config';
1889 | }
1890 | return 'other';
1891 | }
1892 |
1893 | // ===== 创建配置专用UI(简化版)=====
1894 | function createConfigOnlyUI() {
1895 | const oldUI = document.getElementById('cursor-auto-fill-container');
1896 | if (oldUI) {
1897 | oldUI.remove();
1898 | }
1899 |
1900 | const container = document.createElement('div');
1901 | container.id = 'cursor-auto-fill-container';
1902 |
1903 | container.innerHTML = `
1904 |
1905 |
1912 |
1913 |
1914 |
批量URL配置
1915 |
1916 |
1917 |
1922 |
1923 |
1924 |
1925 |
1926 |
1927 |
1928 |
处理进度:
1929 |
总数: 0 | 完成: 0 | 处理中: 0 | 待处理: 0
1930 |
1931 |
1932 |
1933 |
1934 |
执行日志
1935 |
1936 |
1937 |
1938 |
1939 | `;
1940 |
1941 | document.body.appendChild(container);
1942 |
1943 | logger.log('✓ 配置面板已创建', 'success');
1944 | logger.log('💡 提示:在此配置URL,脚本会自动打开标签页并处理', 'info');
1945 |
1946 | // 设置事件
1947 | setupConfigUIEvents();
1948 | makeDraggable();
1949 | logger.updateLogDisplay();
1950 |
1951 | // 加载已有队列
1952 | if (urlQueue.queue.urls.length > 0) {
1953 | document.getElementById('cursor-batch-stats').style.display = 'block';
1954 | updateBatchStats();
1955 | setInterval(updateBatchStats, 2000);
1956 | }
1957 | }
1958 |
1959 | // ===== 配置UI事件 =====
1960 | function setupConfigUIEvents() {
1961 | // 折叠按钮
1962 | const toggleBtn = document.getElementById('cursor-auto-fill-toggle');
1963 | const content = document.getElementById('cursor-auto-fill-content');
1964 | let isCollapsed = false;
1965 |
1966 | toggleBtn.addEventListener('click', (e) => {
1967 | e.stopPropagation();
1968 | isCollapsed = !isCollapsed;
1969 | content.style.display = isCollapsed ? 'none' : 'block';
1970 | toggleBtn.textContent = isCollapsed ? '+' : '−';
1971 | });
1972 |
1973 | // 批量处理按钮
1974 | document.getElementById('cursor-batch-start').addEventListener('click', (e) => {
1975 | e.stopPropagation();
1976 | const urlText = document.getElementById('cursor-url-batch').value;
1977 | if (!urlText.trim()) {
1978 | logger.log('⚠️ 请输入URL列表', 'warning');
1979 | return;
1980 | }
1981 |
1982 | const count = urlQueue.addURLs(urlText);
1983 | if (count > 0) {
1984 | document.getElementById('cursor-batch-stats').style.display = 'block';
1985 | updateBatchStats();
1986 | startBatchProcessing();
1987 | }
1988 | });
1989 |
1990 | // 清空队列按钮
1991 | document.getElementById('cursor-batch-clear').addEventListener('click', (e) => {
1992 | e.stopPropagation();
1993 | urlQueue.clear();
1994 | document.getElementById('cursor-url-batch').value = '';
1995 | document.getElementById('cursor-batch-stats').style.display = 'none';
1996 | logger.log('队列已清空', 'info');
1997 | });
1998 | }
1999 |
2000 | // ===== 主函数 =====
2001 | async function main() {
2002 | logger.log('✓ 脚本已加载', 'info');
2003 |
2004 | // 检测页面类型
2005 | const pageType = getPageType();
2006 | logger.log(`当前页面类型: ${pageType}`, 'info');
2007 |
2008 | // 立即启动保活机制(后台执行关键)
2009 | keepAlive();
2010 |
2011 | await sleep(500);
2012 |
2013 | // 根据页面类型创建不同的UI
2014 | if (pageType === 'config') {
2015 | // 配置页面 - 只显示批量配置UI
2016 | createConfigOnlyUI();
2017 | logger.log('🎯 配置模式已启动,请输入URL开始批量处理', 'success');
2018 | return;
2019 | }
2020 |
2021 | // Stripe支付页面 - 显示完整UI
2022 | createUI();
2023 |
2024 | logger.log('正在检测页面...', 'info');
2025 |
2026 | // 检查页面可见性状态
2027 | const isHidden = document.hidden;
2028 | if (isHidden) {
2029 | logger.log('⚠️ 检测到标签页在后台,强制执行模式已启用', 'warning');
2030 | }
2031 |
2032 | const isCursor = await waitForPageLoad();
2033 |
2034 | if (!isCursor) {
2035 | logger.log('这不是 Cursor 试用页面,脚本待机中', 'warning');
2036 | return;
2037 | }
2038 |
2039 | // 检查是否是批量模式
2040 | const batchMode = isBatchMode();
2041 |
2042 | // 等待0.5秒让页面内容完全加载
2043 | await sleep(500);
2044 |
2045 | // 检查页面是否已经完成支付(已使用的URL)
2046 | const alreadyCompleted = isAlreadyCompleted();
2047 | if (alreadyCompleted) {
2048 | logger.log('========== 检测到此URL已被使用 ==========', 'warning');
2049 | logger.log('⏭️ 跳过填写,直接跳转下一个', 'info');
2050 |
2051 | if (batchMode) {
2052 | // 批量模式下,直接跳转下一个
2053 | await sleep(1000);
2054 | await jumpToNextURL();
2055 | } else {
2056 | logger.log('此URL无需处理,已经使用过了', 'warning');
2057 | }
2058 | return;
2059 | }
2060 |
2061 | if (config.autoFill || batchMode) {
2062 | const mode = batchMode ? '批量模式' : '自动填写';
2063 | const delay = 0; // 立即开始,不等待
2064 |
2065 | logger.log(`${mode}:立即开始...`, 'info');
2066 |
2067 | // 使用setImmediate或setTimeout
2068 | setTimeout(async () => {
2069 | logger.log('========== 自动开始填写 ==========', 'info');
2070 | logger.log(`当前标签页状态: ${document.hidden ? '后台' : '前台'}`, 'info');
2071 |
2072 | // 重试逻辑:最多重试3次
2073 | let retryCount = 0;
2074 | const maxRetries = 3;
2075 | let success = false;
2076 |
2077 | while (retryCount < maxRetries && !success) {
2078 | if (retryCount > 0) {
2079 | logger.log(`========== 第 ${retryCount + 1} 次尝试 ==========`, 'warning');
2080 | }
2081 |
2082 | try {
2083 | success = await fillForm();
2084 |
2085 | if (!success && retryCount < maxRetries - 1) {
2086 | logger.log(`⚠️ 支付失败,2秒后重新生成卡号并重试...`, 'warning');
2087 | await sleep(2000);
2088 | retryCount++;
2089 | } else if (!success) {
2090 | logger.log(`❌ 已重试 ${maxRetries} 次,全部失败`, 'error');
2091 | if (batchMode) {
2092 | logger.log('跳过该URL,3秒后继续下一个...', 'warning');
2093 | await sleep(3000);
2094 | await jumpToNextURL();
2095 | }
2096 | } else {
2097 | // 成功,fillForm内部已经处理了跳转逻辑
2098 | }
2099 | } catch (error) {
2100 | logger.log(`执行出错: ${error.message}`, 'error');
2101 |
2102 | if (batchMode) {
2103 | logger.log('⚠️ 发生错误,3秒后跳过该URL,继续下一个...', 'warning');
2104 | await sleep(3000);
2105 | await jumpToNextURL();
2106 | }
2107 | break;
2108 | }
2109 | }
2110 | }, delay);
2111 | } else {
2112 | logger.log('自动填写未启用,请点击"开始填写"按钮', 'info');
2113 | }
2114 |
2115 | // 如果有队列,定时更新统计
2116 | if (urlQueue.queue.urls.length > 0) {
2117 | document.getElementById('cursor-batch-stats').style.display = 'block';
2118 | updateBatchStats();
2119 | setInterval(updateBatchStats, 2000);
2120 | }
2121 | }
2122 |
2123 | if (document.readyState === 'loading') {
2124 | document.addEventListener('DOMContentLoaded', main);
2125 | } else {
2126 | main();
2127 | }
2128 |
2129 | })();
2130 |
--------------------------------------------------------------------------------