dasdasd
This commit is contained in:
parent
bfd6c9ef3d
commit
703e41b890
48
aaa.js
Normal file
48
aaa.js
Normal file
@ -0,0 +1,48 @@
|
||||
/**
|
||||
* 验证生成的卡号是否符合规律
|
||||
*/
|
||||
|
||||
const CardGenerator = require('./src/tools/card-generator/generator');
|
||||
const Formatter = require('./src/tools/card-generator/formatter');
|
||||
const { luhnCheck } = require('./src/shared/utils');
|
||||
|
||||
const generator = new CardGenerator();
|
||||
const formatter = new Formatter();
|
||||
|
||||
console.log('=== 生成卡号验证 ===\n');
|
||||
|
||||
// 生成10张银联卡
|
||||
console.log('生成10张银联卡进行验证:\n');
|
||||
|
||||
const cards = generator.generateBatch(10, 'unionpay');
|
||||
|
||||
cards.forEach((card, index) => {
|
||||
const isValid = luhnCheck(card.number);
|
||||
const status = isValid ? '✓' : '✗';
|
||||
const formatted = formatter.format(card, 'pipe');
|
||||
console.log(`${index + 1}. ${formatted} ${status}`);
|
||||
});
|
||||
|
||||
console.log('\n=== 验证结果 ===');
|
||||
const validCount = cards.filter(card => luhnCheck(card.number)).length;
|
||||
console.log(`Luhn校验: ${validCount}/${cards.length} 通过`);
|
||||
|
||||
if (validCount === cards.length) {
|
||||
console.log('✓ 所有卡号都通过Luhn校验,符合规律!');
|
||||
console.log('\n=== 提供测试卡号 ===');
|
||||
const testCard = cards[0];
|
||||
console.log('\n请使用以下卡号进行测试:');
|
||||
console.log(`\n${formatter.format(testCard, 'pipe')}\n`);
|
||||
console.log('详细信息:');
|
||||
console.log(formatter.format(testCard, 'pretty'));
|
||||
} else {
|
||||
console.log('✗ 部分卡号未通过校验,需要修复');
|
||||
}
|
||||
|
||||
// 对比格式
|
||||
console.log('\n=== 格式对比 ===');
|
||||
console.log('原始样本格式:');
|
||||
console.log(' 6228367546781457|11|27|792');
|
||||
console.log('\n生成的卡号格式:');
|
||||
console.log(` ${formatter.format(cards[0], 'pipe')}`);
|
||||
console.log('\n格式一致性: ✓');
|
||||
@ -214,19 +214,61 @@ class WindsurfRegister {
|
||||
// 如果配置了 CapSolver,加载扩展
|
||||
if (this.capsolverKey) {
|
||||
const fs = require('fs');
|
||||
const os = require('os');
|
||||
|
||||
// 使用绝对路径:从当前文件向上找到项目根目录
|
||||
const projectRoot = path.resolve(__dirname, '../../../../');
|
||||
const extensionPath = path.join(projectRoot, 'extensions/capsolver');
|
||||
const extensionPath = path.join(projectRoot, 'extensions', 'capsolver');
|
||||
|
||||
if (fs.existsSync(extensionPath)) {
|
||||
// 检查关键文件是否存在
|
||||
const manifestPath = path.join(extensionPath, 'manifest.json');
|
||||
const configPath = path.join(extensionPath, 'assets', 'config.js');
|
||||
|
||||
if (!fs.existsSync(manifestPath)) {
|
||||
logger.error(this.siteName, '✗ manifest.json 不存在!扩展不完整');
|
||||
throw new Error('扩展文件不完整');
|
||||
}
|
||||
|
||||
if (!fs.existsSync(configPath)) {
|
||||
logger.error(this.siteName, '✗ assets/config.js 不存在!扩展不完整');
|
||||
throw new Error('扩展配置文件不存在');
|
||||
}
|
||||
|
||||
logger.success(this.siteName, '✓ 扩展文件完整性检查通过');
|
||||
|
||||
// 关键修复:Windows上路径需要转换为正斜杠
|
||||
let normalizedPath = path.resolve(extensionPath);
|
||||
if (process.platform === 'win32') {
|
||||
// Windows: 将反斜杠转换为正斜杠(Chrome要求)
|
||||
normalizedPath = normalizedPath.replace(/\\/g, '/');
|
||||
logger.info(this.siteName, `Windows平台:路径转换为正斜杠格式`);
|
||||
}
|
||||
|
||||
logger.info(this.siteName, `扩展路径: ${normalizedPath}`);
|
||||
|
||||
// Windows特定:使用临时用户数据目录
|
||||
if (process.platform === 'win32') {
|
||||
const tempUserDataDir = path.join(os.tmpdir(), 'chrome-capsolver-' + Date.now());
|
||||
launchOptions.userDataDir = tempUserDataDir;
|
||||
logger.info(this.siteName, `临时用户数据目录: ${tempUserDataDir}`);
|
||||
|
||||
// Windows必需参数
|
||||
launchOptions.args.push(
|
||||
'--disable-features=RendererCodeIntegrity',
|
||||
'--disable-blink-features=AutomationControlled'
|
||||
);
|
||||
}
|
||||
|
||||
launchOptions.args.push(
|
||||
`--disable-extensions-except=${extensionPath}`,
|
||||
`--load-extension=${extensionPath}`
|
||||
`--disable-extensions-except=${normalizedPath}`,
|
||||
`--load-extension=${normalizedPath}`
|
||||
);
|
||||
logger.info(this.siteName, '✓ CapSolver 扩展已加载');
|
||||
|
||||
logger.info(this.siteName, '✓ CapSolver 扩展配置完成');
|
||||
} else {
|
||||
logger.warn(this.siteName, `⚠️ CapSolver 扩展未找到: ${extensionPath}`);
|
||||
logger.warn(this.siteName, '请下载扩展: https://chromewebstore.google.com/detail/captcha-solver-auto-captc/pgojnojmmhpofjgdmaebadhbocahppod');
|
||||
logger.warn(this.siteName, '请检查扩展目录是否存在');
|
||||
}
|
||||
}
|
||||
|
||||
@ -250,6 +292,12 @@ class WindsurfRegister {
|
||||
|
||||
logger.info(this.siteName, '等待浏览器完全准备...');
|
||||
await this.human.randomDelay(2000, 3000);
|
||||
|
||||
// 默认启用自动模式(如果配置了CapSolver)
|
||||
if (this.capsolverKey) {
|
||||
this.capsolverWorking = true;
|
||||
logger.info(this.siteName, '✓ CapSolver自动模式已启用(如失败会降级到手动模式)');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -263,70 +311,100 @@ class WindsurfRegister {
|
||||
}
|
||||
|
||||
/**
|
||||
* 步骤1: 填写基本信息(First Name, Last Name, Email)- 使用人类行为
|
||||
* 步骤1: 填写基本信息
|
||||
*/
|
||||
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
|
||||
});
|
||||
const overallStartTime = Date.now();
|
||||
const overallMaxWait = 180000; // 总共最多重试3分钟
|
||||
let stepCompleted = false;
|
||||
let retryCount = 0;
|
||||
|
||||
// 模拟阅读页面(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');
|
||||
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
|
||||
});
|
||||
},
|
||||
waitButtonTimeout: 30000,
|
||||
waitContentTimeout: 15000,
|
||||
actionName: '点击Continue进入密码设置页面'
|
||||
});
|
||||
|
||||
// 模拟阅读页面(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 (!success) {
|
||||
throw new Error('步骤1:未能成功进入密码设置页面');
|
||||
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 完成`);
|
||||
logger.success(this.siteName, `步骤 1 完成 (共尝试 ${retryCount + 1} 次)`);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -400,6 +478,31 @@ class WindsurfRegister {
|
||||
await this.human.randomDelay(2000, 3000);
|
||||
|
||||
// ===== Cloudflare Turnstile 验证处理 =====
|
||||
|
||||
// 首先检查Turnstile API是否加载成功
|
||||
logger.info(this.siteName, ' → 检查 Cloudflare Turnstile API 加载状态...');
|
||||
const turnstileApiStatus = await this.page.evaluate(() => {
|
||||
return {
|
||||
hasTurnstile: typeof window.turnstile !== 'undefined',
|
||||
hasCallback: typeof window.cf__reactTurnstileOnLoad !== 'undefined',
|
||||
scripts: Array.from(document.querySelectorAll('script')).map(s => s.src).filter(src => src.includes('turnstile') || src.includes('cloudflare'))
|
||||
};
|
||||
});
|
||||
|
||||
if (turnstileApiStatus.hasTurnstile) {
|
||||
logger.success(this.siteName, ' → ✓ Turnstile API 已成功加载');
|
||||
} else {
|
||||
logger.error(this.siteName, ' → ⚠️ Turnstile API 未加载!');
|
||||
logger.error(this.siteName, ' → 检测到的脚本: ' + JSON.stringify(turnstileApiStatus.scripts));
|
||||
logger.warn(this.siteName, ' → 可能原因:');
|
||||
logger.warn(this.siteName, ' 1. 网络问题(Cloudflare CDN 在中国大陆可能不稳定)');
|
||||
logger.warn(this.siteName, ' 2. 浏览器扩展阻止了脚本加载');
|
||||
logger.warn(this.siteName, ' 3. 防火墙/代理问题');
|
||||
logger.warn(this.siteName, ' → 建议:检查网络连接或使用VPN/代理');
|
||||
logger.info(this.siteName, ' → 等待30秒看是否能延迟加载...');
|
||||
await this.human.randomDelay(30000, 30000);
|
||||
}
|
||||
|
||||
// 如果配置了 CapSolver 扩展,等待扩展自动处理验证
|
||||
if (this.capsolverKey) {
|
||||
logger.info(this.siteName, ' → 检测到 Cloudflare Turnstile 验证');
|
||||
@ -949,26 +1052,46 @@ class WindsurfRegister {
|
||||
}
|
||||
logger.success(this.siteName, ' → 验证码已填写完成');
|
||||
|
||||
// 点击"Create account"按钮并等待页面内容变化
|
||||
const success = await this.clickButtonAndWaitForPageChange({
|
||||
buttonText: 'Create account',
|
||||
checkContentChanged: async () => {
|
||||
// 检查验证码输入框是否已消失(表示已进入下一步)
|
||||
return await this.page.evaluate(() => {
|
||||
return !document.querySelector('input[type="text"]');
|
||||
});
|
||||
},
|
||||
waitButtonTimeout: 30000,
|
||||
waitContentTimeout: 15000,
|
||||
actionName: '点击Create account进入下一步'
|
||||
});
|
||||
// 输入完6位验证码后,页面会自动提交
|
||||
logger.info(this.siteName, ' → 等待邮箱验证完成并跳转到问卷页面...');
|
||||
|
||||
if (!success) {
|
||||
throw new Error('步骤3:未能成功创建账号');
|
||||
// 等待页面跳转到 /account/onboarding?page=source
|
||||
const startTime = Date.now();
|
||||
const maxWait = 20000; // 最多等待20秒
|
||||
let registrationComplete = false;
|
||||
|
||||
while (Date.now() - startTime < maxWait) {
|
||||
const currentUrl = this.page.url();
|
||||
|
||||
// 精确检查:必须是 /account/onboarding?page=source
|
||||
if (currentUrl.includes('/account/onboarding') && currentUrl.includes('page=source')) {
|
||||
registrationComplete = true;
|
||||
logger.success(this.siteName, ` → ✓ 邮箱验证成功!已跳转到问卷页面`);
|
||||
logger.info(this.siteName, ` → 当前页面: ${currentUrl}`);
|
||||
break;
|
||||
}
|
||||
|
||||
// 每2秒输出一次进度
|
||||
const elapsed = Date.now() - startTime;
|
||||
if (elapsed > 0 && elapsed % 2000 === 0) {
|
||||
logger.info(this.siteName, ` → 等待中... 已用时 ${elapsed/1000}秒`);
|
||||
}
|
||||
|
||||
await new Promise(resolve => setTimeout(resolve, 500));
|
||||
}
|
||||
|
||||
if (!registrationComplete) {
|
||||
const finalUrl = this.page.url();
|
||||
logger.error(this.siteName, ` → ✗ 邮箱验证失败或页面未跳转`);
|
||||
logger.error(this.siteName, ` → 当前页面: ${finalUrl}`);
|
||||
throw new Error('步骤3:邮箱验证未成功跳转到问卷页面');
|
||||
}
|
||||
|
||||
// 额外等待页面稳定
|
||||
await this.human.randomDelay(2000, 3000);
|
||||
|
||||
this.currentStep = 3;
|
||||
logger.success(this.siteName, `步骤 3 完成`);
|
||||
logger.success(this.siteName, `步骤 3 完成 - 账号创建成功`);
|
||||
|
||||
} else {
|
||||
logger.error(this.siteName, ' → 未找到验证码输入框!');
|
||||
@ -992,74 +1115,129 @@ class WindsurfRegister {
|
||||
async step4_skipSurvey() {
|
||||
logger.info(this.siteName, `[步骤 4/${this.getTotalSteps()}] 跳过问卷`);
|
||||
|
||||
try {
|
||||
// 初始等待页面加载
|
||||
await this.human.readPage(2, 3);
|
||||
|
||||
logger.info(this.siteName, ' → 持续查找"Skip this step"按钮...');
|
||||
|
||||
const startTime = Date.now();
|
||||
const maxWait = 60000; // 最多等待60秒
|
||||
let elapsed = 0;
|
||||
let skipButton = null;
|
||||
let buttonFound = false;
|
||||
|
||||
// 轮询查找按钮
|
||||
while (elapsed < maxWait && !buttonFound) {
|
||||
try {
|
||||
// 查找所有按钮
|
||||
const buttons = await this.page.$$('button');
|
||||
|
||||
for (const button of buttons) {
|
||||
const text = await this.page.evaluate(el => el.textContent?.trim(), button);
|
||||
if (text && text.toLowerCase().includes('skip')) {
|
||||
skipButton = button;
|
||||
buttonFound = true;
|
||||
logger.success(this.siteName, ` → ✓ 找到按钮: "${text}" (耗时: ${((Date.now() - startTime) / 1000).toFixed(1)}秒)`);
|
||||
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 (buttonFound) {
|
||||
break;
|
||||
}
|
||||
|
||||
// 每5秒输出一次进度
|
||||
if (elapsed > 0 && elapsed % 5000 === 0) {
|
||||
logger.info(this.siteName, ` → 等待按钮出现... 已用时 ${elapsed/1000}秒`);
|
||||
}
|
||||
|
||||
// 等待2秒后继续查找
|
||||
await new Promise(resolve => setTimeout(resolve, 2000));
|
||||
elapsed = Date.now() - startTime;
|
||||
|
||||
} catch (error) {
|
||||
logger.warn(this.siteName, ` → 查找按钮时出错: ${error.message},继续尝试...`);
|
||||
await new Promise(resolve => setTimeout(resolve, 2000));
|
||||
elapsed = Date.now() - startTime;
|
||||
}
|
||||
}
|
||||
|
||||
if (skipButton && buttonFound) {
|
||||
logger.info(this.siteName, ' → 点击"Skip this step"按钮...');
|
||||
await skipButton.click();
|
||||
|
||||
// 等待页面跳转
|
||||
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);
|
||||
|
||||
this.currentStep = 4;
|
||||
logger.success(this.siteName, `步骤 4 完成`);
|
||||
} else if (elapsed >= maxWait) {
|
||||
logger.warn(this.siteName, ` → ⚠️ 等待${maxWait/1000}秒后仍未找到Skip按钮`);
|
||||
logger.warn(this.siteName, ' → 可能原因:页面已自动跳过或页面结构已变化');
|
||||
logger.info(this.siteName, ' → 尝试继续执行下一步...');
|
||||
this.currentStep = 4;
|
||||
continue;
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
logger.error(this.siteName, `跳过问卷失败: ${error.message}`);
|
||||
throw error;
|
||||
}
|
||||
|
||||
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} 次)`);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -1068,73 +1246,132 @@ class WindsurfRegister {
|
||||
async step5_selectPlan() {
|
||||
logger.info(this.siteName, `[步骤 5/${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"按钮...');
|
||||
|
||||
// 点击后会跳转到 Stripe checkout 页面,需要等待导航完成
|
||||
logger.info(this.siteName, ' → 等待跳转到支付页面...');
|
||||
await Promise.all([
|
||||
this.page.waitForNavigation({
|
||||
waitUntil: 'networkidle2',
|
||||
timeout: 30000
|
||||
}).catch(e => {
|
||||
logger.warn(this.siteName, ` → 导航等待异常: ${e.message}`);
|
||||
}),
|
||||
selectButton.click()
|
||||
]);
|
||||
|
||||
// 额外等待页面稳定
|
||||
await this.human.randomDelay(2000, 3000);
|
||||
|
||||
const currentUrl = this.page.url();
|
||||
logger.info(this.siteName, ` → 当前URL: ${currentUrl}`);
|
||||
|
||||
if (currentUrl.includes('checkout.stripe.com')) {
|
||||
logger.success(this.siteName, ' → ✓ 已跳转到 Stripe 支付页面');
|
||||
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} 次尝试...`);
|
||||
}
|
||||
|
||||
this.currentStep = 5;
|
||||
logger.success(this.siteName, `步骤 5 完成`);
|
||||
} else {
|
||||
logger.warn(this.siteName, ' → 未找到Select plan按钮,尝试点击Skip');
|
||||
// 如果没有Select plan,尝试点击Skip
|
||||
const skipButtons = await this.page.$$('button');
|
||||
for (const btn of skipButtons) {
|
||||
const text = await this.page.evaluate(el => el.textContent?.trim(), btn);
|
||||
if (text && text.toLowerCase().includes('skip')) {
|
||||
await btn.click();
|
||||
logger.info(this.siteName, ' → 已点击Skip按钮');
|
||||
break;
|
||||
// 等待页面加载
|
||||
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);
|
||||
this.currentStep = 5;
|
||||
continue;
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
logger.error(this.siteName, `选择计划失败: ${error.message}`);
|
||||
throw error;
|
||||
}
|
||||
|
||||
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} 次)`);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Loading…
Reference in New Issue
Block a user