Parcourir la source

feat: 添加邮件刷新和收件箱链接查找功能,并增加相关测试用例

QLHazyCoder il y a 4 mois
Parent
commit
7290649930
2 fichiers modifiés avec 276 ajouts et 3 suppressions
  1. 55 3
      content/mail-2925.js
  2. 221 0
      tests/mail-2925-content.test.js

+ 55 - 3
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) {

+ 221 - 0
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);
+});