Explorar o código

fix: 修改自动运行失败跳过设置,确保状态一致性

QLHazyCoder hai 4 meses
pai
achega
2f0495fb6e
Modificáronse 3 ficheiros con 135 adicións e 29 borrados
  1. 100 13
      background.js
  2. 5 3
      sidepanel/sidepanel.css
  3. 30 13
      sidepanel/sidepanel.js

+ 100 - 13
background.js

@@ -65,7 +65,7 @@ const PERSISTED_SETTING_DEFAULTS = {
   sub2apiPassword: '',
   sub2apiGroupName: DEFAULT_SUB2API_GROUP_NAME,
   customPassword: '',
-  autoRunSkipFailures: true,
+  autoRunSkipFailures: false,
   autoRunFallbackThreadIntervalMinutes: 0,
   autoRunDelayEnabled: false,
   autoRunDelayMinutes: 30,
@@ -204,7 +204,7 @@ function normalizeScheduledAutoRunPlan(plan) {
 
   return {
     totalRuns: normalizeRunCount(plan.totalRuns),
-    autoRunSkipFailures: true,
+    autoRunSkipFailures: Boolean(plan.autoRunSkipFailures),
     mode: plan.mode === 'continue' ? 'continue' : 'restart',
   };
 }
@@ -2520,7 +2520,7 @@ async function launchScheduledAutoRun(trigger = 'alarm') {
       'info'
     );
     startAutoRunLoop(plan.totalRuns, {
-      autoRunSkipFailures: true,
+      autoRunSkipFailures: Boolean(plan.autoRunSkipFailures),
       mode: plan.mode,
     });
     return true;
@@ -2836,7 +2836,7 @@ async function handleMessage(message, sender) {
         throw new Error('已有自动运行倒计时计划,请先取消或立即开始。');
       }
       const totalRuns = normalizeRunCount(message.payload?.totalRuns || 1);
-      const autoRunSkipFailures = true;
+      const autoRunSkipFailures = Boolean(message.payload?.autoRunSkipFailures);
       const mode = message.payload?.mode === 'continue' ? 'continue' : 'restart';
       await setState({ autoRunSkipFailures });
       startAutoRunLoop(totalRuns, { autoRunSkipFailures, mode });
@@ -2848,7 +2848,7 @@ async function handleMessage(message, sender) {
       const totalRuns = normalizeRunCount(message.payload?.totalRuns || 1);
       return await scheduleAutoRun(totalRuns, {
         delayMinutes: message.payload?.delayMinutes,
-        autoRunSkipFailures: true,
+        autoRunSkipFailures: Boolean(message.payload?.autoRunSkipFailures),
         mode: message.payload?.mode,
       });
     }
@@ -4025,6 +4025,41 @@ async function logAutoRunFinalSummary(totalRuns, roundSummaries = []) {
   }
 }
 
