diff --git a/src/tools/account-register/config.js b/src/tools/account-register/config.js index c1ccc71..dfb660a 100644 --- a/src/tools/account-register/config.js +++ b/src/tools/account-register/config.js @@ -40,6 +40,11 @@ const DEFAULT_CONFIG = { // 信用卡配置 card: { type: 'visa' + }, + + // Cloudflare验证配置 + cloudflare: { + mode: 'manual' // 'auto' = 全自动(实验性), 'manual' = 半自动(推荐) } }; diff --git a/src/tools/account-register/sites/windsurf.js b/src/tools/account-register/sites/windsurf.js index 43be967..da6fc57 100644 --- a/src/tools/account-register/sites/windsurf.js +++ b/src/tools/account-register/sites/windsurf.js @@ -10,10 +10,12 @@ * ... 根据实际情况继续添加步骤 */ -const AccountDataGenerator = require('../generator'); +const BaseSite = require('../base-site'); const HumanBehavior = require('../utils/human-behavior'); -const EmailVerificationService = require('../email-verification'); +const CloudflareHandler = require('../utils/cloudflare-handler'); const logger = require('../../../shared/logger'); +const EmailVerificationService = require('../email-verification'); +const { DEFAULT_CONFIG } = require('../config'); class WindsurfRegister { constructor() { @@ -315,185 +317,12 @@ class WindsurfRegister { } /** - * Cloudflare Turnstile验证(步骤2.5) + * Cloudflare Turnstile验证(步骤2.5)- 使用通用处理器 */ async handleCloudflareVerification() { - logger.info(this.siteName, '[Cloudflare] 检查人机验证...'); - - await this.human.randomDelay(2000, 3000); - - // 检查页面内容,判断是否有验证 - const pageText = await this.page.evaluate(() => document.body.textContent || ''); - - if (!pageText.includes('verify') && !pageText.includes('human')) { - logger.success(this.siteName, '[Cloudflare] 无验证,自动通过'); - return 'passed'; - } - - logger.info(this.siteName, '[Cloudflare] 发现验证页面'); - - // 情况1:等待自动验证(某些情况下会自动通过) - logger.info(this.siteName, '[Cloudflare] 等待自动验证(5秒)...'); - await this.human.randomDelay(5000, 6000); - - const afterWait = await this.page.evaluate(() => document.body.textContent || ''); - if (!afterWait.includes('verify') && !afterWait.includes('human')) { - logger.success(this.siteName, '[Cloudflare] 自动验证通过'); - return 'passed'; - } - - // 情况2:需要点击checkbox - logger.info(this.siteName, '[Cloudflare] 需要点击验证框...'); - - try { - // 方法1: 查找包含Turnstile的iframe - logger.info(this.siteName, '[Cloudflare] 查找iframe...'); - const frames = await this.page.frames(); - let turnstileFrame = null; - - for (const frame of frames) { - const url = frame.url(); - if (url.includes('challenges.cloudflare.com') || url.includes('turnstile')) { - turnstileFrame = frame; - logger.success(this.siteName, `[Cloudflare] 找到iframe: ${url.substring(0, 80)}...`); - break; - } - } - - if (turnstileFrame) { - // 等待iframe内容加载 - logger.info(this.siteName, '[Cloudflare] 等待iframe内容加载...'); - - try { - // 等待checkbox元素出现在iframe中 - await turnstileFrame.waitForSelector('input[type="checkbox"]', { timeout: 10000 }); - logger.success(this.siteName, '[Cloudflare] checkbox元素已加载'); - - // 再等待一下确保完全渲染 - await this.human.randomDelay(2000, 3000); - - logger.info(this.siteName, '[Cloudflare] 尝试点击验证框...'); - - // 关键:使用页面级别的鼠标点击,模拟真实用户行为 - // 查找checkbox在页面中的位置 - const checkboxElement = await turnstileFrame.$('label.cb-lb'); - if (!checkboxElement) { - logger.error(this.siteName, '[Cloudflare] 未找到label.cb-lb元素'); - return 'failed'; - } - - // 获取checkbox在页面中的绝对坐标 - const box = await checkboxElement.boundingBox(); - if (!box) { - logger.error(this.siteName, '[Cloudflare] 无法获取checkbox坐标'); - return 'failed'; - } - - logger.info(this.siteName, `[Cloudflare] checkbox坐标: x=${Math.round(box.x)}, y=${Math.round(box.y)}, width=${Math.round(box.width)}, height=${Math.round(box.height)}`); - - // 计算点击位置(在checkbox中心附近随机偏移) - const clickX = box.x + box.width / 2 + (Math.random() - 0.5) * 5; - const clickY = box.y + box.height / 2 + (Math.random() - 0.5) * 5; - - // 先移动鼠标到目标附近(模拟真实用户) - await this.page.mouse.move(clickX - 50, clickY - 50); - await this.human.randomDelay(100, 300); - - // 移动到目标位置 - await this.page.mouse.move(clickX, clickY, { steps: 10 }); - await this.human.randomDelay(200, 400); - - // 点击 - logger.info(this.siteName, `[Cloudflare] 在坐标 (${Math.round(clickX)}, ${Math.round(clickY)}) 点击`); - await this.page.mouse.click(clickX, clickY); - - const clicked = true; - - if (clicked) { - logger.success(this.siteName, `[Cloudflare] ✓ 已点击: ${clicked}`); - - // 等待验证处理 - logger.info(this.siteName, '[Cloudflare] 等待验证处理(最多15秒)...'); - await this.human.randomDelay(8000, 12000); - - // 检查是否通过 - const finalCheck = await this.page.evaluate(() => document.body.textContent || ''); - if (!finalCheck.includes('verify') && !finalCheck.includes('human')) { - logger.success(this.siteName, '[Cloudflare] ✓✓✓ 验证通过!'); - return 'passed'; - } else { - logger.warn(this.siteName, '[Cloudflare] 第一次检查未通过,再等待5秒...'); - // 再等待一次 - await this.human.randomDelay(5000, 8000); - const secondCheck = await this.page.evaluate(() => document.body.textContent || ''); - if (!secondCheck.includes('verify') && !secondCheck.includes('human')) { - logger.success(this.siteName, '[Cloudflare] ✓✓✓ 验证通过!'); - return 'passed'; - } else { - logger.error(this.siteName, '[Cloudflare] ✗ 验证失败'); - return 'failed'; - } - } - } else { - logger.error(this.siteName, '[Cloudflare] ✗ iframe内未找到可点击元素'); - return 'failed'; - } - } catch (error) { - logger.error(this.siteName, `[Cloudflare] ✗ 处理iframe时出错: ${error.message}`); - return 'failed'; - } - } - - // 方法2: 如果iframe方法失败,尝试直接点击页面上的元素 - logger.info(this.siteName, '[Cloudflare] 尝试在主页面查找...'); - const pageSelectors = [ - 'iframe[src*="cloudflare"]', - 'iframe[src*="turnstile"]', - 'input[type="checkbox"]', - '.cf-turnstile' - ]; - - for (const selector of pageSelectors) { - try { - const element = await this.page.$(selector); - if (element) { - logger.info(this.siteName, `[Cloudflare] 在主页面找到: ${selector}`); - - if (selector.includes('iframe')) { - // 如果是iframe,点击iframe中心 - const box = await element.boundingBox(); - if (box) { - await this.page.mouse.click(box.x + box.width / 2, box.y + box.height / 2); - logger.success(this.siteName, '[Cloudflare] 已点击iframe区域'); - await this.human.randomDelay(5000, 8000); - - const check = await this.page.evaluate(() => document.body.textContent || ''); - if (!check.includes('verify') && !check.includes('human')) { - logger.success(this.siteName, '[Cloudflare] ✓ 验证通过'); - return 'passed'; - } - } - } - } - } catch (e) { - // 继续 - } - } - - // 情况3:自动点击失败 - logger.warn(this.siteName, '[Cloudflare] ⚠ 自动点击失败'); - logger.warn(this.siteName, '[Cloudflare] 请手动点击验证框...'); - logger.warn(this.siteName, '[Cloudflare] 程序将在30秒后继续...'); - - // 等待用户手动完成 - await this.human.randomDelay(30000, 30000); - - return 'manual'; - - } catch (error) { - logger.error(this.siteName, `[Cloudflare] 处理验证时出错: ${error.message}`); - return 'error'; - } + const cloudflareMode = DEFAULT_CONFIG.cloudflare.mode; + const handler = new CloudflareHandler(this.page, this.human, this.siteName, cloudflareMode); + return await handler.handle(); } /** @@ -527,73 +356,64 @@ class WindsurfRegister { logger.success(this.siteName, ` → 验证码: ${code}`); - // 等待验证码输入框 - await this.human.randomDelay(1000, 2000); + // 等待验证码输入框加载 + await this.human.randomDelay(2000, 3000); - // 查找验证码输入框(可能有多种选择器) - const possibleSelectors = [ - '#code', - '#verificationCode', - 'input[name="code"]', - 'input[name="verificationCode"]', - 'input[type="text"][placeholder*="code" i]', - 'input[type="text"][placeholder*="验证" i]' - ]; + // Windsurf使用6个独立的输入框,需要逐个填写 + logger.info(this.siteName, ' → 查找验证码输入框...'); - let codeInputSelector = null; - for (const selector of possibleSelectors) { + // 等待输入框出现 + await this.page.waitForSelector('input[type="text"]', { timeout: 10000 }); + + // 获取所有文本输入框 + const inputs = await this.page.$$('input[type="text"]'); + logger.info(this.siteName, ` → 找到 ${inputs.length} 个输入框`); + + if (inputs.length >= 6 && code.length === 6) { + // 逐个填写每一位验证码 + logger.info(this.siteName, ' → 填写6位验证码...'); + for (let i = 0; i < 6; i++) { + const char = code[i].toUpperCase(); // 确保大写 + + // 点击输入框获取焦点 + await inputs[i].click(); + await this.human.randomDelay(100, 200); + + // 输入字符 + await inputs[i].type(char); + await this.human.randomDelay(300, 500); + + logger.info(this.siteName, ` → 已输入第 ${i + 1} 位: ${char}`); + } + logger.success(this.siteName, ' → 验证码已填写完成'); + + // 等待按钮激活(填完验证码后按钮会自动启用) + logger.info(this.siteName, ' → 等待按钮激活...'); + await this.human.randomDelay(2000, 3000); + + // 查找"Create account"按钮 try { - const input = await this.page.$(selector); - if (input) { - codeInputSelector = selector; - logger.info(this.siteName, ` → 找到验证码输入框: ${selector}`); - break; + await this.page.waitForSelector('button:not([disabled])', { timeout: 10000 }); + logger.success(this.siteName, ' → 按钮已激活'); + + // 点击提交按钮 + const submitButton = await this.page.$('button[type="submit"]'); + if (submitButton) { + logger.info(this.siteName, ' → 点击"Create account"按钮...'); + await submitButton.click(); + } else { + // 尝试点击任何未disabled的按钮 + const anyButton = await this.page.$('button:not([disabled])'); + if (anyButton) { + logger.info(this.siteName, ' → 点击提交按钮...'); + await anyButton.click(); + } } } catch (e) { - // 继续尝试 - } - } - - if (codeInputSelector) { - // 填写验证码 - logger.info(this.siteName, ' → 填写验证码...'); - await this.human.humanType(this.page, codeInputSelector, code); - - // 查找并点击提交按钮 - logger.info(this.siteName, ' → 点击提交按钮...'); - - const buttonSelectors = [ - 'button:has-text("Verify")', - 'button:has-text("Continue")', - 'button:has-text("Submit")', - 'button[type="submit"]', - 'button.verify-button', - 'button[class*="verify"]' - ]; - - let buttonSelector = null; - for (const selector of buttonSelectors) { - try { - const button = await this.page.$(selector); - if (button) { - buttonSelector = selector; - break; - } - } catch (e) { - // 继续尝试 - } + logger.warn(this.siteName, ` → 按钮等待超时,可能已自动提交: ${e.message}`); } - if (buttonSelector) { - await this.human.visualSearch(this.page, buttonSelector); - await this.human.humanClick(this.page, buttonSelector); - } else { - logger.warn(this.siteName, ' → 未找到提交按钮,尝试按Enter键'); - await this.human.randomDelay(500, 1000); - await this.page.keyboard.press('Enter'); - } - - // 等待验证完成 + // 等待页面跳转或验证完成 await this.human.randomDelay(3000, 5000); this.currentStep = 3; diff --git a/src/tools/account-register/utils/cloudflare-handler.js b/src/tools/account-register/utils/cloudflare-handler.js new file mode 100644 index 0000000..0f99cb2 --- /dev/null +++ b/src/tools/account-register/utils/cloudflare-handler.js @@ -0,0 +1,217 @@ +/** + * Cloudflare Turnstile 验证处理器 + * 通用的Cloudflare人机验证处理,支持全自动和半自动模式 + */ + +const logger = require('../../../shared/logger'); + +class CloudflareHandler { + /** + * @param {Page} page - Puppeteer page对象 + * @param {HumanBehavior} human - 人类行为模拟对象 + * @param {string} siteName - 网站名称(用于日志) + * @param {string} mode - 验证模式: 'auto' 或 'manual' (默认 'manual') + */ + constructor(page, human, siteName, mode = 'manual') { + this.page = page; + this.human = human; + this.siteName = siteName; + this.mode = mode; // 'auto' 或 'manual' + } + + /** + * 处理Cloudflare Turnstile验证 + * @returns {Promise} 'passed' | 'failed' | 'manual' + */ + async handle() { + logger.info(this.siteName, '[Cloudflare] 检查人机验证...'); + + await this.human.randomDelay(2000, 3000); + + // 检查页面内容,判断是否有验证 + const pageText = await this.page.evaluate(() => document.body.textContent || ''); + + if (!pageText.includes('verify') && !pageText.includes('human')) { + logger.success(this.siteName, '[Cloudflare] 无验证,自动通过'); + return 'passed'; + } + + logger.info(this.siteName, '[Cloudflare] 发现验证页面'); + + // 情况1:等待自动验证(某些情况下会自动通过) + logger.info(this.siteName, '[Cloudflare] 等待自动验证(5秒)...'); + await this.human.randomDelay(5000, 6000); + + const afterWait = await this.page.evaluate(() => document.body.textContent || ''); + if (!afterWait.includes('verify') && !afterWait.includes('human')) { + logger.success(this.siteName, '[Cloudflare] 自动验证通过'); + return 'passed'; + } + + // 情况2:需要点击验证框 + if (this.mode === 'auto') { + return await this.autoHandle(); + } else { + return await this.manualHandle(); + } + } + + /** + * 自动处理模式(实验性) + */ + async autoHandle() { + logger.info(this.siteName, '[Cloudflare] 自动模式:尝试点击验证框...'); + + try { + // 查找Turnstile iframe + const frames = await this.page.frames(); + let turnstileFrame = null; + + for (const frame of frames) { + const url = frame.url(); + if (url.includes('challenges.cloudflare.com') || url.includes('turnstile')) { + turnstileFrame = frame; + logger.success(this.siteName, `[Cloudflare] 找到iframe: ${url.substring(0, 80)}...`); + break; + } + } + + if (!turnstileFrame) { + logger.warn(this.siteName, '[Cloudflare] 未找到Turnstile iframe'); + return 'failed'; + } + + // 等待iframe加载 + await turnstileFrame.waitForSelector('body', { timeout: 5000 }); + await this.human.randomDelay(8000, 8000); + + // 查找并点击checkbox + const checkboxElement = await turnstileFrame.$('label.cb-lb'); + if (!checkboxElement) { + logger.error(this.siteName, '[Cloudflare] 未找到验证框'); + return 'failed'; + } + + // 使用真实鼠标点击 + const box = await checkboxElement.boundingBox(); + if (box) { + const clickX = box.x + box.width / 2; + const clickY = box.y + box.height / 2; + + await this.page.mouse.move(clickX - 50, clickY - 50); + await this.human.randomDelay(100, 300); + await this.page.mouse.move(clickX, clickY, { steps: 10 }); + await this.human.randomDelay(200, 400); + await this.page.mouse.click(clickX, clickY); + + logger.success(this.siteName, '[Cloudflare] 已点击验证框'); + } + + // 等待验证处理 + await this.human.randomDelay(8000, 12000); + + // 检查是否通过 + const finalCheck = await this.page.evaluate(() => document.body.textContent || ''); + if (!finalCheck.includes('verify') && !finalCheck.includes('human')) { + logger.success(this.siteName, '[Cloudflare] ✓ 验证通过'); + return 'passed'; + } + + return 'failed'; + } catch (error) { + logger.error(this.siteName, `[Cloudflare] 自动处理失败: ${error.message}`); + return 'failed'; + } + } + + /** + * 半自动处理模式(推荐) + */ + async manualHandle() { + logger.info(this.siteName, '[Cloudflare] 半自动模式:等待用户手动点击...'); + + try { + // 查找Turnstile iframe + const frames = await this.page.frames(); + let turnstileFrame = null; + + for (const frame of frames) { + const url = frame.url(); + if (url.includes('challenges.cloudflare.com') || url.includes('turnstile')) { + turnstileFrame = frame; + break; + } + } + + if (turnstileFrame) { + await turnstileFrame.waitForSelector('body', { timeout: 5000 }); + } + + // 显示提示 + logger.warn(this.siteName, '='.repeat(60)); + logger.warn(this.siteName, '[Cloudflare] 🖱️ 请手动点击验证框!'); + logger.warn(this.siteName, '[Cloudflare] 程序将自动检测验证完成(超时10分钟)...'); + logger.warn(this.siteName, '='.repeat(60)); + + // 轮询检查验证是否完成(最多10分钟) + let verified = false; + let attempts = 0; + const maxAttempts = 600; // 10分钟 = 600秒 + + while (!verified && attempts < maxAttempts) { + attempts++; + await this.human.randomDelay(1000, 1000); + + // 方法1: 检查页面内容 + const pageText = await this.page.evaluate(() => document.body.textContent || ''); + const hasVerifyText = pageText.includes('verify') || pageText.includes('human'); + + // 方法2: 检查URL是否改变 + const currentUrl = this.page.url(); + const urlChanged = !currentUrl.includes('register'); + + // 方法3: 检查iframe内成功标志 + let iframeSuccess = false; + if (turnstileFrame) { + try { + iframeSuccess = await turnstileFrame.evaluate(() => { + const successDiv = document.querySelector('#success'); + return successDiv && successDiv.style.display !== 'none'; + }); + } catch (e) { + // iframe可能已消失 + } + } + + // 每15秒输出一次状态 + if (attempts % 15 === 0) { + const minutes = Math.floor(attempts / 60); + const seconds = attempts % 60; + logger.info(this.siteName, `[Cloudflare] 等待中... (${minutes}分${seconds}秒/${Math.floor(maxAttempts/60)}分钟)`); + } + + // 判断验证完成 + if (iframeSuccess || (!hasVerifyText && attempts > 3) || urlChanged) { + verified = true; + logger.success(this.siteName, '[Cloudflare] ✓✓✓ 检测到验证完成!'); + break; + } + } + + if (!verified) { + logger.error(this.siteName, '[Cloudflare] ✗ 10分钟超时'); + return 'failed'; + } + + // 验证完成后等待页面稳定 + await this.human.randomDelay(2000, 3000); + return 'passed'; + + } catch (error) { + logger.error(this.siteName, `[Cloudflare] 半自动处理失败: ${error.message}`); + return 'failed'; + } + } +} + +module.exports = CloudflareHandler;