diff --git a/content/mail-163.js b/content/mail-163.js index dd7ba4f..547ea73 100644 --- a/content/mail-163.js +++ b/content/mail-163.js @@ -69,15 +69,141 @@ chrome.runtime.onMessage.addListener((message, sender, sendResponse) => { // Find mail items // ============================================================ -function findMailItems() { - return document.querySelectorAll('div[sign="letter"]'); +function normalizeText(value) { + return String(value || '').replace(/\s+/g, ' ').trim(); } -function getCurrentMailIds() { +function isVisibleNode(node) { + if (!node) return false; + if (node.hidden) return false; + + const style = typeof window.getComputedStyle === 'function' + ? window.getComputedStyle(node) + : null; + if (style && (style.display === 'none' || style.visibility === 'hidden')) { + return false; + } + + const rect = typeof node.getBoundingClientRect === 'function' + ? node.getBoundingClientRect() + : null; + if (rect && rect.width <= 0 && rect.height <= 0) { + return false; + } + + return true; +} + +function isLikelyMailItemNode(node) { + if (!isVisibleNode(node)) { + return false; + } + if (node.matches?.('div[sign="letter"]')) { + return true; + } + if (node.querySelector?.('.nui-user, span.da0, [sign="trash"], [title="删除邮件"], [class*="subject"], [class*="sender"]')) { + return true; + } + + const summaryText = normalizeText( + node.getAttribute?.('aria-label') + || node.getAttribute?.('title') + || node.textContent + ); + if (!summaryText) { + return false; + } + + return /发件人|验证码|verification|chatgpt|openai|code/i.test(summaryText); +} + +function findMailItems() { + const selectorGroups = [ + 'div[sign="letter"]', + '[role="option"][aria-label]', + '[role="listitem"][aria-label]', + 'tr[aria-label]', + 'li[aria-label]', + 'div[aria-label]', + ]; + + for (const selector of selectorGroups) { + const matches = Array.from(document.querySelectorAll(selector)).filter(isLikelyMailItemNode); + if (matches.length > 0) { + return matches; + } + } + + return []; +} + +function getMailTextBySelectors(item, selectors = []) { + for (const selector of selectors) { + const candidates = item.querySelectorAll(selector); + for (const candidate of candidates) { + const texts = [ + candidate.getAttribute?.('title'), + candidate.getAttribute?.('aria-label'), + candidate.textContent, + ]; + for (const text of texts) { + const normalized = normalizeText(text); + if (normalized) { + return normalized; + } + } + } + } + return ''; +} + +function getMailSenderText(item) { + return getMailTextBySelectors(item, [ + '.nui-user', + '[class*="sender"]', + '[class*="from"]', + '[data-sender]', + ]); +} + +function getMailSubjectText(item) { + return getMailTextBySelectors(item, [ + 'span.da0', + '[class*="subject"]', + '[data-subject]', + 'strong', + ]); +} + +function getMailRowText(item) { + const ariaLabel = normalizeText(item.getAttribute('aria-label')); + const sender = getMailSenderText(item); + const subject = getMailSubjectText(item); + const fullText = normalizeText(item.textContent || ''); + return normalizeText([ariaLabel, sender, subject, fullText].filter(Boolean).join(' ')); +} + +function getMailItemId(item, index = 0) { + const candidates = [ + item.getAttribute('id'), + item.getAttribute('data-id'), + item.dataset?.id, + item.getAttribute('data-key'), + item.getAttribute('key'), + ].filter(Boolean); + + if (candidates.length > 0) { + return String(candidates[0]); + } + + return `${index}|${getMailRowText(item).slice(0, 240)}`; +} + +function getCurrentMailIds(items = []) { const ids = new Set(); - findMailItems().forEach(item => { - const id = item.getAttribute('id') || ''; - if (id) ids.add(id); + const sourceItems = items.length > 0 ? items : findMailItems(); + sourceItems.forEach((item, index) => { + ids.add(getMailItemId(item, index)); }); return ids; } @@ -90,7 +216,7 @@ function normalizeMinuteTimestamp(timestamp) { } function parseMail163Timestamp(rawText) { - const text = (rawText || '').replace(/\s+/g, ' ').trim(); + const text = normalizeText(rawText); if (!text) return null; let match = text.match(/(\d{4})年(\d{1,2})月(\d{1,2})日\s+(\d{1,2}):(\d{2})/); @@ -107,6 +233,37 @@ function parseMail163Timestamp(rawText) { ).getTime(); } + match = text.match(/今天\s*(\d{1,2}):(\d{2})/); + if (match) { + const [, hour, minute] = match; + const now = new Date(); + return new Date( + now.getFullYear(), + now.getMonth(), + now.getDate(), + Number(hour), + Number(minute), + 0, + 0 + ).getTime(); + } + + match = text.match(/昨天\s*(\d{1,2}):(\d{2})/); + if (match) { + const [, hour, minute] = match; + const now = new Date(); + now.setDate(now.getDate() - 1); + return new Date( + now.getFullYear(), + now.getMonth(), + now.getDate(), + Number(hour), + Number(minute), + 0, + 0 + ).getTime(); + } + match = text.match(/\b(\d{1,2}):(\d{2})\b/); if (match) { const [, hour, minute] = match; @@ -125,18 +282,48 @@ function parseMail163Timestamp(rawText) { return null; } -function getMailTimestamp(item) { - const candidates = []; - const timeCell = item.querySelector('.e00[title], [title*="年"][title*=":"]'); - if (timeCell?.getAttribute('title')) candidates.push(timeCell.getAttribute('title')); - if (timeCell?.textContent) candidates.push(timeCell.textContent); +function isLikelyMailTimestampText(value) { + const text = normalizeText(value); + if (!text) { + return false; + } + return /(\d{4}年\d{1,2}月\d{1,2}日\s+\d{1,2}:\d{2})|今天\s*\d{1,2}:\d{2}|昨天\s*\d{1,2}:\d{2}|\b\d{1,2}:\d{2}\b/.test(text); +} - const titledNodes = item.querySelectorAll('[title]'); - titledNodes.forEach((node) => { - const title = node.getAttribute('title'); - if (title) candidates.push(title); +function collectMailTimestampCandidates(item) { + const candidates = []; + const seen = new Set(); + const pushCandidate = (value) => { + const normalized = normalizeText(value); + if (!normalized || !isLikelyMailTimestampText(normalized) || seen.has(normalized)) { + return; + } + seen.add(normalized); + candidates.push(normalized); + }; + + const priorityNodes = item.querySelectorAll('.e00, [title], [aria-label], time, [class*="time"], [class*="date"]'); + priorityNodes.forEach((node) => { + pushCandidate(node.getAttribute?.('title')); + pushCandidate(node.getAttribute?.('aria-label')); + pushCandidate(node.textContent); }); + const textNodes = item.querySelectorAll('span, div, td, strong, b'); + textNodes.forEach((node) => { + const text = normalizeText(node.textContent); + if (text && text.length <= 24) { + pushCandidate(text); + } + }); + + pushCandidate(item.getAttribute('aria-label')); + pushCandidate(item.getAttribute('title')); + return candidates; +} + +function getMailTimestamp(item) { + const candidates = collectMailTimestampCandidates(item); for (const candidate of candidates) { const parsed = parseMail163Timestamp(candidate); if (parsed) return parsed; @@ -145,6 +332,118 @@ function getMailTimestamp(item) { return null; } +function collectOpenedMailTextCandidates() { + const texts = []; + const seen = new Set(); + const pushText = (value) => { + const normalized = normalizeText(value); + if (!normalized || seen.has(normalized)) { + return; + } + seen.add(normalized); + texts.push(normalized); + }; + + const selectors = [ + '.readHtml', + '[class*="readmail"]', + '[class*="mailread"]', + '[class*="mailBody"]', + '[class*="mailbody"]', + '[class*="mail-content"]', + '[class*="mailContent"]', + '[class*="mail-detail"]', + '[class*="mailDetail"]', + '[class*="detail"] [class*="content"]', + '[class*="read"] [class*="content"]', + '[role="main"]', + ]; + + selectors.forEach((selector) => { + document.querySelectorAll(selector).forEach((node) => { + pushText(node.innerText || node.textContent); + }); + }); + + document.querySelectorAll('iframe').forEach((frame) => { + try { + pushText(frame.contentDocument?.body?.innerText || frame.contentDocument?.body?.textContent); + } catch { + // Ignore cross-frame access errors and keep trying other candidates. + } + }); + + pushText(document.body?.innerText || document.body?.textContent); + return texts.sort((a, b) => b.length - a.length); +} + +function readOpenedMailText(item) { + const subject = normalizeText(getMailSubjectText(item)).toLowerCase(); + const sender = normalizeText(getMailSenderText(item)).toLowerCase(); + const candidates = collectOpenedMailTextCandidates(); + + const preferred = candidates.find((candidate) => { + const lower = candidate.toLowerCase(); + if (subject && lower.includes(subject)) { + return true; + } + if (sender && lower.includes(sender)) { + return true; + } + return Boolean(extractVerificationCode(candidate) && /chatgpt|openai|verification|验证码|login code/i.test(lower)); + }); + + return preferred || candidates[0] || ''; +} + +async function returnToInbox() { + const inboxLink = document.querySelector('.nui-tree-item-text[title="收件箱"], [title="收件箱"]'); + if (inboxLink) { + if (typeof simulateClick === 'function') { + simulateClick(inboxLink); + } else { + inboxLink.click(); + } + } + + for (let i = 0; i < 20; i += 1) { + if (findMailItems().length > 0) { + return true; + } + await sleep(250); + } + + return false; +} + +async function openMailAndGetMessageText(item) { + const beforeText = readOpenedMailText(item); + if (typeof simulateClick === 'function') { + simulateClick(item); + } else { + item.click(); + } + + let openedText = ''; + for (let i = 0; i < 24; i += 1) { + await sleep(250); + const candidate = readOpenedMailText(item); + if (!candidate) { + continue; + } + openedText = candidate; + if (extractVerificationCode(candidate)) { + break; + } + if (candidate !== beforeText && candidate.length > beforeText.length + 24) { + break; + } + } + + await returnToInbox(); + return openedText; +} + function scheduleEmailCleanup(item, step) { setTimeout(() => { Promise.resolve(deleteEmail(item, step)).catch(() => { @@ -199,7 +498,7 @@ async function handlePollEmail(step, payload) { log(`步骤 ${step}:邮件列表已加载,共 ${items.length} 封邮件`); // Snapshot existing mail IDs - const existingMailIds = getCurrentMailIds(); + const existingMailIds = getCurrentMailIds(items); log(`步骤 ${step}:已记录当前 ${existingMailIds.size} 封旧邮件快照`); const FALLBACK_AFTER = 3; @@ -215,38 +514,58 @@ async function handlePollEmail(step, payload) { const allItems = findMailItems(); const useFallback = attempt > FALLBACK_AFTER; - for (const item of allItems) { - const id = item.getAttribute('id') || ''; + for (let index = 0; index < allItems.length; index++) { + const item = allItems[index]; + const id = getMailItemId(item, index); const mailTimestamp = getMailTimestamp(item); const mailMinute = normalizeMinuteTimestamp(mailTimestamp || 0); const passesTimeFilter = !filterAfterMinute || (mailMinute && mailMinute >= filterAfterMinute); - const shouldBypassOldSnapshot = Boolean(filterAfterMinute && passesTimeFilter && mailMinute > 0); if (!passesTimeFilter) { continue; } - if (!useFallback && !shouldBypassOldSnapshot && existingMailIds.has(id)) continue; + if (!useFallback && existingMailIds.has(id)) { + continue; + } - const senderEl = item.querySelector('.nui-user'); - const sender = senderEl ? senderEl.textContent.toLowerCase() : ''; + const sender = getMailSenderText(item).toLowerCase(); + const subject = getMailSubjectText(item); + const rowText = getMailRowText(item); + const ariaLabel = normalizeText(item.getAttribute('aria-label')).toLowerCase(); + const combinedText = normalizeText([subject, ariaLabel, rowText].filter(Boolean).join(' ')); - const subjectEl = item.querySelector('span.da0'); - const subject = subjectEl ? subjectEl.textContent : ''; + if (!mailTimestamp) { + log(`步骤 ${step}:邮件 ${id.slice(0, 60)} 未读取到时间,已跳过时间窗口校验后的文本匹配阶段。`, 'info'); + } - const ariaLabel = (item.getAttribute('aria-label') || '').toLowerCase(); - - const senderMatch = senderFilters.some(f => sender.includes(f.toLowerCase()) || ariaLabel.includes(f.toLowerCase())); - const subjectMatch = subjectFilters.some(f => subject.toLowerCase().includes(f.toLowerCase()) || ariaLabel.includes(f.toLowerCase())); + const senderMatch = senderFilters.some((filter) => { + const normalizedFilter = String(filter || '').toLowerCase(); + return normalizedFilter && (sender.includes(normalizedFilter) || ariaLabel.includes(normalizedFilter) || rowText.toLowerCase().includes(normalizedFilter)); + }); + const subjectMatch = subjectFilters.some((filter) => { + const normalizedFilter = String(filter || '').toLowerCase(); + return normalizedFilter && (subject.toLowerCase().includes(normalizedFilter) || ariaLabel.includes(normalizedFilter) || rowText.toLowerCase().includes(normalizedFilter)); + }); if (senderMatch || subjectMatch) { - const code = extractVerificationCode(subject + ' ' + ariaLabel); + let code = extractVerificationCode(combinedText); + let codeSource = '邮件列表'; + + if (!code) { + const openedText = await openMailAndGetMessageText(item); + code = extractVerificationCode(openedText); + if (code) { + codeSource = '邮件正文'; + } + } + if (code && excludedCodeSet.has(code)) { log(`步骤 ${step}:跳过排除的验证码:${code}`, 'info'); } else if (code && !seenCodes.has(code)) { seenCodes.add(code); persistSeenCodes(); - const source = useFallback && existingMailIds.has(id) ? '回退匹配邮件' : '新邮件'; + const source = useFallback && existingMailIds.has(id) ? `回退匹配${codeSource}` : `新邮件${codeSource}`; const timeLabel = mailTimestamp ? `,时间:${new Date(mailTimestamp).toLocaleString('zh-CN', { hour12: false })}` : ''; log(`步骤 ${step}:已找到验证码:${code}(来源:${source}${timeLabel},主题:${subject.slice(0, 40)})`, 'ok'); diff --git a/tests/mail-163-content.test.js b/tests/mail-163-content.test.js new file mode 100644 index 0000000..34f3ec3 --- /dev/null +++ b/tests/mail-163-content.test.js @@ -0,0 +1,585 @@ +const test = require('node:test'); +const assert = require('node:assert/strict'); +const fs = require('node:fs'); + +const source = fs.readFileSync('content/mail-163.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('findMailItems falls back to visible aria-label mail rows when legacy selector is missing', () => { + const bundle = [ + extractFunction('normalizeText'), + extractFunction('isVisibleNode'), + extractFunction('isLikelyMailItemNode'), + extractFunction('findMailItems'), + ].join('\n'); + + const api = new Function(` +const mailRow = { + hidden: false, + textContent: 'Your temporary ChatGPT verification code 911113', + getAttribute(name) { + if (name === 'aria-label') return 'Your temporary ChatGPT verification code 911113 发件人 OpenAI'; + return ''; + }, + getBoundingClientRect() { + return { width: 600, height: 48 }; + }, + matches() { + return false; + }, + querySelector() { + return null; + }, +}; + +const document = { + querySelectorAll(selector) { + if (selector === 'div[sign="letter"]') return []; + if (selector === '[role="option"][aria-label]') return [mailRow]; + return []; + }, +}; + +const window = { + getComputedStyle() { + return { display: 'block', visibility: 'visible' }; + }, +}; + +${bundle} + +return { findMailItems }; +`)(); + + const rows = api.findMailItems(); + assert.equal(rows.length, 1); +}); + +test('getMailTimestamp parses visible hh:mm text even when no title attribute exists', () => { + const bundle = [ + extractFunction('normalizeText'), + extractFunction('parseMail163Timestamp'), + extractFunction('isLikelyMailTimestampText'), + extractFunction('collectMailTimestampCandidates'), + extractFunction('getMailTimestamp'), + ].join('\n'); + + const timestamp = new Function(` +const item = { + getAttribute() { + return ''; + }, + querySelectorAll(selector) { + if (selector === '.e00, [title], [aria-label], time, [class*="time"], [class*="date"]') { + return []; + } + if (selector === 'span, div, td, strong, b') { + return [ + { + textContent: '22:22', + getAttribute() { + return ''; + }, + }, + ]; + } + return []; + }, +}; + +${bundle} + +return getMailTimestamp(item); +`)(); + + const now = new Date(); + const expected = new Date(now.getFullYear(), now.getMonth(), now.getDate(), 22, 22, 0, 0).getTime(); + assert.equal(timestamp, expected); +}); + +test('readOpenedMailText prefers opened body content that contains the verification code', () => { + const bundle = [ + extractFunction('normalizeText'), + extractFunction('collectOpenedMailTextCandidates'), + extractFunction('readOpenedMailText'), + extractFunction('extractVerificationCode'), + ].join('\n'); + + const text = new Function(` +const item = { + querySelectorAll() { + return []; + }, +}; + +function getMailSubjectText() { + return 'Your temporary ChatGPT login code'; +} + +function getMailSenderText() { + return 'OpenAI'; +} + +const document = { + querySelectorAll(selector) { + if (selector === 'iframe') { + return []; + } + return [ + { innerText: 'Your temporary ChatGPT login code', textContent: 'Your temporary ChatGPT login code' }, + { innerText: 'Enter this temporary verification code to continue: 214203', textContent: 'Enter this temporary verification code to continue: 214203' }, + ]; + }, + body: { + innerText: 'fallback body', + textContent: 'fallback body', + }, +}; + +${bundle} + +return readOpenedMailText(item); +`)(); + + assert.match(text, /214203/); +}); + +test('openMailAndGetMessageText reads opened body text and returns to inbox', async () => { + const bundle = [ + extractFunction('normalizeText'), + extractFunction('collectOpenedMailTextCandidates'), + extractFunction('readOpenedMailText'), + extractFunction('returnToInbox'), + extractFunction('openMailAndGetMessageText'), + extractFunction('extractVerificationCode'), + ].join('\n'); + + const api = new Function(` +let inInbox = true; +let clickCount = 0; +const mailItem = { + click() { + clickCount += 1; + inInbox = false; + }, +}; +const inboxLink = { + click() { + inInbox = true; + }, +}; + +function getMailSubjectText() { + return 'Your temporary ChatGPT login code'; +} + +function getMailSenderText() { + return 'OpenAI'; +} + +const document = { + querySelector(selector) { + if (selector === '.nui-tree-item-text[title="收件箱"], [title="收件箱"]') return inboxLink; + return null; + }, + querySelectorAll(selector) { + if (selector === 'iframe') { + return []; + } + return inInbox ? [] : [{ innerText: 'Enter this temporary verification code to continue: 214203', textContent: 'Enter this temporary verification code to continue: 214203' }]; + }, + body: { + innerText: '', + textContent: '', + }, +}; + +function findMailItems() { + return inInbox ? [mailItem] : []; +} + +async function sleep() {} + +${bundle} + +return { + openMailAndGetMessageText, + getClickCount: () => clickCount, + isInInbox: () => inInbox, + mailItem, +}; +`)(); + + const text = await api.openMailAndGetMessageText(api.mailItem); + assert.match(text, /214203/); + assert.equal(api.getClickCount(), 1); + assert.equal(api.isInInbox(), true); +}); + +test('handlePollEmail ignores same-minute old snapshot mail before fallback', async () => { + const bundle = [ + extractFunction('normalizeText'), + extractFunction('normalizeMinuteTimestamp'), + extractFunction('handlePollEmail'), + ].join('\n'); + + const api = new Function(` +let currentItems = [{ id: 'old-mail' }]; +const seenCodes = new Set(); + +function findMailItems() { + return currentItems; +} + +function getCurrentMailIds(items = []) { + return new Set((items.length ? items : currentItems).map((item) => item.id)); +} + +function getMailItemId(item) { + return item.id; +} + +function getMailTimestamp() { + return new Date(2026, 3, 22, 22, 22, 0, 0).getTime(); +} + +function getMailSenderText() { + return 'OpenAI'; +} + +function getMailSubjectText() { + return 'Your temporary ChatGPT verification code'; +} + +function getMailRowText() { + return 'Your temporary ChatGPT verification code 911113 发件人 OpenAI'; +} + +function extractVerificationCode() { + return '911113'; +} + +async function waitForElement() { + return { click() {} }; +} +async function refreshInbox() {} +async function sleep() {} +function log() {} +function persistSeenCodes() {} +function scheduleEmailCleanup() {} + +${bundle} + +return { handlePollEmail }; +`)(); + + await assert.rejects( + () => api.handlePollEmail(4, { + senderFilters: ['openai'], + subjectFilters: ['verification'], + maxAttempts: 1, + intervalMs: 1, + filterAfterTimestamp: new Date(2026, 3, 22, 22, 22, 40, 0).getTime(), + }), + /未在 163 邮箱中找到新的匹配邮件/ + ); +}); + +test('handlePollEmail accepts a new same-minute mail that appears after the snapshot', async () => { + const bundle = [ + extractFunction('normalizeText'), + extractFunction('normalizeMinuteTimestamp'), + extractFunction('handlePollEmail'), + ].join('\n'); + + const api = new Function(` +const oldMail = { + id: 'old-mail', + getAttribute(name) { + if (name === 'aria-label') return 'Old verification mail 111111 发件人 OpenAI'; + return ''; + }, +}; +const newMail = { + id: 'new-mail', + getAttribute(name) { + if (name === 'aria-label') return 'Your temporary ChatGPT verification code 654321 发件人 OpenAI'; + return ''; + }, +}; +let refreshCount = 0; +let currentItems = [oldMail]; +const seenCodes = new Set(); + +function findMailItems() { + return currentItems; +} + +function getCurrentMailIds(items = []) { + return new Set((items.length ? items : currentItems).map((item) => item.id)); +} + +function getMailItemId(item) { + return item.id; +} + +function getMailTimestamp() { + return new Date(2026, 3, 22, 22, 22, 0, 0).getTime(); +} + +function getMailSenderText() { + return 'OpenAI'; +} + +function getMailSubjectText(item) { + return item.id === 'new-mail' ? 'Your temporary ChatGPT verification code' : 'Old verification mail'; +} + +function getMailRowText(item) { + return item.id === 'new-mail' + ? 'Your temporary ChatGPT verification code 654321 发件人 OpenAI' + : 'Old verification mail 111111 发件人 OpenAI'; +} + +function extractVerificationCode(text) { + const match = String(text || '').match(/(\\d{6})/); + return match ? match[1] : null; +} + +async function waitForElement() { + return { click() {} }; +} +async function refreshInbox() { + refreshCount += 1; + if (refreshCount >= 1) { + currentItems = [oldMail, newMail]; + } +} +async function sleep() {} +function log() {} +function persistSeenCodes() {} +function scheduleEmailCleanup() {} + +${bundle} + +return { handlePollEmail }; +`)(); + + const result = await api.handlePollEmail(4, { + senderFilters: ['openai'], + subjectFilters: ['verification'], + maxAttempts: 2, + intervalMs: 1, + filterAfterTimestamp: new Date(2026, 3, 22, 22, 22, 40, 0).getTime(), + }); + + assert.equal(result.code, '654321'); + assert.equal(result.mailId, 'new-mail'); +}); + +test('handlePollEmail falls back to row text when the subject node is missing', async () => { + const bundle = [ + extractFunction('normalizeText'), + extractFunction('normalizeMinuteTimestamp'), + extractFunction('handlePollEmail'), + ].join('\n'); + + const api = new Function(` +const matchingMail = { + id: 'mail-1', + getAttribute(name) { + if (name === 'aria-label') return 'OpenAI Your temporary ChatGPT verification code 123456'; + return ''; + }, +}; +const seenCodes = new Set(); + +function findMailItems() { + return [matchingMail]; +} + +function getCurrentMailIds() { + return new Set(); +} + +function getMailItemId(item) { + return item.id; +} + +function getMailTimestamp() { + return new Date(2026, 3, 22, 22, 22, 0, 0).getTime(); +} + +function getMailSenderText() { + return ''; +} + +function getMailSubjectText() { + return ''; +} + +function getMailRowText() { + return 'OpenAI Your temporary ChatGPT verification code 123456'; +} + +function extractVerificationCode(text) { + const match = String(text || '').match(/(\\d{6})/); + return match ? match[1] : null; +} + +async function waitForElement() { + return { click() {} }; +} +async function refreshInbox() {} +async function sleep() {} +function log() {} +function persistSeenCodes() {} +function scheduleEmailCleanup() {} +async function openMailAndGetMessageText() { + return ''; +} + +${bundle} + +return { handlePollEmail }; +`)(); + + const result = await api.handlePollEmail(8, { + senderFilters: ['openai'], + subjectFilters: ['verification'], + maxAttempts: 1, + intervalMs: 1, + filterAfterTimestamp: 0, + }); + + assert.equal(result.code, '123456'); + assert.equal(result.mailId, 'mail-1'); +}); + +test('handlePollEmail opens matching mail body when preview has no code', async () => { + const bundle = [ + extractFunction('normalizeText'), + extractFunction('normalizeMinuteTimestamp'), + extractFunction('handlePollEmail'), + ].join('\n'); + + const api = new Function(` +const matchingMail = { + id: 'mail-body-1', + getAttribute(name) { + if (name === 'aria-label') return 'OpenAI Your temporary ChatGPT login code'; + return ''; + }, +}; +const seenCodes = new Set(); +let openedCount = 0; + +function findMailItems() { + return [matchingMail]; +} + +function getCurrentMailIds() { + return new Set(); +} + +function getMailItemId(item) { + return item.id; +} + +function getMailTimestamp() { + return new Date(2026, 3, 22, 22, 49, 0, 0).getTime(); +} + +function getMailSenderText() { + return 'OpenAI'; +} + +function getMailSubjectText() { + return 'Your temporary ChatGPT login code'; +} + +function getMailRowText() { + return 'OpenAI Your temporary ChatGPT login code'; +} + +function extractVerificationCode(text) { + const match = String(text || '').match(/(\\d{6})/); + return match ? match[1] : null; +} + +async function waitForElement() { + return { click() {} }; +} +async function refreshInbox() {} +async function sleep() {} +function log() {} +function persistSeenCodes() {} +function scheduleEmailCleanup() {} +async function openMailAndGetMessageText() { + openedCount += 1; + return 'Enter this temporary verification code to continue: 214203'; +} + +${bundle} + +return { handlePollEmail, getOpenedCount: () => openedCount }; +`)(); + + const result = await api.handlePollEmail(8, { + senderFilters: ['openai'], + subjectFilters: ['verification', 'login'], + maxAttempts: 1, + intervalMs: 1, + filterAfterTimestamp: 0, + }); + + assert.equal(result.code, '214203'); + assert.equal(result.mailId, 'mail-body-1'); + assert.equal(api.getOpenedCount(), 1); +});