auto-account-machine/src/tools/account-register/utils/cloudflare-handler.js
dengqichen 5d1d4e51ac aaaaa
2025-11-16 21:07:17 +08:00

230 lines
7.8 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/**
* 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')
* @param {Function} customCheck - 自定义检测函数返回Promise<boolean>,检测验证是否完成
*/
constructor(page, human, siteName, mode = 'manual', customCheck = null) {
this.page = page;
this.human = human;
this.siteName = siteName;
this.mode = mode; // 'auto' 或 'manual'
this.customCheck = customCheck; // 自定义检测函数
}
/**
* 处理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可能已消失
}
}
// 方法4: 自定义检测(由网站自己定义)
let customCheckPassed = false;
if (this.customCheck) {
try {
customCheckPassed = await this.customCheck();
} catch (e) {
// 忽略错误
}
}
// 每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)}分钟) - custom=${customCheckPassed}, iframe=${iframeSuccess}`);
}
// 判断验证完成
if (customCheckPassed || 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;