+async function waitBetweenAutoRunRounds(targetRun, totalRuns, roundSummary) {
+  if (totalRuns <= 1 || targetRun >= totalRuns) {
+    return;
+  }
+
+  const fallbackThreadIntervalMinutes = normalizeAutoRunFallbackThreadIntervalMinutes(
+    (await getState()).autoRunFallbackThreadIntervalMinutes
+  );
+  if (fallbackThreadIntervalMinutes <= 0) {
+    return;
+  }
+
+  const statusLabel = roundSummary?.status === 'failed' ? '失败' : '完成';
+  await addLog(
+    `线程间隔:第 ${targetRun}/${totalRuns} 轮已${statusLabel},等待 ${fallbackThreadIntervalMinutes} 分钟后开始下一轮。`,
+    'info'
+  );
+  await sleepWithStop(fallbackThreadIntervalMinutes * 60 * 1000);
+}
+
+async function waitBeforeAutoRunRetry(targetRun, totalRuns, nextAttemptRun) {
+  const fallbackThreadIntervalMinutes = normalizeAutoRunFallbackThreadIntervalMinutes(
+    (await getState()).autoRunFallbackThreadIntervalMinutes
+  );
+  if (fallbackThreadIntervalMinutes <= 0) {
+    return;
+  }
+
+  await addLog(
+    `线程间隔:等待 ${fallbackThreadIntervalMinutes} 分钟后开始第 ${targetRun}/${totalRuns} 轮第 ${nextAttemptRun} 次尝试。`,
+    'info'
+  );
+  await sleepWithStop(fallbackThreadIntervalMinutes * 60 * 1000);
+}
+
 async function handleAutoRunLoopUnhandledError(error) {
   console.error(LOG_PREFIX, 'Auto run loop crashed:', error);
   if (!isStopError(error)) {
@@ -4059,7 +4094,7 @@ async function autoRunLoop(totalRuns, options = {}) {
   autoRunTotalRuns = totalRuns;
   autoRunCurrentRun = 0;
   autoRunAttemptRun = 0;
-  const autoRunSkipFailures = true;
+  const autoRunSkipFailures = Boolean(options.autoRunSkipFailures);
   const initialMode = options.mode === 'continue' ? 'continue' : 'restart';
   const resumeCurrentRun = Number.isInteger(options.resumeCurrentRun) && options.resumeCurrentRun > 0
     ? Math.min(totalRuns, options.resumeCurrentRun)
@@ -4102,10 +4137,14 @@ async function autoRunLoop(totalRuns, options = {}) {
 
   for (let targetRun = resumeCurrentRun; targetRun <= totalRuns; targetRun += 1) {
     const roundSummary = roundSummaries[targetRun - 1];
-    let attemptRun = continueCurrentOnFirstAttempt && targetRun === resumeCurrentRun ? resumeAttemptRun : 1;
-    let reuseExistingProgress = continueCurrentOnFirstAttempt && targetRun === resumeCurrentRun;
-
-    while (attemptRun <= AUTO_RUN_MAX_RETRIES_PER_ROUND + 1) {
+    const resumingCurrentRound = continueCurrentOnFirstAttempt && targetRun === resumeCurrentRun;
+    let attemptRun = resumingCurrentRound ? resumeAttemptRun : 1;
+    let reuseExistingProgress = resumingCurrentRound;
+    const maxAttemptsForRound = autoRunSkipFailures
+      ? AUTO_RUN_MAX_RETRIES_PER_ROUND + 1
+      : Math.max(1, attemptRun);
+
+    while (attemptRun <= maxAttemptsForRound) {
       autoRunCurrentRun = targetRun;
       autoRunAttemptRun = attemptRun;
       roundSummary.attempts = attemptRun;
@@ -4207,7 +4246,7 @@ async function autoRunLoop(totalRuns, options = {}) {
 
         const reason = getErrorMessage(err);
         roundSummary.failureReasons.push(reason);
-        const canRetry = attemptRun <= AUTO_RUN_MAX_RETRIES_PER_ROUND;
+        const canRetry = autoRunSkipFailures && attemptRun < maxAttemptsForRound;
 
         await setState({
           autoRunRoundSummaries: serializeAutoRunRoundSummaries(totalRuns, roundSummaries),
@@ -4247,6 +4286,21 @@ async function autoRunLoop(totalRuns, options = {}) {
             }
             throw sleepError;
           }
+          try {
+            await waitBeforeAutoRunRetry(targetRun, totalRuns, attemptRun + 1);
+          } catch (sleepError) {
+            if (isStopError(sleepError)) {
+              stoppedEarly = true;
+              await addLog(`第 ${targetRun}/${totalRuns} 轮已被用户停止`, 'warn');
+              await broadcastAutoRunStatus('stopped', {
+                currentRun: targetRun,
+                totalRuns,
+                attemptRun,
+              });
+              break;
+            }
+            throw sleepError;
+          }
           attemptRun += 1;
           reuseExistingProgress = false;
           continue;
@@ -4257,8 +4311,25 @@ async function autoRunLoop(totalRuns, options = {}) {
         await setState({
           autoRunRoundSummaries: serializeAutoRunRoundSummaries(totalRuns, roundSummaries),
         });
+        if (!autoRunSkipFailures) {
+          cancelPendingCommands('当前轮执行失败。');
+          await broadcastStopToContentScripts();
+          await addLog('自动重试未开启,自动运行将在当前失败后停止。', 'warn');
+          stoppedEarly = true;
+          await broadcastAutoRunStatus('stopped', {
+            currentRun: targetRun,
+            totalRuns,
+            attemptRun,
+          });
+          break;
+        }
         await addLog(`第 ${targetRun}/${totalRuns} 轮最终失败:${reason}`, 'error');
-        await addLog(`第 ${targetRun}/${totalRuns} 轮已达到 ${AUTO_RUN_MAX_RETRIES_PER_ROUND} 次重试上限,继续下一轮。`, 'warn');
+        await addLog(
+          targetRun < totalRuns
+            ? `第 ${targetRun}/${totalRuns} 轮已达到 ${AUTO_RUN_MAX_RETRIES_PER_ROUND} 次重试上限,继续下一轮。`
+            : `第 ${targetRun}/${totalRuns} 轮已达到 ${AUTO_RUN_MAX_RETRIES_PER_ROUND} 次重试上限,本次自动运行结束。`,
+          'warn'
+        );
         cancelPendingCommands('当前轮已达到重试上限。');
         await broadcastStopToContentScripts();
         forceFreshTabsNextRun = true;
@@ -4272,6 +4343,22 @@ async function autoRunLoop(totalRuns, options = {}) {
     if (stoppedEarly) {
       break;
     }
+
+    try {
+      await waitBetweenAutoRunRounds(targetRun, totalRuns, roundSummary);
+    } catch (sleepError) {
+      if (isStopError(sleepError)) {
+        stoppedEarly = true;
+        await addLog(`第 ${targetRun}/${totalRuns} 轮已被用户停止`, 'warn');
+        await broadcastAutoRunStatus('stopped', {
+          currentRun: targetRun,
+          totalRuns,
+          attemptRun: autoRunAttemptRun,
+        });
+        break;
+      }
+      throw sleepError;
+    }
   }
 
   await setState({
@@ -4333,7 +4420,7 @@ async function resumeAutoRun() {
 
   await addLog('检测到自动流程暂停上下文已丢失,正在从当前进度恢复自动运行...', 'warn');
   startAutoRunLoop(totalRuns, {
-    autoRunSkipFailures: true,
+    autoRunSkipFailures: Boolean(state.autoRunSkipFailures),
     mode: 'continue',
     resumeCurrentRun: currentRun,
     resumeAttemptRun: attemptRun,

+ 5 - 3
sidepanel/sidepanel.css

@@ -939,9 +939,11 @@ header {
 .toggle-switch input {
   position: absolute;
   opacity: 0;
-  width: 1px;
-  height: 1px;
-  pointer-events: none;
+  inset: 0;
+  width: 100%;
+  height: 100%;
+  margin: 0;
+  cursor: pointer;
 }
 
 .toggle-switch-track {

+ 30 - 13
sidepanel/sidepanel.js

@@ -134,6 +134,7 @@ const AUTO_DELAY_DEFAULT_MINUTES = 30;
 const AUTO_FALLBACK_THREAD_INTERVAL_MIN_MINUTES = 0;
 const AUTO_FALLBACK_THREAD_INTERVAL_MAX_MINUTES = 1440;
 const AUTO_FALLBACK_THREAD_INTERVAL_DEFAULT_MINUTES = 0;
+const AUTO_RUN_MAX_RETRIES_PER_ROUND = 3;
 const AUTO_STEP_DELAY_MIN_SECONDS = 0;
 const AUTO_STEP_DELAY_MAX_SECONDS = 600;
 const DEFAULT_LOCAL_CPA_STEP9_MODE = 'submit';
@@ -427,10 +428,14 @@ function setAutoRunFallbackRiskPromptDismissed(dismissed) {
   setPromptDismissed(AUTO_RUN_FALLBACK_RISK_PROMPT_DISMISSED_STORAGE_KEY, dismissed);
 }
 
+function shouldWarnAutoRunFallbackRisk(totalRuns, autoRunSkipFailures) {
+  return totalRuns >= AUTO_RUN_FALLBACK_RISK_WARNING_MIN_RUNS;
+}
+
 async function openAutoSkipFailuresConfirmModal() {
   const result = await openConfirmModalWithOption({
-    title: '兜底说明',
-    message: '开启后,当某一轮失败且无法继续时,会直接放弃当前线程,重新开新一轮,直到补足目标运行次数。线程间隔只在开启兜底且运行次数大于 1 时生效。',
+    title: '自动重试说明',
+    message: `开启后,自动模式在某一轮失败时,会先在当前轮自动重试;单轮最多重试 ${AUTO_RUN_MAX_RETRIES_PER_ROUND} 次,仍失败则放弃当前轮并继续下一轮。线程间隔只在开启自动重试且总轮数大于 1 时生效。`,
     confirmLabel: '确认开启',
   });
 
@@ -440,10 +445,6 @@ async function openAutoSkipFailuresConfirmModal() {
   };
 }
 
-function shouldWarnAutoRunFallbackRisk(totalRuns, autoRunSkipFailures) {
-  return Boolean(autoRunSkipFailures) && totalRuns >= AUTO_RUN_FALLBACK_RISK_WARNING_MIN_RUNS;
-}
-
 async function openAutoRunFallbackRiskConfirmModal(totalRuns, fallbackThreadIntervalMinutes) {
   const intervalLabel = Number.isFinite(fallbackThreadIntervalMinutes)
     ? `${fallbackThreadIntervalMinutes} 分钟`
@@ -451,7 +452,7 @@ async function openAutoRunFallbackRiskConfirmModal(totalRuns, fallbackThreadInte
 
   const result = await openConfirmModalWithOption({
     title: '自动运行风险提醒',
-    message: `当前设置为 ${totalRuns} 轮自动化,已开启兜底,线程间隔为 ${intervalLabel}。次数太多,可能会因为 IP 短时间注册过多原因而都失败。建议控制在 ${AUTO_RUN_FALLBACK_RISK_WARNING_MIN_RUNS} 轮以下,并将线程间隔设置在 ${AUTO_RUN_FALLBACK_RISK_RECOMMENDED_THREAD_INTERVAL_MINUTES} 分钟以上。是否继续?`,
+    message: `当前设置为 ${totalRuns} 轮自动化,已开启自动重试,线程间隔为 ${intervalLabel}。轮数过多时,可能会因为 IP 短时间注册过多而集中失败。建议控制在 ${AUTO_RUN_FALLBACK_RISK_WARNING_MIN_RUNS} 轮以下,并将线程间隔设置在 ${AUTO_RUN_FALLBACK_RISK_RECOMMENDED_THREAD_INTERVAL_MINUTES} 分钟以上。是否继续?`,
     confirmLabel: '继续',
   });
 
@@ -671,7 +672,7 @@ function updateFallbackThreadIntervalInputState() {
     return;
   }
 
-  inputAutoSkipFailuresThreadIntervalMinutes.disabled = !(inputAutoSkipFailures.checked && getRunCountValue() > 1);
+  inputAutoSkipFailuresThreadIntervalMinutes.disabled = Boolean(inputAutoSkipFailures.disabled);
 }
 
 function updateAutoDelayInputState() {
@@ -845,7 +846,7 @@ function collectSettingsPayload() {
     hotmailLocalBaseUrl: inputHotmailLocalBaseUrl.value.trim(),
     cloudflareDomain: selectedCloudflareDomain,
     cloudflareDomains: domains,
-    autoRunSkipFailures: true,
+    autoRunSkipFailures: inputAutoSkipFailures.checked,
     autoRunFallbackThreadIntervalMinutes: normalizeAutoRunThreadIntervalMinutes(inputAutoSkipFailuresThreadIntervalMinutes.value),
     autoRunDelayEnabled: inputAutoDelayEnabled.checked,
     autoRunDelayMinutes: normalizeAutoDelayMinutes(inputAutoDelayMinutes.value),
@@ -1017,7 +1018,7 @@ function applyAutoRunStatus(payload = currentAutoRun) {
     || usesGeneratedAliasMailProvider(selectMailProvider.value)
     || isCustomEmailGeneratorSelected();
   inputEmail.disabled = locked;
-  inputAutoSkipFailures.disabled = true;
+  inputAutoSkipFailures.disabled = scheduled;
 
   if (currentAutoRun.totalRuns > 0) {
     inputRunCount.value = String(currentAutoRun.totalRuns);
@@ -1121,7 +1122,7 @@ function applySettingsState(state) {
   inputHotmailLocalBaseUrl.value = state?.hotmailLocalBaseUrl || '';
   renderCloudflareDomainOptions(state?.cloudflareDomain || '');
   setCloudflareDomainEditMode(false, { clearInput: true });
-  inputAutoSkipFailures.checked = true;
+  inputAutoSkipFailures.checked = Boolean(state?.autoRunSkipFailures);
   inputAutoSkipFailuresThreadIntervalMinutes.value = String(normalizeAutoRunThreadIntervalMinutes(state?.autoRunFallbackThreadIntervalMinutes));
   inputAutoDelayEnabled.checked = Boolean(state?.autoRunDelayEnabled);
   inputAutoDelayMinutes.value = String(normalizeAutoDelayMinutes(state?.autoRunDelayMinutes));
@@ -2662,6 +2663,11 @@ btnAutoRun.addEventListener('click', async () => {
   try {
     const totalRuns = getRunCountValue();
     let mode = 'restart';
+    const autoRunSkipFailures = inputAutoSkipFailures.checked;
+    const fallbackThreadIntervalMinutes = normalizeAutoRunThreadIntervalMinutes(
+      inputAutoSkipFailuresThreadIntervalMinutes.value
+    );
+    inputAutoSkipFailuresThreadIntervalMinutes.value = String(fallbackThreadIntervalMinutes);
 
     if (shouldOfferAutoModeChoice()) {
       const startStep = getFirstUnfinishedStep();
@@ -2673,6 +2679,17 @@ btnAutoRun.addEventListener('click', async () => {
       mode = choice;
     }
 
+    if (shouldWarnAutoRunFallbackRisk(totalRuns, autoRunSkipFailures)
+      && !isAutoRunFallbackRiskPromptDismissed()) {
+      const result = await openAutoRunFallbackRiskConfirmModal(totalRuns, fallbackThreadIntervalMinutes);
+      if (!result.confirmed) {
+        return;
+      }
+      if (result.dismissPrompt) {
+        setAutoRunFallbackRiskPromptDismissed(true);
+      }
+    }
+
     btnAutoRun.disabled = true;
     inputRunCount.disabled = true;
     const delayEnabled = inputAutoDelayEnabled.checked;
@@ -2687,7 +2704,7 @@ btnAutoRun.addEventListener('click', async () => {
       payload: {
         totalRuns,
         delayMinutes,
-        autoRunSkipFailures: true,
+        autoRunSkipFailures,
         mode,
       },
     });
@@ -3116,7 +3133,7 @@ chrome.runtime.onMessage.addListener((message) => {
         }
       }
       if (message.payload.autoRunSkipFailures !== undefined) {
-        inputAutoSkipFailures.checked = true;
+        inputAutoSkipFailures.checked = Boolean(message.payload.autoRunSkipFailures);
         updateFallbackThreadIntervalInputState();
       }
       if (message.payload.autoRunDelayEnabled !== undefined) {