From a3518c7054caa1f65ad7919802a7bbdfc58ccf35 Mon Sep 17 00:00:00 2001 From: dengqichen Date: Tue, 18 Nov 2025 13:03:30 +0800 Subject: [PATCH] dasdasd --- src/tools/account-register/sites/windsurf.js | 298 +++++++++++++++---- 1 file changed, 233 insertions(+), 65 deletions(-) diff --git a/src/tools/account-register/sites/windsurf.js b/src/tools/account-register/sites/windsurf.js index 848c610..16f53cf 100644 --- a/src/tools/account-register/sites/windsurf.js +++ b/src/tools/account-register/sites/windsurf.js @@ -858,14 +858,35 @@ class WindsurfRegister { * 彻底清空输入框(通过DOM直接操作) */ async clearInputField(selector) { - await this.page.evaluate((sel) => { - const field = document.querySelector(sel); + try { + // 方法1: 通过 DOM 清空 + await this.page.evaluate((sel) => { + const field = document.querySelector(sel); + if (field) { + // 聚焦 + field.focus(); + // 全选 + field.select(); + // 清空 + field.value = ''; + // 触发所有相关事件 + field.dispatchEvent(new Event('input', { bubbles: true })); + field.dispatchEvent(new Event('change', { bubbles: true })); + field.dispatchEvent(new Event('blur', { bubbles: true })); + field.blur(); + } + }, selector); + + // 方法2: 使用 Puppeteer API 进一步清理 + const field = await this.page.$(selector); if (field) { - field.value = ''; - field.dispatchEvent(new Event('input', { bubbles: true })); - field.dispatchEvent(new Event('change', { bubbles: true })); + await field.click({ clickCount: 3 }); // 三击全选 + await this.page.keyboard.press('Backspace'); + await this.human.randomDelay(100, 200); } - }, selector); + } catch (e) { + logger.warn(this.siteName, ` → 清空输入框失败 (${selector}): ${e.message}`); + } } /** @@ -1401,12 +1422,18 @@ class WindsurfRegister { document.querySelector('[name="g-recaptcha-response"]'); const hasResponse = response && response.value && response.value.length > 20; - // 检查 iframe 中的图片挑战 + // 检查 iframe 中的图片挑战(必须是可见的) let hasImageChallenge = false; const iframes = document.querySelectorAll('iframe[src*="hcaptcha"]'); for (const iframe of iframes) { const rect = iframe.getBoundingClientRect(); - if (rect.width > 300 && rect.height > 300) { + const style = window.getComputedStyle(iframe); + const isVisible = style.display !== 'none' && + style.visibility !== 'hidden' && + style.opacity !== '0' && + rect.width > 300 && rect.height > 300; + + if (isVisible) { hasImageChallenge = true; break; } @@ -1423,14 +1450,142 @@ class WindsurfRegister { if (status.hasImageChallenge && !hasImageChallenge) { hasImageChallenge = true; logger.warn(this.siteName, ' → ⚠️ 检测到图片挑战!'); - logger.warn(this.siteName, ' → 请手动完成图片验证...'); + + // 尝试使用 2captcha 自动解决图片挑战 + if (this.twoCaptchaSolver) { + try { + logger.info(this.siteName, ' → 尝试使用 2Captcha 自动解决图片挑战...'); + + // 1. 获取挑战信息 + const challengeInfo = await this.page.evaluate(() => { + // 查找挑战 iframe + const iframes = document.querySelectorAll('iframe[src*="hcaptcha"]'); + for (const iframe of iframes) { + const rect = iframe.getBoundingClientRect(); + if (rect.width > 300 && rect.height > 300) { + // 尝试获取任务文本(可能在主页面或 iframe 外层) + let taskText = ''; + const parentDiv = iframe.closest('div'); + if (parentDiv) { + const textElements = parentDiv.querySelectorAll('h2, h3, div, span, label'); + for (const el of textElements) { + const text = el.textContent?.trim(); + if (text && (text.includes('点击') || text.includes('选择') || text.includes('Select'))) { + taskText = text; + break; + } + } + } + + return { + found: true, + taskText: taskText, + iframeSrc: iframe.src, + rect: { + x: rect.x, + y: rect.y, + width: rect.width, + height: rect.height + } + }; + } + } + return { found: false }; + }); + + if (challengeInfo.found) { + logger.info(this.siteName, ` → 挑战任务: ${challengeInfo.taskText || '未检测到任务文本'}`); + + // 2. 截取挑战图片 + const challengeFrame = this.page.frames().find(f => f.url().includes('hcaptcha') && f.url().includes('challenge')); + if (challengeFrame) { + // 等待图片加载 + await this.human.randomDelay(2000, 3000); + + // 截取挑战区域的图片 + const screenshot = await this.page.screenshot({ + clip: { + x: challengeInfo.rect.x, + y: challengeInfo.rect.y, + width: challengeInfo.rect.width, + height: challengeInfo.rect.height + }, + encoding: 'base64' + }); + + logger.info(this.siteName, ' → 正在发送到 2Captcha 识别...'); + + // 3. 使用 2captcha coordinates API + const result = await this.twoCaptchaSolver.coordinates({ + body: screenshot, + textinstructions: challengeInfo.taskText || '点击正确的图片' + }); + + logger.success(this.siteName, ` → ✓ 2Captcha 识别成功!耗时: ${result.cost}秒`); + + // 4. 点击识别出的坐标 + const coords = result.data.split(',').map(coord => { + const [key, value] = coord.split(':'); + return { [key]: parseInt(value) }; + }).reduce((acc, curr) => ({ ...acc, ...curr }), {}); + + if (coords.x && coords.y) { + // 转换为屏幕坐标 + const clickX = challengeInfo.rect.x + coords.x; + const clickY = challengeInfo.rect.y + coords.y; + + logger.info(this.siteName, ` → 点击坐标: (${clickX}, ${clickY})`); + await this.page.mouse.click(clickX, clickY); + logger.success(this.siteName, ' → ✓ 已点击识别结果'); + + // 等待并点击提交按钮(通常是"验证"或"检查"按钮) + await this.human.randomDelay(1000, 1500); + + // 查找并点击提交按钮 + const submitClicked = await challengeFrame.evaluate(() => { + const buttons = document.querySelectorAll('button, [role="button"]'); + for (const btn of buttons) { + const text = btn.textContent?.trim(); + if (text && (text.includes('验证') || text.includes('检查') || text.includes('Verify') || text.includes('Submit'))) { + btn.click(); + return true; + } + } + return false; + }); + + if (submitClicked) { + logger.success(this.siteName, ' → ✓ 已提交验证'); + } + + // 等待验证结果 + await this.human.randomDelay(2000, 3000); + } + } else { + logger.warn(this.siteName, ' → 未找到挑战 iframe'); + logger.warn(this.siteName, ' → 请手动完成图片验证...'); + } + } else { + logger.warn(this.siteName, ' → 未找到挑战信息'); + logger.warn(this.siteName, ' → 请手动完成图片验证...'); + } + } catch (error) { + logger.error(this.siteName, ` → 2Captcha 自动解决失败: ${error.message}`); + logger.warn(this.siteName, ' → 请手动完成图片验证...'); + } + } else { + logger.warn(this.siteName, ' → 请手动完成图片验证...'); + } } - // 验证完成的条件:token 已填充 且 没有图片挑战 - if (status.verified && !status.hasImageChallenge) { + // 验证完成的条件:token 已填充(不管 iframe 是否还存在) + if (status.verified) { dialogClosed = true; const elapsed = ((Date.now() - startTime) / 1000).toFixed(1); logger.success(this.siteName, ` → ✓ 验证完成(耗时${elapsed}秒)`); + if (status.hasImageChallenge) { + logger.info(this.siteName, ' → 图片挑战 iframe 仍存在,但 token 已填充,视为完成'); + } break; } @@ -1448,7 +1603,9 @@ class WindsurfRegister { } if (!dialogClosed) { - logger.warn(this.siteName, ' → ⚠️ 验证超时,继续尝试支付...'); + logger.error(this.siteName, ' → ✗ 验证超时(120秒)!'); + logger.warn(this.siteName, ' → 验证码可能还在页面上,需要手动完成或重新提交'); + return false; // 返回 false,不要继续 } await this.human.randomDelay(1000, 2000); @@ -1506,11 +1663,17 @@ class WindsurfRegister { const elements = document.querySelectorAll(selector); for (const el of elements) { const text = el.textContent || ''; + // 检查卡被拒绝或卡号无效 if (text.includes('银行卡') && text.includes('拒绝') || text.includes('您的银行卡被拒绝了') || text.includes('card was declined') || text.includes('被拒绝') || - text.includes('declined')) { + text.includes('declined') || + text.includes('卡号无效') || + text.includes('您的卡号无效') || + text.includes('invalid card') || + text.includes('card number is invalid') || + text.includes('无效')) { return true; } } @@ -1675,65 +1838,70 @@ class WindsurfRegister { // 等待 Stripe 开始处理 await this.human.randomDelay(1000, 2000); - // 检查是否有验证码(只在第一次提交时检查) - if (retryCount === 0) { - // 等待可能的验证码弹窗/iframe 加载(最多等5秒) - let captchaDetected = false; - const checkStartTime = Date.now(); - const maxCheckTime = 5000; // 最多检查5秒 - let captchaCheckCount = 0; + // 检查是否有验证码(每次提交都检查) + // 等待可能的验证码弹窗/iframe 加载(最多等5秒) + let captchaDetected = false; + const checkStartTime = Date.now(); + const maxCheckTime = 5000; // 最多检查5秒 + let captchaCheckCount = 0; + + logger.info(this.siteName, ` → 检查验证码状态(第 ${retryCount + 1} 次提交)...`); + + while (Date.now() - checkStartTime < maxCheckTime && !captchaDetected) { + captchaCheckCount++; - while (Date.now() - checkStartTime < maxCheckTime && !captchaDetected) { - captchaCheckCount++; + const captchaCheck = await this.page.evaluate(() => { + // 检查多种可能的验证码 iframe 和对话框 + const stripeHCaptchaFrame = document.querySelector('iframe[src*="hcaptcha-inner"]'); + const hcaptchaFrame = document.querySelector('iframe[src*="hcaptcha.com"]'); + const hcaptchaDiv = document.querySelector('.h-captcha'); - const captchaCheck = await this.page.evaluate(() => { - // 检查多种可能的验证码 iframe 和对话框 - const stripeHCaptchaFrame = document.querySelector('iframe[src*="hcaptcha-inner"]'); - const hcaptchaFrame = document.querySelector('iframe[src*="hcaptcha.com"]'); - const hcaptchaDiv = document.querySelector('.h-captcha'); - - // 检查是否有"还需一步即可完成"的对话框 - const modalTexts = Array.from(document.querySelectorAll('*')).map(el => el.textContent); - const hasCaptchaModal = modalTexts.some(text => - text && (text.includes('还需一步') || text.includes('我是真实访问者') || text.includes('hCaptcha')) - ); - - return { - hasStripeFrame: !!stripeHCaptchaFrame, - hasHCaptchaFrame: !!hcaptchaFrame, - hasDiv: !!hcaptchaDiv, - hasModal: hasCaptchaModal, - stripeFrameUrl: stripeHCaptchaFrame ? stripeHCaptchaFrame.src.substring(0, 100) : null - }; - }); + // 检查是否有"还需一步即可完成"的对话框 + const modalTexts = Array.from(document.querySelectorAll('*')).map(el => el.textContent); + const hasCaptchaModal = modalTexts.some(text => + text && (text.includes('还需一步') || text.includes('我是真实访问者') || text.includes('hCaptcha')) + ); - // 只要检测到任一验证码元素就退出循环 - if (captchaCheck.hasStripeFrame || captchaCheck.hasHCaptchaFrame || - captchaCheck.hasDiv || captchaCheck.hasModal) { - captchaDetected = true; - logger.info(this.siteName, ` → 检测到验证码(第${captchaCheckCount}次检查,${((Date.now() - checkStartTime) / 1000).toFixed(1)}秒)`); - if (captchaCheck.hasModal) { - logger.info(this.siteName, ` → 检测到验证码对话框`); - } - if (captchaCheck.stripeFrameUrl) { - logger.info(this.siteName, ` → Stripe hCaptcha URL: ${captchaCheck.stripeFrameUrl}...`); - } - } else { - // 每500ms检查一次 - await new Promise(resolve => setTimeout(resolve, 500)); + return { + hasStripeFrame: !!stripeHCaptchaFrame, + hasHCaptchaFrame: !!hcaptchaFrame, + hasDiv: !!hcaptchaDiv, + hasModal: hasCaptchaModal, + stripeFrameUrl: stripeHCaptchaFrame ? stripeHCaptchaFrame.src.substring(0, 100) : null + }; + }); + + // 只要检测到任一验证码元素就退出循环 + if (captchaCheck.hasStripeFrame || captchaCheck.hasHCaptchaFrame || + captchaCheck.hasDiv || captchaCheck.hasModal) { + captchaDetected = true; + logger.info(this.siteName, ` → 检测到验证码(第${captchaCheckCount}次检查,${((Date.now() - checkStartTime) / 1000).toFixed(1)}秒)`); + if (captchaCheck.hasModal) { + logger.info(this.siteName, ` → 检测到验证码对话框`); + } + if (captchaCheck.stripeFrameUrl) { + logger.info(this.siteName, ` → Stripe hCaptcha URL: ${captchaCheck.stripeFrameUrl}...`); } - } - - if (captchaDetected) { - 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)}秒)`); + // 每500ms检查一次 + await new Promise(resolve => setTimeout(resolve, 500)); } + } + + if (captchaDetected) { + logger.info(this.siteName, ' → 检测到验证码,使用 YesCaptcha API 处理...'); + // 使用 YesCaptcha API 处理 + const captchaSolved = await this.handleHCaptcha(); + if (!captchaSolved) { + logger.error(this.siteName, ' → ✗ 验证码自动处理失败!'); + logger.warn(this.siteName, ' → 继续检测支付结果(可能用户已手动完成,或卡被拒绝需要重试)'); + // ✅ 不要直接返回 false,继续执行,检测支付结果 + } else { + logger.success(this.siteName, ' → ✓ 验证码处理成功'); + } + await this.human.randomDelay(1000, 2000); } else { - logger.info(this.siteName, ` → 跳过验证码检查(重试 ${retryCount + 1})`); + logger.info(this.siteName, ` → ✓ 无验证码(检查${captchaCheckCount}次,耗时${((Date.now() - checkStartTime) / 1000).toFixed(1)}秒)`); } // 开始轮询检测:同时等待成功或失败