Просмотр исходного кода

Improve A4Sky verification code matching

chendeben 4 месяцев назад
Родитель
Сommit
30b03207d3
2 измененных файлов с 665 добавлено и 0 удалено
  1. 408 0
      content/phplife-mail.js
  2. 257 0
      tests/phplife-mail-content.test.js

+ 408 - 0
content/phplife-mail.js

@@ -210,6 +210,162 @@ function extractEmails(text) {
   return [...new Set(matches.map((item) => item.toLowerCase()))];
 }
 
+function collectPayloadStringFragments(value, bucket = []) {
+  if (typeof value === 'string') {
+    const normalized = value.trim();
+    if (normalized) {
+      bucket.push(normalized);
+    }
+    return bucket;
+  }
+
+  if (Array.isArray(value)) {
+    value.forEach((item) => collectPayloadStringFragments(item, bucket));
+    return bucket;
+  }
+
+  if (value && typeof value === 'object') {
+    Object.values(value).forEach((item) => collectPayloadStringFragments(item, bucket));
+  }
+
+  return bucket;
+}
+
+function extractRemoteExecText(rawText = '') {
+  try {
+    const parsed = JSON.parse(String(rawText || ''));
+    const execText = String(parsed?.exec || '').trim();
+    return execText || String(rawText || '');
+  } catch {
+    return String(rawText || '');
+  }
+}
+
+function readBalancedJsonObject(source = '', startIndex = 0) {
+  if (String(source || '')[startIndex] !== '{') {
+    return null;
+  }
+
+  let depth = 0;
+  let inString = false;
+  let escaped = false;
+  for (let index = startIndex; index < source.length; index += 1) {
+    const ch = source[index];
+    if (escaped) {
+      escaped = false;
+      continue;
+    }
+
+    if (ch === '\\') {
+      escaped = true;
+      continue;
+    }
+
+    if (ch === '"') {
+      inString = !inString;
+      continue;
+    }
+
+    if (inString) {
+      continue;
+    }
+
+    if (ch === '{') depth += 1;
+    if (ch === '}') {
+      depth -= 1;
+      if (depth === 0) {
+        return {
+          text: source.slice(startIndex, index + 1),
+          endIndex: index + 1,
+        };
+      }
+    }
+  }
+
+  return null;
+}
+
+function parseRemoteMessageRowEntries(rawText = '') {
+  const source = extractRemoteExecText(rawText);
+  const entries = [];
+  const marker = 'this.add_message_row(';
+  let cursor = 0;
+
+  while (cursor < source.length) {
+    const start = source.indexOf(marker, cursor);
+    if (start < 0) {
+      break;
+    }
+
+    const argsStart = start + marker.length;
+    const firstComma = source.indexOf(',', argsStart);
+    if (firstComma < 0) {
+      break;
+    }
+
+    const uid = String(source.slice(argsStart, firstComma).trim() || '');
+    const rowObjectStart = source.indexOf('{', firstComma);
+    if (rowObjectStart < 0) {
+      break;
+    }
+
+    const rowObject = readBalancedJsonObject(source, rowObjectStart);
+    if (!rowObject) {
+      break;
+    }
+
+    const metaObjectStart = source.indexOf('{', rowObject.endIndex);
+    const metaObject = metaObjectStart >= 0 ? readBalancedJsonObject(source, metaObjectStart) : null;
+
+    try {
+      const rowData = JSON.parse(rowObject.text);
+      const metaData = metaObject ? JSON.parse(metaObject.text) : {};
+      entries.push({ uid, rowData, metaData });
+    } catch {
+      // Ignore malformed row and continue scanning.
+    }
+
+    cursor = metaObject?.endIndex || rowObject.endIndex;
+  }
+
+  return entries;
+}
+
+function extractRemotePayloadFragments(rawText = '') {
+  const fragments = [];
+  const addFragment = (value) => {
+    const normalized = String(value || '').trim();
+    if (normalized) {
+      fragments.push(normalized);
+    }
+  };
+
+  let parsedJson = null;
+  try {
+    parsedJson = JSON.parse(rawText);
+  } catch {
+    parsedJson = null;
+  }
+
+  if (parsedJson) {
+    collectPayloadStringFragments(parsedJson.exec || parsedJson, fragments);
+  } else {
+    addFragment(rawText);
+  }
+
+  const expanded = [];
+  fragments.forEach((fragment) => {
+    const rowMatches = String(fragment).match(/<tr\b[\s\S]*?<\/tr>/gi);
+    if (rowMatches?.length) {
+      expanded.push(...rowMatches);
+    } else {
+      expanded.push(fragment);
+    }
+  });
+
+  return [...new Set(expanded.map((item) => String(item || '').trim()).filter(Boolean))];
+}
+
 function getTargetEmailMatchState(text, targetEmail) {
   const normalizedTarget = String(targetEmail || '').trim().toLowerCase();
   if (!normalizedTarget) {
@@ -232,6 +388,56 @@ function getTargetEmailMatchState(text, targetEmail) {
   };
 }
 
+function normalizeFragmentText(fragment = '') {
+  const source = String(fragment || '');
+  if (!source) return '';
+  if (!/[<>]/.test(source)) {
+    return normalizeText(source);
+  }
+
+  try {
+    const parser = new DOMParser();
+    const doc = parser.parseFromString(`<table><tbody>${source}</tbody></table>`, 'text/html');
+    return normalizeText(doc.body?.textContent || doc.documentElement?.textContent || source);
+  } catch {
+    return normalizeText(source);
+  }
+}
+
+function buildRemoteRowDetails(entry = {}) {
+  const rowData = entry?.rowData || {};
+  const metaData = entry?.metaData || {};
+  const subject = normalizeText(rowData.subject || '');
+  const from = normalizeFragmentText(rowData.fromto || '');
+  const to = normalizeFragmentText(rowData.to || '');
+  const dateText = normalizeText(rowData.date || '');
+  const combinedText = normalizeText([subject, from, to, dateText].join(' '));
+
+  return {
+    uid: String(entry?.uid || ''),
+    subject,
+    from,
+    to,
+    dateText,
+    ctype: String(metaData?.ctype || ''),
+    emailTimestamp: parseRoundcubeTimestamp(dateText),
+    codes: extractVerificationCodes(combinedText),
+    combinedText,
+  };
+}
+
+function findExactTargetRemoteRows(entries = [], targetEmail = '') {
+  const normalizedTarget = String(targetEmail || '').trim().toLowerCase();
+  if (!normalizedTarget) {
+    return [];
+  }
+
+  return entries.filter((details) => {
+    const targetState = getTargetEmailMatchState(details?.combinedText || '', normalizedTarget);
+    return targetState.hasExplicitEmail && targetState.matches;
+  });
+}
+
 function matchesMailFilters(text, senderFilters = [], subjectFilters = []) {
   const normalizedText = String(text || '').toLowerCase();
   const senderMatched = senderFilters.some((filter) => normalizedText.includes(String(filter || '').toLowerCase()));
@@ -338,6 +544,201 @@ function scoreRowCandidate(details, payload = {}) {
   return score;
 }
 
+function scoreRemotePayloadCandidate(text = '', payload = {}) {
+  const targetState = getTargetEmailMatchState(text, payload.targetEmail);
+  let score = 0;
+
+  if (targetState.matches) score += targetState.hasExplicitEmail ? 10 : 2;
+  if (matchesMailFilters(text, payload.senderFilters, payload.subjectFilters)) score += 5;
+  if (/openai|chatgpt|verification|verify|验证码|登录|login/i.test(text)) score += 4;
+  if (shouldOpenRowForCodeDetection({ subject: text })) score += 2;
+
+  return { score, targetState };
+}
+
+function buildRemoteMailApiUrl(action = 'list', options = {}) {
+  const {
+    targetEmail = '',
+    unlockPrefix = 'loading',
+  } = options;
+  const requestAt = Date.now();
+  const url = new URL(`/?_task=mail&_action=${encodeURIComponent(action)}`, location.origin);
+  url.searchParams.set('_mbox', 'INBOX');
+  url.searchParams.set('_remote', '1');
+  url.searchParams.set('_unlock', `${unlockPrefix}${requestAt}`);
+  url.searchParams.set('_', String(requestAt));
+  if (action === 'list') {
+    url.searchParams.set('_refresh', '1');
+  }
+  if (action === 'search' && targetEmail) {
+    url.searchParams.set('_filter', 'ALL');
+    url.searchParams.set('_interval', '');
+    url.searchParams.set('_q', targetEmail);
+    url.searchParams.set('_headers', 'to');
+    url.searchParams.set('_scope', 'base');
+  }
+  return url;
+}
+
+async function requestRemoteMailApi(action, options = {}) {
+  const url = buildRemoteMailApiUrl(action, options);
+
+  const controller = new AbortController();
+  const timeoutId = setTimeout(() => controller.abort(new Error('timeout')), 15000);
+
+  try {
+    const response = await fetch(url.toString(), {
+      credentials: 'same-origin',
+      headers: {
+        Accept: 'application/json, text/javascript, */*; q=0.01',
+        'X-Requested-With': 'XMLHttpRequest',
+      },
+      cache: 'no-store',
+      signal: controller.signal,
+    });
+
+    if (!response.ok) {
+      throw new Error(`HTTP ${response.status}`);
+    }
+
+    return await response.text();
+  } catch (error) {
+    if (error?.name === 'AbortError') {
+      throw new Error(`A4Sky ${action} 接口请求超时`);
+    }
+    throw new Error(`A4Sky ${action} 接口请求失败:${error?.message || error}`);
+  } finally {
+    clearTimeout(timeoutId);
+  }
+}
+
+async function requestRemoteMessageList(step) {
+  return requestRemoteMailApi('list', { unlockPrefix: 'loading' });
+}
+
+async function requestRemoteMessageSearch(step, targetEmail = '') {
+  if (!String(targetEmail || '').trim()) {
+    return '';
+  }
+  return requestRemoteMailApi('search', {
+    targetEmail: String(targetEmail || '').trim(),
+    unlockPrefix: 'loading',
+  });
+}
+
+function findVerificationCodeFromRemotePayload(rawText, payload, excludedCodeSet = new Set(), filterAfterMinute = 0) {
+  const parsedRows = parseRemoteMessageRowEntries(rawText)
+    .map((entry) => buildRemoteRowDetails(entry));
+  const exactTargetRows = findExactTargetRemoteRows(parsedRows, payload?.targetEmail);
+  const candidateRows = exactTargetRows.length ? exactTargetRows : parsedRows;
+
+  const rowCandidates = candidateRows
+    .map((details) => {
+      const match = matchesCurrentMessage(details, payload, excludedCodeSet, filterAfterMinute);
+      return match.matched
+        ? {
+          code: match.code,
+          emailTimestamp: match.emailTimestamp,
+          score: scoreRowCandidate(details, payload),
+          uid: Number(details.uid || 0) || 0,
+        }
+        : null;
+    })
+    .filter(Boolean)
+    .sort((left, right) => {
+      if (left.score !== right.score) return right.score - left.score;
+      if (left.emailTimestamp !== right.emailTimestamp) return right.emailTimestamp - left.emailTimestamp;
+      return right.uid - left.uid;
+    });
+
+  if (rowCandidates.length) {
+    return rowCandidates[0];
+  }
+
+  if (exactTargetRows.length) {
+    return null;
+  }
+
+  const fragments = extractRemotePayloadFragments(rawText);
+  const candidates = [];
+
+  fragments.forEach((fragment) => {
+    const text = normalizeFragmentText(fragment);
+    if (!text) return;
+
+    const { score, targetState } = scoreRemotePayloadCandidate(text, payload);
+    if (payload?.targetEmail && targetState.hasExplicitEmail && !targetState.matches) {
+      return;
+    }
+    if (score <= 0) return;
+
+    const codes = extractVerificationCodes(text);
+    const code = selectCandidateCode(codes, excludedCodeSet);
+    if (!code) return;
+
+    candidates.push({
+      code,
+      score,
+      text,
+      targetMatched: targetState.matches,
+      explicitTarget: targetState.hasExplicitEmail,
+    });
+  });
+
+  candidates.sort((left, right) => {
+    if (left.score !== right.score) return right.score - left.score;
+    if (left.explicitTarget !== right.explicitTarget) return Number(right.explicitTarget) - Number(left.explicitTarget);
+    if (left.targetMatched !== right.targetMatched) return Number(right.targetMatched) - Number(left.targetMatched);
+    return left.text.length - right.text.length;
+  });
+
+  return candidates[0] || null;
+}
+
+async function tryReadRemoteMessageList(step, payload, excludedCodeSet) {
+  try {
+    const filterAfterMinute = normalizeMinuteTimestamp(Number(payload?.filterAfterTimestamp) || 0);
+    const targetEmail = String(payload?.targetEmail || '').trim().toLowerCase();
+
+    if (targetEmail) {
+      const searchText = await requestRemoteMessageSearch(step, targetEmail);
+      const searchResult = findVerificationCodeFromRemotePayload(
+        searchText,
+        payload,
+        excludedCodeSet,
+        filterAfterMinute
+      );
+      if (searchResult?.code) {
+        log(`步骤 ${step}:已直接从 mail.phplife.net 搜索接口命中验证码邮件。`, 'ok');
+        return {
+          code: searchResult.code,
+          emailTimestamp: Date.now(),
+        };
+      }
+    }
+
+    const rawText = await requestRemoteMessageList(step);
+    const result = findVerificationCodeFromRemotePayload(
+      rawText,
+      payload,
+      excludedCodeSet,
+      filterAfterMinute
+    );
+    if (result?.code) {
+      log(`步骤 ${step}:已直接从 mail.phplife.net 列表接口命中验证码邮件。`, 'ok');
+      return {
+        code: result.code,
+        emailTimestamp: Date.now(),
+      };
+    }
+
+    return null;
+  } catch (error) {
+    console.warn(PHPLIFE_MAIL_PREFIX, 'Remote list request failed:', error?.message || error);
+    return null;
+  }
+}
+
 function shouldOpenRowForCodeDetection(details = {}) {
   const subject = normalizeText(details?.subject || '');
   if (!subject) {
@@ -545,6 +946,13 @@ async function handlePollEmail(step, payload) {
 
     await refreshMessageList();
 
+    const remoteListResult = await tryReadRemoteMessageList(step, payload, excludedCodeSet);
+    if (remoteListResult?.code) {
+      seenCodes.add(remoteListResult.code);
+      await persistSeenCodes();
+      return remoteListResult;
+    }
+
     const currentMessageResult = await tryReadCurrentMessage(payload, excludedCodeSet, filterAfterMinute);
     if (currentMessageResult?.matched) {
       seenCodes.add(currentMessageResult.code);

+ 257 - 0
tests/phplife-mail-content.test.js

@@ -36,8 +36,33 @@ function extractFunction(name) {
 
   let depth = 0;
   let end = braceStart;
+  let inString = null;
+  let escaped = false;
   for (; end < source.length; end += 1) {
     const ch = source[end];
+
+    if (escaped) {
+      escaped = false;
+      continue;
+    }
+
+    if (ch === '\\') {
+      escaped = true;
+      continue;
+    }
+
+    if (inString) {
+      if (ch === inString) {
+        inString = null;
+      }
+      continue;
+    }
+
+    if (ch === '"' || ch === '\'' || ch === '`') {
+      inString = ch;
+      continue;
+    }
+
     if (ch === '{') depth += 1;
     if (ch === '}') {
       depth -= 1;
@@ -312,3 +337,235 @@ return {
   assert.equal(result.code, '112233');
   assert.equal(api.getOpenedCount(), 1);
 });
+
+test('phplife remote payload parser extracts target mailbox verification code directly from refresh API payload', () => {
+  const bundle = [
+    extractFunction('normalizeText'),
+    extractFunction('parseRoundcubeTimestamp'),
+    extractFunction('extractVerificationCodes'),
+    extractFunction('extractEmails'),
+    extractFunction('collectPayloadStringFragments'),
+    extractFunction('extractRemoteExecText'),
+    extractFunction('readBalancedJsonObject'),
+    extractFunction('parseRemoteMessageRowEntries'),
+    extractFunction('extractRemotePayloadFragments'),
+    extractFunction('getTargetEmailMatchState'),
+    extractFunction('matchesMailFilters'),
+    extractFunction('normalizeFragmentText'),
+    extractFunction('buildRemoteRowDetails'),
+    extractFunction('findExactTargetRemoteRows'),
+    extractFunction('scoreRowCandidate'),
+    extractFunction('normalizeMinuteTimestamp'),
+    extractFunction('matchesCurrentMessage'),
+    extractFunction('shouldOpenRowForCodeDetection'),
+    extractFunction('scoreRemotePayloadCandidate'),
+    extractFunction('selectCandidateCode'),
+    extractFunction('findVerificationCodeFromRemotePayload'),
+  ].join('\n');
+
+  const api = new Function(`${bundle}
+const seenCodes = new Set();
+class DOMParser {
+  parseFromString(text) {
+    return {
+      body: { textContent: String(text || '').replace(/<[^>]+>/g, ' ') },
+      documentElement: { textContent: String(text || '').replace(/<[^>]+>/g, ' ') },
+    };
+  }
+}
+return { findVerificationCodeFromRemotePayload };
+`)();
+
+  const payload = JSON.stringify({
+    exec: [
+      "<tr><td class='subject' title='Your ChatGPT code is 445566'>Your ChatGPT code is 445566</td><td class='to'>n20260419170738@a4sky.com</td></tr>",
+    ],
+  });
+
+  const result = api.findVerificationCodeFromRemotePayload(payload, {
+    senderFilters: ['openai'],
+    subjectFilters: ['code', 'verification', '验证码'],
+    targetEmail: 'n20260419170738@a4sky.com',
+  }, new Set(), 0);
+
+  assert.equal(result.code, '445566');
+});
+
+test('phplife remote payload parser ignores codes for other mailbox addresses', () => {
+  const bundle = [
+    extractFunction('normalizeText'),
+    extractFunction('parseRoundcubeTimestamp'),
+    extractFunction('extractVerificationCodes'),
+    extractFunction('extractEmails'),
+    extractFunction('collectPayloadStringFragments'),
+    extractFunction('extractRemoteExecText'),
+    extractFunction('readBalancedJsonObject'),
+    extractFunction('parseRemoteMessageRowEntries'),
+    extractFunction('extractRemotePayloadFragments'),
+    extractFunction('getTargetEmailMatchState'),
+    extractFunction('matchesMailFilters'),
+    extractFunction('normalizeFragmentText'),
+    extractFunction('buildRemoteRowDetails'),
+    extractFunction('findExactTargetRemoteRows'),
+    extractFunction('scoreRowCandidate'),
+    extractFunction('normalizeMinuteTimestamp'),
+    extractFunction('matchesCurrentMessage'),
+    extractFunction('shouldOpenRowForCodeDetection'),
+    extractFunction('scoreRemotePayloadCandidate'),
+    extractFunction('selectCandidateCode'),
+    extractFunction('findVerificationCodeFromRemotePayload'),
+  ].join('\n');
+
+  const api = new Function(`${bundle}
+const seenCodes = new Set();
+class DOMParser {
+  parseFromString(text) {
+    return {
+      body: { textContent: String(text || '').replace(/<[^>]+>/g, ' ') },
+      documentElement: { textContent: String(text || '').replace(/<[^>]+>/g, ' ') },
+    };
+  }
+}
+return { findVerificationCodeFromRemotePayload };
+`)();
+
+  const payload = JSON.stringify({
+    exec: [
+      "<tr><td class='subject'>Your ChatGPT code is 111111</td><td class='to'>other@a4sky.com</td></tr>",
+      "<tr><td class='subject'>Your ChatGPT code is 222222</td><td class='to'>target@a4sky.com</td></tr>",
+    ],
+  });
+
+  const result = api.findVerificationCodeFromRemotePayload(payload, {
+    senderFilters: ['openai'],
+    subjectFilters: ['code', 'verification', '验证码'],
+    targetEmail: 'target@a4sky.com',
+  }, new Set(), 0);
+
+  assert.equal(result.code, '222222');
+});
+
+test('phplife remote payload parser does not fall back to other mailbox rows when target mailbox rows exist without direct code', () => {
+  const bundle = [
+    extractFunction('normalizeText'),
+    extractFunction('parseRoundcubeTimestamp'),
+    extractFunction('extractVerificationCodes'),
+    extractFunction('extractEmails'),
+    extractFunction('collectPayloadStringFragments'),
+    extractFunction('extractRemoteExecText'),
+    extractFunction('readBalancedJsonObject'),
+    extractFunction('parseRemoteMessageRowEntries'),
+    extractFunction('extractRemotePayloadFragments'),
+    extractFunction('getTargetEmailMatchState'),
+    extractFunction('matchesMailFilters'),
+    extractFunction('normalizeFragmentText'),
+    extractFunction('buildRemoteRowDetails'),
+    extractFunction('findExactTargetRemoteRows'),
+    extractFunction('scoreRowCandidate'),
+    extractFunction('normalizeMinuteTimestamp'),
+    extractFunction('matchesCurrentMessage'),
+    extractFunction('shouldOpenRowForCodeDetection'),
+    extractFunction('scoreRemotePayloadCandidate'),
+    extractFunction('selectCandidateCode'),
+    extractFunction('findVerificationCodeFromRemotePayload'),
+  ].join('\n');
+
+  const api = new Function(`${bundle}
+const seenCodes = new Set();
+class DOMParser {
+  parseFromString(text) {
+    return {
+      body: { textContent: String(text || '').replace(/<[^>]+>/g, ' ') },
+      documentElement: { textContent: String(text || '').replace(/<[^>]+>/g, ' ') },
+    };
+  }
+}
+return { findVerificationCodeFromRemotePayload };
+`)();
+
+  const payload = JSON.stringify({
+    exec: [
+      "this.add_message_row(900,{\"subject\":\"Your temporary ChatGPT login code\",\"fromto\":\"<span title=\\\"otp@tm1.openai.com\\\">OpenAI<\\/span>\",\"date\":\"今天 18:24\",\"to\":\"<span title=\\\"target@a4sky.com\\\">target@a4sky.com<\\/span>\"},{\"ctype\":\"text/html\",\"mbox\":\"INBOX\"},false);",
+      "this.add_message_row(899,{\"subject\":\"Your ChatGPT code is 999999\",\"fromto\":\"<span title=\\\"otp@tm1.openai.com\\\">OpenAI<\\/span>\",\"date\":\"今天 18:24\",\"to\":\"<span title=\\\"other@a4sky.com\\\">other@a4sky.com<\\/span>\"},{\"ctype\":\"text/html\",\"mbox\":\"INBOX\"},false);",
+    ],
+  });
+
+  const result = api.findVerificationCodeFromRemotePayload(payload, {
+    senderFilters: ['openai'],
+    subjectFilters: ['code', 'verification', '验证码'],
+    targetEmail: 'target@a4sky.com',
+  }, new Set(), 0);
+
+  assert.equal(result, null);
+});
+
+test('phplife tryReadRemoteMessageList prefers search payload scoped to target mailbox', async () => {
+  const bundle = [
+    extractFunction('normalizeText'),
+    extractFunction('parseRoundcubeTimestamp'),
+    extractFunction('extractVerificationCodes'),
+    extractFunction('extractEmails'),
+    extractFunction('collectPayloadStringFragments'),
+    extractFunction('extractRemoteExecText'),
+    extractFunction('readBalancedJsonObject'),
+    extractFunction('parseRemoteMessageRowEntries'),
+    extractFunction('extractRemotePayloadFragments'),
+    extractFunction('getTargetEmailMatchState'),
+    extractFunction('matchesMailFilters'),
+    extractFunction('normalizeFragmentText'),
+    extractFunction('buildRemoteRowDetails'),
+    extractFunction('findExactTargetRemoteRows'),
+    extractFunction('scoreRowCandidate'),
+    extractFunction('normalizeMinuteTimestamp'),
+    extractFunction('matchesCurrentMessage'),
+    extractFunction('shouldOpenRowForCodeDetection'),
+    extractFunction('scoreRemotePayloadCandidate'),
+    extractFunction('selectCandidateCode'),
+    extractFunction('findVerificationCodeFromRemotePayload'),
+    extractFunction('tryReadRemoteMessageList'),
+  ].join('\n');
+
+  const api = new Function(`${bundle}
+const PHPLIFE_MAIL_PREFIX = '[MultiPage:mail-phplife]';
+function log() {}
+const seenCodes = new Set();
+class DOMParser {
+  parseFromString(text) {
+    return {
+      body: { textContent: String(text || '').replace(/<[^>]+>/g, ' ') },
+      documentElement: { textContent: String(text || '').replace(/<[^>]+>/g, ' ') },
+    };
+  }
+}
+async function requestRemoteMessageSearch() {
+  const row = {
+    subject: '你的 ChatGPT 代码为 991895',
+    fromto: '<span title="otp@tm1.openai.com">OpenAI</span>',
+    date: '今天 18:56',
+    to: '<span title="n20260419185525@a4sky.com">n20260419185525@a4sky.com</span>',
+  };
+  const meta = {
+    ctype: 'text/html',
+    mbox: 'INBOX',
+  };
+  return JSON.stringify({
+    exec: [
+      'this.add_message_row(524,' + JSON.stringify(row) + ',' + JSON.stringify(meta) + ',false);'
+    ],
+  });
+}
+async function requestRemoteMessageList() {
+  throw new Error('list api should not be needed when search already matched');
+}
+return { tryReadRemoteMessageList };
+`)();
+
+  const result = await api.tryReadRemoteMessageList(4, {
+    senderFilters: ['openai'],
+    subjectFilters: ['code', 'verification', '验证码'],
+    targetEmail: 'n20260419185525@a4sky.com',
+    filterAfterTimestamp: 0,
+  }, new Set());
+
+  assert.equal(result.code, '991895');
+});