first init

This commit is contained in:
dengqichen 2025-11-13 15:12:28 +08:00
parent 6a4f0a9de3
commit 8abea7a7c6
3 changed files with 90 additions and 52 deletions

View File

@ -23,8 +23,13 @@ export async function getBrowserAndContexts({ headless, slowMo }) {
const browser = await chromium.connectOverCDP(wsEndpoint);
const contexts = browser.contexts();
const baseContext = contexts[0] || await browser.newContext();
// 关闭所有已有页签,保持干净
for (const p of baseContext.pages()) { try { await p.close(); } catch {} }
// AdsPower 需要至少保留一个标签页,关闭多余的
const existingPages = baseContext.pages();
if (existingPages.length > 1) {
for (let i = 1; i < existingPages.length; i++) {
try { await existingPages[i].close(); } catch {}
}
}
// 在同一个指纹环境下创建两个页签(同上下文不同页)
return {
browser,

View File

@ -141,26 +141,60 @@ export async function setupTempMail(page) {
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) {
export async function waitForVerificationCode(page, sentAfterMs, targetEmail) {
console.log('⏳ 等待接收验证码邮件...');
console.log('[LOG] 不会刷新页面,只等待新邮件出现...');
console.log('[LOG] 基线时间:', globalBaselineTime || '未设置');
const maxWaitTime = 120000; // 最多等待 120 秒
const checkInterval = 1000; // 每 1 秒检查一次
const maxWaitTime = 120000;
const checkInterval = 1000;
let elapsed = 0;
async function extractCodeFromOpenedMail() {
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();
const code = /\b(\d{6})\b/.exec(text || '');
if (code) return code[1];
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();
@ -170,12 +204,14 @@ export async function waitForVerificationCode(page, sentAfterMs) {
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;
}
@ -186,54 +222,45 @@ export async function waitForVerificationCode(page, sentAfterMs) {
console.log(`[LOG] 检查邮件... (已等待 ${elapsed / 1000} 秒)`);
// 邮件列表行(兼容多种结构)
const emailRows = page.locator('.inbox-dataList .mail-item, .inbox-dataList > div, div.row.no-gutters');
const emailCount = await emailRows.count();
console.log(`[LOG] 当前邮件数量: ${emailCount}`);
try {
// 每个邮件包含 <span data-date>,找到所有带时间戳的行
const timeSpans = page.locator('.inbox span[data-date]');
const count = await timeSpans.count();
console.log(`[LOG] 当前邮件数量: ${count}`);
if (emailCount > 0) {
console.log('📬 发现邮件,尝试读取...');
if (count === 0) continue;
// 遍历所有邮件,优先读取发送之后的邮件(按顺序尝试)
for (let i = 0; i < emailCount; i++) {
const emailRow = emailRows.nth(i);
// 过滤发送时间之前的旧邮件(根据 data-date
try {
const timeSpan = emailRow.locator('span[data-date]').first();
if (await timeSpan.count() > 0) {
const dateStr = await timeSpan.getAttribute('data-date');
if (dateStr) {
const parsed = Date.parse(dateStr.replace(' ', 'T')) || new Date(dateStr).getTime();
if (sentAfterMs && parsed && parsed < sentAfterMs) {
console.log(`[LOG] 跳过旧邮件 ${dateStr}`);
continue;
}
}
}
} catch {}
try {
// 点击打开邮件(有些需要双击/再次点击切换到详情)
await emailRow.click({ timeout: 5000 });
await page.waitForTimeout(500);
try { await emailRow.dblclick({ timeout: 1000 }); } catch {}
// 等待内容区域或 iframe 加载
await page.waitForTimeout(1500);
const code = await extractCodeFromOpenedMail();
// 遍历所有邮件,找第一个满足条件的:发件人 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] 邮件 ${i + 1} 中未找到验证码`);
} catch (error) {
console.log(`[LOG] 读取邮件 ${i + 1} 失败:`, error.message);
console.log('[LOG] 该邮件未提取到验证码,继续...');
}
}
console.log('⚠️ 所有邮件中未找到验证码,继续等待...');
} catch (e) {
console.log('[LOG] 检查邮件出错:', e.message);
}
}

View File

@ -1,6 +1,6 @@
import { chromium } from 'playwright';
import { config } from './config.js';
import { setupTempMail, waitForVerificationCode } from './emailModule.js';
import { setupTempMail, waitForVerificationCode, updateBaseline } from './emailModule.js';
import { registerOnVerdent, completeRegistration } from './registerModule.js';
import { getBrowserAndContexts } from './browser.js';
import { generateUsername } from './util.js';
@ -40,13 +40,16 @@ async function main() {
const email = `${localPart}@${config.tempmail.domain}`;
console.log('📧 将用于注册的邮箱:', email);
// 获取初始基线时间
await updateBaseline(emailPage);
// ========== 第二步:在 Verdent 上开始注册 ==========
console.log('\n🌐 === 步骤 2: 开始 Verdent.ai 注册 ===');
const { sentAtMs } = await registerOnVerdent(registerPage, email);
// ========== 第三步:等待验证码邮件(只取发送后的邮件) ==========
console.log('\n📬 === 步骤 3: 等待验证码邮件 ===');
const verificationCode = await waitForVerificationCode(emailPage, sentAtMs);
const verificationCode = await waitForVerificationCode(emailPage, sentAtMs, email);
if (!verificationCode) {
throw new Error('未能获取验证码');
@ -68,6 +71,9 @@ async function main() {
console.log('🔑 密码:', config.verdent.password);
console.log('============================================\n');
// 更新基线为当前最新邮件时间
await updateBaseline(emailPage);
// 持久化保存(注册时间与过期时间=7天
const createdAt = new Date();
const expiresAt = new Date(createdAt.getTime() + 7 * 24 * 60 * 60 * 1000);