添加Cloudflare服务

This commit is contained in:
QLHazyCoder
2026-04-12 19:30:35 +08:00
parent 62664ee862
commit 4228888cca
6 changed files with 508 additions and 84 deletions
+217 -10
View File
@@ -36,10 +36,15 @@ const btnClearLog = document.getElementById('btn-clear-log');
const inputVpsUrl = document.getElementById('input-vps-url');
const inputVpsPassword = document.getElementById('input-vps-password');
const selectMailProvider = document.getElementById('select-mail-provider');
const selectEmailGenerator = document.getElementById('select-email-generator');
const rowInbucketHost = document.getElementById('row-inbucket-host');
const inputInbucketHost = document.getElementById('input-inbucket-host');
const rowInbucketMailbox = document.getElementById('row-inbucket-mailbox');
const inputInbucketMailbox = document.getElementById('input-inbucket-mailbox');
const rowCfDomain = document.getElementById('row-cf-domain');
const selectCfDomain = document.getElementById('select-cf-domain');
const inputCfDomain = document.getElementById('input-cf-domain');
const btnCfDomainMode = document.getElementById('btn-cf-domain-mode');
const inputRunCount = document.getElementById('input-run-count');
const inputAutoSkipFailures = document.getElementById('input-auto-skip-failures');
const inputAutoDelayEnabled = document.getElementById('input-auto-delay-enabled');
@@ -79,6 +84,7 @@ let currentAutoRun = {
let settingsDirty = false;
let settingsSaveInFlight = false;
let settingsAutoSaveTimer = null;
let cloudflareDomainEditMode = false;
let modalChoiceResolver = null;
let currentModalActions = [];
let scheduledCountdownTimer = null;
@@ -367,14 +373,96 @@ function setDefaultAutoRunButton() {
btnAutoRun.innerHTML = '<svg width="14" height="14" viewBox="0 0 24 24" fill="currentColor"><polygon points="5 3 19 12 5 21 5 3"/></svg> 自动';
}
function normalizeCloudflareDomainValue(value = '') {
let normalized = String(value || '').trim().toLowerCase();
if (!normalized) return '';
normalized = normalized.replace(/^@+/, '');
normalized = normalized.replace(/^https?:\/\//, '');
normalized = normalized.replace(/\/.*$/, '');
if (!/^[a-z0-9.-]+\.[a-z]{2,}$/.test(normalized)) {
return '';
}
return normalized;
}
function normalizeCloudflareDomains(values = []) {
const seen = new Set();
const domains = [];
for (const value of Array.isArray(values) ? values : []) {
const normalized = normalizeCloudflareDomainValue(value);
if (!normalized || seen.has(normalized)) continue;
seen.add(normalized);
domains.push(normalized);
}
return domains;
}
function getCloudflareDomainsFromState() {
const domains = normalizeCloudflareDomains(latestState?.cloudflareDomains || []);
const activeDomain = normalizeCloudflareDomainValue(latestState?.cloudflareDomain || '');
if (activeDomain && !domains.includes(activeDomain)) {
domains.unshift(activeDomain);
}
return { domains, activeDomain: activeDomain || domains[0] || '' };
}
function renderCloudflareDomainOptions(preferredDomain = '') {
const preferred = normalizeCloudflareDomainValue(preferredDomain);
const { domains, activeDomain } = getCloudflareDomainsFromState();
const selected = preferred || activeDomain;
selectCfDomain.innerHTML = '';
if (domains.length === 0) {
const option = document.createElement('option');
option.value = '';
option.textContent = '请先添加域名';
selectCfDomain.appendChild(option);
selectCfDomain.disabled = true;
selectCfDomain.value = '';
return;
}
for (const domain of domains) {
const option = document.createElement('option');
option.value = domain;
option.textContent = domain;
selectCfDomain.appendChild(option);
}
selectCfDomain.disabled = false;
selectCfDomain.value = domains.includes(selected) ? selected : domains[0];
}
function setCloudflareDomainEditMode(editing, options = {}) {
const { clearInput = false } = options;
cloudflareDomainEditMode = Boolean(editing);
selectCfDomain.style.display = cloudflareDomainEditMode ? 'none' : '';
inputCfDomain.style.display = cloudflareDomainEditMode ? '' : 'none';
btnCfDomainMode.textContent = cloudflareDomainEditMode ? '保存' : '添加';
if (cloudflareDomainEditMode) {
if (clearInput) {
inputCfDomain.value = '';
}
inputCfDomain.focus();
} else if (clearInput) {
inputCfDomain.value = '';
}
}
function collectSettingsPayload() {
const { domains, activeDomain } = getCloudflareDomainsFromState();
const selectedCloudflareDomain = normalizeCloudflareDomainValue(
!cloudflareDomainEditMode ? selectCfDomain.value : activeDomain
) || activeDomain;
return {
vpsUrl: inputVpsUrl.value.trim(),
vpsPassword: inputVpsPassword.value,
customPassword: inputPassword.value,
mailProvider: selectMailProvider.value,
emailGenerator: selectEmailGenerator.value,
inbucketHost: inputInbucketHost.value.trim(),
inbucketMailbox: inputInbucketMailbox.value.trim(),
cloudflareDomain: selectedCloudflareDomain,
cloudflareDomains: domains,
autoRunSkipFailures: inputAutoSkipFailures.checked,
autoRunDelayEnabled: inputAutoDelayEnabled.checked,
autoRunDelayMinutes: normalizeAutoDelayMinutes(inputAutoDelayMinutes.value),
@@ -551,12 +639,17 @@ async function restoreState() {
if (state.mailProvider) {
selectMailProvider.value = state.mailProvider;
}
if (state.emailGenerator) {
selectEmailGenerator.value = state.emailGenerator;
}
if (state.inbucketHost) {
inputInbucketHost.value = state.inbucketHost;
}
if (state.inbucketMailbox) {
inputInbucketMailbox.value = state.inbucketMailbox;
}
renderCloudflareDomainOptions(state.cloudflareDomain || '');
setCloudflareDomainEditMode(false, { clearInput: true });
inputAutoSkipFailures.checked = Boolean(state.autoRunSkipFailures);
inputAutoDelayEnabled.checked = Boolean(state.autoRunDelayEnabled);
inputAutoDelayMinutes.value = String(normalizeAutoDelayMinutes(state.autoRunDelayMinutes));
@@ -592,10 +685,78 @@ function syncPasswordField(state) {
inputPassword.value = state.customPassword || state.password || '';
}
function getSelectedEmailGenerator() {
return selectEmailGenerator.value === 'cloudflare' ? 'cloudflare' : 'duck';
}
function getEmailGeneratorUiCopy() {
if (getSelectedEmailGenerator() === 'cloudflare') {
return {
buttonLabel: '生成 Cloudflare',
placeholder: '点击生成 Cloudflare 邮箱,或手动粘贴邮箱',
successVerb: '生成',
label: 'Cloudflare 邮箱',
};
}
return {
buttonLabel: '获取 Duck',
placeholder: '点击获取 DuckDuckGo 邮箱,或手动粘贴邮箱',
successVerb: '获取',
label: 'Duck 邮箱',
};
}
function updateMailProviderUI() {
const useInbucket = selectMailProvider.value === 'inbucket';
rowInbucketHost.style.display = useInbucket ? '' : 'none';
rowInbucketMailbox.style.display = useInbucket ? '' : 'none';
const useCloudflare = selectEmailGenerator.value === 'cloudflare';
rowCfDomain.style.display = useCloudflare ? '' : 'none';
const { domains } = getCloudflareDomainsFromState();
if (useCloudflare) {
setCloudflareDomainEditMode(cloudflareDomainEditMode || domains.length === 0, { clearInput: false });
} else {
setCloudflareDomainEditMode(false, { clearInput: false });
}
const uiCopy = getEmailGeneratorUiCopy();
inputEmail.placeholder = uiCopy.placeholder;
if (!btnFetchEmail.disabled) {
btnFetchEmail.textContent = uiCopy.buttonLabel;
}
}
async function saveCloudflareDomainSettings(domains, activeDomain, options = {}) {
const { silent = false } = options;
const normalizedDomains = normalizeCloudflareDomains(domains);
const normalizedActiveDomain = normalizeCloudflareDomainValue(activeDomain) || normalizedDomains[0] || '';
const payload = {
cloudflareDomain: normalizedActiveDomain,
cloudflareDomains: normalizedDomains,
};
const response = await chrome.runtime.sendMessage({
type: 'SAVE_SETTING',
source: 'sidepanel',
payload,
});
if (response?.error) {
throw new Error(response.error);
}
syncLatestState({
...payload,
});
renderCloudflareDomainOptions(normalizedActiveDomain);
setCloudflareDomainEditMode(false, { clearInput: true });
markSettingsDirty(false);
updateMailProviderUI();
if (!silent) {
showToast('Cloudflare 域名已保存', 'success', 1800);
}
}
// ============================================================
@@ -773,32 +934,36 @@ function escapeHtml(text) {
return div.innerHTML;
}
async function fetchDuckEmail(options = {}) {
async function fetchGeneratedEmail(options = {}) {
const { showFailureToast = true } = options;
const defaultLabel = '获取';
const uiCopy = getEmailGeneratorUiCopy();
const defaultLabel = uiCopy.buttonLabel;
btnFetchEmail.disabled = true;
btnFetchEmail.textContent = '...';
try {
const response = await chrome.runtime.sendMessage({
type: 'FETCH_DUCK_EMAIL',
type: 'FETCH_GENERATED_EMAIL',
source: 'sidepanel',
payload: { generateNew: true },
payload: {
generateNew: true,
generator: selectEmailGenerator.value,
},
});
if (response?.error) {
throw new Error(response.error);
}
if (!response?.email) {
throw new Error('未返回 Duck 邮箱。');
throw new Error('未返回可用邮箱。');
}
inputEmail.value = response.email;
showToast(`获取 ${response.email}`, 'success', 2500);
showToast(`${uiCopy.successVerb} ${uiCopy.label}${response.email}`, 'success', 2500);
return response.email;
} catch (err) {
if (showFailureToast) {
showToast(`自动获取失败:${err.message}`, 'error');
showToast(`${uiCopy.label}${uiCopy.successVerb}失败:${err.message}`, 'error');
}
throw err;
} finally {
@@ -897,7 +1062,7 @@ document.querySelectorAll('.step-btn').forEach(btn => {
let email = inputEmail.value.trim();
if (!email) {
try {
email = await fetchDuckEmail({ showFailureToast: false });
email = await fetchGeneratedEmail({ showFailureToast: false });
} catch (err) {
showToast(`自动获取失败:${err.message},请手动粘贴邮箱后重试。`, 'warn');
return;
@@ -920,7 +1085,7 @@ document.querySelectorAll('.step-btn').forEach(btn => {
});
btnFetchEmail.addEventListener('click', async () => {
await fetchDuckEmail().catch(() => { });
await fetchGeneratedEmail().catch(() => { });
});
btnTogglePassword.addEventListener('click', () => {
@@ -1000,7 +1165,7 @@ btnAutoRun.addEventListener('click', async () => {
btnAutoContinue.addEventListener('click', async () => {
const email = inputEmail.value.trim();
if (!email) {
showToast('请先获取或粘贴 DuckDuckGo 邮箱。', 'warn');
showToast('请先获取或粘贴邮箱。', 'warn');
return;
}
autoContinueBar.style.display = 'none';
@@ -1107,6 +1272,48 @@ selectMailProvider.addEventListener('change', () => {
saveSettings({ silent: true }).catch(() => { });
});
selectEmailGenerator.addEventListener('change', () => {
updateMailProviderUI();
markSettingsDirty(true);
saveSettings({ silent: true }).catch(() => { });
});
selectCfDomain.addEventListener('change', () => {
if (selectCfDomain.disabled) {
return;
}
markSettingsDirty(true);
saveSettings({ silent: true }).catch(() => { });
});
btnCfDomainMode.addEventListener('click', async () => {
try {
if (!cloudflareDomainEditMode) {
setCloudflareDomainEditMode(true, { clearInput: true });
return;
}
const newDomain = normalizeCloudflareDomainValue(inputCfDomain.value);
if (!newDomain) {
showToast('请输入有效的 Cloudflare 域名。', 'warn');
inputCfDomain.focus();
return;
}
const { domains } = getCloudflareDomainsFromState();
await saveCloudflareDomainSettings([...domains, newDomain], newDomain);
} catch (err) {
showToast(err.message, 'error');
}
});
inputCfDomain.addEventListener('keydown', (event) => {
if (event.key === 'Enter') {
event.preventDefault();
btnCfDomainMode.click();
}
});
inputInbucketMailbox.addEventListener('input', () => {
markSettingsDirty(true);
scheduleSettingsAutoSave();