diff --git a/content/mail-2925.js b/content/mail-2925.js index 028a2b0..81f00e2 100644 --- a/content/mail-2925.js +++ b/content/mail-2925.js @@ -64,6 +64,13 @@ const MAIL_ITEM_SELECTORS = [ '[class*="list-item"]', 'li[class*="mail"]', ]; +const MAIL_REFRESH_SELECTOR = '[class*="refresh"], [title*="鍒锋柊"], [aria-label*="鍒锋柊"], [class*="Refresh"]'; +const MAIL_INBOX_SELECTORS = [ + 'a[href*="mailList"]', + '[class*="inbox"]', + '[class*="Inbox"]', + '[title*="鏀朵欢绠?]', +]; function findMailItems() { for (const selector of MAIL_ITEM_SELECTORS) { @@ -75,6 +82,20 @@ function findMailItems() { return []; } +function findRefreshButton() { + return document.querySelector(MAIL_REFRESH_SELECTOR); +} + +function findInboxLink() { + for (const selector of MAIL_INBOX_SELECTORS) { + const node = document.querySelector(selector); + if (node) { + return node; + } + } + return null; +} + function getMailItemText(item) { if (!item) return ''; const contentCell = item.querySelector('td.content, .content, .mail-content'); @@ -272,6 +293,37 @@ async function sleepRandom(minMs, maxMs = minMs) { await sleep(duration); } +async function returnToInbox() { + if (findMailItems().length > 0) { + return true; + } + + const inboxLink = findInboxLink(); + if (!inboxLink) { + return false; + } + + simulateClick(inboxLink); + for (let attempt = 0; attempt < 20; attempt += 1) { + await sleep(250); + if (findMailItems().length > 0) { + return true; + } + } + + return false; +} + +async function openMailAndGetMessageText(item) { + simulateClick(item); + try { + await sleepRandom(1200, 2200); + return document.body?.textContent || ''; + } finally { + await returnToInbox(); + } +} + async function refreshInbox() { const refreshBtn = document.querySelector( '[class*="refresh"], [title*="刷新"], [aria-label*="刷新"], [class*="Refresh"]' @@ -318,6 +370,7 @@ async function handlePollEmail(step, payload) { } if (initialItems.length === 0) { + await returnToInbox(); await refreshInbox(); await sleep(2000); initialItems = findMailItems(); @@ -337,6 +390,7 @@ async function handlePollEmail(step, payload) { log(`步骤 ${step}:正在轮询 2925 邮箱,第 ${attempt}/${maxAttempts} 次`); if (attempt > 1) { + await returnToInbox(); await refreshInbox(); await sleepRandom(900, 1500); } @@ -392,9 +446,7 @@ async function handlePollEmail(step, payload) { return { ok: true, code, emailTimestamp: Date.now() }; } - simulateClick(item); - await sleepRandom(1200, 2200); - const openedText = document.body?.textContent || ''; + const openedText = await openMailAndGetMessageText(item); const bodyCode = extractVerificationCode(openedText, strictChatGPTCodeOnly); const openedTargetState = getTargetEmailMatchState(openedText, targetEmail); if (targetEmail && openedTargetState.hasExplicitEmail && !openedTargetState.matches) { diff --git a/tests/mail-2925-content.test.js b/tests/mail-2925-content.test.js new file mode 100644 index 0000000..cb31656 --- /dev/null +++ b/tests/mail-2925-content.test.js @@ -0,0 +1,221 @@ +const test = require('node:test'); +const assert = require('node:assert/strict'); +const fs = require('node:fs'); + +const source = fs.readFileSync('content/mail-2925.js', 'utf8'); + +function extractFunction(name) { + const markers = [`async function ${name}(`, `function ${name}(`]; + const start = markers + .map((marker) => source.indexOf(marker)) + .find((index) => index >= 0); + if (start < 0) { + throw new Error(`missing function ${name}`); + } + + let parenDepth = 0; + let signatureEnded = false; + let braceStart = -1; + for (let i = start; i < source.length; i += 1) { + const ch = source[i]; + if (ch === '(') { + parenDepth += 1; + } else if (ch === ')') { + parenDepth -= 1; + if (parenDepth === 0) { + signatureEnded = true; + } + } else if (ch === '{' && signatureEnded) { + braceStart = i; + break; + } + } + if (braceStart < 0) { + throw new Error(`missing body for function ${name}`); + } + + let depth = 0; + let end = braceStart; + for (; end < source.length; end += 1) { + const ch = source[end]; + if (ch === '{') depth += 1; + if (ch === '}') { + depth -= 1; + if (depth === 0) { + end += 1; + break; + } + } + } + + return source.slice(start, end); +} + +test('handlePollEmail returns to inbox before initial refresh when 2925 opens on a detail page', async () => { + const bundle = extractFunction('handlePollEmail'); + + const api = new Function(` +let detailMode = true; +const clickOrder = []; +const seenCodes = new Set(); +const mailItem = { text: 'OpenAI verification code 654321' }; + +function findMailItems() { + return detailMode ? [] : [mailItem]; +} + +function getMailItemId() { + return 'mail-1'; +} + +function getCurrentMailIds(items = []) { + return new Set(items.map(() => 'mail-1')); +} + +function normalizeMinuteTimestamp(value) { + return Number(value) || 0; +} + +function parseMailItemTimestamp() { + return Date.now(); +} + +function matchesMailFilters() { + return true; +} + +function getMailItemText(item) { + return item.text; +} + +function extractVerificationCode(text) { + const match = String(text || '').match(/(\\d{6})/); + return match ? match[1] : null; +} + +function extractEmails() { + return []; +} + +function emailMatchesTarget() { + return true; +} + +function getTargetEmailMatchState() { + return { matches: true, hasExplicitEmail: false }; +} + +async function sleep() {} +async function sleepRandom() {} + +async function returnToInbox() { + clickOrder.push('inbox'); + detailMode = false; + return true; +} + +async function refreshInbox() { + clickOrder.push('refresh'); +} + +function persistSeenCodes() {} +function log() {} + +${bundle} + +return { + handlePollEmail, + getClickOrder() { + return clickOrder.slice(); + }, +}; +`)(); + + const result = await api.handlePollEmail(4, { + senderFilters: ['openai'], + subjectFilters: ['verification'], + maxAttempts: 1, + intervalMs: 1, + filterAfterTimestamp: Date.now(), + }); + + assert.equal(result.code, '654321'); + assert.deepEqual(api.getClickOrder(), ['inbox', 'refresh']); +}); + +test('openMailAndGetMessageText always returns to inbox after opening a 2925 message', async () => { + const bundle = [ + extractFunction('findInboxLink'), + extractFunction('returnToInbox'), + extractFunction('openMailAndGetMessageText'), + ].join('\n'); + + const api = new Function(` +const MAIL_INBOX_SELECTORS = [ + 'a[href*="mailList"]', + '[class*="inbox"]', + '[class*="Inbox"]', + '[title*="鏀朵欢绠?]', +]; +const clickOrder = []; +const mailItem = { kind: 'mail' }; +const inboxLink = { kind: 'inbox' }; +let listVisible = true; +let bodyText = ''; + +const document = { + body: { + get textContent() { + return bodyText; + }, + }, + querySelector(selector) { + if (selector.includes('mailList') || selector.includes('inbox') || selector.includes('Inbox')) { + return inboxLink; + } + return null; + }, +}; + +function findMailItems() { + return listVisible ? [mailItem] : []; +} + +function simulateClick(node) { + if (node === mailItem) { + clickOrder.push('mail'); + listVisible = false; + bodyText = 'Your ChatGPT code is 731091'; + return; + } + if (node === inboxLink) { + clickOrder.push('inbox'); + listVisible = true; + return; + } + throw new Error('unexpected node'); +} + +async function sleep() {} +async function sleepRandom() {} + +${bundle} + +return { + mailItem, + openMailAndGetMessageText, + getClickOrder() { + return clickOrder.slice(); + }, + isListVisible() { + return listVisible; + }, +}; +`)(); + + const text = await api.openMailAndGetMessageText(api.mailItem); + + assert.match(text, /731091/); + assert.deepEqual(api.getClickOrder(), ['mail', 'inbox']); + assert.equal(api.isListVisible(), true); +});