Эх сурвалжийг харах

feat: 添加邮件时间戳解析功能,优化邮件轮询逻辑,支持时间过滤

QLHazyCoder 4 сар өмнө
parent
commit
a86f6d4007
2 өөрчлөгдсөн 85 нэмэгдсэн , 7 устгасан
  1. 6 4
      background.js
  2. 79 3
      content/mail-163.js

+ 6 - 4
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;
 }
 

+ 79 - 3
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 (!passesTimeFilter) {
+        continue;
+      }
 
-      if (!useFallback && existingMailIds.has(id)) 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);