diff --git a/.env b/.env index 14e1b52..8b4b516 100644 --- a/.env +++ b/.env @@ -9,3 +9,6 @@ MYSQL_PORT=3306 MYSQL_USER=windsurf-auto-register MYSQL_PASSWORD=Qichen5210523 MYSQL_DATABASE=windsurf-auto-register + +# CapSolver 验证码识别 +CAPSOLVER_API_KEY=CAP-0FCDDA4906E87D9F4FF68EAECD34E320876FBA70E4F30EA1ADCD264EDB15E4BF diff --git a/src/tools/account-register/sites/windsurf.js b/src/tools/account-register/sites/windsurf.js index 0cbe561..a45c705 100644 --- a/src/tools/account-register/sites/windsurf.js +++ b/src/tools/account-register/sites/windsurf.js @@ -18,6 +18,7 @@ const EmailVerificationService = require('../email-verification'); const { DEFAULT_CONFIG } = require('../config'); const CardGenerator = require('../../card-generator/generator'); const database = require('../../database'); +const CapSolverAPI = require('../utils/capsolver-api'); class WindsurfRegister { constructor(options = {}) { @@ -26,6 +27,7 @@ class WindsurfRegister { this.dataGen = new AccountDataGenerator(); this.human = new HumanBehavior(); this.emailService = new EmailVerificationService(); + this.capsolver = new CapSolverAPI(); this.browser = null; this.page = null; this.currentStep = 0; @@ -922,44 +924,18 @@ class WindsurfRegister { } /** - * 步骤6: 填写支付信息 + * 填写银行卡表单 */ - async step6_fillPayment() { - logger.info(this.siteName, `[步骤 6/${this.getTotalSteps()}] 填写支付信息`); - - try { - // 等待页面加载 - await this.human.readPage(3, 5); - - // 1. 生成信用卡信息(使用银联卡) - logger.info(this.siteName, ' → 生成银联卡信息...'); - const cardGen = new CardGenerator(); - const card = cardGen.generate('unionpay'); - logger.info(this.siteName, ` → 卡号: ${card.number}`); - logger.info(this.siteName, ` → 有效期: ${card.month}/${card.year}`); - logger.info(this.siteName, ` → CVV: ${card.cvv}`); - - // 保存卡信息供后续使用 - this.cardInfo = { - number: card.number, - month: card.month, - year: card.year, - cvv: card.cvv, - country: 'MO' - }; - - // 2. 点击选择"银行卡"支付方式 + async fillCardForm(card, isRetry = false) { + if (!isRetry) { + // 首次填写:选择支付方式 logger.info(this.siteName, ' → 选择银行卡支付方式...'); const cardRadio = await this.page.$('input[type="radio"][value="card"]'); if (cardRadio) { await cardRadio.click(); logger.success(this.siteName, ' → ✓ 已点击银行卡选项'); - - // 等待支付表单完全加载(等待骨架屏消失,表单元素出现) - logger.info(this.siteName, ' → 等待支付表单加载...'); await this.human.randomDelay(3000, 5000); - // 等待所有必需的支付字段都加载完成 try { await this.page.waitForFunction( () => { @@ -967,7 +943,6 @@ class WindsurfRegister { const cardExpiry = document.querySelector('#cardExpiry'); const cardCvc = document.querySelector('#cardCvc'); const billingName = document.querySelector('#billingName'); - // 检查所有字段都存在且可见 return cardNumber && cardExpiry && cardCvc && billingName; }, { timeout: 30000 } @@ -977,186 +952,281 @@ class WindsurfRegister { logger.warn(this.siteName, ` → 等待表单超时: ${e.message}`); } } - - // 3. 填写卡号 - logger.info(this.siteName, ' → 填写卡号...'); - await this.page.waitForSelector('#cardNumber', { visible: true, timeout: 10000 }); - await this.page.click('#cardNumber'); - await this.human.randomDelay(500, 1000); - await this.page.type('#cardNumber', card.number, { delay: 250 }); - - // 4. 填写有效期(月份/年份) - logger.info(this.siteName, ' → 填写有效期...'); - await this.page.click('#cardExpiry'); - await this.human.randomDelay(300, 500); - const expiry = `${card.month}${card.year}`; // 格式: MMYY - await this.page.type('#cardExpiry', expiry, { delay: 250 }); - - // 5. 填写CVC - logger.info(this.siteName, ' → 填写CVC...'); - await this.page.click('#cardCvc'); - await this.human.randomDelay(300, 500); - await this.page.type('#cardCvc', card.cvv, { delay: 250 }); - - // 6. 填写持卡人姓名 + } + + // 填写卡号 + 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, ' → 填写有效期...'); + 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'); + await this.human.randomDelay(300, 500); + const expiry = `${card.month}${card.year}`; + await cardExpiryField.type(expiry, { delay: 250 }); + + // 填写CVC + logger.info(this.siteName, ' → 填写CVC...'); + const cardCvcField = await this.page.$('#cardCvc'); + await cardCvcField.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'); + await this.human.randomDelay(300, 500); + await cardCvcField.type(card.cvv, { delay: 250 }); + + if (!isRetry) { + // 首次填写:填写持卡人姓名和地址 logger.info(this.siteName, ' → 填写持卡人姓名...'); 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 }); - // 7. 选择地址:中国澳门特别行政区 logger.info(this.siteName, ' → 选择地址:中国澳门特别行政区...'); await this.page.select('#billingCountry', 'MO'); await this.human.randomDelay(1000, 2000); - // 8. 填写地址信息(如果需要) - // 等待地址字段加载 - await this.human.randomDelay(1000, 2000); - - // 检查是否需要填写地址行1和行2 const addressFields = await this.page.$$('input[placeholder*="地址"]'); if (addressFields.length > 0) { logger.info(this.siteName, ' → 填写地址信息...'); - // 填写简单的地址 await addressFields[0].type('Macau', { delay: 100 }); if (addressFields[1]) { await this.human.randomDelay(300, 500); await addressFields[1].type('Macao', { delay: 100 }); } } - - // 9. 点击订阅按钮并检测卡片拒绝(支持重试) - logger.info(this.siteName, ' → 点击订阅按钮...'); - await this.human.randomDelay(2000, 3000); - const maxRetries = 5; // 最多重试5次 - let retryCount = 0; - let paymentSuccess = false; - - while (!paymentSuccess && retryCount < maxRetries) { - const submitButton = await this.page.$('button[type="submit"][data-testid="hosted-payment-submit-button"]'); - if (!submitButton) { - logger.warn(this.siteName, ' → 未找到订阅按钮'); - break; - } - - if (retryCount > 0) { - logger.info(this.siteName, ` → 第 ${retryCount + 1} 次尝试提交...`); - } - - await submitButton.click(); - logger.success(this.siteName, ' → ✓ 已点击订阅按钮'); - - // 等待一下让页面响应 - await this.human.randomDelay(2000, 3000); - - // 检测是否出现"银行卡被拒绝"错误 - const cardRejected = await this.page.evaluate(() => { - const errorDiv = document.querySelector('.FieldError-container'); - if (errorDiv) { - const errorText = errorDiv.textContent; - return errorText.includes('银行卡被拒绝') || - errorText.includes('card was declined') || - errorText.includes('被拒绝'); - } - return false; + } + } + + /** + * 处理 hCaptcha 验证码 + */ + async handleHCaptcha() { + const hasHCaptcha = await this.page.evaluate(() => { + const hcaptchaFrame = document.querySelector('iframe[src*="hcaptcha.com"]'); + const hcaptchaCheckbox = document.querySelector('.h-captcha'); + return !!(hcaptchaFrame || hcaptchaCheckbox); + }); + + if (!hasHCaptcha) return true; + + logger.warn(this.siteName, ' → 检测到 hCaptcha 验证码'); + + 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; }); - if (cardRejected) { - retryCount++; - logger.warn(this.siteName, ` → ⚠️ 银行卡被拒绝!(第 ${retryCount}/${maxRetries} 次)`); - - if (retryCount >= maxRetries) { - logger.error(this.siteName, ` → ✗ 已达到最大重试次数 (${maxRetries})`); - throw new Error(`银行卡被拒绝,已重试 ${maxRetries} 次`); - } - - // 生成新的银行卡 - logger.info(this.siteName, ' → 生成新的银行卡信息...'); - const cardGen = new CardGenerator(); - const newCard = cardGen.generate('unionpay'); - logger.info(this.siteName, ` → 新卡号: ${newCard.number}`); - logger.info(this.siteName, ` → 有效期: ${newCard.month}/${newCard.year}`); - logger.info(this.siteName, ` → CVV: ${newCard.cvv}`); - - // 更新卡信息 - this.cardInfo = { - number: newCard.number, - month: newCard.month, - year: newCard.year, - cvv: newCard.cvv, - country: 'MO' - }; - - // 清空并重新填写卡号 - logger.info(this.siteName, ' → 清空并重新填写卡号...'); - const cardNumberField = await this.page.$('#cardNumber'); - await cardNumberField.click({ clickCount: 3 }); - await this.page.keyboard.press('Backspace'); - await this.human.randomDelay(500, 1000); - await cardNumberField.type(newCard.number, { delay: 250 }); - - // 清空并重新填写有效期 - logger.info(this.siteName, ' → 清空并重新填写有效期...'); - const cardExpiryField = await this.page.$('#cardExpiry'); - await cardExpiryField.click({ clickCount: 3 }); - await this.page.keyboard.press('Backspace'); - await this.human.randomDelay(300, 500); - const expiry = `${newCard.month}${newCard.year}`; - await cardExpiryField.type(expiry, { delay: 250 }); - - // 清空并重新填写CVC - logger.info(this.siteName, ' → 清空并重新填写CVC...'); - const cardCvcField = await this.page.$('#cardCvc'); - await cardCvcField.click({ clickCount: 3 }); - await this.page.keyboard.press('Backspace'); - await this.human.randomDelay(300, 500); - await cardCvcField.type(newCard.cvv, { delay: 250 }); - - logger.success(this.siteName, ' → ✓ 已更新银行卡信息,准备重试...'); - await this.human.randomDelay(2000, 3000); - - // 继续下一次循环重试 - continue; - } - - // 没有错误,等待支付处理完成 - logger.info(this.siteName, ' → 等待支付处理...'); - logger.info(this.siteName, ' → 将持续等待直到支付完成(无时间限制)...'); - - const paymentStartTime = Date.now(); - let paymentComplete = false; - - while (!paymentComplete) { + if (siteKey) { const currentUrl = this.page.url(); - - // 检测是否已经离开 Stripe 页面(支付成功的标志) - if (!currentUrl.includes('stripe.com') && !currentUrl.includes('checkout.stripe.com')) { - paymentComplete = true; - paymentSuccess = true; - const totalTime = ((Date.now() - paymentStartTime) / 1000).toFixed(1); - - // 记录注册时间(支付成功的时间) - this.registrationTime = new Date(); - logger.success(this.siteName, ` → ✓ 支付成功!已离开Stripe页面 (耗时: ${totalTime}秒)`); - logger.info(this.siteName, ` → 当前页面: ${currentUrl}`); - logger.info(this.siteName, ` → 注册时间: ${this.registrationTime.toLocaleString('zh-CN')}`); - break; + const token = await this.capsolver.solveHCaptcha(siteKey, currentUrl); + await this.page.evaluate((token) => { + const textarea = document.querySelector('[name="h-captcha-response"]'); + if (textarea) textarea.value = token; + if (window.hcaptcha && window.hcaptcha.setResponse) { + window.hcaptcha.setResponse(token); + } + }, token); + logger.success(this.siteName, ' → ✓ hCaptcha 自动识别成功'); + await this.human.randomDelay(2000, 3000); + return true; + } + } catch (error) { + logger.error(this.siteName, ` → ✗ 自动识别失败: ${error.message}`); + } + } + + // 手动等待 + 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; + }); + 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}秒)`); + } + await new Promise(resolve => setTimeout(resolve, 1000)); + } + return false; + } + + /** + * 检查银行卡是否被拒绝 + */ + async checkCardRejected() { + return await this.page.evaluate(() => { + const errorContainers = [ + '.FieldError-container', + '[class*="Error"]', + '[class*="error"]', + '.error-message' + ]; + for (const selector of errorContainers) { + 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')) { + return true; } - - // 每5秒输出一次进度 - const elapsed = Date.now() - paymentStartTime; - if (elapsed > 0 && elapsed % 5000 === 0) { - logger.info(this.siteName, ` → 支付处理中... 已用时 ${(elapsed/1000).toFixed(0)}秒`); - } - - await new Promise(resolve => setTimeout(resolve, 500)); } } + return false; + }); + } + + /** + * 等待支付成功 + */ + async waitForPaymentSuccess() { + const paymentStartTime = Date.now(); + logger.info(this.siteName, ' → 等待支付处理...'); + + while (true) { + const currentUrl = this.page.url(); + if (!currentUrl.includes('stripe.com') && !currentUrl.includes('checkout.stripe.com')) { + const totalTime = ((Date.now() - paymentStartTime) / 1000).toFixed(1); + this.registrationTime = new Date(); + logger.success(this.siteName, ` → ✓ 支付成功!(耗时: ${totalTime}秒)`); + logger.info(this.siteName, ` → 当前页面: ${currentUrl}`); + return true; + } + const elapsed = Date.now() - paymentStartTime; + if (elapsed > 0 && elapsed % 5000 === 0) { + logger.info(this.siteName, ` → 支付处理中... (${(elapsed/1000).toFixed(0)}秒)`); + } + await new Promise(resolve => setTimeout(resolve, 500)); + } + } + + /** + * 提交支付(递归重试) + */ + async submitPayment(retryCount = 0, maxRetries = 5) { + if (retryCount >= maxRetries) { + throw new Error(`银行卡被拒绝,已重试 ${maxRetries} 次`); + } + + if (retryCount > 0) { + logger.info(this.siteName, ` → 第 ${retryCount + 1} 次尝试提交...`); + } + + // 点击订阅按钮 + const submitButton = await this.page.$('button[type="submit"][data-testid="hosted-payment-submit-button"]'); + if (!submitButton) { + logger.warn(this.siteName, ' → 未找到订阅按钮'); + return false; + } + + await submitButton.click(); + logger.success(this.siteName, ' → ✓ 已点击订阅按钮'); + await this.human.randomDelay(3000, 5000); + + // 处理验证码 + await this.handleHCaptcha(); + await this.human.randomDelay(2000, 3000); + + // 检查卡是否被拒绝 + const cardRejected = await this.checkCardRejected(); + if (cardRejected) { + logger.warn(this.siteName, ` → ⚠️ 银行卡被拒绝!(第 ${retryCount + 1}/${maxRetries} 次)`); - // 额外等待页面稳定 + // 生成新卡 + logger.info(this.siteName, ' → 生成新的银行卡信息...'); + const cardGen = new CardGenerator(); + const newCard = cardGen.generate('unionpay'); + logger.info(this.siteName, ` → 新卡号: ${newCard.number}`); + + this.cardInfo = { + number: newCard.number, + month: newCard.month, + year: newCard.year, + cvv: newCard.cvv, + country: 'MO' + }; + + // 重新填写卡信息 + await this.fillCardForm(newCard, true); + logger.success(this.siteName, ' → ✓ 已更新银行卡信息'); await this.human.randomDelay(2000, 3000); + // 递归重试 + return await this.submitPayment(retryCount + 1, maxRetries); + } + + // 等待支付成功 + await this.waitForPaymentSuccess(); + return true; + } + + /** + * 步骤6: 填写支付信息 + */ + async step6_fillPayment() { + logger.info(this.siteName, `[步骤 6/${this.getTotalSteps()}] 填写支付信息`); + + try { + await this.human.readPage(3, 5); + + // 生成银行卡 + logger.info(this.siteName, ' → 生成银联卡信息...'); + const cardGen = new CardGenerator(); + const card = cardGen.generate('unionpay'); + logger.info(this.siteName, ` → 卡号: ${card.number}`); + logger.info(this.siteName, ` → 有效期: ${card.month}/${card.year}`); + logger.info(this.siteName, ` → CVV: ${card.cvv}`); + + this.cardInfo = { + number: card.number, + month: card.month, + year: card.year, + cvv: card.cvv, + country: 'MO' + }; + + // 填写卡表单 + await this.fillCardForm(card); + await this.human.randomDelay(2000, 3000); + + // 提交支付(递归重试) + await this.submitPayment(); + this.currentStep = 6; logger.success(this.siteName, `步骤 6 完成`); @@ -1278,34 +1348,8 @@ class WindsurfRegister { logger.warn(this.siteName, ' → ⚠️ 未找到账单周期信息'); } - // 4. 汇总打印所有信息 - logger.info(this.siteName, ''); - logger.info(this.siteName, '┌─────────────────────────────────────────────────────┐'); - logger.info(this.siteName, '│ 订阅信息汇总 │'); - logger.info(this.siteName, '└─────────────────────────────────────────────────────┘'); - - if (this.registrationTime) { - logger.info(this.siteName, `📅 注册时间: ${this.registrationTime.toLocaleString('zh-CN', { - year: 'numeric', - month: '2-digit', - day: '2-digit', - hour: '2-digit', - minute: '2-digit', - second: '2-digit' - })}`); - } - - if (quotaInfo) { - logger.info(this.siteName, `📊 使用配额: ${quotaInfo.used} / ${quotaInfo.total}`); - logger.info(this.siteName, `💎 剩余配额: ${quotaInfo.total - parseFloat(quotaInfo.used)}`); - } - - if (billingInfo && billingInfo.days) { - logger.info(this.siteName, `🔄 下次账单: ${billingInfo.days} 天后`); - logger.info(this.siteName, `📆 账单日期: ${billingInfo.date}`); - } - - logger.info(this.siteName, ''); + // 4. 简要打印关键信息 + logger.success(this.siteName, `✓ 配额: ${quotaInfo ? `${quotaInfo.used}/${quotaInfo.total}` : 'N/A'} | 下次账单: ${billingInfo?.days || 'N/A'}天后`); // 保存订阅信息供后续使用 this.quotaInfo = quotaInfo; diff --git a/src/tools/account-register/utils/capsolver-api.js b/src/tools/account-register/utils/capsolver-api.js new file mode 100644 index 0000000..6761769 --- /dev/null +++ b/src/tools/account-register/utils/capsolver-api.js @@ -0,0 +1,202 @@ +/** + * CapSolver API - hCaptcha/Turnstile 自动识别 + * 使用官方API方式,不依赖浏览器扩展 + */ + +const axios = require('axios'); +const logger = require('../../../shared/logger'); + +class CapSolverAPI { + constructor(apiKey) { + this.apiKey = apiKey || process.env.CAPSOLVER_API_KEY; + this.apiUrl = 'https://api.capsolver.com'; + + if (!this.apiKey) { + logger.warn('CapSolver', '未配置API Key,自动识别将不可用'); + } + } + + /** + * 解决 hCaptcha + * @param {string} siteKey - 网站的 siteKey + * @param {string} pageUrl - 当前页面URL + * @returns {Promise} token + */ + async solveHCaptcha(siteKey, pageUrl) { + if (!this.apiKey) { + throw new Error('CapSolver API Key 未配置'); + } + + logger.info('CapSolver', '开始识别 hCaptcha...'); + logger.info('CapSolver', `SiteKey: ${siteKey.substring(0, 20)}...`); + + try { + // 1. 创建任务 + const createTaskResponse = await axios.post(`${this.apiUrl}/createTask`, { + clientKey: this.apiKey, + task: { + type: 'HCaptchaTaskProxyless', + websiteURL: pageUrl, + websiteKey: siteKey + } + }); + + if (createTaskResponse.data.errorId !== 0) { + throw new Error(`创建任务失败: ${createTaskResponse.data.errorDescription}`); + } + + const taskId = createTaskResponse.data.taskId; + logger.info('CapSolver', `任务创建成功 (ID: ${taskId})`); + + // 2. 轮询获取结果 + let attempts = 0; + const maxAttempts = 60; // 最多等待2分钟 + + while (attempts < maxAttempts) { + attempts++; + await this.delay(2000); // 每2秒查询一次 + + 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('CapSolver', `✓ hCaptcha 识别成功!(耗时: ${attempts * 2}秒)`); + return token; + } + + if (status === 'failed') { + throw new Error('CapSolver 识别失败'); + } + + // status === 'processing' + if (attempts % 5 === 0) { + logger.info('CapSolver', `识别中... (${attempts * 2}/${maxAttempts * 2}秒)`); + } + } + + throw new Error('CapSolver 识别超时(2分钟)'); + + } catch (error) { + logger.error('CapSolver', `识别失败: ${error.message}`); + throw error; + } + } + + /** + * 解决 Cloudflare Turnstile + * @param {string} siteKey - 网站的 siteKey + * @param {string} pageUrl - 当前页面URL + * @returns {Promise} token + */ + async solveTurnstile(siteKey, pageUrl) { + if (!this.apiKey) { + throw new Error('CapSolver API Key 未配置'); + } + + logger.info('CapSolver', '开始识别 Turnstile...'); + logger.info('CapSolver', `SiteKey: ${siteKey.substring(0, 20)}...`); + + try { + // 1. 创建任务 + const createTaskResponse = await axios.post(`${this.apiUrl}/createTask`, { + clientKey: this.apiKey, + task: { + type: 'AntiTurnstileTaskProxyLess', + websiteURL: pageUrl, + websiteKey: siteKey + } + }); + + if (createTaskResponse.data.errorId !== 0) { + throw new Error(`创建任务失败: ${createTaskResponse.data.errorDescription}`); + } + + const taskId = createTaskResponse.data.taskId; + logger.info('CapSolver', `任务创建成功 (ID: ${taskId})`); + + // 2. 轮询获取结果 + let attempts = 0; + const maxAttempts = 60; // 最多等待2分钟 + + while (attempts < maxAttempts) { + attempts++; + await this.delay(2000); + + 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.token; + logger.success('CapSolver', `✓ Turnstile 识别成功!(耗时: ${attempts * 2}秒)`); + return token; + } + + if (status === 'failed') { + throw new Error('CapSolver 识别失败'); + } + + if (attempts % 5 === 0) { + logger.info('CapSolver', `识别中... (${attempts * 2}/${maxAttempts * 2}秒)`); + } + } + + throw new Error('CapSolver 识别超时(2分钟)'); + + } catch (error) { + logger.error('CapSolver', `识别失败: ${error.message}`); + throw error; + } + } + + /** + * 检查余额 + */ + async getBalance() { + if (!this.apiKey) { + throw new Error('CapSolver API Key 未配置'); + } + + try { + const response = await axios.post(`${this.apiUrl}/getBalance`, { + clientKey: this.apiKey + }); + + if (response.data.errorId !== 0) { + throw new Error(`获取余额失败: ${response.data.errorDescription}`); + } + + const balance = response.data.balance; + logger.info('CapSolver', `账户余额: $${balance}`); + return balance; + } catch (error) { + logger.error('CapSolver', `获取余额失败: ${error.message}`); + throw error; + } + } + + /** + * 延迟函数 + */ + async delay(ms) { + return new Promise(resolve => setTimeout(resolve, ms)); + } +} + +module.exports = CapSolverAPI;