diff --git a/background.js b/background.js index 26976bb..c9604ba 100644 --- a/background.js +++ b/background.js @@ -1751,14 +1751,14 @@ async function submitVerificationCode(step, code) { return result || {}; } -async function resolveVerificationStep(step, state, mail) { +async function resolveVerificationStep(step, state, mail, options = {}) { const stateKey = getVerificationCodeStateKey(step); const rejectedCodes = new Set(); if (state[stateKey]) { rejectedCodes.add(state[stateKey]); } - let nextFilterAfterTimestamp = null; + let nextFilterAfterTimestamp = options.filterAfterTimestamp ?? null; const maxSubmitAttempts = 3; for (let attempt = 1; attempt <= maxSubmitAttempts; attempt++) { @@ -1799,6 +1799,7 @@ async function resolveVerificationStep(step, state, mail) { async function executeStep4(state) { const mail = getMailConfig(state); if (mail.error) throw new Error(mail.error); + const stepStartedAt = Date.now(); const signupTabId = await getTabId('signup-page'); if (!signupTabId) { throw new Error('认证页面标签页已关闭,无法继续步骤 4。'); @@ -1850,7 +1851,7 @@ async function executeStep4(state) { }); } - await resolveVerificationStep(4, state, mail); + await resolveVerificationStep(4, state, mail, { filterAfterTimestamp: stepStartedAt }); return; } @@ -1921,6 +1922,7 @@ async function executeStep6(state) { async function executeStep7(state) { const mail = getMailConfig(state); if (mail.error) throw new Error(mail.error); + const stepStartedAt = Date.now(); const authTabId = await getTabId('signup-page'); if (authTabId) { @@ -1964,7 +1966,7 @@ async function executeStep7(state) { }); } - await resolveVerificationStep(7, state, mail); + await resolveVerificationStep(7, state, mail, { filterAfterTimestamp: stepStartedAt }); return; } diff --git a/content/mail-163.js b/content/mail-163.js index 2d1f65e..5455a5a 100644 --- a/content/mail-163.js +++ b/content/mail-163.js @@ -82,15 +82,82 @@ function getCurrentMailIds() { return ids; } +function normalizeMinuteTimestamp(timestamp) { + if (!Number.isFinite(timestamp) || timestamp <= 0) return 0; + const date = new Date(timestamp); + date.setSeconds(0, 0); + return date.getTime(); +} + +function parseMail163Timestamp(rawText) { + const text = (rawText || '').replace(/\s+/g, ' ').trim(); + if (!text) return null; + + let match = text.match(/(\d{4})年(\d{1,2})月(\d{1,2})日\s+(\d{1,2}):(\d{2})/); + if (match) { + const [, year, month, day, hour, minute] = match; + return new Date( + Number(year), + Number(month) - 1, + Number(day), + Number(hour), + Number(minute), + 0, + 0 + ).getTime(); + } + + match = text.match(/\b(\d{1,2}):(\d{2})\b/); + 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(); + } + + 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); + + const titledNodes = item.querySelectorAll('[title]'); + titledNodes.forEach((node) => { + const title = node.getAttribute('title'); + if (title) candidates.push(title); + }); + + for (const candidate of candidates) { + const parsed = parseMail163Timestamp(candidate); + if (parsed) return parsed; + } + + return null; +} + // ============================================================ // Email Polling // ============================================================ async function handlePollEmail(step, payload) { - const { senderFilters, subjectFilters, maxAttempts, intervalMs, excludeCodes = [] } = payload; + const { senderFilters, subjectFilters, maxAttempts, intervalMs, excludeCodes = [], filterAfterTimestamp = 0 } = payload; const excludedCodeSet = new Set(excludeCodes.filter(Boolean)); + const filterAfterMinute = normalizeMinuteTimestamp(Number(filterAfterTimestamp) || 0); log(`步骤 ${step}:开始轮询 163 邮箱(最多 ${maxAttempts} 次)`); + if (filterAfterMinute) { + log(`步骤 ${step}:仅尝试 ${new Date(filterAfterMinute).toLocaleString('zh-CN', { hour12: false })} 及之后时间的邮件。`); + } // Click inbox in sidebar to ensure we're in inbox view log(`步骤 ${step}:正在等待侧边栏加载...`); @@ -142,8 +209,16 @@ async function handlePollEmail(step, payload) { for (const item of allItems) { const id = item.getAttribute('id') || ''; + const mailTimestamp = getMailTimestamp(item); + const mailMinute = normalizeMinuteTimestamp(mailTimestamp || 0); + const passesTimeFilter = !filterAfterMinute || (mailMinute && mailMinute >= filterAfterMinute); + const shouldBypassOldSnapshot = Boolean(filterAfterMinute && passesTimeFilter && mailMinute > 0); - if (!useFallback && existingMailIds.has(id)) continue; + if (!passesTimeFilter) { + continue; + } + + if (!useFallback && !shouldBypassOldSnapshot && existingMailIds.has(id)) continue; const senderEl = item.querySelector('.nui-user'); const sender = senderEl ? senderEl.textContent.toLowerCase() : ''; @@ -164,7 +239,8 @@ async function handlePollEmail(step, payload) { seenCodes.add(code); persistSeenCodes(); const source = useFallback && existingMailIds.has(id) ? '回退匹配邮件' : '新邮件'; - log(`步骤 ${step}:已找到验证码:${code}(来源:${source},主题:${subject.slice(0, 40)})`, 'ok'); + const timeLabel = mailTimestamp ? `,时间:${new Date(mailTimestamp).toLocaleString('zh-CN', { hour12: false })}` : ''; + log(`步骤 ${step}:已找到验证码:${code}(来源:${source}${timeLabel},主题:${subject.slice(0, 40)})`, 'ok'); // Delete this email via right-click menu, WAIT for it to finish before returning await deleteEmail(item, step);