diff --git a/browser.js b/browser.js index c94a7c3..abbe5c2 100644 --- a/browser.js +++ b/browser.js @@ -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, diff --git a/emailModule.js b/emailModule.js index 23331da..c1e410c 100644 --- a/emailModule.js +++ b/emailModule.js @@ -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} 验证码 */ -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 { + // 每个邮件包含 ,找到所有带时间戳的行 + 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); } } diff --git a/index.js b/index.js index 2188821..02118d6 100644 --- a/index.js +++ b/index.js @@ -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);