feat: 支持从 CFTE 同步 Temp 域名列表

This commit is contained in:
QLHazyCoder
2026-05-15 02:52:48 +08:00
parent d681972724
commit a7ea261093
3 changed files with 365 additions and 27 deletions
+2 -2
View File
@@ -725,14 +725,14 @@
<div id="temp-email-domain-picker" class="editable-list-picker">
<button id="btn-temp-email-domain-menu" class="data-select editable-list-trigger" type="button"
aria-haspopup="listbox" aria-expanded="false">
<span id="temp-email-domain-current" class="truncate">请先添加域名</span>
<span id="temp-email-domain-current" class="truncate">请先更新域名</span>
<span class="editable-list-caret" aria-hidden="true"></span>
</button>
<div id="temp-email-domain-menu" class="editable-list-menu" role="listbox" hidden></div>
</div>
<input type="text" id="input-temp-email-domain" class="data-input" placeholder="例如 mail.example.com"
style="display:none;" />
<button id="btn-temp-email-domain-mode" class="btn btn-outline btn-sm" type="button">添加</button>
<button id="btn-temp-email-domain-mode" class="btn btn-outline btn-sm" type="button">更新</button>
</div>
</div>
</div>
+153 -25
View File
@@ -1310,7 +1310,7 @@ const tempEmailDomainPicker = createEditableListPicker({
trigger: btnTempEmailDomainMenu,
current: tempEmailDomainCurrent,
menu: tempEmailDomainMenu,
emptyLabel: '请先添加域名',
emptyLabel: '请先更新域名',
itemLabel: '域名',
normalizeItems: normalizeCloudflareTempEmailDomains,
normalizeValue: normalizeCloudflareTempEmailDomainValue,
@@ -2981,16 +2981,11 @@ function setCloudflareDomainEditMode(editing, options = {}) {
function setCloudflareTempEmailDomainEditMode(editing, options = {}) {
const { clearInput = false } = options;
cloudflareTempEmailDomainEditMode = Boolean(editing);
tempEmailDomainPicker.setVisible(!cloudflareTempEmailDomainEditMode);
inputTempEmailDomain.style.display = cloudflareTempEmailDomainEditMode ? '' : 'none';
btnTempEmailDomainMode.textContent = cloudflareTempEmailDomainEditMode ? '保存' : '添加';
if (cloudflareTempEmailDomainEditMode) {
if (clearInput) {
inputTempEmailDomain.value = '';
}
inputTempEmailDomain.focus();
} else if (clearInput) {
cloudflareTempEmailDomainEditMode = false;
tempEmailDomainPicker.setVisible(true);
inputTempEmailDomain.style.display = 'none';
btnTempEmailDomainMode.textContent = '更新';
if (clearInput) {
inputTempEmailDomain.value = '';
}
}
@@ -10427,6 +10422,152 @@ async function saveCloudflareTempEmailDomainSettings(domains, activeDomain, opti
}
}
function joinCloudflareTempEmailSettingsUrl(baseUrl, path) {
const normalizedBaseUrl = normalizeCloudflareTempEmailBaseUrlValue(baseUrl);
const normalizedPath = String(path || '').trim();
if (!normalizedBaseUrl || !normalizedPath) {
return normalizedBaseUrl || '';
}
return `${normalizedBaseUrl}${normalizedPath.startsWith('/') ? '' : '/'}${normalizedPath}`;
}
function buildCloudflareTempEmailSyncHeaders(options = {}) {
const { includeAdminAuth = false } = options;
const headers = {
Accept: 'application/json',
};
const customAuth = String(inputTempEmailCustomAuth?.value || '').trim();
if (customAuth) {
headers['x-custom-auth'] = customAuth;
}
if (includeAdminAuth) {
const adminAuth = String(inputTempEmailAdminAuth?.value || '').trim();
if (adminAuth) {
headers['x-admin-auth'] = adminAuth;
}
}
return headers;
}
async function requestCloudflareTempEmailSyncPayload(baseUrl, path, options = {}) {
const url = joinCloudflareTempEmailSettingsUrl(baseUrl, path);
if (!url) {
throw new Error('Cloudflare Temp Email 服务地址为空或格式无效。');
}
const response = await fetch(url, {
cache: 'no-store',
headers: buildCloudflareTempEmailSyncHeaders(options),
});
const rawText = await response.text();
let payload = rawText;
try {
payload = rawText ? JSON.parse(rawText) : {};
} catch {
payload = rawText;
}
if (!response.ok) {
const message = typeof payload === 'object' && payload
? (payload.message || payload.error || payload.msg)
: '';
throw new Error(message || `HTTP ${response.status}`);
}
return payload;
}
function mergeCloudflareTempEmailDomains(existingDomains, fetchedDomains) {
const mergedDomains = [];
const seen = new Set();
for (const domain of normalizeCloudflareTempEmailDomains(fetchedDomains)) {
if (seen.has(domain)) continue;
seen.add(domain);
mergedDomains.push(domain);
}
for (const domain of normalizeCloudflareTempEmailDomains(existingDomains)) {
if (seen.has(domain)) continue;
seen.add(domain);
mergedDomains.push(domain);
}
return mergedDomains;
}
async function fetchCloudflareTempEmailAvailableDomains(baseUrl) {
let openSettingsError = null;
try {
const payload = await requestCloudflareTempEmailSyncPayload(baseUrl, '/open_api/settings');
const domains = normalizeCloudflareTempEmailDomains(payload?.domains || []);
if (domains.length) {
return {
domains,
source: 'open_api/settings',
};
}
openSettingsError = new Error('公开设置未返回可用域名。');
} catch (error) {
openSettingsError = error;
}
const adminAuth = String(inputTempEmailAdminAuth?.value || '').trim();
if (!adminAuth) {
throw openSettingsError || new Error('未获取到可用域名。');
}
const payload = await requestCloudflareTempEmailSyncPayload(baseUrl, '/admin/worker/configs', {
includeAdminAuth: true,
});
const domains = normalizeCloudflareTempEmailDomains(payload?.DOMAINS || []);
if (!domains.length) {
throw openSettingsError || new Error('管理配置未返回可用域名。');
}
return {
domains,
source: 'admin/worker/configs',
};
}
async function syncCloudflareTempEmailDomainsFromService() {
const normalizedBaseUrl = normalizeCloudflareTempEmailBaseUrlValue(inputTempEmailBaseUrl?.value || '');
if (!normalizedBaseUrl) {
throw new Error('请先填写有效的 Cloudflare Temp Email 服务地址。');
}
const previousButtonText = btnTempEmailDomainMode.textContent;
btnTempEmailDomainMode.disabled = true;
btnTempEmailDomainMode.textContent = '更新中...';
try {
const { domains: fetchedDomains, source } = await fetchCloudflareTempEmailAvailableDomains(normalizedBaseUrl);
const { domains: existingDomains, activeDomain } = getCloudflareTempEmailDomainsFromState();
const currentDomain = normalizeCloudflareTempEmailDomainValue(selectTempEmailDomain.value || activeDomain);
const mergedDomains = mergeCloudflareTempEmailDomains(existingDomains, fetchedDomains);
const nextActiveDomain = mergedDomains.includes(currentDomain)
? currentDomain
: (fetchedDomains[0] || mergedDomains[0] || '');
const existingSet = new Set(normalizeCloudflareTempEmailDomains(existingDomains));
const addedCount = fetchedDomains.filter((domain) => !existingSet.has(domain)).length;
await saveCloudflareTempEmailDomainSettings(mergedDomains, nextActiveDomain, { silent: true });
if (addedCount > 0) {
showToast(`已更新 Cloudflare Temp Email 域名,新增 ${addedCount} 个。`, 'success', 2200);
} else {
showToast('已同步 Cloudflare Temp Email 域名,暂无新增项。', 'info', 2200);
}
return {
domains: mergedDomains,
activeDomain: nextActiveDomain,
source,
};
} catch (error) {
const message = error?.message || String(error || '未知错误');
throw new Error(`更新 Cloudflare Temp Email 域名失败:${message}`);
} finally {
btnTempEmailDomainMode.disabled = false;
btnTempEmailDomainMode.textContent = previousButtonText || '更新';
}
}
async function handleDeleteCloudflareDomain(domain) {
const targetDomain = normalizeCloudflareDomainValue(domain);
if (!targetDomain) {
@@ -12736,20 +12877,7 @@ btnCfDomainMode.addEventListener('click', async () => {
btnTempEmailDomainMode.addEventListener('click', async () => {
try {
if (!cloudflareTempEmailDomainEditMode) {
setCloudflareTempEmailDomainEditMode(true, { clearInput: true });
return;
}
const newDomain = normalizeCloudflareTempEmailDomainValue(inputTempEmailDomain.value);
if (!newDomain) {
showToast('请输入有效的 Cloudflare Temp Email 域名。', 'warn');
inputTempEmailDomain.focus();
return;
}
const { domains } = getCloudflareTempEmailDomainsFromState();
await saveCloudflareTempEmailDomainSettings([...domains, newDomain], newDomain);
await syncCloudflareTempEmailDomainsFromService();
} catch (err) {
showToast(err.message, 'error');
}