From c7035b078420e1fadcd3479356bbd68482ed7801 Mon Sep 17 00:00:00 2001 From: dengqichen Date: Mon, 17 Nov 2025 23:04:44 +0800 Subject: [PATCH] aaaaa --- .env | 6 + package.json | 1 + src/tools/account-register/sites/windsurf.js | 214 +++++++++++++----- .../account-register/utils/yescaptcha-api.js | 132 +++++++++++ 4 files changed, 292 insertions(+), 61 deletions(-) create mode 100644 src/tools/account-register/utils/yescaptcha-api.js diff --git a/.env b/.env index 8b4b516..54b5ea2 100644 --- a/.env +++ b/.env @@ -12,3 +12,9 @@ MYSQL_DATABASE=windsurf-auto-register # CapSolver 验证码识别 CAPSOLVER_API_KEY=CAP-0FCDDA4906E87D9F4FF68EAECD34E320876FBA70E4F30EA1ADCD264EDB15E4BF + +# 2Captcha 验证码识别 (支持 Stripe hCaptcha) +TWOCAPTCHA_API_KEY=4e6ac0ee29459018fd5e0c454163cd4e + +# YesCaptcha 验证码识别 (优先使用) +YESCAPTCHA_API_KEY=a8a04f2e8ceab43cdf3793e2b72bf4d76f4f4a6b81789 diff --git a/package.json b/package.json index 406e185..4c5ddd3 100644 --- a/package.json +++ b/package.json @@ -21,6 +21,7 @@ "author": "", "license": "MIT", "dependencies": { + "2captcha-ts": "^2.4.1", "axios": "^1.13.2", "commander": "^11.0.0", "dotenv": "^17.2.3", diff --git a/src/tools/account-register/sites/windsurf.js b/src/tools/account-register/sites/windsurf.js index 1bcbbdf..5c50193 100644 --- a/src/tools/account-register/sites/windsurf.js +++ b/src/tools/account-register/sites/windsurf.js @@ -19,7 +19,9 @@ const { DEFAULT_CONFIG } = require('../config'); const CardGenerator = require('../../card-generator/generator'); const database = require('../../database'); const CapSolverAPI = require('../utils/capsolver-api'); +const YesCaptchaAPI = require('../utils/yescaptcha-api'); const BrowserManager = require('../utils/browser-manager'); +const { Solver } = require('2captcha-ts'); class WindsurfRegister { constructor(options = {}) { @@ -29,6 +31,23 @@ class WindsurfRegister { this.human = new HumanBehavior(); this.emailService = new EmailVerificationService(); this.capsolver = new CapSolverAPI(); + this.yescaptcha = new YesCaptchaAPI(); + + // 初始化 2Captcha + const twoCaptchaKey = process.env.TWOCAPTCHA_API_KEY; + if (twoCaptchaKey && twoCaptchaKey !== '你的2Captcha_API_KEY') { + try { + this.twoCaptchaSolver = new Solver(twoCaptchaKey); + logger.info(this.siteName, ` → 2Captcha 已初始化 (Key: ${twoCaptchaKey.substring(0, 10)}...)`); + } catch (error) { + logger.error(this.siteName, ` → 2Captcha 初始化失败: ${error.message}`); + } + } + + // 检查 YesCaptcha 配置 + if (this.yescaptcha.apiKey) { + logger.info(this.siteName, ` → YesCaptcha 已初始化 (Key: ${this.yescaptcha.apiKey.substring(0, 10)}...)`); + } // 浏览器管理器(支持多profile并发) this.browserManager = new BrowserManager({ @@ -1039,33 +1058,26 @@ class WindsurfRegister { return true; } - // 尝试使用 CapSolver API 自动识别 - if (this.capsolver.apiKey) { + // 优先尝试使用 YesCaptcha API + if (this.yescaptcha.apiKey) { try { - logger.info(this.siteName, ' → 使用 CapSolver API 自动识别...'); + logger.info(this.siteName, ' → 使用 YesCaptcha API 自动识别...'); - logger.info(this.siteName, ` → SiteKey: ${captchaInfo.siteKey.substring(0, 20)}...`); - if (captchaInfo.callback) { - logger.info(this.siteName, ` → Callback: ${captchaInfo.callback}`); - } - - // 2. 调用 CapSolver API 获取 token - // 简化 URL,移除 hash 和查询参数(CapSolver 不需要完整 URL) + logger.info(this.siteName, ` → SiteKey: ${captchaInfo.siteKey}`); const currentUrl = this.page.url(); - const simplifiedUrl = new URL(currentUrl); - const cleanUrl = `${simplifiedUrl.protocol}//${simplifiedUrl.host}${simplifiedUrl.pathname}`; + logger.info(this.siteName, ` → 页面 URL: ${currentUrl.substring(0, 80)}...`); - logger.info(this.siteName, ` → 原始 URL: ${currentUrl.substring(0, 80)}...`); - logger.info(this.siteName, ` → 简化 URL: ${cleanUrl}`); + // 调用 YesCaptcha API + const token = await this.yescaptcha.solveHCaptcha(captchaInfo.siteKey, currentUrl, { + isInvisible: true // Stripe 使用 invisible hCaptcha + }); - const token = await this.capsolver.solveHCaptcha(captchaInfo.siteKey, cleanUrl); + logger.info(this.siteName, ` → ✓ 获取到 token: ${token.substring(0, 30)}...`); - logger.info(this.siteName, ` → 获取到 token: ${token.substring(0, 30)}...`); - - // 3. 注入 token 并触发回调 + // 3. 注入 token 并触发回调(按正确顺序) const injected = await this.page.evaluate((token, callbackName) => { try { - // 方法1: 设置到隐藏的 textarea + // 步骤1: 设置到隐藏的 textarea(必须) const textarea = document.querySelector('[name="h-captcha-response"]'); const textareaG = document.querySelector('[name="g-recaptcha-response"]'); if (textarea) { @@ -1077,24 +1089,55 @@ class WindsurfRegister { textareaG.innerHTML = token; } - // 方法2: 使用 hCaptcha API - if (window.hcaptcha && window.hcaptcha.setResponse) { - window.hcaptcha.setResponse(token); - } + // 步骤2: 执行回调函数(关键!) + let callbackExecuted = false; - // 方法3: 触发自定义回调(如果有) + // 方法A: 触发自定义回调(优先级最高) if (callbackName && typeof window[callbackName] === 'function') { window[callbackName](token); - return { success: true, method: 'callback', callback: callbackName }; + callbackExecuted = true; + return { success: true, method: 'custom-callback', callback: callbackName }; } - // 方法4: 触发 hCaptcha 回调事件 - if (window.hcaptcha && window.hcaptcha.callback) { - window.hcaptcha.callback(token); - return { success: true, method: 'hcaptcha.callback' }; + // 方法B: 使用 hcaptcha API 的 setResponse + callback + if (window.hcaptcha) { + if (window.hcaptcha.setResponse) { + window.hcaptcha.setResponse(token); + callbackExecuted = true; + } + + // 触发 hcaptcha 的回调 + if (window.hcaptcha.callback && typeof window.hcaptcha.callback === 'function') { + window.hcaptcha.callback(token); + return { success: true, method: 'hcaptcha.callback' }; + } } - return { success: true, method: 'textarea' }; + // 方法C: 在所有 iframe 中查找回调 + const frames = document.querySelectorAll('iframe'); + for (const frame of frames) { + try { + if (frame.contentWindow && frame.contentWindow.hcaptcha) { + if (frame.contentWindow.hcaptcha.setResponse) { + frame.contentWindow.hcaptcha.setResponse(token); + } + if (frame.contentWindow.hcaptcha.callback) { + frame.contentWindow.hcaptcha.callback(token); + return { success: true, method: 'iframe-hcaptcha.callback' }; + } + } + } catch (e) { + // 跨域iframe无法访问,跳过 + } + } + + // 触发 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 }; } @@ -1104,16 +1147,89 @@ class WindsurfRegister { throw new Error(`Token 注入失败: ${injected.error}`); } - logger.success(this.siteName, ` → ✓ hCaptcha 自动识别成功 (方式: ${injected.method})`); + logger.success(this.siteName, ` → ✓ Token 注入成功 (方式: ${injected.method})`); + if (injected.callback) { + logger.info(this.siteName, ` → ✓ 已执行回调函数: ${injected.callback}`); + } else if (injected.callbackExecuted) { + logger.info(this.siteName, ' → ✓ 已执行 hCaptcha 回调'); + } else { + logger.warn(this.siteName, ' → ⚠️ 未找到回调函数,可能需要手动触发'); + } + logger.info(this.siteName, ' → 等待自动验证...'); + + // 不要主动点击 checkbox!YesCaptcha 的 token 应该自动通过 + // 如果主动点击反而会触发 hCaptcha 的二次验证 + await this.human.randomDelay(2000, 3000); + + // 等待验证完成(如果回调成功,应该很快;如果降级到图片,需要手动) + logger.info(this.siteName, ' → 等待验证完成...'); + const startTime = Date.now(); + let dialogClosed = false; + let hasImageChallenge = false; + const maxWaitTime = 60000; // 最多60秒(包含图片挑战时间) + + while (Date.now() - startTime < maxWaitTime) { + const status = await this.page.evaluate(() => { + const dialog = document.querySelector('[role="dialog"]'); + if (!dialog) return { dialogGone: true, verified: true, hasImageChallenge: false }; + + const style = window.getComputedStyle(dialog); + const isHidden = style.display === 'none' || style.visibility === 'hidden' || style.opacity === '0'; + + // 检查 token 是否已填充 + const response = document.querySelector('[name="h-captcha-response"]') || + document.querySelector('[name="g-recaptcha-response"]'); + const hasResponse = response && response.value && response.value.length > 20; + + // 检查是否有图片挑战 + const dialogText = dialog.innerText || ''; + const hasImageTask = dialogText.includes('选择') || + dialogText.includes('Select') || + dialog.querySelector('img[src*="hcaptcha"]') || + dialog.querySelector('[class*="task"]'); + + return { + dialogGone: !dialog || isHidden, + verified: hasResponse, + hasImageChallenge: hasImageTask, + dialogText: dialogText.substring(0, 80) + }; + }); + + // 第一次检测到图片挑战 + if (status.hasImageChallenge && !hasImageChallenge) { + hasImageChallenge = true; + logger.warn(this.siteName, ' → ⚠️ Token 被降级,出现图片挑战!'); + logger.info(this.siteName, ` → 任务: ${status.dialogText}`); + logger.warn(this.siteName, ' → 请手动完成图片验证...'); + } + + // 验证完成 + if (status.dialogGone || (status.verified && !status.hasImageChallenge)) { + dialogClosed = true; + const elapsed = ((Date.now() - startTime) / 1000).toFixed(1); + logger.success(this.siteName, ` → ✓ 验证完成(耗时${elapsed}秒)`); + break; + } + + // 动态调整检查间隔 + const interval = hasImageChallenge ? 2000 : 1000; + await new Promise(resolve => setTimeout(resolve, interval)); + } + + if (!dialogClosed) { + logger.warn(this.siteName, ' → ⚠️ 验证超时,继续尝试支付...'); + } + await this.human.randomDelay(1000, 2000); return true; } catch (error) { - logger.error(this.siteName, ` → ✗ CapSolver 自动识别失败: ${error.message}`); + logger.error(this.siteName, ` → ✗ YesCaptcha 自动识别失败: ${error.message}`); logger.warn(this.siteName, ' → 将回退到手动模式'); } } else { - logger.warn(this.siteName, ' → 未配置 CapSolver API Key'); + logger.warn(this.siteName, ' → 未配置 YesCaptcha API Key'); } // 手动等待模式 @@ -1272,34 +1388,10 @@ class WindsurfRegister { } if (captchaDetected) { - logger.warn(this.siteName, ' → ⚠️ 检测到验证码,请手动完成(等待60秒)...'); - - // 等待用户手动完成验证码 - const waitStart = Date.now(); - const maxWait = 60000; // 等待60秒 - - while (Date.now() - waitStart < maxWait) { - // 检查验证码是否已完成 - const captchaCompleted = await this.page.evaluate(() => { - // 检查验证码对话框是否消失 - const modal = document.querySelector('[role="dialog"]'); - const modalVisible = modal && window.getComputedStyle(modal).display !== 'none'; - return !modalVisible; - }); - - if (captchaCompleted) { - const elapsed = ((Date.now() - waitStart) / 1000).toFixed(1); - logger.success(this.siteName, ` → ✓ 验证码已完成(耗时${elapsed}秒)`); - break; - } - - // 每秒检查一次 - await new Promise(resolve => setTimeout(resolve, 1000)); - } - - if (Date.now() - waitStart >= maxWait) { - logger.warn(this.siteName, ' → ⚠️ 验证码等待超时,继续尝试...'); - } + logger.info(this.siteName, ' → 检测到验证码,使用 YesCaptcha API 处理...'); + // 使用 YesCaptcha API 处理 + await this.handleHCaptcha(); + await this.human.randomDelay(1000, 2000); } else { logger.info(this.siteName, ` → ✓ 无验证码(检查${captchaCheckCount}次,耗时${((Date.now() - checkStartTime) / 1000).toFixed(1)}秒)`); } diff --git a/src/tools/account-register/utils/yescaptcha-api.js b/src/tools/account-register/utils/yescaptcha-api.js new file mode 100644 index 0000000..19f9ef4 --- /dev/null +++ b/src/tools/account-register/utils/yescaptcha-api.js @@ -0,0 +1,132 @@ +const axios = require('axios'); +const logger = require('../../../shared/logger'); + +/** + * YesCaptcha API 封装 + * 支持 hCaptcha、reCaptcha、Turnstile 等验证码识别 + */ +class YesCaptchaAPI { + constructor() { + this.apiKey = process.env.YESCAPTCHA_API_KEY; + this.apiUrl = 'https://api.yescaptcha.com'; + } + + /** + * 识别 hCaptcha + * @param {string} siteKey - 网站的 sitekey + * @param {string} pageUrl - 网页 URL + * @param {object} options - 可选参数 {isInvisible, rqdata, userAgent} + * @returns {Promise} - hCaptcha token + */ + async solveHCaptcha(siteKey, pageUrl, options = {}) { + if (!this.apiKey) { + throw new Error('YesCaptcha API Key 未配置'); + } + + logger.info('YesCaptcha', '开始识别 hCaptcha...'); + logger.info('YesCaptcha', `SiteKey: ${siteKey.substring(0, 20)}...`); + + try { + // 1. 创建任务 + const requestBody = { + clientKey: this.apiKey, + task: { + type: 'HCaptchaTaskProxyless', + websiteURL: pageUrl, + websiteKey: siteKey + } + }; + + // 添加可选参数 + if (options.isInvisible !== undefined) { + requestBody.task.isInvisible = options.isInvisible; + } + if (options.rqdata) { + requestBody.task.rqdata = options.rqdata; + } + if (options.userAgent) { + requestBody.task.userAgent = options.userAgent; + } + + logger.info('YesCaptcha', `请求 URL: ${pageUrl}`); + logger.info('YesCaptcha', `API Key: ${this.apiKey.substring(0, 10)}...`); + + const createTaskResponse = await axios.post(`${this.apiUrl}/createTask`, requestBody); + + if (createTaskResponse.data.errorId !== 0) { + logger.error('YesCaptcha', `API 返回错误: ${JSON.stringify(createTaskResponse.data)}`); + throw new Error(`创建任务失败: ${createTaskResponse.data.errorDescription}`); + } + + const taskId = createTaskResponse.data.taskId; + logger.info('YesCaptcha', `任务已创建,TaskID: ${taskId}`); + + // 2. 轮询获取结果(最多等待120秒) + const maxAttempts = 40; // 120秒 / 3秒 + let attempts = 0; + + while (attempts < maxAttempts) { + attempts++; + + // 等待3秒再查询 + await new Promise(resolve => setTimeout(resolve, 3000)); + + logger.info('YesCaptcha', `查询结果... (${attempts}/${maxAttempts})`); + + const getResultResponse = await axios.post(`${this.apiUrl}/getTaskResult`, { + clientKey: this.apiKey, + taskId: taskId + }); + + if (getResultResponse.data.errorId !== 0) { + throw new Error(`查询结果失败: ${getResultResponse.data.errorDescription}`); + } + + const status = getResultResponse.data.status; + + if (status === 'ready') { + const token = getResultResponse.data.solution.gRecaptchaResponse; + logger.success('YesCaptcha', `✓ 识别成功!耗时: ${attempts * 3}秒`); + + // 返回 userAgent 如果有的话 + if (getResultResponse.data.solution.userAgent) { + logger.info('YesCaptcha', `返回的 UserAgent: ${getResultResponse.data.solution.userAgent.substring(0, 50)}...`); + } + + return token; + } else if (status === 'processing') { + // 继续等待 + continue; + } else { + throw new Error(`未知状态: ${status}`); + } + } + + throw new Error('识别超时(120秒)'); + + } catch (error) { + // 详细的错误日志 + if (error.response) { + // API 返回了错误响应 + logger.error('YesCaptcha', `HTTP ${error.response.status}: ${error.response.statusText}`); + logger.error('YesCaptcha', `响应数据: ${JSON.stringify(error.response.data)}`); + + if (error.response.status === 400) { + logger.error('YesCaptcha', '可能原因:'); + logger.error('YesCaptcha', ' 1. API Key 无效或已过期'); + logger.error('YesCaptcha', ' 2. 余额不足'); + logger.error('YesCaptcha', ' 3. siteKey 或 URL 格式错误'); + } + } else if (error.request) { + // 请求已发送但没有收到响应 + logger.error('YesCaptcha', '无法连接到 YesCaptcha API'); + } else { + // 其他错误 + logger.error('YesCaptcha', `识别失败: ${error.message}`); + } + throw error; + } + } +} + +module.exports = YesCaptchaAPI;