This commit is contained in:
dengqichen 2025-11-17 16:37:32 +08:00
parent f0c9a0ef1d
commit 29d96742ac
2 changed files with 68 additions and 681 deletions

3
.env
View File

@ -1,6 +1,3 @@
# CapSolver API Key
CAPSOLVER_API_KEY=CAP-0FCDDA4906E87D9F4FF68EAECD34E320876FBA70E4F30EA1ADCD264EDB15E4BF
# AdsPower 指纹浏览器配置
ADSPOWER_USER_ID=k1728p8l
ADSPOWER_API_KEY=35de43696f6241f3df895f2f48777a99

View File

@ -17,7 +17,6 @@ const logger = require('../../../shared/logger');
const EmailVerificationService = require('../email-verification');
const { DEFAULT_CONFIG } = require('../config');
const CardGenerator = require('../../card-generator/generator');
const axios = require('axios');
class WindsurfRegister {
constructor() {
@ -31,14 +30,6 @@ class WindsurfRegister {
this.currentStep = 0;
this.accountData = null;
// 初始化 CapSolver自动解决 Cloudflare Turnstile
this.capsolverKey = process.env.CAPSOLVER_API_KEY || null;
if (this.capsolverKey) {
logger.success(this.siteName, '✓ CapSolver 自动化已启用99%+ 成功率)');
} else {
logger.warn(this.siteName, '⚠️ 未配置 CAPSOLVER_API_KEY将使用手动验证');
}
// 定义所有步骤
this.steps = [
{ id: 1, name: '填写基本信息', method: 'step1_fillBasicInfo' },
@ -316,16 +307,6 @@ class WindsurfRegister {
logger.warn(this.siteName, ` → 清除浏览器数据失败: ${e.message}`);
}
// 提示AdsPower 中需要手动安装 CapSolver 扩展
logger.info(this.siteName, '');
logger.info(this.siteName, '💡 提示:');
logger.info(this.siteName, ' AdsPower 指纹浏览器已启动');
if (this.capsolverKey) {
logger.info(this.siteName, ' 请确保已在 AdsPower 配置中安装 CapSolver 扩展');
logger.info(this.siteName, ' 扩展路径: extensions/capsolver');
}
logger.info(this.siteName, '');
logger.info(this.siteName, '等待浏览器完全准备...');
await this.human.randomDelay(2000, 3000);
@ -511,72 +492,41 @@ class WindsurfRegister {
await this.page.keyboard.press('Enter');
}
// 等待页面稳定(优化:减少等待时间)
await this.human.randomDelay(500, 1000);
// 等待页面稳定
await this.human.randomDelay(1000, 2000);
// ===== Cloudflare Turnstile 验证处理 =====
// 等待 Cloudflare Turnstile 验证完成(手动或其他方式)
logger.info(this.siteName, ' → 等待 Cloudflare Turnstile 验证...');
logger.info(this.siteName, ' → 请手动完成验证或等待自动处理...');
// 首先检查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'))
};
});
const startTime = Date.now();
const maxWait = 120000; // 最多等待120秒
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);
// 轮询检查按钮是否激活
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));
}
// 如果配置了 CapSolver 扩展,等待扩展自动处理验证
if (this.capsolverKey) {
logger.info(this.siteName, ' → 检测到 Cloudflare Turnstile 验证');
logger.info(this.siteName, ' → 等待 CapSolver 扩展处理(请确保已在 AdsPower 中安装扩展)...');
const startTime = Date.now();
let elapsed = 0;
const maxWait = 60000; // 最多等待60秒
// 轮询检查按钮是否激活(扩展完成验证后按钮会变绿)
while (elapsed < 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}`);
logger.success(this.siteName, ' → ✓ 按钮已激活,准备进入下一步');
break;
}
// 每10秒输出一次进度
if (elapsed > 0 && elapsed % 10000 === 0) {
logger.info(this.siteName, ` → 等待验证中... 已用时 ${elapsed/1000}`);
}
// 优化:减少轮询间隔,更快检测到完成状态
await new Promise(resolve => setTimeout(resolve, 500));
elapsed = Date.now() - startTime;
}
if (elapsed >= maxWait) {
logger.error(this.siteName, ' → ⚠️ 验证超时60秒可能需要手动处理');
throw new Error('Turnstile 验证超时');
}
if (Date.now() - startTime >= maxWait) {
logger.error(this.siteName, ' → ⚠️ 验证超时120秒');
throw new Error('Turnstile 验证超时');
}
// 点击 Continue 进入邮箱验证
@ -593,595 +543,13 @@ class WindsurfRegister {
logger.success(this.siteName, `步骤 2 完成`);
}
/**
* 使用 CapSolver 自动解决 Cloudflare Turnstile使用 node-capsolver
* @deprecated 此方法已废弃现在使用 CapSolver 浏览器扩展自动处理
* API 方式只能获取 token无法让 Turnstile checkbox 自动勾选
* 扩展方式可以直接操作页面元素实现真正的自动化
*/
async solveWithCapSolver() {
if (!this.capsolverKey) {
return false; // 未配置,返回 false
}
try {
logger.info(this.siteName, '[CapSolver] 开始自动解决 Cloudflare Turnstile...');
const websiteURL = this.page.url();
logger.info(this.siteName, `[CapSolver] 页面 URL: ${websiteURL}`);
// 先快速检查页面上是否真的有 Turnstile不等待
const hasTurnstile = await this.page.evaluate(() => {
return !!(
document.querySelector('iframe[src*="challenges.cloudflare.com"]') ||
document.querySelector('iframe[src*="turnstile"]') ||
document.querySelector('[data-sitekey]') ||
document.querySelector('input[name="cf-turnstile-response"]')
);
});
if (!hasTurnstile) {
logger.info(this.siteName, '[CapSolver] 当前页面没有 Turnstile跳过');
return false;
}
logger.info(this.siteName, '[CapSolver] 检测到 Turnstile开始处理...');
// 等待 Turnstile 完全加载(检查多种可能的选择器)
logger.info(this.siteName, '[CapSolver] 等待 Turnstile 完全加载...');
let turnstileLoaded = false;
// 尝试多种选择器(缩短超时时间)
const selectors = [
'iframe[src*="challenges.cloudflare.com"]', // Turnstile iframe
'iframe[title*="Turnstile"]', // Turnstile by title
'iframe[id^="cf-chl-widget"]', // Cloudflare challenge widget
'[id^="cf-chl-widget"]', // Turnstile container
'input[name="cf-turnstile-response"]' // Turnstile hidden input
];
for (const selector of selectors) {
try {
await this.page.waitForSelector(selector, { timeout: 3000 });
logger.info(this.siteName, `[CapSolver] ✓ 找到 Turnstile 元素: ${selector}`);
turnstileLoaded = true;
break;
} catch (e) {
// 继续尝试下一个选择器
}
}
if (turnstileLoaded) {
// 额外等待 3 秒,确保 Turnstile 完全初始化
await new Promise(resolve => setTimeout(resolve, 3000));
logger.info(this.siteName, '[CapSolver] ✓ Turnstile 初始化完成');
} else {
logger.warn(this.siteName, '[CapSolver] Turnstile 元素未完全加载,尝试继续');
await new Promise(resolve => setTimeout(resolve, 2000));
}
const sitekeyInfo = await this.page.evaluate(() => {
const info = { sitekey: null, method: null, widgetId: null };
// 方法 1: 从 iframe 中获取
const iframe = document.querySelector('iframe[src*="cloudflare"]');
if (iframe && iframe.src) {
const match = iframe.src.match(/sitekey=([^&]+)/);
if (match && match[1] !== 'explicit') {
info.sitekey = match[1];
info.method = 'iframe-src';
// 尝试获取 widget ID
const idMatch = iframe.id.match(/cf-chl-widget-(.+)/);
if (idMatch) info.widgetId = idMatch[1];
}
}
// 方法 2: 从 data-sitekey 属性获取
if (!info.sitekey) {
const turnstileDiv = document.querySelector('[data-sitekey]');
if (turnstileDiv) {
const key = turnstileDiv.getAttribute('data-sitekey');
if (key && key !== 'explicit') {
info.sitekey = key;
info.method = 'data-sitekey';
if (turnstileDiv.id) info.widgetId = turnstileDiv.id;
}
}
}
// 方法 3: 从页面 JavaScript 中查找
if (!info.sitekey) {
const scripts = Array.from(document.querySelectorAll('script'));
for (const script of scripts) {
if (script.textContent && script.textContent.includes('turnstile')) {
const match = script.textContent.match(/sitekey['":\s]+['"]([0-9x_A-Za-z-]+)['"]/);
if (match && match[1] !== 'explicit') {
info.sitekey = match[1];
info.method = 'script';
break;
}
}
}
}
// 默认使用已知的 Windsurf sitekey后备值
if (!info.sitekey) {
info.sitekey = '0x4AAAAAAA447Bur1xJStKg5';
info.method = 'fallback';
}
return info;
});
const sitekey = sitekeyInfo.sitekey;
logger.info(this.siteName, `[CapSolver] Sitekey: ${sitekey} (来源: ${sitekeyInfo.method})`);
if (sitekeyInfo.widgetId) {
logger.info(this.siteName, `[CapSolver] Widget ID: ${sitekeyInfo.widgetId}`);
}
// 严格按照官方文档创建任务
const payload = {
clientKey: this.capsolverKey,
task: {
type: 'AntiTurnstileTaskProxyLess',
websiteKey: sitekey,
websiteURL: websiteURL,
metadata: {
action: '' // optional
}
}
};
logger.info(this.siteName, `[CapSolver] 创建任务...`);
const res = await axios.post("https://api.capsolver.com/createTask", payload);
const task_id = res.data.taskId;
if (!task_id) {
logger.error(this.siteName, `[CapSolver] 创建任务失败: ${JSON.stringify(res.data)}`);
return false;
}
logger.info(this.siteName, `[CapSolver] 任务ID: ${task_id}`);
// 轮询获取结果
let solution = null;
while (true) {
await new Promise(resolve => setTimeout(resolve, 1000)); // 1秒延迟
const getResultPayload = { clientKey: this.capsolverKey, taskId: task_id };
const resp = await axios.post("https://api.capsolver.com/getTaskResult", getResultPayload);
const status = resp.data.status;
if (status === "ready") {
solution = resp.data.solution;
break;
}
if (status === "failed" || resp.data.errorId) {
logger.error(this.siteName, `[CapSolver] 解决失败: ${JSON.stringify(resp.data)}`);
return false;
}
}
if (!solution || !solution.token) {
logger.error(this.siteName, `[CapSolver] 未获取到solution`);
return false;
}
const token = solution.token;
const userAgent = solution.userAgent;
logger.success(this.siteName, `[CapSolver] ✓ 获取到token: ${token.substring(0, 20)}...`);
logger.info(this.siteName, `[CapSolver] UserAgent: ${userAgent}`);
// 先调试:查看页面上的 Turnstile 配置
const debugInfo = await this.page.evaluate(() => {
const info = {
hasTurnstile: !!window.turnstile,
hasCallback: false,
callbackName: null,
widgetConfig: null
};
// 查找回调函数
const scripts = Array.from(document.querySelectorAll('script'));
for (const script of scripts) {
if (script.textContent && script.textContent.includes('turnstile')) {
// 尝试找到 callback 配置
const callbackMatch = script.textContent.match(/callback['":\s]*['"]?(\w+)['"]?/);
if (callbackMatch) {
info.hasCallback = true;
info.callbackName = callbackMatch[1];
}
}
}
return info;
});
logger.info(this.siteName, `[CapSolver] 调试信息: ${JSON.stringify(debugInfo)}`);
// 3. 注入 token 到页面
logger.info(this.siteName, '[CapSolver] 等待 input 元素...');
await this.page.waitForSelector('input[name="cf-turnstile-response"]', { timeout: 10000 });
logger.info(this.siteName, `[CapSolver] 注入 token 到页面 (长度: ${token.length})...`);
// 步骤1: 注入 token 并触发事件
await this.page.evaluate((token) => {
const input = document.querySelector('input[name="cf-turnstile-response"]');
if (input) {
input.value = token;
// 触发各种事件以通知页面
['input', 'change', 'blur'].forEach(eventType => {
input.dispatchEvent(new Event(eventType, { bubbles: true }));
});
}
}, token);
logger.success(this.siteName, '[CapSolver] ✓ Token 已注入到 hidden input');
// 步骤2: 查找并触发 Turnstile 回调函数
logger.info(this.siteName, '[CapSolver] 尝试触发 Turnstile 回调...');
const callbackResult = await this.page.evaluate((token) => {
const results = [];
// 方法A: 查找 React 组件的回调
if (window.cf__reactTurnstileOnLoad && typeof window.cf__reactTurnstileOnLoad === 'function') {
try {
window.cf__reactTurnstileOnLoad(token);
results.push('✓ cf__reactTurnstileOnLoad');
} catch (e) {
results.push(`✗ cf__reactTurnstileOnLoad: ${e.message}`);
}
}
// 方法B: 从 DOM 元素获取回调名称
const turnstileDiv = document.querySelector('[data-callback]');
if (turnstileDiv) {
const callbackName = turnstileDiv.getAttribute('data-callback');
if (callbackName && window[callbackName] && typeof window[callbackName] === 'function') {
try {
window[callbackName](token);
results.push(`✓ data-callback: ${callbackName}`);
} catch (e) {
results.push(`${callbackName}: ${e.message}`);
}
}
}
// 方法C: 使用 turnstile API (如果存在)
if (window.turnstile) {
if (typeof window.turnstile.reset === 'function') {
try {
// 查找 widget ID
const widgets = document.querySelectorAll('[id^="cf-chl-widget"]');
widgets.forEach((widget, index) => {
const widgetId = widget.id;
try {
window.turnstile.reset(widgetId);
results.push(`✓ reset widget: ${widgetId}`);
} catch (e) {
results.push(`✗ reset ${widgetId}: ${e.message}`);
}
});
} catch (e) {
results.push(`✗ turnstile.reset: ${e.message}`);
}
}
if (typeof window.turnstile.execute === 'function') {
try {
window.turnstile.execute();
results.push('✓ turnstile.execute()');
} catch (e) {
results.push(`✗ turnstile.execute: ${e.message}`);
}
}
}
// 方法D: 遍历 window 查找包含 turnstile 的函数
for (let key in window) {
if (key.toLowerCase().includes('turnstile') &&
typeof window[key] === 'function' &&
!results.some(r => r.includes(key))) {
try {
window[key](token);
results.push(`✓ window.${key}`);
} catch (e) {
// 忽略错误,继续尝试
}
}
}
return results;
}, token);
if (callbackResult.length > 0) {
logger.info(this.siteName, `[CapSolver] 回调触发结果:`);
callbackResult.forEach(result => {
logger.info(this.siteName, ` ${result}`);
});
} else {
logger.warn(this.siteName, '[CapSolver] ⚠️ 未找到任何回调函数');
}
// 等待一下让回调处理
await new Promise(resolve => setTimeout(resolve, 1000));
// 步骤3: 强制激活按钮(后备方案)
logger.info(this.siteName, '[CapSolver] 检查并激活提交按钮...');
const buttonActivated = await this.page.evaluate(() => {
const buttons = Array.from(document.querySelectorAll('button'));
let activated = false;
buttons.forEach(btn => {
if (btn.textContent.trim() === 'Continue' ||
btn.textContent.trim() === 'Submit') {
if (btn.disabled) {
// 移除 disabled 属性
btn.disabled = false;
btn.removeAttribute('disabled');
// 移除可能的 disabled class
btn.classList.remove('disabled');
activated = true;
}
}
});
return activated;
});
if (buttonActivated) {
logger.success(this.siteName, '[CapSolver] ✓ 已强制激活按钮');
} else {
logger.info(this.siteName, '[CapSolver] 按钮已经是激活状态');
}
// 步骤4: 等待验证完成确认
logger.info(this.siteName, '[CapSolver] 等待验证完成确认...');
try {
await this.page.waitForFunction(
() => {
// 检查1: 按钮必须激活
const button = document.querySelector('button');
const buttonEnabled = button && !button.disabled &&
(button.textContent.trim() === 'Continue' ||
button.textContent.trim() === 'Submit');
// 检查2: hidden input 必须有值
const input = document.querySelector('[name="cf-turnstile-response"]');
const hasToken = input && input.value && input.value.length > 0;
// 检查3: iframe 状态(如果存在)
const iframe = document.querySelector('iframe[src*="challenges.cloudflare.com"]');
const iframeOk = !iframe ||
iframe.getAttribute('data-state') === 'success' ||
iframe.getAttribute('data-success') === 'true';
// 至少满足:按钮激活 + 有token
return buttonEnabled && hasToken;
},
{ timeout: 10000, polling: 500 }
);
logger.success(this.siteName, '[CapSolver] ✓ 验证完成确认通过');
} catch (e) {
logger.warn(this.siteName, '[CapSolver] 验证完成检测超时,但将继续尝试...');
// 输出调试信息
const debugInfo = await this.page.evaluate(() => {
const button = document.querySelector('button');
const input = document.querySelector('[name="cf-turnstile-response"]');
return {
buttonDisabled: button?.disabled,
buttonText: button?.textContent.trim(),
hasToken: !!input?.value,
tokenLength: input?.value?.length || 0
};
});
logger.info(this.siteName, `[CapSolver] 当前状态: ${JSON.stringify(debugInfo)}`);
// 如果按钮已激活且有token认为成功
if (!debugInfo.buttonDisabled && debugInfo.hasToken) {
logger.success(this.siteName, '[CapSolver] ✓ 状态检查通过,继续执行');
} else {
logger.warn(this.siteName, '[CapSolver] 等待 10 秒供手动处理...');
await new Promise(resolve => setTimeout(resolve, 10000));
}
}
// 5. 点击 Continue 按钮
await this.human.randomDelay(500, 1000);
const button = await this.page.$('button');
if (button) {
await button.click();
logger.success(this.siteName, '[CapSolver] ✓ 已点击 Continue 按钮');
// 等待页面跳转
await this.human.randomDelay(3000, 4000);
return true;
} else {
logger.error(this.siteName, '[CapSolver] 未找到 Continue 按钮');
return false;
}
} catch (error) {
logger.error(this.siteName, `[CapSolver] 解决失败: ${error.message}`);
return false;
}
}
/**
* Cloudflare Turnstile验证步骤2.5- 使用 CapSolver 扩展自动处理
*/
async handleCloudflareVerification() {
// 如果安装了 CapSolver 扩展,等待其自动处理
if (this.capsolverKey) {
logger.info(this.siteName, '[CapSolver扩展] 检测到 Cloudflare Turnstile 验证');
logger.info(this.siteName, '[CapSolver扩展] 扩展将自动:检测 → 调用API → 勾选checkbox');
const startTime = Date.now();
// 按照官方文档:等待扩展自动处理(检查按钮是否激活)
try {
// 轮询检查按钮状态,提供实时反馈
const checkInterval = 2000; // 每2秒检查一次
let elapsed = 0;
while (elapsed < 60000) { // 最多60秒
const buttonEnabled = await this.page.evaluate(() => {
const button = document.querySelector('button');
return button && !button.disabled;
});
if (buttonEnabled) {
const duration = ((Date.now() - startTime) / 1000).toFixed(1);
logger.success(this.siteName, `[CapSolver扩展] ✓ Turnstile 验证完成!耗时: ${duration}`);
logger.success(this.siteName, '[CapSolver扩展] ✓ Checkbox 已自动勾选,按钮已激活');
return 'passed';
}
// 每10秒输出一次进度
if (elapsed % 10000 === 0 && elapsed > 0) {
logger.info(this.siteName, `[CapSolver扩展] 等待中... 已用时 ${elapsed/1000}`);
}
await new Promise(resolve => setTimeout(resolve, checkInterval));
elapsed += checkInterval;
}
// 超时
logger.error(this.siteName, '[CapSolver扩展] ⚠️ 60秒超时验证未完成');
logger.warn(this.siteName, '可能原因:');
logger.warn(this.siteName, ' 1. 扩展未正确加载(检查浏览器扩展图标)');
logger.warn(this.siteName, ' 2. API Key 未配置或无效');
logger.warn(this.siteName, ' 3. CapSolver 余额不足');
logger.warn(this.siteName, ' 4. 网络连接问题');
logger.warn(this.siteName, '回退到手动模式,请手动点击 Turnstile checkbox...');
} catch (e) {
logger.error(this.siteName, `[CapSolver扩展] 错误: ${e.message}`);
}
}
// 回退到手动模式
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: 邮箱验证
*/
async step3_emailVerification() {
logger.info(this.siteName, `[步骤 3/${this.getTotalSteps()}] 邮箱验证`);
// Cloudflare Turnstile 验证已在步骤2中通过 CapSolver 扩展自动完成
// Cloudflare Turnstile 验证已在步骤2中完成
logger.info(this.siteName, ' → Turnstile 验证已通过,开始邮箱验证...');
try {
@ -1241,39 +609,61 @@ class WindsurfRegister {
// 输入完6位验证码后页面会自动提交
logger.info(this.siteName, ' → 等待邮箱验证完成并跳转到问卷页面...');
logger.info(this.siteName, ' → 将持续等待直到跳转成功(无时间限制)...');
// 等待页面跳转到 /account/onboarding?page=source
// 无限等待页面跳转到 /account/onboarding?page=source
const startTime = Date.now();
const maxWait = 20000; // 最多等待20秒
let registrationComplete = false;
let hasClickedButton = false;
while (Date.now() - startTime < maxWait) {
while (!registrationComplete) {
const currentUrl = this.page.url();
// 精确检查:必须是 /account/onboarding?page=source
// 检查1: 页面是否已经跳转成功(自动跳转成功)
if (currentUrl.includes('/account/onboarding') && currentUrl.includes('page=source')) {
registrationComplete = true;
logger.success(this.siteName, ` → ✓ 邮箱验证成功!已跳转到问卷页面`);
const totalTime = ((Date.now() - startTime) / 1000).toFixed(1);
logger.success(this.siteName, ` → ✓ 邮箱验证成功!已跳转到问卷页面 (耗时: ${totalTime}秒)`);
logger.info(this.siteName, ` → 当前页面: ${currentUrl}`);
break;
}
// 每2秒输出一次进度
// 检查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 % 2000 === 0) {
logger.info(this.siteName, ` → 等待中... 已用时 ${elapsed/1000}`);
if (elapsed > 0 && elapsed % 5000 === 0) {
logger.info(this.siteName, ` → 等待中... 已用时 ${(elapsed/1000).toFixed(0)}`);
}
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);