Răsfoiți Sursa

修复自定义邮箱位置及整体逻辑

QLHazyCoder 4 luni în urmă
părinte
comite
89854f17f2
4 a modificat fișierele cu 180 adăugiri și 39 ștergeri
  1. 55 3
      background.js
  2. 15 0
      sidepanel/sidepanel.css
  3. 2 1
      sidepanel/sidepanel.html
  4. 108 35
      sidepanel/sidepanel.js

+ 55 - 3
background.js

@@ -230,6 +230,7 @@ function normalizePanelMode(value = '') {
 function normalizeMailProvider(value = '') {
   const normalized = String(value || '').trim().toLowerCase();
   switch (normalized) {
+    case 'custom':
     case HOTMAIL_PROVIDER:
     case '163':
     case '163-vip':
@@ -644,6 +645,13 @@ function isHotmailProvider(stateOrProvider) {
   return provider === HOTMAIL_PROVIDER;
 }
 
+function isCustomMailProvider(stateOrProvider) {
+  const provider = typeof stateOrProvider === 'string'
+    ? stateOrProvider
+    : stateOrProvider?.mailProvider;
+  return provider === 'custom';
+}
+
 async function syncHotmailAccounts(accounts) {
   const normalized = normalizeHotmailAccounts(accounts);
   await setPersistentSettings({ hotmailAccounts: normalized });
@@ -1272,9 +1280,10 @@ function isGeneratedAliasProvider(provider) {
 }
 
 function shouldUseCustomRegistrationEmail(state = {}) {
-  return !isHotmailProvider(state)
-    && !isGeneratedAliasProvider(state.mailProvider)
-    && normalizeEmailGenerator(state.emailGenerator) === 'custom';
+  return isCustomMailProvider(state)
+    || (!isHotmailProvider(state)
+      && !isGeneratedAliasProvider(state.mailProvider)
+      && normalizeEmailGenerator(state.emailGenerator) === 'custom');
 }
 
 function buildGeneratedAliasEmail(state) {
@@ -4667,6 +4676,9 @@ async function executeStep3(state) {
 
 function getMailConfig(state) {
   const provider = state.mailProvider || 'qq';
+  if (provider === 'custom') {
+    return { provider: 'custom', label: '自定义邮箱' };
+  }
   if (provider === HOTMAIL_PROVIDER) {
     return { provider: HOTMAIL_PROVIDER, label: 'Hotmail(远程/本地)' };
   }
@@ -4728,6 +4740,36 @@ function getVerificationCodeLabel(step) {
   return step === 4 ? '注册' : '登录';
 }
 
+async function confirmCustomVerificationStepBypass(step) {
+  const verificationLabel = getVerificationCodeLabel(step);
+  await addLog(`步骤 ${step}:当前为自定义邮箱模式,请手动在页面中输入${verificationLabel}验证码并进入下一页面。`, 'warn');
+
+  let response = null;
+  try {
+    response = await chrome.runtime.sendMessage({
+      type: 'REQUEST_CUSTOM_VERIFICATION_BYPASS_CONFIRMATION',
+      payload: { step },
+    });
+  } catch {
+    throw new Error(`步骤 ${step}:无法打开确认弹窗,请先保持侧边栏打开后重试。`);
+  }
+
+  if (response?.error) {
+    throw new Error(response.error);
+  }
+  if (!response?.confirmed) {
+    throw new Error(`步骤 ${step}:已取消手动${verificationLabel}验证码确认。`);
+  }
+
+  await setState({
+    lastEmailTimestamp: null,
+    signupVerificationRequestedAt: null,
+    loginVerificationRequestedAt: null,
+  });
+  await setStepStatus(step, 'skipped');
+  await addLog(`步骤 ${step}:已确认手动完成${verificationLabel}验证码输入,当前步骤已跳过。`, 'warn');
+}
+
 function getVerificationPollPayload(step, state, overrides = {}) {
   if (step === 4) {
     return {
@@ -5004,6 +5046,11 @@ async function executeStep4(state) {
     return;
   }
 
+  if (shouldUseCustomRegistrationEmail(state)) {
+    await confirmCustomVerificationStepBypass(4);
+    return;
+  }
+
   throwIfStopped();
   if (mail.provider === HOTMAIL_PROVIDER) {
     await addLog(`步骤 4:正在通过 ${mail.label} 轮询验证码...`);
@@ -5134,6 +5181,11 @@ async function runStep7Attempt(state) {
     throw new Error(prepareResult.error);
   }
 
+  if (shouldUseCustomRegistrationEmail(state)) {
+    await confirmCustomVerificationStepBypass(7);
+    return;
+  }
+
   throwIfStopped();
   if (mail.provider === HOTMAIL_PROVIDER) {
     await addLog(`步骤 7:正在通过 ${mail.label} 轮询验证码...`);

+ 15 - 0
sidepanel/sidepanel.css

@@ -1474,6 +1474,21 @@ header {
   margin-bottom: 14px;
 }
 
+.modal-alert {
+  font-size: 12px;
+  line-height: 1.5;
+  margin-bottom: 14px;
+  padding: 9px 10px;
+  border-radius: 10px;
+  border: 1px solid transparent;
+}
+
+.modal-alert.is-danger {
+  color: var(--red);
+  background: var(--red-soft);
+  border-color: rgba(220, 38, 38, 0.18);
+}
+
 .modal-option-row {
   margin-bottom: 14px;
 }

+ 2 - 1
sidepanel/sidepanel.html

@@ -152,6 +152,7 @@
         <span class="data-label">邮箱服务</span>
         <div class="data-inline">
           <select id="select-mail-provider" class="data-select">
+            <option value="custom">自定义邮箱</option>
             <option value="hotmail-api">Hotmail(本地助手)</option>
             <option value="163">163 邮箱 (mail.163.com)</option>
             <option value="163-vip">163 VIP 邮箱 (vip.163.com)</option>
@@ -165,7 +166,6 @@
       <div class="data-row" id="row-email-generator">
         <span class="data-label">邮箱生成</span>
         <select id="select-email-generator" class="data-select">
-          <option value="custom">自定义邮箱</option>
           <option value="duck">DuckDuckGo</option>
           <option value="cloudflare">Cloudflare</option>
         </select>
@@ -414,6 +414,7 @@
         <button id="btn-auto-start-close" class="modal-close" type="button" aria-label="关闭">×</button>
       </div>
       <p id="auto-start-message" class="modal-message">检测到已有流程进度,选择继续当前还是重新开始。</p>
+      <p id="auto-start-alert" class="modal-alert" hidden></p>
       <div id="modal-option-row" class="modal-option-row" hidden>
         <label class="modal-option-label" for="modal-option-input">
           <input type="checkbox" id="modal-option-input" />

+ 108 - 35
sidepanel/sidepanel.js

@@ -108,6 +108,7 @@ const inputAutoStepDelaySeconds = document.getElementById('input-auto-step-delay
 const autoStartModal = document.getElementById('auto-start-modal');
 const autoStartTitle = autoStartModal?.querySelector('.modal-title');
 const autoStartMessage = document.getElementById('auto-start-message');
+const autoStartAlert = document.getElementById('auto-start-alert');
 const modalOptionRow = document.getElementById('modal-option-row');
 const modalOptionInput = document.getElementById('modal-option-input');
 const modalOptionText = document.getElementById('modal-option-text');
@@ -255,6 +256,16 @@ function resetActionModalOption() {
   modalOptionText.textContent = '不再提示';
 }
 
+function resetActionModalAlert() {
+  if (!autoStartAlert) {
+    return;
+  }
+
+  autoStartAlert.hidden = true;
+  autoStartAlert.textContent = '';
+  autoStartAlert.className = 'modal-alert';
+}
+
 function resetActionModalButtons() {
   const buttons = [btnAutoStartCancel, btnAutoStartRestart, btnAutoStartContinue];
   buttons.forEach((button) => {
@@ -297,6 +308,21 @@ function configureActionModalOption(option) {
   modalOptionText.textContent = option.label || '不再提示';
 }
 
+function configureActionModalAlert(alert) {
+  if (!autoStartAlert) {
+    return;
+  }
+
+  if (!alert?.text) {
+    resetActionModalAlert();
+    return;
+  }
+
+  autoStartAlert.hidden = false;
+  autoStartAlert.textContent = alert.text;
+  autoStartAlert.className = `modal-alert${alert.tone === 'danger' ? ' is-danger' : ''}`;
+}
+
 function resolveModalChoice(choice) {
   const optionChecked = Boolean(modalOptionInput?.checked);
   const result = typeof modalResultBuilder === 'function'
@@ -308,13 +334,14 @@ function resolveModalChoice(choice) {
   }
   modalResultBuilder = null;
   resetActionModalButtons();
+  resetActionModalAlert();
   resetActionModalOption();
   if (autoStartModal) {
     autoStartModal.hidden = true;
   }
 }
 
-function openActionModal({ title, message, actions, option, buildResult }) {
+function openActionModal({ title, message, actions, option, alert, buildResult }) {
   if (!autoStartModal) {
     return Promise.resolve(null);
   }
@@ -334,6 +361,7 @@ function openActionModal({ title, message, actions, option, buildResult }) {
   buttonSlots.forEach((button, index) => {
     configureActionModalButton(button, currentModalActions[index]);
   });
+  configureActionModalAlert(alert);
   configureActionModalOption(option);
   autoStartModal.hidden = false;
 
@@ -358,10 +386,11 @@ function openAutoStartChoiceDialog(startStep, options = {}) {
   });
 }
 
-async function openConfirmModal({ title, message, confirmLabel = '确认', confirmVariant = 'btn-primary' }) {
+async function openConfirmModal({ title, message, confirmLabel = '确认', confirmVariant = 'btn-primary', alert = null }) {
   const choice = await openActionModal({
     title,
     message,
+    alert,
     actions: [
       { id: null, label: '取消', variant: 'btn-ghost' },
       { id: 'confirm', label: confirmLabel, variant: confirmVariant },
@@ -375,6 +404,7 @@ async function openConfirmModalWithOption({
   message,
   confirmLabel = '确认',
   confirmVariant = 'btn-primary',
+  alert = null,
   optionLabel = '不再提示',
   optionChecked = false,
   optionDisabled = false,
@@ -382,6 +412,7 @@ async function openConfirmModalWithOption({
   const result = await openActionModal({
     title,
     message,
+    alert,
     actions: [
       { id: null, label: '取消', variant: 'btn-ghost' },
       { id: 'confirm', label: confirmLabel, variant: confirmVariant },
@@ -1063,7 +1094,7 @@ function applyAutoRunStatus(payload = currentAutoRun) {
   btnAutoRun.disabled = currentAutoRun.autoRunning;
   btnFetchEmail.disabled = locked
     || usesGeneratedAliasMailProvider(selectMailProvider.value)
-    || isCustomEmailGeneratorSelected();
+    || isCustomMailProvider();
   inputEmail.disabled = locked;
   inputAutoSkipFailures.disabled = scheduled;
 
@@ -1102,7 +1133,7 @@ function applyAutoRunStatus(payload = currentAutoRun) {
       inputEmail.disabled = false;
       if (!locked) {
         btnFetchEmail.disabled = usesGeneratedAliasMailProvider(selectMailProvider.value)
-          || isCustomEmailGeneratorSelected();
+          || isCustomMailProvider();
       }
       break;
   }
@@ -1163,8 +1194,15 @@ function applySettingsState(state) {
   inputSub2ApiEmail.value = state?.sub2apiEmail || '';
   inputSub2ApiPassword.value = state?.sub2apiPassword || '';
   inputSub2ApiGroup.value = state?.sub2apiGroupName || '';
-  selectMailProvider.value = state?.mailProvider || '163';
-  selectEmailGenerator.value = state?.emailGenerator || 'duck';
+  const restoredMailProvider = isCustomMailProvider(state?.mailProvider)
+    || ['hotmail-api', '163', '163-vip', 'qq', 'inbucket', '2925'].includes(String(state?.mailProvider || '').trim())
+    ? String(state?.mailProvider || '163').trim()
+    : (String(state?.emailGenerator || '').trim().toLowerCase() === 'custom'
+      || String(state?.emailGenerator || '').trim().toLowerCase() === 'manual'
+      ? 'custom'
+      : '163');
+  selectMailProvider.value = restoredMailProvider;
+  selectEmailGenerator.value = String(state?.emailGenerator || '').trim().toLowerCase() === 'cloudflare' ? 'cloudflare' : 'duck';
   inputEmailPrefix.value = state?.emailPrefix || '';
   inputInbucketHost.value = state?.inbucketHost || '';
   inputInbucketMailbox.value = state?.inbucketMailbox || '';
@@ -1433,11 +1471,12 @@ function syncPasswordField(state) {
   inputPassword.value = state.customPassword || state.password || '';
 }
 
+function isCustomMailProvider(provider = selectMailProvider.value) {
+  return String(provider || '').trim().toLowerCase() === 'custom';
+}
+
 function getSelectedEmailGenerator() {
   const generator = String(selectEmailGenerator.value || '').trim().toLowerCase();
-  if (generator === 'custom' || generator === 'manual') {
-    return 'custom';
-  }
   if (generator === 'cloudflare') {
     return 'cloudflare';
   }
@@ -1445,15 +1484,6 @@ function getSelectedEmailGenerator() {
 }
 
 function getEmailGeneratorUiCopy() {
-  if (getSelectedEmailGenerator() === 'custom') {
-    return {
-      buttonLabel: '自定义邮箱',
-      placeholder: '请填写本轮要使用的注册邮箱',
-      successVerb: '使用',
-      label: '自定义邮箱',
-    };
-  }
-
   if (getSelectedEmailGenerator() === 'cloudflare') {
     return {
       buttonLabel: '生成',
@@ -1471,8 +1501,25 @@ function getEmailGeneratorUiCopy() {
   };
 }
 
-function isCustomEmailGeneratorSelected() {
-  return getSelectedEmailGenerator() === 'custom';
+function getCustomMailProviderUiCopy() {
+  return {
+    buttonLabel: '自定义邮箱',
+    placeholder: '请填写本轮要使用的注册邮箱',
+    successVerb: '使用',
+    label: '自定义邮箱',
+  };
+}
+
+function getCustomVerificationPromptCopy(step) {
+  const verificationLabel = step === 4 ? '注册验证码' : '登录验证码';
+  return {
+    title: `手动处理${verificationLabel}`,
+    message: `当前邮箱服务为“自定义邮箱”。请先在页面中手动输入${verificationLabel},并确认已经进入下一页面后,再点击确认。`,
+    alert: {
+      text: `点击确认后会跳过步骤 ${step}。`,
+      tone: 'danger',
+    },
+  };
 }
 
 function getHotmailAccounts(state = latestState) {
@@ -1492,6 +1539,12 @@ function getMailProviderLoginConfig(provider = selectMailProvider.value) {
   return MAIL_PROVIDER_LOGIN_CONFIGS[String(provider || '').trim()] || null;
 }
 
+function getMailProviderLoginUrl(provider = selectMailProvider.value) {
+  const config = getMailProviderLoginConfig(provider);
+  const url = String(config?.url || '').trim();
+  return url ? url : '';
+}
+
 function isCurrentEmailManagedByHotmail(state = latestState) {
   const hotmailEmail = getCurrentHotmailEmail(state);
   if (!hotmailEmail) {
@@ -1525,8 +1578,9 @@ function updateMailLoginButtonState() {
   }
 
   const config = getMailProviderLoginConfig();
-  btnMailLogin.disabled = !config;
-  btnMailLogin.title = config ? `打开 ${config.label} 登录页` : '当前邮箱服务无需网页登录';
+  const loginUrl = getMailProviderLoginUrl();
+  btnMailLogin.disabled = !loginUrl;
+  btnMailLogin.title = loginUrl ? `打开 ${config.label} 登录页` : '当前邮箱服务没有可跳转的登录页';
 }
 
 function getHotmailAccountsByUsage(mode = 'all', state = latestState) {
@@ -1738,8 +1792,8 @@ function updateMailProviderUI() {
   const useGeneratedAlias = usesGeneratedAliasMailProvider(selectMailProvider.value);
   const useInbucket = selectMailProvider.value === 'inbucket';
   const useHotmail = selectMailProvider.value === 'hotmail-api';
-  const useCustomEmail = !useGeneratedAlias && !useHotmail && isCustomEmailGeneratorSelected();
-  const useEmailGenerator = !useHotmail && !useGeneratedAlias;
+  const useCustomEmail = isCustomMailProvider();
+  const useEmailGenerator = !useHotmail && !useGeneratedAlias && !useCustomEmail;
   updateMailLoginButtonState();
   rowEmailPrefix.style.display = useGeneratedAlias ? '' : 'none';
   const hotmailServiceMode = getSelectedHotmailServiceMode();
@@ -1763,7 +1817,7 @@ function updateMailProviderUI() {
   }
   labelEmailPrefix.textContent = '邮箱前缀';
   inputEmailPrefix.placeholder = '例如 abc';
-  selectEmailGenerator.disabled = useHotmail || useGeneratedAlias;
+  selectEmailGenerator.disabled = useHotmail || useGeneratedAlias || useCustomEmail;
   if (rowHotmailServiceMode) {
     rowHotmailServiceMode.style.display = useHotmail ? '' : 'none';
   }
@@ -1775,7 +1829,7 @@ function updateMailProviderUI() {
   }
   btnFetchEmail.hidden = useHotmail || useCustomEmail;
   inputEmail.readOnly = useHotmail || useGeneratedAlias;
-  const uiCopy = getEmailGeneratorUiCopy();
+  const uiCopy = useCustomEmail ? getCustomMailProviderUiCopy() : getEmailGeneratorUiCopy();
   inputEmail.placeholder = useHotmail
     ? '由 Hotmail 账号池自动分配'
     : (use2925 ? '步骤 3 自动生成 2925 邮箱并回填' : uiCopy.placeholder);
@@ -2042,8 +2096,8 @@ function escapeHtml(text) {
 async function fetchGeneratedEmail(options = {}) {
   const { showFailureToast = true } = options;
   const uiCopy = getEmailGeneratorUiCopy();
-  if (isCustomEmailGeneratorSelected()) {
-    throw new Error('当前邮箱生成方式为自定义邮箱,请直接填写注册邮箱。');
+  if (isCustomMailProvider()) {
+    throw new Error('当前邮箱服务为自定义邮箱,请直接填写注册邮箱。');
   }
   const defaultLabel = uiCopy.buttonLabel;
   btnFetchEmail.disabled = true;
@@ -2344,8 +2398,8 @@ document.querySelectorAll('.step-btn').forEach(btn => {
         } else {
           let email = inputEmail.value.trim();
           if (!email) {
-            if (isCustomEmailGeneratorSelected()) {
-              showToast('当前邮箱生成方式为自定义邮箱,请先填写注册邮箱后再执行第 3 步。', 'warn');
+            if (isCustomMailProvider()) {
+              showToast('当前邮箱服务为自定义邮箱,请先填写注册邮箱后再执行第 3 步。', 'warn');
               return;
             }
             try {
@@ -2373,7 +2427,7 @@ document.querySelectorAll('.step-btn').forEach(btn => {
 });
 
 btnFetchEmail.addEventListener('click', async () => {
-  if (selectMailProvider.value === 'hotmail-api' || isCustomEmailGeneratorSelected()) {
+  if (selectMailProvider.value === 'hotmail-api' || isCustomMailProvider()) {
     return;
   }
   await fetchGeneratedEmail().catch(() => { });
@@ -2627,12 +2681,13 @@ btnToggleVpsPassword.addEventListener('click', () => {
 
 btnMailLogin?.addEventListener('click', async () => {
   const config = getMailProviderLoginConfig();
-  if (!config) {
+  const loginUrl = getMailProviderLoginUrl();
+  if (!config || !loginUrl) {
     return;
   }
 
   try {
-    await chrome.tabs.create({ url: config.url, active: true });
+    await chrome.tabs.create({ url: loginUrl, active: true });
   } catch (err) {
     showToast(`打开${config.label}失败:${err.message}`, 'error');
   }
@@ -2783,7 +2838,7 @@ btnAutoContinue.addEventListener('click', async () => {
   const email = inputEmail.value.trim();
   if (!email) {
     showToast(
-      isCustomEmailGeneratorSelected() ? '请先填写自定义注册邮箱。' : '请先获取或粘贴邮箱。',
+      isCustomMailProvider() ? '请先填写自定义注册邮箱。' : '请先获取或粘贴邮箱。',
       'warn'
     );
     return;
@@ -3110,8 +3165,26 @@ inputAutoStepDelaySeconds.addEventListener('blur', () => {
 // Listen for Background broadcasts
 // ============================================================
 
-chrome.runtime.onMessage.addListener((message) => {
+chrome.runtime.onMessage.addListener((message, _sender, sendResponse) => {
   switch (message.type) {
+    case 'REQUEST_CUSTOM_VERIFICATION_BYPASS_CONFIRMATION': {
+      (async () => {
+        const step = Number(message.payload?.step);
+        const promptCopy = getCustomVerificationPromptCopy(step);
+        const confirmed = await openConfirmModal({
+          title: promptCopy.title,
+          message: promptCopy.message,
+          confirmLabel: '确认跳过',
+          confirmVariant: 'btn-danger',
+          alert: promptCopy.alert,
+        });
+        sendResponse({ confirmed });
+      })().catch((err) => {
+        sendResponse({ error: err.message });
+      });
+      return true;
+    }
+
     case 'LOG_ENTRY':
       appendLog(message.payload);
       if (message.payload.level === 'error') {