Răsfoiți Sursa

feat: 重构优化2925邮箱和gmail邮箱逻辑

QLHazyCoder 4 luni în urmă
părinte
comite
511cfcfcc7

+ 270 - 6
background.js

@@ -1,6 +1,7 @@
 // background.js — Service Worker: orchestration, state, tab management, message routing
 
 importScripts(
+  'managed-alias-utils.js',
   'background/account-run-history.js',
   'background/panel-bridge.js',
   'background/generated-email-helpers.js',
@@ -215,6 +216,8 @@ const PERSISTED_SETTING_DEFAULTS = {
   icloudHostPreference: 'auto',
   accountRunHistoryTextEnabled: false,
   accountRunHistoryHelperBaseUrl: DEFAULT_ACCOUNT_RUN_HISTORY_HELPER_BASE_URL,
+  gmailBaseEmail: '',
+  mail2925BaseEmail: '',
   emailPrefix: '',
   inbucketHost: '',
   inbucketMailbox: '',
@@ -780,6 +783,8 @@ function normalizePersistentSettingValue(key, value) {
       return normalizeIcloudHost(value) || 'auto';
     case 'accountRunHistoryHelperBaseUrl':
       return normalizeAccountRunHistoryHelperBaseUrl(value);
+    case 'gmailBaseEmail':
+    case 'mail2925BaseEmail':
     case 'emailPrefix':
       return String(value || '').trim();
     case 'inbucketHost':
@@ -1928,13 +1933,11 @@ function isGeneratedAliasProvider(stateOrProvider, mail2925Mode = undefined) {
   const provider = typeof stateOrProvider === 'string'
     ? stateOrProvider
     : stateOrProvider?.mailProvider;
-  if (provider === GMAIL_PROVIDER) {
-    return true;
+  const utils = (typeof self !== 'undefined' ? self : globalThis).MultiPageManagedAliasUtils || null;
+  if (utils?.isManagedAliasProvider) {
+    return utils.isManagedAliasProvider(provider);
   }
-  const resolvedMail2925Mode = mail2925Mode !== undefined
-    ? normalizeMail2925Mode(mail2925Mode)
-    : getMail2925Mode(stateOrProvider);
-  return provider === '2925' && resolvedMail2925Mode === MAIL_2925_MODE_PROVIDE;
+  return provider === GMAIL_PROVIDER || provider === '2925';
 }
 
 function shouldUseCustomRegistrationEmail(state = {}) {
@@ -1970,6 +1973,163 @@ function buildGeneratedAliasEmail(state) {
   throw new Error(`未支持的别名邮箱类型:${provider}`);
 }
 
+function getManagedAliasUtils() {
+  return (typeof self !== 'undefined' ? self : globalThis).MultiPageManagedAliasUtils || null;
+}
+
+function parseGmailBaseEmail(rawValue) {
+  const utils = getManagedAliasUtils();
+  if (utils?.parseManagedAliasBaseEmail) {
+    return utils.parseManagedAliasBaseEmail(rawValue, GMAIL_PROVIDER);
+  }
+
+  const value = String(rawValue || '').trim().toLowerCase();
+  const match = value.match(/^([^@\s+]+)@((?:gmail|googlemail)\.com)$/i);
+  if (!match) return null;
+  return {
+    localPart: match[1],
+    domain: match[2].toLowerCase(),
+  };
+}
+
+function parseManagedAliasBaseEmail(rawValue, provider) {
+  const utils = getManagedAliasUtils();
+  if (utils?.parseManagedAliasBaseEmail) {
+    return utils.parseManagedAliasBaseEmail(rawValue, provider);
+  }
+
+  if (provider === GMAIL_PROVIDER) {
+    return parseGmailBaseEmail(rawValue);
+  }
+
+  const value = String(rawValue || '').trim().toLowerCase();
+  const match = value.match(/^([^@\s+]+)@(2925\.com)$/i);
+  if (!match) return null;
+  return {
+    localPart: match[1],
+    domain: match[2].toLowerCase(),
+  };
+}
+
+function isManagedAliasEmail(value, provider, baseEmail = '') {
+  const utils = getManagedAliasUtils();
+  if (utils?.isManagedAliasEmail) {
+    return utils.isManagedAliasEmail(value, provider, baseEmail);
+  }
+
+  const normalizedValue = String(value || '').trim().toLowerCase();
+  if (!normalizedValue) return false;
+  const parsedEmail = normalizedValue.match(/^([^@\s]+)@([^@\s]+\.[^@\s]+)$/);
+  if (!parsedEmail) return false;
+
+  const candidateLocalPart = parsedEmail[1];
+  const candidateDomain = parsedEmail[2];
+  if (provider === GMAIL_PROVIDER) {
+    if (!/^(?:gmail|googlemail)\.com$/i.test(candidateDomain)) {
+      return false;
+    }
+    const parsedBaseEmail = parseManagedAliasBaseEmail(baseEmail, provider);
+    if (!parsedBaseEmail) {
+      return true;
+    }
+    return candidateDomain === parsedBaseEmail.domain
+      && candidateLocalPart.split('+')[0] === parsedBaseEmail.localPart;
+  }
+
+  if (provider !== '2925' || candidateDomain !== '2925.com') {
+    return false;
+  }
+
+  const parsedBaseEmail = parseManagedAliasBaseEmail(baseEmail, provider);
+  if (!parsedBaseEmail) {
+    return true;
+  }
+
+  return candidateLocalPart === parsedBaseEmail.localPart || candidateLocalPart.startsWith(parsedBaseEmail.localPart);
+}
+
+function getManagedAliasBaseEmail(state = {}, provider = state?.mailProvider) {
+  const normalizedProvider = String(provider || '').trim().toLowerCase();
+  const legacyEmailPrefix = String(state?.emailPrefix || '').trim();
+  if (normalizedProvider === GMAIL_PROVIDER) {
+    const gmailBaseEmail = String(state?.gmailBaseEmail || '').trim();
+    if (gmailBaseEmail) {
+      return gmailBaseEmail;
+    }
+    return parseManagedAliasBaseEmail(legacyEmailPrefix, normalizedProvider) ? legacyEmailPrefix : '';
+  }
+
+  if (normalizedProvider === '2925') {
+    const mail2925BaseEmail = String(state?.mail2925BaseEmail || '').trim();
+    if (mail2925BaseEmail) {
+      return mail2925BaseEmail;
+    }
+    return parseManagedAliasBaseEmail(legacyEmailPrefix, normalizedProvider) ? legacyEmailPrefix : '';
+  }
+
+  return '';
+}
+
+function isGeneratedAliasProvider(stateOrProvider, mail2925Mode = undefined) {
+  const provider = typeof stateOrProvider === 'string'
+    ? stateOrProvider
+    : stateOrProvider?.mailProvider;
+  const utils = getManagedAliasUtils();
+  if (utils?.isManagedAliasProvider) {
+    return utils.isManagedAliasProvider(provider);
+  }
+  return provider === GMAIL_PROVIDER || provider === '2925';
+}
+
+function shouldUseCustomRegistrationEmail(state = {}) {
+  return isCustomMailProvider(state)
+    || (!isHotmailProvider(state)
+      && !isGeneratedAliasProvider(state)
+      && normalizeEmailGenerator(state.emailGenerator) === 'custom');
+}
+
+function isReusableGeneratedAliasEmail(state = {}, email = state?.email) {
+  if (!isGeneratedAliasProvider(state)) {
+    return false;
+  }
+
+  return isManagedAliasEmail(email, state?.mailProvider, getManagedAliasBaseEmail(state));
+}
+
+function buildGeneratedAliasEmail(state) {
+  const provider = state.mailProvider || '163';
+  const baseEmail = getManagedAliasBaseEmail(state, provider);
+  const baseLabel = provider === GMAIL_PROVIDER ? 'Gmail 原邮箱' : '2925 基邮箱';
+  const exampleEmail = provider === GMAIL_PROVIDER ? 'name@gmail.com' : 'name@2925.com';
+
+  if (!baseEmail) {
+    throw new Error(`${baseLabel}未设置,请先在侧边栏填写,或直接在“注册邮箱”中手动填写完整邮箱。`);
+  }
+
+  if (!parseManagedAliasBaseEmail(baseEmail, provider)) {
+    throw new Error(`${baseLabel}格式不正确,请填写类似 ${exampleEmail} 的地址。`);
+  }
+
+  const utils = getManagedAliasUtils();
+  if (utils?.buildManagedAliasEmail) {
+    return utils.buildManagedAliasEmail(
+      provider,
+      baseEmail,
+      provider === GMAIL_PROVIDER ? generateRandomWordAliasTag() : generateRandomSuffix(6)
+    );
+  }
+
+  const parsedBaseEmail = parseManagedAliasBaseEmail(baseEmail, provider);
+  if (provider === GMAIL_PROVIDER) {
+    return `${parsedBaseEmail.localPart}+${generateRandomWordAliasTag()}@${parsedBaseEmail.domain}`;
+  }
+  if (provider === '2925') {
+    return `${parsedBaseEmail.localPart}${generateRandomSuffix(6)}@${parsedBaseEmail.domain}`;
+  }
+
+  throw new Error(`未支持的别名邮箱类型:${provider}`);
+}
+
 function getLuckmailSessionConfig(state = {}) {
   return {
     apiKey: String(state.luckmailApiKey || ''),
@@ -4742,6 +4902,7 @@ function getEmailGeneratorLabel(generator) {
 }
 const generatedEmailHelpers = self.MultiPageGeneratedEmailHelpers?.createGeneratedEmailHelpers({
   addLog,
+  buildGeneratedAliasEmail,
   buildCloudflareTempEmailHeaders,
   CLOUDFLARE_TEMP_EMAIL_GENERATOR,
   DUCK_AUTOFILL_URL,
@@ -4754,6 +4915,7 @@ const generatedEmailHelpers = self.MultiPageGeneratedEmailHelpers?.createGenerat
   normalizeCloudflareDomain,
   normalizeCloudflareTempEmailAddress,
   normalizeEmailGenerator,
+  isGeneratedAliasProvider,
   reuseOrCreateTab,
   sendToContentScript,
   setEmailState,
@@ -5009,6 +5171,107 @@ async function ensureAutoEmailReady(targetRun, totalRuns, attemptRuns) {
   return resumedState.email;
 }
 
+async function ensureAutoEmailReady(targetRun, totalRuns, attemptRuns) {
+  const currentState = await getState();
+  if (isHotmailProvider(currentState)) {
+    const account = await ensureHotmailAccountForFlow({
+      allowAllocate: true,
+      markUsed: true,
+      preferredAccountId: null,
+    });
+    await addLog(`=== 目标 ${targetRun}/${totalRuns} 轮:已分配 Hotmail 账号 ${account.email}(第 ${attemptRuns} 次尝试)===`, 'ok');
+    return account.email;
+  }
+
+  if (isLuckmailProvider(currentState)) {
+    const purchase = await ensureLuckmailPurchaseForFlow({ allowReuse: true });
+    await addLog(`=== 目标 ${targetRun}/${totalRuns} 轮:LuckMail 邮箱已就绪:${purchase.email_address}(第 ${attemptRuns} 次尝试)===`, 'ok');
+    return purchase.email_address;
+  }
+
+  if (isGeneratedAliasProvider(currentState)) {
+    if (isReusableGeneratedAliasEmail(currentState)) {
+      await addLog(`=== 目标 ${targetRun}/${totalRuns} 轮:当前已复用 ${currentState.email},将直接继续执行(第 ${attemptRuns} 次尝试)===`, 'info');
+      return currentState.email;
+    }
+
+    const baseEmail = getManagedAliasBaseEmail(currentState);
+    if (!baseEmail && !currentState.email) {
+      const baseLabel = currentState.mailProvider === GMAIL_PROVIDER ? 'Gmail 原邮箱' : '2925 基邮箱';
+      throw new Error(`${baseLabel}未设置,请先填写,或直接在“注册邮箱”中手动填写完整邮箱。`);
+    }
+
+    await addLog(
+      `=== 目标 ${targetRun}/${totalRuns} 轮:${currentState.mailProvider === GMAIL_PROVIDER ? 'Gmail +tag' : '2925'} 模式已启用,将在步骤 3 自动生成邮箱(第 ${attemptRuns} 次尝试)===`,
+      'info'
+    );
+    return null;
+  }
+
+  if (currentState.email) {
+    return currentState.email;
+  }
+
+  if (shouldUseCustomRegistrationEmail(currentState)) {
+    await addLog(`=== 目标 ${targetRun}/${totalRuns} 轮已暂停:请先填写自定义注册邮箱,然后继续 ===`, 'warn');
+    await broadcastAutoRunStatus('waiting_email', {
+      currentRun: targetRun,
+      totalRuns,
+      attemptRun: attemptRuns,
+    });
+
+    await waitForResume();
+
+    const resumedState = await getState();
+    if (!resumedState.email) {
+      throw new Error('无法继续:当前没有注册邮箱。');
+    }
+    return resumedState.email;
+  }
+
+  const generator = normalizeEmailGenerator(currentState.emailGenerator);
+  const generatorLabel = getEmailGeneratorLabel(generator);
+  let lastError = null;
+  for (let attempt = 1; attempt <= EMAIL_FETCH_MAX_ATTEMPTS; attempt++) {
+    try {
+      if (attempt > 1) {
+        await addLog(`${generatorLabel}:正在进行第 ${attempt}/${EMAIL_FETCH_MAX_ATTEMPTS} 次自动获取重试...`, 'warn');
+      }
+      const generatedEmail = await fetchGeneratedEmail(currentState, { generateNew: true, generator });
+      await addLog(
+        `=== 目标 ${targetRun}/${totalRuns} 轮:${generatorLabel}已就绪:${generatedEmail}(第 ${attemptRuns} 次尝试,第 ${attempt}/${EMAIL_FETCH_MAX_ATTEMPTS} 次获取)===`,
+        'ok'
+      );
+      return generatedEmail;
+    } catch (err) {
+      lastError = err;
+      await addLog(`${generatorLabel}自动获取失败(${attempt}/${EMAIL_FETCH_MAX_ATTEMPTS}):${err.message}`, 'warn');
+      if (
+        (generator === 'cloudflare' && /域名/.test(String(err.message || '')))
+        || (generator === CLOUDFLARE_TEMP_EMAIL_GENERATOR && /(服务地址|Admin Auth|域名)/.test(String(err.message || '')))
+      ) {
+        break;
+      }
+    }
+  }
+
+  await addLog(`${generatorLabel}自动获取已连续失败 ${EMAIL_FETCH_MAX_ATTEMPTS} 次:${lastError?.message || '未知错误'}`, 'error');
+  await addLog(`=== 目标 ${targetRun}/${totalRuns} 轮已暂停:请先自动获取邮箱或手动粘贴邮箱,然后继续 ===`, 'warn');
+  await broadcastAutoRunStatus('waiting_email', {
+    currentRun: targetRun,
+    totalRuns,
+    attemptRun: attemptRuns,
+  });
+
+  await waitForResume();
+
+  const resumedState = await getState();
+  if (!resumedState.email) {
+    throw new Error('无法继续:当前没有邮箱地址。');
+  }
+  return resumedState.email;
+}
+
 async function runAutoSequenceFromStep(startStep, context = {}) {
   const { targetRun, totalRuns, attemptRuns, continued = false } = context;
   let postStep6RestartCount = 0;
@@ -5226,6 +5489,7 @@ const signupFlowHelpers = self.MultiPageSignupFlowHelpers?.createSignupFlowHelpe
   ensureLuckmailPurchaseForFlow,
   getTabId,
   isGeneratedAliasProvider,
+  isReusableGeneratedAliasEmail,
   isSignupEmailVerificationPageUrl,
   isHotmailProvider,
   isLuckmailProvider,

+ 2 - 0
background/auto-run-controller.js

@@ -352,6 +352,8 @@
               autoStepDelaySeconds: prevState.autoStepDelaySeconds,
               mailProvider: prevState.mailProvider,
               emailGenerator: prevState.emailGenerator,
+              gmailBaseEmail: prevState.gmailBaseEmail,
+              mail2925BaseEmail: prevState.mail2925BaseEmail,
               emailPrefix: prevState.emailPrefix,
               inbucketHost: prevState.inbucketHost,
               inbucketMailbox: prevState.inbucketMailbox,

+ 26 - 0
background/generated-email-helpers.js

@@ -4,6 +4,7 @@
   function createGeneratedEmailHelpers(deps = {}) {
     const {
       addLog,
+      buildGeneratedAliasEmail,
       buildCloudflareTempEmailHeaders,
       CLOUDFLARE_TEMP_EMAIL_GENERATOR,
       DUCK_AUTOFILL_URL,
@@ -16,6 +17,7 @@
       normalizeCloudflareDomain,
       normalizeCloudflareTempEmailAddress,
       normalizeEmailGenerator,
+      isGeneratedAliasProvider,
       reuseOrCreateTab,
       sendToContentScript,
       setEmailState,
@@ -185,8 +187,32 @@
       return result.email;
     }
 
+    async function fetchManagedAliasEmail(state, options = {}) {
+      throwIfStopped();
+      const provider = String(options.mailProvider || state?.mailProvider || '').trim().toLowerCase();
+      const mergedState = {
+        ...(state || {}),
+        mailProvider: provider,
+      };
+      if (options.gmailBaseEmail !== undefined) {
+        mergedState.gmailBaseEmail = String(options.gmailBaseEmail || '').trim();
+      }
+      if (options.mail2925BaseEmail !== undefined) {
+        mergedState.mail2925BaseEmail = String(options.mail2925BaseEmail || '').trim();
+      }
+
+      const email = buildGeneratedAliasEmail(mergedState);
+      await setEmailState(email);
+      await addLog(`${provider === 'gmail' ? 'Gmail +tag' : '2925'}:已生成 ${email}`, 'ok');
+      return email;
+    }
+
     async function fetchGeneratedEmail(state, options = {}) {
       const currentState = state || await getState();
+      const provider = String(options.mailProvider || currentState.mailProvider || '').trim().toLowerCase();
+      if (isGeneratedAliasProvider?.(provider)) {
+        return fetchManagedAliasEmail(currentState, options);
+      }
       const generator = normalizeEmailGenerator(options.generator ?? currentState.emailGenerator);
       if (generator === 'custom') {
         throw new Error('当前邮箱生成方式为自定义邮箱,请直接填写注册邮箱。');

+ 4 - 1
background/signup-flow-helpers.js

@@ -9,6 +9,7 @@
       ensureHotmailAccountForFlow,
       ensureLuckmailPurchaseForFlow,
       isGeneratedAliasProvider,
+      isReusableGeneratedAliasEmail,
       isHotmailProvider,
       isLuckmailProvider,
       isSignupEmailVerificationPageUrl,
@@ -162,7 +163,9 @@
         const purchase = await ensureLuckmailPurchaseForFlow({ allowReuse: true });
         resolvedEmail = purchase.email_address;
       } else if (isGeneratedAliasProvider(state)) {
-        resolvedEmail = buildGeneratedAliasEmail(state);
+        if (!isReusableGeneratedAliasEmail?.(state, resolvedEmail)) {
+          resolvedEmail = buildGeneratedAliasEmail(state);
+        }
       }
 
       if (!resolvedEmail) {

+ 146 - 0
managed-alias-utils.js

@@ -0,0 +1,146 @@
+(function attachManagedAliasUtils(root, factory) {
+  root.MultiPageManagedAliasUtils = factory();
+})(typeof self !== 'undefined' ? self : globalThis, function createManagedAliasUtilsModule() {
+  const GMAIL_PROVIDER = 'gmail';
+  const MAIL_2925_PROVIDER = '2925';
+
+  const PROVIDER_CONFIGS = {
+    [GMAIL_PROVIDER]: {
+      baseLabel: 'Gmail 原邮箱',
+      basePlaceholder: '例如 yourname@gmail.com',
+      label: 'Gmail +tag 邮箱',
+      parseBaseEmail(rawValue = '') {
+        const value = String(rawValue || '').trim().toLowerCase();
+        const match = value.match(/^([^@\s+]+)@((?:gmail|googlemail)\.com)$/i);
+        if (!match) return null;
+        return {
+          localPart: match[1],
+          domain: match[2].toLowerCase(),
+        };
+      },
+      matchesProviderDomain(domain = '') {
+        return /^(?:gmail|googlemail)\.com$/i.test(String(domain || '').trim());
+      },
+      matchesAliasLocalPart(baseLocalPart = '', candidateLocalPart = '') {
+        return String(candidateLocalPart || '').split('+')[0] === String(baseLocalPart || '');
+      },
+      buildAlias(parsedBaseEmail, tag) {
+        return `${parsedBaseEmail.localPart}+${tag}@${parsedBaseEmail.domain}`;
+      },
+      generationHint: '先填写 Gmail 原邮箱后点“生成”,也可以直接手动填写完整的 Gmail 邮箱。',
+      registrationPlaceholder: '点击生成 Gmail +tag 邮箱,或手动填写完整邮箱',
+    },
+    [MAIL_2925_PROVIDER]: {
+      baseLabel: '2925 基邮箱',
+      basePlaceholder: '例如 yourname@2925.com',
+      label: '2925 邮箱',
+      parseBaseEmail(rawValue = '') {
+        const value = String(rawValue || '').trim().toLowerCase();
+        const match = value.match(/^([^@\s+]+)@(2925\.com)$/i);
+        if (!match) return null;
+        return {
+          localPart: match[1],
+          domain: match[2].toLowerCase(),
+        };
+      },
+      matchesProviderDomain(domain = '') {
+        return String(domain || '').trim().toLowerCase() === '2925.com';
+      },
+      matchesAliasLocalPart(baseLocalPart = '', candidateLocalPart = '') {
+        const normalizedBaseLocalPart = String(baseLocalPart || '');
+        const normalizedCandidateLocalPart = String(candidateLocalPart || '');
+        return normalizedCandidateLocalPart === normalizedBaseLocalPart
+          || normalizedCandidateLocalPart.startsWith(normalizedBaseLocalPart);
+      },
+      buildAlias(parsedBaseEmail, tag) {
+        return `${parsedBaseEmail.localPart}${tag}@${parsedBaseEmail.domain}`;
+      },
+      generationHint: '先填写 2925 基邮箱后点“生成”,也可以直接手动填写完整的 2925 邮箱。',
+      registrationPlaceholder: '点击生成 2925 邮箱,或手动填写完整邮箱',
+    },
+  };
+
+  function getManagedAliasProviderConfig(provider = '') {
+    return PROVIDER_CONFIGS[String(provider || '').trim().toLowerCase()] || null;
+  }
+
+  function isManagedAliasProvider(provider = '') {
+    return Boolean(getManagedAliasProviderConfig(provider));
+  }
+
+  function parseEmailParts(rawValue = '') {
+    const value = String(rawValue || '').trim().toLowerCase();
+    const match = value.match(/^([^@\s]+)@([^@\s]+\.[^@\s]+)$/);
+    if (!match) return null;
+    return {
+      localPart: match[1],
+      domain: match[2],
+    };
+  }
+
+  function parseManagedAliasBaseEmail(rawValue, provider = '') {
+    const config = getManagedAliasProviderConfig(provider);
+    return config?.parseBaseEmail(rawValue) || null;
+  }
+
+  function isManagedAliasEmail(value, provider = '', baseEmail = '') {
+    const config = getManagedAliasProviderConfig(provider);
+    if (!config) return false;
+
+    const parsedEmail = parseEmailParts(value);
+    if (!parsedEmail || !config.matchesProviderDomain(parsedEmail.domain)) {
+      return false;
+    }
+
+    const parsedBaseEmail = parseManagedAliasBaseEmail(baseEmail, provider);
+    if (!parsedBaseEmail) {
+      return true;
+    }
+
+    return parsedEmail.domain === parsedBaseEmail.domain
+      && config.matchesAliasLocalPart(parsedBaseEmail.localPart, parsedEmail.localPart);
+  }
+
+  function buildManagedAliasEmail(provider = '', baseEmail = '', tag = '') {
+    const config = getManagedAliasProviderConfig(provider);
+    if (!config) {
+      throw new Error(`Unsupported managed alias provider: ${provider}`);
+    }
+
+    const parsedBaseEmail = parseManagedAliasBaseEmail(baseEmail, provider);
+    if (!parsedBaseEmail) {
+      throw new Error(`${config.baseLabel}格式不正确`);
+    }
+
+    const normalizedTag = String(tag || '').trim();
+    if (!normalizedTag) {
+      throw new Error(`${config.label}生成标签为空`);
+    }
+
+    return config.buildAlias(parsedBaseEmail, normalizedTag);
+  }
+
+  function getManagedAliasProviderUiCopy(provider = '') {
+    const config = getManagedAliasProviderConfig(provider);
+    if (!config) return null;
+    return {
+      baseLabel: config.baseLabel,
+      basePlaceholder: config.basePlaceholder,
+      buttonLabel: '生成',
+      successVerb: '生成',
+      label: config.label,
+      placeholder: config.registrationPlaceholder,
+      hint: config.generationHint,
+    };
+  }
+
+  return {
+    buildManagedAliasEmail,
+    getManagedAliasProviderConfig,
+    getManagedAliasProviderUiCopy,
+    isManagedAliasEmail,
+    isManagedAliasProvider,
+    parseEmailParts,
+    parseManagedAliasBaseEmail,
+  };
+});

+ 3 - 2
sidepanel/sidepanel.html

@@ -225,8 +225,8 @@
         </div>
       </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="例如 abc" />
+        <span class="data-label" id="label-email-prefix">别名基邮箱</span>
+        <input type="text" id="input-email-prefix" class="data-input" placeholder="例如 yourname@example.com" />
       </div>
       <div class="data-row" id="row-inbucket-host" style="display:none;">
         <span class="data-label">Inbucket</span>
@@ -587,6 +587,7 @@
 
   <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="../icloud-utils.js"></script>
   <script src="../hotmail-utils.js"></script>
   <script src="../luckmail-utils.js"></script>

+ 187 - 32
sidepanel/sidepanel.js

@@ -212,6 +212,152 @@ const DEFAULT_LUCKMAIL_EMAIL_TYPE = 'ms_graph';
 const DISPLAY_TIMEZONE = 'Asia/Shanghai';
 const DEFAULT_ACCOUNT_RUN_HISTORY_HELPER_BASE_URL = 'http://127.0.0.1:17373';
 
+function getManagedAliasUtils() {
+  return window.MultiPageManagedAliasUtils || null;
+}
+
+function isManagedAliasProvider(provider = selectMailProvider.value) {
+  const utils = getManagedAliasUtils();
+  if (utils?.isManagedAliasProvider) {
+    return utils.isManagedAliasProvider(provider);
+  }
+  return [GMAIL_PROVIDER, '2925'].includes(String(provider || '').trim().toLowerCase());
+}
+
+function parseManagedAliasBaseEmail(rawValue, provider = selectMailProvider.value) {
+  const utils = getManagedAliasUtils();
+  if (utils?.parseManagedAliasBaseEmail) {
+    return utils.parseManagedAliasBaseEmail(rawValue, provider);
+  }
+  return null;
+}
+
+function isManagedAliasEmail(value, baseEmail = '', provider = selectMailProvider.value) {
+  const utils = getManagedAliasUtils();
+  if (utils?.isManagedAliasEmail) {
+    return utils.isManagedAliasEmail(value, provider, baseEmail);
+  }
+  return false;
+}
+
+function getManagedAliasProviderUiCopy(provider = selectMailProvider.value) {
+  const utils = getManagedAliasUtils();
+  if (utils?.getManagedAliasProviderUiCopy) {
+    return utils.getManagedAliasProviderUiCopy(provider);
+  }
+  if (String(provider || '').trim().toLowerCase() === GMAIL_PROVIDER) {
+    return {
+      baseLabel: 'Gmail 原邮箱',
+      basePlaceholder: '例如 yourname@gmail.com',
+      buttonLabel: '生成',
+      successVerb: '生成',
+      label: 'Gmail +tag 邮箱',
+      placeholder: '点击生成 Gmail +tag 邮箱,或手动填写完整邮箱',
+      hint: '先填写 Gmail 原邮箱后点“生成”,也可以直接手动填写完整的 Gmail 邮箱。',
+    };
+  }
+  if (String(provider || '').trim().toLowerCase() === '2925') {
+    return {
+      baseLabel: '2925 基邮箱',
+      basePlaceholder: '例如 yourname@2925.com',
+      buttonLabel: '生成',
+      successVerb: '生成',
+      label: '2925 邮箱',
+      placeholder: '点击生成 2925 邮箱,或手动填写完整邮箱',
+      hint: '先填写 2925 基邮箱后点“生成”,也可以直接手动填写完整的 2925 邮箱。',
+    };
+  }
+  return null;
+}
+
+function getManagedAliasBaseEmailKey(provider = selectMailProvider.value) {
+  const normalizedProvider = String(provider || '').trim().toLowerCase();
+  if (normalizedProvider === GMAIL_PROVIDER) {
+    return 'gmailBaseEmail';
+  }
+  if (normalizedProvider === '2925') {
+    return 'mail2925BaseEmail';
+  }
+  return '';
+}
+
+function getManagedAliasBaseEmailForProvider(provider = selectMailProvider.value, state = latestState) {
+  const key = getManagedAliasBaseEmailKey(provider);
+  if (!key) {
+    return '';
+  }
+
+  const providerValue = String(state?.[key] || '').trim();
+  if (providerValue) {
+    return providerValue;
+  }
+
+  const legacyEmailPrefix = String(state?.emailPrefix || '').trim();
+  return parseManagedAliasBaseEmail(legacyEmailPrefix, provider) ? legacyEmailPrefix : '';
+}
+
+function buildManagedAliasBaseEmailPayload(state = latestState) {
+  const payload = {
+    gmailBaseEmail: String(state?.gmailBaseEmail || '').trim(),
+    mail2925BaseEmail: String(state?.mail2925BaseEmail || '').trim(),
+    emailPrefix: '',
+  };
+  const key = getManagedAliasBaseEmailKey();
+  if (key) {
+    payload[key] = inputEmailPrefix.value.trim();
+  }
+  return payload;
+}
+
+function syncManagedAliasBaseEmailDraftFromInput(provider = selectMailProvider.value) {
+  const key = getManagedAliasBaseEmailKey(provider);
+  if (!key) {
+    return;
+  }
+  syncLatestState({ [key]: inputEmailPrefix.value.trim() });
+}
+
+function setManagedAliasBaseEmailInputForProvider(provider = selectMailProvider.value, state = latestState) {
+  inputEmailPrefix.value = getManagedAliasBaseEmailForProvider(provider, state);
+}
+
+function getCurrentRegistrationEmailUiCopy() {
+  if (isCustomMailProvider()) {
+    return getCustomMailProviderUiCopy();
+  }
+  if (isManagedAliasProvider()) {
+    return getManagedAliasProviderUiCopy();
+  }
+  return getEmailGeneratorUiCopy();
+}
+
+function isCurrentRegistrationEmailCompatible(email = inputEmail.value.trim(), provider = selectMailProvider.value, state = latestState) {
+  if (!isManagedAliasProvider(provider) || !email) {
+    return true;
+  }
+  const baseEmail = getManagedAliasBaseEmailForProvider(provider, state);
+  return isManagedAliasEmail(email, baseEmail, provider);
+}
+
+function validateCurrentRegistrationEmail(email = inputEmail.value.trim(), options = {}) {
+  const { showToastOnFailure = false } = options;
+  if (isCurrentRegistrationEmailCompatible(email)) {
+    return true;
+  }
+
+  if (showToastOnFailure) {
+    const uiCopy = getManagedAliasProviderUiCopy();
+    const baseEmail = getManagedAliasBaseEmailForProvider();
+    showToast(
+      baseEmail
+        ? `当前邮箱服务为“${uiCopy?.label || '别名邮箱'}”,注册邮箱需与 ${uiCopy?.baseLabel || '基邮箱'} 对应。`
+        : `当前邮箱服务为“${uiCopy?.label || '别名邮箱'}”,请直接填写完整邮箱,或先填写基邮箱后点击“生成”。`,
+      'warn'
+    );
+  }
+  return false;
+}
+
 let latestState = null;
 let currentAutoRun = {
   autoRunning: false,
@@ -320,12 +466,7 @@ const LOG_LEVEL_LABELS = {
 };
 
 function usesGeneratedAliasMailProvider(provider, mail2925Mode = getSelectedMail2925Mode()) {
-  const normalizedProvider = String(provider || '').trim().toLowerCase();
-  if (normalizedProvider === GMAIL_PROVIDER) {
-    return true;
-  }
-  return normalizedProvider === '2925'
-    && normalizeMail2925Mode(mail2925Mode) === MAIL_2925_MODE_PROVIDE;
+  return isManagedAliasProvider(provider);
 }
 
 function parseGmailBaseEmail(rawValue = '') {
@@ -1146,7 +1287,7 @@ function collectSettingsPayload() {
     icloudHostPreference: selectIcloudHostPreference?.value || 'auto',
     accountRunHistoryTextEnabled: Boolean(inputAccountRunHistoryTextEnabled?.checked),
     accountRunHistoryHelperBaseUrl: normalizeAccountRunHistoryHelperBaseUrlValue(inputAccountRunHistoryHelperBaseUrl?.value),
-    emailPrefix: inputEmailPrefix.value.trim(),
+    ...buildManagedAliasBaseEmailPayload(),
     inbucketHost: inputInbucketHost.value.trim(),
     inbucketMailbox: inputInbucketMailbox.value.trim(),
     hotmailServiceMode: getSelectedHotmailServiceMode(),
@@ -1409,7 +1550,6 @@ function applyAutoRunStatus(payload = currentAutoRun) {
   inputRunCount.disabled = currentAutoRun.autoRunning;
   btnAutoRun.disabled = currentAutoRun.autoRunning;
   btnFetchEmail.disabled = locked
-    || usesGeneratedAliasMailProvider(selectMailProvider.value)
     || isCustomMailProvider();
   inputEmail.disabled = locked;
   inputAutoSkipFailures.disabled = scheduled;
@@ -1448,8 +1588,7 @@ function applyAutoRunStatus(payload = currentAutoRun) {
       setDefaultAutoRunButton();
       inputEmail.disabled = false;
       if (!locked) {
-        btnFetchEmail.disabled = usesGeneratedAliasMailProvider(selectMailProvider.value)
-          || isCustomMailProvider();
+        btnFetchEmail.disabled = isCustomMailProvider();
       }
       break;
   }
@@ -1562,7 +1701,7 @@ function applySettingsState(state) {
   if (inputAccountRunHistoryHelperBaseUrl) {
     inputAccountRunHistoryHelperBaseUrl.value = normalizeAccountRunHistoryHelperBaseUrlValue(state?.accountRunHistoryHelperBaseUrl);
   }
-  inputEmailPrefix.value = state?.emailPrefix || '';
+  setManagedAliasBaseEmailInputForProvider(restoredMailProvider, state);
   inputInbucketHost.value = state?.inbucketHost || '';
   inputInbucketMailbox.value = state?.inbucketMailbox || '';
   setHotmailServiceMode(state?.hotmailServiceMode);
@@ -2066,33 +2205,28 @@ function isCurrentEmailManagedByGeneratedAlias(
 
   const inputEmailValue = String(inputEmail.value || '').trim().toLowerCase();
   const stateEmailValue = String(state?.email || '').trim().toLowerCase();
-
-  if (normalizedProvider === GMAIL_PROVIDER) {
-    const baseEmail = String(state?.emailPrefix || inputEmailPrefix.value || '').trim();
-    return isManagedGmailAlias(inputEmailValue, baseEmail) || isManagedGmailAlias(stateEmailValue, baseEmail);
-  }
-
-  if (normalizedProvider === '2925') {
-    return inputEmailValue.endsWith('@2925.com') || stateEmailValue.endsWith('@2925.com');
-  }
-
-  return false;
+  const baseEmail = getManagedAliasBaseEmailForProvider(normalizedProvider, state);
+  return isManagedAliasEmail(inputEmailValue, baseEmail, normalizedProvider)
+    || isManagedAliasEmail(stateEmailValue, baseEmail, normalizedProvider);
 }
 
 async function maybeClearGeneratedAliasAfterEmailPrefixChange() {
   const provider = selectMailProvider.value;
-  const mail2925Mode = latestState?.mail2925Mode;
-  if (!usesGeneratedAliasMailProvider(provider, mail2925Mode)) {
+  if (!usesGeneratedAliasMailProvider(provider, latestState?.mail2925Mode)) {
     return;
   }
 
-  const previousPrefix = String(latestState?.emailPrefix || '').trim();
+  const previousPrefix = getManagedAliasBaseEmailForProvider(provider, latestState);
   const nextPrefix = inputEmailPrefix.value.trim();
   if (previousPrefix === nextPrefix) {
     return;
   }
 
-  if (!isCurrentEmailManagedByGeneratedAlias(provider, latestState, mail2925Mode)) {
+  if (!previousPrefix) {
+    return;
+  }
+
+  if (!isCurrentEmailManagedByGeneratedAlias(provider, latestState, latestState?.mail2925Mode)) {
     return;
   }
 
@@ -2123,9 +2257,11 @@ function updateMailProviderUI() {
   const useIcloudProvider = isIcloudMailProvider();
   const useEmailGenerator = !useHotmail && !useLuckmail && !useGeneratedAlias && !useCustomEmail;
   const useCloudflareTempEmailProvider = selectMailProvider.value === 'cloudflare-temp-email';
+  const aliasUiCopy = useGeneratedAlias ? getManagedAliasProviderUiCopy(selectMailProvider.value) : null;
+  const uiCopy = getCurrentRegistrationEmailUiCopy();
   updateMailLoginButtonState();
   if (rowMail2925Mode) {
-    rowMail2925Mode.style.display = use2925 ? '' : 'none';
+    rowMail2925Mode.style.display = 'none';
   }
   rowEmailPrefix.style.display = useGeneratedAlias ? '' : 'none';
   const hotmailServiceMode = getSelectedHotmailServiceMode();
@@ -2184,6 +2320,8 @@ function updateMailProviderUI() {
     labelEmailPrefix.textContent = 'Gmail 原邮箱';
     inputEmailPrefix.placeholder = '例如 yourname@gmail.com';
   }
+  labelEmailPrefix.textContent = aliasUiCopy?.baseLabel || labelEmailPrefix.textContent;
+  inputEmailPrefix.placeholder = aliasUiCopy?.basePlaceholder || inputEmailPrefix.placeholder;
   if (rowHotmailServiceMode) {
     rowHotmailServiceMode.style.display = useHotmail ? '' : 'none';
   }
@@ -2194,8 +2332,7 @@ function updateMailProviderUI() {
     rowHotmailLocalBaseUrl.style.display = useHotmail && hotmailServiceMode === HOTMAIL_SERVICE_MODE_LOCAL ? '' : 'none';
   }
   btnFetchEmail.hidden = useHotmail || useLuckmail || useCustomEmail;
-  inputEmail.readOnly = useHotmail || useLuckmail || useGeneratedAlias;
-  const uiCopy = useCustomEmail ? getCustomMailProviderUiCopy() : getEmailGeneratorUiCopy();
+  inputEmail.readOnly = useHotmail || useLuckmail;
   inputEmail.placeholder = useHotmail
     ? '由 Hotmail 账号池自动分配'
     : (useLuckmail
@@ -2204,7 +2341,10 @@ function updateMailProviderUI() {
   if (useGmail && useGeneratedAlias) {
     inputEmail.placeholder = '步骤 3 自动生成 Gmail +tag 邮箱并回填';
   }
-  btnFetchEmail.disabled = useGeneratedAlias || useLuckmail || useCustomEmail || isAutoRunLockedPhase();
+  if (!useHotmail && !useLuckmail) {
+    inputEmail.placeholder = uiCopy.placeholder;
+  }
+  btnFetchEmail.disabled = useLuckmail || useCustomEmail || isAutoRunLockedPhase();
   if (!btnFetchEmail.disabled) {
     btnFetchEmail.textContent = uiCopy.buttonLabel;
   }
@@ -2220,6 +2360,9 @@ function updateMailProviderUI() {
   if (autoHintText && useGmail && useGeneratedAlias) {
     autoHintText.textContent = '请先填写 Gmail 原邮箱,步骤 3 会自动生成 Gmail +tag 地址';
   }
+  if (autoHintText && useGeneratedAlias && aliasUiCopy?.hint) {
+    autoHintText.textContent = aliasUiCopy.hint;
+  }
   if (autoHintText && showCloudflareTempEmailReceiveMailbox) {
     autoHintText.textContent = '若注册邮箱会转发到 Cloudflare Temp Email,请在“邮件接收”中填写实际接收转发邮件的邮箱。';
   }
@@ -2711,7 +2854,7 @@ function escapeHtml(text) {
 
 async function fetchGeneratedEmail(options = {}) {
   const { showFailureToast = true } = options;
-  const uiCopy = getEmailGeneratorUiCopy();
+  const uiCopy = getCurrentRegistrationEmailUiCopy();
   if (isCustomMailProvider()) {
     throw new Error('当前邮箱服务为自定义邮箱,请直接填写注册邮箱。');
   }
@@ -2726,6 +2869,8 @@ async function fetchGeneratedEmail(options = {}) {
       payload: {
         generateNew: true,
         generator: selectEmailGenerator.value,
+        mailProvider: selectMailProvider.value,
+        ...buildManagedAliasBaseEmailPayload(),
       },
     });
 
@@ -3108,7 +3253,7 @@ stepsList?.addEventListener('click', async (event) => {
         if (response?.error) {
           throw new Error(response.error);
         }
-      } else if (usesGeneratedAliasMailProvider(selectMailProvider.value)) {
+      } else if (false && usesGeneratedAliasMailProvider(selectMailProvider.value)) {
         const emailPrefix = inputEmailPrefix.value.trim();
         if (!emailPrefix) {
           showToast(selectMailProvider.value === GMAIL_PROVIDER ? '请先填写 Gmail 原邮箱。' : '请先填写 2925 邮箱前缀。', 'warn');
@@ -3132,6 +3277,9 @@ stepsList?.addEventListener('click', async (event) => {
             return;
           }
         }
+        if (!validateCurrentRegistrationEmail(email, { showToastOnFailure: true })) {
+          return;
+        }
         const response = await chrome.runtime.sendMessage({ type: 'EXECUTE_STEP', source: 'sidepanel', payload: { step, email } });
         if (response?.error) {
           throw new Error(response.error);
@@ -3450,6 +3598,9 @@ inputEmail.addEventListener('change', async () => {
   inputEmail.value = email;
   try {
     if (email) {
+      if (!validateCurrentRegistrationEmail(email, { showToastOnFailure: true })) {
+        return;
+      }
       const response = await chrome.runtime.sendMessage({ type: 'SAVE_EMAIL', source: 'sidepanel', payload: { email } });
       if (response?.error) {
         throw new Error(response.error);
@@ -3516,6 +3667,8 @@ selectMailProvider.addEventListener('change', async () => {
   const previousProvider = latestState?.mailProvider || '';
   const previousMail2925Mode = latestState?.mail2925Mode;
   const nextProvider = selectMailProvider.value;
+  syncManagedAliasBaseEmailDraftFromInput(previousProvider);
+  setManagedAliasBaseEmailInputForProvider(nextProvider, latestState);
   updateMailProviderUI();
   const leavingHotmail = previousProvider === 'hotmail-api'
     && nextProvider !== 'hotmail-api'
@@ -3694,11 +3847,13 @@ inputSub2ApiGroup.addEventListener('blur', () => {
 
 inputEmailPrefix.addEventListener('input', () => {
   maybeClearGeneratedAliasAfterEmailPrefixChange().catch(() => { });
+  syncManagedAliasBaseEmailDraftFromInput();
   markSettingsDirty(true);
   scheduleSettingsAutoSave();
 });
 inputEmailPrefix.addEventListener('blur', () => {
   maybeClearGeneratedAliasAfterEmailPrefixChange().catch(() => {});
+  syncManagedAliasBaseEmailDraftFromInput();
   saveSettings({ silent: true }).catch(() => {});
 });
 

+ 6 - 0
tests/auto-run-fresh-attempt-reset.test.js

@@ -102,6 +102,8 @@ let currentState = {
   autoStepDelaySeconds: null,
   mailProvider: '163',
   emailGenerator: 'duck',
+  gmailBaseEmail: 'demo@gmail.com',
+  mail2925BaseEmail: 'demo@2925.com',
   emailPrefix: 'demo',
   inbucketHost: '',
   inbucketMailbox: '',
@@ -151,6 +153,8 @@ async function resetState() {
     autoStepDelaySeconds: prev.autoStepDelaySeconds,
     mailProvider: prev.mailProvider,
     emailGenerator: prev.emailGenerator,
+    gmailBaseEmail: prev.gmailBaseEmail,
+    mail2925BaseEmail: prev.mail2925BaseEmail,
     emailPrefix: prev.emailPrefix,
     inbucketHost: prev.inbucketHost,
     inbucketMailbox: prev.inbucketMailbox,
@@ -302,6 +306,8 @@ return {
   assert.strictEqual(snapshot.currentState.autoRunPhase, 'complete', 'both runs should complete after reset');
   assert.strictEqual(snapshot.currentState.autoRunCurrentRun, 2, 'final run index should be recorded');
   assert.strictEqual(snapshot.autoRunActive, false, 'auto-run should exit active state after completion');
+  assert.strictEqual(snapshot.currentState.gmailBaseEmail, 'demo@gmail.com', 'gmail base email should survive fresh-attempt reset');
+  assert.strictEqual(snapshot.currentState.mail2925BaseEmail, 'demo@2925.com', '2925 base email should survive fresh-attempt reset');
 
   console.log('auto-run fresh attempt reset tests passed');
 })().catch((error) => {

+ 39 - 0
tests/background-signup-step2-branching.test.js

@@ -132,3 +132,42 @@ test('signup flow helper recognizes email verification page as post-email landin
   assert.equal(ensureCalls, 1);
   assert.equal(passwordReadyChecks, 0);
 });
+
+test('signup flow helper reuses existing managed alias email when it is still compatible', async () => {
+  let buildCalls = 0;
+  let setEmailCalls = 0;
+
+  const helpers = signupFlowApi.createSignupFlowHelpers({
+    buildGeneratedAliasEmail: () => {
+      buildCalls += 1;
+      return 'demo+fresh@gmail.com';
+    },
+    chrome: { tabs: { get: async () => ({ id: 21, url: 'https://auth.openai.com/create-account/password' }) } },
+    ensureContentScriptReadyOnTab: async () => {},
+    ensureHotmailAccountForFlow: async () => ({}),
+    ensureLuckmailPurchaseForFlow: async () => ({}),
+    isGeneratedAliasProvider: () => true,
+    isReusableGeneratedAliasEmail: (_state, email) => email === 'demo+saved@gmail.com',
+    isHotmailProvider: () => false,
+    isLuckmailProvider: () => false,
+    isSignupEmailVerificationPageUrl: () => false,
+    isSignupPasswordPageUrl: () => true,
+    reuseOrCreateTab: async () => 21,
+    sendToContentScriptResilient: async () => ({}),
+    setEmailState: async () => {
+      setEmailCalls += 1;
+    },
+    SIGNUP_ENTRY_URL: 'https://chatgpt.com/',
+    SIGNUP_PAGE_INJECT_FILES: [],
+    waitForTabUrlMatch: async () => null,
+  });
+
+  const email = await helpers.resolveSignupEmailForFlow({
+    mailProvider: 'gmail',
+    email: 'demo+saved@gmail.com',
+  });
+
+  assert.equal(email, 'demo+saved@gmail.com');
+  assert.equal(buildCalls, 0);
+  assert.equal(setEmailCalls, 0);
+});

+ 28 - 0
tests/managed-alias-utils.test.js

@@ -0,0 +1,28 @@
+const test = require('node:test');
+const assert = require('node:assert/strict');
+const fs = require('node:fs');
+
+const source = fs.readFileSync('managed-alias-utils.js', 'utf8');
+const scope = {};
+const api = new Function('self', `${source}; return self.MultiPageManagedAliasUtils;`)(scope);
+
+test('managed alias utils build gmail +tag email from full base email', () => {
+  assert.equal(
+    api.buildManagedAliasEmail('gmail', 'demo@gmail.com', 'riverstone'),
+    'demo+riverstone@gmail.com'
+  );
+});
+
+test('managed alias utils build 2925 email from full base email', () => {
+  assert.equal(
+    api.buildManagedAliasEmail('2925', 'demo@2925.com', '123456'),
+    'demo123456@2925.com'
+  );
+});
+
+test('managed alias utils validate provider email with or without configured base email', () => {
+  assert.equal(api.isManagedAliasEmail('demo+riverstone@gmail.com', 'gmail', 'demo@gmail.com'), true);
+  assert.equal(api.isManagedAliasEmail('demo123456@2925.com', '2925', 'demo@2925.com'), true);
+  assert.equal(api.isManagedAliasEmail('manual@gmail.com', 'gmail', ''), true);
+  assert.equal(api.isManagedAliasEmail('manual@qq.com', 'gmail', ''), false);
+});