auto-account-machine/src/tools/account-register/sites/windsurf.js
2025-11-17 17:44:17 +08:00

1525 lines
58 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');
const database = require('../../database');
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.registrationTime = null;
this.quotaInfo = null;
this.billingInfo = null;
this.cardInfo = null;
// 定义所有步骤
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: 7, name: '获取订阅信息', method: 'step7_getSubscriptionInfo' },
{ id: 8, name: '保存到数据库', method: 'step8_saveToDatabase' },
{ id: 9, name: '清理并关闭浏览器', method: 'step9_clearAndCloseBrowser' },
];
}
/**
* 获取步骤总数
*/
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()
};
}
/**
* 通用方法:等待按钮激活并点击,然后等待页面内容变化
* @param {Object} options - 配置选项
* @param {string} options.buttonText - 按钮文本(用于识别,如'Continue'
* @param {Function} options.checkContentChanged - 检查页面内容是否已变化的函数返回Promise<boolean>
* @param {number} options.waitButtonTimeout - 等待按钮激活的超时时间毫秒默认30000
* @param {number} options.waitContentTimeout - 等待内容变化的超时时间毫秒默认15000
* @param {string} options.actionName - 操作名称(用于日志)
* @returns {Promise<boolean>} - 是否成功完成
*/
async clickButtonAndWaitForPageChange(options) {
const {
buttonText = 'Continue',
checkContentChanged,
waitButtonTimeout = 30000,
waitContentTimeout = 15000,
actionName = '点击按钮'
} = options;
try {
// 阶段1: 等待按钮变为可点击状态enabled
logger.info(this.siteName, ` → 等待"${buttonText}"按钮激活...`);
const buttonStartTime = Date.now();
let buttonEnabled = false;
while (Date.now() - buttonStartTime < waitButtonTimeout) {
buttonEnabled = await this.page.evaluate((btnText) => {
const buttons = Array.from(document.querySelectorAll('button'));
const targetButton = buttons.find(btn =>
btn.textContent.trim() === btnText && !btn.disabled
);
return !!targetButton;
}, buttonText);
if (buttonEnabled) {
const elapsed = ((Date.now() - buttonStartTime) / 1000).toFixed(1);
logger.success(this.siteName, ` → ✓ 按钮已激活 (耗时: ${elapsed}秒)`);
break;
}
// 每5秒输出一次进度
const elapsed = Date.now() - buttonStartTime;
if (elapsed > 0 && elapsed % 5000 === 0) {
logger.info(this.siteName, ` → 等待按钮激活中... 已用时 ${(elapsed/1000).toFixed(0)}`);
}
await new Promise(resolve => setTimeout(resolve, 1000));
}
if (!buttonEnabled) {
logger.error(this.siteName, ` → ⚠️ 等待${waitButtonTimeout/1000}秒后按钮仍未激活`);
return false;
}
// 阶段2: 点击按钮
logger.info(this.siteName, `${actionName}...`);
await this.human.humanClick(this.page, `button:not([disabled])`);
await this.human.randomDelay(1000, 2000);
// 阶段3: 等待页面内容变化
logger.info(this.siteName, ` → 等待页面内容变化...`);
const contentStartTime = Date.now();
let contentChanged = false;
while (Date.now() - contentStartTime < waitContentTimeout) {
contentChanged = await checkContentChanged();
if (contentChanged) {
const elapsed = ((Date.now() - contentStartTime) / 1000).toFixed(1);
logger.success(this.siteName, ` → ✓ 页面内容已变化 (耗时: ${elapsed}秒)`);
break;
}
await new Promise(resolve => setTimeout(resolve, 500));
}
if (!contentChanged) {
logger.warn(this.siteName, ` → ⚠️ 等待${waitContentTimeout/1000}秒后页面内容未变化`);
return false;
}
return true;
} catch (error) {
logger.error(this.siteName, `${actionName}失败: ${error.message}`);
return false;
}
}
/**
* 生成账号数据(干运行模式)
*/
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;
}
/**
* 初始化浏览器 - 使用 AdsPower 指纹浏览器
*/
async initBrowser(options = {}) {
const puppeteer = require('puppeteer');
const axios = require('axios');
logger.info(this.siteName, '启动 AdsPower 指纹浏览器...');
// 检查 AdsPower 配置
const adspowerUserId = process.env.ADSPOWER_USER_ID;
if (!adspowerUserId) {
logger.error(this.siteName, '');
logger.error(this.siteName, '❌ 未配置 ADSPOWER_USER_ID');
logger.error(this.siteName, '');
logger.error(this.siteName, '请在 .env 文件中配置:');
logger.error(this.siteName, 'ADSPOWER_USER_ID=your_profile_id');
logger.error(this.siteName, '');
throw new Error('未配置 AdsPower 用户ID');
}
const apiBase = process.env.ADSPOWER_API || 'http://local.adspower.net:50325';
const apiKey = process.env.ADSPOWER_API_KEY;
const startUrl = `${apiBase}/api/v1/browser/start?user_id=${encodeURIComponent(adspowerUserId)}`;
// 配置请求头
const headers = {};
if (apiKey && apiKey.trim()) {
headers['Authorization'] = `Bearer ${apiKey}`;
logger.info(this.siteName, '✓ 使用 API Key 认证');
}
logger.info(this.siteName, ` → 启动 AdsPower 配置: ${adspowerUserId}`);
try {
const response = await axios.get(startUrl, { headers });
const data = response.data;
if (data.code !== 0) {
logger.error(this.siteName, '');
logger.error(this.siteName, `AdsPower API 返回错误: ${JSON.stringify(data)}`);
logger.error(this.siteName, '');
logger.error(this.siteName, '解决方案:');
logger.error(this.siteName, '1. 确保 AdsPower 应用已启动并登录');
logger.error(this.siteName, '2. 检查配置文件 ID 是否正确: ' + adspowerUserId);
logger.error(this.siteName, '3. 如果需要 API Key请在 AdsPower 设置中生成');
logger.error(this.siteName, '4. 尝试在 AdsPower 中手动打开一次浏览器配置');
logger.error(this.siteName, '');
throw new Error(`AdsPower 启动失败: ${data.msg || JSON.stringify(data)}`);
}
// 获取 WebSocket 端点
const wsEndpoint = data.data.ws && (
data.data.ws.puppeteer ||
data.data.ws.selenium ||
data.data.ws.ws ||
data.data.ws
);
if (!wsEndpoint) {
throw new Error('AdsPower 未返回 WebSocket 端点');
}
logger.info(this.siteName, ` → WebSocket: ${wsEndpoint}`);
// 连接到 AdsPower 浏览器
this.browser = await puppeteer.connect({
browserWSEndpoint: wsEndpoint,
defaultViewport: null
});
// 获取已存在的页面
const pages = await this.browser.pages();
this.page = pages[0] || await this.browser.newPage();
// 关闭多余的标签页AdsPower 需要至少保留一个)
if (pages.length > 1) {
for (let i = 1; i < pages.length; i++) {
try {
await pages[i].close();
} catch (e) {
// 忽略关闭失败
}
}
}
logger.success(this.siteName, '✓ AdsPower 浏览器连接成功');
logger.info(this.siteName, '✓ 使用真实指纹,可同时绕过 Cloudflare 和 Stripe');
logger.info(this.siteName, '等待浏览器完全准备...');
await this.human.randomDelay(2000, 3000);
} catch (error) {
logger.error(this.siteName, '');
logger.error(this.siteName, `❌ AdsPower 连接失败: ${error.message}`);
logger.error(this.siteName, '');
throw error;
}
}
/**
* 关闭浏览器
*/
async closeBrowser() {
if (this.browser) {
await this.browser.close();
logger.info(this.siteName, '浏览器已关闭');
}
}
/**
* 步骤1: 填写基本信息
*/
async step1_fillBasicInfo() {
logger.info(this.siteName, `[步骤 1/${this.getTotalSteps()}] 填写基本信息`);
const overallStartTime = Date.now();
const overallMaxWait = 180000; // 总共最多重试3分钟
let stepCompleted = false;
let retryCount = 0;
while (!stepCompleted && (Date.now() - overallStartTime < overallMaxWait)) {
try {
if (retryCount > 0) {
logger.info(this.siteName, ` → 第 ${retryCount + 1} 次尝试...`);
}
// 打开注册页面
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按钮并等待跳转到密码页面
const success = await this.clickButtonAndWaitForPageChange({
buttonText: 'Continue',
checkContentChanged: async () => {
// 检查是否有密码输入框(表示已进入下一步)
return await this.page.evaluate(() => {
return !!document.querySelector('#password');
});
},
waitButtonTimeout: 30000,
waitContentTimeout: 15000,
actionName: '点击Continue进入密码设置页面'
});
if (!success) {
logger.warn(this.siteName, ' → ⚠️ 未能进入密码页面,将重新尝试');
retryCount++;
await this.human.randomDelay(2000, 3000);
continue;
}
// 成功完成
stepCompleted = true;
} catch (error) {
logger.warn(this.siteName, ` → 执行出错: ${error.message},将重新尝试`);
retryCount++;
await this.human.randomDelay(3000, 5000);
continue;
}
}
if (!stepCompleted) {
logger.error(this.siteName, ` → ✗ 步骤1失败${retryCount + 1}次尝试后仍未成功`);
throw new Error(`步骤1${retryCount + 1}次尝试后仍未能完成基本信息填写`);
}
// 额外等待确保页面稳定
await this.human.randomDelay(1000, 2000);
this.currentStep = 1;
logger.success(this.siteName, `步骤 1 完成 (共尝试 ${retryCount + 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按钮此时会进入 Cloudflare Turnstile 验证页面)
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, ' → 按钮已激活,点击进入验证页面...');
// 点击按钮
await Promise.all([
this.page.waitForNavigation({ waitUntil: 'load', timeout: 10000 }).catch(() => {}),
this.human.humanClick(this.page, 'button:not([disabled])')
]);
logger.success(this.siteName, ' → 已进入 Cloudflare Turnstile 验证页面');
} catch (error) {
logger.warn(this.siteName, ' → 按钮等待超时尝试按Enter键');
await this.page.keyboard.press('Enter');
}
// 等待页面稳定
await this.human.randomDelay(1000, 2000);
// 等待 Cloudflare Turnstile 验证完成(手动或其他方式)
logger.info(this.siteName, ' → 等待 Cloudflare Turnstile 验证...');
logger.info(this.siteName, ' → 请手动完成验证或等待自动处理...');
const startTime = Date.now();
const maxWait = 120000; // 最多等待120秒
// 轮询检查按钮是否激活
while (Date.now() - startTime < maxWait) {
const buttonEnabled = await this.page.evaluate(() => {
const button = document.querySelector('button');
return button && !button.disabled && button.textContent.trim() === 'Continue';
});
if (buttonEnabled) {
const duration = ((Date.now() - startTime) / 1000).toFixed(1);
logger.success(this.siteName, ` → ✓ Turnstile 验证完成!耗时: ${duration}`);
break;
}
// 每10秒输出一次进度
const elapsed = Date.now() - startTime;
if (elapsed > 0 && elapsed % 10000 === 0) {
logger.info(this.siteName, ` → 等待验证中... 已用时 ${elapsed/1000}`);
}
await new Promise(resolve => setTimeout(resolve, 500));
}
if (Date.now() - startTime >= maxWait) {
logger.error(this.siteName, ' → ⚠️ 验证超时120秒');
throw new Error('Turnstile 验证超时');
}
// 点击 Continue 进入邮箱验证
logger.info(this.siteName, ' → 点击Continue进入邮箱验证...');
await Promise.all([
this.page.waitForNavigation({ waitUntil: 'load', timeout: 10000 }).catch(() => {}),
this.human.humanClick(this.page, 'button:not([disabled])')
]);
// 额外等待确保页面稳定
await this.human.randomDelay(1000, 2000);
this.currentStep = 2;
logger.success(this.siteName, `步骤 2 完成`);
}
/**
* 步骤3: 邮箱验证
*/
async step3_emailVerification() {
logger.info(this.siteName, `[步骤 3/${this.getTotalSteps()}] 邮箱验证`);
// Cloudflare Turnstile 验证已在步骤2中完成
logger.info(this.siteName, ' → Turnstile 验证已通过,开始邮箱验证...');
try {
// 等待验证码页面加载
await this.human.readPage(1, 2);
// 延迟2秒后再获取验证码让邮件有足够时间到达
logger.info(this.siteName, ' → 延迟2秒等待邮件到达...');
await new Promise(resolve => setTimeout(resolve, 2000));
// 获取验证码(从邮箱)
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, ' → 验证码已填写完成');
// 输入完6位验证码后页面会自动提交
logger.info(this.siteName, ' → 等待邮箱验证完成并跳转到问卷页面...');
logger.info(this.siteName, ' → 将持续等待直到跳转成功(无时间限制)...');
// 无限等待页面跳转到 /account/onboarding?page=source
const startTime = Date.now();
let registrationComplete = false;
let hasClickedButton = false;
while (!registrationComplete) {
const currentUrl = this.page.url();
// 检查1: 页面是否已经跳转成功(自动跳转成功)
if (currentUrl.includes('/account/onboarding') && currentUrl.includes('page=source')) {
registrationComplete = true;
const totalTime = ((Date.now() - startTime) / 1000).toFixed(1);
logger.success(this.siteName, ` → ✓ 邮箱验证成功!已跳转到问卷页面 (耗时: ${totalTime}秒)`);
logger.info(this.siteName, ` → 当前页面: ${currentUrl}`);
break;
}
// 检查2: 页面按钮是否变为激活状态(自动跳转失败,需要手动点击)
if (!hasClickedButton) {
const buttonEnabled = await this.page.evaluate(() => {
const buttons = Array.from(document.querySelectorAll('button'));
const continueButton = buttons.find(btn =>
btn.textContent.trim() === 'Continue' && !btn.disabled
);
return !!continueButton;
});
if (buttonEnabled) {
const elapsed = ((Date.now() - startTime) / 1000).toFixed(1);
logger.warn(this.siteName, ` → ⚠️ 检测到按钮重新激活 (${elapsed}秒后)`);
logger.info(this.siteName, ' → 自动跳转可能失败,尝试手动点击按钮...');
try {
// 点击 Continue 按钮
await this.human.humanClick(this.page, 'button:not([disabled])');
hasClickedButton = true;
logger.success(this.siteName, ' → ✓ 已点击按钮,继续等待跳转...');
await this.human.randomDelay(1000, 2000);
} catch (e) {
logger.warn(this.siteName, ` → 点击按钮失败: ${e.message}`);
}
}
}
// 每5秒输出一次进度
const elapsed = Date.now() - startTime;
if (elapsed > 0 && elapsed % 5000 === 0) {
logger.info(this.siteName, ` → 等待中... 已用时 ${(elapsed/1000).toFixed(0)}`);
}
await new Promise(resolve => setTimeout(resolve, 500));
}
// 额外等待页面稳定
await this.human.randomDelay(2000, 3000);
this.currentStep = 3;
logger.success(this.siteName, `步骤 3 完成 - 账号创建成功`);
} else {
logger.error(this.siteName, ' → 未找到验证码输入框!');
logger.warn(this.siteName, ' → 请手动输入验证码: ' + code);
// 等待用户手动输入
await this.human.randomDelay(30000, 30000);
this.currentStep = 3;
}
} catch (error) {
logger.error(this.siteName, `邮箱验证失败: ${error.message}`);
throw error;
}
}
/**
* 步骤4: 跳过问卷调查
*/
async step4_skipSurvey() {
logger.info(this.siteName, `[步骤 4/${this.getTotalSteps()}] 跳过问卷`);
const overallStartTime = Date.now();
const overallMaxWait = 120000; // 总共最多重试2分钟
let stepCompleted = false;
let retryCount = 0;
while (!stepCompleted && (Date.now() - overallStartTime < overallMaxWait)) {
try {
if (retryCount > 0) {
logger.info(this.siteName, ` → 第 ${retryCount + 1} 次尝试...`);
}
// 等待页面加载
await this.human.readPage(2, 3);
logger.info(this.siteName, ' → 查找"Skip this step"按钮...');
const startTime = Date.now();
const maxWait = 30000; // 单次查找最多等待30秒
let skipButton = null;
let buttonFound = false;
// 轮询查找按钮
while (!buttonFound && (Date.now() - startTime < maxWait)) {
try {
// 查找所有按钮或链接
const buttons = await this.page.$$('button, a');
for (const button of buttons) {
const text = await this.page.evaluate(el => el.textContent?.trim(), button);
// 匹配 "Skip this step" 或 "skip"
if (text && (text.toLowerCase().includes('skip this step') || text.toLowerCase() === 'skip')) {
skipButton = button;
buttonFound = true;
logger.success(this.siteName, ` → ✓ 找到按钮: "${text}" (耗时: ${((Date.now() - startTime) / 1000).toFixed(1)}秒)`);
break;
}
}
if (buttonFound) {
break;
}
// 每5秒输出一次进度
const elapsed = Date.now() - startTime;
if (elapsed > 0 && elapsed % 5000 === 0) {
logger.info(this.siteName, ` → 等待按钮出现... 已用时 ${elapsed/1000}`);
}
// 等待2秒后继续查找
await new Promise(resolve => setTimeout(resolve, 2000));
} catch (error) {
logger.warn(this.siteName, ` → 查找按钮时出错: ${error.message},继续尝试...`);
await new Promise(resolve => setTimeout(resolve, 2000));
}
}
if (skipButton && buttonFound) {
logger.info(this.siteName, ' → 点击"Skip this step"按钮...');
// 直接点击找到的按钮元素
await skipButton.click();
logger.success(this.siteName, ' → ✓ 已点击按钮');
// 等待页面跳转到 /account/upgrade-prompt
logger.info(this.siteName, ' → 等待跳转到 /account/upgrade-prompt 页面...');
const jumpStartTime = Date.now();
const jumpMaxWait = 15000; // 最多等待15秒
let jumpSuccess = false;
while (Date.now() - jumpStartTime < jumpMaxWait) {
const newUrl = this.page.url();
// 必须跳转到 upgrade-prompt 页面
if (newUrl.includes('/account/upgrade-prompt')) {
jumpSuccess = true;
stepCompleted = true;
logger.success(this.siteName, ` → ✓ 成功跳转到计划选择页面`);
logger.info(this.siteName, ` → 当前页面: ${newUrl}`);
break;
}
await new Promise(resolve => setTimeout(resolve, 500));
}
if (!jumpSuccess) {
const finalUrl = this.page.url();
logger.warn(this.siteName, ` → ⚠️ 未跳转到升级页面,将重新尝试`);
logger.warn(this.siteName, ` → 当前页面: ${finalUrl}`);
retryCount++;
await this.human.randomDelay(2000, 3000);
continue; // 重新开始循环
}
} else {
// 未找到按钮,重试
logger.warn(this.siteName, ` → ⚠️ 未找到Skip按钮将重新尝试`);
retryCount++;
await this.human.randomDelay(2000, 3000);
continue;
}
} catch (error) {
logger.warn(this.siteName, ` → 执行出错: ${error.message},将重新尝试`);
retryCount++;
await this.human.randomDelay(2000, 3000);
continue;
}
}
if (!stepCompleted) {
const currentUrl = this.page.url();
logger.error(this.siteName, ` → ✗ 步骤4失败${retryCount + 1}次尝试后仍未成功`);
logger.error(this.siteName, ` → 当前页面: ${currentUrl}`);
throw new Error(`步骤4${retryCount + 1}次尝试后仍未成功跳转到 /account/upgrade-prompt 页面`);
}
// 额外等待页面稳定
await this.human.randomDelay(2000, 3000);
this.currentStep = 4;
logger.success(this.siteName, `步骤 4 完成 (共尝试 ${retryCount + 1} 次)`);
}
/**
* 步骤5: 选择计划
*/
async step5_selectPlan() {
logger.info(this.siteName, `[步骤 5/${this.getTotalSteps()}] 选择计划`);
const overallStartTime = Date.now();
const overallMaxWait = 120000; // 总共最多重试2分钟
let stepCompleted = false;
let retryCount = 0;
while (!stepCompleted && (Date.now() - overallStartTime < overallMaxWait)) {
try {
if (retryCount > 0) {
logger.info(this.siteName, ` → 第 ${retryCount + 1} 次尝试...`);
}
// 等待页面加载
await this.human.readPage(2, 3);
logger.info(this.siteName, ' → 查找计划选择按钮...');
const startTime = Date.now();
const maxWait = 30000; // 单次查找最多等待30秒
let selectButton = null;
let buttonFound = false;
// 轮询查找按钮
while (!buttonFound && (Date.now() - startTime < maxWait)) {
try {
const buttons = await this.page.$$('button, a');
for (const button of buttons) {
const text = await this.page.evaluate(el => el.textContent?.trim(), button);
// 匹配多种可能的按钮文本
if (text && (
text.toLowerCase().includes('select plan') ||
text.toLowerCase().includes('continue') ||
text.toLowerCase().includes('get started') ||
text.toLowerCase() === 'select'
)) {
selectButton = button;
buttonFound = true;
logger.success(this.siteName, ` → ✓ 找到按钮: "${text}" (耗时: ${((Date.now() - startTime) / 1000).toFixed(1)}秒)`);
break;
}
}
if (buttonFound) {
break;
}
// 每5秒输出一次进度
const elapsed = Date.now() - startTime;
if (elapsed > 0 && elapsed % 5000 === 0) {
logger.info(this.siteName, ` → 等待按钮出现... 已用时 ${elapsed/1000}`);
}
await new Promise(resolve => setTimeout(resolve, 2000));
} catch (error) {
logger.warn(this.siteName, ` → 查找按钮时出错: ${error.message},继续尝试...`);
await new Promise(resolve => setTimeout(resolve, 2000));
}
}
if (selectButton && buttonFound) {
logger.info(this.siteName, ' → 点击计划选择按钮...');
// 直接点击找到的按钮元素
await selectButton.click();
logger.success(this.siteName, ' → ✓ 已点击按钮');
// 等待页面跳转到 Stripe checkout
logger.info(this.siteName, ' → 等待跳转到 Stripe 支付页面...');
const jumpStartTime = Date.now();
const jumpMaxWait = 20000; // 最多等待20秒
let jumpSuccess = false;
while (Date.now() - jumpStartTime < jumpMaxWait) {
const newUrl = this.page.url();
// 必须跳转到 Stripe checkout
if (newUrl.includes('checkout.stripe.com') || newUrl.includes('stripe.com')) {
jumpSuccess = true;
stepCompleted = true;
logger.success(this.siteName, ` → ✓ 成功跳转到 Stripe 支付页面`);
logger.info(this.siteName, ` → 当前页面: ${newUrl}`);
break;
}
await new Promise(resolve => setTimeout(resolve, 500));
}
if (!jumpSuccess) {
const finalUrl = this.page.url();
logger.warn(this.siteName, ` → ⚠️ 未跳转到支付页面,将重新尝试`);
logger.warn(this.siteName, ` → 当前页面: ${finalUrl}`);
retryCount++;
await this.human.randomDelay(2000, 3000);
continue;
}
} else {
// 未找到按钮,重试
logger.warn(this.siteName, ` → ⚠️ 未找到计划选择按钮,将重新尝试`);
retryCount++;
await this.human.randomDelay(2000, 3000);
continue;
}
} catch (error) {
logger.warn(this.siteName, ` → 执行出错: ${error.message},将重新尝试`);
retryCount++;
await this.human.randomDelay(2000, 3000);
continue;
}
}
if (!stepCompleted) {
const currentUrl = this.page.url();
logger.error(this.siteName, ` → ✗ 步骤5失败${retryCount + 1}次尝试后仍未成功`);
logger.error(this.siteName, ` → 当前页面: ${currentUrl}`);
throw new Error(`步骤5${retryCount + 1}次尝试后仍未成功跳转到 Stripe 支付页面`);
}
// 额外等待页面稳定
await this.human.randomDelay(2000, 3000);
this.currentStep = 5;
logger.success(this.siteName, `步骤 5 完成 (共尝试 ${retryCount + 1} 次)`);
}
/**
* 步骤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. 点击选择"银行卡"支付方式
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(
() => {
const cardNumber = document.querySelector('#cardNumber');
const cardExpiry = document.querySelector('#cardExpiry');
const cardCvc = document.querySelector('#cardCvc');
const billingName = document.querySelector('#billingName');
// 检查所有字段都存在且可见
return cardNumber && cardExpiry && cardCvc && billingName;
},
{ timeout: 30000 }
);
logger.success(this.siteName, ' → ✓ 支付表单加载完成');
} catch (e) {
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, ' → 填写持卡人姓名...');
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;
});
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) {
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;
}
// 每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));
}
}
// 额外等待页面稳定
await this.human.randomDelay(2000, 3000);
this.currentStep = 6;
logger.success(this.siteName, `步骤 6 完成`);
} catch (error) {
logger.error(this.siteName, `填写支付信息失败: ${error.message}`);
throw error;
}
}
/**
* 步骤7: 获取订阅信息
*/
async step7_getSubscriptionInfo() {
logger.info(this.siteName, `[步骤 7/${this.getTotalSteps()}] 获取订阅信息`);
try {
// 0. 检查并关闭可能存在的弹窗("要打开 Windsurf 吗?"
logger.info(this.siteName, ' → 检查是否有弹窗需要关闭...');
try {
await new Promise(resolve => setTimeout(resolve, 2000)); // 等待弹窗出现
// 方法1: 尝试按ESC键关闭浏览器原生对话框
logger.info(this.siteName, ' → 尝试按ESC键关闭原生对话框...');
await this.page.keyboard.press('Escape');
await this.human.randomDelay(500, 1000);
// 方法2: 尝试查找并点击网页内的按钮如果是HTML弹窗
const closeDialog = await this.page.evaluate(() => {
// 查找包含"取消"或"打开Windsurf"的按钮
const buttons = Array.from(document.querySelectorAll('button'));
const cancelBtn = buttons.find(btn =>
btn.textContent.includes('取消') ||
btn.textContent.includes('打开Windsurf') ||
btn.textContent.includes('Cancel')
);
if (cancelBtn) {
// 优先点击"取消"按钮
const actualCancelBtn = buttons.find(btn =>
btn.textContent.includes('取消') ||
btn.textContent.includes('Cancel')
);
if (actualCancelBtn) {
actualCancelBtn.click();
return '取消';
} else {
cancelBtn.click();
return '打开Windsurf';
}
}
return null;
});
if (closeDialog) {
logger.success(this.siteName, ` → ✓ 已关闭HTML弹窗点击了"${closeDialog}"`);
await this.human.randomDelay(1000, 2000);
} else {
logger.success(this.siteName, ' → ✓ 已尝试关闭原生对话框ESC键');
}
} catch (e) {
logger.info(this.siteName, ` → 关闭弹窗时出错: ${e.message}`);
}
// 1. 跳转到订阅使用页面
logger.info(this.siteName, ' → 跳转到订阅使用页面...');
await this.page.goto('https://windsurf.com/subscription/usage', {
waitUntil: 'networkidle2',
timeout: 30000
});
// 等待页面加载
await this.human.randomDelay(3000, 5000);
// 2. 获取配额信息
logger.info(this.siteName, ' → 获取配额信息...');
const quotaInfo = await this.page.evaluate(() => {
// 查找包含配额的 p 标签
const quotaElement = document.querySelector('p.caption1.font-medium.text-sk-black\\/80');
if (!quotaElement) return null;
const spans = quotaElement.querySelectorAll('span.caption3 span');
if (spans.length >= 2) {
const used = spans[0].textContent.trim();
const total = spans[1].textContent.trim().replace('/', '').trim();
return { used, total };
}
return null;
});
if (quotaInfo) {
logger.success(this.siteName, ` → ✓ 配额: ${quotaInfo.used} / ${quotaInfo.total} used`);
} else {
logger.warn(this.siteName, ' → ⚠️ 未找到配额信息');
}
// 3. 获取下次账单日期信息
logger.info(this.siteName, ' → 获取账单周期信息...');
const billingInfo = await this.page.evaluate(() => {
// 查找包含 "Next billing cycle" 的 p 标签
const billingElement = Array.from(document.querySelectorAll('p.caption1'))
.find(p => p.textContent.includes('Next billing cycle'));
if (!billingElement) return null;
// 提取天数
const daysMatch = billingElement.textContent.match(/(\d+)\s+days?/);
const dateMatch = billingElement.textContent.match(/on\s+([A-Za-z]+\s+\d+,\s+\d{4})/);
return {
days: daysMatch ? daysMatch[1] : null,
date: dateMatch ? dateMatch[1] : null,
fullText: billingElement.textContent.trim()
};
});
if (billingInfo && billingInfo.days) {
logger.success(this.siteName, ` → ✓ 下次账单: ${billingInfo.days} 天后 (${billingInfo.date})`);
} else {
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, '');
// 保存订阅信息供后续使用
this.quotaInfo = quotaInfo;
this.billingInfo = billingInfo;
this.currentStep = 7;
logger.success(this.siteName, `步骤 7 完成`);
} catch (error) {
logger.error(this.siteName, `获取订阅信息失败: ${error.message}`);
throw error;
}
}
/**
* 步骤8: 保存到数据库
*/
async step8_saveToDatabase() {
logger.info(this.siteName, `[步骤 8/${this.getTotalSteps()}] 保存到数据库`);
try {
// 初始化数据库连接
logger.info(this.siteName, ' → 连接数据库...');
await database.initialize();
// 准备账号数据
const accountData = {
email: this.accountData.email,
password: this.accountData.password,
firstName: this.accountData.firstName,
lastName: this.accountData.lastName,
registrationTime: this.registrationTime,
quotaUsed: this.quotaInfo ? parseFloat(this.quotaInfo.used) : 0,
quotaTotal: this.quotaInfo ? parseFloat(this.quotaInfo.total) : 0,
billingDays: this.billingInfo ? parseInt(this.billingInfo.days) : null,
billingDate: this.billingInfo ? this.billingInfo.date : null,
paymentCardNumber: this.cardInfo ? this.cardInfo.number : null,
paymentCountry: this.cardInfo ? this.cardInfo.country : 'MO',
status: 'active'
};
// 保存到数据库
logger.info(this.siteName, ' → 保存账号信息...');
const accountRepo = database.getRepository('account');
const accountId = await accountRepo.create(accountData);
logger.success(this.siteName, ` → ✓ 账号信息已保存到数据库 (ID: ${accountId})`);
logger.info(this.siteName, ` → 邮箱: ${accountData.email}`);
logger.info(this.siteName, ` → 配额: ${accountData.quotaUsed} / ${accountData.quotaTotal}`);
logger.info(this.siteName, ` → 卡号: ${accountData.paymentCardNumber}`);
this.currentStep = 8;
logger.success(this.siteName, `步骤 8 完成`);
} catch (error) {
logger.error(this.siteName, `保存到数据库失败: ${error.message}`);
throw error;
}
}
/**
* 步骤9: 清理并关闭浏览器
*/
async step9_clearAndCloseBrowser() {
logger.info(this.siteName, `[步骤 9/${this.getTotalSteps()}] 清理并关闭浏览器`);
try {
// 清除所有浏览器数据(类似 Ctrl+Shift+Delete
logger.info(this.siteName, ' → 清除所有浏览器数据Cookies、Cache、Storage等...');
try {
// 使用 Chrome DevTools Protocol 进行深度清理
const client = await this.page.target().createCDPSession();
// 1. 清除浏览器 Cookies
await client.send('Network.clearBrowserCookies');
logger.success(this.siteName, ' → ✓ 已清除所有 Cookies');
// 2. 清除浏览器缓存
await client.send('Network.clearBrowserCache');
logger.success(this.siteName, ' → ✓ 已清除浏览器缓存');
// 3. 清除所有存储数据localStorage, sessionStorage, IndexedDB, WebSQL, Cache Storage, Service Workers
await client.send('Storage.clearDataForOrigin', {
origin: '*',
storageTypes: 'all'
});
logger.success(this.siteName, ' → ✓ 已清除所有存储数据');
// 4. 额外清理:访问目标网站并清除其存储
await this.page.goto('https://windsurf.com', { waitUntil: 'domcontentloaded' });
await this.page.evaluate(() => {
try {
localStorage.clear();
sessionStorage.clear();
} catch (e) {}
});
// 5. 关闭 CDP 会话
await client.detach();
logger.success(this.siteName, ' → ✓ 浏览器数据清除完成(全新状态)');
} catch (e) {
logger.warn(this.siteName, ` → 清除浏览器数据失败: ${e.message}`);
}
// 关闭浏览器
logger.info(this.siteName, ' → 关闭浏览器...');
await this.closeBrowser();
logger.success(this.siteName, ' → ✓ 浏览器已关闭');
this.currentStep = 9;
logger.success(this.siteName, `步骤 9 完成`);
} 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})`);
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) {
// 忽略截图错误
}
}
// 清理浏览器缓存并关闭
if (this.page) {
try {
logger.info(this.siteName, ' → 清除浏览器缓存...');
const client = await this.page.target().createCDPSession();
await client.send('Network.clearBrowserCookies');
await client.send('Network.clearBrowserCache');
await client.send('Storage.clearDataForOrigin', {
origin: '*',
storageTypes: 'all'
});
await this.page.goto('https://windsurf.com', { waitUntil: 'domcontentloaded' });
await this.page.evaluate(() => {
try {
localStorage.clear();
sessionStorage.clear();
} catch (e) {}
});
await client.detach();
logger.success(this.siteName, ' → ✓ 浏览器缓存已清除');
} catch (e) {
logger.warn(this.siteName, ` → 清除缓存失败: ${e.message}`);
}
}
await this.closeBrowser();
throw error;
}
}
}
module.exports = WindsurfRegister;