auto-register-verdent/emailModule.js
2025-11-13 15:12:28 +08:00

269 lines
10 KiB
JavaScript
Raw Permalink 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.

import { config } from './config.js';
/**
* 设置临时邮箱并获取验证码
* @param {Page} page - Playwright 页面对象
* @returns {Promise<{email: string, code: string}>}
*/
export async function setupTempMail(page) {
console.log('📧 开始设置临时邮箱...');
// 访问 tempmail.plus增加超时时间因为网络较慢
await page.goto(config.tempmail.url, {
waitUntil: 'domcontentloaded', // 改为 domcontentloaded不等待所有网络请求
timeout: 60000 // 60秒超时
});
await page.waitForTimeout(3000); // 等待页面稳定
// 输入邮箱用户名(固定为配置的 qichen111
console.log('输入邮箱用户名:', config.tempmail.username);
await page.fill('#pre_button', config.tempmail.username);
// 点击页面其他位置或按回车来确认输入,触发邮箱地址更新
console.log('确认邮箱用户名...');
await page.click('body'); // 点击页面空白处
await page.waitForTimeout(2000);
// 检查 PIN 码弹窗是否已经显示
console.log('[LOG] 检查 PIN 码弹窗状态...');
const pinModal = page.locator('#modal-verify');
await page.waitForTimeout(2000); // 等待页面稳定
const initialModalClass = await pinModal.getAttribute('class');
console.log('[LOG] 弹窗 class:', initialModalClass);
// 判断弹窗是否已经显示(包含 "show" class
const isModalShown = initialModalClass && initialModalClass.includes('show');
console.log('[LOG] 弹窗是否已显示:', isModalShown);
if (!isModalShown) {
// 如果弹窗未显示,才需要点击 PIN 保护元素
console.log('[LOG] 弹窗未显示,点击 PIN 码保护元素...');
const pinProtectSpan = page.locator('span.pin-text[data-tr="box_protected"]');
await pinProtectSpan.waitFor({ state: 'visible', timeout: 15000 });
await pinProtectSpan.click({ force: true });
console.log('[LOG] ✅ 已点击 PIN 保护元素');
await page.waitForTimeout(2000);
} else {
console.log('[LOG] ✅ 弹窗已自动显示,无需点击');
}
// 查找 PIN 码输入框
console.log('[LOG] 查找 PIN 码输入框...');
const pinInput = page.locator('#pin');
const pinInputCount = await pinInput.count();
console.log('[LOG] 找到 PIN 输入框数量:', pinInputCount);
if (pinInputCount > 0) {
// 检查输入框是否可见
const isVisible = await pinInput.isVisible();
console.log('[LOG] PIN 输入框是否可见:', isVisible);
// 输入 PIN 码
console.log('[LOG] 输入 PIN 码:', config.tempmail.pinCode);
await pinInput.fill(config.tempmail.pinCode);
console.log('[LOG] ✅ PIN 码已输入');
await page.waitForTimeout(500);
// 查找提交按钮
console.log('[LOG] 查找提交按钮 #verify...');
const verifyButton = page.locator('#verify');
const verifyButtonCount = await verifyButton.count();
console.log('[LOG] 找到提交按钮数量:', verifyButtonCount);
if (verifyButtonCount > 0) {
const buttonText = await verifyButton.textContent();
console.log('[LOG] 按钮文本:', buttonText);
console.log('[LOG] 点击提交按钮...');
await verifyButton.click();
console.log('[LOG] ✅ 已点击提交按钮');
} else {
console.log('[LOG] ⚠️ 未找到提交按钮,尝试按回车');
await pinInput.press('Enter');
}
// 等待验证完成
await page.waitForTimeout(2000);
console.log('[LOG] ✅ PIN 码验证流程完成');
} else {
console.log('[LOG] ❌ 未找到 PIN 输入框!');
throw new Error('未找到 PIN 码输入框');
}
// 等待弹窗关闭后获取邮箱地址
await page.waitForTimeout(2000);
console.log('[LOG] 获取生成的邮箱地址...');
// 尝试多个可能的邮箱地址元素
let emailAddress = '';
// 方法 1: 查找特定的邮箱显示元素
const emailDisplay = page.locator('#email-address, #mail, .email-address, [id*="email"]').first();
if (await emailDisplay.count() > 0 && await emailDisplay.isVisible()) {
const text = await emailDisplay.textContent();
const match = text.match(/[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}/);
if (match) {
emailAddress = match[0];
}
}
// 方法 2: 如果没找到,尝试从页面中提取所有包含 @ 的文本
if (!emailAddress) {
const pageContent = await page.content();
const emailMatches = pageContent.match(/qichen111@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}/);
if (emailMatches && emailMatches.length > 0) {
emailAddress = emailMatches[0];
}
}
// 方法 3: 查找复制按钮附近的文本
if (!emailAddress) {
const copyButton = page.locator('button:has-text("复制")').first();
if (await copyButton.count() > 0) {
const parent = copyButton.locator('..');
const text = await parent.textContent();
const match = text.match(/[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}/);
if (match) {
emailAddress = match[0];
}
}
}
console.log('[LOG] 提取到的邮箱地址:', emailAddress);
if (!emailAddress || !emailAddress.includes('@')) {
console.log('[LOG] ⚠️ 未能正确获取邮箱地址,尝试使用默认格式');
emailAddress = `${config.tempmail.username}@mailto.plus`;
}
console.log('✅ 邮箱地址:', emailAddress);
return { inboxReady: true, page };
}
// 全局基线时间,在 setupTempMail 后初始化,每次注册成功后更新
let globalBaselineTime = null;
/**
* 获取收件箱最新一条邮件的时间作为基线
*/
export async function updateBaseline(page) {
try {
// 等待收件箱加载
await page.waitForSelector('.inbox', { timeout: 10000 }).catch(() => {});
await page.waitForTimeout(2000);
// 查找所有含有时间的邮件行
const timeSpans = page.locator('.inbox span[data-date]');
const count = await timeSpans.count();
if (count === 0) {
globalBaselineTime = null;
console.log('[基线] 收件箱无邮件,基线为 null');
return;
}
// 第一个就是最新的
const dateStr = await timeSpans.first().getAttribute('data-date');
globalBaselineTime = dateStr;
console.log('[基线] 更新为最新邮件时间:', globalBaselineTime);
} catch (e) {
console.log('[基线] 获取失败:', e.message);
globalBaselineTime = null;
}
}
/**
* 等待并获取验证码
* @param {Page} page - Playwright 页面对象
* @returns {Promise<string>} 验证码
*/
export async function waitForVerificationCode(page, sentAfterMs, targetEmail) {
console.log('⏳ 等待接收验证码邮件...');
console.log('[LOG] 基线时间:', globalBaselineTime || '未设置');
const maxWaitTime = 120000;
const checkInterval = 1000;
let elapsed = 0;
async function extractCodeFromOpenedMail(expectEmail) {
// 1) 优先从特定内容区域读取
const contentLoc = page.locator('.mail-content, .email-body, #mail-body, [class*="mail-content"], .pm-text, .view, .message-body').first();
if (await contentLoc.count() > 0) {
const text = await contentLoc.textContent();
if (expectEmail && text && !text.includes(expectEmail)) {
console.log('[LOG] 内容不匹配目标邮箱,继续查找');
} else {
const code = /\b(\d{6})\b/.exec(text || '');
if (code) return code[1];
}
}
// 2) 遍历所有 iframe 提取文本
const frames = page.frames();
console.log(`[LOG] 当前 frame 数量: ${frames.length}`);
for (const f of frames) {
try {
const body = await f.$('body');
if (!body) continue;
const text = await f.evaluate(() => document.body.innerText || '');
if (expectEmail && text && !text.includes(expectEmail)) continue;
const m = /\b(\d{6})\b/.exec(text);
if (m) return m[1];
} catch {}
}
// 3) 退化为整页文本
const all = await page.textContent('body');
if (expectEmail && all && !all.includes(expectEmail)) return null;
const m = /\b(\d{6})\b/.exec(all || '');
return m ? m[1] : null;
}
while (elapsed < maxWaitTime) {
await page.waitForTimeout(checkInterval);
elapsed += checkInterval;
console.log(`[LOG] 检查邮件... (已等待 ${elapsed / 1000} 秒)`);
try {
// 每个邮件包含 <span data-date>,找到所有带时间戳的行
const timeSpans = page.locator('.inbox span[data-date]');
const count = await timeSpans.count();
console.log(`[LOG] 当前邮件数量: ${count}`);
if (count === 0) continue;
// 遍历所有邮件,找第一个满足条件的:发件人 cfbounces && 时间 > 基线
for (let i = 0; i < count; i++) {
const timeSpan = timeSpans.nth(i);
const dateStr = await timeSpan.getAttribute('data-date');
// 向上找到包含 from 的父元素获取发件人
const row = timeSpan.locator('xpath=ancestor::div[contains(@class, "row")]').first();
const fromSpan = row.locator('.from span').last();
const sender = await fromSpan.textContent().catch(() => '');
const isCfbounces = /cfbounces/i.test(sender || '');
const isNewer = globalBaselineTime ? (dateStr > globalBaselineTime) : true;
console.log(`[LOG] Row ${i+1}: sender=${sender} date=${dateStr} baseline=${globalBaselineTime} newer=${isNewer}`);
if (isCfbounces && isNewer) {
console.log('✅ 发现新验证邮件,点击读取...');
await row.scrollIntoViewIfNeeded().catch(() => {});
await row.click({ timeout: 5000 });
await page.waitForTimeout(2000);
const code = await extractCodeFromOpenedMail(targetEmail);
if (code) {
console.log('✅ 获取到验证码:', code);
return code;
}
console.log('[LOG] 该邮件未提取到验证码,继续...');
}
}
} catch (e) {
console.log('[LOG] 检查邮件出错:', e.message);
}
}
throw new Error('超时:未能在规定时间内收到验证码');
}