dasdasd
This commit is contained in:
parent
f0c9a0ef1d
commit
29d96742ac
3
.env
3
.env
@ -1,6 +1,3 @@
|
|||||||
# CapSolver API Key
|
|
||||||
CAPSOLVER_API_KEY=CAP-0FCDDA4906E87D9F4FF68EAECD34E320876FBA70E4F30EA1ADCD264EDB15E4BF
|
|
||||||
|
|
||||||
# AdsPower 指纹浏览器配置
|
# AdsPower 指纹浏览器配置
|
||||||
ADSPOWER_USER_ID=k1728p8l
|
ADSPOWER_USER_ID=k1728p8l
|
||||||
ADSPOWER_API_KEY=35de43696f6241f3df895f2f48777a99
|
ADSPOWER_API_KEY=35de43696f6241f3df895f2f48777a99
|
||||||
|
|||||||
@ -17,7 +17,6 @@ const logger = require('../../../shared/logger');
|
|||||||
const EmailVerificationService = require('../email-verification');
|
const EmailVerificationService = require('../email-verification');
|
||||||
const { DEFAULT_CONFIG } = require('../config');
|
const { DEFAULT_CONFIG } = require('../config');
|
||||||
const CardGenerator = require('../../card-generator/generator');
|
const CardGenerator = require('../../card-generator/generator');
|
||||||
const axios = require('axios');
|
|
||||||
|
|
||||||
class WindsurfRegister {
|
class WindsurfRegister {
|
||||||
constructor() {
|
constructor() {
|
||||||
@ -31,14 +30,6 @@ class WindsurfRegister {
|
|||||||
this.currentStep = 0;
|
this.currentStep = 0;
|
||||||
this.accountData = null;
|
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 = [
|
this.steps = [
|
||||||
{ id: 1, name: '填写基本信息', method: 'step1_fillBasicInfo' },
|
{ id: 1, name: '填写基本信息', method: 'step1_fillBasicInfo' },
|
||||||
@ -316,16 +307,6 @@ class WindsurfRegister {
|
|||||||
logger.warn(this.siteName, ` → 清除浏览器数据失败: ${e.message}`);
|
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, '等待浏览器完全准备...');
|
logger.info(this.siteName, '等待浏览器完全准备...');
|
||||||
await this.human.randomDelay(2000, 3000);
|
await this.human.randomDelay(2000, 3000);
|
||||||
|
|
||||||
@ -511,72 +492,41 @@ class WindsurfRegister {
|
|||||||
await this.page.keyboard.press('Enter');
|
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是否加载成功
|
const startTime = Date.now();
|
||||||
logger.info(this.siteName, ' → 检查 Cloudflare Turnstile API 加载状态...');
|
const maxWait = 120000; // 最多等待120秒
|
||||||
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 已成功加载');
|
while (Date.now() - startTime < maxWait) {
|
||||||
} else {
|
const buttonEnabled = await this.page.evaluate(() => {
|
||||||
logger.error(this.siteName, ' → ⚠️ Turnstile API 未加载!');
|
const button = document.querySelector('button');
|
||||||
logger.error(this.siteName, ' → 检测到的脚本: ' + JSON.stringify(turnstileApiStatus.scripts));
|
return button && !button.disabled && button.textContent.trim() === 'Continue';
|
||||||
logger.warn(this.siteName, ' → 可能原因:');
|
});
|
||||||
logger.warn(this.siteName, ' 1. 网络问题(Cloudflare CDN 在中国大陆可能不稳定)');
|
|
||||||
logger.warn(this.siteName, ' 2. 浏览器扩展阻止了脚本加载');
|
if (buttonEnabled) {
|
||||||
logger.warn(this.siteName, ' 3. 防火墙/代理问题');
|
const duration = ((Date.now() - startTime) / 1000).toFixed(1);
|
||||||
logger.warn(this.siteName, ' → 建议:检查网络连接或使用VPN/代理');
|
logger.success(this.siteName, ` → ✓ Turnstile 验证完成!耗时: ${duration}秒`);
|
||||||
logger.info(this.siteName, ' → 等待30秒看是否能延迟加载...');
|
break;
|
||||||
await this.human.randomDelay(30000, 30000);
|
}
|
||||||
|
|
||||||
|
// 每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 (Date.now() - startTime >= maxWait) {
|
||||||
if (this.capsolverKey) {
|
logger.error(this.siteName, ' → ⚠️ 验证超时(120秒)');
|
||||||
logger.info(this.siteName, ' → 检测到 Cloudflare Turnstile 验证');
|
throw new Error('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 验证超时');
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 点击 Continue 进入邮箱验证
|
// 点击 Continue 进入邮箱验证
|
||||||
@ -593,595 +543,13 @@ class WindsurfRegister {
|
|||||||
logger.success(this.siteName, `步骤 2 完成`);
|
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: 邮箱验证
|
* 步骤3: 邮箱验证
|
||||||
*/
|
*/
|
||||||
async step3_emailVerification() {
|
async step3_emailVerification() {
|
||||||
logger.info(this.siteName, `[步骤 3/${this.getTotalSteps()}] 邮箱验证`);
|
logger.info(this.siteName, `[步骤 3/${this.getTotalSteps()}] 邮箱验证`);
|
||||||
|
|
||||||
// Cloudflare Turnstile 验证已在步骤2中通过 CapSolver 扩展自动完成
|
// Cloudflare Turnstile 验证已在步骤2中完成
|
||||||
logger.info(this.siteName, ' → Turnstile 验证已通过,开始邮箱验证...');
|
logger.info(this.siteName, ' → Turnstile 验证已通过,开始邮箱验证...');
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@ -1241,39 +609,61 @@ class WindsurfRegister {
|
|||||||
|
|
||||||
// 输入完6位验证码后,页面会自动提交
|
// 输入完6位验证码后,页面会自动提交
|
||||||
logger.info(this.siteName, ' → 等待邮箱验证完成并跳转到问卷页面...');
|
logger.info(this.siteName, ' → 等待邮箱验证完成并跳转到问卷页面...');
|
||||||
|
logger.info(this.siteName, ' → 将持续等待直到跳转成功(无时间限制)...');
|
||||||
|
|
||||||
// 等待页面跳转到 /account/onboarding?page=source
|
// 无限等待页面跳转到 /account/onboarding?page=source
|
||||||
const startTime = Date.now();
|
const startTime = Date.now();
|
||||||
const maxWait = 20000; // 最多等待20秒
|
|
||||||
let registrationComplete = false;
|
let registrationComplete = false;
|
||||||
|
let hasClickedButton = false;
|
||||||
|
|
||||||
while (Date.now() - startTime < maxWait) {
|
while (!registrationComplete) {
|
||||||
const currentUrl = this.page.url();
|
const currentUrl = this.page.url();
|
||||||
|
|
||||||
// 精确检查:必须是 /account/onboarding?page=source
|
// 检查1: 页面是否已经跳转成功(自动跳转成功)
|
||||||
if (currentUrl.includes('/account/onboarding') && currentUrl.includes('page=source')) {
|
if (currentUrl.includes('/account/onboarding') && currentUrl.includes('page=source')) {
|
||||||
registrationComplete = true;
|
registrationComplete = true;
|
||||||
logger.success(this.siteName, ` → ✓ 邮箱验证成功!已跳转到问卷页面`);
|
const totalTime = ((Date.now() - startTime) / 1000).toFixed(1);
|
||||||
|
logger.success(this.siteName, ` → ✓ 邮箱验证成功!已跳转到问卷页面 (耗时: ${totalTime}秒)`);
|
||||||
logger.info(this.siteName, ` → 当前页面: ${currentUrl}`);
|
logger.info(this.siteName, ` → 当前页面: ${currentUrl}`);
|
||||||
break;
|
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;
|
const elapsed = Date.now() - startTime;
|
||||||
if (elapsed > 0 && elapsed % 2000 === 0) {
|
if (elapsed > 0 && elapsed % 5000 === 0) {
|
||||||
logger.info(this.siteName, ` → 等待中... 已用时 ${elapsed/1000}秒`);
|
logger.info(this.siteName, ` → 等待中... 已用时 ${(elapsed/1000).toFixed(0)}秒`);
|
||||||
}
|
}
|
||||||
|
|
||||||
await new Promise(resolve => setTimeout(resolve, 500));
|
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);
|
await this.human.randomDelay(2000, 3000);
|
||||||
|
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user