This commit is contained in:
dengqichen 2025-11-16 20:52:52 +08:00
parent 7090744cf0
commit 740456ba56
3 changed files with 281 additions and 239 deletions

View File

@ -40,6 +40,11 @@ const DEFAULT_CONFIG = {
// 信用卡配置 // 信用卡配置
card: { card: {
type: 'visa' type: 'visa'
},
// Cloudflare验证配置
cloudflare: {
mode: 'manual' // 'auto' = 全自动(实验性), 'manual' = 半自动(推荐)
} }
}; };

View File

@ -10,10 +10,12 @@
* ... 根据实际情况继续添加步骤 * ... 根据实际情况继续添加步骤
*/ */
const AccountDataGenerator = require('../generator'); const BaseSite = require('../base-site');
const HumanBehavior = require('../utils/human-behavior'); const HumanBehavior = require('../utils/human-behavior');
const EmailVerificationService = require('../email-verification'); const CloudflareHandler = require('../utils/cloudflare-handler');
const logger = require('../../../shared/logger'); const logger = require('../../../shared/logger');
const EmailVerificationService = require('../email-verification');
const { DEFAULT_CONFIG } = require('../config');
class WindsurfRegister { class WindsurfRegister {
constructor() { constructor() {
@ -315,185 +317,12 @@ class WindsurfRegister {
} }
/** /**
* Cloudflare Turnstile验证步骤2.5 * Cloudflare Turnstile验证步骤2.5- 使用通用处理器
*/ */
async handleCloudflareVerification() { async handleCloudflareVerification() {
logger.info(this.siteName, '[Cloudflare] 检查人机验证...'); const cloudflareMode = DEFAULT_CONFIG.cloudflare.mode;
const handler = new CloudflareHandler(this.page, this.human, this.siteName, cloudflareMode);
await this.human.randomDelay(2000, 3000); return await handler.handle();
// 检查页面内容,判断是否有验证
const pageText = await this.page.evaluate(() => document.body.textContent || '');
if (!pageText.includes('verify') && !pageText.includes('human')) {
logger.success(this.siteName, '[Cloudflare] 无验证,自动通过');
return 'passed';
}
logger.info(this.siteName, '[Cloudflare] 发现验证页面');
// 情况1等待自动验证某些情况下会自动通过
logger.info(this.siteName, '[Cloudflare] 等待自动验证5秒...');
await this.human.randomDelay(5000, 6000);
const afterWait = await this.page.evaluate(() => document.body.textContent || '');
if (!afterWait.includes('verify') && !afterWait.includes('human')) {
logger.success(this.siteName, '[Cloudflare] 自动验证通过');
return 'passed';
}
// 情况2需要点击checkbox
logger.info(this.siteName, '[Cloudflare] 需要点击验证框...');
try {
// 方法1: 查找包含Turnstile的iframe
logger.info(this.siteName, '[Cloudflare] 查找iframe...');
const frames = await this.page.frames();
let turnstileFrame = null;
for (const frame of frames) {
const url = frame.url();
if (url.includes('challenges.cloudflare.com') || url.includes('turnstile')) {
turnstileFrame = frame;
logger.success(this.siteName, `[Cloudflare] 找到iframe: ${url.substring(0, 80)}...`);
break;
}
}
if (turnstileFrame) {
// 等待iframe内容加载
logger.info(this.siteName, '[Cloudflare] 等待iframe内容加载...');
try {
// 等待checkbox元素出现在iframe中
await turnstileFrame.waitForSelector('input[type="checkbox"]', { timeout: 10000 });
logger.success(this.siteName, '[Cloudflare] checkbox元素已加载');
// 再等待一下确保完全渲染
await this.human.randomDelay(2000, 3000);
logger.info(this.siteName, '[Cloudflare] 尝试点击验证框...');
// 关键:使用页面级别的鼠标点击,模拟真实用户行为
// 查找checkbox在页面中的位置
const checkboxElement = await turnstileFrame.$('label.cb-lb');
if (!checkboxElement) {
logger.error(this.siteName, '[Cloudflare] 未找到label.cb-lb元素');
return 'failed';
}
// 获取checkbox在页面中的绝对坐标
const box = await checkboxElement.boundingBox();
if (!box) {
logger.error(this.siteName, '[Cloudflare] 无法获取checkbox坐标');
return 'failed';
}
logger.info(this.siteName, `[Cloudflare] checkbox坐标: x=${Math.round(box.x)}, y=${Math.round(box.y)}, width=${Math.round(box.width)}, height=${Math.round(box.height)}`);
// 计算点击位置在checkbox中心附近随机偏移
const clickX = box.x + box.width / 2 + (Math.random() - 0.5) * 5;
const clickY = box.y + box.height / 2 + (Math.random() - 0.5) * 5;
// 先移动鼠标到目标附近(模拟真实用户)
await this.page.mouse.move(clickX - 50, clickY - 50);
await this.human.randomDelay(100, 300);
// 移动到目标位置
await this.page.mouse.move(clickX, clickY, { steps: 10 });
await this.human.randomDelay(200, 400);
// 点击
logger.info(this.siteName, `[Cloudflare] 在坐标 (${Math.round(clickX)}, ${Math.round(clickY)}) 点击`);
await this.page.mouse.click(clickX, clickY);
const clicked = true;
if (clicked) {
logger.success(this.siteName, `[Cloudflare] ✓ 已点击: ${clicked}`);
// 等待验证处理
logger.info(this.siteName, '[Cloudflare] 等待验证处理最多15秒...');
await this.human.randomDelay(8000, 12000);
// 检查是否通过
const finalCheck = await this.page.evaluate(() => document.body.textContent || '');
if (!finalCheck.includes('verify') && !finalCheck.includes('human')) {
logger.success(this.siteName, '[Cloudflare] ✓✓✓ 验证通过!');
return 'passed';
} else {
logger.warn(this.siteName, '[Cloudflare] 第一次检查未通过再等待5秒...');
// 再等待一次
await this.human.randomDelay(5000, 8000);
const secondCheck = await this.page.evaluate(() => document.body.textContent || '');
if (!secondCheck.includes('verify') && !secondCheck.includes('human')) {
logger.success(this.siteName, '[Cloudflare] ✓✓✓ 验证通过!');
return 'passed';
} else {
logger.error(this.siteName, '[Cloudflare] ✗ 验证失败');
return 'failed';
}
}
} else {
logger.error(this.siteName, '[Cloudflare] ✗ iframe内未找到可点击元素');
return 'failed';
}
} catch (error) {
logger.error(this.siteName, `[Cloudflare] ✗ 处理iframe时出错: ${error.message}`);
return 'failed';
}
}
// 方法2: 如果iframe方法失败尝试直接点击页面上的元素
logger.info(this.siteName, '[Cloudflare] 尝试在主页面查找...');
const pageSelectors = [
'iframe[src*="cloudflare"]',
'iframe[src*="turnstile"]',
'input[type="checkbox"]',
'.cf-turnstile'
];
for (const selector of pageSelectors) {
try {
const element = await this.page.$(selector);
if (element) {
logger.info(this.siteName, `[Cloudflare] 在主页面找到: ${selector}`);
if (selector.includes('iframe')) {
// 如果是iframe点击iframe中心
const box = await element.boundingBox();
if (box) {
await this.page.mouse.click(box.x + box.width / 2, box.y + box.height / 2);
logger.success(this.siteName, '[Cloudflare] 已点击iframe区域');
await this.human.randomDelay(5000, 8000);
const check = await this.page.evaluate(() => document.body.textContent || '');
if (!check.includes('verify') && !check.includes('human')) {
logger.success(this.siteName, '[Cloudflare] ✓ 验证通过');
return 'passed';
}
}
}
}
} catch (e) {
// 继续
}
}
// 情况3自动点击失败
logger.warn(this.siteName, '[Cloudflare] ⚠ 自动点击失败');
logger.warn(this.siteName, '[Cloudflare] 请手动点击验证框...');
logger.warn(this.siteName, '[Cloudflare] 程序将在30秒后继续...');
// 等待用户手动完成
await this.human.randomDelay(30000, 30000);
return 'manual';
} catch (error) {
logger.error(this.siteName, `[Cloudflare] 处理验证时出错: ${error.message}`);
return 'error';
}
} }
/** /**
@ -527,73 +356,64 @@ class WindsurfRegister {
logger.success(this.siteName, ` → 验证码: ${code}`); logger.success(this.siteName, ` → 验证码: ${code}`);
// 等待验证码输入框 // 等待验证码输入框加载
await this.human.randomDelay(1000, 2000); await this.human.randomDelay(2000, 3000);
// 查找验证码输入框(可能有多种选择器) // Windsurf使用6个独立的输入框需要逐个填写
const possibleSelectors = [ logger.info(this.siteName, ' → 查找验证码输入框...');
'#code',
'#verificationCode',
'input[name="code"]',
'input[name="verificationCode"]',
'input[type="text"][placeholder*="code" i]',
'input[type="text"][placeholder*="验证" i]'
];
let codeInputSelector = null; // 等待输入框出现
for (const selector of possibleSelectors) { await this.page.waitForSelector('input[type="text"]', { timeout: 10000 });
// 获取所有文本输入框
const inputs = await this.page.$$('input[type="text"]');
logger.info(this.siteName, ` → 找到 ${inputs.length} 个输入框`);
if (inputs.length >= 6 && code.length === 6) {
// 逐个填写每一位验证码
logger.info(this.siteName, ' → 填写6位验证码...');
for (let i = 0; i < 6; i++) {
const char = code[i].toUpperCase(); // 确保大写
// 点击输入框获取焦点
await inputs[i].click();
await this.human.randomDelay(100, 200);
// 输入字符
await inputs[i].type(char);
await this.human.randomDelay(300, 500);
logger.info(this.siteName, ` → 已输入第 ${i + 1} 位: ${char}`);
}
logger.success(this.siteName, ' → 验证码已填写完成');
// 等待按钮激活(填完验证码后按钮会自动启用)
logger.info(this.siteName, ' → 等待按钮激活...');
await this.human.randomDelay(2000, 3000);
// 查找"Create account"按钮
try { try {
const input = await this.page.$(selector); await this.page.waitForSelector('button:not([disabled])', { timeout: 10000 });
if (input) { logger.success(this.siteName, ' → 按钮已激活');
codeInputSelector = selector;
logger.info(this.siteName, ` → 找到验证码输入框: ${selector}`);
break;
}
} catch (e) {
// 继续尝试
}
}
if (codeInputSelector) { // 点击提交按钮
// 填写验证码 const submitButton = await this.page.$('button[type="submit"]');
logger.info(this.siteName, ' → 填写验证码...'); if (submitButton) {
await this.human.humanType(this.page, codeInputSelector, code); logger.info(this.siteName, ' → 点击"Create account"按钮...');
await submitButton.click();
// 查找并点击提交按钮
logger.info(this.siteName, ' → 点击提交按钮...');
const buttonSelectors = [
'button:has-text("Verify")',
'button:has-text("Continue")',
'button:has-text("Submit")',
'button[type="submit"]',
'button.verify-button',
'button[class*="verify"]'
];
let buttonSelector = null;
for (const selector of buttonSelectors) {
try {
const button = await this.page.$(selector);
if (button) {
buttonSelector = selector;
break;
}
} catch (e) {
// 继续尝试
}
}
if (buttonSelector) {
await this.human.visualSearch(this.page, buttonSelector);
await this.human.humanClick(this.page, buttonSelector);
} else { } else {
logger.warn(this.siteName, ' → 未找到提交按钮尝试按Enter键'); // 尝试点击任何未disabled的按钮
await this.human.randomDelay(500, 1000); const anyButton = await this.page.$('button:not([disabled])');
await this.page.keyboard.press('Enter'); if (anyButton) {
logger.info(this.siteName, ' → 点击提交按钮...');
await anyButton.click();
}
}
} catch (e) {
logger.warn(this.siteName, ` → 按钮等待超时,可能已自动提交: ${e.message}`);
} }
// 等待验证完成 // 等待页面跳转或验证完成
await this.human.randomDelay(3000, 5000); await this.human.randomDelay(3000, 5000);
this.currentStep = 3; this.currentStep = 3;

View File

@ -0,0 +1,217 @@
/**
* Cloudflare Turnstile 验证处理器
* 通用的Cloudflare人机验证处理支持全自动和半自动模式
*/
const logger = require('../../../shared/logger');
class CloudflareHandler {
/**
* @param {Page} page - Puppeteer page对象
* @param {HumanBehavior} human - 人类行为模拟对象
* @param {string} siteName - 网站名称用于日志
* @param {string} mode - 验证模式: 'auto' 'manual' (默认 'manual')
*/
constructor(page, human, siteName, mode = 'manual') {
this.page = page;
this.human = human;
this.siteName = siteName;
this.mode = mode; // 'auto' 或 'manual'
}
/**
* 处理Cloudflare Turnstile验证
* @returns {Promise<string>} 'passed' | 'failed' | 'manual'
*/
async handle() {
logger.info(this.siteName, '[Cloudflare] 检查人机验证...');
await this.human.randomDelay(2000, 3000);
// 检查页面内容,判断是否有验证
const pageText = await this.page.evaluate(() => document.body.textContent || '');
if (!pageText.includes('verify') && !pageText.includes('human')) {
logger.success(this.siteName, '[Cloudflare] 无验证,自动通过');
return 'passed';
}
logger.info(this.siteName, '[Cloudflare] 发现验证页面');
// 情况1等待自动验证某些情况下会自动通过
logger.info(this.siteName, '[Cloudflare] 等待自动验证5秒...');
await this.human.randomDelay(5000, 6000);
const afterWait = await this.page.evaluate(() => document.body.textContent || '');
if (!afterWait.includes('verify') && !afterWait.includes('human')) {
logger.success(this.siteName, '[Cloudflare] 自动验证通过');
return 'passed';
}
// 情况2需要点击验证框
if (this.mode === 'auto') {
return await this.autoHandle();
} else {
return await this.manualHandle();
}
}
/**
* 自动处理模式实验性
*/
async autoHandle() {
logger.info(this.siteName, '[Cloudflare] 自动模式:尝试点击验证框...');
try {
// 查找Turnstile iframe
const frames = await this.page.frames();
let turnstileFrame = null;
for (const frame of frames) {
const url = frame.url();
if (url.includes('challenges.cloudflare.com') || url.includes('turnstile')) {
turnstileFrame = frame;
logger.success(this.siteName, `[Cloudflare] 找到iframe: ${url.substring(0, 80)}...`);
break;
}
}
if (!turnstileFrame) {
logger.warn(this.siteName, '[Cloudflare] 未找到Turnstile iframe');
return 'failed';
}
// 等待iframe加载
await turnstileFrame.waitForSelector('body', { timeout: 5000 });
await this.human.randomDelay(8000, 8000);
// 查找并点击checkbox
const checkboxElement = await turnstileFrame.$('label.cb-lb');
if (!checkboxElement) {
logger.error(this.siteName, '[Cloudflare] 未找到验证框');
return 'failed';
}
// 使用真实鼠标点击
const box = await checkboxElement.boundingBox();
if (box) {
const clickX = box.x + box.width / 2;
const clickY = box.y + box.height / 2;
await this.page.mouse.move(clickX - 50, clickY - 50);
await this.human.randomDelay(100, 300);
await this.page.mouse.move(clickX, clickY, { steps: 10 });
await this.human.randomDelay(200, 400);
await this.page.mouse.click(clickX, clickY);
logger.success(this.siteName, '[Cloudflare] 已点击验证框');
}
// 等待验证处理
await this.human.randomDelay(8000, 12000);
// 检查是否通过
const finalCheck = await this.page.evaluate(() => document.body.textContent || '');
if (!finalCheck.includes('verify') && !finalCheck.includes('human')) {
logger.success(this.siteName, '[Cloudflare] ✓ 验证通过');
return 'passed';
}
return 'failed';
} catch (error) {
logger.error(this.siteName, `[Cloudflare] 自动处理失败: ${error.message}`);
return 'failed';
}
}
/**
* 半自动处理模式推荐
*/
async manualHandle() {
logger.info(this.siteName, '[Cloudflare] 半自动模式:等待用户手动点击...');
try {
// 查找Turnstile iframe
const frames = await this.page.frames();
let turnstileFrame = null;
for (const frame of frames) {
const url = frame.url();
if (url.includes('challenges.cloudflare.com') || url.includes('turnstile')) {
turnstileFrame = frame;
break;
}
}
if (turnstileFrame) {
await turnstileFrame.waitForSelector('body', { timeout: 5000 });
}
// 显示提示
logger.warn(this.siteName, '='.repeat(60));
logger.warn(this.siteName, '[Cloudflare] 🖱️ 请手动点击验证框!');
logger.warn(this.siteName, '[Cloudflare] 程序将自动检测验证完成超时10分钟...');
logger.warn(this.siteName, '='.repeat(60));
// 轮询检查验证是否完成最多10分钟
let verified = false;
let attempts = 0;
const maxAttempts = 600; // 10分钟 = 600秒
while (!verified && attempts < maxAttempts) {
attempts++;
await this.human.randomDelay(1000, 1000);
// 方法1: 检查页面内容
const pageText = await this.page.evaluate(() => document.body.textContent || '');
const hasVerifyText = pageText.includes('verify') || pageText.includes('human');
// 方法2: 检查URL是否改变
const currentUrl = this.page.url();
const urlChanged = !currentUrl.includes('register');
// 方法3: 检查iframe内成功标志
let iframeSuccess = false;
if (turnstileFrame) {
try {
iframeSuccess = await turnstileFrame.evaluate(() => {
const successDiv = document.querySelector('#success');
return successDiv && successDiv.style.display !== 'none';
});
} catch (e) {
// iframe可能已消失
}
}
// 每15秒输出一次状态
if (attempts % 15 === 0) {
const minutes = Math.floor(attempts / 60);
const seconds = attempts % 60;
logger.info(this.siteName, `[Cloudflare] 等待中... (${minutes}${seconds}秒/${Math.floor(maxAttempts/60)}分钟)`);
}
// 判断验证完成
if (iframeSuccess || (!hasVerifyText && attempts > 3) || urlChanged) {
verified = true;
logger.success(this.siteName, '[Cloudflare] ✓✓✓ 检测到验证完成!');
break;
}
}
if (!verified) {
logger.error(this.siteName, '[Cloudflare] ✗ 10分钟超时');
return 'failed';
}
// 验证完成后等待页面稳定
await this.human.randomDelay(2000, 3000);
return 'passed';
} catch (error) {
logger.error(this.siteName, `[Cloudflare] 半自动处理失败: ${error.message}`);
return 'failed';
}
}
}
module.exports = CloudflareHandler;