From 2f0495fb6eb5f71e687b4e1fdb76bd95221430e6 Mon Sep 17 00:00:00 2001 From: QLHazyCoder <2825305047@qq.com> Date: Tue, 14 Apr 2026 12:48:52 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20=E4=BF=AE=E6=94=B9=E8=87=AA=E5=8A=A8?= =?UTF-8?q?=E8=BF=90=E8=A1=8C=E5=A4=B1=E8=B4=A5=E8=B7=B3=E8=BF=87=E8=AE=BE?= =?UTF-8?q?=E7=BD=AE=EF=BC=8C=E7=A1=AE=E4=BF=9D=E7=8A=B6=E6=80=81=E4=B8=80?= =?UTF-8?q?=E8=87=B4=E6=80=A7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- background.js | 111 +++++++++++++++++++++++++++++++++++----- sidepanel/sidepanel.css | 8 +-- sidepanel/sidepanel.js | 43 +++++++++++----- 3 files changed, 134 insertions(+), 28 deletions(-) diff --git a/background.js b/background.js index fedb60d..9d623c6 100644 --- a/background.js +++ b/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; + 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 <= AUTO_RUN_MAX_RETRIES_PER_ROUND + 1) { + 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, diff --git a/sidepanel/sidepanel.css b/sidepanel/sidepanel.css index d097ed4..8d0ffe3 100644 --- a/sidepanel/sidepanel.css +++ b/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 { diff --git a/sidepanel/sidepanel.js b/sidepanel/sidepanel.js index bb9aa02..0ed7e32 100644 --- a/sidepanel/sidepanel.js +++ b/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) {