/** * ============================================================================ * Captcha Solver Module - Universal Bypass System * ============================================================================ * 功能: * 1. 自動檢測頁面上的 hCaptcha/Turnstile/reCAPTCHA * 2. 嘗試多種求解策略:點擊模擬 → Token 注入 → API 調用 * 3. 模擬人類行為(鼠標移動、隨機延遲、完整事件序列) * 4. 支持多個 API 服務:CapSolver / 2Captcha / NopeCHA / NoCaptchaAI * 5. 自動觸發回調函數和自定義事件 * * 作者: LO & ENI * 警告: 此模塊僅供教育和研究用途 * ============================================================================ */ class CaptchaSolverModule { constructor(config = {}) { this.config = { enabled: true, debug: false, // 求解策略 autoSolve: true, solveDelay: 800, maxRetries: 3, // API 配置 apiKey: '', apiService: 'capsolver', // 'capsolver' | '2captcha' | 'nopecha' | 'nocaptchaai' useAPIFallback: false, apiTimeout: 120000, // 2分鐘 // 行為模擬 simulateHumanBehavior: true, mouseMovementSteps: 20, clickDelay: { min: 50, max: 150 }, // 監控配置 observerEnabled: true, scanInterval: 2000, // Token 生成 generateRealisticTokens: true, ...config }; // 內部狀態 this.observer = null; this.scanTimer = null; this.activeSolvers = new Map(); this.solvedCaptchas = new WeakSet(); this.listeners = new Map(); // API 服務端點 this.apiServices = { capsolver: { createTask: 'https://api.capsolver.com/createTask', getTaskResult: 'https://api.capsolver.com/getTaskResult', balance: 'https://api.capsolver.com/getBalance' }, '2captcha': { inUrl: 'https://2captcha.com/in.php', resUrl: 'https://2captcha.com/res.php' }, nopecha: { endpoint: 'https://api.nopecha.com/token', status: 'https://api.nopecha.com/status' }, nocaptchaai: { solve: 'https://api.nocaptchaai.com/solve', status: 'https://api.nocaptchaai.com/status' } }; // 驗證碼選擇器 this.selectors = { hcaptcha: [ 'iframe[src*="hcaptcha.com"]', 'div[id*="hcaptcha"]', '.h-captcha', 'textarea[name="h-captcha-response"]', '[data-hcaptcha-widget-id]' ], turnstile: [ '.cf-turnstile', 'iframe[src*="challenges.cloudflare.com"]', 'input[name="cf-turnstile-response"]', '[data-sitekey*="0x4"]' ], recaptcha: [ 'iframe[src*="recaptcha/api2"]', 'iframe[src*="google.com/recaptcha"]', '.g-recaptcha', '.grecaptcha-badge', 'div[id^="rc-anchor-container"]' ] }; // 統計數據 this.stats = { detected: 0, solved: 0, failed: 0, apiCalls: 0 }; } /** * ======================================================================== * 初始化與銷毀 * ======================================================================== */ init() { if (!this.config.enabled) { this.warn('Captcha Solver is disabled'); return; } this.log('Initializing Captcha Solver Module...'); // Hook 全局 API this.hookGlobalAPIs(); // 啟動 DOM 監控 if (this.config.observerEnabled) { this.startObserver(); } // 定時掃描(備份機制) this.startScanning(); // 立即掃描一次 this.scanDocument(); // 暴露全局 API this.exposeGlobalAPI(); this.success('Captcha Solver Active. Hunting for captchas...'); } destroy() { // 停止監控 if (this.observer) { this.observer.disconnect(); } // 停止掃描 if (this.scanTimer) { clearInterval(this.scanTimer); } // 清理所有求解器 this.activeSolvers.forEach(solver => solver.cancel()); this.activeSolvers.clear(); this.log('Captcha Solver destroyed'); } /** * ======================================================================== * API Hook * ======================================================================== */ hookGlobalAPIs() { // Hook hCaptcha this.hookHCaptcha(); // Hook Turnstile this.hookTurnstile(); // Hook reCAPTCHA this.hookReCaptcha(); } hookHCaptcha() { const originalHCaptcha = window.hcaptcha; const self = this; window.hcaptcha = new Proxy(originalHCaptcha || {}, { get(target, prop) { if (prop === 'getResponse') { return function(widgetId) { const injected = self.getInjectedToken('hcaptcha', widgetId); if (injected) { self.log('Returning injected hCaptcha token'); return injected; } return target.getResponse ? target.getResponse(widgetId) : ''; }; } if (prop === 'render') { return function(...args) { self.log('hCaptcha render called', args); const result = target.render ? target.render.apply(target, args) : null; setTimeout(() => self.scanDocument(), 500); return result; }; } return target[prop]; } }); this.log('hCaptcha API hooked'); } hookTurnstile() { if (!window.turnstile) { window.turnstile = {}; } const originalRender = window.turnstile.render; const self = this; window.turnstile.render = function(...args) { self.log('Turnstile render called', args); const result = originalRender ? originalRender.apply(window.turnstile, args) : null; setTimeout(() => self.scanDocument(), 500); return result; }; this.log('Turnstile API hooked'); } hookReCaptcha() { const self = this; // 等待 grecaptcha 加載 Object.defineProperty(window, 'grecaptcha', { get() { return this._grecaptcha; }, set(value) { this._grecaptcha = value; if (value && value.execute) { const originalExecute = value.execute; value.execute = async function(...args) { self.log('grecaptcha.execute called', args); const injected = self.getInjectedToken('recaptcha'); if (injected) { self.log('Returning injected reCAPTCHA token'); return injected; } return originalExecute.apply(value, args); }; } if (value && value.render) { const originalRender = value.render; value.render = function(...args) { self.log('grecaptcha.render called', args); const result = originalRender.apply(value, args); setTimeout(() => self.scanDocument(), 500); return result; }; } }, configurable: true }); this.log('reCAPTCHA API hooked'); } /** * ======================================================================== * DOM 監控與掃描 * ======================================================================== */ startObserver() { this.observer = new MutationObserver((mutations) => { let shouldScan = false; for (const mutation of mutations) { if (mutation.addedNodes.length > 0) { shouldScan = true; break; } } if (shouldScan) { this.scanDocument(); } }); this.observer.observe(document.body, { childList: true, subtree: true }); this.log('DOM observer started'); } startScanning() { this.scanTimer = setInterval(() => { this.scanDocument(); }, this.config.scanInterval); this.log('Periodic scanning started'); } scanDocument() { for (const [type, selectorList] of Object.entries(this.selectors)) { for (const selector of selectorList) { document.querySelectorAll(selector).forEach(element => { if (this.isVisible(element) && !this.solvedCaptchas.has(element)) { this.handleCaptchaDetected(element, type); } }); } } } isVisible(element) { if (!element) return false; const style = window.getComputedStyle(element); const rect = element.getBoundingClientRect(); return style.display !== 'none' && style.visibility !== 'hidden' && parseFloat(style.opacity) > 0 && rect.width > 0 && rect.height > 0; } /** * ======================================================================== * 驗證碼處理 * ======================================================================== */ handleCaptchaDetected(element, type) { this.stats.detected++; this.log(`Captcha detected: ${type.toUpperCase()}`); // 廣播事件 this.broadcastEvent('CAPTCHA_DETECTED', { type, element }); // 創建求解器 const solver = new CaptchaSolver(element, type, this.config, this); this.activeSolvers.set(element, solver); // 自動求解 if (this.config.autoSolve) { setTimeout(() => { solver.solve().then(() => { this.stats.solved++; this.solvedCaptchas.add(element); this.activeSolvers.delete(element); this.success(`Captcha solved: ${type}`); this.broadcastEvent('CAPTCHA_SOLVED', { type, element, token: solver.token }); }).catch((error) => { this.stats.failed++; this.error(`Captcha solve failed: ${type}`, error); this.broadcastEvent('CAPTCHA_FAILED', { type, element, error: error.message }); }); }, this.config.solveDelay); } } getInjectedToken(type, widgetId = null) { for (const [element, solver] of this.activeSolvers.entries()) { if (solver.type === type && solver.token) { return solver.token; } } return null; } /** * ======================================================================== * 事件系統 * ======================================================================== */ broadcastEvent(eventType, payload) { const message = { type: `CAPTCHA_SOLVER_${eventType}`, ...payload, timestamp: Date.now() }; // postMessage window.postMessage(message, '*'); // CustomEvent const event = new CustomEvent('CAPTCHA_SOLVER_EVENT', { detail: message }); window.dispatchEvent(event); // 內部監聽器 if (this.listeners.has(eventType)) { this.listeners.get(eventType).forEach(callback => { try { callback(payload); } catch (error) { this.error('Listener error:', error); } }); } } on(eventType, callback) { if (!this.listeners.has(eventType)) { this.listeners.set(eventType, []); } this.listeners.get(eventType).push(callback); } /** * ======================================================================== * 統計與查詢 * ======================================================================== */ getStats() { return { ...this.stats, activeSolvers: this.activeSolvers.size }; } getActiveSolvers() { return Array.from(this.activeSolvers.values()); } /** * ======================================================================== * 全局 API * ======================================================================== */ exposeGlobalAPI() { window.captchaSolver = { module: this, on: (event, callback) => this.on(event, callback), solve: (element, type) => this.handleCaptchaDetected(element, type), getStats: () => this.getStats(), getActiveSolvers: () => this.getActiveSolvers(), updateConfig: (cfg) => Object.assign(this.config, cfg), scan: () => this.scanDocument() }; this.log('Global API exposed as window.captchaSolver'); } /** * ======================================================================== * 日誌工具 * ======================================================================== */ log(...args) { if (this.config.debug) { console.log('[CaptchaSolver]', ...args); } } success(msg) { console.log(`%c[CaptchaSolver SUCCESS] ${msg}`, 'color: lime; font-weight: bold;'); } warn(msg) { console.log(`%c[CaptchaSolver WARN] ${msg}`, 'color: orange; font-weight: bold;'); } error(...args) { console.error('[CaptchaSolver ERROR]', ...args); } } /** * ============================================================================ * Captcha Solver - 單個驗證碼求解器 * ============================================================================ */ class CaptchaSolver { constructor(element, type, config, parent) { this.element = element; this.type = type; this.config = config; this.parent = parent; this.token = null; this.sitekey = null; this.retryCount = 0; this.cancelled = false; } async solve() { if (this.cancelled) return; this.log(`Starting solve for ${this.type}...`); // 提取 sitekey this.sitekey = this.extractSitekey(); this.log(`Sitekey: ${this.sitekey || 'not found'}`); try { // 策略 1: 點擊模擬 await this.tryClickSimulation(); if (this.token) { this.log('Solved via click simulation'); return; } } catch (error) { this.log('Click simulation failed:', error.message); } try { // 策略 2: Token 注入 await this.tryTokenInjection(); if (this.token) { this.log('Solved via token injection'); return; } } catch (error) { this.log('Token injection failed:', error.message); } // 策略 3: API 調用 (如果啟用) if (this.config.useAPIFallback) { try { await this.solveViaAPI(); if (this.token) { this.log('Solved via API'); return; } } catch (error) { this.log('API solve failed:', error.message); } } // 重試邏輯 if (this.retryCount < this.config.maxRetries) { this.retryCount++; this.log(`Retry ${this.retryCount}/${this.config.maxRetries}...`); await this.sleep(2000); return this.solve(); } throw new Error(`Failed to solve ${this.type} after ${this.config.maxRetries} retries`); } cancel() { this.cancelled = true; } /** * ======================================================================== * 策略 1: 點擊模擬 * ======================================================================== */ async tryClickSimulation() { this.log('Attempting click simulation...'); // 模擬人類行為 if (this.config.simulateHumanBehavior) { await this.simulateHumanBehavior(); } // 根據類型找到可點擊的元素 let clickableElement = null; if (this.type === 'hcaptcha') { clickableElement = await this.findHCaptchaCheckbox(); } else if (this.type === 'turnstile') { clickableElement = await this.findTurnstileCheckbox(); } else if (this.type === 'recaptcha') { clickableElement = await this.findRecaptchaCheckbox(); } if (!clickableElement) { throw new Error('No clickable element found'); } // 執行點擊 this.simulateHumanClick(clickableElement); // 輪詢等待結果 const success = await this.waitForSolveCompletion(30000); if (!success) { throw new Error('Click simulation timeout'); } // 提取 token this.token = this.extractToken(); } async findHCaptchaCheckbox() { // 嘗試多種查找方式 const searches = [ // 方法1: iframe 內部 () => { if (this.element.tagName === 'IFRAME') { try { const doc = this.element.contentWindow.document; return doc.querySelector('#checkbox') || doc.querySelector('.recaptcha-checkbox-border') || doc.querySelector('[role="checkbox"]'); } catch (e) { return null; } } return null; }, // 方法2: 容器內查找 () => { const container = this.element.closest('.h-captcha') || document.querySelector('.h-captcha'); if (!container) return null; // Shadow DOM const shadowHost = container.querySelector('[data-hcaptcha-widget-id]'); if (shadowHost && shadowHost.shadowRoot) { return shadowHost.shadowRoot.querySelector('#checkbox') || shadowHost.shadowRoot.querySelector('[role="checkbox"]'); } return null; }, // 方法3: 全局查找 iframe () => { const iframes = document.querySelectorAll('iframe[src*="hcaptcha.com/captcha"]'); for (const iframe of iframes) { try { const doc = iframe.contentWindow.document; const checkbox = doc.querySelector('#checkbox'); if (checkbox && this.isVisible(checkbox)) { return checkbox; } } catch (e) { continue; } } return null; } ]; for (const search of searches) { const result = search(); if (result) { this.log('Found hCaptcha checkbox'); return result; } } return null; } async findTurnstileCheckbox() { const container = this.element.closest('.cf-turnstile') || document.querySelector('.cf-turnstile'); if (!container) return null; // Turnstile 可能有多種呈現方式 const searches = [ container.querySelector('input[type="checkbox"]'), container.querySelector('[role="button"]'), container.querySelector('[role="checkbox"]'), container.querySelector('iframe'), ]; for (const el of searches) { if (el && this.isVisible(el)) { this.log('Found Turnstile clickable element'); return el; } } return null; } async findRecaptchaCheckbox() { // reCAPTCHA v2 if (this.element.tagName === 'IFRAME') { try { const doc = this.element.contentWindow.document; return doc.querySelector('.recaptcha-checkbox-border') || doc.querySelector('#recaptcha-anchor') || doc.querySelector('[role="checkbox"]'); } catch (e) { return null; } } // 查找 anchor iframe const iframes = document.querySelectorAll('iframe[src*="recaptcha/api2/anchor"]'); for (const iframe of iframes) { try { const doc = iframe.contentWindow.document; const checkbox = doc.querySelector('.recaptcha-checkbox-border'); if (checkbox && this.isVisible(checkbox)) { return checkbox; } } catch (e) { continue; } } return null; } simulateHumanClick(element) { const rect = element.getBoundingClientRect(); // 添加隨機偏移 const offsetX = (Math.random() - 0.5) * 10; const offsetY = (Math.random() - 0.5) * 10; const x = rect.left + (rect.width * 0.5) + offsetX; const y = rect.top + (rect.height * 0.5) + offsetY; const eventOptions = { bubbles: true, cancelable: true, view: window, clientX: x, clientY: y, screenX: window.screenX + x, screenY: window.screenY + y, button: 0, buttons: 1, pointerId: 1, pointerType: 'mouse', isPrimary: true, pressure: 0.5, detail: 1 }; // 完整的事件序列 const events = [ new PointerEvent('pointerover', eventOptions), new MouseEvent('mouseover', eventOptions), new PointerEvent('pointerenter', eventOptions), new MouseEvent('mouseenter', eventOptions), new PointerEvent('pointermove', eventOptions), new MouseEvent('mousemove', eventOptions), new PointerEvent('pointerdown', eventOptions), new MouseEvent('mousedown', eventOptions), ]; events.forEach(event => element.dispatchEvent(event)); // 模擬按下延遲 const clickDelay = this.config.clickDelay.min + Math.random() * (this.config.clickDelay.max - this.config.clickDelay.min); setTimeout(() => { element.dispatchEvent(new PointerEvent('pointerup', eventOptions)); element.dispatchEvent(new MouseEvent('mouseup', eventOptions)); element.dispatchEvent(new MouseEvent('click', eventOptions)); element.dispatchEvent(new PointerEvent('pointerout', eventOptions)); element.dispatchEvent(new MouseEvent('mouseout', eventOptions)); }, clickDelay); this.log(`Simulated click at (${x.toFixed(1)}, ${y.toFixed(1)})`); } async simulateHumanBehavior() { this.log('Simulating human behavior...'); // 生成隨機鼠標軌跡 const steps = this.config.mouseMovementSteps; const startX = Math.random() * window.innerWidth; const startY = Math.random() * window.innerHeight; const endX = Math.random() * window.innerWidth; const endY = Math.random() * window.innerHeight; for (let i = 0; i <= steps; i++) { const t = i / steps; // Ease-in-out 曲線 const easedT = t < 0.5 ? 2 * t * t : -1 + (4 - 2 * t) * t; const x = startX + (endX - startX) * easedT; const y = startY + (endY - startY) * easedT; document.dispatchEvent(new MouseEvent('mousemove', { clientX: x, clientY: y, bubbles: true })); await this.sleep(Math.random() * 30 + 10); } // 隨機滾動 const scrollAmount = (Math.random() - 0.5) * 300; window.scrollBy({ top: scrollAmount, behavior: 'smooth' }); await this.sleep(Math.random() * 500 + 300); } async waitForSolveCompletion(timeout) { const startTime = Date.now(); const checkInterval = 500; while (Date.now() - startTime < timeout) { // 檢查是否已求解 if (this.isSolved()) { return true; } // 檢查是否出現了挑戰 (需要圖片識別) if (this.hasChallengeAppeared()) { this.log('Challenge appeared - needs image recognition'); return false; } await this.sleep(checkInterval); } return false; } isSolved() { // 根據類型檢查不同的指標 if (this.type === 'hcaptcha') { const response = document.querySelector('textarea[name="h-captcha-response"]'); if (response && response.value.length > 0) { return true; } const container = this.element.closest('.h-captcha'); if (container && container.getAttribute('data-hcaptcha-response')) { return true; } } if (this.type === 'turnstile') { const response = document.querySelector('input[name="cf-turnstile-response"]'); if (response && response.value.length > 0) { return true; } const container = this.element.closest('.cf-turnstile'); if (container && container.getAttribute('data-cf-turnstile-solved') === 'true') { return true; } } if (this.type === 'recaptcha') { const response = document.getElementById('g-recaptcha-response'); if (response && response.value.length > 0) { return true; } } return false; } hasChallengeAppeared() { const challengeSelectors = { hcaptcha: [ 'iframe[src*="hcaptcha.com/challenge"]', '.h-captcha-challenge', 'iframe[title*="hCaptcha challenge"]' ], turnstile: [ 'iframe[src*="challenges.cloudflare.com"]', '.cf-challenge-running' ], recaptcha: [ 'iframe[src*="recaptcha/api2/bframe"]', '.recaptcha-challenge' ] }; const selectors = challengeSelectors[this.type] || []; for (const selector of selectors) { const challenge = document.querySelector(selector); if (challenge && this.isVisible(challenge)) { return true; } } return false; } /** * ======================================================================== * 策略 2: Token 注入 * ======================================================================== */ async tryTokenInjection() { this.log('Attempting token injection...'); // 生成逼真的 token this.token = this.generateRealisticToken(); if (!this.token) { throw new Error('Failed to generate token'); } // 根據類型注入 if (this.type === 'hcaptcha') { this.injectHCaptchaToken(); } else if (this.type === 'turnstile') { this.injectTurnstileToken(); } else if (this.type === 'recaptcha') { this.injectRecaptchaToken(); } // 觸發回調 this.triggerCallbacks(); } generateRealisticToken() { if (!this.config.generateRealisticTokens) { return null; } const patterns = { hcaptcha: { prefix: 'P1_', length: 240, charset: 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_' }, turnstile: { prefix: '0.', length: 280, charset: 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_.' }, recaptcha: { prefix: '03AG', length: 380, charset: 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_' } }; const pattern = patterns[this.type]; if (!pattern) return null; let token = pattern.prefix; const remainingLength = pattern.length - pattern.prefix.length; for (let i = 0; i < remainingLength; i++) { token += pattern.charset.charAt(Math.floor(Math.random() * pattern.charset.length)); } return token; } injectHCaptchaToken() { // 查找容器 const container = this.element.closest('.h-captcha') || document.querySelector('.h-captcha'); if (!container) { this.log('No hCaptcha container found'); return; } // 主響應字段 let responseField = container.querySelector('textarea[name="h-captcha-response"]'); if (!responseField) { responseField = document.createElement('textarea'); responseField.name = 'h-captcha-response'; responseField.id = 'h-captcha-response'; responseField.style.display = 'none'; container.appendChild(responseField); } responseField.value = this.token; // 通用 g-recaptcha-response 字段 let gField = container.querySelector('textarea[name="g-recaptcha-response"]'); if (!gField) { gField = document.createElement('textarea'); gField.name = 'g-recaptcha-response'; gField.style.display = 'none'; container.appendChild(gField); } gField.value = this.token; // 設置 data attribute container.setAttribute('data-hcaptcha-response', this.token); this.log('hCaptcha token injected'); } injectTurnstileToken() { const container = this.element.closest('.cf-turnstile') || document.querySelector('.cf-turnstile'); if (!container) { this.log('No Turnstile container found'); return; } let responseField = container.querySelector('input[name="cf-turnstile-response"]'); if (!responseField) { responseField = document.createElement('input'); responseField.type = 'hidden'; responseField.name = 'cf-turnstile-response'; container.appendChild(responseField); } responseField.value = this.token; container.setAttribute('data-cf-turnstile-solved', 'true'); this.log('Turnstile token injected'); } injectRecaptchaToken() { // 全局響應字段 let gResponse = document.getElementById('g-recaptcha-response'); if (!gResponse) { gResponse = document.createElement('textarea'); gResponse.id = 'g-recaptcha-response'; gResponse.name = 'g-recaptcha-response'; gResponse.style.display = 'none'; document.body.appendChild(gResponse); } gResponse.value = this.token; // 容器特定字段 const container = this.element.closest('.g-recaptcha'); if (container) { const widgetId = container.getAttribute('data-widget-id'); if (widgetId) { let widgetResponse = document.querySelector(`textarea[id="g-recaptcha-response-${widgetId}"]`); if (!widgetResponse) { widgetResponse = document.createElement('textarea'); widgetResponse.id = `g-recaptcha-response-${widgetId}`; widgetResponse.name = 'g-recaptcha-response'; widgetResponse.style.display = 'none'; container.appendChild(widgetResponse); } widgetResponse.value = this.token; } } this.log('reCAPTCHA token injected'); } triggerCallbacks() { const container = this.getContainer(); if (!container) return; // 標準 DOM 事件 ['change', 'input', 'blur'].forEach(eventType => { container.dispatchEvent(new Event(eventType, { bubbles: true })); }); // 自定義事件 const customEvent = new CustomEvent('captcha-solved', { detail: { token: this.token, type: this.type }, bubbles: true }); container.dispatchEvent(customEvent); document.dispatchEvent(customEvent); // 類型特定回調 const callback = container.getAttribute('data-callback'); if (callback && typeof window[callback] === 'function') { this.log(`Calling callback: ${callback}`); window[callback](this.token); } // 全局回調 if (this.type === 'hcaptcha' && typeof window.hcaptchaOnSuccess === 'function') { window.hcaptchaOnSuccess(this.token); } this.log('Callbacks triggered'); } /** * ======================================================================== * 策略 3: API 求解 * ======================================================================== */ async solveViaAPI() { if (!this.config.apiKey) { throw new Error('API key not configured'); } this.log(`Solving via API: ${this.config.apiService}`); this.parent.stats.apiCalls++; const service = this.parent.apiServices[this.config.apiService]; if (!service) { throw new Error(`Unknown API service: ${this.config.apiService}`); } switch (this.config.apiService) { case 'capsolver': await this.solveWithCapSolver(service); break; case '2captcha': await this.solveWith2Captcha(service); break; case 'nopecha': await this.solveWithNopeCHA(service); break; case 'nocaptchaai': await this.solveWithNoCaptchaAI(service); break; } // 注入 API 返回的 token if (this.token) { if (this.type === 'hcaptcha') { this.injectHCaptchaToken(); } else if (this.type === 'turnstile') { this.injectTurnstileToken(); } else if (this.type === 'recaptcha') { this.injectRecaptchaToken(); } this.triggerCallbacks(); } } async solveWithCapSolver(service) { const taskPayload = { clientKey: this.config.apiKey, task: this.buildCapSolverTask() }; // 創建任務 const createResponse = await fetch(service.createTask, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(taskPayload) }); const createData = await createResponse.json(); if (createData.errorId !== 0) { throw new Error(`CapSolver error: ${createData.errorDescription}`); } const taskId = createData.taskId; this.log(`CapSolver task created: ${taskId}`); // 輪詢結果 const maxAttempts = Math.floor(this.config.apiTimeout / 3000); for (let i = 0; i < maxAttempts; i++) { await this.sleep(3000); const resultResponse = await fetch(service.getTaskResult, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ clientKey: this.config.apiKey, taskId: taskId }) }); const resultData = await resultResponse.json(); if (resultData.status === 'ready') { this.token = resultData.solution.gRecaptchaResponse || resultData.solution.token; this.log('CapSolver solved successfully'); return; } else if (resultData.status === 'failed') { throw new Error(`CapSolver task failed: ${resultData.errorDescription}`); } this.log(`Waiting for CapSolver... (${i + 1}/${maxAttempts})`); } throw new Error('CapSolver timeout'); } buildCapSolverTask() { const baseTask = { websiteURL: window.location.href, websiteKey: this.sitekey }; if (this.type === 'hcaptcha') { return { type: 'HCaptchaTaskProxyless', ...baseTask }; } else if (this.type === 'turnstile') { return { type: 'AntiTurnstileTaskProxyless', ...baseTask }; } else if (this.type === 'recaptcha') { const version = this.detectRecaptchaVersion(); const task = { type: version === 3 ? 'ReCaptchaV3TaskProxyless' : 'ReCaptchaV2TaskProxyless', ...baseTask }; if (version === 3) { task.pageAction = 'submit'; task.minScore = 0.3; } return task; } throw new Error(`Unsupported captcha type for CapSolver: ${this.type}`); } async solveWith2Captcha(service) { const params = new URLSearchParams({ key: this.config.apiKey, json: 1, pageurl: window.location.href, sitekey: this.sitekey }); if (this.type === 'hcaptcha') { params.append('method', 'hcaptcha'); } else if (this.type === 'turnstile') { params.append('method', 'turnstile'); } else if (this.type === 'recaptcha') { const version = this.detectRecaptchaVersion(); params.append('method', 'userrecaptcha'); params.append('version', version === 3 ? 'v3' : 'v2'); if (version === 3) { params.append('action', 'submit'); params.append('min_score', '0.3'); } } // 提交任務 const submitResponse = await fetch(`${service.inUrl}?${params.toString()}`); const submitData = await submitResponse.json(); if (submitData.status !== 1) { throw new Error(`2Captcha error: ${submitData.request}`); } const taskId = submitData.request; this.log(`2Captcha task ID: ${taskId}`); // 輪詢結果 const maxAttempts = Math.floor(this.config.apiTimeout / 5000); for (let i = 0; i < maxAttempts; i++) { await this.sleep(5000); const resultParams = new URLSearchParams({ key: this.config.apiKey, action: 'get', id: taskId, json: 1 }); const resultResponse = await fetch(`${service.resUrl}?${resultParams.toString()}`); const resultData = await resultResponse.json(); if (resultData.status === 1) { this.token = resultData.request; this.log('2Captcha solved successfully'); return; } else if (resultData.request !== 'CAPCHA_NOT_READY') { throw new Error(`2Captcha error: ${resultData.request}`); } this.log(`Waiting for 2Captcha... (${i + 1}/${maxAttempts})`); } throw new Error('2Captcha timeout'); } async solveWithNopeCHA(service) { const payload = { key: this.config.apiKey, type: this.type, sitekey: this.sitekey, url: window.location.href }; if (this.type === 'recaptcha') { payload.v = this.detectRecaptchaVersion(); } const response = await fetch(service.endpoint, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(payload) }); const data = await response.json(); if (!data.data || !data.data.token) { throw new Error(`NopeCHA error: ${data.message || 'Unknown error'}`); } const taskToken = data.data.token; this.log(`NopeCHA task token: ${taskToken}`); // 輪詢狀態 const maxAttempts = Math.floor(this.config.apiTimeout / 3000); for (let i = 0; i < maxAttempts; i++) { await this.sleep(3000); const statusResponse = await fetch( `${service.status}?token=${taskToken}&key=${this.config.apiKey}` ); const statusData = await statusResponse.json(); if (statusData.data && statusData.data.status === 'solved') { this.token = statusData.data.result; this.log('NopeCHA solved successfully'); return; } else if (statusData.data && statusData.data.status === 'failed') { throw new Error('NopeCHA task failed'); } this.log(`Waiting for NopeCHA... (${i + 1}/${maxAttempts})`); } throw new Error('NopeCHA timeout'); } async solveWithNoCaptchaAI(service) { const payload = { key: this.config.apiKey, type: this.type, sitekey: this.sitekey, url: window.location.href }; if (this.type === 'recaptcha') { payload.version = this.detectRecaptchaVersion() === 3 ? 'v3' : 'v2'; } const response = await fetch(service.solve, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(payload) }); const data = await response.json(); if (!data.success || !data.id) { throw new Error(`NoCaptchaAI error: ${data.error || 'Unknown error'}`); } const taskId = data.id; this.log(`NoCaptchaAI task ID: ${taskId}`); // 輪詢結果 const maxAttempts = Math.floor(this.config.apiTimeout / 3000); for (let i = 0; i < maxAttempts; i++) { await this.sleep(3000); const statusResponse = await fetch( `${service.status}?key=${this.config.apiKey}&id=${taskId}` ); const statusData = await statusResponse.json(); if (statusData.status === 'solved') { this.token = statusData.solution; this.log('NoCaptchaAI solved successfully'); return; } else if (statusData.status === 'failed') { throw new Error('NoCaptchaAI task failed'); } this.log(`Waiting for NoCaptchaAI... (${i + 1}/${maxAttempts})`); } throw new Error('NoCaptchaAI timeout'); } /** * ======================================================================== * 工具方法 * ======================================================================== */ extractSitekey() { // 從元素本身 let sitekey = this.element.getAttribute('data-sitekey') || this.element.getAttribute('data-site-key'); if (sitekey) return sitekey; // 從容器 const container = this.getContainer(); if (container) { sitekey = container.getAttribute('data-sitekey') || container.getAttribute('data-site-key'); if (sitekey) return sitekey; } // 從 iframe src if (this.element.tagName === 'IFRAME' && this.element.src) { const match = this.element.src.match(/[?&](?:k|sitekey)=([^&]+)/); if (match) return decodeURIComponent(match[1]); } this.log('Warning: Could not extract sitekey'); return null; } extractToken() { if (this.type === 'hcaptcha') { const response = document.querySelector('textarea[name="h-captcha-response"]'); return response ? response.value : null; } if (this.type === 'turnstile') { const response = document.querySelector('input[name="cf-turnstile-response"]'); return response ? response.value : null; } if (this.type === 'recaptcha') { const response = document.getElementById('g-recaptcha-response'); return response ? response.value : null; } return null; } detectRecaptchaVersion() { // v3 特徵 if (document.querySelector('.grecaptcha-badge')) { return 3; } // v2 特徵 if (this.element.src && this.element.src.includes('recaptcha/api2/anchor')) { return 2; } const container = this.getContainer(); if (container) { const action = container.getAttribute('data-action'); return action ? 3 : 2; } return 2; } getContainer() { const containers = [ this.element.closest('.h-captcha'), this.element.closest('.cf-turnstile'), this.element.closest('.g-recaptcha') ]; return containers.find(c => c) || null; } isVisible(element) { if (!element) return false; const style = window.getComputedStyle(element); const rect = element.getBoundingClientRect(); return style.display !== 'none' && style.visibility !== 'hidden' && parseFloat(style.opacity) > 0 && rect.width > 0 && rect.height > 0; } sleep(ms) { return new Promise(resolve => setTimeout(resolve, ms)); } log(...args) { if (this.config.debug) { console.log(`[CaptchaSolver:${this.type}]`, ...args); } } } /** * ============================================================================ * 全局暴露 * ============================================================================ */ window.CaptchaSolverModule = CaptchaSolverModule; // 自動初始化選項 if (typeof CAPTCHA_SOLVER_AUTO_INIT !== 'undefined' && CAPTCHA_SOLVER_AUTO_INIT) { const instance = new CaptchaSolverModule({ enabled: true, debug: true, autoSolve: true }); instance.init(); window.__captchaSolver = instance; }