/** * Windsurf Register - Windsurf网站注册 * https://windsurf.com/account/register * * 注册流程: * Step 1: 填写基本信息(First Name, Last Name, Email) * Step 2: 设置密码 * Step 3: 邮箱验证 * Step 4: 完善个人信息 * ... 根据实际情况继续添加步骤 */ const AccountDataGenerator = require('../generator'); const HumanBehavior = require('../utils/human-behavior'); const CloudflareHandler = require('../utils/cloudflare-handler'); const logger = require('../../../shared/logger'); const EmailVerificationService = require('../email-verification'); const { DEFAULT_CONFIG } = require('../config'); const CardGenerator = require('../../card-generator/generator'); class WindsurfRegister { constructor() { this.siteName = 'Windsurf'; this.siteUrl = 'https://windsurf.com/account/register'; this.dataGen = new AccountDataGenerator(); this.human = new HumanBehavior(); this.emailService = new EmailVerificationService(); this.browser = null; this.page = null; this.currentStep = 0; this.accountData = null; // 定义所有步骤 this.steps = [ { id: 1, name: '填写基本信息', method: 'step1_fillBasicInfo' }, { id: 2, name: '设置密码', method: 'step2_setPassword' }, { 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' }, // 根据实际注册流程继续添加 ]; } /** * 获取步骤总数 */ getTotalSteps() { return this.steps.length; } /** * 获取当前步骤信息 */ getCurrentStepInfo() { if (this.currentStep === 0) { return { id: 0, name: '未开始', total: this.getTotalSteps() }; } const step = this.steps[this.currentStep - 1]; return { ...step, current: this.currentStep, total: this.getTotalSteps() }; } /** * 通用方法:点击按钮并等待页面跳转 * 使用页面特征(URL、DOM元素)来判断是否真的跳转了 * @param {Function} checkPageChanged - 检查页面是否已跳转的函数,返回Promise * @param {number} maxAttempts - 最多尝试次数 * @param {string} actionName - 操作名称(用于日志) * @param {string} buttonText - 按钮文本过滤(可选,如"Continue") * @returns {Promise} - 是否成功跳转 */ 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 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}"...`); // 点击按钮 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 customChanged = await checkPageChanged(); logger.info(this.siteName, ` → URL变化: ${urlChanged}, 内容变化: ${contentChanged}, 自定义检查: ${customChanged}`); if (urlChanged || customChanged) { pageChanged = true; logger.success(this.siteName, ` → ✓ 页面已跳转 (${urlChanged ? 'URL变化' : ''}${customChanged ? ' 元素变化' : ''})`); break; } logger.warn(this.siteName, ` → 页面未跳转,${attempts}/${maxAttempts}`); await this.human.randomDelay(1000, 2000); } else { logger.warn(this.siteName, ` → 未找到可点击的按钮`); break; } } if (!pageChanged) { logger.error(this.siteName, ` → ${maxAttempts}次尝试后页面仍未跳转`); } return pageChanged; } /** * 生成账号数据(干运行模式) */ generateData(options = {}) { logger.info(this.siteName, '生成账号数据...'); const account = this.dataGen.generateAccount({ name: options.name, email: options.email, username: options.username, password: { strategy: options.passwordStrategy || 'email', ...options.password }, includePhone: false // Windsurf第一步不需要手机号 }); return account; } /** * 初始化浏览器(使用puppeteer-real-browser,自动处理Cloudflare) */ async initBrowser() { const { connect } = require('puppeteer-real-browser'); logger.info(this.siteName, '启动浏览器(自动Cloudflare绕过模式)...'); // 随机视口大小(模拟不同设备) const viewports = [ { width: 1920, height: 1080 }, { width: 1366, height: 768 }, { width: 1536, height: 864 }, { width: 1440, height: 900 } ]; const viewport = viewports[Math.floor(Math.random() * viewports.length)]; // 使用 puppeteer-real-browser 连接,启用 turnstile 自动处理 Cloudflare const result = await connect({ turnstile: true, // 自动处理 Cloudflare Turnstile headless: false, args: [ '--no-sandbox', '--disable-setuid-sandbox', `--window-size=${viewport.width},${viewport.height}` ] }); this.browser = result.browser; this.page = result.page; // 设置随机视口 await this.page.setViewport(viewport); // 设置语言和时区 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); } /** * 关闭浏览器 */ async closeBrowser() { if (this.browser) { await this.browser.close(); logger.info(this.siteName, '浏览器已关闭'); } } /** * 步骤1: 填写基本信息(First Name, Last Name, Email)- 使用人类行为 */ async step1_fillBasicInfo() { logger.info(this.siteName, `[步骤 1/${this.getTotalSteps()}] 填写基本信息`); // 打开注册页面 logger.info(this.siteName, `打开注册页面: ${this.siteUrl}`); await this.page.goto(this.siteUrl, { waitUntil: 'networkidle2', timeout: 30000 }); // 模拟阅读页面(1-3秒) await this.human.readPage(1, 3); // 填写First Name(使用人类行为) logger.info(this.siteName, ' → 填写First Name...'); await this.page.waitForSelector('#firstName', { timeout: 10000 }); await this.human.humanType(this.page, '#firstName', this.accountData.firstName); // 填写Last Name logger.info(this.siteName, ' → 填写Last Name...'); await this.page.waitForSelector('#lastName', { timeout: 10000 }); await this.human.humanType(this.page, '#lastName', this.accountData.lastName); // 填写Email logger.info(this.siteName, ' → 填写Email...'); await this.page.waitForSelector('#email', { timeout: 10000 }); await this.human.humanType(this.page, '#email', this.accountData.email); // 勾选同意条款(如果有) try { const checkbox = await this.page.$('input[type="checkbox"]'); if (checkbox) { logger.info(this.siteName, ' → 勾选同意条款...'); await this.human.humanCheckbox(this.page, 'input[type="checkbox"]'); } } catch (error) { logger.warn(this.siteName, ' → 未找到同意条款checkbox,跳过'); } // 点击Continue按钮并等待跳转到密码页面 logger.info(this.siteName, ' → 点击Continue按钮...'); // 等待按钮激活 await this.page.waitForSelector('button:not([disabled])', { timeout: 10000 }); logger.info(this.siteName, ' → 按钮已激活'); // 使用通用方法处理页面跳转 const checkPageChanged = async () => { // 检查是否有密码输入框 const hasPasswordField = await this.page.evaluate(() => { return !!document.querySelector('#password'); }); // 检查URL是否改变 const currentUrl = this.page.url(); const urlChanged = currentUrl !== this.siteUrl; return hasPasswordField || urlChanged; }; await this.clickButtonAndWaitForPageChange(checkPageChanged, 5, '点击Continue', 'Continue'); // 额外等待确保页面稳定 await this.human.randomDelay(2000, 3000); this.currentStep = 1; logger.success(this.siteName, `步骤 1 完成`); } /** * 步骤2: 设置密码 */ async step2_setPassword() { logger.info(this.siteName, `[步骤 2/${this.getTotalSteps()}] 设置密码`); // 等待密码页面加载 await this.human.readPage(1, 2); // 填写密码 logger.info(this.siteName, ' → 填写密码...'); await this.page.waitForSelector('#password', { timeout: 10000 }); // 先清空密码框 await this.page.evaluate(() => { const elem = document.querySelector('#password'); if (elem) elem.value = ''; }); await this.human.humanType(this.page, '#password', this.accountData.password); // 填写确认密码 logger.info(this.siteName, ' → 填写确认密码...'); await this.page.waitForSelector('#passwordConfirmation', { timeout: 10000 }); // 先清空确认密码框(防止有残留) await this.page.evaluate(() => { const elem = document.querySelector('#passwordConfirmation'); if (elem) elem.value = ''; }); await this.human.humanType(this.page, '#passwordConfirmation', this.accountData.password); // 等待验证通过 logger.info(this.siteName, ' → 等待密码验证...'); await this.human.randomDelay(1000, 2000); // 查找并点击Continue按钮 logger.info(this.siteName, ' → 点击Continue按钮...'); // 等待按钮激活 try { await this.page.waitForFunction( () => { const button = document.querySelector('button'); if (!button) return false; const text = button.textContent.trim(); return text === 'Continue' && !button.disabled; }, { timeout: 20000 } ); logger.info(this.siteName, ' → 按钮已激活'); } catch (e) { logger.warn(this.siteName, ' → 按钮等待超时,尝试继续'); } // 使用通用方法点击按钮并等待页面跳转 // 密码页面点击后会出现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 完成`); } /** * Cloudflare Turnstile验证(步骤2.5)- 使用通用处理器 */ async handleCloudflareVerification() { const cloudflareMode = DEFAULT_CONFIG.cloudflare.mode; // 自定义检测函数:检查Continue按钮是否激活 const customCheck = async () => { try { const buttonEnabled = await this.page.evaluate(() => { const button = document.querySelector('button'); return button && !button.disabled; }); return buttonEnabled; } catch (e) { return false; } }; const handler = new CloudflareHandler(this.page, this.human, this.siteName, cloudflareMode, customCheck); const result = await handler.handle(); // 如果验证通过,点击Continue按钮进入下一页 if (result === 'passed') { logger.info(this.siteName, '[Cloudflare] 点击Continue按钮进入验证码页面...'); try { let pageChanged = false; let attempts = 0; const maxAttempts = 10; // 最多尝试10次 while (!pageChanged && attempts < maxAttempts) { attempts++; // 查找并点击Continue按钮 const button = await this.page.$('button:not([disabled])'); if (button) { logger.info(this.siteName, `[Cloudflare] 第${attempts}次点击Continue...`); await button.click(); await this.human.randomDelay(2000, 3000); // 检查是否有错误提示 const hasError = await this.page.evaluate(() => { const errorMsg = document.querySelector('p.caption1.text-sk-error'); return errorMsg && errorMsg.textContent.includes('An error occurred'); }); if (hasError) { logger.warn(this.siteName, '[Cloudflare] 检测到错误提示,重新尝试...'); await this.human.randomDelay(2000, 3000); continue; } // 检查页面是否已改变 const checkResult = await this.page.evaluate(() => { // 方法1: 检查是否有"Check your inbox"文本 const hasCheckInbox = document.body.textContent.includes('Check your inbox'); // 方法2: 检查按钮是否被禁用 const button = document.querySelector('button'); const buttonDisabled = button && button.disabled; // 方法3: 检查是否还有"verify that you are human"文本 const stillOnVerifyPage = document.body.textContent.includes('verify that you are human'); return { hasCheckInbox, buttonDisabled, stillOnVerifyPage }; }); logger.info(this.siteName, `[Cloudflare] 页面状态: inbox=${checkResult.hasCheckInbox}, buttonDisabled=${checkResult.buttonDisabled}, stillVerify=${checkResult.stillOnVerifyPage}`); // 判断是否成功跳转 if (checkResult.hasCheckInbox || (!checkResult.stillOnVerifyPage && checkResult.buttonDisabled)) { pageChanged = true; logger.success(this.siteName, '[Cloudflare] ✓ 已进入验证码页面'); break; } // 如果还在验证页面,继续等待 logger.info(this.siteName, '[Cloudflare] 页面未跳转,继续尝试...'); await this.human.randomDelay(2000, 3000); } else { logger.warn(this.siteName, '[Cloudflare] 未找到可点击的按钮'); break; } } if (!pageChanged) { logger.warn(this.siteName, `[Cloudflare] ${maxAttempts}次尝试后页面仍未跳转`); } // 额外等待确保页面稳定 await this.human.randomDelay(2000, 3000); } catch (e) { logger.warn(this.siteName, `[Cloudflare] 点击按钮失败: ${e.message}`); } } return result; } /** * 步骤3: Cloudflare验证并进入验证码页面 */ async step3_cloudflareVerification() { logger.info(this.siteName, `[步骤 3/${this.getTotalSteps()}] 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); // 延迟5秒后再获取验证码,让邮件有足够时间到达 logger.info(this.siteName, ' → 延迟5秒,等待邮件到达...'); await new Promise(resolve => setTimeout(resolve, 5000)); // 获取验证码(从邮箱) logger.info(this.siteName, ' → 正在从邮箱获取验证码...'); logger.info(this.siteName, ` → 接收邮箱: ${this.accountData.email}`); logger.info(this.siteName, ' → 注意:如果长时间无响应,请检查:'); logger.info(this.siteName, ' 1. 邮件是否已发送到邮箱'); logger.info(this.siteName, ' 2. QQ邮箱IMAP配置是否正确'); logger.info(this.siteName, ' 3. 邮件是否被标记为垃圾邮件'); const code = await this.emailService.getVerificationCode( 'windsurf', this.accountData.email, 120 // 增加到120秒超时 ); logger.success(this.siteName, ` → ✓ 验证码: ${code}`); // 等待验证码输入框加载 await this.human.randomDelay(2000, 3000); // Windsurf使用6个独立的输入框,需要逐个填写 logger.info(this.siteName, ' → 查找验证码输入框...'); // 等待输入框出现 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); // 等待按钮激活 try { await this.page.waitForSelector('button:not([disabled])', { timeout: 10000 }); logger.success(this.siteName, ' → 按钮已激活'); } catch (e) { logger.warn(this.siteName, ` → 按钮等待超时: ${e.message}`); } // 点击按钮并等待页面跳转 const checkPageChanged = async () => { // 检查是否离开了验证码页面(URL改变或页面元素改变) const currentUrl = this.page.url(); return !currentUrl.includes('/register') || await this.page.$('input[type="text"]').then(el => !el).catch(() => true); }; await this.clickButtonAndWaitForPageChange(checkPageChanged, 3, '点击Create account'); this.currentStep = 4; logger.success(this.siteName, `步骤 4 完成`); } else { logger.error(this.siteName, ' → 未找到验证码输入框!'); logger.warn(this.siteName, ' → 请手动输入验证码: ' + code); // 等待用户手动输入 await this.human.randomDelay(30000, 30000); this.currentStep = 4; } } catch (error) { logger.error(this.siteName, `邮箱验证失败: ${error.message}`); throw error; } } /** * 步骤5: 跳过问卷调查 */ async step5_skipSurvey() { logger.info(this.siteName, `[步骤 5/${this.getTotalSteps()}] 跳过问卷`); try { // 等待页面加载 await this.human.readPage(2, 3); // 查找并点击"Skip this step"按钮 logger.info(this.siteName, ' → 查找"Skip this step"按钮...'); // 方式1: 通过button文本查找 const buttons = await this.page.$$('button'); let skipButton = null; for (const button of buttons) { const text = await this.page.evaluate(el => el.textContent?.trim(), button); if (text && text.toLowerCase().includes('skip')) { skipButton = button; logger.info(this.siteName, ` → 找到按钮: "${text}"`); break; } } if (skipButton) { logger.info(this.siteName, ' → 点击"Skip this step"按钮...'); await skipButton.click(); // 等待页面跳转 await this.human.randomDelay(2000, 3000); this.currentStep = 5; logger.success(this.siteName, `步骤 5 完成`); } else { logger.warn(this.siteName, ' → 未找到Skip按钮,可能已跳过此页面'); this.currentStep = 5; } } catch (error) { logger.error(this.siteName, `跳过问卷失败: ${error.message}`); throw error; } } /** * 步骤6: 选择计划 */ async step6_selectPlan() { logger.info(this.siteName, `[步骤 6/${this.getTotalSteps()}] 选择计划`); try { // 等待页面加载 await this.human.readPage(2, 3); // 查找并点击"Select plan"按钮 logger.info(this.siteName, ' → 查找"Select plan"按钮...'); // 方式1: 通过按钮文本查找 const buttons = await this.page.$$('button'); let selectButton = null; for (const button of buttons) { const text = await this.page.evaluate(el => el.textContent?.trim(), button); if (text && text.toLowerCase().includes('select plan')) { selectButton = button; logger.info(this.siteName, ` → 找到按钮: "${text}"`); break; } } if (selectButton) { logger.info(this.siteName, ' → 点击"Select plan"按钮...'); await selectButton.click(); // 等待页面跳转到支付页面 logger.info(this.siteName, ' → 等待进入支付页面...'); await this.human.randomDelay(3000, 5000); // 验证是否进入支付页面(检查是否有Stripe元素或支付表单) const hasPaymentForm = await this.page.$('button[data-testid="card-accordion-item-button"]').then(el => !!el).catch(() => false); const hasStripeCheckout = this.page.url().includes('checkout.stripe.com'); 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按钮,且不在支付页面'); } } } catch (error) { logger.error(this.siteName, `选择计划失败: ${error.message}`); throw error; } } /** * 步骤7: 填写支付信息 */ async step7_fillPayment() { logger.info(this.siteName, `[步骤 7/${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}`); // 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},继续尝试...`); } // 等待Stripe表单iframe加载 logger.info(this.siteName, ' → 等待支付表单加载...'); await this.human.randomDelay(3000, 5000); // 3. 填写卡号(使用humanType逐字符输入) logger.info(this.siteName, ' → 填写卡号...'); 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, ' → 填写有效期...'); 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...'); 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, ' → 填写持卡人姓名...'); 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, ' → 选择地址:中国澳门特别行政区...'); 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. 填写地址信息(如果需要) // 等待地址字段加载 await this.human.randomDelay(1000, 2000); // 检查是否需要填写地址行1和行2 const addressFields = await this.page.$$('input[placeholder*="地址"]'); if (addressFields.length > 0) { logger.info(this.siteName, ' → 填写地址信息...'); // 填写地址: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('kowloon', { delay: 100 }); logger.success(this.siteName, ' → ✓ 地址行2已填写: kowloon'); } } // 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); const submitButton = await this.page.$('button[type="submit"][data-testid="hosted-payment-submit-button"]'); if (submitButton) { await submitButton.click(); logger.success(this.siteName, ' → ✓ 已点击订阅按钮'); // 等待处理 await this.human.randomDelay(5000, 7000); this.currentStep = 7; logger.success(this.siteName, `步骤 7 完成`); } else { logger.warn(this.siteName, ' → 未找到订阅按钮'); this.currentStep = 7; } } catch (error) { logger.error(this.siteName, `填写支付信息失败: ${error.message}`); throw error; } } /** * 执行注册流程 * @param {Object} options - 选项 * @param {number} options.fromStep - 从第几步开始(默认1) * @param {number} options.toStep - 执行到第几步(默认全部) */ async register(options = {}) { const fromStep = options.fromStep || 1; const toStep = options.toStep || this.getTotalSteps(); try { // 1. 生成数据 this.accountData = this.generateData(options); logger.success(this.siteName, '账号数据生成完成'); logger.info(this.siteName, `First Name: ${this.accountData.firstName}`); logger.info(this.siteName, `Last Name: ${this.accountData.lastName}`); logger.info(this.siteName, `Email: ${this.accountData.email}`); logger.info(this.siteName, `Password: ${this.accountData.password}`); // 2. 初始化浏览器 await this.initBrowser(); // 3. 执行指定范围的步骤 logger.info(this.siteName, `\n开始执行步骤 ${fromStep} 到 ${toStep} (共 ${this.getTotalSteps()} 步)\n`); for (let i = fromStep; i <= toStep && i <= this.getTotalSteps(); i++) { const step = this.steps[i - 1]; if (typeof this[step.method] === 'function') { try { await this[step.method](); } catch (error) { logger.error(this.siteName, `步骤 ${i} 执行失败: ${error.message}`); throw error; } } else { logger.warn(this.siteName, `步骤 ${i} (${step.name}) 未实现`); break; } } const stepInfo = this.getCurrentStepInfo(); logger.success(this.siteName, `\n已完成步骤 ${fromStep} 到 ${this.currentStep}`); if (this.currentStep < this.getTotalSteps()) { logger.info(this.siteName, `剩余步骤需要手动完成或等待后续开发`); } // 不自动关闭浏览器,让用户查看结果 if (!options.keepBrowserOpen) { logger.info(this.siteName, '10秒后关闭浏览器...'); await this.page.waitForTimeout(10000); await this.closeBrowser(); } else { logger.info(this.siteName, '浏览器保持打开状态'); } return { success: true, account: this.accountData, completedSteps: this.currentStep, totalSteps: this.getTotalSteps(), message: `完成 ${this.currentStep}/${this.getTotalSteps()} 步` }; } catch (error) { logger.error(this.siteName, `注册失败: ${error.message}`); // 截图保存错误状态 if (this.page) { try { const screenshotPath = `/tmp/windsurf-error-${Date.now()}.png`; await this.page.screenshot({ path: screenshotPath }); logger.info(this.siteName, `错误截图已保存: ${screenshotPath}`); } catch (e) { // 忽略截图错误 } } await this.closeBrowser(); throw error; } } } module.exports = WindsurfRegister;