diff --git a/package.json b/package.json index b238eb7..9fc8a4d 100644 --- a/package.json +++ b/package.json @@ -22,7 +22,7 @@ "license": "MIT", "dependencies": { "commander": "^11.0.0", - "puppeteer": "npm:rebrowser-puppeteer@^23.9.0", + "puppeteer-real-browser": "^1.3.12", "imap": "^0.8.19", "mailparser": "^3.6.5" }, diff --git a/src/tools/account-register/email-verification/index.js b/src/tools/account-register/email-verification/index.js index ba69768..45e628d 100644 --- a/src/tools/account-register/email-verification/index.js +++ b/src/tools/account-register/email-verification/index.js @@ -64,13 +64,6 @@ class EmailVerificationService { logger.info('EmailVerification', `✓ 找到 ${emails.length} 封未读邮件`); - // 按日期倒序排序(最新的在前) - emails.sort((a, b) => { - const dateA = a.date ? new Date(a.date).getTime() : 0; - const dateB = b.date ? new Date(b.date).getTime() : 0; - return dateB - dateA; - }); - // 打印最近5条邮件信息 const recentEmails = emails.slice(0, 5); logger.info('EmailVerification', '='.repeat(60)); @@ -85,12 +78,28 @@ class EmailVerificationService { logger.info('EmailVerification', '='.repeat(60)); // 查找匹配的邮件并提取验证码 - // 注意:QQ邮箱转发邮件后,收件人字段会被改写为QQ邮箱地址,所以不能检查收件人 - // 改为只检查发件人和主题,并按时间取最新的 for (const email of emails) { if (isResolved) return; - logger.info('EmailVerification', `检查邮件: 发件人="${email.from}", 主题="${email.subject}", 时间="${email.date}"`); + logger.info('EmailVerification', `检查邮件: 发件人="${email.from}", 主题="${email.subject}", 收件人="${email.to}", 时间="${email.date}"`); + + // 收件人匹配:从收件人字段提取邮箱地址 + // 格式可能是: "Name" 或 "email@example.com" + const extractEmail = (str) => { + if (!str) return ''; + const match = str.match(/<(.+?)>/) || str.match(/([^\s<>]+@[^\s<>]+)/); + return match ? match[1].toLowerCase() : str.toLowerCase(); + }; + + const emailTo = extractEmail(email.to); + const targetEmail = recipientEmail.toLowerCase(); + + if (!emailTo.includes(targetEmail)) { + logger.info('EmailVerification', ` ✗ 跳过:收件人不匹配(期望:${targetEmail},实际:${emailTo})`); + continue; + } + + logger.success('EmailVerification', ` ✓ 收件人匹配!`); for (const parser of this.parsers) { if (parser.canParse(email)) { diff --git a/src/tools/account-register/sites/windsurf.js b/src/tools/account-register/sites/windsurf.js index aa5a7a1..db8e2bd 100644 --- a/src/tools/account-register/sites/windsurf.js +++ b/src/tools/account-register/sites/windsurf.js @@ -34,10 +34,11 @@ class WindsurfRegister { this.steps = [ { id: 1, name: '填写基本信息', method: 'step1_fillBasicInfo' }, { id: 2, name: '设置密码', method: 'step2_setPassword' }, - { id: 3, name: '邮箱验证', method: 'step3_emailVerification' }, - { id: 4, name: '跳过问卷', method: 'step4_skipSurvey' }, - { id: 5, name: '选择计划', method: 'step5_selectPlan' }, - { id: 6, name: '填写支付信息', method: 'step6_fillPayment' }, + { id: 3, name: 'Cloudflare验证', method: 'step3_cloudflareVerification' }, + { id: 4, name: '邮箱验证码', method: 'step4_emailVerificationCode' }, + { id: 5, name: '跳过问卷', method: 'step5_skipSurvey' }, + { id: 6, name: '选择计划', method: 'step6_selectPlan' }, + { id: 7, name: '填写支付信息', method: 'step7_fillPayment' }, // 根据实际注册流程继续添加 ]; } @@ -66,37 +67,88 @@ class WindsurfRegister { /** * 通用方法:点击按钮并等待页面跳转 + * 使用页面特征(URL、DOM元素)来判断是否真的跳转了 * @param {Function} checkPageChanged - 检查页面是否已跳转的函数,返回Promise * @param {number} maxAttempts - 最多尝试次数 * @param {string} actionName - 操作名称(用于日志) + * @param {string} buttonText - 按钮文本过滤(可选,如"Continue") * @returns {Promise} - 是否成功跳转 */ - async clickButtonAndWaitForPageChange(checkPageChanged, maxAttempts = 5, actionName = '点击按钮') { + async clickButtonAndWaitForPageChange(checkPageChanged, maxAttempts = 5, actionName = '点击按钮', buttonText = null) { let pageChanged = false; let attempts = 0; + let lastClickedAt = 0; // 记录上次点击的时间,避免重复点击 while (!pageChanged && attempts < maxAttempts) { attempts++; + // 记录点击前的页面特征 + const beforeUrl = this.page.url(); + const beforeHtml = await this.page.content(); + // 查找未禁用的按钮 - const button = await this.page.$('button:not([disabled])'); - if (button) { - const text = await this.page.evaluate(el => el.textContent.trim(), button); + const buttons = await this.page.$$('button:not([disabled])'); + let targetButton = null; + + // 如果指定了按钮文本,找到匹配的按钮 + if (buttonText) { + for (const btn of buttons) { + const text = await this.page.evaluate(el => el.textContent.trim(), btn); + // 不区分大小写匹配 + if (text.toLowerCase().includes(buttonText.toLowerCase())) { + targetButton = btn; + break; + } + } + + // 如果没找到匹配的按钮,使用第一个(回退策略) + if (!targetButton && buttons.length > 0) { + logger.warn(this.siteName, ` → 未找到包含"${buttonText}"的按钮,使用第一个按钮`); + targetButton = buttons[0]; + } + } else { + // 没有指定文本,使用第一个按钮 + targetButton = buttons[0]; + } + + if (targetButton) { + const text = await this.page.evaluate(el => el.textContent.trim(), targetButton); logger.info(this.siteName, ` → 第${attempts}次${actionName}"${text}"...`); - await button.click(); - await this.human.randomDelay(2000, 3000); + + // 点击按钮 + const now = Date.now(); + if (now - lastClickedAt < 3000) { + logger.warn(this.siteName, ` → 距离上次点击时间太短,等待中...`); + await this.human.randomDelay(2000, 3000); + } + + await targetButton.click(); + lastClickedAt = Date.now(); + + // 等待页面响应 + await this.human.randomDelay(3000, 4000); + + // 检查URL是否改变 + const afterUrl = this.page.url(); + const urlChanged = afterUrl !== beforeUrl; + + // 检查页面内容是否改变(至少改变10%) + const afterHtml = await this.page.content(); + const contentChanged = Math.abs(afterHtml.length - beforeHtml.length) / beforeHtml.length > 0.1; // 使用自定义检查函数判断页面是否跳转 - const changed = await checkPageChanged(); + const customChanged = await checkPageChanged(); - if (changed) { + logger.info(this.siteName, ` → URL变化: ${urlChanged}, 内容变化: ${contentChanged}, 自定义检查: ${customChanged}`); + + if (urlChanged || customChanged) { pageChanged = true; - logger.success(this.siteName, ` → ✓ 页面已跳转`); + logger.success(this.siteName, ` → ✓ 页面已跳转 (${urlChanged ? 'URL变化' : ''}${customChanged ? ' 元素变化' : ''})`); break; } logger.warn(this.siteName, ` → 页面未跳转,${attempts}/${maxAttempts}`); - await this.human.randomDelay(2000, 3000); + await this.human.randomDelay(1000, 2000); } else { logger.warn(this.siteName, ` → 未找到可点击的按钮`); break; @@ -131,13 +183,12 @@ class WindsurfRegister { } /** - * 初始化浏览器(使用rebrowser-puppeteer,自带反检测) + * 初始化浏览器(使用puppeteer-real-browser,自动处理Cloudflare) */ async initBrowser() { - // rebrowser-puppeteer 已经打好补丁,无需额外配置 - const puppeteer = require('puppeteer'); + const { connect } = require('puppeteer-real-browser'); - logger.info(this.siteName, '启动浏览器(反检测模式)...'); + logger.info(this.siteName, '启动浏览器(自动Cloudflare绕过模式)...'); // 随机视口大小(模拟不同设备) const viewports = [ @@ -148,39 +199,36 @@ class WindsurfRegister { ]; const viewport = viewports[Math.floor(Math.random() * viewports.length)]; - this.browser = await puppeteer.launch({ - headless: false, // 非无头模式更难被检测 + // 使用 puppeteer-real-browser 连接,启用 turnstile 自动处理 Cloudflare + const result = await connect({ + turnstile: true, // 自动处理 Cloudflare Turnstile + headless: false, args: [ '--no-sandbox', '--disable-setuid-sandbox', - '--disable-blink-features=AutomationControlled', - '--disable-dev-shm-usage', `--window-size=${viewport.width},${viewport.height}` - ], - ignoreDefaultArgs: ['--enable-automation'] + ] }); - - this.page = await this.browser.newPage(); + + this.browser = result.browser; + this.page = result.page; // 设置随机视口 await this.page.setViewport(viewport); - - // 随机用户代理 - const userAgents = [ - 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36', - 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36', - 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0.0.0 Safari/537.36' - ]; - await this.page.setUserAgent( - userAgents[Math.floor(Math.random() * userAgents.length)] - ); // 设置语言和时区 await this.page.setExtraHTTPHeaders({ 'Accept-Language': 'en-US,en;q=0.9,zh-CN;q=0.8,zh;q=0.7' }); + // 设置更长的导航超时时间 + await this.page.setDefaultNavigationTimeout(60000); + logger.success(this.siteName, `浏览器启动成功 (${viewport.width}x${viewport.height})`); + + // 浏览器启动后等待一段时间,确保完全准备好 + logger.info(this.siteName, '等待浏览器完全准备...'); + await this.human.randomDelay(3000, 5000); } /** @@ -256,7 +304,7 @@ class WindsurfRegister { return hasPasswordField || urlChanged; }; - await this.clickButtonAndWaitForPageChange(checkPageChanged, 5, '点击Continue'); + await this.clickButtonAndWaitForPageChange(checkPageChanged, 5, '点击Continue', 'Continue'); // 额外等待确保页面稳定 await this.human.randomDelay(2000, 3000); @@ -302,12 +350,10 @@ class WindsurfRegister { logger.info(this.siteName, ' → 等待密码验证...'); await this.human.randomDelay(1000, 2000); - // 移除旧的验证逻辑,将在步骤3之前单独处理 - // 查找并点击Continue按钮 logger.info(this.siteName, ' → 点击Continue按钮...'); - // 等待按钮变为可点击状态(不再disabled)- 步骤2 + // 等待按钮激活 try { await this.page.waitForFunction( () => { @@ -316,35 +362,31 @@ class WindsurfRegister { const text = button.textContent.trim(); return text === 'Continue' && !button.disabled; }, - { timeout: 20000 } // 增加超时时间,因为逐字符输入较慢 + { timeout: 20000 } ); - logger.info(this.siteName, ' → 按钮已激活'); - - // 使用更精确的选择器 - const button = await this.page.$('button:not([disabled])'); - if (button) { - const text = await this.page.evaluate(el => el.textContent.trim(), button); - if (text === 'Continue') { - // 同时等待导航和点击 - await Promise.all([ - this.page.waitForNavigation({ waitUntil: 'networkidle2', timeout: 15000 }).catch(() => {}), - this.human.humanClick(this.page, 'button:not([disabled])') - ]); - logger.success(this.siteName, ' → 已点击Continue按钮并等待跳转'); - } - } - } catch (error) { - logger.warn(this.siteName, ' → 按钮等待超时,尝试按Enter键'); - await this.human.randomDelay(500, 1000); - await Promise.all([ - this.page.waitForNavigation({ waitUntil: 'networkidle2', timeout: 15000 }).catch(() => {}), - this.page.keyboard.press('Enter') - ]); + } catch (e) { + logger.warn(this.siteName, ' → 按钮等待超时,尝试继续'); } - // 额外等待确保页面稳定 - await this.human.randomDelay(1000, 2000); + // 使用通用方法点击按钮并等待页面跳转 + // 密码页面点击后会出现Cloudflare验证,检查验证元素是否出现 + const checkPageChanged = async () => { + // 检查是否出现了Cloudflare验证页面的特征 + const hasCloudflare = await this.page.$('iframe[src*="challenges.cloudflare.com"]').then(el => !!el).catch(() => false); + // 或者检查密码输入框是否消失 + const noPasswordField = await this.page.$('#password').then(el => !el).catch(() => true); + return hasCloudflare || noPasswordField; + }; + + const success = await this.clickButtonAndWaitForPageChange(checkPageChanged, 3, '点击Continue', 'Continue'); + + if (!success) { + // 如果点击失败,尝试按Enter键 + logger.warn(this.siteName, ' → 点击失败,尝试按Enter键'); + await this.page.keyboard.press('Enter'); + await this.human.randomDelay(2000, 3000); + } this.currentStep = 2; logger.success(this.siteName, `步骤 2 完成`); @@ -456,27 +498,56 @@ class WindsurfRegister { } /** - * 步骤3: 邮箱验证 + * 步骤3: Cloudflare验证并进入验证码页面 */ - async step3_emailVerification() { - logger.info(this.siteName, `[步骤 3/${this.getTotalSteps()}] 邮箱验证`); + async step3_cloudflareVerification() { + logger.info(this.siteName, `[步骤 3/${this.getTotalSteps()}] Cloudflare验证`); - // 先处理Cloudflare验证(如果有) - const verifyResult = await this.handleCloudflareVerification(); - if (verifyResult === 'failed' || verifyResult === 'error') { - throw new Error('Cloudflare验证失败,无法继续'); - } - if (verifyResult === 'manual') { - logger.info(this.siteName, 'Cloudflare需要手动验证,已等待完成'); + // puppeteer-real-browser 的 turnstile 会自动处理 Cloudflare + logger.info(this.siteName, '[Cloudflare] turnstile 自动处理中...'); + + // 等待一段时间让 Cloudflare 自动验证完成 + await this.human.randomDelay(5000, 8000); + logger.success(this.siteName, '[Cloudflare] ✓ 自动验证完成'); + + // 点击 Cloudflare 页面的 Continue 按钮进入验证码页面 + logger.info(this.siteName, '[Cloudflare] 点击Continue按钮进入验证码页面...'); + + const currentUrl = this.page.url(); + logger.info(this.siteName, ` → 当前URL: ${currentUrl}`); + + // 使用通用方法检测页面跳转 + const checkPageChanged = async () => { + const newUrl = this.page.url(); + // 检查URL是否变化或是否有验证码输入框 + const hasCodeInput = await this.page.$('input[type="text"]').then(el => !!el).catch(() => false); + return newUrl !== currentUrl || hasCodeInput; + }; + + // 点击Continue按钮并等待页面跳转 + const success = await this.clickButtonAndWaitForPageChange(checkPageChanged, 5, '点击Continue', 'Continue'); + + if (!success) { + throw new Error('Cloudflare验证后页面未成功跳转到验证码页面'); } + this.currentStep = 3; + logger.success(this.siteName, `步骤 3 完成`); + } + + /** + * 步骤4: 获取邮箱验证码并填写 + */ + async step4_emailVerificationCode() { + logger.info(this.siteName, `[步骤 4/${this.getTotalSteps()}] 邮箱验证码`); + try { // 等待验证码页面加载 await this.human.readPage(1, 2); - // 延迟2秒后再获取验证码,让邮件有足够时间到达 - logger.info(this.siteName, ' → 延迟2秒,等待邮件到达...'); - await new Promise(resolve => setTimeout(resolve, 2000)); + // 延迟5秒后再获取验证码,让邮件有足够时间到达 + logger.info(this.siteName, ' → 延迟5秒,等待邮件到达...'); + await new Promise(resolve => setTimeout(resolve, 5000)); // 获取验证码(从邮箱) logger.info(this.siteName, ' → 正在从邮箱获取验证码...'); @@ -547,8 +618,8 @@ class WindsurfRegister { await this.clickButtonAndWaitForPageChange(checkPageChanged, 3, '点击Create account'); - this.currentStep = 3; - logger.success(this.siteName, `步骤 3 完成`); + this.currentStep = 4; + logger.success(this.siteName, `步骤 4 完成`); } else { logger.error(this.siteName, ' → 未找到验证码输入框!'); @@ -557,7 +628,7 @@ class WindsurfRegister { // 等待用户手动输入 await this.human.randomDelay(30000, 30000); - this.currentStep = 3; + this.currentStep = 4; } } catch (error) { @@ -567,10 +638,10 @@ class WindsurfRegister { } /** - * 步骤4: 跳过问卷调查 + * 步骤5: 跳过问卷调查 */ - async step4_skipSurvey() { - logger.info(this.siteName, `[步骤 4/${this.getTotalSteps()}] 跳过问卷`); + async step5_skipSurvey() { + logger.info(this.siteName, `[步骤 5/${this.getTotalSteps()}] 跳过问卷`); try { // 等待页面加载 @@ -599,11 +670,11 @@ class WindsurfRegister { // 等待页面跳转 await this.human.randomDelay(2000, 3000); - this.currentStep = 4; - logger.success(this.siteName, `步骤 4 完成`); + this.currentStep = 5; + logger.success(this.siteName, `步骤 5 完成`); } else { logger.warn(this.siteName, ' → 未找到Skip按钮,可能已跳过此页面'); - this.currentStep = 4; + this.currentStep = 5; } } catch (error) { @@ -613,10 +684,10 @@ class WindsurfRegister { } /** - * 步骤5: 选择计划 + * 步骤6: 选择计划 */ - async step5_selectPlan() { - logger.info(this.siteName, `[步骤 5/${this.getTotalSteps()}] 选择计划`); + async step6_selectPlan() { + logger.info(this.siteName, `[步骤 6/${this.getTotalSteps()}] 选择计划`); try { // 等待页面加载 @@ -642,25 +713,33 @@ class WindsurfRegister { logger.info(this.siteName, ' → 点击"Select plan"按钮...'); await selectButton.click(); - // 等待页面跳转或加载 + // 等待页面跳转到支付页面 + logger.info(this.siteName, ' → 等待进入支付页面...'); await this.human.randomDelay(3000, 5000); - this.currentStep = 5; - logger.success(this.siteName, `步骤 5 完成`); - } else { - logger.warn(this.siteName, ' → 未找到Select plan按钮,尝试点击Skip'); - // 如果没有Select plan,尝试点击Skip - const skipButtons = await this.page.$$('button'); - for (const btn of skipButtons) { - const text = await this.page.evaluate(el => el.textContent?.trim(), btn); - if (text && text.toLowerCase().includes('skip')) { - await btn.click(); - logger.info(this.siteName, ' → 已点击Skip按钮'); - break; - } + // 验证是否进入支付页面(检查是否有Stripe元素或支付表单) + const hasPaymentForm = await this.page.$('button[data-testid="card-accordion-item-button"]').then(el => !!el).catch(() => false); + const hasStripeCheckout = await this.page.url().then(url => url.includes('checkout.stripe.com')).catch(() => false); + + if (hasPaymentForm || hasStripeCheckout) { + logger.success(this.siteName, ' → ✓ 已进入支付页面'); + this.currentStep = 6; + logger.success(this.siteName, `步骤 6 完成`); + } else { + logger.warn(this.siteName, ' → 未进入支付页面,可能需要手动处理'); + logger.info(this.siteName, ` → 当前URL: ${this.page.url()}`); + this.currentStep = 6; + } + } else { + logger.warn(this.siteName, ' → 未找到Select plan按钮'); + // 检查是否已经在支付页面 + const hasPaymentForm = await this.page.$('button[data-testid="card-accordion-item-button"]').then(el => !!el).catch(() => false); + if (hasPaymentForm) { + logger.success(this.siteName, ' → 已在支付页面,跳过选择计划'); + this.currentStep = 6; + } else { + throw new Error('未找到Select plan按钮,且不在支付页面'); } - await this.human.randomDelay(2000, 3000); - this.currentStep = 5; } } catch (error) { @@ -670,10 +749,10 @@ class WindsurfRegister { } /** - * 步骤6: 填写支付信息 + * 步骤7: 填写支付信息 */ - async step6_fillPayment() { - logger.info(this.siteName, `[步骤 6/${this.getTotalSteps()}] 填写支付信息`); + async step7_fillPayment() { + logger.info(this.siteName, `[步骤 7/${this.getTotalSteps()}] 填写支付信息`); try { // 等待页面加载 @@ -687,46 +766,104 @@ class WindsurfRegister { logger.info(this.siteName, ` → 有效期: ${card.month}/${card.year}`); logger.info(this.siteName, ` → CVV: ${card.cvv}`); - // 2. 点击选择"银行卡"支付方式 - logger.info(this.siteName, ' → 选择银行卡支付方式...'); - const cardRadio = await this.page.$('input[type="radio"][value="card"]'); - if (cardRadio) { - await cardRadio.click(); - await this.human.randomDelay(1000, 2000); - logger.success(this.siteName, ' → ✓ 已选择银行卡'); + // 2. 首先找到并点击银行卡区域展开表单 + logger.info(this.siteName, ' → 点击展开银行卡支付区域...'); + try { + // 点击银行卡区域的按钮来展开表单 + const cardButton = await this.page.$('button[data-testid="card-accordion-item-button"]'); + if (cardButton) { + await cardButton.click(); + await this.human.randomDelay(2000, 3000); + logger.success(this.siteName, ' → ✓ 银行卡区域已展开'); + } else { + // 如果按钮不存在,尝试点击radio + const cardRadio = await this.page.$('input[type="radio"][value="card"]'); + if (cardRadio) { + await cardRadio.click(); + await this.human.randomDelay(2000, 3000); + logger.success(this.siteName, ' → ✓ 已选择银行卡'); + } + } + } catch (e) { + logger.warn(this.siteName, ` → 展开银行卡区域失败: ${e.message},继续尝试...`); } - // 3. 填写卡号 + // 等待Stripe表单iframe加载 + logger.info(this.siteName, ' → 等待支付表单加载...'); + await this.human.randomDelay(3000, 5000); + + // 3. 填写卡号(使用humanType逐字符输入) logger.info(this.siteName, ' → 填写卡号...'); - await this.page.waitForSelector('#cardNumber', { timeout: 10000 }); - await this.page.click('#cardNumber'); - await this.human.randomDelay(500, 1000); - await this.page.type('#cardNumber', card.number, { delay: 100 }); + try { + await this.page.waitForSelector('#cardNumber', { timeout: 15000 }); + await this.page.click('#cardNumber'); + await this.human.randomDelay(500, 800); + // 清空 + await this.page.evaluate(() => document.querySelector('#cardNumber').value = ''); + // 使用人类行为输入 + await this.human.humanType(this.page, '#cardNumber', card.number); + logger.success(this.siteName, ` → ✓ 卡号已填写: ${card.number}`); + await this.human.randomDelay(1500, 2000); + } catch (e) { + logger.error(this.siteName, ` → 填写卡号失败: ${e.message}`); + throw e; + } // 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: 100 }); + try { + await this.page.click('#cardExpiry'); + await this.human.randomDelay(300, 500); + await this.page.evaluate(() => document.querySelector('#cardExpiry').value = ''); + const expiry = `${card.month}${card.year}`; // 格式: MMYY + await this.human.humanType(this.page, '#cardExpiry', expiry); + logger.success(this.siteName, ` → ✓ 有效期已填写: ${expiry}`); + await this.human.randomDelay(1500, 2000); + } catch (e) { + logger.error(this.siteName, ` → 填写有效期失败: ${e.message}`); + throw e; + } // 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: 100 }); + try { + await this.page.click('#cardCvc'); + await this.human.randomDelay(300, 500); + await this.page.evaluate(() => document.querySelector('#cardCvc').value = ''); + await this.human.humanType(this.page, '#cardCvc', card.cvv); + logger.success(this.siteName, ` → ✓ CVC已填写: ${card.cvv}`); + await this.human.randomDelay(1500, 2000); + } catch (e) { + logger.error(this.siteName, ` → 填写CVC失败: ${e.message}`); + throw e; + } // 6. 填写持卡人姓名 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: 100 }); + try { + await this.page.click('#billingName'); + await this.human.randomDelay(300, 500); + await this.page.evaluate(() => document.querySelector('#billingName').value = ''); + const fullName = `${this.accountData.firstName} ${this.accountData.lastName}`; + await this.human.humanType(this.page, '#billingName', fullName); + logger.success(this.siteName, ` → ✓ 姓名已填写: ${fullName}`); + await this.human.randomDelay(1500, 2000); + } catch (e) { + logger.error(this.siteName, ` → 填写姓名失败: ${e.message}`); + throw e; + } // 7. 选择地址:中国澳门特别行政区 logger.info(this.siteName, ' → 选择地址:中国澳门特别行政区...'); - await this.page.select('#billingCountry', 'MO'); - await this.human.randomDelay(1000, 2000); + try { + await this.page.waitForSelector('#billingCountry', { timeout: 15000 }); + await this.page.select('#billingCountry', 'MO'); + logger.success(this.siteName, ' → ✓ 地址已选择: 中国澳门特别行政区'); + await this.human.randomDelay(2000, 3000); + } catch (e) { + logger.error(this.siteName, ` → 选择地址失败: ${e.message}`); + throw e; + } // 8. 填写地址信息(如果需要) // 等待地址字段加载 @@ -736,15 +873,33 @@ class WindsurfRegister { const addressFields = await this.page.$$('input[placeholder*="地址"]'); if (addressFields.length > 0) { logger.info(this.siteName, ' → 填写地址信息...'); - // 填写简单的地址 - await addressFields[0].type('Macau', { delay: 100 }); + // 填写地址:kowloon + await addressFields[0].type('kowloon', { delay: 100 }); + logger.success(this.siteName, ' → ✓ 地址行1已填写: kowloon'); if (addressFields[1]) { await this.human.randomDelay(300, 500); - await addressFields[1].type('Macao', { delay: 100 }); + await addressFields[1].type('kowloon', { delay: 100 }); + logger.success(this.siteName, ' → ✓ 地址行2已填写: kowloon'); } } - // 9. 点击订阅按钮 + // 9. 取消勾选"保存我的信息" + logger.info(this.siteName, ' → 检查并取消勾选保存信息...'); + try { + const saveCheckbox = await this.page.$('input[type="checkbox"]'); + if (saveCheckbox) { + const isChecked = await this.page.evaluate(el => el.checked, saveCheckbox); + if (isChecked) { + await saveCheckbox.click(); + logger.success(this.siteName, ' → ✓ 已取消勾选保存信息'); + await this.human.randomDelay(500, 1000); + } + } + } catch (e) { + logger.warn(this.siteName, ` → 取消勾选失败: ${e.message}`); + } + + // 10. 点击订阅按钮 logger.info(this.siteName, ' → 点击订阅按钮...'); await this.human.randomDelay(2000, 3000); @@ -756,11 +911,11 @@ class WindsurfRegister { // 等待处理 await this.human.randomDelay(5000, 7000); - this.currentStep = 6; - logger.success(this.siteName, `步骤 6 完成`); + this.currentStep = 7; + logger.success(this.siteName, `步骤 7 完成`); } else { logger.warn(this.siteName, ' → 未找到订阅按钮'); - this.currentStep = 6; + this.currentStep = 7; } } catch (error) {