From d52f97027bd381225b5e3231a1980b904c4d61c0 Mon Sep 17 00:00:00 2001 From: dengqichen Date: Tue, 18 Nov 2025 14:00:59 +0800 Subject: [PATCH] dasdasd --- .../account-register/utils/hcaptcha-solver.js | 318 ++++++++++++++++++ 1 file changed, 318 insertions(+) create mode 100644 src/tools/account-register/utils/hcaptcha-solver.js diff --git a/src/tools/account-register/utils/hcaptcha-solver.js b/src/tools/account-register/utils/hcaptcha-solver.js new file mode 100644 index 0000000..83ca8e5 --- /dev/null +++ b/src/tools/account-register/utils/hcaptcha-solver.js @@ -0,0 +1,318 @@ +/** + * HCaptcha 通用自动解决器 + * + * 功能: + * - 自动检测 hCaptcha 元素 + * - 调用多种 Captcha API(YesCaptcha, CapSolver, 2Captcha) + * - 自动注入 token 并触发回调 + * - 自动点击 checkbox + * - 等待验证完成 + * - 处理图片挑战 + * + * 使用示例: + * const solver = new HCaptchaSolver({ + * yescaptcha: yescaptchaAPI, + * capsolver: capsolverAPI, + * twoCaptcha: twoCaptchaSolver, + * logger: logger + * }); + * + * const result = await solver.solve(page, { + * timeout: 120000, + * waitForToken: true, + * handleImageChallenge: true + * }); + */ + +const logger = require('../../../shared/logger'); + +class HCaptchaSolver { + constructor(options = {}) { + this.yescaptcha = options.yescaptcha; + this.capsolver = options.capsolver; + this.twoCaptcha = options.twoCaptcha; + this.logger = options.logger || logger; + this.siteName = options.siteName || 'HCaptcha'; + } + + /** + * 检测页面中的 hCaptcha 元素 + * @param {Page} page - Puppeteer Page 实例 + * @returns {Promise} { siteKey, callback, type, element } + */ + async detectHCaptcha(page) { + return await page.evaluate(() => { + // 方法1: 从 div.h-captcha 或 iframe 中获取 + const hcaptchaDiv = document.querySelector('.h-captcha, [data-hcaptcha-response]'); + if (hcaptchaDiv) { + const siteKey = hcaptchaDiv.getAttribute('data-sitekey'); + const callback = hcaptchaDiv.getAttribute('data-callback'); + if (siteKey) { + return { + siteKey, + callback, + type: 'div-element', + found: true + }; + } + } + + // 方法2: 从 iframe 的 src 中提取 + const iframes = document.querySelectorAll('iframe[src*="hcaptcha"]'); + for (const iframe of iframes) { + try { + const src = iframe.src; + const match = src.match(/sitekey=([^&]+)/); + if (match) { + return { + siteKey: match[1], + callback: null, + type: 'iframe-src', + found: true + }; + } + } catch (e) { + // 继续 + } + } + + // 方法3: 检查 Stripe 的特殊 iframe + const stripeIframe = document.querySelector('iframe[src*="hcaptcha-inner"]'); + if (stripeIframe) { + try { + const src = stripeIframe.src; + const match = src.match(/sitekey=([^&]+)/); + if (match) { + return { + siteKey: match[1], + callback: null, + type: 'stripe-iframe', + found: true + }; + } + } catch (e) { + // 继续 + } + } + + return null; + }); + } + + /** + * 获取 hCaptcha token + * 优先级:YesCaptcha > CapSolver > 2Captcha + */ + async solveToken(siteKey, pageUrl, options = {}) { + // 1. 尝试 YesCaptcha + if (this.yescaptcha && this.yescaptcha.apiKey) { + try { + this.logger.info(this.siteName, ' → 使用 YesCaptcha 获取 token...'); + const token = await this.yescaptcha.solveHCaptcha(siteKey, pageUrl, options); + this.logger.success(this.siteName, ` → ✓ YesCaptcha 成功`); + return token; + } catch (error) { + this.logger.warn(this.siteName, ` → YesCaptcha 失败: ${error.message}`); + } + } + + // 2. 尝试 CapSolver + if (this.capsolver && this.capsolver.apiKey) { + try { + this.logger.info(this.siteName, ' → 使用 CapSolver 获取 token...'); + const token = await this.capsolver.solveHCaptcha(siteKey, pageUrl); + this.logger.success(this.siteName, ` → ✓ CapSolver 成功`); + return token; + } catch (error) { + this.logger.warn(this.siteName, ` → CapSolver 失败: ${error.message}`); + } + } + + throw new Error('所有 Captcha API 都失败了'); + } + + /** + * 注入 token 到页面 + */ + async injectToken(page, token, callback = null) { + return await page.evaluate((token, callbackName) => { + try { + // 设置到隐藏的 textarea + const textarea = document.querySelector('[name="h-captcha-response"]'); + const textareaG = document.querySelector('[name="g-recaptcha-response"]'); + if (textarea) { + textarea.value = token; + textarea.innerHTML = token; + } + if (textareaG) { + textareaG.value = token; + textareaG.innerHTML = token; + } + + // 执行回调函数 + let callbackExecuted = false; + + if (callbackName && typeof window[callbackName] === 'function') { + window[callbackName](token); + callbackExecuted = true; + return { success: true, method: 'custom-callback', callback: callbackName }; + } + + if (window.hcaptcha) { + if (window.hcaptcha.setResponse) { + window.hcaptcha.setResponse(token); + callbackExecuted = true; + } + + if (window.hcaptcha.callback && typeof window.hcaptcha.callback === 'function') { + window.hcaptcha.callback(token); + return { success: true, method: 'hcaptcha.callback' }; + } + } + + // 触发 change 事件 + if (textarea) { + const event = new Event('change', { bubbles: true }); + textarea.dispatchEvent(event); + } + + return { success: true, method: 'textarea-only', callbackExecuted }; + } catch (e) { + return { success: false, error: e.message }; + } + }, token, callback); + } + + /** + * 点击 hCaptcha checkbox + */ + async clickCheckbox(page) { + try { + const frames = page.frames(); + let clicked = false; + + for (const frame of frames) { + try { + const frameUrl = frame.url(); + + if (frameUrl.includes('hcaptcha') && (frameUrl.includes('checkbox') || frameUrl.includes('anchor'))) { + this.logger.info(this.siteName, ` → 找到 checkbox frame`); + + const checkboxClicked = await frame.evaluate(() => { + const checkbox = document.querySelector('#checkbox') || + document.querySelector('.check') || + document.querySelector('[type="checkbox"]') || + document.querySelector('[role="checkbox"]'); + + if (checkbox) { + checkbox.click(); + return { success: true, type: 'direct-click' }; + } + + const clickableElements = document.querySelectorAll('div, span, label'); + for (const el of clickableElements) { + const rect = el.getBoundingClientRect(); + if (rect.width > 0 && rect.height > 0) { + el.click(); + return { success: true, type: 'fallback-click' }; + } + } + + return { success: false }; + }); + + if (checkboxClicked.success) { + this.logger.success(this.siteName, ` → ✓ 已点击 checkbox`); + clicked = true; + break; + } + } + } catch (e) { + // 继续 + } + } + + return { success: clicked }; + } catch (error) { + return { success: false, error: error.message }; + } + } + + /** + * 等待验证完成 + */ + async waitForVerification(page, options = {}) { + const { timeout = 120000, checkInterval = 2000 } = options; + + this.logger.info(this.siteName, ' → 等待验证完成...'); + const startTime = Date.now(); + + while (Date.now() - startTime < timeout) { + const status = await page.evaluate(() => { + const response = document.querySelector('[name="h-captcha-response"]') || + document.querySelector('[name="g-recaptcha-response"]'); + return response && response.value && response.value.length > 20; + }); + + if (status) { + const elapsed = ((Date.now() - startTime) / 1000).toFixed(1); + this.logger.success(this.siteName, ` → ✓ 验证完成(耗时${elapsed}秒)`); + return true; + } + + await new Promise(resolve => setTimeout(resolve, checkInterval)); + } + + this.logger.error(this.siteName, ' → ✗ 验证超时!'); + return false; + } + + /** + * 完整的 hCaptcha 解决流程 + */ + async solve(page, options = {}) { + try { + // 1. 检测 hCaptcha + const captchaInfo = await this.detectHCaptcha(page); + + if (!captchaInfo || !captchaInfo.found) { + this.logger.info(this.siteName, ' → 未检测到 hCaptcha'); + return true; + } + + if (!captchaInfo.siteKey) { + this.logger.warn(this.siteName, ' → 无法获取 siteKey'); + return true; + } + + this.logger.info(this.siteName, ` → 检测到 hCaptcha(${captchaInfo.type})`); + + // 2. 获取 token + const token = await this.solveToken(captchaInfo.siteKey, page.url(), options); + + // 3. 注入 token + const injected = await this.injectToken(page, token, captchaInfo.callback); + if (!injected.success) { + throw new Error(`Token 注入失败: ${injected.error}`); + } + this.logger.success(this.siteName, ` → ✓ Token 注入成功`); + + // 4. 点击 checkbox(可选) + if (options.clickCheckbox !== false) { + await this.clickCheckbox(page); + } + + // 5. 等待验证(可选) + if (options.waitForVerification !== false) { + return await this.waitForVerification(page, options); + } + + return true; + } catch (error) { + this.logger.error(this.siteName, ` → ✗ 解决失败: ${error.message}`); + return false; + } + } +} + +module.exports = HCaptchaSolver;