diff --git a/README.md b/README.md index c954718..7cf5f51 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ 一个用于批量跑通 ChatGPT OAuth 注册/登录流程的 Chrome 扩展。 -当前版本基于侧边栏控制,支持单步执行、整套自动执行、停止当前流程、保存常用配置,以及通过 DuckDuckGo / QQ / 163 / Inbucket / Hotmail 协助获取验证码。 +当前版本基于侧边栏控制,支持单步执行、整套自动执行、停止当前流程、保存常用配置,以及通过 DuckDuckGo / QQ / 163 / 163 VIP / 126 / Inbucket / Hotmail 协助获取验证码。 ## 插件效果 @@ -54,7 +54,7 @@ - 自动获取注册验证码与登录验证码 - 支持 `Hotmail`:继续使用 `邮箱 + 客户端 ID + 刷新令牌(refresh token)`,并可在远程服务与本地助手两种模式间切换 - 支持 `2925`:新增多账号池、自动登录登出、Step 4 / Step 8 命中“子邮箱已达上限邮箱”后的 24 小时禁用与自动切号 -- 支持 `QQ Mail`、`163 Mail`、`Inbucket mailbox` +- 支持 `QQ Mail`、`163 Mail`、`163 VIP Mail`、`126 Mail`、`Inbucket mailbox` - 支持从 DuckDuckGo Email Protection 自动生成新的 `@duck.com` 地址 - 支持基于 Cloudflare 自定义域名自动生成随机邮箱前缀 - Step 5 同时兼容两种页面: @@ -76,7 +76,7 @@ - DuckDuckGo `@duck.com` + QQ / 163 / Inbucket 转发 - Cloudflare 自定义域邮箱前缀 + QQ / 163 / Inbucket 转发 - 手动填写一个可收信邮箱 -- 如果使用 `QQ` / `163` / `Inbucket`,对应页面需要提前能正常打开 +- 如果使用 `QQ` / `163` / `163 VIP` / `126` / `Inbucket`,对应页面需要提前能正常打开 ## 安装 @@ -135,17 +135,17 @@ 如果你只是想先跑通一套最稳的组合,建议直接按下面三种方案之一配置。 -### 方案 A:`CPA + QQ / 163 / 163 VIP` +### 方案 A:`CPA + QQ / 163 / 163 VIP / 126` 1. `CPA` 填你的管理面板 OAuth 页面地址 -2. `Mail` 选择 `QQ Mail`、`163 Mail` 或 `163 VIP Mail` +2. `Mail` 选择 `QQ Mail`、`163 Mail`、`163 VIP Mail` 或 `126 Mail` 3. `邮箱生成` 选择 `DuckDuckGo`、`Cloudflare` 或 `自定义邮箱池` 4. 若你选择 `Cloudflare`,先按下文把 Cloudflare Email Routing 配好 5. 若你选择 `自定义邮箱池`,就在 `邮箱池` 中按行填入邮箱;否则点击 `获取` 生成邮箱,或手动粘贴一个你能收信的邮箱 6. 先单步验证 `Step 1 ~ Step 4` 7. 验证没问题后再点右上角 `Auto` -### 方案 B:`SUB2API + QQ / 163 / 163 VIP` +### 方案 B:`SUB2API + QQ / 163 / 163 VIP / 126` 1. `来源` 选择 `SUB2API` 2. 填好 `SUB2API` 地址、登录邮箱、登录密码、分组名 @@ -153,7 +153,7 @@ 4. Step 1 会直接在 SUB2API 后台生成 OAuth 链接 5. Step 10 会把 localhost 回调提交回 SUB2API,并直接创建 OpenAI 账号 -### 方案 C:`Codex2API + QQ / 163 / 163 VIP` +### 方案 C:`Codex2API + QQ / 163 / 163 VIP / 126` 1. `来源` 选择 `Codex2API` 2. 填好 `Codex2API` 后台地址、管理密钥 @@ -222,12 +222,13 @@ Step 1 和 Step 10 都依赖这个地址。 ### `Mail` -支持五种验证码来源: +支持七种验证码来源: - `Hotmail` - `2925` - `163 Mail` - `163 VIP Mail` +- `126 Mail` - `QQ Mail` - `Inbucket` @@ -235,7 +236,7 @@ Step 1 和 Step 10 都依赖这个地址。 - `Hotmail` 通过侧边栏里的 Hotmail 账号池选择账号,可切换为远程服务模式或本地助手模式 - `2925` 通过侧边栏里的 2925 账号池选择账号,并在 Step 4 / Step 8 前自动校验网页邮箱登录态 -- `QQ`、`163`、`163 VIP` 用于直接轮询网页邮箱 +- `QQ`、`163`、`163 VIP`、`126` 用于直接轮询网页邮箱 - `Inbucket` 通过你在侧边栏里配置的 host 访问 `mailbox` 页面:`https:///m//` ### `Hotmail 账号池` @@ -382,7 +383,7 @@ Step 3 使用的注册邮箱。 - 当 `Mail = Hotmail` 时,这个输入框由账号池自动同步当前账号邮箱 - 当 `Mail = Hotmail` 时,Step 3 会直接使用 Hotmail 账号池里的邮箱;`Duck / Cloudflare` 不参与自动邮箱生成 - 当 `Mail = 自定义邮箱` 且启用了 `自定义号池` 时,Auto 会先为当前轮分配一个邮箱;后续普通失败不会换号,只有成功或出现手机号验证才会切到下一个邮箱 -- 若你准备走 `Cloudflare`,更推荐把 `Mail` 设为 `QQ / 163 / 163 VIP`;`Inbucket` 仅在它能真实接收外部邮件并完成 Cloudflare 验证时再使用 +- 若你准备走 `Cloudflare`,更推荐把 `Mail` 设为 `QQ / 163 / 163 VIP / 126`;`Inbucket` 仅在它能真实接收外部邮件并完成 Cloudflare 验证时再使用 - `Auto` 会按当前“邮箱生成”配置自动获取或分配邮箱;若当前是 `自定义邮箱池`,则会按邮箱池顺序取用 - 如果你使用 Inbucket,它只是验证码收件箱,不会自动生成 Inbucket 地址 @@ -423,6 +424,7 @@ Cloudflare 模式下,插件不会再调用 Cloudflare API 创建路由。 - `Mail = QQ Mail`:Cloudflare 的 `Destination address / Destination addresses` 填你的 QQ 邮箱全地址 - `Mail = 163 Mail`:Cloudflare 的 `Destination address / Destination addresses` 填你的 163 邮箱全地址 - `Mail = 163 VIP Mail`:Cloudflare 的 `Destination address / Destination addresses` 填你的 163 VIP 邮箱全地址 +- `Mail = 126 Mail`:Cloudflare 的 `Destination address / Destination addresses` 填你的 126 邮箱全地址 - `Mail = Inbucket`:仅当你的 Inbucket 实例本身就是一个真实可收外部邮件、且能收到 Cloudflare 验证邮件的地址时再使用 - `Mail = Hotmail`:当前项目的自动流程不推荐和 Cloudflare 同时使用;因为 `Mail = Hotmail` 时,注册邮箱会直接使用 Hotmail 账号池邮箱 @@ -466,7 +468,7 @@ Cloudflare 模式下,插件不会再调用 Cloudflare API 创建路由。 1. 先在插件里点击 `获取`,拿到一个随机前缀邮箱 2. 用另一个邮箱给这个地址发一封测试邮件 3. 不要用目标邮箱给自己发测试邮件,否则某些邮箱服务会把它当成重复邮件直接吞掉 -4. 如果你的 `Mail` 选的是 `QQ / 163 / 163 VIP / Inbucket`,就去对应收件链路里确认这封测试邮件能否到达 +4. 如果你的 `Mail` 选的是 `QQ / 163 / 163 VIP / 126 / Inbucket`,就去对应收件链路里确认这封测试邮件能否到达 #### 官方参考 @@ -591,7 +593,7 @@ Cloudflare 模式下,插件不会再调用 Cloudflare API 创建路由。 - `Hotmail`(远程服务 / 本地助手) - `content/qq-mail.js` -- `content/mail-163.js` +- `content/mail-163.js`(163 / 163 VIP / 126) - `content/inbucket-mail.js` 邮件匹配规则以以下关键词为主: @@ -771,9 +773,10 @@ content/utils.js 通用工具:等待元素、点击、日志、停 content/vps-panel.js CPA 面板步骤:内部 OAuth 刷新 / Step 10 content/signup-page.js ChatGPT 官网 + OpenAI 注册/登录页步骤:Step 1 / 2 / 3 / 5 / 7 / 9 hotmail-utils.js Hotmail 收信相关通用辅助 +mail-provider-utils.js 网页邮箱 provider 配置辅助 content/duck-mail.js Duck 邮箱自动获取 content/qq-mail.js QQ 邮箱验证码轮询 -content/mail-163.js 163 邮箱验证码轮询 +content/mail-163.js 163 / 163 VIP / 126 邮箱验证码轮询 content/inbucket-mail.js Inbucket mailbox 验证码轮询 sidepanel/ 侧边栏 UI ``` diff --git a/background.js b/background.js index 77b360a..19743ac 100644 --- a/background.js +++ b/background.js @@ -754,6 +754,7 @@ function normalizeMailProvider(value = '') { case CLOUDFLARE_TEMP_EMAIL_PROVIDER: case '163': case '163-vip': + case '126': case 'qq': case 'inbucket': case '2925': @@ -3856,6 +3857,8 @@ function is163MailHost(hostname = '') { } return hostname === 'mail.163.com' || hostname.endsWith('.mail.163.com') + || hostname === 'mail.126.com' + || hostname.endsWith('.mail.126.com') || hostname === 'webmail.vip.163.com'; } @@ -5956,7 +5959,7 @@ async function ensureAutoEmailReady(targetRun, totalRuns, attemptRuns) { if (!currentState.emailPrefix) { throw new Error('Gmail 原邮箱未设置,请先在侧边栏填写。'); } - await addLog(`=== 鐩爣 ${targetRun}/${totalRuns} 杞細Gmail +tag 妯″紡宸插惎鐢紝灏嗗湪姝ラ 3 鑷姩鐢熸垚閭锛堢 ${attemptRuns} 娆″皾璇曪級===`, 'info'); + await addLog(`=== 目标 ${targetRun}/${totalRuns} 轮:Gmail +tag 模式已启用,将在步骤 3 自动生成邮箱(第 ${attemptRuns} 次尝试)===`, 'info'); return null; } if (!currentState.emailPrefix) { @@ -6904,6 +6907,9 @@ function getMailConfig(state) { if (provider === '163-vip') { return { source: 'mail-163', url: 'https://webmail.vip.163.com/js6/main.jsp?df=mail163_letter#module=mbox.ListModule%7C%7B%22fid%22%3A1%2C%22order%22%3A%22date%22%2C%22desc%22%3Atrue%7D', label: '163 VIP 邮箱' }; } + if (provider === '126') { + return { source: 'mail-163', url: 'https://mail.126.com/js6/main.jsp?df=mail163_letter#module=mbox.ListModule%7C%7B%22fid%22%3A1%2C%22order%22%3A%22date%22%2C%22desc%22%3Atrue%7D', label: '126 邮箱' }; + } if (provider === 'inbucket') { const host = normalizeInbucketOrigin(state.inbucketHost); const mailbox = (state.inbucketMailbox || '').trim(); diff --git a/background/navigation-utils.js b/background/navigation-utils.js index 3193a54..5292727 100644 --- a/background/navigation-utils.js +++ b/background/navigation-utils.js @@ -85,6 +85,8 @@ function is163MailHost(hostname = '') { return hostname === 'mail.163.com' || hostname.endsWith('.mail.163.com') + || hostname === 'mail.126.com' + || hostname.endsWith('.mail.126.com') || hostname === 'webmail.vip.163.com'; } diff --git a/content/gmail-mail.js b/content/gmail-mail.js index 54d43ac..3444d6e 100644 --- a/content/gmail-mail.js +++ b/content/gmail-mail.js @@ -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]; diff --git a/content/icloud-mail.js b/content/icloud-mail.js index 35c6d44..adc402f 100644 --- a/content/icloud-mail.js +++ b/content/icloud-mail.js @@ -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]; diff --git a/content/inbucket-mail.js b/content/inbucket-mail.js index 1de5949..ade97f4 100644 --- a/content/inbucket-mail.js +++ b/content/inbucket-mail.js @@ -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 }; diff --git a/content/mail-163.js b/content/mail-163.js index edab71b..3756a87 100644 --- a/content/mail-163.js +++ b/content/mail-163.js @@ -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]; diff --git a/content/mail-2925.js b/content/mail-2925.js index 4263f87..1e698c4 100644 --- a/content/mail-2925.js +++ b/content/mail-2925.js @@ -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]; diff --git a/content/qq-mail.js b/content/qq-mail.js index fe21bb5..dc72ca1 100644 --- a/content/qq-mail.js +++ b/content/qq-mail.js @@ -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]; diff --git a/content/utils.js b/content/utils.js index 1960e26..7eab2e4 100644 --- a/content/utils.js +++ b/content/utils.js @@ -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}]`; diff --git a/hotmail-utils.js b/hotmail-utils.js index 5d736ca..14103c6 100644 --- a/hotmail-utils.js +++ b/hotmail-utils.js @@ -38,6 +38,9 @@ const matchCn = source.match(/(?:代码为|验证码[^0-9]*?)[\s::]*(\d{6})/i); if (matchCn) return matchCn[1]; + const matchOpenAiLogin = source.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 = source.match(/code(?:\s+is|[\s:])+(\d{6})/i); if (matchEn) return matchEn[1]; diff --git a/luckmail-utils.js b/luckmail-utils.js index e4b404d..34f9af2 100644 --- a/luckmail-utils.js +++ b/luckmail-utils.js @@ -89,6 +89,9 @@ const matchCn = source.match(/(?:代码为|验证码[^0-9]*?)[\s::]*(\d{6})/i); if (matchCn) return matchCn[1]; + const matchOpenAiLogin = source.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 = source.match(/your\s+chatgpt\s+code\s+is\s+(\d{6})/i); if (matchChatGPT) return matchChatGPT[1]; diff --git a/mail-provider-utils.js b/mail-provider-utils.js new file mode 100644 index 0000000..cccb000 --- /dev/null +++ b/mail-provider-utils.js @@ -0,0 +1,80 @@ +const HOTMAIL_PROVIDER = 'hotmail-api'; +const NETEASE_LIST_PATH = '/js6/main.jsp?df=mail163_letter#module=mbox.ListModule%7C%7B%22fid%22%3A1%2C%22order%22%3A%22date%22%2C%22desc%22%3Atrue%7D'; + +function normalizeMailProvider(value = '') { + const normalized = String(value || '').trim().toLowerCase(); + switch (normalized) { + case HOTMAIL_PROVIDER: + case '163': + case '163-vip': + case '126': + case 'qq': + case 'inbucket': + return normalized; + default: + return '163'; + } +} + +function getMailProviderConfig(state = {}, options = {}) { + const provider = normalizeMailProvider(state.mailProvider); + const normalizeInbucketOrigin = options.normalizeInbucketOrigin || (() => ''); + + if (provider === HOTMAIL_PROVIDER) { + return { provider: HOTMAIL_PROVIDER, label: 'Hotmail(微软 Graph)' }; + } + if (provider === '163') { + return { + source: 'mail-163', + url: `https://mail.163.com${NETEASE_LIST_PATH}`, + label: '163 邮箱', + }; + } + if (provider === '163-vip') { + return { + source: 'mail-163', + url: `https://webmail.vip.163.com${NETEASE_LIST_PATH}`, + label: '163 VIP 邮箱', + }; + } + if (provider === '126') { + return { + source: 'mail-163', + url: `https://mail.126.com${NETEASE_LIST_PATH}`, + label: '126 邮箱', + }; + } + if (provider === 'inbucket') { + const host = normalizeInbucketOrigin(state.inbucketHost); + const mailbox = String(state.inbucketMailbox || '').trim(); + if (!host) { + return { error: 'Inbucket 主机地址为空或无效。' }; + } + if (!mailbox) { + return { error: 'Inbucket 邮箱名称为空。' }; + } + return { + source: 'inbucket-mail', + url: `${host}/m/${encodeURIComponent(mailbox)}/`, + label: `Inbucket 邮箱(${mailbox})`, + navigateOnReuse: true, + inject: ['content/activation-utils.js', 'content/utils.js', 'content/inbucket-mail.js'], + injectSource: 'inbucket-mail', + }; + } + return { source: 'qq-mail', url: 'https://wx.mail.qq.com/', label: 'QQ 邮箱' }; +} + +const api = { + HOTMAIL_PROVIDER, + getMailProviderConfig, + normalizeMailProvider, +}; + +if (typeof module !== 'undefined' && module.exports) { + module.exports = api; +} + +if (typeof self !== 'undefined') { + self.MailProviderUtils = api; +} diff --git a/manifest.json b/manifest.json index bf2ab65..ec5b434 100644 --- a/manifest.json +++ b/manifest.json @@ -70,7 +70,9 @@ "matches": [ "https://mail.163.com/*", "https://*.mail.163.com/*", - "https://webmail.vip.163.com/*" + "https://webmail.vip.163.com/*", + "https://mail.126.com/*", + "https://*.mail.126.com/*" ], "js": [ "content/activation-utils.js", diff --git a/sidepanel/sidepanel.html b/sidepanel/sidepanel.html index 9b76ac1..ff8faa3 100644 --- a/sidepanel/sidepanel.html +++ b/sidepanel/sidepanel.html @@ -222,6 +222,7 @@ + diff --git a/sidepanel/sidepanel.js b/sidepanel/sidepanel.js index 192dac1..9a73739 100644 --- a/sidepanel/sidepanel.js +++ b/sidepanel/sidepanel.js @@ -602,6 +602,11 @@ const MAIL_PROVIDER_LOGIN_CONFIGS = { url: 'https://webmail.vip.163.com/', buttonLabel: '登录', }, + '126': { + label: '126 邮箱', + url: 'https://mail.126.com/', + buttonLabel: '登录', + }, qq: { label: 'QQ 邮箱', url: 'https://wx.mail.qq.com/', @@ -2166,7 +2171,7 @@ function applySettingsState(state) { inputCodex2ApiUrl.value = state?.codex2apiUrl || ''; inputCodex2ApiAdminKey.value = state?.codex2apiAdminKey || ''; const restoredMailProvider = isCustomMailProvider(state?.mailProvider) - || [ICLOUD_PROVIDER, 'hotmail-api', GMAIL_PROVIDER, 'luckmail-api', '163', '163-vip', 'qq', 'inbucket', '2925', 'cloudflare-temp-email'].includes(String(state?.mailProvider || '').trim()) + || [ICLOUD_PROVIDER, 'hotmail-api', GMAIL_PROVIDER, 'luckmail-api', '163', '163-vip', '126', 'qq', 'inbucket', '2925', 'cloudflare-temp-email'].includes(String(state?.mailProvider || '').trim()) ? String(state?.mailProvider || '163').trim() : (String(state?.emailGenerator || '').trim().toLowerCase() === 'custom' || String(state?.emailGenerator || '').trim().toLowerCase() === 'manual' diff --git a/tests/background-navigation-utils-module.test.js b/tests/background-navigation-utils-module.test.js index 5dbeb84..44a9fd1 100644 --- a/tests/background-navigation-utils-module.test.js +++ b/tests/background-navigation-utils-module.test.js @@ -16,6 +16,26 @@ test('navigation utils module exposes a factory', () => { assert.equal(typeof api?.createNavigationUtils, 'function'); }); +test('navigation utils treat 126 mail hosts as part of the shared NetEase mail family', () => { + const source = fs.readFileSync('background/navigation-utils.js', 'utf8'); + const globalScope = {}; + const moduleApi = new Function('self', `${source}; return self.MultiPageBackgroundNavigationUtils;`)(globalScope); + const navigationUtils = moduleApi.createNavigationUtils({ + DEFAULT_SUB2API_URL: 'https://example.com/admin/accounts', + normalizeLocalCpaStep9Mode: value => value, + }); + + assert.equal(navigationUtils.is163MailHost('mail.126.com'), true); + assert.equal( + navigationUtils.matchesSourceUrlFamily( + 'mail-163', + 'https://mail.126.com/js6/main.jsp', + 'https://mail.163.com/js6/main.jsp' + ), + true + ); +}); + test('navigation utils support codex2api mode and url normalization', () => { const source = fs.readFileSync('background/navigation-utils.js', 'utf8'); const globalScope = {}; diff --git a/tests/content-utils.test.js b/tests/content-utils.test.js new file mode 100644 index 0000000..e3a11d0 --- /dev/null +++ b/tests/content-utils.test.js @@ -0,0 +1,82 @@ +const test = require('node:test'); +const assert = require('node:assert/strict'); +const fs = require('node:fs'); + +const source = fs.readFileSync('content/utils.js', 'utf8'); + +function extractFunction(name) { + const start = source.indexOf(`function ${name}(`); + if (start < 0) { + throw new Error(`missing function ${name}`); + } + + let parenDepth = 0; + let signatureEnded = false; + let braceStart = -1; + for (let index = start; index < source.length; index += 1) { + const ch = source[index]; + if (ch === '(') { + parenDepth += 1; + } else if (ch === ')') { + parenDepth -= 1; + if (parenDepth === 0) { + signatureEnded = true; + } + } else if (ch === '{' && signatureEnded) { + braceStart = index; + break; + } + } + + if (braceStart < 0) { + throw new Error(`missing body for function ${name}`); + } + + let depth = 0; + let end = braceStart; + for (; end < source.length; end += 1) { + const ch = source[end]; + if (ch === '{') depth += 1; + if (ch === '}') { + depth -= 1; + if (depth === 0) { + end += 1; + break; + } + } + } + + return source.slice(start, end); +} + +test('detectScriptSource maps 126 mail hosts to the shared 163 mail source', () => { + const bundle = [extractFunction('detectScriptSource')].join('\n'); + const api = new Function(` +${bundle} +return { detectScriptSource }; +`)(); + + assert.equal( + api.detectScriptSource({ + url: 'https://mail.126.com/js6/main.jsp', + hostname: 'mail.126.com', + }), + 'mail-163' + ); +}); + +test('detectScriptSource maps 126 mail subdomains to the shared 163 mail source', () => { + const bundle = [extractFunction('detectScriptSource')].join('\n'); + const api = new Function(` +${bundle} +return { detectScriptSource }; +`)(); + + assert.equal( + api.detectScriptSource({ + url: 'https://app.mail.126.com/js6/main.jsp', + hostname: 'app.mail.126.com', + }), + 'mail-163' + ); +}); diff --git a/tests/hotmail-utils.test.js b/tests/hotmail-utils.test.js index 0a31ba6..5518ff2 100644 --- a/tests/hotmail-utils.test.js +++ b/tests/hotmail-utils.test.js @@ -181,6 +181,7 @@ test('shouldClearHotmailCurrentSelection returns true only when account becomes test('extractVerificationCode returns first six-digit code from multilingual mail text', () => { assert.equal(extractVerificationCode('你的 ChatGPT 验证码为 370794,请勿泄露。'), '370794'); assert.equal(extractVerificationCode('Your verification code is 654321.'), '654321'); + assert.equal(extractVerificationCode('ChatGPT Log-in Code\nIf that was you, enter this code:\n\n982219'), '982219'); assert.equal(extractVerificationCode('No code here'), null); }); @@ -202,6 +203,15 @@ test('extractVerificationCodeFromMessage reads code from the latest message subj }), '654321' ); + + assert.equal( + extractVerificationCodeFromMessage({ + subject: 'ChatGPT Log-in Code', + bodyPreview: 'We noticed a suspicious log-in on your account. If that was you, enter this code:\n\n982219', + from: { emailAddress: { address: 'noreply@openai.com' } }, + }), + '982219' + ); }); test('getHotmailListToggleLabel reflects expanded state and account count', () => { diff --git a/tests/icloud-mail-content.test.js b/tests/icloud-mail-content.test.js index 42a7cac..269776b 100644 --- a/tests/icloud-mail-content.test.js +++ b/tests/icloud-mail-content.test.js @@ -81,6 +81,20 @@ return { readOpenedMailBody, extractVerificationCode }; assert.equal(api.extractVerificationCode(bodyText), '731091'); }); +test('extractVerificationCode matches the new suspicious log-in mail body', () => { + const bundle = [ + extractFunction('extractVerificationCode'), + ].join('\n'); + + const api = new Function(` +${bundle} +return { extractVerificationCode }; +`)(); + + const bodyText = 'ChatGPT Log-in Code\nWe noticed a suspicious log-in on your account. If that was you, enter this code:\n\n982219'; + assert.equal(api.extractVerificationCode(bodyText), '982219'); +}); + test('readOpenedMailBody ignores conversation list rows when no detail pane is open', () => { const bundle = [ extractFunction('normalizeText'), diff --git a/tests/luckmail-utils.test.js b/tests/luckmail-utils.test.js index 1d9acbc..ccf4ecd 100644 --- a/tests/luckmail-utils.test.js +++ b/tests/luckmail-utils.test.js @@ -68,6 +68,20 @@ test('normalizeLuckmailTokenCode and normalizeLuckmailTokenMail extract verifica assert.equal(normalizedMail.verification_code, '654321'); assert.equal(extractLuckmailVerificationCode('你的验证码为 778899'), '778899'); + assert.equal( + extractLuckmailVerificationCode('ChatGPT Log-in Code\nIf that was you, enter this code:\n\n982219'), + '982219' + ); + + const suspiciousLoginMail = normalizeLuckmailTokenMail({ + message_id: 'mail-3', + from: 'noreply@openai.com', + subject: 'ChatGPT Log-in Code', + body: 'We noticed a suspicious log-in on your account. If that was you, enter this code:\n\n982219', + received_at: '2026-04-14T10:02:00Z', + }); + + assert.equal(suspiciousLoginMail.verification_code, '982219'); }); test('normalizeLuckmailProjectName and isLuckmailPurchaseForProject match openai case-insensitively', () => { diff --git a/tests/mail-163-content.test.js b/tests/mail-163-content.test.js index 72f7bd6..8d8f384 100644 --- a/tests/mail-163-content.test.js +++ b/tests/mail-163-content.test.js @@ -101,6 +101,23 @@ return { findMailItems }; assert.equal(rows.length, 1); }); +test('getNetEaseMailLabel returns the active NetEase mailbox brand', () => { + const bundle = [ + extractFunction('getNetEaseMailLabel'), + ].join('\n'); + + const api = new Function(` +${bundle} +return { getNetEaseMailLabel }; +`)(); + + assert.equal(api.getNetEaseMailLabel('mail.126.com'), '126 邮箱'); + assert.equal(api.getNetEaseMailLabel('app.mail.126.com'), '126 邮箱'); + assert.equal(api.getNetEaseMailLabel('webmail.vip.163.com'), '163 VIP 邮箱'); + assert.equal(api.getNetEaseMailLabel('mail.163.com'), '163 邮箱'); + assert.equal(api.getNetEaseMailLabel('example.com'), '163 邮箱'); +}); + test('getMailTimestamp parses visible hh:mm text even when no title attribute exists', () => { const bundle = [ extractFunction('normalizeText'), @@ -340,14 +357,30 @@ return { openMailAndGetMessageText, mailItem }; assert.doesNotMatch(text, /111111/); }); +test('extractVerificationCode matches the new suspicious log-in mail body', () => { + const bundle = [ + extractFunction('extractVerificationCode'), + ].join('\n'); + + const api = new Function(` +${bundle} +return { extractVerificationCode }; +`)(); + + const bodyText = 'ChatGPT Log-in Code\nWe noticed a suspicious log-in on your account. If that was you, enter this code:\n\n982219'; + assert.equal(api.extractVerificationCode(bodyText), '982219'); +}); + test('handlePollEmail ignores same-minute old snapshot mail before fallback', async () => { const bundle = [ extractFunction('normalizeText'), extractFunction('normalizeMinuteTimestamp'), + extractFunction('getNetEaseMailLabel'), extractFunction('handlePollEmail'), ].join('\n'); const api = new Function(` +const location = { hostname: 'mail.126.com' }; let currentItems = [{ id: 'old-mail' }]; const seenCodes = new Set(); @@ -405,7 +438,7 @@ return { handlePollEmail }; intervalMs: 1, filterAfterTimestamp: new Date(2026, 3, 22, 22, 22, 40, 0).getTime(), }), - /未在 163 邮箱中找到新的匹配邮件/ + /未在 126 邮箱中找到新的匹配邮件/ ); }); @@ -413,6 +446,7 @@ test('handlePollEmail accepts a new same-minute mail that appears after the snap const bundle = [ extractFunction('normalizeText'), extractFunction('normalizeMinuteTimestamp'), + extractFunction('getNetEaseMailLabel'), extractFunction('handlePollEmail'), ].join('\n'); @@ -505,6 +539,7 @@ test('handlePollEmail falls back to row text when the subject node is missing', const bundle = [ extractFunction('normalizeText'), extractFunction('normalizeMinuteTimestamp'), + extractFunction('getNetEaseMailLabel'), extractFunction('handlePollEmail'), ].join('\n'); @@ -584,6 +619,7 @@ test('handlePollEmail opens matching mail body when preview has no code', async const bundle = [ extractFunction('normalizeText'), extractFunction('normalizeMinuteTimestamp'), + extractFunction('getNetEaseMailLabel'), extractFunction('handlePollEmail'), ].join('\n'); diff --git a/tests/mail-163-manifest.test.js b/tests/mail-163-manifest.test.js new file mode 100644 index 0000000..f922c14 --- /dev/null +++ b/tests/mail-163-manifest.test.js @@ -0,0 +1,22 @@ +const test = require('node:test'); +const assert = require('node:assert/strict'); +const fs = require('node:fs'); + +test('manifest 为网易邮箱内容脚本覆盖 126 子域名', () => { + const manifest = JSON.parse(fs.readFileSync('manifest.json', 'utf8')); + const mail163Script = manifest.content_scripts.find((script) => ( + Array.isArray(script.js) && script.js.includes('content/mail-163.js') + )); + + assert.ok(mail163Script, '应存在 mail-163 内容脚本声明'); + assert.equal( + mail163Script.matches.includes('https://mail.126.com/*'), + true, + '应覆盖 mail.126.com' + ); + assert.equal( + mail163Script.matches.includes('https://*.mail.126.com/*'), + true, + '应覆盖 *.mail.126.com' + ); +}); diff --git a/tests/mail-2925-content.test.js b/tests/mail-2925-content.test.js index f923d7f..29f5306 100644 --- a/tests/mail-2925-content.test.js +++ b/tests/mail-2925-content.test.js @@ -546,6 +546,21 @@ return { ]); }); +test('extractVerificationCode strict mode matches the new suspicious log-in mail body', () => { + const bundle = [ + extractFunction('extractVerificationCode'), + ].join('\n'); + + const api = new Function(` +${bundle} +return { extractVerificationCode }; +`)(); + + const bodyText = 'ChatGPT Log-in Code\nWe noticed a suspicious log-in on your account. If that was you, enter this code:\n\n982219'; + assert.equal(api.extractVerificationCode(bodyText, true), '982219'); + assert.equal(api.extractVerificationCode(bodyText, false), '982219'); +}); + test('openMailAndGetMessageText always returns to inbox after opening a 2925 message', async () => { const bundle = [ extractFunction('returnToInbox'), diff --git a/tests/mail-provider-utils.test.js b/tests/mail-provider-utils.test.js new file mode 100644 index 0000000..a52ce10 --- /dev/null +++ b/tests/mail-provider-utils.test.js @@ -0,0 +1,35 @@ +const test = require('node:test'); +const assert = require('node:assert/strict'); + +const { + HOTMAIL_PROVIDER, + getMailProviderConfig, + normalizeMailProvider, +} = require('../mail-provider-utils.js'); + +test('normalizeMailProvider accepts 126 and falls back to 163', () => { + assert.equal(normalizeMailProvider('126'), '126'); + assert.equal(normalizeMailProvider('163-vip'), '163-vip'); + assert.equal(normalizeMailProvider('unknown-provider'), '163'); +}); + +test('getMailProviderConfig returns the shared NetEase source for 126 mail', () => { + assert.deepEqual( + getMailProviderConfig({ mailProvider: '126' }), + { + source: 'mail-163', + url: 'https://mail.126.com/js6/main.jsp?df=mail163_letter#module=mbox.ListModule%7C%7B%22fid%22%3A1%2C%22order%22%3A%22date%22%2C%22desc%22%3Atrue%7D', + label: '126 邮箱', + } + ); +}); + +test('getMailProviderConfig preserves the hotmail provider sentinel', () => { + assert.deepEqual( + getMailProviderConfig({ mailProvider: HOTMAIL_PROVIDER }), + { + provider: HOTMAIL_PROVIDER, + label: 'Hotmail(微软 Graph)', + } + ); +}); diff --git a/tests/signup-page-tab-cleanup.test.js b/tests/signup-page-tab-cleanup.test.js index a00ce11..c6e76bc 100644 --- a/tests/signup-page-tab-cleanup.test.js +++ b/tests/signup-page-tab-cleanup.test.js @@ -48,6 +48,7 @@ const helperBundle = [ extractFunction(helperSource, 'parseUrlSafely'), extractFunction(helperSource, 'isSignupPageHost'), extractFunction(helperSource, 'isSignupEntryHost'), + extractFunction(helperSource, 'is163MailHost'), extractFunction(helperSource, 'matchesSourceUrlFamily'), ].join('\n'); @@ -153,6 +154,11 @@ return { true, 'signup-page family should include legacy chat.openai.com' ); + assert.strictEqual( + api.matchesSourceUrlFamily('mail-163', 'https://mail.126.com/js6/main.jsp', 'https://mail.163.com/js6/main.jsp'), + true, + 'mail-163 family should include mail.126.com' + ); api.reset({ tabs: [ diff --git a/项目完整链路说明.md b/项目完整链路说明.md index 28dd78b..15daf08 100644 --- a/项目完整链路说明.md +++ b/项目完整链路说明.md @@ -593,7 +593,24 @@ Codex2API 补充: - [luckmail-utils.js](c:/Users/projectf/Downloads/codex注册扩展/luckmail-utils.js) - LuckMail 相关后台领域逻辑仍在 [background.js](c:/Users/projectf/Downloads/codex注册扩展/background.js) -### 7.4 2925 账号池 +### 7.4 163 / 163 VIP / 126 网页邮箱 + +组成: + +- [mail-provider-utils.js](c:/Users/projectf/Downloads/codex注册扩展/mail-provider-utils.js) +- [content/mail-163.js](c:/Users/projectf/Downloads/codex注册扩展/content/mail-163.js) + +行为约定: + +- `163`、`163 VIP`、`126` 都走同一条“网易网页邮箱”验证码链路。 +- sidepanel 只负责切换 provider 与展示登录入口;后台根据 provider 选择对应网页邮箱首页。 +- 内容脚本来源统一归类到 `mail-163`,这样 Step 4 / Step 8 继续复用同一套验证码读取与邮件清理逻辑。 +- `manifest.json` 需要同时覆盖: + - `https://mail.163.com/*` + - `https://webmail.vip.163.com/*` + - `https://mail.126.com/*` + +### 7.5 2925 账号池 组成: @@ -625,7 +642,7 @@ Codex2API 补充: 8. sidepanel 中 2925 账号池的新增表单也走与 Hotmail 相同的共享交互:默认收起,头部按钮切换“添加账号 / 取消添加”,操作行右侧提供“批量导入”,保存成功后自动收起并清空。 9. 当 2925 号池模式开启时,当前选中的号池邮箱会同步回写到同一个 `mail2925BaseEmail` 字段;因此用户切换号池账号后,即使再次关闭号池模式,也会直接沿用刚才选中的邮箱作为手动基邮箱,无需重新输入。 -### 7.4.1 2925 双模式维护约定 +### 7.5.1 2925 双模式维护约定 这是后续维护 `2925` 时最容易被重新打散的一段链路,建议按下面的职责边界理解: @@ -677,7 +694,7 @@ Codex2API 补充: 4. `background/verification-flow.js` 与 `content/mail-2925.js` 的“弱目标邮箱匹配”是否保持同步 5. `项目文件结构说明.md` 与当前文件是否已同步更新 -### 7.5 iCloud +### 7.6 iCloud 组成: diff --git a/项目文件结构说明.md b/项目文件结构说明.md index 017ec79..db47d43 100644 --- a/项目文件结构说明.md +++ b/项目文件结构说明.md @@ -26,6 +26,7 @@ - `hotmail-utils.js`:Hotmail 账号与验证码提取相关的纯工具函数,负责账号筛选、验证码匹配、第三方接口数据归一化。 - `icloud-utils.js`:iCloud 隐私邮箱相关的纯工具函数,负责 host、别名列表、保留状态、已用状态等归一化。 - `luckmail-utils.js`:LuckMail 相关的纯工具函数,负责邮箱购买记录、标签、邮件 cursor、验证码匹配等归一化。 +- `mail-provider-utils.js`:网页邮箱 provider 配置纯工具,负责 `163 / 163 VIP / 126 / QQ / Inbucket / Hotmail` 的基础归一化与页面入口配置。 - `mail2925-utils.js`:2925 账号池相关的纯工具函数,负责账号归一化、冷却期判断、可用账号挑选、批量导入解析与列表更新。 - `managed-alias-utils.js`:共享 Gmail / 2925 的别名邮箱规则,负责解析基邮箱、校验“当前完整注册邮箱”是否仍与基邮箱兼容、生成 Gmail `+tag` 与 2925 随机后缀邮箱,并输出 sidepanel 可复用的 UI 文案;当前还统一承接 `mail2925Mode` 的归一化与“2925 仅在 provide 模式下参与别名生成”的共享判定。 - `manifest.json`:Chrome 扩展清单,声明权限、背景脚本、侧边栏、内容脚本与规则集。 @@ -77,7 +78,7 @@ - `content/gmail-mail.js`:Gmail 邮箱轮询脚本,负责在 Gmail 页面中匹配验证码邮件。 - `content/icloud-mail.js`:iCloud 邮箱页面脚本,负责在 iCloud Mail 页面中读取邮件详情和验证码。 - `content/inbucket-mail.js`:Inbucket 邮箱轮询脚本,负责在 Inbucket 页面中读取、删除验证码邮件。 -- `content/mail-163.js`:163 / 163 VIP 邮箱轮询脚本,负责网页邮箱验证码读取和邮件清理。 +- `content/mail-163.js`:163 / 163 VIP / 126 邮箱轮询脚本,负责网页邮箱验证码读取和邮件清理。 - `content/mail-2925.js`:2925 邮箱页面脚本,负责 2925 邮箱自动登录态确认、收件轮询、按步骤会话隔离“已试验证码”、在每次重发验证码之间执行一轮最多 15 次的邮箱刷新轮询、命中邮件后立即删当前邮件,以及在成功后配合后台执行整箱清理;若页面出现“子邮箱已达上限邮箱”提示,会立即上报后台进入切号链路;当后台显式开启 `mail2925MatchTargetEmail` 时,会对邮件里显式出现的目标邮箱做弱匹配,避免 `receive` 模式误捞别人的验证码。 - `content/phone-auth.js`:认证页手机号验证脚本,负责识别 `add-phone / phone-verification` 页面、选择国家、填写手机号、提交短信验证码、触发重发短信,以及把“回到 add-phone / 进入 OAuth 同意页”的结果反馈给后台。 - `content/qq-mail.js`:QQ 邮箱轮询脚本,负责网页邮箱验证码读取。 @@ -168,12 +169,14 @@ - `tests/phone-verification-flow.test.js`:测试手机号验证共享流程对 HeroSMS 的取号、复用、重发、换号与 Step 7 重开错误分支。 - `tests/cloudflare-temp-email-provider.test.js`:测试 Cloudflare Temp Email provider 的轮询与目标邮箱选择逻辑。 - `tests/cloudflare-temp-email-utils.test.js`:测试 Cloudflare Temp Email 工具层的 URL、域名、邮件解析逻辑。 +- `tests/content-utils.test.js`:测试内容脚本公共工具层对 `mail.126.com` 等网页邮箱来源的识别逻辑。 - `tests/hotmail-api-mode.test.js`:测试 Hotmail API 模式相关文案和集成方式。 - `tests/hotmail-cors-headers.test.js`:测试 Microsoft token 请求相关 DNR 配置。 - `tests/hotmail-utils.test.js`:测试 Hotmail 工具层的账号筛选、验证码提取和消息匹配逻辑。 - `tests/icloud-mail-content.test.js`:测试 iCloud Mail 内容脚本读取邮件正文和选中状态逻辑。 - `tests/icloud-utils.test.js`:测试 iCloud 工具层的 host、别名列表、保留状态与筛选逻辑。 - `tests/luckmail-utils.test.js`:测试 LuckMail 工具层的购买记录、邮件、游标和验证码匹配逻辑。 +- `tests/mail-provider-utils.test.js`:测试网页邮箱 provider 配置工具对 `126` 等 provider 的归一化与入口配置解析。 - `tests/mail-2925-content.test.js`:测试 2925 邮箱内容脚本的轮询快照、忽略邮箱对比、按步骤会话隔离已试验证码、单封邮件删除与整箱删除逻辑。 - `tests/mail2925-utils.test.js`:测试 2925 账号池工具层的账号归一化、冷却期判断、可用账号挑选与批量导入解析。 - `tests/managed-alias-utils.test.js`:覆盖 Gmail / 2925 共享别名工具的解析、生成、兼容性判断。