diff --git a/background.js b/background.js
index 0f5aea1..3d4fc59 100644
--- a/background.js
+++ b/background.js
@@ -139,10 +139,12 @@ const ICLOUD_LOGIN_URLS = [
];
const ICLOUD_PROVIDER = 'icloud';
const GMAIL_PROVIDER = 'gmail';
+const GMAIL_ALIAS_GENERATOR = 'gmail-alias';
const HOTMAIL_PROVIDER = 'hotmail-api';
const LUCKMAIL_PROVIDER = 'luckmail-api';
const CLOUDFLARE_TEMP_EMAIL_PROVIDER = 'cloudflare-temp-email';
const CLOUDFLARE_TEMP_EMAIL_GENERATOR = 'cloudflare-temp-email';
+const CUSTOM_EMAIL_POOL_GENERATOR = 'custom-pool';
const HOTMAIL_MAILBOXES = ['INBOX', 'Junk'];
const STOP_ERROR_MESSAGE = '流程已被用户停止。';
const CLOUDFLARE_SECURITY_BLOCK_ERROR_PREFIX = 'CF_SECURITY_BLOCKED::';
@@ -263,6 +265,7 @@ const PERSISTED_SETTING_DEFAULTS = {
mail2925Mode: DEFAULT_MAIL_2925_MODE,
mail2925UseAccountPool: false,
emailGenerator: 'duck',
+ customEmailPool: [],
autoDeleteUsedIcloudAlias: false,
icloudHostPreference: 'auto',
accountRunHistoryTextEnabled: false,
@@ -640,9 +643,21 @@ function getAutoRunTimerStatusPayload(plan) {
function normalizeEmailGenerator(value = '') {
const normalized = String(value || '').trim().toLowerCase();
+ const customEmailPoolGenerator = typeof CUSTOM_EMAIL_POOL_GENERATOR === 'string'
+ ? CUSTOM_EMAIL_POOL_GENERATOR
+ : 'custom-pool';
+ const gmailAliasGenerator = typeof GMAIL_ALIAS_GENERATOR === 'string'
+ ? GMAIL_ALIAS_GENERATOR
+ : 'gmail-alias';
if (normalized === 'custom' || normalized === 'manual') {
return 'custom';
}
+ if (normalized === gmailAliasGenerator) {
+ return gmailAliasGenerator;
+ }
+ if (normalized === customEmailPoolGenerator) {
+ return customEmailPoolGenerator;
+ }
if (normalized === 'icloud') {
return 'icloud';
}
@@ -651,6 +666,36 @@ function normalizeEmailGenerator(value = '') {
return 'duck';
}
+function normalizeCustomEmailPool(value = []) {
+ const source = Array.isArray(value)
+ ? value
+ : String(value || '').split(/[\r\n,,;;]+/);
+
+ return source
+ .map((item) => String(item || '').trim().toLowerCase())
+ .filter((item) => /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(item));
+}
+
+function isCustomEmailPoolGenerator(stateOrValue = {}) {
+ const generator = typeof stateOrValue === 'string'
+ ? stateOrValue
+ : stateOrValue?.emailGenerator;
+ const customEmailPoolGenerator = typeof CUSTOM_EMAIL_POOL_GENERATOR === 'string'
+ ? CUSTOM_EMAIL_POOL_GENERATOR
+ : 'custom-pool';
+ return normalizeEmailGenerator(generator) === customEmailPoolGenerator;
+}
+
+function getCustomEmailPool(state = {}) {
+ return normalizeCustomEmailPool(state?.customEmailPool);
+}
+
+function getCustomEmailPoolEmailForRun(state = {}, targetRun = 1) {
+ const entries = getCustomEmailPool(state);
+ const numericRun = Math.max(1, Math.floor(Number(targetRun) || 1));
+ return entries[numericRun - 1] || '';
+}
+
function normalizePanelMode(value = '') {
return String(value || '').trim().toLowerCase() === 'sub2api' ? 'sub2api' : 'cpa';
}
@@ -894,6 +939,8 @@ function normalizePersistentSettingValue(key, value) {
return Boolean(value);
case 'emailGenerator':
return normalizeEmailGenerator(value);
+ case 'customEmailPool':
+ return normalizeCustomEmailPool(value);
case 'autoDeleteUsedIcloudAlias':
case 'accountRunHistoryTextEnabled':
return Boolean(value);
@@ -2130,6 +2177,18 @@ function parseGmailBaseEmail(rawValue) {
}
function isGeneratedAliasProvider(stateOrProvider, mail2925Mode = undefined) {
+ if (
+ stateOrProvider
+ && typeof stateOrProvider === 'object'
+ && !Array.isArray(stateOrProvider)
+ && normalizeEmailGenerator(stateOrProvider.emailGenerator) === (
+ typeof CUSTOM_EMAIL_POOL_GENERATOR === 'string'
+ ? CUSTOM_EMAIL_POOL_GENERATOR
+ : 'custom-pool'
+ )
+ ) {
+ return false;
+ }
const provider = typeof stateOrProvider === 'string'
? stateOrProvider
: stateOrProvider?.mailProvider;
@@ -2287,6 +2346,18 @@ function getManagedAliasBaseEmail(state = {}, provider = state?.mailProvider) {
}
function isGeneratedAliasProvider(stateOrProvider, mail2925Mode = undefined) {
+ if (
+ stateOrProvider
+ && typeof stateOrProvider === 'object'
+ && !Array.isArray(stateOrProvider)
+ && normalizeEmailGenerator(stateOrProvider.emailGenerator) === (
+ typeof CUSTOM_EMAIL_POOL_GENERATOR === 'string'
+ ? CUSTOM_EMAIL_POOL_GENERATOR
+ : 'custom-pool'
+ )
+ ) {
+ return false;
+ }
const provider = typeof stateOrProvider === 'string'
? stateOrProvider
: stateOrProvider?.mailProvider;
@@ -4999,7 +5070,12 @@ async function handleStepData(step, payload) {
});
}
await finalizeIcloudAliasAfterSuccessfulFlow(latestState);
- if (shouldUseCustomRegistrationEmail(latestState) && latestState.email) {
+ const shouldClearCustomPoolEmail = String(latestState?.emailGenerator || '').trim().toLowerCase() === (
+ typeof CUSTOM_EMAIL_POOL_GENERATOR === 'string'
+ ? CUSTOM_EMAIL_POOL_GENERATOR
+ : 'custom-pool'
+ );
+ if ((shouldUseCustomRegistrationEmail(latestState) || shouldClearCustomPoolEmail) && latestState.email) {
await setEmailStateSilently(null);
}
break;
@@ -5413,9 +5489,21 @@ async function executeStepAndWait(step, delayAfter = 2000) {
}
function getEmailGeneratorLabel(generator) {
+ const customEmailPoolGenerator = typeof CUSTOM_EMAIL_POOL_GENERATOR === 'string'
+ ? CUSTOM_EMAIL_POOL_GENERATOR
+ : 'custom-pool';
+ const gmailAliasGenerator = typeof GMAIL_ALIAS_GENERATOR === 'string'
+ ? GMAIL_ALIAS_GENERATOR
+ : 'gmail-alias';
if (generator === 'custom') {
return '自定义邮箱';
}
+ if (generator === gmailAliasGenerator) {
+ return 'Gmail +tag 邮箱';
+ }
+ if (generator === customEmailPoolGenerator) {
+ return '自定义邮箱池';
+ }
if (generator === 'icloud') {
return 'iCloud 隐私邮箱';
}
@@ -5515,11 +5603,13 @@ const generatedEmailHelpers = self.MultiPageGeneratedEmailHelpers?.createGenerat
buildGeneratedAliasEmail,
buildCloudflareTempEmailHeaders,
CLOUDFLARE_TEMP_EMAIL_GENERATOR,
+ CUSTOM_EMAIL_POOL_GENERATOR,
DUCK_AUTOFILL_URL,
fetch,
fetchIcloudHideMyEmail,
getCloudflareTempEmailAddressFromResponse,
getCloudflareTempEmailConfig,
+ getCustomEmailPoolEmail: getCustomEmailPoolEmailForRun,
getState,
ensureMail2925AccountForFlow,
joinCloudflareTempEmailUrl,
@@ -5762,6 +5852,21 @@ async function ensureAutoEmailReady(targetRun, totalRuns, attemptRuns) {
return currentState.email;
}
+ if (isCustomEmailPoolGenerator(currentState)) {
+ const queuedEmail = getCustomEmailPoolEmailForRun(currentState, targetRun);
+ if (!queuedEmail) {
+ const poolSize = getCustomEmailPool(currentState).length;
+ throw new Error(
+ poolSize > 0
+ ? `自定义邮箱池第 ${targetRun} 个邮箱不存在,请检查邮箱池数量是否与自动轮数一致。`
+ : '自定义邮箱池为空,请先至少填写 1 个邮箱。'
+ );
+ }
+ await setEmailState(queuedEmail);
+ await addLog(`=== 目标 ${targetRun}/${totalRuns} 轮:自定义邮箱池已就绪:${queuedEmail}(第 ${attemptRuns} 次尝试)===`, 'ok');
+ return queuedEmail;
+ }
+
if (shouldUseCustomRegistrationEmail(currentState)) {
await addLog(`=== 目标 ${targetRun}/${totalRuns} 轮已暂停:请先填写自定义注册邮箱,然后继续 ===`, 'warn');
await broadcastAutoRunStatus('waiting_email', {
@@ -5880,6 +5985,21 @@ async function ensureAutoEmailReady(targetRun, totalRuns, attemptRuns) {
return currentState.email;
}
+ if (isCustomEmailPoolGenerator(currentState)) {
+ const queuedEmail = getCustomEmailPoolEmailForRun(currentState, targetRun);
+ if (!queuedEmail) {
+ const poolSize = getCustomEmailPool(currentState).length;
+ throw new Error(
+ poolSize > 0
+ ? `自定义邮箱池第 ${targetRun} 个邮箱不存在,请检查邮箱池数量是否与自动轮数一致。`
+ : '自定义邮箱池为空,请先至少填写 1 个邮箱。'
+ );
+ }
+ await setEmailState(queuedEmail);
+ await addLog(`=== 目标 ${targetRun}/${totalRuns} 轮:自定义邮箱池已就绪:${queuedEmail}(第 ${attemptRuns} 次尝试)===`, 'ok');
+ return queuedEmail;
+ }
+
if (shouldUseCustomRegistrationEmail(currentState)) {
await addLog(`=== 目标 ${targetRun}/${totalRuns} 轮已暂停:请先填写自定义注册邮箱,然后继续 ===`, 'warn');
await broadcastAutoRunStatus('waiting_email', {
diff --git a/background/generated-email-helpers.js b/background/generated-email-helpers.js
index 3d924a7..f5ec2b3 100644
--- a/background/generated-email-helpers.js
+++ b/background/generated-email-helpers.js
@@ -7,11 +7,13 @@
buildGeneratedAliasEmail,
buildCloudflareTempEmailHeaders,
CLOUDFLARE_TEMP_EMAIL_GENERATOR,
+ CUSTOM_EMAIL_POOL_GENERATOR,
DUCK_AUTOFILL_URL,
fetch,
fetchIcloudHideMyEmail,
getCloudflareTempEmailAddressFromResponse,
getCloudflareTempEmailConfig,
+ getCustomEmailPoolEmail,
getState,
ensureMail2925AccountForFlow,
joinCloudflareTempEmailUrl,
@@ -188,6 +190,24 @@
return result.email;
}
+ async function fetchCustomEmailPoolEmail(state, options = {}) {
+ throwIfStopped();
+ const latestState = state || await getState();
+ const requestedIndex = Math.max(0, Math.floor(Number(options.poolIndex) || 0));
+ const email = String(getCustomEmailPoolEmail?.(latestState, requestedIndex + 1) || '').trim().toLowerCase();
+ if (!email) {
+ throw new Error(
+ requestedIndex > 0
+ ? `自定义邮箱池第 ${requestedIndex + 1} 个邮箱不存在,请检查邮箱池配置。`
+ : '自定义邮箱池为空,请先至少填写 1 个邮箱。'
+ );
+ }
+
+ await setEmailState(email);
+ await addLog(`自定义邮箱池:已取用 ${email}`, 'ok');
+ return email;
+ }
+
async function fetchManagedAliasEmail(state, options = {}) {
throwIfStopped();
const provider = String(options.mailProvider || state?.mailProvider || '').trim().toLowerCase();
@@ -233,13 +253,19 @@
const mail2925Mode = options.mail2925Mode !== undefined
? options.mail2925Mode
: currentState.mail2925Mode;
- if (isGeneratedAliasProvider?.(provider, mail2925Mode)) {
- return fetchManagedAliasEmail(currentState, options);
- }
const generator = normalizeEmailGenerator(options.generator ?? currentState.emailGenerator);
if (generator === 'custom') {
throw new Error('当前邮箱生成方式为自定义邮箱,请直接填写注册邮箱。');
}
+ if (generator === CUSTOM_EMAIL_POOL_GENERATOR) {
+ return fetchCustomEmailPoolEmail(currentState, options);
+ }
+ const shouldUseManagedAlias = typeof isGeneratedAliasProvider === 'function'
+ ? isGeneratedAliasProvider(currentState, mail2925Mode)
+ : false;
+ if (shouldUseManagedAlias) {
+ return fetchManagedAliasEmail(currentState, options);
+ }
if (generator === 'icloud') {
return fetchIcloudHideMyEmail();
}
@@ -255,6 +281,7 @@
return {
ensureCloudflareTempEmailConfig,
fetchCloudflareEmail,
+ fetchCustomEmailPoolEmail,
fetchCloudflareTempEmailAddress,
fetchDuckEmail,
fetchGeneratedEmail,
diff --git a/sidepanel/sidepanel.html b/sidepanel/sidepanel.html
index 835ae4b..0f3128d 100644
--- a/sidepanel/sidepanel.html
+++ b/sidepanel/sidepanel.html
@@ -229,12 +229,19 @@
邮箱生成
+
+ 邮箱池
+
+
Temp API
diff --git a/sidepanel/sidepanel.js b/sidepanel/sidepanel.js
index 37a6d3e..38ecb8f 100644
--- a/sidepanel/sidepanel.js
+++ b/sidepanel/sidepanel.js
@@ -102,6 +102,8 @@ const rowMail2925PoolSettings = document.getElementById('row-mail2925-pool-setti
const mail2925ModeButtons = Array.from(document.querySelectorAll('[data-mail2925-mode]'));
const rowEmailGenerator = document.getElementById('row-email-generator');
const selectEmailGenerator = document.getElementById('select-email-generator');
+const rowCustomEmailPool = document.getElementById('row-custom-email-pool');
+const inputCustomEmailPool = document.getElementById('input-custom-email-pool');
const rowTempEmailBaseUrl = document.getElementById('row-temp-email-base-url');
const inputTempEmailBaseUrl = document.getElementById('input-temp-email-base-url');
const rowTempEmailAdminAuth = document.getElementById('row-temp-email-admin-auth');
@@ -254,7 +256,9 @@ const HOTMAIL_SERVICE_MODE_REMOTE = 'remote';
const HOTMAIL_SERVICE_MODE_LOCAL = 'local';
const ICLOUD_PROVIDER = 'icloud';
const GMAIL_PROVIDER = 'gmail';
+const GMAIL_ALIAS_GENERATOR = 'gmail-alias';
const LUCKMAIL_PROVIDER = 'luckmail-api';
+const CUSTOM_EMAIL_POOL_GENERATOR = 'custom-pool';
const DEFAULT_LUCKMAIL_BASE_URL = 'https://mails.luckyous.com';
const DEFAULT_LUCKMAIL_EMAIL_TYPE = 'ms_graph';
const DISPLAY_TIMEZONE = 'Asia/Shanghai';
@@ -463,14 +467,14 @@ function getCurrentRegistrationEmailUiCopy() {
if (isCustomMailProvider()) {
return getCustomMailProviderUiCopy();
}
- if (isManagedAliasProvider()) {
+ if (usesGeneratedAliasMailProvider()) {
return getManagedAliasProviderUiCopy();
}
return getEmailGeneratorUiCopy();
}
function isCurrentRegistrationEmailCompatible(email = inputEmail.value.trim(), provider = selectMailProvider.value, state = latestState) {
- if (!isManagedAliasProvider(provider) || !email) {
+ if (!usesGeneratedAliasMailProvider(provider, getSelectedMail2925Mode()) || !email) {
return true;
}
const baseEmail = getManagedAliasBaseEmailForProvider(provider, state);
@@ -606,8 +610,19 @@ const LOG_LEVEL_LABELS = {
error: '错误',
};
-function usesGeneratedAliasMailProvider(provider, mail2925Mode = getSelectedMail2925Mode()) {
- return isManagedAliasProvider(provider, mail2925Mode);
+function usesGeneratedAliasMailProvider(
+ provider,
+ mail2925Mode = getSelectedMail2925Mode(),
+ generator = undefined
+) {
+ const customEmailPoolGenerator = typeof CUSTOM_EMAIL_POOL_GENERATOR === 'string'
+ ? CUSTOM_EMAIL_POOL_GENERATOR
+ : 'custom-pool';
+ const resolvedGenerator = generator !== undefined
+ ? generator
+ : (typeof getSelectedEmailGenerator === 'function' ? getSelectedEmailGenerator() : '');
+ return resolvedGenerator !== customEmailPoolGenerator
+ && isManagedAliasProvider(provider, mail2925Mode);
}
function parseGmailBaseEmail(rawValue = '') {
@@ -1173,7 +1188,37 @@ function formatAutoStepDelayInputValue(value) {
return normalized === null ? '' : String(normalized);
}
+function normalizeCustomEmailPoolEntries(value = '') {
+ const source = Array.isArray(value)
+ ? value
+ : String(value || '').split(/[\r\n,,;;]+/);
+
+ return source
+ .map((item) => String(item || '').trim().toLowerCase())
+ .filter((item) => /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(item));
+}
+
+function usesCustomEmailPoolGenerator(provider = selectMailProvider.value) {
+ return !isCustomMailProvider(provider)
+ && !isLuckmailProvider(provider)
+ && getSelectedEmailGenerator() === CUSTOM_EMAIL_POOL_GENERATOR;
+}
+
+function getCustomEmailPoolSize() {
+ return normalizeCustomEmailPoolEntries(inputCustomEmailPool?.value).length;
+}
+
+function syncRunCountFromCustomEmailPool() {
+ if (!usesCustomEmailPoolGenerator()) {
+ return;
+ }
+ inputRunCount.value = String(getCustomEmailPoolSize());
+}
+
function getRunCountValue() {
+ if (typeof usesCustomEmailPoolGenerator === 'function' && usesCustomEmailPoolGenerator()) {
+ return getCustomEmailPoolSize();
+ }
return Math.max(1, parseInt(inputRunCount.value, 10) || 1);
}
@@ -1486,6 +1531,9 @@ function collectSettingsPayload() {
mail2925Mode: getSelectedMail2925Mode(),
mail2925UseAccountPool,
emailGenerator: selectEmailGenerator.value,
+ customEmailPool: typeof normalizeCustomEmailPoolEntries === 'function'
+ ? normalizeCustomEmailPoolEntries(inputCustomEmailPool?.value)
+ : [],
autoDeleteUsedIcloudAlias: checkboxAutoDeleteIcloud?.checked,
icloudHostPreference: selectIcloudHostPreference?.value || 'auto',
...(contributionModeEnabled ? {} : {
@@ -1738,10 +1786,11 @@ function applyAutoRunStatus(payload = currentAutoRun) {
setSettingsCardLocked(settingsCardLocked);
- inputRunCount.disabled = currentAutoRun.autoRunning;
+ inputRunCount.disabled = currentAutoRun.autoRunning || usesCustomEmailPoolGenerator();
btnAutoRun.disabled = currentAutoRun.autoRunning;
btnFetchEmail.disabled = locked
- || isCustomMailProvider();
+ || isCustomMailProvider()
+ || usesCustomEmailPoolGenerator();
inputEmail.disabled = locked;
inputAutoSkipFailures.disabled = scheduled;
@@ -1779,7 +1828,7 @@ function applyAutoRunStatus(payload = currentAutoRun) {
setDefaultAutoRunButton();
inputEmail.disabled = false;
if (!locked) {
- btnFetchEmail.disabled = isCustomMailProvider();
+ btnFetchEmail.disabled = isCustomMailProvider() || usesCustomEmailPoolGenerator();
}
break;
}
@@ -1869,7 +1918,13 @@ function applySettingsState(state) {
setMail2925Mode(state?.mail2925Mode);
{
const restoredEmailGenerator = String(state?.emailGenerator || '').trim().toLowerCase();
- if (restoredEmailGenerator === 'icloud') {
+ if (restoredMailProvider === GMAIL_PROVIDER) {
+ selectEmailGenerator.value = restoredEmailGenerator === CUSTOM_EMAIL_POOL_GENERATOR
+ ? CUSTOM_EMAIL_POOL_GENERATOR
+ : GMAIL_ALIAS_GENERATOR;
+ } else if (restoredEmailGenerator === CUSTOM_EMAIL_POOL_GENERATOR) {
+ selectEmailGenerator.value = CUSTOM_EMAIL_POOL_GENERATOR;
+ } else if (restoredEmailGenerator === 'icloud') {
selectEmailGenerator.value = 'icloud';
} else if (restoredEmailGenerator === 'cloudflare') {
selectEmailGenerator.value = 'cloudflare';
@@ -1905,6 +1960,9 @@ function applySettingsState(state) {
setManagedAliasBaseEmailInputForProvider(restoredMailProvider, state);
inputInbucketHost.value = state?.inbucketHost || '';
inputInbucketMailbox.value = state?.inbucketMailbox || '';
+ if (inputCustomEmailPool) {
+ inputCustomEmailPool.value = normalizeCustomEmailPoolEntries(state?.customEmailPool).join('\n');
+ }
setHotmailServiceMode(state?.hotmailServiceMode);
inputHotmailRemoteBaseUrl.value = state?.hotmailRemoteBaseUrl || '';
inputHotmailLocalBaseUrl.value = state?.hotmailLocalBaseUrl || '';
@@ -2390,6 +2448,12 @@ function getSelectedEmailGenerator() {
if (generator === 'custom' || generator === 'manual') {
return 'custom';
}
+ if (generator === GMAIL_ALIAS_GENERATOR) {
+ return GMAIL_ALIAS_GENERATOR;
+ }
+ if (generator === CUSTOM_EMAIL_POOL_GENERATOR) {
+ return CUSTOM_EMAIL_POOL_GENERATOR;
+ }
if (generator === 'icloud') {
return 'icloud';
}
@@ -2402,6 +2466,22 @@ function getEmailGeneratorUiCopy() {
if (getSelectedEmailGenerator() === 'custom') {
return getCustomMailProviderUiCopy();
}
+ if (getSelectedEmailGenerator() === GMAIL_ALIAS_GENERATOR) {
+ return {
+ buttonLabel: '生成',
+ placeholder: '步骤 3 自动生成 Gmail +tag 邮箱并回填',
+ successVerb: '生成',
+ label: 'Gmail +tag 邮箱',
+ };
+ }
+ if (getSelectedEmailGenerator() === CUSTOM_EMAIL_POOL_GENERATOR) {
+ return {
+ buttonLabel: '取下一个',
+ placeholder: '按邮箱池顺序自动回填,也可以手动粘贴当前轮邮箱',
+ successVerb: '取用',
+ label: '自定义邮箱池',
+ };
+ }
if (getSelectedEmailGenerator() === 'icloud') {
return {
buttonLabel: '获取',
@@ -2642,13 +2722,29 @@ function updateMailProviderUI() {
const useMail2925 = selectMailProvider.value === '2925';
const useMail2925AccountPool = useMail2925 && Boolean(inputMail2925UseAccountPool?.checked);
const mail2925Mode = getSelectedMail2925Mode();
- const useGeneratedAlias = usesGeneratedAliasMailProvider(selectMailProvider.value, mail2925Mode);
+ const gmailOnlyGenerators = new Set([GMAIL_ALIAS_GENERATOR, CUSTOM_EMAIL_POOL_GENERATOR]);
+ Array.from(selectEmailGenerator?.options || []).forEach((option) => {
+ if (!option) return;
+ if (useGmail) {
+ option.hidden = !gmailOnlyGenerators.has(String(option.value || '').trim().toLowerCase());
+ return;
+ }
+ option.hidden = String(option.value || '').trim().toLowerCase() === GMAIL_ALIAS_GENERATOR;
+ });
+ if (useGmail && !gmailOnlyGenerators.has(String(selectEmailGenerator.value || '').trim().toLowerCase())) {
+ selectEmailGenerator.value = GMAIL_ALIAS_GENERATOR;
+ }
+ if (!useGmail && String(selectEmailGenerator.value || '').trim().toLowerCase() === GMAIL_ALIAS_GENERATOR) {
+ selectEmailGenerator.value = 'duck';
+ }
+ const selectedGenerator = getSelectedEmailGenerator();
+ const useGeneratedAlias = usesGeneratedAliasMailProvider(selectMailProvider.value, mail2925Mode, selectedGenerator);
const useInbucket = selectMailProvider.value === 'inbucket';
const useHotmail = selectMailProvider.value === 'hotmail-api';
const useLuckmail = isLuckmailProvider();
const useCustomEmail = isCustomMailProvider();
const useIcloudProvider = isIcloudMailProvider();
- const useEmailGenerator = !useHotmail && !useLuckmail && !useGeneratedAlias && !useCustomEmail;
+ const useEmailGenerator = !useHotmail && !useLuckmail && !useCustomEmail && (!useGeneratedAlias || useGmail);
const useCloudflareTempEmailProvider = selectMailProvider.value === 'cloudflare-temp-email';
const aliasUiCopy = useGeneratedAlias
? getManagedAliasProviderUiCopy(selectMailProvider.value, mail2925Mode)
@@ -2665,7 +2761,7 @@ function updateMailProviderUI() {
const hotmailServiceMode = getSelectedHotmailServiceMode();
rowInbucketHost.style.display = useInbucket ? '' : 'none';
rowInbucketMailbox.style.display = useInbucket ? '' : 'none';
- const selectedGenerator = getSelectedEmailGenerator();
+ const useCustomEmailPool = useEmailGenerator && selectedGenerator === CUSTOM_EMAIL_POOL_GENERATOR;
const useCloudflare = selectedGenerator === 'cloudflare';
const useIcloud = selectedGenerator === 'icloud';
const useCloudflareTempEmailGenerator = selectedGenerator === 'cloudflare-temp-email';
@@ -2676,6 +2772,9 @@ function updateMailProviderUI() {
if (rowEmailGenerator) {
rowEmailGenerator.style.display = useEmailGenerator ? '' : 'none';
}
+ if (rowCustomEmailPool) {
+ rowCustomEmailPool.style.display = useCustomEmailPool ? '' : 'none';
+ }
if (icloudSection) {
const showIcloudSection = (useEmailGenerator && useIcloud) || useIcloudProvider;
icloudSection.style.display = showIcloudSection ? '' : 'none';
@@ -2726,7 +2825,7 @@ function updateMailProviderUI() {
}
inputEmailPrefix.style.display = '';
inputEmailPrefix.readOnly = false;
- selectEmailGenerator.disabled = useHotmail || useLuckmail || useGeneratedAlias || useCustomEmail;
+ selectEmailGenerator.disabled = useHotmail || useLuckmail || useCustomEmail || (useGeneratedAlias && !useGmail);
if (useGmail) {
labelEmailPrefix.textContent = 'Gmail 原邮箱';
inputEmailPrefix.placeholder = '例如 yourname@gmail.com';
@@ -2742,7 +2841,7 @@ function updateMailProviderUI() {
if (rowHotmailLocalBaseUrl) {
rowHotmailLocalBaseUrl.style.display = useHotmail && hotmailServiceMode === HOTMAIL_SERVICE_MODE_LOCAL ? '' : 'none';
}
- btnFetchEmail.hidden = useHotmail || useLuckmail || useCustomEmail;
+ btnFetchEmail.hidden = useHotmail || useLuckmail || useCustomEmail || useCustomEmailPool;
inputEmail.readOnly = useHotmail || useLuckmail;
inputEmail.placeholder = useHotmail
? '由 Hotmail 账号池自动分配'
@@ -2755,7 +2854,7 @@ function updateMailProviderUI() {
if (!useHotmail && !useLuckmail) {
inputEmail.placeholder = uiCopy.placeholder;
}
- btnFetchEmail.disabled = useLuckmail || useCustomEmail || isAutoRunLockedPhase();
+ btnFetchEmail.disabled = useLuckmail || useCustomEmail || useCustomEmailPool || isAutoRunLockedPhase();
if (!btnFetchEmail.disabled) {
btnFetchEmail.textContent = uiCopy.buttonLabel;
}
@@ -2768,20 +2867,25 @@ function updateMailProviderUI() {
? '步骤 3 会自动生成邮箱,无需手动获取'
: (useCustomEmail ? '请先填写自定义注册邮箱,成功一轮后会自动清空' : `先自动获取${uiCopy.label},或手动粘贴邮箱后再继续`)));
}
+ if (autoHintText && useCustomEmailPool) {
+ autoHintText.textContent = getCustomEmailPoolSize() > 0
+ ? `当前邮箱池共 ${getCustomEmailPoolSize()} 个邮箱,自动轮数会跟随数量;实际收码仍走当前邮箱服务`
+ : '请先在邮箱池里每行填写一个邮箱,自动轮数会跟随数量';
+ }
if (autoHintText && useGmail && useGeneratedAlias) {
autoHintText.textContent = '请先填写 Gmail 原邮箱,步骤 3 会自动生成 Gmail +tag 地址';
}
if (autoHintText && useGeneratedAlias && aliasUiCopy?.hint) {
autoHintText.textContent = aliasUiCopy.hint;
}
- if (autoHintText && useMail2925AccountPool) {
+ if (autoHintText && useMail2925AccountPool && !useCustomEmailPool) {
autoHintText.textContent = getMail2925Accounts().length
? (useGeneratedAlias
? '当前已启用 2925 号池模式,步骤 3 会基于下拉框选中的号池邮箱生成别名地址'
: '当前已启用 2925 号池模式,步骤 4 / 8 遇到登录页时会优先使用下拉框选中的账号自动登录')
: '当前已启用 2925 号池模式,请先在下方 2925 账号池中添加账号并选择邮箱';
}
- if (autoHintText && showCloudflareTempEmailReceiveMailbox) {
+ if (autoHintText && showCloudflareTempEmailReceiveMailbox && !useCustomEmailPool) {
autoHintText.textContent = '若注册邮箱会转发到 Cloudflare Temp Email,请在“邮件接收”中填写实际接收转发邮件的邮箱。';
}
if (useHotmail) {
@@ -2789,6 +2893,10 @@ function updateMailProviderUI() {
} else if (useLuckmail) {
inputEmail.value = getCurrentLuckmailEmail();
}
+ if (useCustomEmailPool) {
+ syncRunCountFromCustomEmailPool();
+ }
+ inputRunCount.disabled = currentAutoRun.autoRunning || useCustomEmailPool;
renderHotmailAccounts();
if (useMail2925) {
renderMail2925Accounts();
@@ -3826,7 +3934,22 @@ async function startAutoRunFromCurrentSettings() {
console.warn('Failed to refresh contribution content hint before auto run:', error);
}
- const totalRuns = getRunCountValue();
+ if (typeof settingsDirty !== 'undefined' && settingsDirty && typeof saveSettings === 'function') {
+ await saveSettings({ silent: true });
+ }
+
+ const customEmailPoolEnabled = typeof usesCustomEmailPoolGenerator === 'function'
+ && usesCustomEmailPoolGenerator();
+ const customEmailPoolSize = customEmailPoolEnabled && typeof getCustomEmailPoolSize === 'function'
+ ? getCustomEmailPoolSize()
+ : 0;
+ if (customEmailPoolEnabled && customEmailPoolSize <= 0) {
+ throw new Error('请先在邮箱池里至少填写 1 个邮箱。');
+ }
+ const totalRuns = customEmailPoolEnabled ? customEmailPoolSize : getRunCountValue();
+ if (customEmailPoolEnabled) {
+ inputRunCount.value = String(customEmailPoolSize);
+ }
let mode = 'restart';
const autoRunSkipFailures = inputAutoSkipFailures.checked;
const contributionNickname = String(inputContributionNickname?.value || '').trim();
@@ -4282,6 +4405,19 @@ inputEmailPrefix.addEventListener('blur', () => {
saveSettings({ silent: true }).catch(() => {});
});
+inputCustomEmailPool?.addEventListener('input', () => {
+ syncRunCountFromCustomEmailPool();
+ updateMailProviderUI();
+ markSettingsDirty(true);
+ scheduleSettingsAutoSave();
+});
+inputCustomEmailPool?.addEventListener('blur', () => {
+ inputCustomEmailPool.value = normalizeCustomEmailPoolEntries(inputCustomEmailPool.value).join('\n');
+ syncRunCountFromCustomEmailPool();
+ updateMailProviderUI();
+ saveSettings({ silent: true }).catch(() => {});
+});
+
selectMail2925PoolAccount?.addEventListener('change', async () => {
try {
await syncSelectedMail2925PoolAccount();
@@ -4332,6 +4468,11 @@ inputRunCount.addEventListener('input', () => {
updateFallbackThreadIntervalInputState();
});
inputRunCount.addEventListener('blur', () => {
+ if (usesCustomEmailPoolGenerator()) {
+ syncRunCountFromCustomEmailPool();
+ updateFallbackThreadIntervalInputState();
+ return;
+ }
inputRunCount.value = String(getRunCountValue());
updateFallbackThreadIntervalInputState();
});
diff --git a/tests/background-custom-email-pool.test.js b/tests/background-custom-email-pool.test.js
new file mode 100644
index 0000000..2db42f9
--- /dev/null
+++ b/tests/background-custom-email-pool.test.js
@@ -0,0 +1,104 @@
+const test = require('node:test');
+const assert = require('node:assert/strict');
+const fs = require('node:fs');
+
+const source = fs.readFileSync('background.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);
+}
+
+const bundle = [
+ extractFunction('normalizeEmailGenerator'),
+ extractFunction('normalizeCustomEmailPool'),
+ extractFunction('getCustomEmailPool'),
+ extractFunction('getCustomEmailPoolEmailForRun'),
+ extractFunction('getEmailGeneratorLabel'),
+].join('\n');
+
+function createApi() {
+ return new Function(`
+const CUSTOM_EMAIL_POOL_GENERATOR = 'custom-pool';
+const CLOUDFLARE_TEMP_EMAIL_GENERATOR = 'cloudflare-temp-email';
+
+${bundle}
+
+return {
+ normalizeEmailGenerator,
+ normalizeCustomEmailPool,
+ getCustomEmailPool,
+ getCustomEmailPoolEmailForRun,
+ getEmailGeneratorLabel,
+};
+`)();
+}
+
+test('background recognizes custom email pool generator and label', () => {
+ const api = createApi();
+
+ assert.equal(api.normalizeEmailGenerator('custom-pool'), 'custom-pool');
+ assert.equal(api.getEmailGeneratorLabel('custom-pool'), '自定义邮箱池');
+});
+
+test('background normalizes custom email pool input and keeps order', () => {
+ const api = createApi();
+
+ assert.deepEqual(
+ api.normalizeCustomEmailPool(' Foo@Example.com \ninvalid\nbar@example.com;baz@example.com '),
+ ['foo@example.com', 'bar@example.com', 'baz@example.com']
+ );
+});
+
+test('background selects the matching email for the current auto-run round', () => {
+ const api = createApi();
+ const state = {
+ customEmailPool: ['first@example.com', 'second@example.com', 'third@example.com'],
+ };
+
+ assert.equal(api.getCustomEmailPoolEmailForRun(state, 1), 'first@example.com');
+ assert.equal(api.getCustomEmailPoolEmailForRun(state, 2), 'second@example.com');
+ assert.equal(api.getCustomEmailPoolEmailForRun(state, 4), '');
+});
diff --git a/tests/background-generated-email-module.test.js b/tests/background-generated-email-module.test.js
index fb5d764..d0fe20a 100644
--- a/tests/background-generated-email-module.test.js
+++ b/tests/background-generated-email-module.test.js
@@ -76,3 +76,63 @@ test('generated email helper falls back to normal generator when 2925 is in rece
['email', 'duck@example.com'],
]);
});
+
+test('generated email helper can read the requested address from custom email pool', 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');
+ },
+ buildCloudflareTempEmailHeaders: () => ({}),
+ CLOUDFLARE_TEMP_EMAIL_GENERATOR: 'cloudflare-temp-email',
+ CUSTOM_EMAIL_POOL_GENERATOR: 'custom-pool',
+ 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: '' }),
+ getCustomEmailPoolEmail: (state, targetRun) => state.customEmailPool?.[targetRun - 1] || '',
+ getState: async () => ({
+ customEmailPool: ['first@example.com', 'second@example.com'],
+ emailGenerator: 'custom-pool',
+ mailProvider: 'gmail',
+ }),
+ ensureMail2925AccountForFlow: async () => {
+ throw new Error('should not allocate 2925 account');
+ },
+ joinCloudflareTempEmailUrl: () => '',
+ normalizeCloudflareDomain: () => '',
+ normalizeCloudflareTempEmailAddress: () => '',
+ normalizeEmailGenerator: (value) => String(value || '').trim().toLowerCase(),
+ isGeneratedAliasProvider: () => false,
+ reuseOrCreateTab: async () => {},
+ sendToContentScript: async () => {
+ throw new Error('should not open duck tab');
+ },
+ setEmailState: async (email) => {
+ events.push(['email', email]);
+ },
+ throwIfStopped: () => {},
+ });
+
+ const email = await helpers.fetchGeneratedEmail({
+ customEmailPool: ['first@example.com', 'second@example.com'],
+ emailGenerator: 'custom-pool',
+ mailProvider: 'gmail',
+ }, {
+ generator: 'custom-pool',
+ poolIndex: 1,
+ });
+
+ assert.equal(email, 'second@example.com');
+ assert.deepStrictEqual(events, [
+ ['email', 'second@example.com'],
+ ]);
+});
diff --git a/tests/sidepanel-custom-email-pool.test.js b/tests/sidepanel-custom-email-pool.test.js
new file mode 100644
index 0000000..3449e5d
--- /dev/null
+++ b/tests/sidepanel-custom-email-pool.test.js
@@ -0,0 +1,115 @@
+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 exposes custom email pool generator option and input row', () => {
+ const html = fs.readFileSync('sidepanel/sidepanel.html', 'utf8');
+
+ assert.match(html, /option value="custom-pool">自定义邮箱池<\/option>/);
+ assert.match(html, /id="row-custom-email-pool"/);
+ assert.match(html, /id="input-custom-email-pool"/);
+});
+
+test('sidepanel locks run count to custom email pool size', () => {
+ const bundle = [
+ extractFunction('isCustomMailProvider'),
+ extractFunction('normalizeCustomEmailPoolEntries'),
+ extractFunction('getSelectedEmailGenerator'),
+ extractFunction('usesGeneratedAliasMailProvider'),
+ extractFunction('usesCustomEmailPoolGenerator'),
+ extractFunction('getCustomEmailPoolSize'),
+ extractFunction('getRunCountValue'),
+ ].join('\n');
+
+ const api = new Function(`
+const GMAIL_PROVIDER = 'gmail';
+const GMAIL_ALIAS_GENERATOR = 'gmail-alias';
+const CUSTOM_EMAIL_POOL_GENERATOR = 'custom-pool';
+const selectMailProvider = { value: 'gmail' };
+const selectEmailGenerator = { value: 'custom-pool' };
+const inputCustomEmailPool = { value: 'first@example.com\\nsecond@example.com' };
+const inputRunCount = { value: '99' };
+
+function isLuckmailProvider() {
+ return false;
+}
+
+function isManagedAliasProvider() {
+ return false;
+}
+
+function getSelectedMail2925Mode() {
+ return 'provide';
+}
+
+function isManagedAliasProvider(provider) {
+ return String(provider || '').trim().toLowerCase() === GMAIL_PROVIDER;
+}
+
+${bundle}
+
+return {
+ getSelectedEmailGenerator,
+ usesGeneratedAliasMailProvider,
+ usesCustomEmailPoolGenerator,
+ getCustomEmailPoolSize,
+ getRunCountValue,
+};
+`)();
+
+ assert.equal(api.getSelectedEmailGenerator(), 'custom-pool');
+ assert.equal(api.usesGeneratedAliasMailProvider('gmail', 'provide', 'gmail-alias'), true);
+ assert.equal(api.usesGeneratedAliasMailProvider('gmail', 'provide', 'custom-pool'), false);
+ assert.equal(api.usesCustomEmailPoolGenerator(), true);
+ assert.equal(api.getCustomEmailPoolSize(), 2);
+ assert.equal(api.getRunCountValue(), 2);
+});