重构修改2925获取邮箱

This commit is contained in:
QLHazyCoder
2026-04-18 17:28:43 +08:00
parent 7a4b1e1e12
commit 8e3e085f3d
8 changed files with 717 additions and 205 deletions
+259 -148
View File
@@ -1,4 +1,4 @@
// content/mail-2925.js Content script for 2925 Mail (steps 4, 7)
// content/mail-2925.js - Content script for 2925 Mail (steps 4, 8)
// Injected dynamically on: 2925.com
const MAIL2925_PREFIX = '[MultiPage:mail-2925]';
@@ -37,19 +37,28 @@ async function persistSeenCodes() {
chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
if (message.type === 'POLL_EMAIL') {
resetStopState();
handlePollEmail(message.step, message.payload).then(result => {
handlePollEmail(message.step, message.payload).then((result) => {
sendResponse(result);
}).catch(err => {
}).catch((err) => {
if (isStopError(err)) {
log(`步骤 ${message.step}:已被用户停止。`, 'warn');
sendResponse({ stopped: true, error: err.message });
return;
}
log(`步骤 ${message.step}:邮箱轮询失败:${err.message}`, 'warn');
sendResponse({ error: err.message });
});
return true;
}
if (message.type === 'DELETE_ALL_EMAILS') {
Promise.resolve(deleteAllMailboxEmails(message.step)).catch(() => {});
sendResponse({ ok: true });
return true;
}
return false;
});
const MAIL_ITEM_SELECTORS = [
@@ -64,13 +73,67 @@ const MAIL_ITEM_SELECTORS = [
'[class*="list-item"]',
'li[class*="mail"]',
];
const MAIL_REFRESH_SELECTOR = '[class*="refresh"], [title*="鍒锋柊"], [aria-label*="鍒锋柊"], [class*="Refresh"]';
const MAIL_ITEM_SELECTOR_GROUP = MAIL_ITEM_SELECTORS.join(', ');
const MAIL_REFRESH_SELECTORS = [
'[class*="refresh"]',
'[title*="刷新"]',
'[aria-label*="刷新"]',
'[class*="Refresh"]',
];
const MAIL_INBOX_SELECTORS = [
'a[href*="mailList"]',
'[class*="inbox"]',
'[class*="Inbox"]',
'[title*="鏀朵欢绠?]',
'[title*="收件箱"]',
];
const MAIL_DELETE_SELECTORS = [
'[class*="delete"]',
'[title*="删除"]',
'[aria-label*="删除"]',
'[class*="Delete"]',
];
const MAIL_SELECT_ALL_SELECTORS = [
'input[type="checkbox"]',
'[role="checkbox"]',
'.el-checkbox__input',
'.el-checkbox',
'label[class*="checkbox"]',
'[class*="checkbox"]',
];
const MAIL_ACTION_CANDIDATE_SELECTORS = 'button, [role="button"], a, label, span, div';
function normalizeNodeText(value) {
return String(value || '').replace(/\s+/g, ' ').trim();
}
function isVisibleNode(node) {
if (!node) return false;
if (node.hidden) return false;
const style = typeof window.getComputedStyle === 'function'
? window.getComputedStyle(node)
: null;
if (style && (style.display === 'none' || style.visibility === 'hidden')) {
return false;
}
const rect = typeof node.getBoundingClientRect === 'function'
? node.getBoundingClientRect()
: null;
if (rect && rect.width <= 0 && rect.height <= 0) {
return false;
}
return true;
}
function isMailItemNode(node) {
return Boolean(node?.closest?.(MAIL_ITEM_SELECTOR_GROUP));
}
function resolveActionTarget(node) {
return node?.closest?.('button, [role="button"], a, label, .el-checkbox, .el-checkbox__input') || node || null;
}
function findMailItems() {
for (const selector of MAIL_ITEM_SELECTORS) {
@@ -82,20 +145,81 @@ 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;
function findActionBySelectors(selectors = []) {
for (const selector of selectors) {
const candidates = document.querySelectorAll(selector);
for (const candidate of candidates) {
const target = resolveActionTarget(candidate);
if (!isVisibleNode(target) || isMailItemNode(target)) {
continue;
}
return target;
}
}
return null;
}
function findToolbarActionButton(patterns = [], selectors = []) {
const directMatch = findActionBySelectors(selectors);
if (directMatch) {
return directMatch;
}
const candidates = document.querySelectorAll(MAIL_ACTION_CANDIDATE_SELECTORS);
for (const candidate of candidates) {
const target = resolveActionTarget(candidate);
if (!isVisibleNode(target) || isMailItemNode(target)) {
continue;
}
const text = normalizeNodeText(target.innerText || target.textContent || '');
const label = normalizeNodeText(target.getAttribute?.('aria-label') || target.getAttribute?.('title') || '');
if (patterns.some((pattern) => pattern.test(text) || pattern.test(label))) {
return target;
}
}
return null;
}
function findRefreshButton() {
return findToolbarActionButton([
/刷新/i,
/refresh/i,
], MAIL_REFRESH_SELECTORS);
}
function findInboxLink() {
return findActionBySelectors(MAIL_INBOX_SELECTORS);
}
function findDeleteButton() {
return findToolbarActionButton([
/删除/i,
/delete/i,
], MAIL_DELETE_SELECTORS);
}
function findSelectAllControl() {
return findActionBySelectors(MAIL_SELECT_ALL_SELECTORS);
}
function isCheckboxChecked(node) {
const checkbox = node?.matches?.('input[type="checkbox"], [role="checkbox"]')
? node
: node?.querySelector?.('input[type="checkbox"], [role="checkbox"]');
if (checkbox?.checked === true) {
return true;
}
if (String(checkbox?.getAttribute?.('aria-checked') || '').toLowerCase() === 'true') {
return true;
}
return Boolean(
node?.classList?.contains('is-checked')
|| node?.classList?.contains('checked')
);
}
function getMailItemText(item) {
if (!item) return '';
const contentCell = item.querySelector('td.content, .content, .mail-content');
@@ -112,11 +236,11 @@ function getMailItemText(item) {
function getMailItemTimeText(item) {
const timeEl = item?.querySelector('.date-time-text, [class*="date-time"], [class*="time"], td.time');
return (timeEl?.textContent || '').replace(/\s+/g, ' ').trim();
return normalizeNodeText(timeEl?.textContent || '');
}
function normalizeMailIdentityPart(value) {
return (value || '').replace(/\s+/g, ' ').trim().toLowerCase();
return normalizeNodeText(value).toLowerCase();
}
function getMailItemId(item, index = 0) {
@@ -148,82 +272,36 @@ function getCurrentMailIds(items = []) {
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 matchesMailFilters(text, senderFilters, subjectFilters) {
const lower = (text || '').toLowerCase();
const senderMatch = senderFilters.some(filter => lower.includes(filter.toLowerCase()));
const subjectMatch = subjectFilters.some(filter => lower.includes(filter.toLowerCase()));
const lower = String(text || '').toLowerCase();
const senderMatch = senderFilters.some((filter) => lower.includes(String(filter || '').toLowerCase()));
const subjectMatch = subjectFilters.some((filter) => lower.includes(String(filter || '').toLowerCase()));
return senderMatch || subjectMatch;
}
function extractVerificationCode(text, strictChatGPTCodeOnly = false) {
if (strictChatGPTCodeOnly) {
const strictMatch = text.match(/your\s+chatgpt\s+code\s+is\s+(\d{6})/i);
const strictMatch = String(text || '').match(/your\s+chatgpt\s+code\s+is\s+(\d{6})/i);
return strictMatch ? strictMatch[1] : null;
}
const matchCn = text.match(/(?:代码为|验证码[^0-9]*?)[\s:]*(\d{6})/);
const normalized = String(text || '');
const matchCn = normalized.match(/(?:代码为|验证码[^0-9]*?)[\s:]*(\d{6})/);
if (matchCn) return matchCn[1];
const matchChatGPT = text.match(/your\s+chatgpt\s+code\s+is\s+(\d{6})/i);
const matchChatGPT = normalized.match(/your\s+chatgpt\s+code\s+is\s+(\d{6})/i);
if (matchChatGPT) return matchChatGPT[1];
const matchEn = text.match(/code[:\s]+is[:\s]+(\d{6})|code[:\s]+(\d{6})/i);
const matchEn = normalized.match(/code[:\s]+is[:\s]+(\d{6})|code[:\s]+(\d{6})/i);
if (matchEn) return matchEn[1] || matchEn[2];
const match6 = text.match(/\b(\d{6})\b/);
const match6 = normalized.match(/\b(\d{6})\b/);
if (match6) return match6[1];
return null;
}
function extractEmails(text) {
const matches = text.match(/[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}/ig) || [];
return [...new Set(matches.map(item => item.toLowerCase()))];
}
function emailMatchesTarget(candidate, targetEmail) {
const normalizedCandidate = String(candidate || '').trim().toLowerCase();
const normalizedTarget = String(targetEmail || '').trim().toLowerCase();
return Boolean(normalizedCandidate && normalizedTarget && normalizedCandidate === normalizedTarget);
}
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 atIndex = normalizedTarget.indexOf('@');
if (atIndex > 0) {
const encodedTarget = `${normalizedTarget.slice(0, atIndex)}=${normalizedTarget.slice(atIndex + 1)}`;
if (normalizedText.includes(encodedTarget)) {
return { matches: true, hasExplicitEmail: true };
}
}
const emails = extractEmails(text);
if (!emails.length) {
return { matches: false, hasExplicitEmail: false };
}
return {
matches: emails.some(email => emailMatchesTarget(email, normalizedTarget)),
hasExplicitEmail: true,
};
}
function parseMailItemTimestamp(item) {
const timeText = getMailItemTimeText(item);
if (!timeText) return null;
@@ -236,7 +314,7 @@ function parseMailItemTimestamp(item) {
return now.getTime();
}
match = timeText.match(/(\d+)\s*分(?:钟)?前/);
match = timeText.match(/(\d+)\s*分前/);
if (match) {
return now.getTime() - Number(match[1]) * 60 * 1000;
}
@@ -324,19 +402,70 @@ async function openMailAndGetMessageText(item) {
}
}
async function deleteCurrentMailboxEmail(step) {
try {
const deleteButton = findDeleteButton();
if (!deleteButton) {
return false;
}
simulateClick(deleteButton);
await sleepRandom(200, 500);
return true;
} catch (err) {
console.warn(MAIL2925_PREFIX, `Step ${step}: delete-current cleanup failed:`, err?.message || err);
return false;
}
}
async function openMailAndDeleteAfterRead(item, step) {
simulateClick(item);
try {
await sleepRandom(1200, 2200);
return document.body?.textContent || '';
} finally {
await deleteCurrentMailboxEmail(step);
await returnToInbox();
}
}
async function deleteAllMailboxEmails(step) {
try {
await returnToInbox();
const selectAllControl = findSelectAllControl();
if (!selectAllControl) {
return false;
}
if (!isCheckboxChecked(selectAllControl)) {
simulateClick(selectAllControl);
await sleepRandom(200, 500);
}
const deleteButton = findDeleteButton();
if (!deleteButton) {
return false;
}
simulateClick(deleteButton);
await sleepRandom(200, 500);
return true;
} catch (err) {
console.warn(MAIL2925_PREFIX, `Step ${step}: delete-all cleanup failed:`, err?.message || err);
return false;
}
}
async function refreshInbox() {
const refreshBtn = document.querySelector(
'[class*="refresh"], [title*="刷新"], [aria-label*="刷新"], [class*="Refresh"]'
);
const refreshBtn = findRefreshButton();
if (refreshBtn) {
simulateClick(refreshBtn);
await sleepRandom(700, 1200);
return;
}
const inboxLink = document.querySelector(
'a[href*="mailList"], [class*="inbox"], [class*="Inbox"], [title*="收件箱"]'
);
const inboxLink = findInboxLink();
if (inboxLink) {
simulateClick(inboxLink);
await sleepRandom(700, 1200);
@@ -349,27 +478,26 @@ async function handlePollEmail(step, payload) {
subjectFilters,
maxAttempts,
intervalMs,
filterAfterTimestamp = 0,
excludeCodes = [],
strictChatGPTCodeOnly = false,
targetEmail = '',
} = payload;
} = payload || {};
const excludedCodeSet = new Set(excludeCodes.filter(Boolean));
const filterAfterMinute = normalizeMinuteTimestamp(Number(filterAfterTimestamp) || 0);
log(`步骤 ${step}:开始轮询 2925 邮箱(最多 ${maxAttempts} 次)`);
if (filterAfterMinute) {
log(`步骤 ${step}:仅尝试 ${new Date(filterAfterMinute).toLocaleString('zh-CN', { hour12: false })} 及之后时间的邮件。`);
}
let initialItems = [];
for (let i = 0; i < 20; i++) {
let initialLoadUsedRefresh = false;
for (let i = 0; i < 20; i += 1) {
initialItems = findMailItems();
if (initialItems.length > 0) break;
if (initialItems.length > 0) {
break;
}
await sleep(500);
}
if (initialItems.length === 0) {
initialLoadUsedRefresh = true;
await returnToInbox();
await refreshInbox();
await sleep(2000);
@@ -380,16 +508,16 @@ async function handlePollEmail(step, payload) {
throw new Error('2925 邮箱列表未加载完成,请确认当前已打开收件箱。');
}
const existingMailIds = getCurrentMailIds(initialItems);
const knownMailIds = getCurrentMailIds(initialItems);
log(`步骤 ${step}:邮件列表已加载,共 ${initialItems.length} 封邮件`);
log(`步骤 ${step}:已记录当前 ${existingMailIds.size} 封旧邮件快照`);
log(`步骤 ${step}:已记录当前 ${knownMailIds.size} 封旧邮件快照`);
const FALLBACK_AFTER = 3;
for (let attempt = 1; attempt <= maxAttempts; attempt++) {
for (let attempt = 1; attempt <= maxAttempts; attempt += 1) {
log(`步骤 ${step}:正在轮询 2925 邮箱,第 ${attempt}/${maxAttempts}`);
if (attempt > 1) {
if (attempt > 1 || !initialLoadUsedRefresh) {
await returnToInbox();
await refreshInbox();
await sleepRandom(900, 1500);
@@ -398,78 +526,61 @@ async function handlePollEmail(step, payload) {
const items = findMailItems();
if (items.length > 0) {
const useFallback = attempt > FALLBACK_AFTER;
const newMailIds = new Set();
for (let index = 0; index < items.length; index++) {
items.forEach((item, index) => {
const itemId = getMailItemId(item, index);
if (!knownMailIds.has(itemId)) {
newMailIds.add(itemId);
}
});
for (let index = 0; index < items.length; index += 1) {
const item = items[index];
const itemId = getMailItemId(item, index);
const isNewMail = newMailIds.has(itemId);
const itemTimestamp = parseMailItemTimestamp(item);
const itemMinute = normalizeMinuteTimestamp(itemTimestamp || 0);
const passesTimeFilter = !filterAfterMinute || (itemMinute && itemMinute >= filterAfterMinute);
const shouldBypassOldSnapshot = Boolean(filterAfterMinute && passesTimeFilter && itemMinute > 0);
if (!passesTimeFilter) {
if (!useFallback && !isNewMail) {
continue;
}
if (!useFallback && !shouldBypassOldSnapshot && existingMailIds.has(itemId)) {
const previewText = getMailItemText(item);
if (!matchesMailFilters(previewText, senderFilters, subjectFilters)) {
continue;
}
const text = getMailItemText(item);
if (!matchesMailFilters(text, senderFilters, subjectFilters)) {
continue;
}
const previewEmails = extractEmails(text);
const previewTargetState = getTargetEmailMatchState(text, targetEmail);
const previewMatchesTarget = previewTargetState.matches;
if (targetEmail && previewEmails.length > 0 && !previewMatchesTarget) {
continue;
}
const code = extractVerificationCode(text, strictChatGPTCodeOnly);
if (code && previewMatchesTarget) {
if (excludedCodeSet.has(code)) {
log(`步骤 ${step}:跳过排除的验证码:${code}`, 'info');
continue;
}
if (seenCodes.has(code)) {
log(`步骤 ${step}:跳过已处理过的验证码:${code}`, 'info');
continue;
}
seenCodes.add(code);
persistSeenCodes();
const source = useFallback && existingMailIds.has(itemId) ? '回退匹配邮件' : '新邮件';
const timeLabel = itemTimestamp ? `,时间:${new Date(itemTimestamp).toLocaleString('zh-CN', { hour12: false })}` : '';
log(`步骤 ${step}:已找到验证码:${code}(来源:${source}${timeLabel}`, 'ok');
await sleep(1000);
return { ok: true, code, emailTimestamp: Date.now() };
}
const openedText = await openMailAndGetMessageText(item);
const previewCode = extractVerificationCode(previewText, strictChatGPTCodeOnly);
const openedText = await openMailAndDeleteAfterRead(item, step);
const bodyCode = extractVerificationCode(openedText, strictChatGPTCodeOnly);
const openedTargetState = getTargetEmailMatchState(openedText, targetEmail);
if (targetEmail && openedTargetState.hasExplicitEmail && !openedTargetState.matches) {
const candidateCode = bodyCode || previewCode;
if (!candidateCode) {
continue;
}
if (bodyCode) {
if (excludedCodeSet.has(bodyCode)) {
log(`步骤 ${step}:跳过排除的验证码:${bodyCode}`, 'info');
continue;
}
if (seenCodes.has(bodyCode)) {
log(`步骤 ${step}:跳过已处理过的验证码:${bodyCode}`, 'info');
continue;
}
seenCodes.add(bodyCode);
persistSeenCodes();
const source = useFallback && existingMailIds.has(itemId) ? '回退匹配邮件正文' : '新邮件正文';
const timeLabel = itemTimestamp ? `,时间:${new Date(itemTimestamp).toLocaleString('zh-CN', { hour12: false })}` : '';
log(`步骤 ${step}:已在邮件正文中找到验证码:${bodyCode}(来源:${source}${timeLabel}`, 'ok');
await sleep(1000);
return { ok: true, code: bodyCode, emailTimestamp: Date.now() };
if (excludedCodeSet.has(candidateCode)) {
log(`步骤 ${step}:跳过排除的验证码:${candidateCode}`, 'info');
continue;
}
if (seenCodes.has(candidateCode)) {
log(`步骤 ${step}:跳过已处理过的验证码:${candidateCode}`, 'info');
continue;
}
seenCodes.add(candidateCode);
persistSeenCodes();
const source = useFallback && !isNewMail
? (bodyCode ? '回退匹配邮件正文' : '回退匹配邮件')
: (bodyCode ? '新邮件正文' : '新邮件');
const timeLabel = itemTimestamp ? `,时间:${new Date(itemTimestamp).toLocaleString('zh-CN', { hour12: false })}` : '';
log(`步骤 ${step}:已找到验证码:${candidateCode}(来源:${source}${timeLabel}`, 'ok');
return { ok: true, code: candidateCode, emailTimestamp: Date.now() };
}
items.forEach((item, index) => {
knownMailIds.add(getMailItemId(item, index));
});
}
if (attempt === FALLBACK_AFTER + 1) {
+8
View File
@@ -695,6 +695,12 @@ function getPageTextSnapshot() {
.trim();
}
function getLoginVerificationDisplayedEmail() {
const pageText = getPageTextSnapshot();
const matches = pageText.match(/[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}/ig) || [];
return matches[0] ? String(matches[0]).trim().toLowerCase() : '';
}
function getOAuthConsentForm() {
return document.querySelector(OAUTH_CONSENT_FORM_SELECTOR);
}
@@ -1076,6 +1082,7 @@ function inspectLoginAuthState() {
state: 'unknown',
url: location.href,
path: location.pathname || '',
displayedEmail: getLoginVerificationDisplayedEmail(),
retryButton: retryState?.retryButton || null,
retryEnabled: Boolean(retryState?.retryEnabled),
titleMatched: Boolean(retryState?.titleMatched),
@@ -1141,6 +1148,7 @@ function serializeLoginAuthState(snapshot) {
state: snapshot?.state || 'unknown',
url: snapshot?.url || location.href,
path: snapshot?.path || location.pathname || '',
displayedEmail: snapshot?.displayedEmail || '',
retryEnabled: Boolean(snapshot?.retryEnabled),
titleMatched: Boolean(snapshot?.titleMatched),
detailMatched: Boolean(snapshot?.detailMatched),