2925邮箱添加邮箱接收和发送功能
This commit is contained in:
@@ -81,33 +81,34 @@
|
||||
## 安装
|
||||
|
||||
1. 打开 `chrome://extensions/`
|
||||
2. 开启“开发者模式”
|
||||
3. 点击“加载已解压的扩展程序”
|
||||
4. 选择本项目目录
|
||||
5. 打开扩展侧边栏
|
||||
|
||||
## 2026-04-17 更新补充:Gmail / 2925 别名邮箱
|
||||
|
||||
本次版本对 `Gmail` 与 `2925` 的注册邮箱逻辑做了统一整理:
|
||||
本次版本对 `Gmail` 与 `2925 provide` 的注册邮箱逻辑做了统一整理:
|
||||
|
||||
- `Gmail` 与 `2925` 现在都走同一套“别名邮箱”逻辑。
|
||||
- `Gmail` 与 `2925 provide` 现在都走同一套“别名邮箱”逻辑。
|
||||
- 两者都不再使用“只填前缀再特殊拼接”的界面交互。
|
||||
- 两者都要求先填写“基邮箱”:
|
||||
- `Gmail`:例如 `name@gmail.com`
|
||||
- `2925`:例如 `name@2925.com`
|
||||
- `2925`(仅 provide 模式):例如 `name@2925.com`
|
||||
- 侧边栏里的“注册邮箱”输入框对这两种模式都已开放,可直接手动填写完整邮箱。
|
||||
- 侧边栏里的 `获取 / 生成` 按钮对这两种模式也可用,行为与 Duck / Cloudflare 一样,都是“可自动生成,也可手动覆盖”。
|
||||
- 当 `Mail = 2925` 且模式切到 `接收邮箱` 时,不再走别名基邮箱链路,而是回退到普通“邮箱生成 / 手动填写注册邮箱”路线,2925 只负责后续收信。
|
||||
|
||||
具体行为:
|
||||
|
||||
- `Gmail` 会基于完整基邮箱生成 `name+tag@gmail.com`
|
||||
- `2925` 会基于完整基邮箱生成 `name123456@2925.com`
|
||||
- `2925` 仅在 provide 模式下会基于完整基邮箱生成 `name123456@2925.com`
|
||||
- 如果当前“注册邮箱”里已经是与当前基邮箱兼容的完整邮箱,流程会优先复用,不会强行重新生成
|
||||
|
||||
注意:
|
||||
|
||||
- `2925` 旧的“只填前缀”使用方式已经不再推荐,应该改为填写完整基邮箱
|
||||
- 如果你手动填写了与当前 `Gmail / 2925` 基邮箱不匹配的完整邮箱,侧边栏会在保存或执行 Step 3 时拦截
|
||||
2. 开启“开发者模式”
|
||||
3. 点击“加载已解压的扩展程序”
|
||||
4. 选择本项目目录
|
||||
5. 打开扩展侧边栏
|
||||
- 如果你手动填写了与当前 `Gmail / 2925 provide` 基邮箱不匹配的完整邮箱,侧边栏会在保存或执行 Step 3 时拦截
|
||||
|
||||
## 快速开始
|
||||
|
||||
@@ -143,12 +144,15 @@
|
||||
|
||||
1. `Mail` 选择 `2925`
|
||||
2. 在 `2925 账号池` 中添加 `邮箱 / 密码`
|
||||
3. 在“2925 基邮箱”右侧打开 `号池` 开关后,基邮箱输入框会切成下拉框,只能从 2925 账号池中选择邮箱;关闭开关则回退到原来的手填基邮箱路线
|
||||
4. 可先点 `使用此账号` 让当前别名基邮箱切到这条 2925 账号,再点 `登录` 手动验证网页邮箱登录态
|
||||
5. 只有在 `号池` 开关开启时,自动流程执行到 Step 4 / Step 8 前才会自动检查 2925 登录态;如果未登录,会先清理登录 cookie、等待 `3 秒`,再打开登录页,并在页面打开后再等待 `3 秒`,然后使用当前账号自动登录;点击登录后若 `20 秒`内仍未进入收件箱,则会判定当前登录失败
|
||||
6. 当 Step 4 / Step 8 轮询邮箱时遇到“子邮箱已达上限邮箱”,扩展会记录当前时间;如果还有下一个可用账号,就禁用当前账号 24 小时并自动切换登录;如果没有下一个可用账号,或当前未启用号池模式,则会直接复用现有“手动暂停 / 停止”逻辑终止自动流程
|
||||
7. 如果你同时开启了 `Auto` 的自动重试,当前尝试结束后会按现有逻辑自动进入下一次尝试,不需要再手动介入
|
||||
8. `Mail = 2925` 仍然走 Gmail / 2925 共用的别名邮箱链路;实际注册邮箱会基于当前 2925 账号邮箱生成,例如 `name@2925.com -> name123456@2925.com`
|
||||
3. 先根据你的用途选择 `2925 模式`
|
||||
- `提供邮箱`:注册邮箱本身就是 2925 别名,会显示“别名基邮箱”输入
|
||||
- `接收邮箱`:注册邮箱回退到普通“邮箱生成 / 手动填写”路线,2925 只负责收信
|
||||
4. `2925 号池` 现在是独立配置行;开启 `号池` 后可从下拉框中选择当前 2925 账号。若当前处于 `提供邮箱` 模式,这个账号也会同步作为别名基邮箱
|
||||
5. 可先点 `使用此账号` 让当前 2925 账号切到这条记录,再点 `登录` 手动验证网页邮箱登录态
|
||||
6. 只有在 `号池` 开关开启时,自动流程执行到 Step 4 / Step 8 前才会自动检查 2925 登录态;如果未登录,会先清理登录 cookie、等待 `3 秒`,再打开登录页,并在页面打开后再等待 `3 秒`,然后使用当前账号自动登录;点击登录后若 `20 秒`内仍未进入收件箱,则会判定当前登录失败
|
||||
7. 当 Step 4 / Step 8 轮询邮箱时遇到“子邮箱已达上限邮箱”,扩展会记录当前时间;如果还有下一个可用账号,就禁用当前账号 24 小时并自动切换登录;如果没有下一个可用账号,或当前未启用号池模式,则会直接复用现有“手动暂停 / 停止”逻辑终止自动流程
|
||||
8. 如果你同时开启了 `Auto` 的自动重试,当前尝试结束后会按现有逻辑自动进入下一次尝试,不需要再手动介入
|
||||
9. 只有 `Mail = 2925` 且模式为 `提供邮箱` 时,才会继续走 Gmail / 2925 共用的别名邮箱链路;例如 `name@2925.com -> name123456@2925.com`
|
||||
|
||||
## 侧边栏配置说明
|
||||
|
||||
|
||||
+22
-2
@@ -2132,11 +2132,21 @@ function isGeneratedAliasProvider(stateOrProvider, mail2925Mode = undefined) {
|
||||
const provider = typeof stateOrProvider === 'string'
|
||||
? stateOrProvider
|
||||
: stateOrProvider?.mailProvider;
|
||||
const resolvedMail2925Mode = mail2925Mode !== undefined
|
||||
? normalizeMail2925Mode(mail2925Mode)
|
||||
: getMail2925Mode(stateOrProvider);
|
||||
const utils = (typeof self !== 'undefined' ? self : globalThis).MultiPageManagedAliasUtils || null;
|
||||
if (utils?.usesManagedAliasGeneration) {
|
||||
return utils.usesManagedAliasGeneration(provider, { mail2925Mode: resolvedMail2925Mode });
|
||||
}
|
||||
if (utils?.isManagedAliasProvider) {
|
||||
if (String(provider || '').trim().toLowerCase() === '2925') {
|
||||
return utils.isManagedAliasProvider(provider) && resolvedMail2925Mode === MAIL_2925_MODE_PROVIDE;
|
||||
}
|
||||
return utils.isManagedAliasProvider(provider);
|
||||
}
|
||||
return provider === GMAIL_PROVIDER || provider === '2925';
|
||||
return provider === GMAIL_PROVIDER
|
||||
|| (provider === '2925' && resolvedMail2925Mode === MAIL_2925_MODE_PROVIDE);
|
||||
}
|
||||
|
||||
function shouldUseCustomRegistrationEmail(state = {}) {
|
||||
@@ -2279,11 +2289,21 @@ function isGeneratedAliasProvider(stateOrProvider, mail2925Mode = undefined) {
|
||||
const provider = typeof stateOrProvider === 'string'
|
||||
? stateOrProvider
|
||||
: stateOrProvider?.mailProvider;
|
||||
const resolvedMail2925Mode = mail2925Mode !== undefined
|
||||
? normalizeMail2925Mode(mail2925Mode)
|
||||
: getMail2925Mode(stateOrProvider);
|
||||
const utils = getManagedAliasUtils();
|
||||
if (utils?.usesManagedAliasGeneration) {
|
||||
return utils.usesManagedAliasGeneration(provider, { mail2925Mode: resolvedMail2925Mode });
|
||||
}
|
||||
if (utils?.isManagedAliasProvider) {
|
||||
if (String(provider || '').trim().toLowerCase() === '2925') {
|
||||
return utils.isManagedAliasProvider(provider) && resolvedMail2925Mode === MAIL_2925_MODE_PROVIDE;
|
||||
}
|
||||
return utils.isManagedAliasProvider(provider);
|
||||
}
|
||||
return provider === GMAIL_PROVIDER || provider === '2925';
|
||||
return provider === GMAIL_PROVIDER
|
||||
|| (provider === '2925' && resolvedMail2925Mode === MAIL_2925_MODE_PROVIDE);
|
||||
}
|
||||
|
||||
function shouldUseCustomRegistrationEmail(state = {}) {
|
||||
|
||||
@@ -195,6 +195,9 @@
|
||||
...(state || {}),
|
||||
mailProvider: provider,
|
||||
};
|
||||
if (options.mail2925Mode !== undefined) {
|
||||
mergedState.mail2925Mode = String(options.mail2925Mode || '').trim();
|
||||
}
|
||||
if (options.gmailBaseEmail !== undefined) {
|
||||
mergedState.gmailBaseEmail = String(options.gmailBaseEmail || '').trim();
|
||||
}
|
||||
@@ -227,7 +230,10 @@
|
||||
async function fetchGeneratedEmail(state, options = {}) {
|
||||
const currentState = state || await getState();
|
||||
const provider = String(options.mailProvider || currentState.mailProvider || '').trim().toLowerCase();
|
||||
if (isGeneratedAliasProvider?.(provider)) {
|
||||
const mail2925Mode = options.mail2925Mode !== undefined
|
||||
? options.mail2925Mode
|
||||
: currentState.mail2925Mode;
|
||||
if (isGeneratedAliasProvider?.(provider, mail2925Mode)) {
|
||||
return fetchManagedAliasEmail(currentState, options);
|
||||
}
|
||||
const generator = normalizeEmailGenerator(options.generator ?? currentState.emailGenerator);
|
||||
|
||||
@@ -113,12 +113,15 @@
|
||||
|
||||
function getVerificationPollPayload(step, state, overrides = {}) {
|
||||
const is2925Provider = state?.mailProvider === '2925';
|
||||
const mail2925MatchTargetEmail = is2925Provider
|
||||
&& String(state?.mail2925Mode || '').trim().toLowerCase() === 'receive';
|
||||
if (step === 4) {
|
||||
return {
|
||||
filterAfterTimestamp: is2925Provider ? 0 : getHotmailVerificationRequestTimestamp(4, state),
|
||||
senderFilters: ['openai', 'noreply', 'verify', 'auth', 'duckduckgo', 'forward'],
|
||||
subjectFilters: ['verify', 'verification', 'code', '验证码', 'confirm'],
|
||||
targetEmail: state.email,
|
||||
mail2925MatchTargetEmail,
|
||||
maxAttempts: is2925Provider ? MAIL_2925_VERIFICATION_MAX_ATTEMPTS : 5,
|
||||
intervalMs: is2925Provider ? MAIL_2925_VERIFICATION_INTERVAL_MS : 3000,
|
||||
...overrides,
|
||||
@@ -130,6 +133,7 @@
|
||||
senderFilters: ['openai', 'noreply', 'verify', 'auth', 'chatgpt', 'duckduckgo', 'forward'],
|
||||
subjectFilters: ['verify', 'verification', 'code', '验证码', 'confirm', 'login'],
|
||||
targetEmail: String(state?.step8VerificationTargetEmail || '').trim() || state.email,
|
||||
mail2925MatchTargetEmail,
|
||||
maxAttempts: is2925Provider ? MAIL_2925_VERIFICATION_MAX_ATTEMPTS : 5,
|
||||
intervalMs: is2925Provider ? MAIL_2925_VERIFICATION_INTERVAL_MS : 3000,
|
||||
...overrides,
|
||||
|
||||
@@ -629,6 +629,39 @@ function extractVerificationCode(text, strictChatGPTCodeOnly = false) {
|
||||
return null;
|
||||
}
|
||||
|
||||
function extractEmails(text = '') {
|
||||
const matches = String(text || '').match(/[a-z0-9._%+-]+@[a-z0-9.-]+\.[a-z]{2,}/gi) || [];
|
||||
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 extractedEmails = extractEmails(normalizedText);
|
||||
if (!extractedEmails.length) {
|
||||
return { matches: true, hasExplicitEmail: false };
|
||||
}
|
||||
|
||||
return {
|
||||
matches: extractedEmails.some((candidate) => emailMatchesTarget(candidate, normalizedTarget)),
|
||||
hasExplicitEmail: true,
|
||||
};
|
||||
}
|
||||
|
||||
function normalizeMinuteTimestamp(timestamp) {
|
||||
if (!Number.isFinite(timestamp) || timestamp <= 0) return 0;
|
||||
const date = new Date(timestamp);
|
||||
@@ -930,6 +963,8 @@ async function handlePollEmail(step, payload) {
|
||||
filterAfterTimestamp = 0,
|
||||
excludeCodes = [],
|
||||
strictChatGPTCodeOnly = false,
|
||||
targetEmail = '',
|
||||
mail2925MatchTargetEmail = false,
|
||||
} = payload || {};
|
||||
const excludedCodeSet = new Set(excludeCodes.filter(Boolean));
|
||||
const filterAfterMinute = normalizeMinuteTimestamp(Number(filterAfterTimestamp) || 0);
|
||||
@@ -994,9 +1029,21 @@ async function handlePollEmail(step, payload) {
|
||||
if (!matchesMailFilters(previewText, senderFilters, subjectFilters)) {
|
||||
continue;
|
||||
}
|
||||
const previewTargetState = mail2925MatchTargetEmail
|
||||
? getTargetEmailMatchState(previewText, targetEmail)
|
||||
: { matches: true, hasExplicitEmail: false };
|
||||
if (mail2925MatchTargetEmail && previewTargetState.hasExplicitEmail && !previewTargetState.matches) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const previewCode = extractVerificationCode(previewText, strictChatGPTCodeOnly);
|
||||
const openedText = await openMailAndDeleteAfterRead(item, step);
|
||||
const openedTargetState = mail2925MatchTargetEmail
|
||||
? getTargetEmailMatchState(openedText, targetEmail)
|
||||
: { matches: true, hasExplicitEmail: false };
|
||||
if (mail2925MatchTargetEmail && openedTargetState.hasExplicitEmail && !openedTargetState.matches) {
|
||||
continue;
|
||||
}
|
||||
const bodyCode = extractVerificationCode(openedText, strictChatGPTCodeOnly);
|
||||
const candidateCode = bodyCode || previewCode;
|
||||
|
||||
|
||||
@@ -3,6 +3,9 @@
|
||||
})(typeof self !== 'undefined' ? self : globalThis, function createManagedAliasUtilsModule() {
|
||||
const GMAIL_PROVIDER = 'gmail';
|
||||
const MAIL_2925_PROVIDER = '2925';
|
||||
const MAIL_2925_MODE_PROVIDE = 'provide';
|
||||
const MAIL_2925_MODE_RECEIVE = 'receive';
|
||||
const DEFAULT_MAIL_2925_MODE = MAIL_2925_MODE_PROVIDE;
|
||||
|
||||
const PROVIDER_CONFIGS = {
|
||||
[GMAIL_PROVIDER]: {
|
||||
@@ -64,10 +67,31 @@
|
||||
return PROVIDER_CONFIGS[String(provider || '').trim().toLowerCase()] || null;
|
||||
}
|
||||
|
||||
function normalizeMail2925Mode(value = '') {
|
||||
return String(value || '').trim().toLowerCase() === MAIL_2925_MODE_RECEIVE
|
||||
? MAIL_2925_MODE_RECEIVE
|
||||
: DEFAULT_MAIL_2925_MODE;
|
||||
}
|
||||
|
||||
function isManagedAliasProvider(provider = '') {
|
||||
return Boolean(getManagedAliasProviderConfig(provider));
|
||||
}
|
||||
|
||||
function usesManagedAliasGeneration(provider = '', options = {}) {
|
||||
const normalizedProvider = String(provider || '').trim().toLowerCase();
|
||||
if (!isManagedAliasProvider(normalizedProvider)) {
|
||||
return false;
|
||||
}
|
||||
if (normalizedProvider !== MAIL_2925_PROVIDER) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const mail2925Mode = typeof options === 'string'
|
||||
? options
|
||||
: options?.mail2925Mode;
|
||||
return normalizeMail2925Mode(mail2925Mode) === MAIL_2925_MODE_PROVIDE;
|
||||
}
|
||||
|
||||
function parseEmailParts(rawValue = '') {
|
||||
const value = String(rawValue || '').trim().toLowerCase();
|
||||
const match = value.match(/^([^@\s]+)@([^@\s]+\.[^@\s]+)$/);
|
||||
@@ -136,11 +160,16 @@
|
||||
|
||||
return {
|
||||
buildManagedAliasEmail,
|
||||
DEFAULT_MAIL_2925_MODE,
|
||||
getManagedAliasProviderConfig,
|
||||
getManagedAliasProviderUiCopy,
|
||||
isManagedAliasEmail,
|
||||
isManagedAliasProvider,
|
||||
MAIL_2925_MODE_PROVIDE,
|
||||
MAIL_2925_MODE_RECEIVE,
|
||||
normalizeMail2925Mode,
|
||||
parseEmailParts,
|
||||
parseManagedAliasBaseEmail,
|
||||
usesManagedAliasGeneration,
|
||||
};
|
||||
});
|
||||
|
||||
@@ -269,20 +269,25 @@
|
||||
<button id="btn-cf-domain-mode" class="btn btn-outline btn-sm data-inline-btn" type="button">添加</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="data-row" id="row-email-prefix" style="display:none;">
|
||||
<span class="data-label" id="label-email-prefix">别名基邮箱</span>
|
||||
<div class="data-row" id="row-mail2925-pool-settings" style="display:none;">
|
||||
<span class="data-label">2925 号池</span>
|
||||
<div class="data-inline mail2925-base-inline">
|
||||
<input type="text" id="input-email-prefix" class="data-input mail2925-base-input" placeholder="例如 yourname@example.com" />
|
||||
<select id="select-mail2925-pool-account" class="data-select mail2925-pool-select" style="display:none;">
|
||||
<option value="">请选择号池邮箱</option>
|
||||
</select>
|
||||
<label class="toggle-switch mail2925-pool-toggle" id="label-mail2925-use-account-pool" for="input-mail2925-use-account-pool" style="display:none;" title="开启后只能从号池中选择 2925 基邮箱">
|
||||
<label class="toggle-switch mail2925-pool-toggle" id="label-mail2925-use-account-pool" for="input-mail2925-use-account-pool" style="display:none;" title="开启后启用 2925 账号池">
|
||||
<input type="checkbox" id="input-mail2925-use-account-pool" />
|
||||
<span class="toggle-switch-track" aria-hidden="true">
|
||||
<span class="toggle-switch-thumb"></span>
|
||||
</span>
|
||||
<span>号池</span>
|
||||
</label>
|
||||
<select id="select-mail2925-pool-account" class="data-select mail2925-pool-select" style="display:none;">
|
||||
<option value="">请选择号池邮箱</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="data-row" id="row-email-prefix" style="display:none;">
|
||||
<span class="data-label" id="label-email-prefix">别名基邮箱</span>
|
||||
<div class="data-inline">
|
||||
<input type="text" id="input-email-prefix" class="data-input mail2925-base-input" placeholder="例如 yourname@example.com" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="data-row" id="row-inbucket-host" style="display:none;">
|
||||
|
||||
+33
-9
@@ -98,6 +98,7 @@ const rowCustomPassword = document.getElementById('row-custom-password');
|
||||
const selectMailProvider = document.getElementById('select-mail-provider');
|
||||
const btnMailLogin = document.getElementById('btn-mail-login');
|
||||
const rowMail2925Mode = document.getElementById('row-mail-2925-mode');
|
||||
const rowMail2925PoolSettings = document.getElementById('row-mail2925-pool-settings');
|
||||
const mail2925ModeButtons = Array.from(document.querySelectorAll('[data-mail2925-mode]'));
|
||||
const rowEmailGenerator = document.getElementById('row-email-generator');
|
||||
const selectEmailGenerator = document.getElementById('select-email-generator');
|
||||
@@ -264,12 +265,24 @@ function getManagedAliasUtils() {
|
||||
return window.MultiPageManagedAliasUtils || null;
|
||||
}
|
||||
|
||||
function isManagedAliasProvider(provider = selectMailProvider.value) {
|
||||
function isManagedAliasProvider(provider = selectMailProvider.value, mail2925Mode = getSelectedMail2925Mode()) {
|
||||
const utils = getManagedAliasUtils();
|
||||
if (utils?.usesManagedAliasGeneration) {
|
||||
return utils.usesManagedAliasGeneration(provider, { mail2925Mode });
|
||||
}
|
||||
if (utils?.isManagedAliasProvider) {
|
||||
const normalizedProvider = String(provider || '').trim().toLowerCase();
|
||||
if (normalizedProvider === '2925') {
|
||||
return utils.isManagedAliasProvider(provider)
|
||||
&& normalizeMail2925Mode(mail2925Mode) === MAIL_2925_MODE_PROVIDE;
|
||||
}
|
||||
return utils.isManagedAliasProvider(provider);
|
||||
}
|
||||
return [GMAIL_PROVIDER, '2925'].includes(String(provider || '').trim().toLowerCase());
|
||||
const normalizedProvider = String(provider || '').trim().toLowerCase();
|
||||
if (normalizedProvider === '2925') {
|
||||
return normalizeMail2925Mode(mail2925Mode) === MAIL_2925_MODE_PROVIDE;
|
||||
}
|
||||
return normalizedProvider === GMAIL_PROVIDER;
|
||||
}
|
||||
|
||||
function parseManagedAliasBaseEmail(rawValue, provider = selectMailProvider.value) {
|
||||
@@ -288,7 +301,10 @@ function isManagedAliasEmail(value, baseEmail = '', provider = selectMailProvide
|
||||
return false;
|
||||
}
|
||||
|
||||
function getManagedAliasProviderUiCopy(provider = selectMailProvider.value) {
|
||||
function getManagedAliasProviderUiCopy(provider = selectMailProvider.value, mail2925Mode = getSelectedMail2925Mode()) {
|
||||
if (!isManagedAliasProvider(provider, mail2925Mode)) {
|
||||
return null;
|
||||
}
|
||||
const utils = getManagedAliasUtils();
|
||||
if (utils?.getManagedAliasProviderUiCopy) {
|
||||
return utils.getManagedAliasProviderUiCopy(provider);
|
||||
@@ -592,7 +608,7 @@ const LOG_LEVEL_LABELS = {
|
||||
};
|
||||
|
||||
function usesGeneratedAliasMailProvider(provider, mail2925Mode = getSelectedMail2925Mode()) {
|
||||
return isManagedAliasProvider(provider);
|
||||
return isManagedAliasProvider(provider, mail2925Mode);
|
||||
}
|
||||
|
||||
function parseGmailBaseEmail(rawValue = '') {
|
||||
@@ -2639,13 +2655,18 @@ function updateMailProviderUI() {
|
||||
const useIcloudProvider = isIcloudMailProvider();
|
||||
const useEmailGenerator = !useHotmail && !useLuckmail && !useGeneratedAlias && !useCustomEmail;
|
||||
const useCloudflareTempEmailProvider = selectMailProvider.value === 'cloudflare-temp-email';
|
||||
const aliasUiCopy = useGeneratedAlias ? getManagedAliasProviderUiCopy(selectMailProvider.value) : null;
|
||||
const aliasUiCopy = useGeneratedAlias
|
||||
? getManagedAliasProviderUiCopy(selectMailProvider.value, mail2925Mode)
|
||||
: null;
|
||||
const uiCopy = getCurrentRegistrationEmailUiCopy();
|
||||
updateMailLoginButtonState();
|
||||
if (rowMail2925Mode) {
|
||||
rowMail2925Mode.style.display = 'none';
|
||||
rowMail2925Mode.style.display = use2925 ? '' : 'none';
|
||||
}
|
||||
rowEmailPrefix.style.display = useGeneratedAlias ? '' : 'none';
|
||||
if (rowMail2925PoolSettings) {
|
||||
rowMail2925PoolSettings.style.display = useMail2925 ? '' : 'none';
|
||||
}
|
||||
rowEmailPrefix.style.display = useGeneratedAlias && !useMail2925AccountPool ? '' : 'none';
|
||||
const hotmailServiceMode = getSelectedHotmailServiceMode();
|
||||
rowInbucketHost.style.display = useInbucket ? '' : 'none';
|
||||
rowInbucketMailbox.style.display = useInbucket ? '' : 'none';
|
||||
@@ -2708,8 +2729,8 @@ function updateMailProviderUI() {
|
||||
selectMail2925PoolAccount.style.display = useMail2925AccountPool ? '' : 'none';
|
||||
selectMail2925PoolAccount.disabled = !useMail2925AccountPool || getMail2925Accounts().length === 0;
|
||||
}
|
||||
inputEmailPrefix.style.display = useMail2925AccountPool ? 'none' : '';
|
||||
inputEmailPrefix.readOnly = useMail2925AccountPool;
|
||||
inputEmailPrefix.style.display = '';
|
||||
inputEmailPrefix.readOnly = false;
|
||||
selectEmailGenerator.disabled = useHotmail || useLuckmail || useGeneratedAlias || useCustomEmail;
|
||||
if (useGmail) {
|
||||
labelEmailPrefix.textContent = 'Gmail 原邮箱';
|
||||
@@ -2760,7 +2781,9 @@ function updateMailProviderUI() {
|
||||
}
|
||||
if (autoHintText && useMail2925AccountPool) {
|
||||
autoHintText.textContent = getMail2925Accounts().length
|
||||
? (useGeneratedAlias
|
||||
? '当前已启用 2925 号池模式,步骤 3 会基于下拉框选中的号池邮箱生成别名地址'
|
||||
: '当前已启用 2925 号池模式,步骤 4 / 8 遇到登录页时会优先使用下拉框选中的账号自动登录')
|
||||
: '当前已启用 2925 号池模式,请先在下方 2925 账号池中添加账号并选择邮箱';
|
||||
}
|
||||
if (autoHintText && showCloudflareTempEmailReceiveMailbox) {
|
||||
@@ -3088,6 +3111,7 @@ async function fetchGeneratedEmail(options = {}) {
|
||||
generateNew: true,
|
||||
generator: selectEmailGenerator.value,
|
||||
mailProvider: selectMailProvider.value,
|
||||
mail2925Mode: getSelectedMail2925Mode(),
|
||||
...buildManagedAliasBaseEmailPayload(),
|
||||
},
|
||||
});
|
||||
|
||||
@@ -15,3 +15,64 @@ test('generated email helper module exposes a factory', () => {
|
||||
|
||||
assert.equal(typeof api?.createGeneratedEmailHelpers, 'function');
|
||||
});
|
||||
|
||||
test('generated email helper falls back to normal generator when 2925 is in receive mode', async () => {
|
||||
const source = fs.readFileSync('background/generated-email-helpers.js', 'utf8');
|
||||
const globalScope = {};
|
||||
const api = new Function('self', `${source}; return self.MultiPageGeneratedEmailHelpers;`)(globalScope);
|
||||
const events = [];
|
||||
|
||||
const helpers = api.createGeneratedEmailHelpers({
|
||||
addLog: async () => {},
|
||||
buildGeneratedAliasEmail: () => {
|
||||
throw new Error('should not build alias in receive mode');
|
||||
},
|
||||
buildCloudflareTempEmailHeaders: () => ({}),
|
||||
CLOUDFLARE_TEMP_EMAIL_GENERATOR: 'cloudflare-temp-email',
|
||||
DUCK_AUTOFILL_URL: 'https://duckduckgo.com/email',
|
||||
fetch: async () => ({ ok: true, text: async () => '{}' }),
|
||||
fetchIcloudHideMyEmail: async () => {
|
||||
throw new Error('should not use icloud generator');
|
||||
},
|
||||
getCloudflareTempEmailAddressFromResponse: () => '',
|
||||
getCloudflareTempEmailConfig: () => ({ baseUrl: '', adminAuth: '', domain: '' }),
|
||||
getState: async () => ({
|
||||
mailProvider: '2925',
|
||||
mail2925Mode: 'receive',
|
||||
emailGenerator: 'duck',
|
||||
}),
|
||||
ensureMail2925AccountForFlow: async () => {
|
||||
throw new Error('should not allocate 2925 account in receive mode');
|
||||
},
|
||||
joinCloudflareTempEmailUrl: () => '',
|
||||
normalizeCloudflareDomain: () => '',
|
||||
normalizeCloudflareTempEmailAddress: () => '',
|
||||
normalizeEmailGenerator: (value) => String(value || '').trim().toLowerCase(),
|
||||
isGeneratedAliasProvider: (_provider, mail2925Mode) => mail2925Mode === 'provide',
|
||||
reuseOrCreateTab: async () => {},
|
||||
sendToContentScript: async (_source, message) => {
|
||||
events.push(message.type);
|
||||
return { email: 'duck@example.com', generated: true };
|
||||
},
|
||||
setEmailState: async (email) => {
|
||||
events.push(['email', email]);
|
||||
},
|
||||
throwIfStopped: () => {},
|
||||
});
|
||||
|
||||
const email = await helpers.fetchGeneratedEmail({
|
||||
mailProvider: '2925',
|
||||
mail2925Mode: 'receive',
|
||||
emailGenerator: 'duck',
|
||||
}, {
|
||||
mailProvider: '2925',
|
||||
mail2925Mode: 'receive',
|
||||
generator: 'duck',
|
||||
});
|
||||
|
||||
assert.equal(email, 'duck@example.com');
|
||||
assert.deepStrictEqual(events, [
|
||||
'FETCH_DUCK_EMAIL',
|
||||
['email', 'duck@example.com'],
|
||||
]);
|
||||
});
|
||||
|
||||
@@ -164,7 +164,7 @@ return {
|
||||
assert.deepEqual(api.getReadAndDeleteCalls(), ['baseline', 'new']);
|
||||
});
|
||||
|
||||
test('handlePollEmail ignores targetEmail and still tests any matching ChatGPT mail', async () => {
|
||||
test('handlePollEmail keeps ignoring targetEmail when receive-mode matching is disabled', async () => {
|
||||
const bundle = [
|
||||
extractFunction('normalizeMinuteTimestamp'),
|
||||
extractFunction('handlePollEmail'),
|
||||
@@ -242,12 +242,103 @@ return {
|
||||
maxAttempts: 4,
|
||||
intervalMs: 1,
|
||||
targetEmail: 'expected@example.com',
|
||||
mail2925MatchTargetEmail: false,
|
||||
});
|
||||
|
||||
assert.equal(result.code, '112233');
|
||||
assert.deepEqual(api.getReadAndDeleteCalls(), ['mail-1']);
|
||||
});
|
||||
|
||||
test('handlePollEmail skips explicit mismatched target emails when receive-mode matching is enabled', async () => {
|
||||
const bundle = [
|
||||
extractFunction('extractEmails'),
|
||||
extractFunction('emailMatchesTarget'),
|
||||
extractFunction('getTargetEmailMatchState'),
|
||||
extractFunction('normalizeMinuteTimestamp'),
|
||||
extractFunction('handlePollEmail'),
|
||||
].join('\n');
|
||||
|
||||
const api = new Function(`
|
||||
let state = 'ready';
|
||||
const seenCodes = new Set();
|
||||
const readAndDeleteCalls = [];
|
||||
const mismatchMail = {
|
||||
id: 'mail-1',
|
||||
text: 'ChatGPT verification code 112233 for another.user@example.com',
|
||||
};
|
||||
const targetMail = {
|
||||
id: 'mail-2',
|
||||
text: 'ChatGPT verification code 445566 for expected@example.com',
|
||||
};
|
||||
|
||||
function findMailItems() {
|
||||
return state === 'ready' ? [mismatchMail, targetMail] : [];
|
||||
}
|
||||
|
||||
function getMailItemId(item) {
|
||||
return item.id;
|
||||
}
|
||||
|
||||
function getCurrentMailIds(items = []) {
|
||||
return new Set(items.map((item) => item.id));
|
||||
}
|
||||
|
||||
function parseMailItemTimestamp() {
|
||||
return Date.now();
|
||||
}
|
||||
|
||||
function matchesMailFilters(text) {
|
||||
return /chatgpt|openai|verification/i.test(String(text || ''));
|
||||
}
|
||||
|
||||
function getMailItemText(item) {
|
||||
return item.text;
|
||||
}
|
||||
|
||||
function extractVerificationCode(text) {
|
||||
const match = String(text || '').match(/(\\d{6})/);
|
||||
return match ? match[1] : null;
|
||||
}
|
||||
|
||||
async function sleep() {}
|
||||
async function sleepRandom() {}
|
||||
async function returnToInbox() {
|
||||
return true;
|
||||
}
|
||||
async function refreshInbox() {}
|
||||
|
||||
async function openMailAndDeleteAfterRead(item) {
|
||||
readAndDeleteCalls.push(item.id);
|
||||
return item.text;
|
||||
}
|
||||
|
||||
async function ensureSeenCodesSession() {}
|
||||
function persistSeenCodes() {}
|
||||
function log() {}
|
||||
|
||||
${bundle}
|
||||
|
||||
return {
|
||||
handlePollEmail,
|
||||
getReadAndDeleteCalls() {
|
||||
return readAndDeleteCalls.slice();
|
||||
},
|
||||
};
|
||||
`)();
|
||||
|
||||
const result = await api.handlePollEmail(8, {
|
||||
senderFilters: ['chatgpt'],
|
||||
subjectFilters: ['verification'],
|
||||
maxAttempts: 1,
|
||||
intervalMs: 1,
|
||||
targetEmail: 'expected@example.com',
|
||||
mail2925MatchTargetEmail: true,
|
||||
});
|
||||
|
||||
assert.equal(result.code, '445566');
|
||||
assert.deepEqual(api.getReadAndDeleteCalls(), ['mail-2']);
|
||||
});
|
||||
|
||||
test('handlePollEmail only accepts 2925 mails inside the fixed lookback window', async () => {
|
||||
const bundle = [
|
||||
extractFunction('normalizeMinuteTimestamp'),
|
||||
|
||||
@@ -26,3 +26,12 @@ test('managed alias utils validate provider email with or without configured bas
|
||||
assert.equal(api.isManagedAliasEmail('manual@gmail.com', 'gmail', ''), true);
|
||||
assert.equal(api.isManagedAliasEmail('manual@qq.com', 'gmail', ''), false);
|
||||
});
|
||||
|
||||
test('managed alias utils keep 2925 alias generation behind provide mode only', () => {
|
||||
assert.equal(api.normalizeMail2925Mode('provide'), 'provide');
|
||||
assert.equal(api.normalizeMail2925Mode('receive'), 'receive');
|
||||
assert.equal(api.normalizeMail2925Mode('other'), 'provide');
|
||||
assert.equal(api.usesManagedAliasGeneration('gmail'), true);
|
||||
assert.equal(api.usesManagedAliasGeneration('2925', { mail2925Mode: 'provide' }), true);
|
||||
assert.equal(api.usesManagedAliasGeneration('2925', { mail2925Mode: 'receive' }), false);
|
||||
});
|
||||
|
||||
@@ -0,0 +1,109 @@
|
||||
const test = require('node:test');
|
||||
const assert = require('node:assert/strict');
|
||||
const fs = require('node:fs');
|
||||
|
||||
const source = fs.readFileSync('sidepanel/sidepanel.js', 'utf8');
|
||||
|
||||
function extractFunction(name) {
|
||||
const markers = [`async function ${name}(`, `function ${name}(`];
|
||||
const start = markers
|
||||
.map((marker) => source.indexOf(marker))
|
||||
.find((index) => index >= 0);
|
||||
if (start < 0) {
|
||||
throw new Error(`missing function ${name}`);
|
||||
}
|
||||
|
||||
let parenDepth = 0;
|
||||
let signatureEnded = false;
|
||||
let braceStart = -1;
|
||||
for (let i = start; i < source.length; i += 1) {
|
||||
const ch = source[i];
|
||||
if (ch === '(') {
|
||||
parenDepth += 1;
|
||||
} else if (ch === ')') {
|
||||
parenDepth -= 1;
|
||||
if (parenDepth === 0) {
|
||||
signatureEnded = true;
|
||||
}
|
||||
} else if (ch === '{' && signatureEnded) {
|
||||
braceStart = i;
|
||||
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('sidepanel html keeps 2925 mode row and standalone pool settings row', () => {
|
||||
const html = fs.readFileSync('sidepanel/sidepanel.html', 'utf8');
|
||||
|
||||
assert.match(html, /id="row-mail-2925-mode"/);
|
||||
assert.match(html, /data-mail2925-mode="provide"/);
|
||||
assert.match(html, /data-mail2925-mode="receive"/);
|
||||
assert.match(html, /id="row-mail2925-pool-settings"/);
|
||||
});
|
||||
|
||||
test('sidepanel only treats 2925 as generated alias provider in provide mode', () => {
|
||||
const bundle = [
|
||||
extractFunction('isManagedAliasProvider'),
|
||||
extractFunction('usesGeneratedAliasMailProvider'),
|
||||
].join('\n');
|
||||
|
||||
const api = new Function(`
|
||||
const GMAIL_PROVIDER = 'gmail';
|
||||
const MAIL_2925_MODE_PROVIDE = 'provide';
|
||||
const MAIL_2925_MODE_RECEIVE = 'receive';
|
||||
const DEFAULT_MAIL_2925_MODE = MAIL_2925_MODE_PROVIDE;
|
||||
const selectMailProvider = { value: '2925' };
|
||||
|
||||
function normalizeMail2925Mode(value = '') {
|
||||
return String(value || '').trim().toLowerCase() === MAIL_2925_MODE_RECEIVE
|
||||
? MAIL_2925_MODE_RECEIVE
|
||||
: DEFAULT_MAIL_2925_MODE;
|
||||
}
|
||||
|
||||
function getSelectedMail2925Mode() {
|
||||
return MAIL_2925_MODE_PROVIDE;
|
||||
}
|
||||
|
||||
function getManagedAliasUtils() {
|
||||
return {
|
||||
usesManagedAliasGeneration(provider, options = {}) {
|
||||
return String(provider || '').trim().toLowerCase() === 'gmail'
|
||||
|| (String(provider || '').trim().toLowerCase() === '2925'
|
||||
&& normalizeMail2925Mode(options.mail2925Mode) === MAIL_2925_MODE_PROVIDE);
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
${bundle}
|
||||
|
||||
return {
|
||||
isManagedAliasProvider,
|
||||
usesGeneratedAliasMailProvider,
|
||||
};
|
||||
`)();
|
||||
|
||||
assert.equal(api.isManagedAliasProvider('2925', 'provide'), true);
|
||||
assert.equal(api.isManagedAliasProvider('2925', 'receive'), false);
|
||||
assert.equal(api.usesGeneratedAliasMailProvider('2925', 'provide'), true);
|
||||
assert.equal(api.usesGeneratedAliasMailProvider('2925', 'receive'), false);
|
||||
assert.equal(api.usesGeneratedAliasMailProvider('gmail', 'receive'), true);
|
||||
});
|
||||
@@ -43,6 +43,49 @@ test('verification flow keeps 2925 polling cadence in the default payload', () =
|
||||
assert.equal(step8Payload.intervalMs, 15000);
|
||||
});
|
||||
|
||||
test('verification flow only enables 2925 target email matching in receive mode', () => {
|
||||
const helpers = api.createVerificationFlowHelpers({
|
||||
addLog: async () => {},
|
||||
chrome: { tabs: { update: async () => {} } },
|
||||
CLOUDFLARE_TEMP_EMAIL_PROVIDER: 'cloudflare-temp-email',
|
||||
completeStepFromBackground: async () => {},
|
||||
confirmCustomVerificationStepBypassRequest: async () => ({ confirmed: true }),
|
||||
getHotmailVerificationPollConfig: () => ({}),
|
||||
getHotmailVerificationRequestTimestamp: () => 0,
|
||||
getState: async () => ({}),
|
||||
getTabId: async () => 1,
|
||||
HOTMAIL_PROVIDER: 'hotmail-api',
|
||||
isStopError: () => false,
|
||||
LUCKMAIL_PROVIDER: 'luckmail-api',
|
||||
MAIL_2925_VERIFICATION_INTERVAL_MS: 15000,
|
||||
MAIL_2925_VERIFICATION_MAX_ATTEMPTS: 15,
|
||||
pollCloudflareTempEmailVerificationCode: async () => ({}),
|
||||
pollHotmailVerificationCode: async () => ({}),
|
||||
pollLuckmailVerificationCode: async () => ({}),
|
||||
sendToContentScript: async () => ({}),
|
||||
sendToMailContentScriptResilient: async () => ({}),
|
||||
setState: async () => {},
|
||||
setStepStatus: async () => {},
|
||||
sleepWithStop: async () => {},
|
||||
throwIfStopped: () => {},
|
||||
VERIFICATION_POLL_MAX_ROUNDS: 5,
|
||||
});
|
||||
|
||||
const providePayload = helpers.getVerificationPollPayload(4, {
|
||||
email: 'user@example.com',
|
||||
mailProvider: '2925',
|
||||
mail2925Mode: 'provide',
|
||||
});
|
||||
const receivePayload = helpers.getVerificationPollPayload(4, {
|
||||
email: 'user@example.com',
|
||||
mailProvider: '2925',
|
||||
mail2925Mode: 'receive',
|
||||
});
|
||||
|
||||
assert.equal(providePayload.mail2925MatchTargetEmail, false);
|
||||
assert.equal(receivePayload.mail2925MatchTargetEmail, true);
|
||||
});
|
||||
|
||||
test('verification flow runs beforeSubmit hook before filling the code', async () => {
|
||||
const events = [];
|
||||
|
||||
|
||||
+19
-15
@@ -336,7 +336,8 @@
|
||||
- 自动重新发送验证码次数现在使用 sidepanel 里的单一“验证码重发”配置;普通邮箱仍按 25 秒间隔节流,Hotmail / 2925 不走这个 25 秒间隔。Step 4 若启用先请求新验证码,会先消耗一次当前步骤的自动重发次数。
|
||||
- 验证码提交重试上限当前为 15 次;页面明确拒绝验证码时,会在上限内继续拉取新验证码并重提。
|
||||
- `2925` 内容脚本会把每一封实际打开检测的邮件立即删除;同一验证码步骤启动后,试过的验证码会按“步骤 ID + 启动时间”隔离缓存,不会在本次步骤里重复提交;如果再次遇到相同验证码,对应邮件也会在读取后立即删除,避免后续反复打开。
|
||||
- `2925` 当前不再对邮件里的收件邮箱做比对,只要邮件内容命中 ChatGPT / OpenAI 验证码过滤条件,就会尝试该邮件。
|
||||
- `2925` 在 provide 模式下仍保持宽松匹配:只要邮件内容命中 ChatGPT / OpenAI 验证码过滤条件,就会尝试该邮件。
|
||||
- `2925` 在 receive 模式下会恢复“弱目标邮箱匹配”:只有当邮件里显式出现了其他收件邮箱时才会跳过;如果邮件里没有明确写出邮箱,仍允许继续尝试该验证码。
|
||||
- 当验证码最终提交成功后,后台会异步向 2925 邮箱页发送 `DELETE_ALL_EMAILS`,执行“全选 + 删除”清理剩余邮件,不阻塞主流程。
|
||||
- 如果 `2925` 邮箱页在轮询期间出现“子邮箱已达上限邮箱”,后台会记录当前时间,把当前 2925 账号禁用 24 小时,自动切到下一个可用账号并完成登录,然后直接报错结束当前尝试;如果 Auto 开启了自动重试,现有控制器会按原逻辑进入下一次尝试。
|
||||
|
||||
@@ -409,7 +410,8 @@
|
||||
|
||||
补充:
|
||||
|
||||
- 对 `2925` 而言,Step 8 不再依赖验证码页显示邮箱做收件匹配,而是直接测试所有命中 ChatGPT / OpenAI 过滤条件的邮件。
|
||||
- 对 `2925 provide` 而言,Step 8 仍不依赖验证码页显示邮箱做收件匹配,而是直接测试所有命中 ChatGPT / OpenAI 过滤条件的邮件。
|
||||
- 对 `2925 receive` 而言,Step 8 会把当前目标注册邮箱一并传给 2925 内容脚本;只有当邮件里显式写出了其他邮箱时才会跳过,从而在不破坏历史兼容性的前提下,尽量降低误收验证码的概率。
|
||||
|
||||
### Step 9
|
||||
|
||||
@@ -452,25 +454,26 @@
|
||||
|
||||
### 7.1 2026-04-17 补充:Gmail / 2925 统一别名邮箱链路
|
||||
|
||||
本轮将 Gmail 与 2925 的注册邮箱逻辑统一收敛为“共享别名邮箱链路”:
|
||||
本轮将 Gmail 与 `2925 + provide` 的注册邮箱逻辑统一收敛为“共享别名邮箱链路”:
|
||||
|
||||
- 两者都先填写“基邮箱”
|
||||
- Gmail:`name@gmail.com`
|
||||
- 2925:`name@2925.com`
|
||||
- 2925(仅 provide 模式):`name@2925.com`
|
||||
- 两者都允许两种入口:
|
||||
- 点击侧边栏按钮自动生成完整注册邮箱
|
||||
- 直接在“注册邮箱”输入框中手动填写完整邮箱
|
||||
- 当 `mailProvider = 2925` 且 `mail2925Mode = receive` 时,不再走别名基邮箱链路,而是回退到普通“邮箱生成 / 手动填写注册邮箱”路线;2925 仅负责后续收信与登录态管理
|
||||
|
||||
当前行为约定:
|
||||
|
||||
1. sidepanel 展示“别名基邮箱”输入框,并根据当前 provider 显示对应文案
|
||||
1. sidepanel 会根据当前 provider 与 `mail2925Mode` 决定是否展示“别名基邮箱”输入框
|
||||
2. 点击 `获取 / 生成` 时:
|
||||
- Gmail 生成 `name+tag@gmail.com`
|
||||
- 2925 生成 `name123456@2925.com`
|
||||
- 2925 仅在 provide 模式下生成 `name123456@2925.com`
|
||||
3. Step 2 / Step 3 进入注册流程前,会先判断当前 `state.email`
|
||||
- 如果已经是与当前基邮箱兼容的完整邮箱,则直接复用
|
||||
- 如果为空或不兼容,则按当前 provider 重新生成
|
||||
4. 保存或执行 Step 3 时,如果手动填写的完整邮箱与当前 Gmail / 2925 基邮箱不兼容,sidepanel 会直接拦截
|
||||
- 如果为空或不兼容,则按当前 provider / mode 重新生成
|
||||
4. 保存或执行 Step 3 时,如果手动填写的完整邮箱与当前 Gmail / 2925 provide 基邮箱不兼容,sidepanel 会直接拦截;`2925 receive` 不参与这条兼容性约束
|
||||
5. auto-run fresh attempt reset 时,会保留:
|
||||
- `gmailBaseEmail`
|
||||
- `mail2925BaseEmail`
|
||||
@@ -554,13 +557,14 @@
|
||||
链路:
|
||||
|
||||
1. 用户在 sidepanel 的 2925 账号池中保存 `email / password`
|
||||
2. 只有当 sidepanel 中的 `mail2925UseAccountPool` 开关开启时,别名基邮箱才会优先取当前账号池选中的 2925 账号邮箱;关闭时会回退到原来的手填 `mail2925BaseEmail`
|
||||
3. 手动点击 `登录` 或自动流程进入 Step 4 前,后台会先打开当前 2925 邮箱页:如果仍停留在收件箱则直接复用;如果跳到登录页,则仅在号池模式开启时才自动登录,关闭号池时直接调用现有停止逻辑结束流程
|
||||
4. 一旦轮询期间出现“子邮箱已达上限邮箱”,后台会先判断是否启用了号池模式:若已启用且还有下一个可用账号,则把当前账号禁用 24 小时并自动切到下一个账号重新登录;若未启用,则直接调用现有停止逻辑结束流程
|
||||
5. 如果登录提交后 20 秒内仍未进入收件箱,且当前正处于自动运行中,则后台会直接复用现有 `requestStop()` 停止链路,把整个自动流程停成和用户手动点击“停止”一致的状态;这类情况常见于图片验证、行为验证或其他阻断登录的中间页
|
||||
6. 如果没有下一个可用账号,或当前未启用号池模式,则不会继续消耗自动重试次数,而是直接复用现有 `requestStop()` 停止链路,把整个自动流程停成和用户手动点击“停止”一致的状态
|
||||
7. sidepanel 中 2925 账号池的新增表单也走与 Hotmail 相同的共享交互:默认收起,头部按钮切换“添加账号 / 取消添加”,操作行右侧提供“批量导入”,保存成功后自动收起并清空。
|
||||
8. 当 2925 号池模式开启时,当前选中的号池邮箱会同步回写到同一个 `mail2925BaseEmail` 字段;因此用户切换号池账号后,即使再次关闭号池模式,也会直接沿用刚才选中的邮箱作为手动基邮箱,无需重新输入。
|
||||
2. sidepanel 中会单独展示 `提供邮箱 / 接收邮箱` 模式切换,以及独立的 `2925 号池` 开关 / 当前账号下拉框;这样即使切到 receive 模式,账号池设置也不会被别名基邮箱行一起隐藏
|
||||
3. 只有当 sidepanel 中的 `mail2925UseAccountPool` 开关开启时,provide 模式下的别名基邮箱才会优先取当前账号池选中的 2925 账号邮箱;关闭时会回退到原来的手填 `mail2925BaseEmail`
|
||||
4. 手动点击 `登录` 或自动流程进入 Step 4 前,后台会先打开当前 2925 邮箱页:如果仍停留在收件箱则直接复用;如果跳到登录页,则仅在号池模式开启时才自动登录,关闭号池时直接调用现有停止逻辑结束流程
|
||||
5. 一旦轮询期间出现“子邮箱已达上限邮箱”,后台会先判断是否启用了号池模式:若已启用且还有下一个可用账号,则把当前账号禁用 24 小时并自动切到下一个账号重新登录;若未启用,则直接调用现有停止逻辑结束流程
|
||||
6. 如果登录提交后 20 秒内仍未进入收件箱,且当前正处于自动运行中,则后台会直接复用现有 `requestStop()` 停止链路,把整个自动流程停成和用户手动点击“停止”一致的状态;这类情况常见于图片验证、行为验证或其他阻断登录的中间页
|
||||
7. 如果没有下一个可用账号,或当前未启用号池模式,则不会继续消耗自动重试次数,而是直接复用现有 `requestStop()` 停止链路,把整个自动流程停成和用户手动点击“停止”一致的状态
|
||||
8. sidepanel 中 2925 账号池的新增表单也走与 Hotmail 相同的共享交互:默认收起,头部按钮切换“添加账号 / 取消添加”,操作行右侧提供“批量导入”,保存成功后自动收起并清空。
|
||||
9. 当 2925 号池模式开启时,当前选中的号池邮箱会同步回写到同一个 `mail2925BaseEmail` 字段;因此用户切换号池账号后,即使再次关闭号池模式,也会直接沿用刚才选中的邮箱作为手动基邮箱,无需重新输入。
|
||||
|
||||
### 7.5 iCloud
|
||||
|
||||
|
||||
+8
-7
@@ -27,7 +27,7 @@
|
||||
- `icloud-utils.js`:iCloud 隐私邮箱相关的纯工具函数,负责 host、别名列表、保留状态、已用状态等归一化。
|
||||
- `luckmail-utils.js`:LuckMail 相关的纯工具函数,负责邮箱购买记录、标签、邮件 cursor、验证码匹配等归一化。
|
||||
- `mail2925-utils.js`:2925 账号池相关的纯工具函数,负责账号归一化、冷却期判断、可用账号挑选、批量导入解析与列表更新。
|
||||
- `managed-alias-utils.js`:共享 Gmail / 2925 的别名邮箱规则,负责解析基邮箱、校验“当前完整注册邮箱”是否仍与基邮箱兼容、生成 Gmail `+tag` 与 2925 随机后缀邮箱,并输出 sidepanel 可复用的 UI 文案。
|
||||
- `managed-alias-utils.js`:共享 Gmail / 2925 的别名邮箱规则,负责解析基邮箱、校验“当前完整注册邮箱”是否仍与基邮箱兼容、生成 Gmail `+tag` 与 2925 随机后缀邮箱,并输出 sidepanel 可复用的 UI 文案;当前还统一承接 `mail2925Mode` 的归一化与“2925 仅在 provide 模式下参与别名生成”的共享判定。
|
||||
- `manifest.json`:Chrome 扩展清单,声明权限、背景脚本、侧边栏、内容脚本与规则集。
|
||||
- `microsoft-email.js`:Microsoft Graph / Outlook 邮件读取辅助模块,负责刷新令牌换 token、邮箱夹轮询和验证码提取。
|
||||
- `package.json`:仓库最小 Node 包配置,目前主要提供测试脚本定义。
|
||||
@@ -44,15 +44,15 @@
|
||||
- `background/account-run-history.js`:邮箱记录模块,负责以邮箱为主键维护最新记录(同邮箱后续状态覆盖),在步骤 2 设定邮箱后支持先写入“停止/未完成”占位状态,统一归一化成功/失败/停止三态并落地到 `chrome.storage.local`,支持清理记录,并在启用独立本地同步配置后把完整快照同步到本地 helper。
|
||||
- `background/auto-run-controller.js`:自动运行主控制器,封装多轮执行、重试、轮次摘要、线程间隔与倒计时恢复逻辑;当前自动流程会绑定 `autoRunSessionId`,手动停止后旧的倒计时计划、旧重试链路和旧恢复入口不会再复活已失效的自动运行;fresh-attempt reset 时会额外保留 `gmailBaseEmail`、`mail2925BaseEmail` 与当前 2925 账号选择,避免自动流程重置后丢失别名基邮箱与 2925 切号上下文。
|
||||
- `background/contribution-oauth.js`:贡献模式的公开 OAuth 流程模块,负责调用 `apikey.qzz.io` 的公开贡献接口、保存贡献会话运行态、在主 10 步流程里为步骤 7 提供贡献登录地址、在步骤 9/10 衔接 callback 捕获与兼容提交 `/oauth/api/submit-callback`,并把真实服务端状态映射回 sidepanel 运行态。
|
||||
- `background/generated-email-helpers.js`:生成邮箱辅助层,除了 Duck / Cloudflare / iCloud / Cloudflare Temp Email,也统一承接 Gmail / 2925 的别名邮箱生成入口;当 provider 为 2925 时,会先确保当前账号池里已有可用账号,再基于该账号生成别名邮箱。
|
||||
- `background/generated-email-helpers.js`:生成邮箱辅助层,除了 Duck / Cloudflare / iCloud / Cloudflare Temp Email,也统一承接 Gmail / 2925 的别名邮箱生成入口;当 provider 为 2925 且 `mail2925Mode = provide` 时,会先确保当前账号池里已有可用账号,再基于该账号生成别名邮箱;若 `mail2925Mode = receive`,则会回退到普通邮箱生成链路。
|
||||
- `background/logging-status.js`:后台日志、步骤状态、错误信息和若干状态判断的公共工具层;当前额外承接 `add-phone / 手机号页` 这类认证 fatal 错误的共享判定。
|
||||
- `background/mail-2925-session.js`:2925 会话模块,负责 2925 账号池持久化、当前账号切换、cookie 清理登出、自动登录、命中“子邮箱已达上限邮箱”后的 24 小时禁用与自动切号。
|
||||
- `background/message-router.js`:后台消息路由层,负责处理 `chrome.runtime.onMessage` 进入的所有业务消息;当前额外接入 2925 账号池的新增、导入、切换、登录、禁用与删除消息。
|
||||
- `background/navigation-utils.js`:导航与 URL 判断工具层,负责 callback、入口页、CPA / SUB2API 地址、步骤跳转相关判断。
|
||||
- `background/panel-bridge.js`:CPA / SUB2API 面板桥接层,封装 OAuth 地址获取所需的页面打开、脚本注入和通信。
|
||||
- `background/signup-flow-helpers.js`:注册页辅助层,负责打开注册入口、等待密码页以及解析当前流程所用邮箱;在 Gmail / 2925 模式下会优先复用已经存在且仍兼容的完整注册邮箱,只有不兼容或为空时才重新生成;当 provider 为 2925 时,会先确保账号池中已选中可用账号。
|
||||
- `background/signup-flow-helpers.js`:注册页辅助层,负责打开注册入口、等待密码页以及解析当前流程所用邮箱;在 Gmail 与 `2925 + provide` 模式下会优先复用已经存在且仍兼容的完整注册邮箱,只有不兼容或为空时才重新生成;当 provider 为 2925 且启用了 provide 模式下的号池时,会先确保账号池中已选中可用账号。
|
||||
- `background/tab-runtime.js`:标签页与内容脚本运行时基础设施,封装标签注册、冲突清理、消息超时、注入重试与队列;当前等待标签完成、注入后的短暂延迟和内容脚本重试等待都已做 Stop 感知,避免用户停止后后台还继续等待并恢复执行。
|
||||
- `background/verification-flow.js`:注册/登录验证码共享流程层,封装重发、轮询、提交、失败回退、自定义邮箱跳过、共享验证码自动重发次数配置以及 2925 长轮询参数;当前验证码提交重试上限为 15 次,2925 每次重发验证码之间都会固定跑完一轮 15 次邮箱刷新轮询;若 2925 轮询命中“子邮箱已达上限邮箱”,会转入 2925 会话模块执行“记录时间、禁用 24 小时、切下一个账号并重新登录”,然后直接结束当前尝试。
|
||||
- `background/verification-flow.js`:注册/登录验证码共享流程层,封装重发、轮询、提交、失败回退、自定义邮箱跳过、共享验证码自动重发次数配置以及 2925 长轮询参数;当前验证码提交重试上限为 15 次,2925 每次重发验证码之间都会固定跑完一轮 15 次邮箱刷新轮询;若 `mail2925Mode = receive`,会额外把目标注册邮箱传给 2925 内容脚本做弱匹配;若 2925 轮询命中“子邮箱已达上限邮箱”,会转入 2925 会话模块执行“记录时间、禁用 24 小时、切下一个账号并重新登录”,然后直接结束当前尝试。
|
||||
|
||||
## `background/steps/`
|
||||
|
||||
@@ -77,7 +77,7 @@
|
||||
- `content/icloud-mail.js`:iCloud 邮箱页面脚本,负责在 iCloud Mail 页面中读取邮件详情和验证码。
|
||||
- `content/inbucket-mail.js`:Inbucket 邮箱轮询脚本,负责在 Inbucket 页面中读取、删除验证码邮件。
|
||||
- `content/mail-163.js`:163 / 163 VIP 邮箱轮询脚本,负责网页邮箱验证码读取和邮件清理。
|
||||
- `content/mail-2925.js`:2925 邮箱页面脚本,负责 2925 邮箱自动登录态确认、收件轮询、按步骤会话隔离“已试验证码”、在每次重发验证码之间执行一轮最多 15 次的邮箱刷新轮询、命中邮件后立即删当前邮件,以及在成功后配合后台执行整箱清理;若页面出现“子邮箱已达上限邮箱”提示,会立即上报后台进入切号链路。
|
||||
- `content/mail-2925.js`:2925 邮箱页面脚本,负责 2925 邮箱自动登录态确认、收件轮询、按步骤会话隔离“已试验证码”、在每次重发验证码之间执行一轮最多 15 次的邮箱刷新轮询、命中邮件后立即删当前邮件,以及在成功后配合后台执行整箱清理;若页面出现“子邮箱已达上限邮箱”提示,会立即上报后台进入切号链路;当后台显式开启 `mail2925MatchTargetEmail` 时,会对邮件里显式出现的目标邮箱做弱匹配,避免 `receive` 模式误捞别人的验证码。
|
||||
- `content/qq-mail.js`:QQ 邮箱轮询脚本,负责网页邮箱验证码读取。
|
||||
- `content/signup-page.js`:注册、登录、授权主内容脚本,负责 OpenAI / ChatGPT 页面上的步骤执行;其中 Step 2 / 3 的延迟点击与延迟提交在真正触发前会先检查 Stop 状态,避免停止后页面继续自动点击。
|
||||
- `content/sub2api-panel.js`:SUB2API 后台内容脚本,负责获取 OAuth 地址和提交 localhost 回调;当前承接步骤 10。
|
||||
@@ -121,8 +121,8 @@
|
||||
- `sidepanel/contribution-content-update-service.js`:侧边栏贡献内容更新服务,负责拉取 `https://apikey.qzz.io/api/content-summary`、缓存公开公告/教程摘要,并输出可用于提示展示的 `promptVersion` 与最新更新时间。
|
||||
- `sidepanel/contribution-mode.js`:侧边栏贡献模式管理器,负责顶部“贡献”按钮、确认弹窗、贡献模式显隐、复用主自动流程启动、侧栏内贡献状态轮询、上传页跳转,以及贡献模式下对来源选择、配置入口、记录入口和敏感配置行的禁用与隐藏。
|
||||
- `sidepanel/sidepanel.css`:侧边栏样式文件;当前额外提供 Hotmail / 2925 共用的号池表单容器、操作按钮行与统一的收起态列表高度样式。
|
||||
- `sidepanel/sidepanel.html`:侧边栏页面结构;当前步骤列表已改为动态容器,日志区提供“记录”按钮并挂接邮箱记录覆盖层,顶部新增“贡献”按钮并在设置卡片中新增贡献模式主面板;贡献按钮下方额外挂接一个可关闭的轻提示气泡,用于提示公开公告 / 使用教程有更新;贡献面板内展示 `OAUTH / 回调 / 总状态` 三块真实运行态信息,同时把“本地同步”与“验证码重发”拆成独立行以避免特殊模式隐藏时互相影响;页面继续加载 `managed-alias-utils.js`,并把旧的“邮箱前缀”字段语义改为“别名基邮箱”;当 provider 为 2925 时,别名基邮箱行右侧会显示“号池”开关,开启后输入框切换为号池邮箱下拉选择,并显示 2925 账号池管理区;Hotmail / 2925 两个账号池当前都使用统一的头部“添加账号/取消添加”按钮和共享表单容器。
|
||||
- `sidepanel/sidepanel.js`:侧边栏主入口脚本,负责 UI 状态同步、动态步骤渲染、按钮交互、独立本地同步配置、共享验证码自动重发次数配置与广播接收,并装配 Hotmail / 2925 / iCloud / LuckMail / 贡献模式 / 邮箱记录面板 / 贡献内容更新服务;当前贡献模式的“开始贡献”会直接复用主自动流程启动逻辑,而独立 manager 负责贡献运行态展示与轮询,同时把 Gmail / 2925 的基邮箱输入、完整注册邮箱输入、自动生成按钮与兼容性校验统一接到共享别名逻辑上;当 2925 号池开关开启时,当前账号变化会同步映射到 2925 基邮箱下拉选择,关闭时则回退到原来的手填基邮箱路线;Hotmail / 2925 的新增表单显隐则统一接到 `sidepanel/account-pool-ui.js` 这一层共享 helper;侧边栏初始化与点击“自动”前会刷新一次贡献站公开内容摘要,并按本地关闭版本决定是否展示轻提示。
|
||||
- `sidepanel/sidepanel.html`:侧边栏页面结构;当前步骤列表已改为动态容器,日志区提供“记录”按钮并挂接邮箱记录覆盖层,顶部新增“贡献”按钮并在设置卡片中新增贡献模式主面板;贡献按钮下方额外挂接一个可关闭的轻提示气泡,用于提示公开公告 / 使用教程有更新;贡献面板内展示 `OAUTH / 回调 / 总状态` 三块真实运行态信息,同时把“本地同步”与“验证码重发”拆成独立行以避免特殊模式隐藏时互相影响;页面继续加载 `managed-alias-utils.js`,并把旧的“邮箱前缀”字段语义改为“别名基邮箱”;当 provider 为 2925 时,会额外显示 `提供邮箱 / 接收邮箱` 模式切换,并把“2925 号池”从别名基邮箱行拆成独立配置行,避免 receive 模式把账号池开关一起隐藏;Hotmail / 2925 两个账号池当前都使用统一的头部“添加账号/取消添加”按钮和共享表单容器。
|
||||
- `sidepanel/sidepanel.js`:侧边栏主入口脚本,负责 UI 状态同步、动态步骤渲染、按钮交互、独立本地同步配置、共享验证码自动重发次数配置与广播接收,并装配 Hotmail / 2925 / iCloud / LuckMail / 贡献模式 / 邮箱记录面板 / 贡献内容更新服务;当前贡献模式的“开始贡献”会直接复用主自动流程启动逻辑,而独立 manager 负责贡献运行态展示与轮询,同时把 Gmail / 2925 的基邮箱输入、完整注册邮箱输入、自动生成按钮与兼容性校验统一接到共享别名逻辑上;当 provider 为 2925 时,会根据 `mail2925Mode` 决定是否启用别名基邮箱链路,而 2925 号池开关与当前账号选择则独立显示并同时服务于 provide / receive 两种模式;Hotmail / 2925 的新增表单显隐统一接到 `sidepanel/account-pool-ui.js` 这一层共享 helper;侧边栏初始化与点击“自动”前会刷新一次贡献站公开内容摘要,并按本地关闭版本决定是否展示轻提示。
|
||||
- `sidepanel/update-service.js`:侧边栏更新检查服务,负责 GitHub Releases 查询、`Pro` / `v` 双版本族排序、缓存读取与版本展示。
|
||||
|
||||
## `tests/`
|
||||
@@ -182,6 +182,7 @@
|
||||
- `tests/sidepanel-icloud-provider.test.js`:测试侧边栏 iCloud 登录地址解析逻辑。
|
||||
- `tests/sidepanel-luckmail-manager.test.js`:测试侧边栏 LuckMail 管理器模块接线与空态渲染。
|
||||
- `tests/sidepanel-mail2925-manager.test.js`:测试侧边栏 2925 管理器模块接线、共享号池表单脚本加载顺序、显隐交互与空态渲染。
|
||||
- `tests/sidepanel-mail2925-mode.test.js`:测试侧边栏保留 `2925 provide / receive` 模式行与独立的号池配置行,并验证 sidepanel 只在 provide 模式下把 2925 视为别名邮箱 provider。
|
||||
- `tests/signup-entry-diagnostics.test.js`:测试注册入口诊断快照输出。
|
||||
- `tests/signup-page-tab-cleanup.test.js`:测试注册页来源标签的冲突清理逻辑。
|
||||
- `tests/step-definitions-module.test.js`:测试共享步骤定义模块及侧边栏脚本加载顺序。
|
||||
|
||||
Reference in New Issue
Block a user