// content/phplife-mail.js — Content script for A4Sky mailbox on mail.phplife.net
// Injected dynamically on: mail.phplife.net
const PHPLIFE_MAIL_PREFIX = '[MultiPage:mail-phplife]';
const isTopFrame = window === window.top;
console.log(PHPLIFE_MAIL_PREFIX, 'Content script loaded on', location.href, 'frame:', isTopFrame ? 'top' : 'child');
if (!isTopFrame) {
console.log(PHPLIFE_MAIL_PREFIX, 'Skipping child frame');
} else {
let seenCodes = new Set();
let waitingLoginLogged = false;
async function loadSeenCodes() {
try {
const data = await chrome.storage.session.get('seenPhplifeCodes');
if (Array.isArray(data.seenPhplifeCodes)) {
seenCodes = new Set(data.seenPhplifeCodes.filter(Boolean));
}
} catch (err) {
console.warn(PHPLIFE_MAIL_PREFIX, 'Could not load seen codes:', err?.message || err);
}
}
loadSeenCodes();
async function persistSeenCodes() {
try {
await chrome.storage.session.set({ seenPhplifeCodes: [...seenCodes] });
} catch (err) {
console.warn(PHPLIFE_MAIL_PREFIX, 'Could not persist seen codes:', err?.message || err);
}
}
chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
if (message.type === 'POLL_EMAIL') {
resetStopState();
handlePollEmail(message.step, message.payload).then((result) => {
sendResponse(result);
}).catch((err) => {
if (isStopError(err)) {
log(`步骤 ${message.step}:已被用户停止。`, 'warn');
sendResponse({ stopped: true, error: err.message });
return;
}
log(`步骤 ${message.step}:A4Sky 邮箱轮询失败:${err.message}`, 'warn');
sendResponse({ error: err.message });
});
return true;
}
});
function normalizeText(value) {
return String(value || '').replace(/\s+/g, ' ').trim();
}
function sleep(ms) {
return new Promise((resolve, reject) => {
if (flowStopped) {
reject(new Error(STOP_ERROR_MESSAGE));
return;
}
setTimeout(() => {
if (flowStopped) {
reject(new Error(STOP_ERROR_MESSAGE));
return;
}
resolve();
}, ms);
});
}
function isVisibleElement(element) {
if (!element) return false;
const style = window.getComputedStyle(element);
if (style.display === 'none' || style.visibility === 'hidden') return false;
const rect = element.getBoundingClientRect();
return rect.width > 0 && rect.height > 0;
}
function getRcmailEnv() {
try {
return window.rcmail?.env || null;
} catch {
return null;
}
}
function isLoginPageLikely() {
const url = location.href.toLowerCase();
if (/[_-]task=login|[?&]_action=login\b|\/login\b/.test(url)) {
return true;
}
const passwordInput = document.querySelector('input[type="password"]');
const loginForm = document.querySelector('form[action*="login"], form[name*="login"], #login-form, .login-form');
const loginButton = Array.from(document.querySelectorAll('button, input[type="submit"], a')).find((element) => {
const text = normalizeText(
element?.textContent
|| element?.value
|| element?.getAttribute?.('title')
|| element?.getAttribute?.('aria-label')
|| ''
);
return /登录|登入|sign in|log in/i.test(text);
});
const mailboxList = document.querySelector('#mailboxlist');
const messageList = document.querySelector('#messagelist');
const hasMailUi = mailboxList || messageList || getRcmailEnv()?.task === 'mail';
if ((passwordInput || loginForm || loginButton) && !hasMailUi) {
return true;
}
const pageText = normalizeText(document.body?.innerText || document.body?.textContent || '');
return /(企业邮箱登录|请输入密码|登录邮箱|sign in)/i.test(pageText) && !hasMailUi;
}
async function waitUntilLoggedIn(step) {
let loggedWaitMessage = false;
while (isLoginPageLikely()) {
throwIfStopped();
if (!loggedWaitMessage && !waitingLoginLogged) {
waitingLoginLogged = true;
loggedWaitMessage = true;
log(`步骤 ${step}:检测到 mail.phplife.net 未登录,正在等待你手动登录...`, 'warn');
}
await sleep(1000);
}
if (loggedWaitMessage || waitingLoginLogged) {
log(`步骤 ${step}:已检测到 mail.phplife.net 登录完成,继续读取验证码...`, 'ok');
}
waitingLoginLogged = false;
}
function normalizeMinuteTimestamp(timestamp) {
if (!Number.isFinite(timestamp) || timestamp <= 0) return 0;
const date = new Date(timestamp);
date.setSeconds(0, 0);
return date.getTime();
}
function parseRoundcubeTimestamp(rawText) {
const text = normalizeText(rawText);
if (!text) return null;
let match = text.match(/今天\s*(\d{1,2}):(\d{2})/);
if (match) {
const now = new Date();
return new Date(now.getFullYear(), now.getMonth(), now.getDate(), Number(match[1]), Number(match[2]), 0, 0).getTime();
}
match = text.match(/昨天\s*(\d{1,2}):(\d{2})/);
if (match) {
const now = new Date();
now.setDate(now.getDate() - 1);
return new Date(now.getFullYear(), now.getMonth(), now.getDate(), Number(match[1]), Number(match[2]), 0, 0).getTime();
}
match = text.match(/(\d{4})[-/年](\d{1,2})[-/月](\d{1,2})日?\s*(\d{1,2}):(\d{2})/);
if (match) {
return new Date(Number(match[1]), Number(match[2]) - 1, Number(match[3]), Number(match[4]), Number(match[5]), 0, 0).getTime();
}
match = text.match(/(\d{1,2})[-/](\d{1,2})\s*(\d{1,2}):(\d{2})/);
if (match) {
const now = new Date();
return new Date(now.getFullYear(), Number(match[1]) - 1, Number(match[2]), Number(match[3]), Number(match[4]), 0, 0).getTime();
}
match = text.match(/^(\d{1,2}):(\d{2})$/);
if (match) {
const now = new Date();
return new Date(now.getFullYear(), now.getMonth(), now.getDate(), Number(match[1]), Number(match[2]), 0, 0).getTime();
}
const parsed = Date.parse(text);
return Number.isFinite(parsed) ? parsed : null;
}
function extractVerificationCodes(text) {
const source = String(text || '');
const codes = [];
const patterns = [
/(?:验证码|代码)[^0-9]{0,24}(\d{6})/ig,
/(?:chatgpt\s+log-?in\s+code|your\s+chatgpt\s+code\s+is|verification\s+code|temporary\s+verification\s+code|enter\s+this\s+code)[^0-9]{0,24}(\d{6})/ig,
/\b(\d{6})\b/g,
];
for (const pattern of patterns) {
let match = null;
while ((match = pattern.exec(source))) {
if (match[1] && !codes.includes(match[1])) {
codes.push(match[1]);
}
}
if (codes.length) {
return codes;
}
}
return codes;
}
function extractEmails(text) {
const matches = String(text || '').match(/[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}/ig) || [];
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(/
/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) {
return { matches: true, hasExplicitEmail: false };
}
const normalizedText = String(text || '').toLowerCase();
if (normalizedText.includes(normalizedTarget)) {
return { matches: true, hasExplicitEmail: true };
}
const emails = extractEmails(text);
if (!emails.length) {
return { matches: false, hasExplicitEmail: false };
}
return {
matches: emails.includes(normalizedTarget),
hasExplicitEmail: true,
};
}
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(``, '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()));
const subjectMatched = subjectFilters.some((filter) => normalizedText.includes(String(filter || '').toLowerCase()));
return senderMatched || subjectMatched;
}
function getPreviewDocument() {
const frame = document.getElementById('messagecontframe');
if (!frame) return null;
try {
return frame.contentDocument || frame.contentWindow?.document || null;
} catch {
return null;
}
}
function getMessageDocument() {
if (document.querySelector('#messagebody, #messageheader, .headers-table')) {
return document;
}
const previewDocument = getPreviewDocument();
if (previewDocument?.querySelector?.('#messagebody, #messageheader, .headers-table')) {
return previewDocument;
}
return null;
}
function getMessageDetailsFromDocument(doc = null) {
const sourceDocument = doc || getMessageDocument();
if (!sourceDocument) return null;
const subject = normalizeText(sourceDocument.querySelector('h2.subject')?.textContent || '');
const from = normalizeText(sourceDocument.querySelector('.headers-table .header.from')?.textContent || '');
const to = normalizeText(sourceDocument.querySelector('.headers-table .header.to')?.textContent || '');
const dateText = normalizeText(sourceDocument.querySelector('.headers-table .header.date')?.textContent || '');
const bodyText = normalizeText(
sourceDocument.querySelector('#messagebody')?.innerText
|| sourceDocument.querySelector('#messagebody')?.textContent
|| sourceDocument.body?.innerText
|| sourceDocument.body?.textContent
|| ''
);
const combinedText = normalizeText([subject, from, to, dateText, bodyText].join(' '));
const codes = extractVerificationCodes(combinedText);
return {
subject,
from,
to,
dateText,
emailTimestamp: parseRoundcubeTimestamp(dateText),
bodyText,
combinedText,
codes,
};
}
function getMessageListRows() {
return Array.from(document.querySelectorAll('#messagelist tbody tr')).filter(isVisibleElement);
}
function getRowText(row, selector) {
const node = row?.querySelector(selector);
return normalizeText(
node?.getAttribute?.('title')
|| node?.getAttribute?.('aria-label')
|| node?.textContent
|| ''
);
}
function getRowDetails(row) {
const subject = getRowText(row, 'td.subject');
const from = getRowText(row, 'td.fromto');
const to = getRowText(row, 'td.to');
const dateText = getRowText(row, 'td.date');
const combinedText = normalizeText([subject, from, to, dateText, row?.textContent || ''].join(' '));
return {
row,
subject,
from,
to,
dateText,
emailTimestamp: parseRoundcubeTimestamp(dateText),
codes: extractVerificationCodes(combinedText),
combinedText,
};
}
function scoreRowCandidate(details, payload = {}) {
const { senderFilters = [], subjectFilters = [], targetEmail = '' } = payload;
let score = 0;
const combinedText = details?.combinedText || '';
if (matchesMailFilters(combinedText, senderFilters, subjectFilters)) score += 4;
if (/openai|chatgpt|verification|verify|验证码/i.test(combinedText)) score += 3;
const targetMatch = getTargetEmailMatchState(combinedText, targetEmail);
if (targetMatch.matches) score += targetMatch.hasExplicitEmail ? 4 : 1;
if (details?.emailTimestamp) score += 1;
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) {
return false;
}
return /your\s+temporary\s+chatgpt\s+login\s+code|(?:你(?:的)?|您的)?\s*临时\s*chatgpt\s*登录代?码/i.test(subject);
}
function getCurrentMessageUid() {
const envUid = getRcmailEnv()?.uid;
if (envUid) return String(envUid);
const doc = getMessageDocument();
const permaLink = doc?.querySelector?.('a[href*="_uid="]')?.getAttribute?.('href') || location.href;
const match = String(permaLink || '').match(/[?&]_uid=(\d+)/);
return match ? match[1] : '';
}
async function waitForPreviewLoaded(previousUid = '', timeoutMs = 15000) {
const start = Date.now();
while (Date.now() - start < timeoutMs) {
throwIfStopped();
const doc = getMessageDocument();
const details = getMessageDetailsFromDocument(doc);
const currentUid = getCurrentMessageUid();
if (details?.combinedText && (!previousUid || !currentUid || currentUid !== previousUid)) {
return details;
}
await sleep(250);
}
return getMessageDetailsFromDocument();
}
function openMessageRow(row) {
if (!row) {
return;
}
if (typeof row.click === 'function') {
row.click();
return;
}
row.dispatchEvent(new MouseEvent('click', { bubbles: true, cancelable: true }));
}
function findRefreshButton() {
const selectors = [
'#rcmbtn105',
'a.button.checkmail',
'a[title*="检查新邮件"]',
'a[title*="刷新"]',
];
for (const selector of selectors) {
const node = document.querySelector(selector);
if (node) return node;
}
return Array.from(document.querySelectorAll('a, button')).find((element) => /刷新|检查新邮件|check mail/i.test(normalizeText(element.textContent || element.getAttribute('title') || ''))) || null;
}
function findInboxLink() {
return document.querySelector('#mailboxlist .mailbox.inbox a[rel="INBOX"], #mailboxlist a[rel="INBOX"]');
}
async function ensureInboxActive(options = {}) {
const { forceRefresh = false } = options;
const inboxLink = findInboxLink();
if (!inboxLink) return;
const inboxItem = inboxLink.closest('.mailbox');
if (!forceRefresh && inboxItem?.classList?.contains('selected')) {
return;
}
inboxLink.click();
await sleep(800);
}
async function refreshMessageList() {
await ensureInboxActive({ forceRefresh: true });
const refreshButton = findRefreshButton();
if (!refreshButton) return true;
refreshButton.click();
await sleep(1200);
return true;
}
function selectCandidateCode(codes = [], excludedCodeSet = new Set()) {
for (const code of codes) {
if (!excludedCodeSet.has(code) && !seenCodes.has(code)) {
return code;
}
}
return null;
}
function matchesCurrentMessage(details, payload = {}, excludedCodeSet = new Set(), filterAfterMinute = 0) {
if (!details?.combinedText) {
return { matched: false };
}
const targetMatch = getTargetEmailMatchState(details.combinedText, payload.targetEmail);
if (targetMatch.hasExplicitEmail && !targetMatch.matches) {
return { matched: false };
}
if (!matchesMailFilters(details.combinedText, payload.senderFilters, payload.subjectFilters)) {
return { matched: false };
}
const normalizedTimestamp = normalizeMinuteTimestamp(details.emailTimestamp || 0);
if (filterAfterMinute && normalizedTimestamp && normalizedTimestamp < filterAfterMinute) {
return { matched: false };
}
const code = selectCandidateCode(details.codes, excludedCodeSet);
if (!code) {
return { matched: false };
}
return {
matched: true,
code,
emailTimestamp: details.emailTimestamp || Date.now(),
};
}
async function tryReadCurrentMessage(payload, excludedCodeSet, filterAfterMinute) {
const details = getMessageDetailsFromDocument();
const result = matchesCurrentMessage(details, payload, excludedCodeSet, filterAfterMinute);
return result.matched ? result : null;
}
async function tryOpenRowsAndRead(step, payload, excludedCodeSet, filterAfterMinute) {
const rows = getMessageListRows()
.map((row) => getRowDetails(row))
.map((details) => ({ ...details, score: scoreRowCandidate(details, payload) }))
.filter((details) => details.score > 0)
.sort((left, right) => right.score - left.score);
for (const details of rows.slice(0, 8)) {
throwIfStopped();
const rowMinuteTimestamp = normalizeMinuteTimestamp(details.emailTimestamp || 0);
if (filterAfterMinute && rowMinuteTimestamp && rowMinuteTimestamp < filterAfterMinute) {
continue;
}
const directResult = matchesCurrentMessage(details, payload, excludedCodeSet, filterAfterMinute);
const shouldOpenDetail = shouldOpenRowForCodeDetection(details);
if (directResult.matched && !shouldOpenDetail) {
log(`步骤 ${step}:已直接从 mail.phplife.net 列表命中验证码邮件。`, 'ok');
return directResult;
}
if (!shouldOpenDetail) {
continue;
}
log(`步骤 ${step}:检测到临时登录验证码邮件标题,正在打开详情读取验证码...`, 'info');
const previousUid = getCurrentMessageUid();
openMessageRow(details.row);
await sleep(500);
const openedDetails = await waitForPreviewLoaded(previousUid);
const detailResult = matchesCurrentMessage(openedDetails, payload, excludedCodeSet, filterAfterMinute);
if (detailResult.matched) {
log(`步骤 ${step}:已在 mail.phplife.net 邮件详情中命中验证码。`, 'ok');
return detailResult;
}
if (directResult.matched) {
log(`步骤 ${step}:邮件详情未提取到验证码,已回退使用列表中的匹配结果。`, 'warn');
return directResult;
}
}
return null;
}
async function handlePollEmail(step, payload) {
const {
maxAttempts = 5,
intervalMs = 3000,
excludeCodes = [],
filterAfterTimestamp = 0,
} = payload || {};
const excludedCodeSet = new Set((excludeCodes || []).filter(Boolean));
const filterAfterMinute = normalizeMinuteTimestamp(Number(filterAfterTimestamp) || 0);
await waitUntilLoggedIn(step);
await ensureInboxActive();
log(`步骤 ${step}:开始轮询 A4Sky 邮箱(最多 ${maxAttempts} 次)`);
if (filterAfterMinute) {
log(`步骤 ${step}:仅尝试 ${new Date(filterAfterMinute).toLocaleString('zh-CN', { hour12: false })} 及之后时间的邮件。`);
}
let lastError = null;
for (let attempt = 1; attempt <= maxAttempts; attempt += 1) {
throwIfStopped();
await waitUntilLoggedIn(step);
log(`步骤 ${step}:正在检查 mail.phplife.net 邮件(${attempt}/${maxAttempts})...`);
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);
await persistSeenCodes();
return {
code: currentMessageResult.code,
emailTimestamp: currentMessageResult.emailTimestamp,
};
}
const openedRowResult = await tryOpenRowsAndRead(step, payload, excludedCodeSet, filterAfterMinute);
if (openedRowResult?.matched) {
seenCodes.add(openedRowResult.code);
await persistSeenCodes();
return {
code: openedRowResult.code,
emailTimestamp: openedRowResult.emailTimestamp,
};
}
lastError = new Error(`步骤 ${step}:暂未在 A4Sky 邮箱中找到新的匹配验证码(${attempt}/${maxAttempts})。`);
if (attempt < maxAttempts) {
await sleep(intervalMs);
}
}
throw lastError || new Error(`步骤 ${step}:未在 A4Sky 邮箱中找到新的匹配验证码。`);
}
}