From 75986287d12e13381b6a140685d09c0cee70835d Mon Sep 17 00:00:00 2001 From: dengqichen Date: Mon, 17 Nov 2025 21:14:17 +0800 Subject: [PATCH] aaaaa --- src/tools/account-register/sites/windsurf.js | 395 ++++++++++++++---- .../account-register/utils/capsolver-api.js | 30 +- 2 files changed, 343 insertions(+), 82 deletions(-) diff --git a/src/tools/account-register/sites/windsurf.js b/src/tools/account-register/sites/windsurf.js index 164b0bc..0996741 100644 --- a/src/tools/account-register/sites/windsurf.js +++ b/src/tools/account-register/sites/windsurf.js @@ -836,7 +836,21 @@ class WindsurfRegister { } /** - * 填写银行卡表单 + * 彻底清空输入框(通过DOM直接操作) + */ + async clearInputField(selector) { + await this.page.evaluate((sel) => { + const field = document.querySelector(sel); + if (field) { + field.value = ''; + field.dispatchEvent(new Event('input', { bubbles: true })); + field.dispatchEvent(new Event('change', { bubbles: true })); + } + }, selector); + } + + /** + * 填写银行卡表单(每次都彻底清空后重新填写) */ async fillCardForm(card, isRetry = false) { if (!isRetry) { @@ -866,56 +880,81 @@ class WindsurfRegister { } } - // 填写卡号 - logger.info(this.siteName, ' → 填写卡号...'); - const cardNumberField = await this.page.$('#cardNumber'); - await cardNumberField.click(); - await this.human.randomDelay(300, 500); - if (isRetry) { - await this.page.keyboard.down('Control'); - await this.page.keyboard.press('A'); - await this.page.keyboard.up('Control'); - } - await this.page.keyboard.press('Backspace'); - await this.human.randomDelay(500, 1000); - await cardNumberField.type(card.number, { delay: 250 }); + // ===== 填写卡号 ===== + logger.info(this.siteName, ` → 填写卡号: ${card.number}...`); - // 填写有效期 - logger.info(this.siteName, ' → 填写有效期...'); - const cardExpiryField = await this.page.$('#cardExpiry'); - await cardExpiryField.click(); - await this.human.randomDelay(200, 300); - if (isRetry) { - await this.page.keyboard.down('Control'); - await this.page.keyboard.press('A'); - await this.page.keyboard.up('Control'); - } - await this.page.keyboard.press('Backspace'); + // 1. 彻底清空卡号 + await this.clearInputField('#cardNumber'); await this.human.randomDelay(300, 500); + + // 2. 点击并聚焦 + await this.page.click('#cardNumber'); + await this.human.randomDelay(300, 500); + + // 3. 再次确保清空(按多次 Backspace) + for (let i = 0; i < 25; i++) { + await this.page.keyboard.press('Backspace'); + } + await this.human.randomDelay(300, 500); + + // 4. 填写新卡号 + await this.page.type('#cardNumber', card.number, { delay: 100 }); + await this.human.randomDelay(500, 800); + logger.success(this.siteName, ' → ✓ 卡号已填写'); + + // ===== 填写有效期 ===== const expiry = `${card.month}${card.year}`; - await cardExpiryField.type(expiry, { delay: 250 }); + logger.info(this.siteName, ` → 填写有效期: ${card.month}/${card.year}...`); - // 填写CVC - logger.info(this.siteName, ' → 填写CVC...'); - const cardCvcField = await this.page.$('#cardCvc'); - await cardCvcField.click(); + // 1. 彻底清空 + await this.clearInputField('#cardExpiry'); await this.human.randomDelay(200, 300); - if (isRetry) { - await this.page.keyboard.down('Control'); - await this.page.keyboard.press('A'); - await this.page.keyboard.up('Control'); - } - await this.page.keyboard.press('Backspace'); - await this.human.randomDelay(300, 500); - await cardCvcField.type(card.cvv, { delay: 250 }); + // 2. 点击并聚焦 + await this.page.click('#cardExpiry'); + await this.human.randomDelay(200, 300); + + // 3. 再次确保清空 + for (let i = 0; i < 10; i++) { + await this.page.keyboard.press('Backspace'); + } + await this.human.randomDelay(300, 500); + + // 4. 填写新有效期 + await this.page.type('#cardExpiry', expiry, { delay: 100 }); + await this.human.randomDelay(500, 800); + logger.success(this.siteName, ' → ✓ 有效期已填写'); + + // ===== 填写CVC ===== + logger.info(this.siteName, ` → 填写CVC: ${card.cvv}...`); + + // 1. 彻底清空 + await this.clearInputField('#cardCvc'); + await this.human.randomDelay(200, 300); + + // 2. 点击并聚焦 + await this.page.click('#cardCvc'); + await this.human.randomDelay(200, 300); + + // 3. 再次确保清空 + for (let i = 0; i < 10; i++) { + await this.page.keyboard.press('Backspace'); + } + await this.human.randomDelay(300, 500); + + // 4. 填写新CVC + await this.page.type('#cardCvc', card.cvv, { delay: 100 }); + await this.human.randomDelay(500, 800); + logger.success(this.siteName, ' → ✓ CVC已填写'); + + // ===== 首次填写:持卡人姓名和地址 ===== if (!isRetry) { - // 首次填写:填写持卡人姓名和地址 logger.info(this.siteName, ' → 填写持卡人姓名...'); + await this.clearInputField('#billingName'); await this.page.click('#billingName'); await this.human.randomDelay(300, 500); const fullName = `${this.accountData.firstName} ${this.accountData.lastName}`; - await this.page.type('#billingName', fullName, { delay: 200 }); + await this.page.type('#billingName', fullName, { delay: 100 }); logger.info(this.siteName, ' → 选择地址:中国澳门特别行政区...'); await this.page.select('#billingCountry', 'MO'); @@ -931,67 +970,171 @@ class WindsurfRegister { } } } + + logger.success(this.siteName, ' → ✓ 银行卡信息填写完成'); } /** - * 处理 hCaptcha 验证码 + * 处理 hCaptcha 验证码(使用 CapSolver API 自动识别) */ async handleHCaptcha() { - const hasHCaptcha = await this.page.evaluate(() => { + // 获取 captcha 信息(支持 Stripe iframe 和标准 hCaptcha) + const captchaInfo = await this.page.evaluate(() => { + // 方式1: Stripe 的 hCaptcha iframe + const stripeFrame = document.querySelector('iframe[src*="hcaptcha-inner"]'); + if (stripeFrame) { + try { + const url = new URL(stripeFrame.src); + const hash = url.hash.substring(1); + const params = new URLSearchParams(hash); + const sitekey = params.get('sitekey'); + + if (sitekey) { + return { + type: 'stripe-iframe', + siteKey: sitekey, + callback: null, + containerId: null + }; + } + } catch (e) { + // 继续尝试其他方式 + } + } + + // 方式2: 标准的 hCaptcha + const hcaptchaDiv = document.querySelector('.h-captcha'); + if (hcaptchaDiv) { + return { + type: 'standard', + siteKey: hcaptchaDiv.getAttribute('data-sitekey'), + callback: hcaptchaDiv.getAttribute('data-callback'), + containerId: hcaptchaDiv.id || hcaptchaDiv.getAttribute('data-hcaptcha-widget-id') + }; + } + + // 方式3: hcaptcha.com iframe const hcaptchaFrame = document.querySelector('iframe[src*="hcaptcha.com"]'); - const hcaptchaCheckbox = document.querySelector('.h-captcha'); - return !!(hcaptchaFrame || hcaptchaCheckbox); + if (hcaptchaFrame) { + return { + type: 'iframe', + siteKey: null, + callback: null, + containerId: null + }; + } + + return null; }); - if (!hasHCaptcha) return true; + if (!captchaInfo) { + logger.info(this.siteName, ' → 未检测到 hCaptcha,跳过'); + return true; + } - logger.warn(this.siteName, ' → 检测到 hCaptcha 验证码'); + logger.info(this.siteName, ` → 检测到 hCaptcha(类型: ${captchaInfo.type})`); + if (!captchaInfo.siteKey) { + logger.warn(this.siteName, ' → 无法获取 siteKey,跳过自动识别'); + return true; + } + + // 尝试使用 CapSolver API 自动识别 if (this.capsolver.apiKey) { try { - logger.info(this.siteName, ' → 尝试使用 CapSolver 自动识别...'); - const siteKey = await this.page.evaluate(() => { - const hcaptchaDiv = document.querySelector('.h-captcha'); - return hcaptchaDiv ? hcaptchaDiv.getAttribute('data-sitekey') : null; - }); + logger.info(this.siteName, ' → 使用 CapSolver API 自动识别...'); - if (siteKey) { - const currentUrl = this.page.url(); - const token = await this.capsolver.solveHCaptcha(siteKey, currentUrl); - await this.page.evaluate((token) => { + logger.info(this.siteName, ` → SiteKey: ${captchaInfo.siteKey.substring(0, 20)}...`); + if (captchaInfo.callback) { + logger.info(this.siteName, ` → Callback: ${captchaInfo.callback}`); + } + + // 2. 调用 CapSolver API 获取 token + const currentUrl = this.page.url(); + const token = await this.capsolver.solveHCaptcha(captchaInfo.siteKey, currentUrl); + + logger.info(this.siteName, ` → 获取到 token: ${token.substring(0, 30)}...`); + + // 3. 注入 token 并触发回调 + const injected = await this.page.evaluate((token, callbackName) => { + try { + // 方法1: 设置到隐藏的 textarea const textarea = document.querySelector('[name="h-captcha-response"]'); - if (textarea) textarea.value = token; + const textareaG = document.querySelector('[name="g-recaptcha-response"]'); + if (textarea) { + textarea.value = token; + textarea.innerHTML = token; + } + if (textareaG) { + textareaG.value = token; + textareaG.innerHTML = token; + } + + // 方法2: 使用 hCaptcha API if (window.hcaptcha && window.hcaptcha.setResponse) { window.hcaptcha.setResponse(token); } - }, token); - logger.success(this.siteName, ' → ✓ hCaptcha 自动识别成功'); - await this.human.randomDelay(2000, 3000); - return true; + + // 方法3: 触发自定义回调(如果有) + if (callbackName && typeof window[callbackName] === 'function') { + window[callbackName](token); + return { success: true, method: 'callback', callback: callbackName }; + } + + // 方法4: 触发 hCaptcha 回调事件 + if (window.hcaptcha && window.hcaptcha.callback) { + window.hcaptcha.callback(token); + return { success: true, method: 'hcaptcha.callback' }; + } + + return { success: true, method: 'textarea' }; + } catch (e) { + return { success: false, error: e.message }; + } + }, token, captchaInfo.callback); + + if (!injected.success) { + throw new Error(`Token 注入失败: ${injected.error}`); } + + logger.success(this.siteName, ` → ✓ hCaptcha 自动识别成功 (方式: ${injected.method})`); + await this.human.randomDelay(1000, 2000); + return true; + } catch (error) { - logger.error(this.siteName, ` → ✗ 自动识别失败: ${error.message}`); + logger.error(this.siteName, ` → ✗ CapSolver 自动识别失败: ${error.message}`); + logger.warn(this.siteName, ' → 将回退到手动模式'); } + } else { + logger.warn(this.siteName, ' → 未配置 CapSolver API Key'); } - // 手动等待 + // 手动等待模式 logger.warn(this.siteName, ' → 请手动完成验证码(等待120秒)...'); const startWait = Date.now(); + while (Date.now() - startWait < 120000) { const captchaSolved = await this.page.evaluate(() => { - const response = document.querySelector('[name="h-captcha-response"]'); - return response && response.value.length > 0; + const response = document.querySelector('[name="h-captcha-response"]') || + document.querySelector('[name="g-recaptcha-response"]'); + return response && response.value && response.value.length > 0; }); + if (captchaSolved) { logger.success(this.siteName, ' → ✓ 验证码已完成'); return true; } - if ((Date.now() - startWait) % 10000 === 0) { - const elapsed = Math.floor((Date.now() - startWait) / 1000); - logger.info(this.siteName, ` → 等待验证码... (${elapsed}秒)`); + + // 每10秒输出一次进度 + const elapsed = Date.now() - startWait; + if (elapsed > 0 && elapsed % 10000 === 0) { + logger.info(this.siteName, ` → 等待验证码... (${(elapsed/1000).toFixed(0)}秒)`); } + await new Promise(resolve => setTimeout(resolve, 1000)); } + + logger.error(this.siteName, ' → ✗ 验证码超时(120秒)'); return false; } @@ -1072,18 +1215,64 @@ class WindsurfRegister { // 等待一下让 Stripe 开始处理 await this.human.randomDelay(2000, 3000); - // 检查是否有验证码(快速检查) - const hasHCaptcha = await this.page.evaluate(() => { - return !!( - document.querySelector('iframe[src*="hcaptcha.com"]') || - document.querySelector('.h-captcha') - ); - }); - - if (hasHCaptcha) { - logger.info(this.siteName, ' → 检测到验证码,处理中...'); - await this.handleHCaptcha(); - 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; + + 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 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 + }; + }); + + // 只要检测到任一验证码元素就退出循环 + 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)); + } + } + + if (captchaDetected) { + logger.info(this.siteName, ' → 正在处理验证码...'); + await this.handleHCaptcha(); + await this.human.randomDelay(1000, 2000); + } else { + logger.info(this.siteName, ` → ✓ 无验证码(检查${captchaCheckCount}次,耗时${((Date.now() - checkStartTime) / 1000).toFixed(1)}秒)`); + } + } else { + logger.info(this.siteName, ` → 跳过验证码检查(重试 ${retryCount + 1})`); } // 开始轮询检测:同时等待成功或失败 @@ -1100,6 +1289,9 @@ class WindsurfRegister { if (cardRejected) { logger.warn(this.siteName, ` → ⚠️ 银行卡被拒绝!(第 ${retryCount + 1}/${maxRetries} 次,检测了 ${checkCount} 次)`); + // 等待错误信息完全显示(1-2秒) + await this.human.randomDelay(1000, 2000); + // 生成新卡 logger.info(this.siteName, ' → 生成新的银行卡信息...'); const cardGen = new CardGenerator(); @@ -1114,9 +1306,30 @@ class WindsurfRegister { country: 'MO' }; + // 检查页面状态,确保表单仍然可用 + try { + await this.page.waitForFunction( + () => { + const cardNumber = document.querySelector('#cardNumber'); + const cardExpiry = document.querySelector('#cardExpiry'); + const cardCvc = document.querySelector('#cardCvc'); + return cardNumber && cardExpiry && cardCvc; + }, + { timeout: 5000 } + ); + } catch (e) { + logger.warn(this.siteName, ` → 表单可能已刷新,等待恢复...`); + await this.human.randomDelay(2000, 3000); + } + + // 模拟人工思考(1-2秒) + await this.human.randomDelay(1000, 2000); + // 重新填写卡信息 await this.fillCardForm(newCard, true); logger.success(this.siteName, ' → ✓ 已更新银行卡信息'); + + // 填写后等待页面响应(2-3秒) await this.human.randomDelay(2000, 3000); // 递归重试 @@ -1147,6 +1360,9 @@ class WindsurfRegister { logger.warn(this.siteName, ` → ⚠️ 支付超时(${maxWait/1000}秒),共检测 ${checkCount} 次`); logger.info(this.siteName, ` → 将生成新卡重试 (第 ${retryCount + 1}/${maxRetries} 次)`); + // 等待页面稳定(1-2秒) + await this.human.randomDelay(1000, 2000); + // 生成新卡 const cardGen = new CardGenerator(); const newCard = cardGen.generate('unionpay'); @@ -1160,9 +1376,30 @@ class WindsurfRegister { country: 'MO' }; + // 检查页面状态 + try { + await this.page.waitForFunction( + () => { + const cardNumber = document.querySelector('#cardNumber'); + const cardExpiry = document.querySelector('#cardExpiry'); + const cardCvc = document.querySelector('#cardCvc'); + return cardNumber && cardExpiry && cardCvc; + }, + { timeout: 5000 } + ); + } catch (e) { + logger.warn(this.siteName, ` → 表单可能已刷新,等待恢复...`); + await this.human.randomDelay(2000, 3000); + } + + // 模拟人工思考(1-2秒) + await this.human.randomDelay(1000, 2000); + // 重新填写卡信息 await this.fillCardForm(newCard, true); logger.success(this.siteName, ' → ✓ 已更新银行卡信息'); + + // 填写后等待(2-3秒) await this.human.randomDelay(2000, 3000); // 递归重试 diff --git a/src/tools/account-register/utils/capsolver-api.js b/src/tools/account-register/utils/capsolver-api.js index 6761769..a8e636c 100644 --- a/src/tools/account-register/utils/capsolver-api.js +++ b/src/tools/account-register/utils/capsolver-api.js @@ -32,16 +32,22 @@ class CapSolverAPI { try { // 1. 创建任务 - const createTaskResponse = await axios.post(`${this.apiUrl}/createTask`, { + const requestBody = { clientKey: this.apiKey, task: { type: 'HCaptchaTaskProxyless', websiteURL: pageUrl, websiteKey: siteKey } - }); + }; + + logger.info('CapSolver', `请求 URL: ${pageUrl}`); + logger.info('CapSolver', `API Key: ${this.apiKey.substring(0, 10)}...`); + + const createTaskResponse = await axios.post(`${this.apiUrl}/createTask`, requestBody); if (createTaskResponse.data.errorId !== 0) { + logger.error('CapSolver', `API 返回错误: ${JSON.stringify(createTaskResponse.data)}`); throw new Error(`创建任务失败: ${createTaskResponse.data.errorDescription}`); } @@ -86,7 +92,25 @@ class CapSolverAPI { throw new Error('CapSolver 识别超时(2分钟)'); } catch (error) { - logger.error('CapSolver', `识别失败: ${error.message}`); + // 详细的错误日志 + if (error.response) { + // API 返回了错误响应 + logger.error('CapSolver', `HTTP ${error.response.status}: ${error.response.statusText}`); + logger.error('CapSolver', `响应数据: ${JSON.stringify(error.response.data)}`); + + if (error.response.status === 400) { + logger.error('CapSolver', '可能原因:'); + logger.error('CapSolver', ' 1. API Key 无效或已过期'); + logger.error('CapSolver', ' 2. 余额不足'); + logger.error('CapSolver', ' 3. siteKey 或 URL 格式错误'); + } + } else if (error.request) { + // 请求已发送但没有收到响应 + logger.error('CapSolver', '无法连接到 CapSolver API'); + } else { + // 其他错误 + logger.error('CapSolver', `识别失败: ${error.message}`); + } throw error; } }