diff --git a/background.js b/background.js
index 86a1012..7f0b1a3 100644
--- a/background.js
+++ b/background.js
@@ -532,6 +532,7 @@ const PERSISTED_SETTING_DEFAULTS = {
emailGenerator: 'duck',
customMailProviderPool: [],
customEmailPool: [],
+ customEmailPoolEntries: [],
autoDeleteUsedIcloudAlias: false,
icloudHostPreference: 'auto',
icloudTargetMailboxType: 'icloud-inbox',
@@ -1432,6 +1433,36 @@ function normalizeCustomEmailPool(value = []) {
.filter((item) => /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(item));
}
+function normalizeCustomEmailPoolEntryObjects(value = []) {
+ const source = Array.isArray(value) ? value : [];
+ const seenEmails = new Set();
+ const entries = [];
+
+ for (const rawEntry of source) {
+ const asObject = rawEntry && typeof rawEntry === 'object'
+ ? rawEntry
+ : { email: rawEntry };
+ const email = String(asObject.email || '').trim().toLowerCase();
+ if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email)) {
+ continue;
+ }
+ if (seenEmails.has(email)) {
+ continue;
+ }
+ seenEmails.add(email);
+ entries.push({
+ id: String(asObject.id || `custom-pool-${Date.now()}-${Math.random().toString(36).slice(2, 10)}`),
+ email,
+ enabled: asObject.enabled !== undefined ? Boolean(asObject.enabled) : true,
+ used: Boolean(asObject.used),
+ note: String(asObject.note || '').trim(),
+ lastUsedAt: Number.isFinite(Number(asObject.lastUsedAt)) ? Number(asObject.lastUsedAt) : 0,
+ });
+ }
+
+ return entries;
+}
+
function isCustomEmailPoolGenerator(stateOrValue = {}) {
const generator = typeof stateOrValue === 'string'
? stateOrValue
@@ -1443,9 +1474,91 @@ function isCustomEmailPoolGenerator(stateOrValue = {}) {
}
function getCustomEmailPool(state = {}) {
+ if (typeof normalizeCustomEmailPoolEntryObjects === 'function') {
+ const entries = normalizeCustomEmailPoolEntryObjects(state?.customEmailPoolEntries);
+ if (entries.length > 0) {
+ return entries
+ .filter((entry) => entry.enabled && !entry.used)
+ .map((entry) => entry.email);
+ }
+ }
return normalizeCustomEmailPool(state?.customEmailPool);
}
+function getCustomEmailPoolEntries(state = {}) {
+ const entries = normalizeCustomEmailPoolEntryObjects(state?.customEmailPoolEntries);
+ if (entries.length > 0) {
+ return entries;
+ }
+ return normalizeCustomEmailPool(state?.customEmailPool).map((email) => ({
+ id: `custom-pool-${Date.now()}-${Math.random().toString(36).slice(2, 10)}`,
+ email,
+ enabled: true,
+ used: false,
+ note: '',
+ lastUsedAt: 0,
+ }));
+}
+
+async function markCurrentCustomEmailPoolEntryUsed(state = {}) {
+ if (!isCustomEmailPoolGenerator(state)) {
+ return { updated: false };
+ }
+
+ const currentEmail = String(state?.email || '').trim().toLowerCase();
+ if (!currentEmail) {
+ return { updated: false };
+ }
+
+ const entries = getCustomEmailPoolEntries(state);
+ if (!entries.length) {
+ return { updated: false };
+ }
+
+ let changed = false;
+ const now = Date.now();
+ const nextEntries = entries.map((entry) => {
+ if (entry.email !== currentEmail) {
+ return entry;
+ }
+ if (entry.used && entry.lastUsedAt) {
+ return entry;
+ }
+ changed = true;
+ return {
+ ...entry,
+ used: true,
+ lastUsedAt: now,
+ };
+ });
+
+ if (!changed) {
+ return { updated: false };
+ }
+
+ const nextCustomEmailPool = nextEntries
+ .filter((entry) => entry.enabled && !entry.used)
+ .map((entry) => entry.email);
+ await setPersistentSettings({
+ customEmailPoolEntries: nextEntries,
+ customEmailPool: nextCustomEmailPool,
+ });
+ await setState({
+ customEmailPoolEntries: nextEntries,
+ customEmailPool: nextCustomEmailPool,
+ });
+ broadcastDataUpdate({
+ customEmailPoolEntries: nextEntries,
+ customEmailPool: nextCustomEmailPool,
+ });
+ await addLog(`自定义邮箱池:流程成功后已将 ${currentEmail} 标记为已用。`, 'ok');
+ return {
+ updated: true,
+ customEmailPoolEntries: nextEntries,
+ customEmailPool: nextCustomEmailPool,
+ };
+}
+
function getCustomEmailPoolEmailForRun(state = {}, targetRun = 1) {
const entries = getCustomEmailPool(state);
const numericRun = Math.max(1, Math.floor(Number(targetRun) || 1));
@@ -1814,6 +1927,8 @@ function normalizePersistentSettingValue(key, value) {
case 'customMailProviderPool':
case 'customEmailPool':
return normalizeCustomEmailPool(value);
+ case 'customEmailPoolEntries':
+ return normalizeCustomEmailPoolEntryObjects(value);
case 'autoDeleteUsedIcloudAlias':
case 'accountRunHistoryTextEnabled':
case 'cloudflareTempEmailUseRandomSubdomain':
@@ -7687,6 +7802,9 @@ async function handleStepData(step, payload) {
});
}
await finalizeIcloudAliasAfterSuccessfulFlow(latestState);
+ if (typeof markCurrentCustomEmailPoolEntryUsed === 'function') {
+ await markCurrentCustomEmailPoolEntryUsed(latestState);
+ }
const shouldClearCustomPoolEmail = String(latestState?.emailGenerator || '').trim().toLowerCase() === (
typeof CUSTOM_EMAIL_POOL_GENERATOR === 'string'
? CUSTOM_EMAIL_POOL_GENERATOR
@@ -9794,8 +9912,7 @@ const messageRouter = self.MultiPageBackgroundMessageRouter?.createMessageRouter
runIpProxyAutoSync,
listIcloudAliases,
listLuckmailPurchasesForManagement,
- refreshIpProxyPool,
- getCurrentPayPalAccount,
+ markCurrentCustomEmailPoolEntryUsed,
getCurrentMail2925Account,
normalizeHotmailAccounts,
normalizeMail2925Accounts,
diff --git a/background/message-router.js b/background/message-router.js
index ed32560..bc1462f 100644
--- a/background/message-router.js
+++ b/background/message-router.js
@@ -66,7 +66,7 @@
runIpProxyAutoSync,
listIcloudAliases,
listLuckmailPurchasesForManagement,
- refreshIpProxyPool,
+ markCurrentCustomEmailPoolEntryUsed,
normalizeHotmailAccounts,
normalizeMail2925Accounts,
normalizePayPalAccounts,
@@ -339,6 +339,62 @@
}
}
break;
+ case 8:
+ await setState({
+ lastEmailTimestamp: payload.emailTimestamp || null,
+ loginVerificationRequestedAt: null,
+ });
+ break;
+ case 9:
+ if (payload.localhostUrl) {
+ if (!isLocalhostOAuthCallbackUrl(payload.localhostUrl)) {
+ throw new Error('步骤 9 返回了无效的 localhost OAuth 回调地址。');
+ }
+ await setState({ localhostUrl: payload.localhostUrl });
+ broadcastDataUpdate({ localhostUrl: payload.localhostUrl });
+ }
+ break;
+ case 10: {
+ if (payload.localhostUrl) {
+ await closeLocalhostCallbackTabs(payload.localhostUrl);
+ }
+ const latestState = await getState();
+ if (latestState.currentHotmailAccountId && isHotmailProvider(latestState)) {
+ await patchHotmailAccount(latestState.currentHotmailAccountId, {
+ used: true,
+ lastUsedAt: Date.now(),
+ });
+ await addLog('当前 Hotmail 账号已自动标记为已用。', 'ok');
+ }
+ if (String(latestState.mailProvider || '').trim().toLowerCase() === '2925' && latestState.currentMail2925AccountId) {
+ await patchMail2925Account(latestState.currentMail2925AccountId, {
+ lastUsedAt: Date.now(),
+ lastError: '',
+ });
+ await addLog('当前 2925 账号已记录最近使用时间。', 'ok');
+ }
+ if (isLuckmailProvider(latestState)) {
+ const currentPurchase = getCurrentLuckmailPurchase(latestState);
+ if (currentPurchase?.id) {
+ await setLuckmailPurchaseUsedState(currentPurchase.id, true);
+ await addLog(`当前 LuckMail 邮箱 ${currentPurchase.email_address} 已在本地标记为已用。`, 'ok');
+ }
+ await clearLuckmailRuntimeState({ clearEmail: true });
+ await addLog('当前 LuckMail 邮箱运行态已清空,下轮将优先复用未用邮箱或重新购买邮箱。', 'ok');
+ }
+ const localhostPrefix = buildLocalhostCleanupPrefix(payload.localhostUrl);
+ if (localhostPrefix) {
+ await closeTabsByUrlPrefix(localhostPrefix, {
+ excludeUrls: [payload.localhostUrl],
+ excludeLocalhostCallbacks: true,
+ });
+ }
+ await finalizeIcloudAliasAfterSuccessfulFlow(latestState);
+ if (typeof markCurrentCustomEmailPoolEntryUsed === 'function') {
+ await markCurrentCustomEmailPoolEntryUsed(latestState);
+ }
+ break;
+ }
default:
break;
}
diff --git a/sidepanel/custom-email-pool-manager.js b/sidepanel/custom-email-pool-manager.js
new file mode 100644
index 0000000..2509bef
--- /dev/null
+++ b/sidepanel/custom-email-pool-manager.js
@@ -0,0 +1,536 @@
+(function attachSidepanelCustomEmailPoolManager(globalScope) {
+ function createCustomEmailPoolManager(context = {}) {
+ const {
+ dom,
+ helpers,
+ state,
+ actions,
+ constants = {},
+ } = context;
+
+ const copyIcon = constants.copyIcon || '';
+
+ let renderedEntries = [];
+ let selectedEntryIds = new Set();
+ let searchTerm = '';
+ let filterMode = 'all';
+ let refreshQueued = false;
+ let loading = false;
+
+ function normalizeEmail(value = '') {
+ return String(value || '').trim().toLowerCase();
+ }
+
+ function isValidEmail(value = '') {
+ return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(normalizeEmail(value));
+ }
+
+ function createEntryId() {
+ if (typeof crypto !== 'undefined' && typeof crypto.randomUUID === 'function') {
+ return crypto.randomUUID();
+ }
+ return `custom-pool-${Date.now()}-${Math.random().toString(36).slice(2, 10)}`;
+ }
+
+ function normalizeEntry(rawEntry = {}) {
+ const email = normalizeEmail(rawEntry?.email || '');
+ if (!isValidEmail(email)) {
+ return null;
+ }
+
+ return {
+ id: String(rawEntry?.id || createEntryId()),
+ email,
+ enabled: rawEntry?.enabled !== undefined ? Boolean(rawEntry.enabled) : true,
+ used: Boolean(rawEntry?.used),
+ note: String(rawEntry?.note || '').trim(),
+ lastUsedAt: Number.isFinite(Number(rawEntry?.lastUsedAt)) ? Number(rawEntry.lastUsedAt) : 0,
+ };
+ }
+
+ function normalizeEntries(entries = []) {
+ if (!Array.isArray(entries)) {
+ return [];
+ }
+
+ const seenEmails = new Set();
+ const normalized = [];
+ for (const entry of entries) {
+ const item = normalizeEntry(entry);
+ if (!item) continue;
+ if (seenEmails.has(item.email)) continue;
+ seenEmails.add(item.email);
+ normalized.push(item);
+ }
+ return normalized;
+ }
+
+ function withCurrentFlag(entries = renderedEntries) {
+ const currentEmail = normalizeEmail(state.getCurrentEmail?.());
+ return normalizeEntries(entries).map((entry) => ({
+ ...entry,
+ current: Boolean(currentEmail) && entry.email === currentEmail,
+ }));
+ }
+
+ function normalizeSearchText(value = '') {
+ return String(value || '').trim().toLowerCase();
+ }
+
+ function getFilteredEntries(entries = renderedEntries) {
+ const normalizedSearchTerm = normalizeSearchText(searchTerm);
+ return withCurrentFlag(entries).filter((entry) => {
+ const matchesFilter = (() => {
+ switch (filterMode) {
+ case 'enabled': return Boolean(entry.enabled);
+ case 'disabled': return !entry.enabled;
+ case 'used': return Boolean(entry.used);
+ case 'unused': return !entry.used;
+ case 'current': return Boolean(entry.current);
+ default: return true;
+ }
+ })();
+
+ if (!matchesFilter) return false;
+ if (!normalizedSearchTerm) return true;
+
+ const haystack = [
+ entry.email,
+ entry.note,
+ entry.enabled ? 'enabled 启用' : 'disabled 停用',
+ entry.used ? 'used 已用' : 'unused 未用',
+ entry.current ? 'current 当前' : '',
+ ].join(' ').toLowerCase();
+
+ return haystack.includes(normalizedSearchTerm);
+ });
+ }
+
+ function pruneSelection(entries = renderedEntries) {
+ const existingIds = new Set(withCurrentFlag(entries).map((entry) => String(entry.id)));
+ selectedEntryIds = new Set([...selectedEntryIds].filter((id) => existingIds.has(id)));
+ }
+
+ function updateBulkUi(visibleEntries = getFilteredEntries()) {
+ if (!dom.checkboxCustomEmailPoolSelectAll || !dom.customEmailPoolSelectionSummary) {
+ return;
+ }
+
+ const visibleIds = visibleEntries.map((entry) => String(entry.id));
+ const selectedVisibleCount = visibleIds.filter((id) => selectedEntryIds.has(id)).length;
+ const hasVisible = visibleIds.length > 0;
+ const hasSelection = selectedEntryIds.size > 0;
+
+ dom.checkboxCustomEmailPoolSelectAll.checked = hasVisible && selectedVisibleCount === visibleIds.length;
+ dom.checkboxCustomEmailPoolSelectAll.indeterminate = selectedVisibleCount > 0 && selectedVisibleCount < visibleIds.length;
+ dom.checkboxCustomEmailPoolSelectAll.disabled = loading || !hasVisible;
+ dom.customEmailPoolSelectionSummary.textContent = `已选 ${selectedEntryIds.size} 个(当前显示 ${visibleIds.length} 个)`;
+
+ if (dom.btnCustomEmailPoolBulkUsed) dom.btnCustomEmailPoolBulkUsed.disabled = loading || !hasSelection;
+ if (dom.btnCustomEmailPoolBulkUnused) dom.btnCustomEmailPoolBulkUnused.disabled = loading || !hasSelection;
+ if (dom.btnCustomEmailPoolBulkEnable) dom.btnCustomEmailPoolBulkEnable.disabled = loading || !hasSelection;
+ if (dom.btnCustomEmailPoolBulkDisable) dom.btnCustomEmailPoolBulkDisable.disabled = loading || !hasSelection;
+ if (dom.btnCustomEmailPoolBulkDelete) dom.btnCustomEmailPoolBulkDelete.disabled = loading || !hasSelection;
+ }
+
+ function setLoadingState(nextLoading, summary = '') {
+ loading = Boolean(nextLoading);
+ if (dom.btnCustomEmailPoolImport) dom.btnCustomEmailPoolImport.disabled = loading;
+ if (dom.btnCustomEmailPoolRefresh) dom.btnCustomEmailPoolRefresh.disabled = loading;
+ if (dom.btnCustomEmailPoolClearUsed) dom.btnCustomEmailPoolClearUsed.disabled = loading;
+ if (dom.btnCustomEmailPoolDeleteAll) dom.btnCustomEmailPoolDeleteAll.disabled = loading;
+ if (dom.inputCustomEmailPoolImport) dom.inputCustomEmailPoolImport.disabled = loading;
+ if (dom.inputCustomEmailPoolSearch) dom.inputCustomEmailPoolSearch.disabled = loading;
+ if (dom.selectCustomEmailPoolFilter) dom.selectCustomEmailPoolFilter.disabled = loading;
+
+ if (summary && dom.customEmailPoolSummary) {
+ dom.customEmailPoolSummary.textContent = summary;
+ }
+
+ updateBulkUi(getFilteredEntries());
+ }
+
+ function renderCustomEmailPoolEntries(entries = state.getEntries?.()) {
+ if (!dom.customEmailPoolList || !dom.customEmailPoolSummary) {
+ return;
+ }
+
+ renderedEntries = normalizeEntries(entries);
+ pruneSelection(renderedEntries);
+ dom.customEmailPoolList.innerHTML = '';
+
+ if (!renderedEntries.length) {
+ selectedEntryIds.clear();
+ dom.customEmailPoolList.innerHTML = '
还没有自定义邮箱,先导入一批邮箱再开始。
';
+ dom.customEmailPoolSummary.textContent = '导入你提前准备好的注册邮箱,每行一个邮箱地址。';
+ if (dom.btnCustomEmailPoolClearUsed) dom.btnCustomEmailPoolClearUsed.disabled = true;
+ if (dom.btnCustomEmailPoolDeleteAll) dom.btnCustomEmailPoolDeleteAll.disabled = true;
+ updateBulkUi([]);
+ return;
+ }
+
+ const entriesWithCurrent = withCurrentFlag(renderedEntries);
+ const usedCount = entriesWithCurrent.filter((entry) => entry.used).length;
+ const enabledCount = entriesWithCurrent.filter((entry) => entry.enabled).length;
+ dom.customEmailPoolSummary.textContent = `已加载 ${entriesWithCurrent.length} 个邮箱,其中 ${enabledCount} 个启用,${usedCount} 个已标记为已用。`;
+ if (dom.btnCustomEmailPoolClearUsed) dom.btnCustomEmailPoolClearUsed.disabled = loading || usedCount === 0;
+ if (dom.btnCustomEmailPoolDeleteAll) dom.btnCustomEmailPoolDeleteAll.disabled = loading || entriesWithCurrent.length === 0;
+
+ const visibleEntries = getFilteredEntries(entriesWithCurrent);
+ if (!visibleEntries.length) {
+ dom.customEmailPoolList.innerHTML = '没有匹配当前筛选条件的邮箱。
';
+ updateBulkUi([]);
+ return;
+ }
+
+ for (const entry of visibleEntries) {
+ const entryId = String(entry.id);
+ const item = document.createElement('div');
+ item.className = `luckmail-item${entry.current ? ' is-current' : ''}`;
+ item.innerHTML = `
+
+
+
+
${helpers.escapeHtml(entry.email || '(未知邮箱)')}
+
+
+
+ ${entry.current ? '当前' : ''}
+ ${entry.used ? '已用' : '未用'}
+ ${entry.enabled ? '启用' : '停用'}
+ ${entry.note ? `${helpers.escapeHtml(entry.note)}` : ''}
+
+
+
+
+
+
+
+
+ `;
+
+ item.querySelector('[data-action="select"]').addEventListener('change', (event) => {
+ if (event.target.checked) {
+ selectedEntryIds.add(entryId);
+ } else {
+ selectedEntryIds.delete(entryId);
+ }
+ updateBulkUi(visibleEntries);
+ });
+
+ item.querySelector('[data-action="copy-email"]').addEventListener('click', async () => {
+ await helpers.copyTextToClipboard(entry.email || '');
+ helpers.showToast('邮箱已复制', 'success', 1600);
+ });
+
+ item.querySelector('[data-action="use"]').addEventListener('click', async () => {
+ try {
+ setLoadingState(true, '正在切换当前邮箱...');
+ await actions.setRuntimeEmail?.(entry.email);
+ helpers.showToast(`已切换到 ${entry.email}`, 'success', 1800);
+ queueCustomEmailPoolRefresh();
+ } catch (error) {
+ helpers.showToast(`切换邮箱失败:${error.message}`, 'error');
+ } finally {
+ setLoadingState(false);
+ }
+ });
+
+ item.querySelector('[data-action="toggle-used"]').addEventListener('click', async () => {
+ await patchEntries((entriesList) => entriesList.map((candidate) => (
+ String(candidate.id) === entryId
+ ? {
+ ...candidate,
+ used: !entry.used,
+ lastUsedAt: !entry.used ? Date.now() : candidate.lastUsedAt,
+ }
+ : candidate
+ )));
+ });
+
+ item.querySelector('[data-action="toggle-enabled"]').addEventListener('click', async () => {
+ await patchEntries((entriesList) => entriesList.map((candidate) => (
+ String(candidate.id) === entryId
+ ? { ...candidate, enabled: !entry.enabled }
+ : candidate
+ )));
+ });
+
+ item.querySelector('[data-action="delete"]').addEventListener('click', async () => {
+ await deleteEntries({
+ ids: [entry.id],
+ }, `确认删除 ${entry.email} 吗?此操作不可撤销。`);
+ });
+
+ dom.customEmailPoolList.appendChild(item);
+ }
+
+ updateBulkUi(visibleEntries);
+ }
+
+ async function patchEntries(mutator) {
+ const previousEntries = normalizeEntries(state.getEntries?.() || []);
+ const nextEntries = normalizeEntries(mutator(previousEntries.map((entry) => ({ ...entry }))));
+
+ setLoadingState(true, '正在更新自定义邮箱池...');
+ state.setEntries?.(nextEntries);
+ renderCustomEmailPoolEntries(nextEntries);
+
+ try {
+ await actions.persistEntries?.();
+ } catch (error) {
+ state.setEntries?.(previousEntries);
+ renderCustomEmailPoolEntries(previousEntries);
+ helpers.showToast(`更新自定义邮箱池失败:${error.message}`, 'error');
+ } finally {
+ setLoadingState(false);
+ }
+ }
+
+ async function deleteEntries(payload = {}, confirmMessage = '') {
+ const confirmed = await helpers.openConfirmModal({
+ title: '删除邮箱',
+ message: confirmMessage || '确认删除选中的邮箱吗?此操作不可撤销。',
+ confirmLabel: '确认删除',
+ confirmVariant: 'btn-danger',
+ });
+ if (!confirmed) {
+ return;
+ }
+
+ const ids = Array.isArray(payload.ids)
+ ? payload.ids.map((id) => String(id || '').trim()).filter(Boolean)
+ : [];
+ const mode = String(payload.mode || '').trim().toLowerCase();
+
+ await patchEntries((entriesList) => {
+ if (mode === 'all') {
+ selectedEntryIds.clear();
+ return [];
+ }
+ if (mode === 'used') {
+ const usedIds = new Set(entriesList.filter((entry) => entry.used).map((entry) => String(entry.id)));
+ usedIds.forEach((id) => selectedEntryIds.delete(id));
+ return entriesList.filter((entry) => !entry.used);
+ }
+
+ const targetIds = new Set(ids);
+ ids.forEach((id) => selectedEntryIds.delete(id));
+ return entriesList.filter((entry) => !targetIds.has(String(entry.id)));
+ });
+ }
+
+ async function importEntriesFromTextarea() {
+ const text = String(dom.inputCustomEmailPoolImport?.value || '');
+ if (!text.trim()) {
+ helpers.showToast('请先粘贴邮箱列表,每行一个邮箱。', 'warn');
+ return;
+ }
+
+ const previousEntries = normalizeEntries(state.getEntries?.() || []);
+ const knownEmails = new Set(previousEntries.map((entry) => entry.email));
+ const importedEntries = [];
+ let skippedCount = 0;
+
+ for (const line of String(text || '').split(/[\r\n,,;;]+/)) {
+ const email = normalizeEmail(line);
+ if (!email) {
+ continue;
+ }
+ if (!isValidEmail(email) || knownEmails.has(email)) {
+ skippedCount += 1;
+ continue;
+ }
+
+ knownEmails.add(email);
+ importedEntries.push({
+ id: createEntryId(),
+ email,
+ enabled: true,
+ used: false,
+ note: '',
+ lastUsedAt: 0,
+ });
+ }
+
+ if (!importedEntries.length && skippedCount > 0) {
+ helpers.showToast('没有可导入的新邮箱(可能都重复或无效)。', 'warn');
+ return;
+ }
+
+ const nextEntries = normalizeEntries([...previousEntries, ...importedEntries]);
+ setLoadingState(true, '正在导入邮箱...');
+ state.setEntries?.(nextEntries);
+ renderCustomEmailPoolEntries(nextEntries);
+
+ try {
+ await actions.persistEntries?.();
+ if (dom.inputCustomEmailPoolImport) {
+ dom.inputCustomEmailPoolImport.value = '';
+ }
+ helpers.showToast(
+ skippedCount > 0
+ ? `已导入 ${importedEntries.length} 个邮箱,跳过 ${skippedCount} 条无效或重复数据。`
+ : `已导入 ${importedEntries.length} 个邮箱。`,
+ importedEntries.length > 0 ? 'success' : 'warn',
+ 2400
+ );
+ } catch (error) {
+ state.setEntries?.(previousEntries);
+ renderCustomEmailPoolEntries(previousEntries);
+ helpers.showToast(`导入邮箱失败:${error.message}`, 'error');
+ } finally {
+ setLoadingState(false);
+ }
+ }
+
+ async function refreshCustomEmailPoolEntries(options = {}) {
+ const { silent = false } = options;
+ if (state.isVisible && !state.isVisible()) {
+ return;
+ }
+
+ if (!silent) {
+ setLoadingState(true, '正在刷新自定义邮箱池...');
+ }
+ renderCustomEmailPoolEntries(state.getEntries?.());
+ if (!silent) {
+ setLoadingState(false);
+ }
+ }
+
+ function queueCustomEmailPoolRefresh() {
+ if (refreshQueued) return;
+ refreshQueued = true;
+ setTimeout(() => {
+ refreshQueued = false;
+ refreshCustomEmailPoolEntries({ silent: true });
+ }, 120);
+ }
+
+ function reset() {
+ selectedEntryIds.clear();
+ searchTerm = '';
+ filterMode = 'all';
+ if (dom.inputCustomEmailPoolSearch) dom.inputCustomEmailPoolSearch.value = '';
+ if (dom.selectCustomEmailPoolFilter) dom.selectCustomEmailPoolFilter.value = 'all';
+ if (dom.customEmailPoolList) {
+ dom.customEmailPoolList.innerHTML = '';
+ }
+ if (dom.customEmailPoolSummary) {
+ dom.customEmailPoolSummary.textContent = '导入你提前准备好的注册邮箱,每行一个邮箱地址。';
+ }
+ updateBulkUi([]);
+ }
+
+ function bindEvents() {
+ dom.btnCustomEmailPoolRefresh?.addEventListener('click', async () => {
+ await refreshCustomEmailPoolEntries();
+ });
+
+ dom.btnCustomEmailPoolImport?.addEventListener('click', async () => {
+ await importEntriesFromTextarea();
+ });
+
+ dom.inputCustomEmailPoolImport?.addEventListener('keydown', async (event) => {
+ if ((event.metaKey || event.ctrlKey) && event.key === 'Enter') {
+ event.preventDefault();
+ await importEntriesFromTextarea();
+ }
+ });
+
+ dom.inputCustomEmailPoolSearch?.addEventListener('input', (event) => {
+ searchTerm = normalizeSearchText(event.target.value);
+ renderCustomEmailPoolEntries(renderedEntries);
+ });
+
+ dom.selectCustomEmailPoolFilter?.addEventListener('change', (event) => {
+ filterMode = String(event.target.value || 'all');
+ renderCustomEmailPoolEntries(renderedEntries);
+ });
+
+ dom.checkboxCustomEmailPoolSelectAll?.addEventListener('change', (event) => {
+ const visibleEntries = getFilteredEntries(renderedEntries);
+ const visibleIds = visibleEntries.map((entry) => String(entry.id));
+ if (event.target.checked) {
+ visibleIds.forEach((id) => selectedEntryIds.add(id));
+ } else {
+ visibleIds.forEach((id) => selectedEntryIds.delete(id));
+ }
+ renderCustomEmailPoolEntries(renderedEntries);
+ });
+
+ dom.btnCustomEmailPoolBulkUsed?.addEventListener('click', async () => {
+ const targetIds = new Set([...selectedEntryIds]);
+ await patchEntries((entriesList) => entriesList.map((entry) => (
+ targetIds.has(String(entry.id))
+ ? { ...entry, used: true, lastUsedAt: entry.lastUsedAt || Date.now() }
+ : entry
+ )));
+ });
+
+ dom.btnCustomEmailPoolBulkUnused?.addEventListener('click', async () => {
+ const targetIds = new Set([...selectedEntryIds]);
+ await patchEntries((entriesList) => entriesList.map((entry) => (
+ targetIds.has(String(entry.id))
+ ? { ...entry, used: false }
+ : entry
+ )));
+ });
+
+ dom.btnCustomEmailPoolBulkEnable?.addEventListener('click', async () => {
+ const targetIds = new Set([...selectedEntryIds]);
+ await patchEntries((entriesList) => entriesList.map((entry) => (
+ targetIds.has(String(entry.id))
+ ? { ...entry, enabled: true }
+ : entry
+ )));
+ });
+
+ dom.btnCustomEmailPoolBulkDisable?.addEventListener('click', async () => {
+ const targetIds = new Set([...selectedEntryIds]);
+ await patchEntries((entriesList) => entriesList.map((entry) => (
+ targetIds.has(String(entry.id))
+ ? { ...entry, enabled: false }
+ : entry
+ )));
+ });
+
+ dom.btnCustomEmailPoolBulkDelete?.addEventListener('click', async () => {
+ await deleteEntries({
+ ids: [...selectedEntryIds],
+ }, `确认删除当前选中的 ${selectedEntryIds.size} 个邮箱吗?此操作不可撤销。`);
+ });
+
+ dom.btnCustomEmailPoolClearUsed?.addEventListener('click', async () => {
+ await deleteEntries({
+ mode: 'used',
+ }, '确认删除当前所有已用邮箱吗?此操作不可撤销。');
+ });
+
+ dom.btnCustomEmailPoolDeleteAll?.addEventListener('click', async () => {
+ await deleteEntries({
+ mode: 'all',
+ }, '确认删除当前全部邮箱吗?此操作不可撤销。');
+ });
+ }
+
+ return {
+ bindEvents,
+ queueCustomEmailPoolRefresh,
+ refreshCustomEmailPoolEntries,
+ renderCustomEmailPoolEntries,
+ reset,
+ };
+ }
+
+ globalScope.SidepanelCustomEmailPoolManager = {
+ createCustomEmailPoolManager,
+ };
+})(typeof window !== 'undefined' ? window : globalThis);
diff --git a/sidepanel/sidepanel.html b/sidepanel/sidepanel.html
index 8b0448b..d29e83b 100644
--- a/sidepanel/sidepanel.html
+++ b/sidepanel/sidepanel.html
@@ -297,8 +297,54 @@
邮箱池
-
+
+
+
+
+
+
+
+
+
导入你提前准备好的注册邮箱,每行一个邮箱地址。
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
CF 域名
@@ -1391,8 +1437,7 @@
-
-
+
diff --git a/sidepanel/sidepanel.js b/sidepanel/sidepanel.js
index eb1af75..9cfcf4f 100644
--- a/sidepanel/sidepanel.js
+++ b/sidepanel/sidepanel.js
@@ -177,6 +177,22 @@ 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 btnCustomEmailPoolRefresh = document.getElementById('btn-custom-email-pool-refresh');
+const btnCustomEmailPoolClearUsed = document.getElementById('btn-custom-email-pool-clear-used');
+const btnCustomEmailPoolDeleteAll = document.getElementById('btn-custom-email-pool-delete-all');
+const inputCustomEmailPoolImport = document.getElementById('input-custom-email-pool-import');
+const btnCustomEmailPoolImport = document.getElementById('btn-custom-email-pool-import');
+const customEmailPoolSummary = document.getElementById('custom-email-pool-summary');
+const inputCustomEmailPoolSearch = document.getElementById('input-custom-email-pool-search');
+const selectCustomEmailPoolFilter = document.getElementById('select-custom-email-pool-filter');
+const checkboxCustomEmailPoolSelectAll = document.getElementById('checkbox-custom-email-pool-select-all');
+const customEmailPoolSelectionSummary = document.getElementById('custom-email-pool-selection-summary');
+const btnCustomEmailPoolBulkUsed = document.getElementById('btn-custom-email-pool-bulk-used');
+const btnCustomEmailPoolBulkUnused = document.getElementById('btn-custom-email-pool-bulk-unused');
+const btnCustomEmailPoolBulkEnable = document.getElementById('btn-custom-email-pool-bulk-enable');
+const btnCustomEmailPoolBulkDisable = document.getElementById('btn-custom-email-pool-bulk-disable');
+const btnCustomEmailPoolBulkDelete = document.getElementById('btn-custom-email-pool-bulk-delete');
+const customEmailPoolList = document.getElementById('custom-email-pool-list');
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');
@@ -869,44 +885,7 @@ let configActionInFlight = false;
let currentReleaseSnapshot = null;
let currentContributionContentSnapshot = null;
let contributionContentSnapshotRequestInFlight = null;
-let phoneVerificationSectionExpanded = true;
-
-function readPhoneVerificationSectionExpanded() {
- try {
- return globalThis.localStorage?.getItem(PHONE_VERIFICATION_SECTION_EXPANDED_STORAGE_KEY) !== '0';
- } catch (err) {
- return true;
- }
-}
-
-function persistPhoneVerificationSectionExpanded(expanded) {
- try {
- globalThis.localStorage?.setItem(
- PHONE_VERIFICATION_SECTION_EXPANDED_STORAGE_KEY,
- expanded ? '1' : '0'
- );
- } catch (err) {
- // Ignore storage errors; in-memory state is sufficient for current session.
- }
-}
-
-function setPhoneVerificationSectionExpanded(expanded) {
- phoneVerificationSectionExpanded = Boolean(expanded);
- persistPhoneVerificationSectionExpanded(phoneVerificationSectionExpanded);
- updatePhoneVerificationSettingsUI();
-}
-
-function togglePhoneVerificationSectionExpanded() {
- if (!inputPhoneVerificationEnabled?.checked) {
- return;
- }
- setPhoneVerificationSectionExpanded(!phoneVerificationSectionExpanded);
-}
-
-function initPhoneVerificationSectionExpandedState() {
- phoneVerificationSectionExpanded = readPhoneVerificationSectionExpanded();
- updatePhoneVerificationSettingsUI();
-}
+let customEmailPoolEntriesState = [];
const EYE_OPEN_ICON = '
';
const EYE_CLOSED_ICON = '
';
@@ -1982,6 +1961,85 @@ function normalizeCustomEmailPoolEntries(value = '') {
.filter((item) => /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(item));
}
+function normalizeCustomEmailPoolEntryEmail(value = '') {
+ return String(value || '').trim().toLowerCase();
+}
+
+function createCustomEmailPoolEntryId() {
+ if (typeof crypto !== 'undefined' && typeof crypto.randomUUID === 'function') {
+ return crypto.randomUUID();
+ }
+ return `custom-pool-${Date.now()}-${Math.random().toString(36).slice(2, 10)}`;
+}
+
+function normalizeCustomEmailPoolEntryObjects(value = []) {
+ const source = Array.isArray(value) ? value : [];
+ const seenEmails = new Set();
+ const entries = [];
+
+ for (const rawEntry of source) {
+ const asObject = rawEntry && typeof rawEntry === 'object'
+ ? rawEntry
+ : { email: rawEntry };
+ const email = normalizeCustomEmailPoolEntryEmail(asObject.email || '');
+ if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email)) {
+ continue;
+ }
+ if (seenEmails.has(email)) {
+ continue;
+ }
+ seenEmails.add(email);
+ entries.push({
+ id: String(asObject.id || createCustomEmailPoolEntryId()),
+ email,
+ enabled: asObject.enabled !== undefined ? Boolean(asObject.enabled) : true,
+ used: Boolean(asObject.used),
+ note: String(asObject.note || '').trim(),
+ lastUsedAt: Number.isFinite(Number(asObject.lastUsedAt)) ? Number(asObject.lastUsedAt) : 0,
+ });
+ }
+
+ return entries;
+}
+
+function getNormalizedCustomEmailPoolEntriesState() {
+ const entries = (typeof customEmailPoolEntriesState !== 'undefined' && Array.isArray(customEmailPoolEntriesState))
+ ? customEmailPoolEntriesState
+ : [];
+ return normalizeCustomEmailPoolEntryObjects(entries);
+}
+
+function getActiveCustomEmailPoolEmails(entries = getNormalizedCustomEmailPoolEntriesState()) {
+ return normalizeCustomEmailPoolEntryObjects(entries)
+ .filter((entry) => entry.enabled && !entry.used)
+ .map((entry) => entry.email);
+}
+
+function setCustomEmailPoolEntriesState(entries = [], options = {}) {
+ const { syncInput = true } = options;
+ customEmailPoolEntriesState = normalizeCustomEmailPoolEntryObjects(entries);
+ if (syncInput && inputCustomEmailPool) {
+ inputCustomEmailPool.value = getActiveCustomEmailPoolEmails(customEmailPoolEntriesState).join('\n');
+ }
+}
+
+function restoreCustomEmailPoolEntriesFromState(state = {}) {
+ const rawEntries = Array.isArray(state?.customEmailPoolEntries)
+ ? state.customEmailPoolEntries
+ : [];
+ if (rawEntries.length > 0) {
+ return normalizeCustomEmailPoolEntryObjects(rawEntries);
+ }
+ return normalizeCustomEmailPoolEntries(state?.customEmailPool).map((email) => ({
+ id: createCustomEmailPoolEntryId(),
+ email,
+ enabled: true,
+ used: false,
+ note: '',
+ lastUsedAt: 0,
+ }));
+}
+
function usesCustomEmailPoolGenerator(provider = selectMailProvider.value) {
return !isCustomMailProvider(provider)
&& !isLuckmailProvider(provider)
@@ -1997,6 +2055,12 @@ function usesCustomMailProviderPool(provider = selectMailProvider.value) {
}
function getCustomEmailPoolSize() {
+ if (typeof customEmailPoolEntriesState !== 'undefined' && Array.isArray(customEmailPoolEntriesState)) {
+ const activeEntries = getActiveCustomEmailPoolEmails(customEmailPoolEntriesState);
+ if (activeEntries.length > 0 || customEmailPoolEntriesState.length > 0) {
+ return activeEntries.length;
+ }
+ }
return normalizeCustomEmailPoolEntries(inputCustomEmailPool?.value).length;
}
@@ -2802,27 +2866,14 @@ function collectSettingsPayload() {
id: normalizeHeroSmsCountryId(latestState?.heroSmsCountryId, 0),
label: normalizeHeroSmsCountryLabel(latestState?.heroSmsCountryLabel),
};
- const heroSmsCountryFallback = phoneSmsProviderValue === heroProviderValue
- ? (typeof syncHeroSmsFallbackSelectionOrderFromSelect === 'function'
- ? syncHeroSmsFallbackSelectionOrderFromSelect()
- .filter((country) => Number(country.id) !== Number(heroSmsCountry.id))
- : [])
- : normalizeHeroSmsCountryFallbackList(
- Array.isArray(latestState?.heroSmsCountryFallback) ? latestState.heroSmsCountryFallback : []
- );
- const payPalAccounts = typeof getPayPalAccounts === 'function'
- ? getPayPalAccounts(latestState)
- : (Array.isArray(latestState?.paypalAccounts) ? latestState.paypalAccounts : []);
- const currentPayPalAccount = typeof getCurrentPayPalAccount === 'function'
- ? getCurrentPayPalAccount(latestState)
- : payPalAccounts.find((account) => account?.id === String(latestState?.currentPayPalAccountId || '').trim()) || null;
- const plusPaymentMethod = typeof getSelectedPlusPaymentMethod === 'function'
- ? getSelectedPlusPaymentMethod()
- : (String(
- (typeof selectPlusPaymentMethod !== 'undefined' && selectPlusPaymentMethod
- ? selectPlusPaymentMethod.value
- : latestState?.plusPaymentMethod) || ''
- ).trim().toLowerCase() === 'gopay' ? 'gopay' : 'paypal');
+ const normalizedCustomEmailPool = typeof getActiveCustomEmailPoolEmails === 'function'
+ ? getActiveCustomEmailPoolEmails()
+ : (typeof normalizeCustomEmailPoolEntries === 'function'
+ ? normalizeCustomEmailPoolEntries(inputCustomEmailPool?.value)
+ : []);
+ const normalizedCustomEmailPoolEntries = typeof getNormalizedCustomEmailPoolEntriesState === 'function'
+ ? getNormalizedCustomEmailPoolEntriesState()
+ : [];
return {
...(contributionModeEnabled ? {} : {
panelMode: selectPanelMode.value,
@@ -2876,9 +2927,8 @@ function collectSettingsPayload() {
customMailProviderPool: typeof normalizeCustomEmailPoolEntries === 'function'
? normalizeCustomEmailPoolEntries(inputCustomMailProviderPool?.value)
: [],
- customEmailPool: typeof normalizeCustomEmailPoolEntries === 'function'
- ? normalizeCustomEmailPoolEntries(inputCustomEmailPool?.value)
- : [],
+ customEmailPool: normalizedCustomEmailPool,
+ customEmailPoolEntries: normalizedCustomEmailPoolEntries,
autoDeleteUsedIcloudAlias: checkboxAutoDeleteIcloud?.checked,
icloudHostPreference: selectIcloudHostPreference?.value || 'auto',
icloudTargetMailboxType: normalizedIcloudTargetMailboxType,
@@ -6980,9 +7030,7 @@ function applySettingsState(state) {
if (inputCustomMailProviderPool) {
inputCustomMailProviderPool.value = normalizeCustomEmailPoolEntries(state?.customMailProviderPool).join('\n');
}
- if (inputCustomEmailPool) {
- inputCustomEmailPool.value = normalizeCustomEmailPoolEntries(state?.customEmailPool).join('\n');
- }
+ setCustomEmailPoolEntriesState(restoreCustomEmailPoolEntriesFromState(state));
setHotmailServiceMode(state?.hotmailServiceMode);
inputHotmailRemoteBaseUrl.value = state?.hotmailRemoteBaseUrl || '';
inputHotmailLocalBaseUrl.value = state?.hotmailLocalBaseUrl || '';
@@ -7179,6 +7227,7 @@ function applySettingsState(state) {
}
updatePanelModeUI();
updateMailProviderUI();
+ queueCustomEmailPoolRefresh();
if (isLuckmailProvider(state?.mailProvider)) {
queueLuckmailPurchaseRefresh();
}
@@ -8130,6 +8179,11 @@ function updateMailProviderUI() {
}
if (typeof rowCustomEmailPool !== 'undefined' && rowCustomEmailPool) {
rowCustomEmailPool.style.display = useCustomEmailPool ? '' : 'none';
+ if (useCustomEmailPool) {
+ queueCustomEmailPoolRefresh();
+ } else {
+ resetCustomEmailPoolManager();
+ }
}
if (cloudflareTempEmailSection) {
cloudflareTempEmailSection.style.display = showCloudflareTempEmailSettings ? '' : 'none';
@@ -8642,8 +8696,8 @@ async function fetchGeneratedEmail(options = {}) {
mail2925Mode: getSelectedMail2925Mode(),
...(getSelectedEmailGenerator() === CUSTOM_EMAIL_POOL_GENERATOR
? {
- customEmailPool: normalizeCustomEmailPoolEntries(inputCustomEmailPool?.value),
- }
+ customEmailPool: getActiveCustomEmailPoolEmails(),
+ }
: {}),
...buildManagedAliasBaseEmailPayload(),
},
@@ -8976,6 +9030,70 @@ const bindLuckmailEvents = luckmailManager?.bindLuckmailEvents
|| (() => { });
bindLuckmailEvents();
+const customEmailPoolManager = window.SidepanelCustomEmailPoolManager?.createCustomEmailPoolManager({
+ dom: {
+ btnCustomEmailPoolRefresh,
+ btnCustomEmailPoolClearUsed,
+ btnCustomEmailPoolDeleteAll,
+ inputCustomEmailPoolImport,
+ btnCustomEmailPoolImport,
+ customEmailPoolSummary,
+ inputCustomEmailPoolSearch,
+ selectCustomEmailPoolFilter,
+ checkboxCustomEmailPoolSelectAll,
+ customEmailPoolSelectionSummary,
+ btnCustomEmailPoolBulkUsed,
+ btnCustomEmailPoolBulkUnused,
+ btnCustomEmailPoolBulkEnable,
+ btnCustomEmailPoolBulkDisable,
+ btnCustomEmailPoolBulkDelete,
+ customEmailPoolList,
+ },
+ helpers: {
+ copyTextToClipboard,
+ escapeHtml,
+ openConfirmModal,
+ showToast,
+ },
+ state: {
+ getEntries: () => getNormalizedCustomEmailPoolEntriesState(),
+ setEntries: (entries) => {
+ setCustomEmailPoolEntriesState(entries);
+ },
+ getCurrentEmail: () => String(inputEmail?.value || latestState?.email || '').trim().toLowerCase(),
+ isVisible: () => Boolean(rowCustomEmailPool) && rowCustomEmailPool.style.display !== 'none',
+ },
+ actions: {
+ persistEntries: async () => {
+ syncRunCountFromConfiguredEmailPool();
+ updateMailProviderUI();
+ markSettingsDirty(true);
+ await saveSettings({ silent: true });
+ },
+ setRuntimeEmail: async (email) => {
+ await setRuntimeEmailState(email);
+ syncLatestState({ email });
+ if (inputEmail) {
+ inputEmail.value = email || '';
+ }
+ },
+ },
+ constants: {
+ copyIcon: COPY_ICON,
+ },
+});
+const queueCustomEmailPoolRefresh = customEmailPoolManager?.queueCustomEmailPoolRefresh
+ || (() => { });
+const refreshCustomEmailPoolEntries = customEmailPoolManager?.refreshCustomEmailPoolEntries
+ || (async () => { });
+const renderCustomEmailPoolEntries = customEmailPoolManager?.renderCustomEmailPoolEntries
+ || (() => { });
+const resetCustomEmailPoolManager = customEmailPoolManager?.reset
+ || (() => { });
+const bindCustomEmailPoolEvents = customEmailPoolManager?.bindEvents
+ || (() => { });
+bindCustomEmailPoolEvents();
+
const accountRecordsManager = window.SidepanelAccountRecordsManager?.createAccountRecordsManager({
state: {
getLatestState: () => latestState,
@@ -11171,6 +11289,7 @@ chrome.runtime.onMessage.addListener((message, _sender, sendResponse) => {
logArea.innerHTML = '';
resetIcloudManager();
resetLuckmailManager();
+ resetCustomEmailPoolManager();
document.querySelectorAll('.step-row').forEach(row => row.className = 'step-row');
document.querySelectorAll('.step-status').forEach(el => el.textContent = '');
syncAutoRunState({
@@ -11200,6 +11319,7 @@ chrome.runtime.onMessage.addListener((message, _sender, sendResponse) => {
syncLatestState(message.payload);
if (message.payload.email !== undefined) {
inputEmail.value = message.payload.email || '';
+ queueCustomEmailPoolRefresh();
}
if (
message.payload.password !== undefined
@@ -11424,6 +11544,14 @@ chrome.runtime.onMessage.addListener((message, _sender, sendResponse) => {
setManagedAliasBaseEmailInputForProvider('2925', latestState);
}
}
+ if (message.payload.customEmailPoolEntries !== undefined || message.payload.customEmailPool !== undefined) {
+ setCustomEmailPoolEntriesState(restoreCustomEmailPoolEntriesFromState({
+ ...latestState,
+ ...message.payload,
+ }));
+ syncRunCountFromConfiguredEmailPool();
+ queueCustomEmailPoolRefresh();
+ }
if (message.payload.luckmailApiKey !== undefined) {
inputLuckmailApiKey.value = message.payload.luckmailApiKey || '';
}
diff --git a/tests/background-custom-email-pool.test.js b/tests/background-custom-email-pool.test.js
index b02a701..b9844df 100644
--- a/tests/background-custom-email-pool.test.js
+++ b/tests/background-custom-email-pool.test.js
@@ -54,6 +54,7 @@ function extractFunction(name) {
const bundle = [
extractFunction('normalizeEmailGenerator'),
extractFunction('normalizeCustomEmailPool'),
+ extractFunction('normalizeCustomEmailPoolEntryObjects'),
extractFunction('getCustomEmailPool'),
extractFunction('getCustomEmailPoolEmailForRun'),
extractFunction('getCustomMailProviderPool'),
@@ -122,3 +123,18 @@ test('background selects the matching custom provider pool email for the current
assert.equal(api.getCustomMailProviderPoolEmailForRun(state, 3), 'third@example.com');
assert.equal(api.getCustomMailProviderPoolEmailForRun(state, 4), '');
});
+
+test('background derives active custom email pool from structured entries', () => {
+ const api = createApi();
+ const state = {
+ customEmailPoolEntries: [
+ { id: 'a', email: 'one@example.com', enabled: true, used: false },
+ { id: 'b', email: 'two@example.com', enabled: true, used: true },
+ { id: 'c', email: 'three@example.com', enabled: false, used: false },
+ ],
+ };
+
+ assert.deepEqual(api.getCustomEmailPool(state), ['one@example.com']);
+ assert.equal(api.getCustomEmailPoolEmailForRun(state, 1), 'one@example.com');
+ assert.equal(api.getCustomEmailPoolEmailForRun(state, 2), '');
+});
diff --git a/tests/sidepanel-custom-email-pool.test.js b/tests/sidepanel-custom-email-pool.test.js
index 75e5c89..46dca63 100644
--- a/tests/sidepanel-custom-email-pool.test.js
+++ b/tests/sidepanel-custom-email-pool.test.js
@@ -57,6 +57,9 @@ test('sidepanel html exposes custom email pool generator option and input row',
assert.match(html, /option value="custom-pool">自定义邮箱池<\/option>/);
assert.match(html, /id="row-custom-email-pool"/);
assert.match(html, /id="input-custom-email-pool"/);
+ assert.match(html, /id="input-custom-email-pool-import"/);
+ assert.match(html, /id="custom-email-pool-list"/);
+ assert.match(html, /id="btn-custom-email-pool-bulk-used"/);
assert.match(html, /id="row-custom-mail-provider-pool"/);
assert.match(html, /id="input-custom-mail-provider-pool"/);
});