auto-account-machine/src/tools/account-register/sites/windsurf.js
dengqichen 6dd4763cda aaaaa
2025-11-17 00:14:54 +08:00

1014 lines
37 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/**
* 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<boolean>
* @param {number} maxAttempts - 最多尝试次数
* @param {string} actionName - 操作名称(用于日志)
* @param {string} buttonText - 按钮文本过滤(可选,如"Continue"
* @returns {Promise<boolean>} - 是否成功跳转
*/
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;