feat: 添加邮件时间戳解析功能,优化邮件轮询逻辑,支持时间过滤
This commit is contained in:
+6
-4
@@ -1751,14 +1751,14 @@ async function submitVerificationCode(step, code) {
|
|||||||
return result || {};
|
return result || {};
|
||||||
}
|
}
|
||||||
|
|
||||||
async function resolveVerificationStep(step, state, mail) {
|
async function resolveVerificationStep(step, state, mail, options = {}) {
|
||||||
const stateKey = getVerificationCodeStateKey(step);
|
const stateKey = getVerificationCodeStateKey(step);
|
||||||
const rejectedCodes = new Set();
|
const rejectedCodes = new Set();
|
||||||
if (state[stateKey]) {
|
if (state[stateKey]) {
|
||||||
rejectedCodes.add(state[stateKey]);
|
rejectedCodes.add(state[stateKey]);
|
||||||
}
|
}
|
||||||
|
|
||||||
let nextFilterAfterTimestamp = null;
|
let nextFilterAfterTimestamp = options.filterAfterTimestamp ?? null;
|
||||||
const maxSubmitAttempts = 3;
|
const maxSubmitAttempts = 3;
|
||||||
|
|
||||||
for (let attempt = 1; attempt <= maxSubmitAttempts; attempt++) {
|
for (let attempt = 1; attempt <= maxSubmitAttempts; attempt++) {
|
||||||
@@ -1799,6 +1799,7 @@ async function resolveVerificationStep(step, state, mail) {
|
|||||||
async function executeStep4(state) {
|
async function executeStep4(state) {
|
||||||
const mail = getMailConfig(state);
|
const mail = getMailConfig(state);
|
||||||
if (mail.error) throw new Error(mail.error);
|
if (mail.error) throw new Error(mail.error);
|
||||||
|
const stepStartedAt = Date.now();
|
||||||
const signupTabId = await getTabId('signup-page');
|
const signupTabId = await getTabId('signup-page');
|
||||||
if (!signupTabId) {
|
if (!signupTabId) {
|
||||||
throw new Error('认证页面标签页已关闭,无法继续步骤 4。');
|
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;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1921,6 +1922,7 @@ async function executeStep6(state) {
|
|||||||
async function executeStep7(state) {
|
async function executeStep7(state) {
|
||||||
const mail = getMailConfig(state);
|
const mail = getMailConfig(state);
|
||||||
if (mail.error) throw new Error(mail.error);
|
if (mail.error) throw new Error(mail.error);
|
||||||
|
const stepStartedAt = Date.now();
|
||||||
const authTabId = await getTabId('signup-page');
|
const authTabId = await getTabId('signup-page');
|
||||||
|
|
||||||
if (authTabId) {
|
if (authTabId) {
|
||||||
@@ -1964,7 +1966,7 @@ async function executeStep7(state) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
await resolveVerificationStep(7, state, mail);
|
await resolveVerificationStep(7, state, mail, { filterAfterTimestamp: stepStartedAt });
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+79
-3
@@ -82,15 +82,82 @@ function getCurrentMailIds() {
|
|||||||
return ids;
|
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
|
// Email Polling
|
||||||
// ============================================================
|
// ============================================================
|
||||||
|
|
||||||
async function handlePollEmail(step, payload) {
|
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 excludedCodeSet = new Set(excludeCodes.filter(Boolean));
|
||||||
|
const filterAfterMinute = normalizeMinuteTimestamp(Number(filterAfterTimestamp) || 0);
|
||||||
|
|
||||||
log(`步骤 ${step}:开始轮询 163 邮箱(最多 ${maxAttempts} 次)`);
|
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
|
// Click inbox in sidebar to ensure we're in inbox view
|
||||||
log(`步骤 ${step}:正在等待侧边栏加载...`);
|
log(`步骤 ${step}:正在等待侧边栏加载...`);
|
||||||
@@ -142,8 +209,16 @@ async function handlePollEmail(step, payload) {
|
|||||||
|
|
||||||
for (const item of allItems) {
|
for (const item of allItems) {
|
||||||
const id = item.getAttribute('id') || '';
|
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 senderEl = item.querySelector('.nui-user');
|
||||||
const sender = senderEl ? senderEl.textContent.toLowerCase() : '';
|
const sender = senderEl ? senderEl.textContent.toLowerCase() : '';
|
||||||
@@ -164,7 +239,8 @@ async function handlePollEmail(step, payload) {
|
|||||||
seenCodes.add(code);
|
seenCodes.add(code);
|
||||||
persistSeenCodes();
|
persistSeenCodes();
|
||||||
const source = useFallback && existingMailIds.has(id) ? '回退匹配邮件' : '新邮件';
|
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
|
// Delete this email via right-click menu, WAIT for it to finish before returning
|
||||||
await deleteEmail(item, step);
|
await deleteEmail(item, step);
|
||||||
|
|||||||
Reference in New Issue
Block a user