From 508fbdc50aafa6cb7058a8c2f29632d83e6652c7 Mon Sep 17 00:00:00 2001 From: Ryan Liu Date: Sun, 19 Apr 2026 11:36:31 +0800 Subject: [PATCH 1/8] feat: add 126 mail provider routing --- background.js | 4 ++ content/utils.js | 25 ++++++++-- mail-provider-utils.js | 80 +++++++++++++++++++++++++++++++ manifest.json | 3 +- sidepanel/sidepanel.html | 1 + sidepanel/sidepanel.js | 7 ++- tests/content-utils.test.js | 66 +++++++++++++++++++++++++ tests/mail-provider-utils.test.js | 35 ++++++++++++++ 8 files changed, 214 insertions(+), 7 deletions(-) create mode 100644 mail-provider-utils.js create mode 100644 tests/content-utils.test.js create mode 100644 tests/mail-provider-utils.test.js diff --git a/background.js b/background.js index 03e822f..1b1a176 100644 --- a/background.js +++ b/background.js @@ -628,6 +628,7 @@ function normalizeMailProvider(value = '') { case CLOUDFLARE_TEMP_EMAIL_PROVIDER: case '163': case '163-vip': + case '126': case 'qq': case 'inbucket': case '2925': @@ -6131,6 +6132,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/content/utils.js b/content/utils.js index 1960e26..3b8f0f5 100644 --- a/content/utils.js +++ b/content/utils.js @@ -2,13 +2,20 @@ 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' + ) 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 +23,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/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 dd2a526..aca5f9b 100644 --- a/manifest.json +++ b/manifest.json @@ -69,7 +69,8 @@ "matches": [ "https://mail.163.com/*", "https://*.mail.163.com/*", - "https://webmail.vip.163.com/*" + "https://webmail.vip.163.com/*", + "https://mail.126.com/*" ], "js": [ "content/activation-utils.js", diff --git a/sidepanel/sidepanel.html b/sidepanel/sidepanel.html index 3dc8cec..f6f7c49 100644 --- a/sidepanel/sidepanel.html +++ b/sidepanel/sidepanel.html @@ -169,6 +169,7 @@ + diff --git a/sidepanel/sidepanel.js b/sidepanel/sidepanel.js index f8bf5df..2a70689 100644 --- a/sidepanel/sidepanel.js +++ b/sidepanel/sidepanel.js @@ -444,6 +444,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/', @@ -1693,7 +1698,7 @@ function applySettingsState(state) { inputSub2ApiGroup.value = state?.sub2apiGroupName || ''; inputSub2ApiDefaultProxy.value = state?.sub2apiDefaultProxyName || ''; 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/content-utils.test.js b/tests/content-utils.test.js new file mode 100644 index 0000000..0ea69ca --- /dev/null +++ b/tests/content-utils.test.js @@ -0,0 +1,66 @@ +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' + ); +}); 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)', + } + ); +}); From 45546dcb8507caadb2c4528698d05b096724e168 Mon Sep 17 00:00:00 2001 From: Ryan Liu Date: Sun, 19 Apr 2026 11:57:04 +0800 Subject: [PATCH 2/8] =?UTF-8?q?fix:=20=E5=AF=B9=E9=BD=90=20126=20provider?= =?UTF-8?q?=20=E5=8F=98=E6=9B=B4=E7=9A=84=E8=A7=84=E8=8C=83=E9=A1=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 25 ++++++++++++++----------- background.js | 2 +- 项目完整链路说明.md | 19 ++++++++++++++++++- 项目文件结构说明.md | 5 ++++- 4 files changed, 37 insertions(+), 14 deletions(-) diff --git a/README.md b/README.md index d52dff2..9577c7a 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 协助获取验证码。 ## 插件效果 @@ -53,7 +53,7 @@ - 自动显示当前使用中的密码,便于后续保存 - 自动获取注册验证码与登录验证码 - 支持 `Hotmail`:继续使用 `邮箱 + 客户端 ID + 刷新令牌(refresh token)`,并可在远程服务与本地助手两种模式间切换 -- 支持 `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 同时兼容两种页面: @@ -75,7 +75,7 @@ - DuckDuckGo `@duck.com` + QQ / 163 / Inbucket 转发 - Cloudflare 自定义域邮箱前缀 + QQ / 163 / Inbucket 转发 - 手动填写一个可收信邮箱 -- 如果使用 `QQ` / `163` / `Inbucket`,对应页面需要提前能正常打开 +- 如果使用 `QQ` / `163` / `163 VIP` / `126` / `Inbucket`,对应页面需要提前能正常打开 ## 安装 @@ -112,17 +112,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` 地址、登录邮箱、登录密码、分组名 @@ -168,13 +168,14 @@ Step 1 和 Step 10 都依赖这个地址。 - `Hotmail` - `163 Mail` - `163 VIP Mail` +- `126 Mail` - `QQ Mail` - `Inbucket` 说明: - `Hotmail` 通过侧边栏里的 Hotmail 账号池选择账号,可切换为远程服务模式或本地助手模式 -- `QQ`、`163`、`163 VIP` 用于直接轮询网页邮箱 +- `QQ`、`163`、`163 VIP`、`126` 用于直接轮询网页邮箱 - `Inbucket` 通过你在侧边栏里配置的 host 访问 `mailbox` 页面:`https:///m//` ### `Hotmail 账号池` @@ -300,7 +301,7 @@ Step 3 使用的注册邮箱。 - Cloudflare 侧的转发规则、Catch-all、路由目标邮箱等,都需要你自己提前在 Cloudflare 后台配置好 - 当 `Mail = Hotmail` 时,这个输入框由账号池自动同步当前账号邮箱 - 当 `Mail = Hotmail` 时,Step 3 会直接使用 Hotmail 账号池里的邮箱;`Duck / Cloudflare` 不参与自动邮箱生成 -- 若你准备走 `Cloudflare`,更推荐把 `Mail` 设为 `QQ / 163 / 163 VIP`;`Inbucket` 仅在它能真实接收外部邮件并完成 Cloudflare 验证时再使用 +- 若你准备走 `Cloudflare`,更推荐把 `Mail` 设为 `QQ / 163 / 163 VIP / 126`;`Inbucket` 仅在它能真实接收外部邮件并完成 Cloudflare 验证时再使用 - 当前 `Auto` 按钮只负责 DuckDuckGo 地址获取 - 如果你使用 Inbucket,它只是验证码收件箱,不会自动生成 Inbucket 地址 @@ -341,6 +342,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 账号池邮箱 @@ -384,7 +386,7 @@ Cloudflare 模式下,插件不会再调用 Cloudflare API 创建路由。 1. 先在插件里点击 `获取`,拿到一个随机前缀邮箱 2. 用另一个邮箱给这个地址发一封测试邮件 3. 不要用目标邮箱给自己发测试邮件,否则某些邮箱服务会把它当成重复邮件直接吞掉 -4. 如果你的 `Mail` 选的是 `QQ / 163 / 163 VIP / Inbucket`,就去对应收件链路里确认这封测试邮件能否到达 +4. 如果你的 `Mail` 选的是 `QQ / 163 / 163 VIP / 126 / Inbucket`,就去对应收件链路里确认这封测试邮件能否到达 #### 官方参考 @@ -507,7 +509,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` 邮件匹配规则以以下关键词为主: @@ -684,9 +686,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 1b1a176..fb7d1cb 100644 --- a/background.js +++ b/background.js @@ -5332,7 +5332,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) { diff --git a/项目完整链路说明.md b/项目完整链路说明.md index 140c3e9..e17e348 100644 --- a/项目完整链路说明.md +++ b/项目完整链路说明.md @@ -464,7 +464,24 @@ - [luckmail-utils.js](c:/Users/projectf/Downloads/codex注册扩展/luckmail-utils.js) - LuckMail 相关后台领域逻辑仍在 [background.js](c:/Users/projectf/Downloads/codex注册扩展/background.js) -### 7.4 iCloud +### 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 iCloud 组成: diff --git a/项目文件结构说明.md b/项目文件结构说明.md index 4d90214..1d0dbfb 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` 的基础归一化与页面入口配置。 - `managed-alias-utils.js`:共享 Gmail / 2925 的别名邮箱规则,负责解析基邮箱、校验“当前完整注册邮箱”是否仍与基邮箱兼容、生成 Gmail `+tag` 与 2925 随机后缀邮箱,并输出 sidepanel 可复用的 UI 文案。 - `manifest.json`:Chrome 扩展清单,声明权限、背景脚本、侧边栏、内容脚本与规则集。 - `microsoft-email.js`:Microsoft Graph / Outlook 邮件读取辅助模块,负责刷新令牌换 token、邮箱夹轮询和验证码提取。 @@ -73,7 +74,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 邮箱收件轮询、按步骤会话隔离“已试验证码”、命中邮件后立即删当前邮件,以及在成功后配合后台执行整箱清理。 - `content/qq-mail.js`:QQ 邮箱轮询脚本,负责网页邮箱验证码读取。 - `content/signup-page.js`:注册、登录、授权主内容脚本,负责 OpenAI / ChatGPT 页面上的步骤执行;其中 Step 2 / 3 的延迟点击与延迟提交在真正触发前会先检查 Stop 状态,避免停止后页面继续自动点击。 @@ -151,12 +152,14 @@ - `tests/background-verification-flow-module.test.js`:测试验证码流程模块已接入且导出工厂。 - `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/managed-alias-utils.test.js`:覆盖 Gmail / 2925 共享别名工具的解析、生成、兼容性判断。 - `tests/microsoft-email.test.js`:测试 Microsoft 邮件拉取与验证码提取逻辑。 From b89d5389eef3429bd5c2113724d1fe355084752a Mon Sep 17 00:00:00 2001 From: Ryan Liu Date: Thu, 23 Apr 2026 09:57:29 +0800 Subject: [PATCH 3/8] =?UTF-8?q?fix:=20=E8=A1=A5=E9=BD=90=20126=20=E9=82=AE?= =?UTF-8?q?=E7=AE=B1=20host=20=E5=AE=B6=E6=97=8F=E8=AF=86=E5=88=AB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- background.js | 2 ++ background/navigation-utils.js | 2 ++ ...background-navigation-utils-module.test.js | 20 +++++++++++++++++++ tests/signup-page-tab-cleanup.test.js | 6 ++++++ 4 files changed, 30 insertions(+) diff --git a/background.js b/background.js index fb7d1cb..1e94b12 100644 --- a/background.js +++ b/background.js @@ -3542,6 +3542,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/background/navigation-utils.js b/background/navigation-utils.js index 2c7b793..7773db1 100644 --- a/background/navigation-utils.js +++ b/background/navigation-utils.js @@ -61,6 +61,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/tests/background-navigation-utils-module.test.js b/tests/background-navigation-utils-module.test.js index cf03bf0..e8afd71 100644 --- a/tests/background-navigation-utils-module.test.js +++ b/tests/background-navigation-utils-module.test.js @@ -15,3 +15,23 @@ 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 + ); +}); 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: [ From 9c1c0bc076fd1b9fb0d8ca848d701ea69b3fd1ab Mon Sep 17 00:00:00 2001 From: Ryan Liu Date: Fri, 24 Apr 2026 09:42:39 +0800 Subject: [PATCH 4/8] =?UTF-8?q?fix:=20=E8=A1=A5=E9=BD=90126=E9=82=AE?= =?UTF-8?q?=E7=AE=B1=E5=AD=90=E5=9F=9F=E5=90=8D=E6=B3=A8=E5=85=A5=E8=8C=83?= =?UTF-8?q?=E5=9B=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- manifest.json | 3 ++- tests/mail-163-manifest.test.js | 22 ++++++++++++++++++++++ 2 files changed, 24 insertions(+), 1 deletion(-) create mode 100644 tests/mail-163-manifest.test.js diff --git a/manifest.json b/manifest.json index 7a0f6f6..b55cc7a 100644 --- a/manifest.json +++ b/manifest.json @@ -70,7 +70,8 @@ "https://mail.163.com/*", "https://*.mail.163.com/*", "https://webmail.vip.163.com/*", - "https://mail.126.com/*" + "https://mail.126.com/*", + "https://*.mail.126.com/*" ], "js": [ "content/activation-utils.js", 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' + ); +}); From 70f3ffb266fe415df2e2838dce7a082ea8621a58 Mon Sep 17 00:00:00 2001 From: Ryan Liu Date: Fri, 24 Apr 2026 18:06:13 +0800 Subject: [PATCH 5/8] =?UTF-8?q?fix:=20=E6=94=AF=E6=8C=81=E6=96=B0=E7=89=88?= =?UTF-8?q?=20ChatGPT=20=E7=99=BB=E5=BD=95=E9=AA=8C=E8=AF=81=E7=A0=81?= =?UTF-8?q?=E9=82=AE=E4=BB=B6=E5=8C=B9=E9=85=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- content/gmail-mail.js | 3 +++ content/icloud-mail.js | 3 +++ content/inbucket-mail.js | 5 ++++- content/mail-163.js | 7 +++++-- content/mail-2925.js | 5 ++++- content/qq-mail.js | 3 +++ hotmail-utils.js | 3 +++ luckmail-utils.js | 3 +++ tests/hotmail-utils.test.js | 10 ++++++++++ tests/icloud-mail-content.test.js | 14 ++++++++++++++ tests/luckmail-utils.test.js | 14 ++++++++++++++ tests/mail-163-content.test.js | 14 ++++++++++++++ tests/mail-2925-content.test.js | 15 +++++++++++++++ 13 files changed, 95 insertions(+), 4 deletions(-) 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..f31608a 100644 --- a/content/mail-163.js +++ b/content/mail-163.js @@ -114,7 +114,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 +391,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))); @@ -704,6 +704,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/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/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..e3be17d 100644 --- a/tests/mail-163-content.test.js +++ b/tests/mail-163-content.test.js @@ -340,6 +340,20 @@ 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'), 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'), From 9c3de546c030ac2d7d31aa9aa0997001e7493ca8 Mon Sep 17 00:00:00 2001 From: Ryan Liu Date: Sat, 25 Apr 2026 11:30:55 +0800 Subject: [PATCH 6/8] =?UTF-8?q?fix:=20=E8=A1=A5=E9=BD=90126=E9=82=AE?= =?UTF-8?q?=E7=AE=B1=E5=AD=90=E5=9F=9F=E5=90=8D=E6=9D=A5=E6=BA=90=E8=AF=86?= =?UTF-8?q?=E5=88=AB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- content/utils.js | 1 + tests/content-utils.test.js | 16 ++++++++++++++++ 2 files changed, 17 insertions(+) diff --git a/content/utils.js b/content/utils.js index 3b8f0f5..7eab2e4 100644 --- a/content/utils.js +++ b/content/utils.js @@ -15,6 +15,7 @@ function detectScriptSource({ || 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'; diff --git a/tests/content-utils.test.js b/tests/content-utils.test.js index 0ea69ca..e3a11d0 100644 --- a/tests/content-utils.test.js +++ b/tests/content-utils.test.js @@ -64,3 +64,19 @@ return { detectScriptSource }; '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' + ); +}); From a273bc2c4b4962f04c454da44b47bcd2fb371e30 Mon Sep 17 00:00:00 2001 From: Ryan Liu Date: Sat, 25 Apr 2026 11:49:51 +0800 Subject: [PATCH 7/8] =?UTF-8?q?fix:=20=E4=BF=AE=E6=AD=A3=E7=BD=91=E6=98=93?= =?UTF-8?q?=E9=82=AE=E7=AE=B1=E5=85=B1=E4=BA=AB=E8=84=9A=E6=9C=AC=E7=9A=84?= =?UTF-8?q?=E9=82=AE=E7=AE=B1=E5=93=81=E7=89=8C=E6=97=A5=E5=BF=97?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- content/mail-163.js | 24 ++++++++++++++++++++---- tests/mail-163-content.test.js | 24 +++++++++++++++++++++++- 2 files changed, 43 insertions(+), 5 deletions(-) diff --git a/content/mail-163.js b/content/mail-163.js index f31608a..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; @@ -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}中找到新的匹配邮件。` + '请手动检查收件箱。' ); } diff --git a/tests/mail-163-content.test.js b/tests/mail-163-content.test.js index e3be17d..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'), @@ -358,10 +375,12 @@ test('handlePollEmail ignores same-minute old snapshot mail before fallback', as 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(); @@ -419,7 +438,7 @@ return { handlePollEmail }; intervalMs: 1, filterAfterTimestamp: new Date(2026, 3, 22, 22, 22, 40, 0).getTime(), }), - /未在 163 邮箱中找到新的匹配邮件/ + /未在 126 邮箱中找到新的匹配邮件/ ); }); @@ -427,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'); @@ -519,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'); @@ -598,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'); From c56e9c9e3eebaa325316ad1136f5e6ef5bf8b824 Mon Sep 17 00:00:00 2001 From: Ryan Liu Date: Sat, 25 Apr 2026 12:02:04 +0800 Subject: [PATCH 8/8] =?UTF-8?q?test:=20=E5=90=8C=E6=AD=A5=E8=B4=A1?= =?UTF-8?q?=E7=8C=AE=E6=8C=89=E9=92=AE=E6=96=87=E6=A1=88=E6=96=AD=E8=A8=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- tests/sidepanel-contribution-button.test.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/sidepanel-contribution-button.test.js b/tests/sidepanel-contribution-button.test.js index d58a4dd..5e1cb7b 100644 --- a/tests/sidepanel-contribution-button.test.js +++ b/tests/sidepanel-contribution-button.test.js @@ -10,7 +10,7 @@ test('sidepanel html keeps a single contribution mode button in header', () => { assert.equal(matches.length, 1); assert.match(html, /id="btn-contribution-mode"[^>]*title="进入贡献模式并打开官网页"/); - assert.match(html, />贡献\/使用<\/button>/); + assert.match(html, />贡献\/使用教程<\/button>/); assert.match(html, /<\/header>\s*