auto-register-verdent/registerModule.js
2025-11-13 14:31:44 +08:00

235 lines
8.3 KiB
JavaScript
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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.

import { config } from './config.js';
/**
* 处理 Cloudflare Turnstile 验证
* @param {Page} page - Playwright 页面对象
*/
async function handleTurnstile(page) {
console.log('🔐 检查 Cloudflare Turnstile 验证...');
try {
// 使用 frame 或 frameLocator 两种方式尝试定位 Cloudflare 挑战 iframe
const cfFrames = page.frames().filter(f => /cloudflare|turnstile|challenges/i.test(f.url()));
const cfFrameLocator = page.frameLocator('iframe[title*="Cloudflare" i], iframe[src*="challenges.cloudflare.com" i], iframe[src*="turnstile" i]').first();
const hasLocator = await cfFrameLocator.locator('body').count().catch(() => 0);
const hasFrame = cfFrames.length > 0;
if (hasLocator || hasFrame) {
console.log('[LOG] 发现 Cloudflare 验证 iframe');
async function tryClickIn(locatorBase) {
const possibleTargets = [
'input[type="checkbox"]',
'div[role="checkbox"]',
'label:has-text("确认您是真人")',
'label:has-text("I am human")',
'span:has-text("确认您是真人")',
'span:has-text("I am human")',
'#cf-stage label',
'button'
];
for (const sel of possibleTargets) {
const loc = locatorBase.locator(sel).first();
if (await loc.count() > 0 && await loc.isVisible()) {
console.log('[LOG] 尝试点击选择器:', sel);
try { await loc.click({ force: true }); return true; } catch {}
}
}
return false;
}
let clicked = false;
// 方案 A用 frameLocator 点击
try { clicked = await tryClickIn(cfFrameLocator); } catch {}
// 方案 B直接用 frame 对象点击
if (!clicked) {
for (const f of cfFrames) {
const base = f.locator('html');
try { if (await tryClickIn(base)) { clicked = true; break; } } catch {}
}
}
// 如果没有匹配元素,尝试点击 iframe 中心
if (!clicked) {
console.log('[LOG] 未找到明确的控件,点击 iframe 中心尝试通过');
const handle = await cfFrameLocator.elementHandle().catch(() => null);
if (handle) {
const box = await handle.boundingBox();
if (box) await page.mouse.click(box.x + box.width / 2, box.y + box.height / 2);
}
}
// 等待验证成功,观察注册按钮是否可点击
const signUpCandidate = page.locator('.sign-button [role="button"], .sign-button button, button:has-text("Sign Up"), [role="button"]:has-text("Sign Up")').first();
try {
await page.waitForFunction(
(el) => {
if (!el) return false;
const cls = el.getAttribute('class') || '';
const aria = el.getAttribute('aria-disabled');
return !cls.includes('disabled') && aria !== 'true';
},
signUpCandidate,
{ timeout: 10000 }
);
console.log('✅ Turnstile 验证完成(按钮已可点击)');
} catch {
console.log(' 未检测到按钮状态变化,继续流程');
}
} else {
console.log(' 未找到 Cloudflare 验证 iframe可能已自动通过');
}
} catch (error) {
console.log(' Turnstile 处理异常:', error.message);
}
}
/**
* 在 Verdent.ai 上执行注册流程
* @param {Page} page - Playwright 页面对象
* @param {string} email - 邮箱地址
* @returns {Promise<void>}
*/
export async function registerOnVerdent(page, email) {
console.log('🌐 开始 Verdent.ai 注册流程...');
// 访问注册页面
await page.goto(config.verdent.signupUrl, {
waitUntil: 'domcontentloaded',
timeout: 60000
});
await page.waitForTimeout(3000);
// 输入邮箱地址
console.log('📝 输入邮箱:', email);
const emailInput = page.locator('input[type="email"], input[placeholder*="email" i], input[placeholder*="邮箱"]').first();
await emailInput.fill(email);
await page.waitForTimeout(1000);
// 处理 Turnstile 验证(如果存在)
await handleTurnstile(page);
// 点击发送验证码按钮
console.log('📤 点击发送验证码...');
const sendCodeButton = page.locator('button.send-code-button').first();
// 等待按钮变为可点击状态
await page.waitForTimeout(2000);
// 检查按钮是否被禁用
const isDisabled = await sendCodeButton.getAttribute('disabled');
if (isDisabled !== null) {
console.log('⚠️ 发送验证码按钮被禁用,可能需要先完成其他验证');
// 再次尝试处理 Turnstile
await handleTurnstile(page);
await page.waitForTimeout(2000);
}
await sendCodeButton.click();
const sentAtMs = Date.now();
console.log('✅ 验证码已发送 at', new Date(sentAtMs).toISOString());
return { page, sentAtMs };
}
/**
* 填写验证码和密码,完成注册
* @param {Page} page - Playwright 页面对象
* @param {string} verificationCode - 验证码
*/
export async function completeRegistration(page, verificationCode) {
console.log('✍️ 填写验证码和密码...');
// 输入验证码
console.log('输入验证码:', verificationCode);
const codeInput = page.locator('input[placeholder*="Verification code" i], input[autocomplete="one-time-code"]').first();
await codeInput.fill(verificationCode);
await page.waitForTimeout(1000);
// 输入密码
console.log('输入密码...');
const passwordInput = page.locator('input[type="password"][placeholder*="Password" i], input[autocomplete="new-password"]').first();
await passwordInput.fill(config.verdent.password);
await page.waitForTimeout(1000);
// 点击注册按钮
console.log('🚀 点击注册按钮...');
console.log('[LOG] 查找注册按钮...');
// 等待一下,让表单验证完成
await page.waitForTimeout(2000);
// 尝试多种方式定位按钮(包含 div[role=button]
let signUpButton = page.locator('.sign-button [role="button"], .sign-button button').first();
let buttonCount = await signUpButton.count();
console.log('[LOG] 找到 .sign-button [role=button]/button:', buttonCount);
if (buttonCount === 0) {
signUpButton = page.locator('button:has-text("Sign Up"), [role="button"]:has-text("Sign Up"), [role="button"]:has-text("注册")').first();
buttonCount = await signUpButton.count();
console.log('[LOG] 找到包含 Sign Up/注册 文本的按钮:', buttonCount);
}
if (buttonCount === 0) {
console.log('[LOG] 未找到按钮,输出页面内容调试...');
const roles = page.locator('[role="button"]');
const allRoleBtnCount = await roles.count();
console.log('[LOG] 页面上 role=button 数量', allRoleBtnCount);
for (let i = 0; i < Math.min(allRoleBtnCount, 5); i++) {
const btn = roles.nth(i);
const text = (await btn.textContent() || '').trim();
console.log(`[LOG] role 按钮 ${i+1}:`, text);
}
throw new Error('未找到注册按钮');
}
// 检查按钮状态并等待启用
try {
await page.waitForFunction(
(el) => {
if (!el) return false;
const cls = el.getAttribute('class') || '';
const aria = el.getAttribute('aria-disabled');
return !cls.includes('disabled') && aria !== 'true';
},
signUpButton,
{ timeout: 15000 }
);
} catch (error) {
console.log('[LOG] 等待按钮启用超时:', error.message);
}
try {
await signUpButton.click({ timeout: 5000, force: true });
console.log('✅ 注册请求已提交');
// 等待跳转或成功提示
await page.waitForTimeout(5000);
// 检查是否注册成功
const currentUrl = page.url();
console.log('当前页面:', currentUrl);
if (!currentUrl.includes('/signup')) {
console.log('🎉 注册成功!');
return true;
} else {
// 检查是否有错误提示
const errorElement = page.locator('[class*="error"], [class*="alert"]').first();
if (await errorElement.count() > 0) {
const errorText = await errorElement.textContent();
console.log('❌ 注册失败:', errorText);
return false;
}
console.log('⚠️ 注册状态未知,请手动检查');
return null;
}
} catch (error) {
console.log('❌ 点击注册按钮失败:', error.message);
return false;
}
}