2925邮箱增加账号池功能

This commit is contained in:
QLHazyCoder
2026-04-20 18:56:08 +08:00
parent 8dd6daccd1
commit 6783cf02b4
25 changed files with 2630 additions and 108 deletions
-1
View File
@@ -25,7 +25,6 @@
dom.rowSub2ApiGroup,
dom.rowSub2ApiDefaultProxy,
dom.rowCustomPassword,
dom.rowAccountRunHistoryTextEnabled,
dom.rowAccountRunHistoryHelperBaseUrl,
].filter(Boolean);
+463
View File
@@ -0,0 +1,463 @@
(function attachSidepanelMail2925Manager(globalScope) {
function createMail2925Manager(context = {}) {
const {
state,
dom,
helpers,
runtime,
constants = {},
mail2925Utils = {},
} = context;
const expandedStorageKey = constants.expandedStorageKey || 'multipage-mail2925-list-expanded';
const displayTimeZone = constants.displayTimeZone || 'Asia/Shanghai';
const copyIcon = constants.copyIcon || '';
let actionInFlight = false;
let listExpanded = false;
function getMail2925Accounts(currentState = state.getLatestState()) {
return helpers.getMail2925Accounts(currentState);
}
function getCurrentMail2925AccountId(currentState = state.getLatestState()) {
return String(currentState?.currentMail2925AccountId || '');
}
function updateMail2925ListViewport() {
const count = getMail2925Accounts().length;
if (dom.btnDeleteAllMail2925Accounts) {
dom.btnDeleteAllMail2925Accounts.textContent = `全部删除${count > 0 ? `${count}` : ''}`;
dom.btnDeleteAllMail2925Accounts.disabled = count === 0;
}
if (dom.btnToggleMail2925List) {
const label = typeof mail2925Utils.getMail2925ListToggleLabel === 'function'
? mail2925Utils.getMail2925ListToggleLabel(listExpanded, count)
: `${listExpanded ? '收起列表' : '展开列表'}${count > 0 ? `${count}` : ''}`;
dom.btnToggleMail2925List.textContent = label;
dom.btnToggleMail2925List.setAttribute('aria-expanded', String(listExpanded));
dom.btnToggleMail2925List.disabled = count === 0;
}
if (dom.mail2925ListShell) {
dom.mail2925ListShell.classList.toggle('is-expanded', listExpanded);
dom.mail2925ListShell.classList.toggle('is-collapsed', !listExpanded);
}
}
function setMail2925ListExpanded(expanded, options = {}) {
const { persist = true } = options;
listExpanded = Boolean(expanded);
updateMail2925ListViewport();
if (persist) {
localStorage.setItem(expandedStorageKey, listExpanded ? '1' : '0');
}
}
function initMail2925ListExpandedState() {
const saved = localStorage.getItem(expandedStorageKey);
setMail2925ListExpanded(saved === '1', { persist: false });
}
function formatDateTime(timestamp) {
const value = Number(timestamp);
if (!Number.isFinite(value) || value <= 0) {
return '未记录';
}
return new Date(value).toLocaleString('zh-CN', {
hour12: false,
timeZone: displayTimeZone,
});
}
function getStatusSnapshot(account) {
const status = typeof mail2925Utils.getMail2925AccountStatus === 'function'
? mail2925Utils.getMail2925AccountStatus(account, Date.now())
: 'ready';
switch (status) {
case 'cooldown':
return { label: '冷却中', className: 'status-used' };
case 'disabled':
return { label: '已禁用', className: 'status-disabled' };
case 'error':
return { label: '异常', className: 'status-error' };
case 'pending':
return { label: '待完善', className: 'status-pending' };
default:
return { label: '可用', className: 'status-authorized' };
}
}
function refreshManagedAliasBaseEmail() {
if (typeof helpers.refreshManagedAliasBaseEmail === 'function') {
helpers.refreshManagedAliasBaseEmail();
}
}
function applyMail2925AccountMutation(account, options = {}) {
if (!account?.id) return;
const latestState = state.getLatestState();
const currentId = getCurrentMail2925AccountId(latestState);
const nextAccounts = typeof mail2925Utils.upsertMail2925AccountInList === 'function'
? mail2925Utils.upsertMail2925AccountInList(getMail2925Accounts(latestState), account)
: getMail2925Accounts(latestState).map((item) => (item.id === account.id ? account : item));
const nextState = {
mail2925Accounts: nextAccounts,
};
if (currentId === account.id && account.enabled === false) {
nextState.currentMail2925AccountId = null;
}
state.syncLatestState(nextState);
refreshManagedAliasBaseEmail();
renderMail2925Accounts();
}
function clearMail2925Form() {
if (dom.inputMail2925Email) dom.inputMail2925Email.value = '';
if (dom.inputMail2925Password) dom.inputMail2925Password.value = '';
}
function renderMail2925Accounts() {
if (!dom.mail2925AccountsList) return;
const latestState = state.getLatestState();
const accounts = getMail2925Accounts(latestState);
const currentId = getCurrentMail2925AccountId(latestState);
if (!accounts.length) {
dom.mail2925AccountsList.innerHTML = '<div class="hotmail-empty">还没有 2925 账号,先添加一条再使用。</div>';
updateMail2925ListViewport();
return;
}
dom.mail2925AccountsList.innerHTML = accounts.map((account) => {
const status = getStatusSnapshot(account);
const coolingDown = status.label === '冷却中';
return `
<div class="hotmail-account-item${account.id === currentId ? ' is-current' : ''}">
<div class="hotmail-account-top">
<div class="hotmail-account-title-row">
<div class="hotmail-account-email">${helpers.escapeHtml(account.email || '(未命名账号)')}</div>
<button
class="hotmail-copy-btn"
type="button"
data-account-action="copy-email"
data-account-id="${helpers.escapeHtml(account.id)}"
title="复制邮箱"
aria-label="复制邮箱 ${helpers.escapeHtml(account.email || '')}"
>${copyIcon}</button>
</div>
<span class="hotmail-status-chip ${helpers.escapeHtml(status.className)}">${helpers.escapeHtml(status.label)}</span>
</div>
<div class="hotmail-account-meta">
<span>密码:${account.password ? '已保存' : '未保存'}</span>
<span>上次登录:${helpers.escapeHtml(formatDateTime(account.lastLoginAt))}</span>
<span>上次使用:${helpers.escapeHtml(formatDateTime(account.lastUsedAt))}</span>
<span>上限记录:${helpers.escapeHtml(formatDateTime(account.lastLimitAt))}</span>
<span>恢复时间:${helpers.escapeHtml(formatDateTime(account.disabledUntil))}</span>
</div>
${account.lastError ? `<div class="hotmail-account-error">${helpers.escapeHtml(account.lastError)}</div>` : ''}
<div class="hotmail-account-actions">
<button class="btn btn-outline btn-sm" type="button" data-account-action="select" data-account-id="${helpers.escapeHtml(account.id)}">使用此账号</button>
<button class="btn btn-primary btn-sm" type="button" data-account-action="login" data-account-id="${helpers.escapeHtml(account.id)}">登录</button>
<button class="btn btn-outline btn-sm" type="button" data-account-action="toggle-enabled" data-account-id="${helpers.escapeHtml(account.id)}">${account.enabled === false ? '启用' : '禁用'}</button>
${coolingDown ? `<button class="btn btn-outline btn-sm" type="button" data-account-action="clear-cooldown" data-account-id="${helpers.escapeHtml(account.id)}">清冷却</button>` : ''}
<button class="btn btn-ghost btn-sm" type="button" data-account-action="delete" data-account-id="${helpers.escapeHtml(account.id)}">删除</button>
</div>
</div>
`;
}).join('');
updateMail2925ListViewport();
}
async function handleAddMail2925Account() {
if (actionInFlight) return;
const email = String(dom.inputMail2925Email?.value || '').trim();
const password = String(dom.inputMail2925Password?.value || '');
if (!email) {
helpers.showToast('请先填写 2925 邮箱。', 'warn');
return;
}
if (!password) {
helpers.showToast('请先填写 2925 密码。', 'warn');
return;
}
actionInFlight = true;
if (dom.btnAddMail2925Account) {
dom.btnAddMail2925Account.disabled = true;
}
try {
const response = await runtime.sendMessage({
type: 'UPSERT_MAIL2925_ACCOUNT',
source: 'sidepanel',
payload: {
email,
password,
},
});
if (response?.error) {
throw new Error(response.error);
}
applyMail2925AccountMutation(response.account);
clearMail2925Form();
helpers.showToast(`已保存 2925 账号 ${email}`, 'success', 1800);
} catch (err) {
helpers.showToast(`保存 2925 账号失败:${err.message}`, 'error');
} finally {
actionInFlight = false;
if (dom.btnAddMail2925Account) {
dom.btnAddMail2925Account.disabled = false;
}
}
}
async function handleImportMail2925Accounts() {
if (actionInFlight) return;
if (typeof mail2925Utils.parseMail2925ImportText !== 'function') {
helpers.showToast('2925 导入解析器未加载,请刷新扩展后重试。', 'error');
return;
}
const rawText = String(dom.inputMail2925Import?.value || '').trim();
if (!rawText) {
helpers.showToast('请先粘贴 2925 账号导入内容。', 'warn');
return;
}
const parsedAccounts = mail2925Utils.parseMail2925ImportText(rawText);
if (!parsedAccounts.length) {
helpers.showToast('没有解析到有效账号,请检查格式是否为 邮箱----密码。', 'error');
return;
}
actionInFlight = true;
if (dom.btnImportMail2925Accounts) {
dom.btnImportMail2925Accounts.disabled = true;
}
try {
for (const account of parsedAccounts) {
const response = await runtime.sendMessage({
type: 'UPSERT_MAIL2925_ACCOUNT',
source: 'sidepanel',
payload: account,
});
if (response?.error) {
throw new Error(response.error);
}
}
if (dom.inputMail2925Import) {
dom.inputMail2925Import.value = '';
}
helpers.showToast(`已导入 ${parsedAccounts.length} 条 2925 账号`, 'success', 2200);
} catch (err) {
helpers.showToast(`批量导入 2925 账号失败:${err.message}`, 'error');
} finally {
actionInFlight = false;
if (dom.btnImportMail2925Accounts) {
dom.btnImportMail2925Accounts.disabled = false;
}
}
}
async function deleteAllMail2925Accounts() {
const accounts = getMail2925Accounts();
if (!accounts.length) {
helpers.showToast('没有可删除的 2925 账号。', 'warn');
return;
}
const confirmed = await helpers.openConfirmModal({
title: '全部删除 2925 账号',
message: `确认删除当前全部 ${accounts.length} 个 2925 账号吗?`,
confirmLabel: '确认全部删除',
confirmVariant: 'btn-danger',
});
if (!confirmed) {
return;
}
const response = await runtime.sendMessage({
type: 'DELETE_MAIL2925_ACCOUNTS',
source: 'sidepanel',
payload: { mode: 'all' },
});
if (response?.error) {
throw new Error(response.error);
}
state.syncLatestState({
mail2925Accounts: [],
currentMail2925AccountId: null,
});
refreshManagedAliasBaseEmail();
renderMail2925Accounts();
helpers.showToast(`已删除全部 ${response.deletedCount || 0} 个 2925 账号`, 'success', 2200);
}
async function handleAccountListClick(event) {
const actionButton = event.target.closest('[data-account-action]');
if (!actionButton || actionInFlight) {
return;
}
const accountId = String(actionButton.dataset.accountId || '');
const action = String(actionButton.dataset.accountAction || '');
if (!accountId || !action) {
return;
}
const targetAccount = getMail2925Accounts().find((account) => account.id === accountId) || null;
actionInFlight = true;
actionButton.disabled = true;
try {
if (action === 'copy-email') {
if (!targetAccount?.email) throw new Error('未找到可复制的 2925 邮箱。');
await helpers.copyTextToClipboard(targetAccount.email);
helpers.showToast(`已复制 ${targetAccount.email}`, 'success', 1800);
return;
}
if (action === 'select') {
const response = await runtime.sendMessage({
type: 'SELECT_MAIL2925_ACCOUNT',
source: 'sidepanel',
payload: { accountId },
});
if (response?.error) throw new Error(response.error);
state.syncLatestState({ currentMail2925AccountId: response.account.id });
refreshManagedAliasBaseEmail();
renderMail2925Accounts();
helpers.showToast(`已切换当前 2925 账号为 ${response.account.email}`, 'success', 2000);
return;
}
if (action === 'login') {
const response = await runtime.sendMessage({
type: 'LOGIN_MAIL2925_ACCOUNT',
source: 'sidepanel',
payload: {
accountId,
forceRelogin: true,
},
});
if (response?.error) throw new Error(response.error);
state.syncLatestState({ currentMail2925AccountId: response.account.id });
refreshManagedAliasBaseEmail();
renderMail2925Accounts();
helpers.showToast(`已使用 ${response.account.email} 登录 2925 邮箱`, 'success', 2200);
return;
}
if (action === 'toggle-enabled') {
if (!targetAccount) throw new Error('未找到目标 2925 账号。');
const response = await runtime.sendMessage({
type: 'PATCH_MAIL2925_ACCOUNT',
source: 'sidepanel',
payload: {
accountId,
updates: {
enabled: targetAccount.enabled === false,
},
},
});
if (response?.error) throw new Error(response.error);
applyMail2925AccountMutation(response.account);
helpers.showToast(`2925 账号 ${response.account.email}${response.account.enabled === false ? '禁用' : '启用'}`, 'success', 2200);
return;
}
if (action === 'clear-cooldown') {
const response = await runtime.sendMessage({
type: 'PATCH_MAIL2925_ACCOUNT',
source: 'sidepanel',
payload: {
accountId,
updates: {
disabledUntil: 0,
lastError: '',
},
},
});
if (response?.error) throw new Error(response.error);
applyMail2925AccountMutation(response.account);
helpers.showToast(`2925 账号 ${response.account.email} 已清除冷却`, 'success', 2200);
return;
}
if (action === 'delete') {
const confirmed = await helpers.openConfirmModal({
title: '删除 2925 账号',
message: '确认删除这个 2925 账号吗?',
confirmLabel: '确认删除',
confirmVariant: 'btn-danger',
});
if (!confirmed) {
return;
}
const response = await runtime.sendMessage({
type: 'DELETE_MAIL2925_ACCOUNT',
source: 'sidepanel',
payload: { accountId },
});
if (response?.error) throw new Error(response.error);
const nextAccounts = getMail2925Accounts().filter((account) => account.id !== accountId);
const nextState = { mail2925Accounts: nextAccounts };
if (getCurrentMail2925AccountId() === accountId) {
nextState.currentMail2925AccountId = null;
}
state.syncLatestState(nextState);
refreshManagedAliasBaseEmail();
renderMail2925Accounts();
helpers.showToast('2925 账号已删除', 'success', 1800);
}
} catch (err) {
helpers.showToast(err.message, 'error');
} finally {
actionInFlight = false;
actionButton.disabled = false;
}
}
function bindMail2925Events() {
dom.btnToggleMail2925List?.addEventListener('click', () => {
setMail2925ListExpanded(!listExpanded);
});
dom.btnDeleteAllMail2925Accounts?.addEventListener('click', async () => {
if (actionInFlight) return;
actionInFlight = true;
try {
await deleteAllMail2925Accounts();
} catch (err) {
helpers.showToast(err.message, 'error');
} finally {
actionInFlight = false;
updateMail2925ListViewport();
}
});
dom.btnAddMail2925Account?.addEventListener('click', handleAddMail2925Account);
dom.btnImportMail2925Accounts?.addEventListener('click', handleImportMail2925Accounts);
dom.mail2925AccountsList?.addEventListener('click', handleAccountListClick);
}
return {
bindMail2925Events,
initMail2925ListExpandedState,
renderMail2925Accounts,
};
}
globalScope.SidepanelMail2925Manager = {
createMail2925Manager,
};
})(window);
+44 -59
View File
@@ -697,6 +697,21 @@ header {
min-width: 0;
}
.mail2925-base-inline {
gap: 10px;
}
.mail2925-base-input,
.mail2925-pool-select {
flex: 1;
min-width: 0;
}
.mail2925-pool-toggle {
flex: 0 0 auto;
white-space: nowrap;
}
.data-value-actions {
justify-content: space-between;
}
@@ -1395,36 +1410,51 @@ header {
flex-shrink: 0;
}
.fallback-inline {
justify-content: space-between;
.setting-pair {
display: flex;
align-items: center;
justify-content: space-between;
gap: 12px;
flex: 1;
min-width: 0;
flex-wrap: nowrap;
}
.fallback-thread-interval {
margin-left: auto;
}
.timing-field {
.setting-group {
display: flex;
align-items: center;
gap: 12px;
min-width: 0;
}
.timing-field-labeled {
min-width: 196px;
justify-content: flex-end;
.setting-group-primary {
flex: 1;
min-width: 112px;
}
.timing-field-right {
.setting-group-secondary {
margin-left: auto;
}
.setting-inline-right {
min-width: 196px;
justify-content: flex-end;
}
.setting-caption {
font-size: 12px;
font-weight: 600;
color: var(--text-muted);
flex: 0 0 auto;
white-space: nowrap;
min-width: 56px;
text-align: right;
}
.setting-controls {
display: flex;
align-items: center;
gap: 8px;
flex-shrink: 0;
}
.toggle-switch {
position: relative;
display: inline-flex;
@@ -1482,51 +1512,6 @@ header {
opacity: 0.55;
}
.auto-delay-inline {
justify-content: space-between;
align-items: center;
gap: 12px;
flex-wrap: nowrap;
}
.auto-delay-side {
display: flex;
align-items: center;
gap: 12px;
flex: 0 0 auto;
min-width: 0;
}
.auto-delay-side-right {
flex: 0 0 auto;
}
.auto-delay-check {
flex: 0 0 auto;
align-items: center;
}
.auto-delay-check span {
white-space: nowrap;
}
.auto-delay-caption {
font-size: 12px;
font-weight: 600;
color: var(--text-muted);
flex: 0 0 auto;
white-space: nowrap;
min-width: 56px;
text-align: right;
}
.auto-delay-controls {
display: flex;
align-items: center;
gap: 8px;
flex-shrink: 0;
}
.auto-delay-input {
width: 72px;
flex: 0 0 auto;
+79 -32
View File
@@ -261,7 +261,19 @@
</div>
<div class="data-row" id="row-email-prefix" style="display:none;">
<span class="data-label" id="label-email-prefix">别名基邮箱</span>
<input type="text" id="input-email-prefix" class="data-input" placeholder="例如 yourname@example.com" />
<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 基邮箱">
<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>
</div>
</div>
<div class="data-row" id="row-inbucket-host" style="display:none;">
<span class="data-label">Inbucket</span>
@@ -280,23 +292,23 @@
</div>
<div class="data-row">
<span class="data-label">延迟</span>
<div class="data-inline auto-delay-inline">
<div class="auto-delay-side auto-delay-side-left">
<div class="data-inline setting-pair">
<div class="setting-group setting-group-primary">
<label class="toggle-switch" for="input-auto-delay-enabled">
<input type="checkbox" id="input-auto-delay-enabled" />
<span class="toggle-switch-track" aria-hidden="true">
<span class="toggle-switch-thumb"></span>
</span>
</label>
<div class="auto-delay-controls">
<div class="setting-controls">
<input type="number" id="input-auto-delay-minutes" class="data-input auto-delay-input" value="30" min="1"
max="1440" step="1" title="启动前倒计时分钟数" />
<span class="data-unit">分钟</span>
</div>
</div>
<div class="timing-field timing-field-labeled timing-field-right auto-delay-side auto-delay-side-right">
<span class="auto-delay-caption">步间间隔</span>
<div class="auto-delay-controls">
<div class="setting-group setting-group-secondary">
<span class="setting-caption">步间间隔</span>
<div class="setting-controls">
<input type="number" id="input-auto-step-delay-seconds" class="data-input auto-delay-input" value=""
min="0" max="600" step="1" title="步间延迟秒数,0 或留空则不延迟" />
<span class="data-unit"></span>
@@ -306,16 +318,18 @@
</div>
<div class="data-row">
<span class="data-label">自动重试</span>
<div class="data-inline fallback-inline">
<label class="toggle-switch" for="input-auto-skip-failures">
<input type="checkbox" id="input-auto-skip-failures" />
<span class="toggle-switch-track" aria-hidden="true">
<span class="toggle-switch-thumb"></span>
</span>
</label>
<div class="timing-field timing-field-labeled timing-field-right fallback-thread-interval">
<span class="auto-delay-caption">线程间隔</span>
<div class="auto-delay-controls">
<div class="data-inline setting-pair">
<div class="setting-group setting-group-primary">
<label class="toggle-switch" for="input-auto-skip-failures">
<input type="checkbox" id="input-auto-skip-failures" />
<span class="toggle-switch-track" aria-hidden="true">
<span class="toggle-switch-thumb"></span>
</span>
</label>
</div>
<div class="setting-group setting-group-secondary">
<span class="setting-caption">线程间隔</span>
<div class="setting-controls">
<input type="number" id="input-auto-skip-failures-thread-interval-minutes" class="data-input auto-delay-input" value="0"
min="0" max="1440" step="1" title="兜底模式下,两轮线程之间的等待分钟数" />
<span class="data-unit">分钟</span>
@@ -325,21 +339,18 @@
</div>
<div class="data-row" id="row-account-run-history-text-enabled">
<span class="data-label">本地同步</span>
<div class="data-inline">
<label class="toggle-switch" for="input-account-run-history-text-enabled">
<input type="checkbox" id="input-account-run-history-text-enabled" />
<span class="toggle-switch-track" aria-hidden="true">
<span class="toggle-switch-thumb"></span>
</span>
</label>
</div>
</div>
<div class="data-row" id="row-verification-resend-count">
<span class="data-label">验证码</span>
<div class="data-inline">
<div class="timing-field timing-field-labeled timing-field-right setting-inline-right">
<span class="auto-delay-caption">验证码重发</span>
<div class="auto-delay-controls">
<div class="data-inline setting-pair">
<div class="setting-group setting-group-primary">
<label class="toggle-switch" for="input-account-run-history-text-enabled">
<input type="checkbox" id="input-account-run-history-text-enabled" />
<span class="toggle-switch-track" aria-hidden="true">
<span class="toggle-switch-thumb"></span>
</span>
</label>
</div>
<div class="setting-group setting-group-secondary">
<span class="setting-caption">验证码重发</span>
<div class="setting-controls">
<input type="number" id="input-verification-resend-count" class="data-input auto-delay-input" value="4"
min="0" max="20" step="1" title="自动点击重新发送验证码的次数" />
<span class="data-unit"></span>
@@ -424,6 +435,40 @@
<div id="hotmail-accounts-list" class="hotmail-accounts-list"></div>
</div>
</div>
<div id="mail2925-section" class="data-card hotmail-card" style="display:none;">
<div class="section-mini-header">
<div class="section-mini-copy">
<span class="section-label">2925 账号池</span>
</div>
<div class="section-mini-actions">
<button id="btn-delete-all-mail2925-accounts" class="btn btn-ghost btn-xs" type="button">全部删除</button>
<button id="btn-toggle-mail2925-list" class="btn btn-ghost btn-xs" type="button" aria-expanded="false">展开列表</button>
</div>
</div>
<div class="data-row">
<span class="data-label">邮箱</span>
<input type="text" id="input-mail2925-email" class="data-input" placeholder="name@2925.com" />
</div>
<div class="data-row">
<span class="data-label">密码</span>
<input type="password" id="input-mail2925-password" class="data-input" placeholder="2925 登录密码" />
</div>
<div class="data-row hotmail-actions-row">
<span class="data-label"></span>
<button id="btn-add-mail2925-account" class="btn btn-primary btn-sm" type="button">添加账号</button>
</div>
<div class="data-row hotmail-import-row">
<span class="data-label">批量导入</span>
<div class="hotmail-import-box">
<textarea id="input-mail2925-import" class="data-textarea mono"
placeholder="邮箱----密码&#10;name@2925.com----password"></textarea>
<button id="btn-import-mail2925-accounts" class="btn btn-outline btn-sm" type="button">导入</button>
</div>
</div>
<div id="mail2925-list-shell" class="hotmail-list-shell is-collapsed">
<div id="mail2925-accounts-list" class="hotmail-accounts-list"></div>
</div>
</div>
<div id="luckmail-section" class="data-card luckmail-card" style="display:none;">
<div class="section-mini-header">
<div class="section-mini-copy">
@@ -656,12 +701,14 @@
<div id="toast-container"></div>
<input id="input-import-settings-file" type="file" accept=".json,application/json" hidden />
<script src="../managed-alias-utils.js"></script>
<script src="../mail2925-utils.js"></script>
<script src="../icloud-utils.js"></script>
<script src="../hotmail-utils.js"></script>
<script src="../luckmail-utils.js"></script>
<script src="../data/step-definitions.js"></script>
<script src="update-service.js"></script>
<script src="hotmail-manager.js"></script>
<script src="mail-2925-manager.js"></script>
<script src="icloud-manager.js"></script>
<script src="luckmail-manager.js"></script>
<script src="contribution-mode.js"></script>
+218 -1
View File
@@ -110,6 +110,7 @@ const selectTempEmailDomain = document.getElementById('select-temp-email-domain'
const inputTempEmailDomain = document.getElementById('input-temp-email-domain');
const btnTempEmailDomainMode = document.getElementById('btn-temp-email-domain-mode');
const hotmailSection = document.getElementById('hotmail-section');
const mail2925Section = document.getElementById('mail2925-section');
const luckmailSection = document.getElementById('luckmail-section');
const icloudSection = document.getElementById('icloud-section');
const icloudSummary = document.getElementById('icloud-summary');
@@ -150,6 +151,15 @@ const btnDeleteAllHotmailAccounts = document.getElementById('btn-delete-all-hotm
const btnToggleHotmailList = document.getElementById('btn-toggle-hotmail-list');
const hotmailListShell = document.getElementById('hotmail-list-shell');
const hotmailAccountsList = document.getElementById('hotmail-accounts-list');
const inputMail2925Email = document.getElementById('input-mail2925-email');
const inputMail2925Password = document.getElementById('input-mail2925-password');
const inputMail2925Import = document.getElementById('input-mail2925-import');
const btnAddMail2925Account = document.getElementById('btn-add-mail2925-account');
const btnImportMail2925Accounts = document.getElementById('btn-import-mail2925-accounts');
const btnDeleteAllMail2925Accounts = document.getElementById('btn-delete-all-mail2925-accounts');
const btnToggleMail2925List = document.getElementById('btn-toggle-mail2925-list');
const mail2925ListShell = document.getElementById('mail2925-list-shell');
const mail2925AccountsList = document.getElementById('mail2925-accounts-list');
const inputLuckmailApiKey = document.getElementById('input-luckmail-api-key');
const inputLuckmailBaseUrl = document.getElementById('input-luckmail-base-url');
const selectLuckmailEmailType = document.getElementById('select-luckmail-email-type');
@@ -171,6 +181,9 @@ const luckmailList = document.getElementById('luckmail-list');
const rowEmailPrefix = document.getElementById('row-email-prefix');
const labelEmailPrefix = document.getElementById('label-email-prefix');
const inputEmailPrefix = document.getElementById('input-email-prefix');
const selectMail2925PoolAccount = document.getElementById('select-mail2925-pool-account');
const inputMail2925UseAccountPool = document.getElementById('input-mail2925-use-account-pool');
const labelMail2925UseAccountPool = document.getElementById('label-mail2925-use-account-pool');
const rowInbucketHost = document.getElementById('row-inbucket-host');
const inputInbucketHost = document.getElementById('input-inbucket-host');
const rowInbucketMailbox = document.getElementById('row-inbucket-mailbox');
@@ -307,7 +320,70 @@ function getManagedAliasBaseEmailKey(provider = selectMailProvider.value) {
return '';
}
function isMail2925AccountPoolEnabled(state = latestState) {
return Boolean(state?.mail2925UseAccountPool);
}
function getPreferredMail2925PoolAccountId(state = latestState) {
const currentId = String(state?.currentMail2925AccountId || '').trim();
if (currentId && getMail2925Accounts(state).some((account) => account.id === currentId)) {
return currentId;
}
return '';
}
function syncMail2925PoolAccountOptions(state = latestState) {
if (!selectMail2925PoolAccount) {
return;
}
const accounts = getMail2925Accounts(state);
const selectedId = getPreferredMail2925PoolAccountId(state);
const options = ['<option value="">请选择号池邮箱</option>'].concat(
accounts.map((account) => `<option value="${escapeHtml(account.id)}">${escapeHtml(account.email || '(未命名账号)')}</option>`)
);
selectMail2925PoolAccount.innerHTML = options.join('');
selectMail2925PoolAccount.value = selectedId;
}
async function syncSelectedMail2925PoolAccount(options = {}) {
const { silent = false } = options;
if (!selectMail2925PoolAccount || !isMail2925AccountPoolEnabled(latestState)) {
return null;
}
const accountId = String(selectMail2925PoolAccount.value || '').trim();
if (!accountId) {
syncLatestState({ currentMail2925AccountId: null });
setManagedAliasBaseEmailInputForProvider('2925', latestState);
return null;
}
const response = await chrome.runtime.sendMessage({
type: 'SELECT_MAIL2925_ACCOUNT',
source: 'sidepanel',
payload: { accountId },
});
if (response?.error) {
throw new Error(response.error);
}
syncLatestState({ currentMail2925AccountId: response.account?.id || accountId });
setManagedAliasBaseEmailInputForProvider('2925', latestState);
if (!silent) {
showToast(`已切换当前 2925 号池邮箱为 ${response.account?.email || accountId}`, 'success', 1800);
}
return response.account || null;
}
function getManagedAliasBaseEmailForProvider(provider = selectMailProvider.value, state = latestState) {
if (String(provider || '').trim().toLowerCase() === '2925' && isMail2925AccountPoolEnabled(state)) {
const currentMail2925Email = getCurrentMail2925Email(state);
if (currentMail2925Email) {
return currentMail2925Email;
}
}
const key = getManagedAliasBaseEmailKey(provider);
if (!key) {
return '';
@@ -326,11 +402,16 @@ function buildManagedAliasBaseEmailPayload(state = latestState) {
const payload = {
gmailBaseEmail: String(state?.gmailBaseEmail || '').trim(),
mail2925BaseEmail: String(state?.mail2925BaseEmail || '').trim(),
mail2925UseAccountPool: Boolean(state?.mail2925UseAccountPool),
emailPrefix: '',
};
const key = getManagedAliasBaseEmailKey();
if (key) {
payload[key] = inputEmailPrefix.value.trim();
if (key === 'mail2925BaseEmail' && isMail2925AccountPoolEnabled(state)) {
payload[key] = String(state?.mail2925BaseEmail || '').trim();
} else {
payload[key] = inputEmailPrefix.value.trim();
}
}
return payload;
}
@@ -340,10 +421,14 @@ function syncManagedAliasBaseEmailDraftFromInput(provider = selectMailProvider.v
if (!key) {
return;
}
if (key === 'mail2925BaseEmail' && isMail2925AccountPoolEnabled(latestState)) {
return;
}
syncLatestState({ [key]: inputEmailPrefix.value.trim() });
}
function setManagedAliasBaseEmailInputForProvider(provider = selectMailProvider.value, state = latestState) {
syncMail2925PoolAccountOptions(state);
inputEmailPrefix.value = getManagedAliasBaseEmailForProvider(provider, state);
}
@@ -1342,6 +1427,9 @@ function collectSettingsPayload() {
!cloudflareTempEmailDomainEditMode ? selectTempEmailDomain.value : tempEmailActiveDomain
) || tempEmailActiveDomain;
const contributionModeEnabled = Boolean(latestState?.contributionMode);
const mail2925UseAccountPool = typeof inputMail2925UseAccountPool !== 'undefined'
? Boolean(inputMail2925UseAccountPool?.checked)
: Boolean(latestState?.mail2925UseAccountPool);
return {
panelMode: selectPanelMode.value,
vpsUrl: inputVpsUrl.value.trim(),
@@ -1357,6 +1445,7 @@ function collectSettingsPayload() {
}),
mailProvider: selectMailProvider.value,
mail2925Mode: getSelectedMail2925Mode(),
mail2925UseAccountPool,
emailGenerator: selectEmailGenerator.value,
autoDeleteUsedIcloudAlias: checkboxAutoDeleteIcloud?.checked,
icloudHostPreference: selectIcloudHostPreference?.value || 'auto',
@@ -1771,6 +1860,9 @@ function applySettingsState(state) {
if (inputContributionQq) {
inputContributionQq.value = state?.contributionQq || '';
}
if (inputMail2925UseAccountPool) {
inputMail2925UseAccountPool.checked = Boolean(state?.mail2925UseAccountPool);
}
setManagedAliasBaseEmailInputForProvider(restoredMailProvider, state);
inputInbucketHost.value = state?.inbucketHost || '';
inputInbucketMailbox.value = state?.inbucketMailbox || '';
@@ -2226,6 +2318,19 @@ function getCurrentHotmailEmail(state = latestState) {
return String(getCurrentHotmailAccount(state)?.email || '').trim();
}
function getMail2925Accounts(state = latestState) {
return Array.isArray(state?.mail2925Accounts) ? state.mail2925Accounts : [];
}
function getCurrentMail2925Account(state = latestState) {
const currentId = state?.currentMail2925AccountId;
return getMail2925Accounts(state).find((account) => account.id === currentId) || null;
}
function getCurrentMail2925Email(state = latestState) {
return String(getCurrentMail2925Account(state)?.email || '').trim();
}
function getCurrentLuckmailPurchase(state = latestState) {
return state?.currentLuckmailPurchase || null;
}
@@ -2365,6 +2470,8 @@ function updateMailLoginButtonState() {
function updateMailProviderUI() {
const use2925 = selectMailProvider.value === '2925';
const useGmail = selectMailProvider.value === GMAIL_PROVIDER;
const useMail2925 = selectMailProvider.value === '2925';
const useMail2925AccountPool = useMail2925 && Boolean(inputMail2925UseAccountPool?.checked);
const mail2925Mode = getSelectedMail2925Mode();
const useGeneratedAlias = usesGeneratedAliasMailProvider(selectMailProvider.value, mail2925Mode);
const useInbucket = selectMailProvider.value === 'inbucket';
@@ -2427,11 +2534,24 @@ function updateMailProviderUI() {
if (hotmailSection) {
hotmailSection.style.display = useHotmail ? '' : 'none';
}
if (mail2925Section) {
mail2925Section.style.display = useMail2925AccountPool ? '' : 'none';
}
if (luckmailSection) {
luckmailSection.style.display = useLuckmail ? '' : 'none';
}
labelEmailPrefix.textContent = '邮箱前缀';
inputEmailPrefix.placeholder = '例如 abc';
if (labelMail2925UseAccountPool) {
labelMail2925UseAccountPool.style.display = useMail2925 ? '' : 'none';
}
syncMail2925PoolAccountOptions(latestState);
if (selectMail2925PoolAccount) {
selectMail2925PoolAccount.style.display = useMail2925AccountPool ? '' : 'none';
selectMail2925PoolAccount.disabled = !useMail2925AccountPool || getMail2925Accounts().length === 0;
}
inputEmailPrefix.style.display = useMail2925AccountPool ? 'none' : '';
inputEmailPrefix.readOnly = useMail2925AccountPool;
selectEmailGenerator.disabled = useHotmail || useLuckmail || useGeneratedAlias || useCustomEmail;
if (useGmail) {
labelEmailPrefix.textContent = 'Gmail 原邮箱';
@@ -2480,6 +2600,11 @@ function updateMailProviderUI() {
if (autoHintText && useGeneratedAlias && aliasUiCopy?.hint) {
autoHintText.textContent = aliasUiCopy.hint;
}
if (autoHintText && useMail2925AccountPool) {
autoHintText.textContent = getMail2925Accounts().length
? '当前已启用 2925 号池模式,步骤 3 会基于下拉框选中的号池邮箱生成别名地址'
: '当前已启用 2925 号池模式,请先在下方 2925 账号池中添加账号并选择邮箱';
}
if (autoHintText && showCloudflareTempEmailReceiveMailbox) {
autoHintText.textContent = '若注册邮箱会转发到 Cloudflare Temp Email,请在“邮件接收”中填写实际接收转发邮件的邮箱。';
}
@@ -2489,6 +2614,9 @@ function updateMailProviderUI() {
inputEmail.value = getCurrentLuckmailEmail();
}
renderHotmailAccounts();
if (useMail2925) {
renderMail2925Accounts();
}
if (useLuckmail) {
renderLuckmailPurchases();
}
@@ -2905,6 +3033,50 @@ const bindHotmailEvents = hotmailManager?.bindHotmailEvents
|| (() => { });
bindHotmailEvents();
const mail2925Manager = window.SidepanelMail2925Manager?.createMail2925Manager({
state: {
getLatestState: () => latestState,
syncLatestState,
},
dom: {
btnAddMail2925Account,
btnDeleteAllMail2925Accounts,
btnImportMail2925Accounts,
btnToggleMail2925List,
inputMail2925Email,
inputMail2925Import,
inputMail2925Password,
mail2925AccountsList,
mail2925ListShell,
},
helpers: {
copyTextToClipboard,
escapeHtml,
getMail2925Accounts,
openConfirmModal,
refreshManagedAliasBaseEmail: () => {
setManagedAliasBaseEmailInputForProvider('2925', latestState);
},
showToast,
},
runtime: {
sendMessage: (message) => chrome.runtime.sendMessage(message),
},
constants: {
copyIcon: COPY_ICON,
displayTimeZone: DISPLAY_TIMEZONE,
expandedStorageKey: 'multipage-mail2925-list-expanded',
},
mail2925Utils: window.Mail2925Utils || {},
});
const initMail2925ListExpandedState = mail2925Manager?.initMail2925ListExpandedState
|| (() => { });
const renderMail2925Accounts = mail2925Manager?.renderMail2925Accounts
|| (() => { });
const bindMail2925Events = mail2925Manager?.bindMail2925Events
|| (() => { });
bindMail2925Events();
const icloudManager = window.SidepanelIcloudManager?.createIcloudManager({
dom: {
btnIcloudBulkDelete,
@@ -3775,6 +3947,13 @@ selectMailProvider.addEventListener('change', async () => {
if (leavingHotmail || leavingLuckmail || leavingGeneratedAlias) {
await clearRegistrationEmail({ silent: true }).catch(() => { });
}
if (nextProvider === '2925' && Boolean(inputMail2925UseAccountPool?.checked)) {
syncMail2925PoolAccountOptions(latestState);
if (!selectMail2925PoolAccount.value && getMail2925Accounts().length > 0) {
selectMail2925PoolAccount.value = String(getMail2925Accounts()[0]?.id || '');
}
await syncSelectedMail2925PoolAccount({ silent: true }).catch(() => { });
}
if (nextProvider === LUCKMAIL_PROVIDER) {
queueLuckmailPurchaseRefresh();
}
@@ -3956,6 +4135,36 @@ inputEmailPrefix.addEventListener('blur', () => {
saveSettings({ silent: true }).catch(() => {});
});
selectMail2925PoolAccount?.addEventListener('change', async () => {
try {
await syncSelectedMail2925PoolAccount();
markSettingsDirty(true);
saveSettings({ silent: true }).catch(() => {});
} catch (err) {
showToast(err.message, 'error');
}
});
inputMail2925UseAccountPool?.addEventListener('change', async () => {
const enabled = Boolean(inputMail2925UseAccountPool.checked);
syncLatestState({ mail2925UseAccountPool: enabled });
if (enabled) {
syncMail2925PoolAccountOptions(latestState);
if (!selectMail2925PoolAccount.value && getMail2925Accounts().length > 0) {
selectMail2925PoolAccount.value = String(getMail2925Accounts()[0]?.id || '');
}
try {
await syncSelectedMail2925PoolAccount({ silent: true });
} catch (err) {
showToast(err.message, 'error');
}
}
setManagedAliasBaseEmailInputForProvider('2925', latestState);
updateMailProviderUI();
markSettingsDirty(true);
saveSettings({ silent: true }).catch(() => {});
});
inputInbucketMailbox.addEventListener('input', () => {
markSettingsDirty(true);
scheduleSettingsAutoSave();
@@ -4207,6 +4416,7 @@ chrome.runtime.onMessage.addListener((message, _sender, sendResponse) => {
updateProgressCounter();
updateButtonStates();
renderHotmailAccounts();
renderMail2925Accounts();
if (isLuckmailProvider()) {
queueLuckmailPurchaseRefresh();
}
@@ -4261,6 +4471,12 @@ chrome.runtime.onMessage.addListener((message, _sender, sendResponse) => {
inputEmail.value = getCurrentHotmailEmail();
}
}
if (message.payload.currentMail2925AccountId !== undefined || message.payload.mail2925Accounts !== undefined) {
renderMail2925Accounts();
if (selectMailProvider.value === '2925') {
setManagedAliasBaseEmailInputForProvider('2925', latestState);
}
}
if (message.payload.luckmailApiKey !== undefined) {
inputLuckmailApiKey.value = message.payload.luckmailApiKey || '';
}
@@ -4421,6 +4637,7 @@ document.addEventListener('keydown', (event) => {
initializeManualStepActions();
initTheme();
initHotmailListExpandedState();
initMail2925ListExpandedState();
updateSaveButtonState();
updateConfigMenuControls();
setLocalCpaStep9Mode(DEFAULT_LOCAL_CPA_STEP9_MODE);