feat: support 126 mail provider routing and shared NetEase verification flow

- 合并 PR #112 的核心改动:新增 126 邮箱 provider,并复用 163 / 163 VIP 的网页邮箱接码链路
- 本地补充修复:吸收 126 子域名注入与来源识别修正,以及新版 ChatGPT 登录验证码邮件匹配适配
- 影响范围:sidepanel mail provider、background navigation/source routing、content mail polling、相关回归测试
This commit is contained in:
markyal
2026-04-25 17:42:23 +08:00
committed by GitHub
28 changed files with 463 additions and 34 deletions
+3
View File
@@ -171,6 +171,9 @@ function extractVerificationCode(text) {
const cnMatch = normalized.match(/(?:验证码|代码)[^0-9]{0,16}(\d{6})/i);
if (cnMatch) return cnMatch[1];
const openAiLoginMatch = normalized.match(/(?:chatgpt\s+log-?in\s+code|enter\s+this\s+code)[^0-9]{0,24}(\d{6})/i);
if (openAiLoginMatch) return openAiLoginMatch[1];
const enMatch = normalized.match(/(?:verification\s+code|temporary\s+verification\s+code|your\s+chatgpt\s+code|code(?:\s+is)?)[^0-9]{0,16}(\d{6})/i);
if (enMatch) return enMatch[1];
+3
View File
@@ -82,6 +82,9 @@ if (isTopFrame) {
const matchCn = text.match(/(?:代码为|验证码[^0-9]*?)[\s:]*(\d{6})/);
if (matchCn) return matchCn[1];
const matchOpenAiLogin = text.match(/(?:chatgpt\s+log-?in\s+code|enter\s+this\s+code)[^0-9]{0,24}(\d{6})/i);
if (matchOpenAiLogin) return matchOpenAiLogin[1];
const matchEn = text.match(/code[:\s]+is[:\s]+(\d{6})|code[:\s]+(\d{6})/i);
if (matchEn) return matchEn[1] || matchEn[2];
+4 -1
View File
@@ -64,6 +64,9 @@ function extractVerificationCode(text) {
const matchCn = text.match(/(?:代码为|验证码[^0-9]*?)[\s:]*(\d{6})/);
if (matchCn) return matchCn[1];
const matchOpenAiLogin = text.match(/(?:chatgpt\s+log-?in\s+code|enter\s+this\s+code)[^0-9]{0,24}(\d{6})/i);
if (matchOpenAiLogin) return matchOpenAiLogin[1];
const matchEn = text.match(/code[:\s]+is[:\s]+(\d{6})|code[:\s]+(\d{6})/i);
if (matchEn) return matchEn[1] || matchEn[2];
@@ -85,7 +88,7 @@ function rowMatchesFilters(mail, senderFilters, subjectFilters, targetEmail) {
const mailboxMatch = Boolean(targetLocal) && mailbox.includes(targetLocal);
const forwardedDuck = /duckduckgo|forward(?:ed)?\s*by/i.test(mail.combinedText);
const code = extractVerificationCode(mail.combinedText);
const keywordMatch = /openai|chatgpt|verify|verification|confirm|login|验证码|代码/.test(combined);
const keywordMatch = /openai|chatgpt|verify|verification|confirm|log-?in|验证码|代码/.test(combined);
if (mailboxMatch) return { matched: true, mailboxMatch, code };
if (senderMatch || subjectMatch) return { matched: true, mailboxMatch: false, code };
+25 -6
View File
@@ -73,6 +73,21 @@ function normalizeText(value) {
return String(value || '').replace(/\s+/g, ' ').trim();
}
function getNetEaseMailLabel(hostname) {
const currentHostname = String(
hostname || (typeof location !== 'undefined' ? location.hostname : '') || ''
).toLowerCase();
if (currentHostname === 'mail.126.com' || currentHostname.endsWith('.mail.126.com')) {
return '126 邮箱';
}
if (currentHostname === 'webmail.vip.163.com') {
return '163 VIP 邮箱';
}
return '163 邮箱';
}
function isVisibleNode(node) {
if (!node) return false;
if (node.hidden) return false;
@@ -114,7 +129,7 @@ function isLikelyMailItemNode(node) {
return false;
}
return /发件人|验证码|verification|chatgpt|openai|code/i.test(summaryText);
return /发件人|验证码|verification|chatgpt|openai|code|log-?in/i.test(summaryText);
}
function findMailItems() {
@@ -391,7 +406,7 @@ function selectOpenedMailTextCandidate(item, candidates = [], options = {}) {
if (sender && lower.includes(sender)) {
return true;
}
return Boolean(extractVerificationCode(candidate) && /chatgpt|openai|verification|验证码|login code/i.test(lower));
return Boolean(extractVerificationCode(candidate) && /chatgpt|openai|verification|验证码|log-?in\s+code/i.test(lower));
}) || source[0] || '';
const filteredCandidates = candidates.filter((candidate) => !excludedSet.has(normalizeText(candidate)));
@@ -476,8 +491,9 @@ async function handlePollEmail(step, payload) {
const { senderFilters, subjectFilters, maxAttempts, intervalMs, excludeCodes = [], filterAfterTimestamp = 0 } = payload;
const excludedCodeSet = new Set(excludeCodes.filter(Boolean));
const filterAfterMinute = normalizeMinuteTimestamp(Number(filterAfterTimestamp) || 0);
const mailLabel = getNetEaseMailLabel();
log(`步骤 ${step}:开始轮询 163 邮箱(最多 ${maxAttempts} 次)`);
log(`步骤 ${step}:开始轮询 ${mailLabel}(最多 ${maxAttempts} 次)`);
if (filterAfterMinute) {
log(`步骤 ${step}:仅尝试 ${new Date(filterAfterMinute).toLocaleString('zh-CN', { hour12: false })} 及之后时间的邮件。`);
}
@@ -508,7 +524,7 @@ async function handlePollEmail(step, payload) {
}
if (items.length === 0) {
throw new Error('163 邮箱列表未加载完成,请确认当前已打开收件箱。');
throw new Error(`${mailLabel}列表未加载完成,请确认当前已打开收件箱。`);
}
log(`步骤 ${step}:邮件列表已加载,共 ${items.length} 封邮件`);
@@ -520,7 +536,7 @@ async function handlePollEmail(step, payload) {
const FALLBACK_AFTER = 3;
for (let attempt = 1; attempt <= maxAttempts; attempt++) {
log(`步骤 ${step}:正在轮询 163 邮箱,第 ${attempt}/${maxAttempts}`);
log(`步骤 ${step}:正在轮询 ${mailLabel},第 ${attempt}/${maxAttempts}`);
if (attempt > 1) {
await refreshInbox();
@@ -605,7 +621,7 @@ async function handlePollEmail(step, payload) {
}
throw new Error(
`${(maxAttempts * intervalMs / 1000).toFixed(0)} 秒后仍未在 163 邮箱中找到新的匹配邮件。` +
`${(maxAttempts * intervalMs / 1000).toFixed(0)} 秒后仍未在 ${mailLabel}中找到新的匹配邮件。` +
'请手动检查收件箱。'
);
}
@@ -704,6 +720,9 @@ function extractVerificationCode(text) {
const matchCn = text.match(/(?:代码为|验证码[^0-9]*?)[\s:]*(\d{6})/);
if (matchCn) return matchCn[1];
const matchOpenAiLogin = text.match(/(?:chatgpt\s+log-?in\s+code|enter\s+this\s+code)[^0-9]{0,24}(\d{6})/i);
if (matchOpenAiLogin) return matchOpenAiLogin[1];
const matchEn = text.match(/code[:\s]+is[:\s]+(\d{6})|code[:\s]+(\d{6})/i);
if (matchEn) return matchEn[1] || matchEn[2];
+4 -1
View File
@@ -670,7 +670,7 @@ function matchesMailFilters(text, senderFilters, subjectFilters) {
function extractVerificationCode(text, strictChatGPTCodeOnly = false) {
if (strictChatGPTCodeOnly) {
const strictMatch = String(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|(?:chatgpt\s+log-?in\s+code|suspicious\s+log-?in)[\s\S]{0,120}enter\s+this\s+code)[^0-9]{0,24}(\d{6})/i);
return strictMatch ? strictMatch[1] : null;
}
@@ -679,6 +679,9 @@ function extractVerificationCode(text, strictChatGPTCodeOnly = false) {
const matchCn = normalized.match(/(?:代码为|验证码[^0-9]*?)[\s:]*(\d{6})/);
if (matchCn) return matchCn[1];
const matchOpenAiLogin = normalized.match(/(?:chatgpt\s+log-?in\s+code|enter\s+this\s+code)[^0-9]{0,24}(\d{6})/i);
if (matchOpenAiLogin) return matchOpenAiLogin[1];
const matchChatGPT = normalized.match(/your\s+chatgpt\s+code\s+is\s+(\d{6})/i);
if (matchChatGPT) return matchChatGPT[1];
+3
View File
@@ -174,6 +174,9 @@ function extractVerificationCode(text) {
const matchCn = text.match(/(?:代码为|验证码[^0-9]*?)[\s:]*(\d{6})/);
if (matchCn) return matchCn[1];
const matchOpenAiLogin = text.match(/(?:chatgpt\s+log-?in\s+code|enter\s+this\s+code)[^0-9]{0,24}(\d{6})/i);
if (matchOpenAiLogin) return matchOpenAiLogin[1];
// Pattern 2: English format "code is 370794" or "code: 370794"
const matchEn = text.match(/code[:\s]+is[:\s]+(\d{6})|code[:\s]+(\d{6})/i);
if (matchEn) return matchEn[1] || matchEn[2];
+21 -5
View File
@@ -2,13 +2,21 @@
const getActivationStrategy = self.MultiPageActivationUtils?.getActivationStrategy;
const SCRIPT_SOURCE = (() => {
if (window.__MULTIPAGE_SOURCE) return window.__MULTIPAGE_SOURCE;
const url = location.href;
const hostname = location.hostname;
function detectScriptSource({
injectedSource,
url = '',
hostname = '',
} = {}) {
if (injectedSource) return injectedSource;
if (url.includes('auth0.openai.com') || url.includes('auth.openai.com') || url.includes('accounts.openai.com')) return 'signup-page';
if (hostname === 'mail.qq.com' || hostname === 'wx.mail.qq.com') return 'qq-mail';
if (hostname === 'mail.163.com' || hostname.endsWith('.mail.163.com') || hostname === 'webmail.vip.163.com') return 'mail-163';
if (
hostname === 'mail.163.com'
|| hostname.endsWith('.mail.163.com')
|| hostname === 'webmail.vip.163.com'
|| hostname === 'mail.126.com'
|| hostname.endsWith('.mail.126.com')
) return 'mail-163';
if (hostname === 'mail.google.com') return 'gmail-mail';
if (hostname === 'www.icloud.com' || hostname === 'www.icloud.com.cn') return 'icloud-mail';
if (url.includes('duckduckgo.com/email/settings/autofill')) return 'duck-mail';
@@ -16,6 +24,14 @@ const SCRIPT_SOURCE = (() => {
if (url.includes("2925.com")) return "mail-2925";
// VPS panel — detected dynamically since URL is configurable
return 'vps-panel';
}
const SCRIPT_SOURCE = (() => {
return detectScriptSource({
injectedSource: window.__MULTIPAGE_SOURCE,
url: location.href,
hostname: location.hostname,
});
})();
const LOG_PREFIX = `[MultiPage:${SCRIPT_SOURCE}]`;