feat: support configurable random delays between auto steps
- 合并 PR #44 的核心改动:新增 Auto 步间随机延迟配置,并在后台执行前应用该随机等待\n- 本地补充修复:无\n- 影响范围:background auto orchestration、sidepanel auto settings、README、tests
This commit is contained in:
@@ -324,6 +324,10 @@ Cloudflare 模式下,插件不会再调用 Cloudflare API 创建路由。
|
|||||||
|
|
||||||
支持多轮运行,运行次数由右上角数字框决定。
|
支持多轮运行,运行次数由右上角数字框决定。
|
||||||
|
|
||||||
|
`延迟` 里的“启动前倒计时”只控制整轮 Auto 开始前要不要先倒计时多少分钟。
|
||||||
|
|
||||||
|
`步间随机` 控制 Auto 流程里**每一步真正执行前**的随机等待秒数,默认是 `12 ~ 18` 秒。这个设置只影响 Auto,不影响你手动单步点击执行。
|
||||||
|
|
||||||
如果当前面板里已经存在未完成进度,点击 `Auto` 时会弹出选择:
|
如果当前面板里已经存在未完成进度,点击 `Auto` 时会弹出选择:
|
||||||
|
|
||||||
- `重新开始`:重置当前流程进度,从 Step 1 开始新一轮
|
- `重新开始`:重置当前流程进度,从 Step 1 开始新一轮
|
||||||
@@ -366,6 +370,7 @@ Cloudflare 模式下,插件不会再调用 Cloudflare API 创建路由。
|
|||||||
- Auto 的暂停状态会保存在会话状态中,重新打开侧边栏后仍可继续
|
- Auto 的暂停状态会保存在会话状态中,重新打开侧边栏后仍可继续
|
||||||
- 如果你在 Auto 暂停时改为手动点步骤或跳过步骤,面板会先确认并停止 Auto,再切回手动控制
|
- 如果你在 Auto 暂停时改为手动点步骤或跳过步骤,面板会先确认并停止 Auto,再切回手动控制
|
||||||
- 选择 `继续当前` 时,后台不会先做大而全的前置校验,而是从当前步骤状态直接继续;缺什么条件,就在运行到那一步时再报错或暂停
|
- 选择 `继续当前` 时,后台不会先做大而全的前置校验,而是从当前步骤状态直接继续;缺什么条件,就在运行到那一步时再报错或暂停
|
||||||
|
- 除了现有的页面切换等待外,Auto 还会在每一步执行前额外随机等待一段时间;默认是 `12 ~ 18` 秒,可在侧边栏 `步间随机` 中改成你自己的范围
|
||||||
|
|
||||||
## 详细步骤说明
|
## 详细步骤说明
|
||||||
|
|
||||||
|
|||||||
@@ -39,6 +39,10 @@ const DEFAULT_SUB2API_REDIRECT_URI = 'http://localhost:1455/auth/callback';
|
|||||||
const AUTO_RUN_ALARM_NAME = 'scheduled-auto-run';
|
const AUTO_RUN_ALARM_NAME = 'scheduled-auto-run';
|
||||||
const AUTO_RUN_DELAY_MIN_MINUTES = 1;
|
const AUTO_RUN_DELAY_MIN_MINUTES = 1;
|
||||||
const AUTO_RUN_DELAY_MAX_MINUTES = 1440;
|
const AUTO_RUN_DELAY_MAX_MINUTES = 1440;
|
||||||
|
const AUTO_STEP_RANDOM_DELAY_MIN_ALLOWED_SECONDS = 0;
|
||||||
|
const AUTO_STEP_RANDOM_DELAY_MAX_ALLOWED_SECONDS = 600;
|
||||||
|
const AUTO_STEP_RANDOM_DELAY_DEFAULT_MIN_SECONDS = 12;
|
||||||
|
const AUTO_STEP_RANDOM_DELAY_DEFAULT_MAX_SECONDS = 18;
|
||||||
const DEFAULT_LOCAL_CPA_STEP9_MODE = 'submit';
|
const DEFAULT_LOCAL_CPA_STEP9_MODE = 'submit';
|
||||||
|
|
||||||
initializeSessionStorageAccess();
|
initializeSessionStorageAccess();
|
||||||
@@ -60,6 +64,8 @@ const PERSISTED_SETTING_DEFAULTS = {
|
|||||||
autoRunSkipFailures: false, // 自动运行遇到失败步骤后,是否继续执行后续流程。
|
autoRunSkipFailures: false, // 自动运行遇到失败步骤后,是否继续执行后续流程。
|
||||||
autoRunDelayEnabled: false, // 自动运行是否启用启动前倒计时。
|
autoRunDelayEnabled: false, // 自动运行是否启用启动前倒计时。
|
||||||
autoRunDelayMinutes: 30, // 自动运行倒计时分钟数。
|
autoRunDelayMinutes: 30, // 自动运行倒计时分钟数。
|
||||||
|
autoStepRandomDelayMinSeconds: AUTO_STEP_RANDOM_DELAY_DEFAULT_MIN_SECONDS, // 自动运行每一步执行前的最小随机等待秒数。
|
||||||
|
autoStepRandomDelayMaxSeconds: AUTO_STEP_RANDOM_DELAY_DEFAULT_MAX_SECONDS, // 自动运行每一步执行前的最大随机等待秒数。
|
||||||
mailProvider: '163', // 验证码邮箱来源(163 / 163-vip / qq / inbucket)。
|
mailProvider: '163', // 验证码邮箱来源(163 / 163-vip / qq / inbucket)。
|
||||||
emailGenerator: 'duck', // 注册邮箱生成方式:duck / cloudflare。
|
emailGenerator: 'duck', // 注册邮箱生成方式:duck / cloudflare。
|
||||||
inbucketHost: '', // 仅当 mailProvider 为 inbucket 时填写 Inbucket 地址,其他情况保持为空。
|
inbucketHost: '', // 仅当 mailProvider 为 inbucket 时填写 Inbucket 地址,其他情况保持为空。
|
||||||
@@ -119,6 +125,32 @@ function normalizeAutoRunDelayMinutes(value) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function normalizeAutoStepRandomDelaySeconds(value, fallback = AUTO_STEP_RANDOM_DELAY_DEFAULT_MIN_SECONDS) {
|
||||||
|
const numeric = Number(value);
|
||||||
|
if (!Number.isFinite(numeric)) {
|
||||||
|
return fallback;
|
||||||
|
}
|
||||||
|
return Math.min(
|
||||||
|
AUTO_STEP_RANDOM_DELAY_MAX_ALLOWED_SECONDS,
|
||||||
|
Math.max(AUTO_STEP_RANDOM_DELAY_MIN_ALLOWED_SECONDS, Math.floor(numeric))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function normalizeAutoStepRandomDelayRange(settings = {}) {
|
||||||
|
const minSeconds = normalizeAutoStepRandomDelaySeconds(
|
||||||
|
settings.autoStepRandomDelayMinSeconds,
|
||||||
|
PERSISTED_SETTING_DEFAULTS.autoStepRandomDelayMinSeconds
|
||||||
|
);
|
||||||
|
const maxSeconds = Math.max(
|
||||||
|
minSeconds,
|
||||||
|
normalizeAutoStepRandomDelaySeconds(
|
||||||
|
settings.autoStepRandomDelayMaxSeconds,
|
||||||
|
PERSISTED_SETTING_DEFAULTS.autoStepRandomDelayMaxSeconds
|
||||||
|
)
|
||||||
|
);
|
||||||
|
return { minSeconds, maxSeconds };
|
||||||
|
}
|
||||||
|
|
||||||
function normalizeRunCount(value) {
|
function normalizeRunCount(value) {
|
||||||
const numeric = Number(value);
|
const numeric = Number(value);
|
||||||
if (!Number.isFinite(numeric)) {
|
if (!Number.isFinite(numeric)) {
|
||||||
@@ -216,6 +248,16 @@ function normalizePersistentSettingValue(key, value) {
|
|||||||
return Boolean(value);
|
return Boolean(value);
|
||||||
case 'autoRunDelayMinutes':
|
case 'autoRunDelayMinutes':
|
||||||
return normalizeAutoRunDelayMinutes(value);
|
return normalizeAutoRunDelayMinutes(value);
|
||||||
|
case 'autoStepRandomDelayMinSeconds':
|
||||||
|
return normalizeAutoStepRandomDelaySeconds(
|
||||||
|
value,
|
||||||
|
PERSISTED_SETTING_DEFAULTS.autoStepRandomDelayMinSeconds
|
||||||
|
);
|
||||||
|
case 'autoStepRandomDelayMaxSeconds':
|
||||||
|
return normalizeAutoStepRandomDelaySeconds(
|
||||||
|
value,
|
||||||
|
PERSISTED_SETTING_DEFAULTS.autoStepRandomDelayMaxSeconds
|
||||||
|
);
|
||||||
case 'mailProvider':
|
case 'mailProvider':
|
||||||
return normalizeMailProvider(value);
|
return normalizeMailProvider(value);
|
||||||
case 'emailGenerator':
|
case 'emailGenerator':
|
||||||
@@ -264,6 +306,15 @@ function buildPersistentSettingsPayload(input = {}, options = {}) {
|
|||||||
payload.cloudflareDomains = domains;
|
payload.cloudflareDomains = domains;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (payload.autoStepRandomDelayMinSeconds !== undefined || payload.autoStepRandomDelayMaxSeconds !== undefined) {
|
||||||
|
const normalizedDelayRange = normalizeAutoStepRandomDelayRange({
|
||||||
|
autoStepRandomDelayMinSeconds: payload.autoStepRandomDelayMinSeconds,
|
||||||
|
autoStepRandomDelayMaxSeconds: payload.autoStepRandomDelayMaxSeconds,
|
||||||
|
});
|
||||||
|
payload.autoStepRandomDelayMinSeconds = normalizedDelayRange.minSeconds;
|
||||||
|
payload.autoStepRandomDelayMaxSeconds = normalizedDelayRange.maxSeconds;
|
||||||
|
}
|
||||||
|
|
||||||
return payload;
|
return payload;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2422,6 +2473,12 @@ async function humanStepDelay(min = HUMAN_STEP_DELAY_MIN, max = HUMAN_STEP_DELAY
|
|||||||
await sleepWithStop(duration);
|
await sleepWithStop(duration);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getAutoStepRandomDelayMs(min, max) {
|
||||||
|
const normalizedMin = Math.max(0, Math.floor(Number(min) || 0));
|
||||||
|
const normalizedMax = Math.max(normalizedMin, Math.floor(Number(max) || normalizedMin));
|
||||||
|
return Math.floor(Math.random() * (normalizedMax - normalizedMin + 1)) + normalizedMin;
|
||||||
|
}
|
||||||
|
|
||||||
async function clickWithDebugger(tabId, rect) {
|
async function clickWithDebugger(tabId, rect) {
|
||||||
throwIfStopped();
|
throwIfStopped();
|
||||||
if (!tabId) {
|
if (!tabId) {
|
||||||
@@ -3028,6 +3085,19 @@ async function executeStep(step) {
|
|||||||
async function executeStepAndWait(step, delayAfter = 2000) {
|
async function executeStepAndWait(step, delayAfter = 2000) {
|
||||||
throwIfStopped();
|
throwIfStopped();
|
||||||
|
|
||||||
|
const delaySettings = normalizeAutoStepRandomDelayRange(await getState());
|
||||||
|
const randomDelayMs = getAutoStepRandomDelayMs(
|
||||||
|
delaySettings.minSeconds * 1000,
|
||||||
|
delaySettings.maxSeconds * 1000
|
||||||
|
);
|
||||||
|
if (randomDelayMs > 0) {
|
||||||
|
await addLog(
|
||||||
|
`自动运行:步骤 ${step} 执行前随机等待 ${delaySettings.minSeconds}-${delaySettings.maxSeconds} 秒,本次约 ${Math.round(randomDelayMs / 1000)} 秒,避免节奏过快。`,
|
||||||
|
'info'
|
||||||
|
);
|
||||||
|
await sleepWithStop(randomDelayMs);
|
||||||
|
}
|
||||||
|
|
||||||
if (AUTO_RUN_BACKGROUND_COMPLETED_STEPS.has(step)) {
|
if (AUTO_RUN_BACKGROUND_COMPLETED_STEPS.has(step)) {
|
||||||
await addLog(`自动运行:步骤 ${step} 由后台流程负责收尾,执行函数返回后将直接进入下一步。`, 'info');
|
await addLog(`自动运行:步骤 ${step} 由后台流程负责收尾,执行函数返回后将直接进入下一步。`, 'info');
|
||||||
await executeStep(step);
|
await executeStep(step);
|
||||||
@@ -3376,6 +3446,8 @@ async function autoRunLoop(totalRuns, options = {}) {
|
|||||||
autoRunSkipFailures: prevState.autoRunSkipFailures,
|
autoRunSkipFailures: prevState.autoRunSkipFailures,
|
||||||
autoRunDelayEnabled: prevState.autoRunDelayEnabled,
|
autoRunDelayEnabled: prevState.autoRunDelayEnabled,
|
||||||
autoRunDelayMinutes: prevState.autoRunDelayMinutes,
|
autoRunDelayMinutes: prevState.autoRunDelayMinutes,
|
||||||
|
autoStepRandomDelayMinSeconds: prevState.autoStepRandomDelayMinSeconds,
|
||||||
|
autoStepRandomDelayMaxSeconds: prevState.autoStepRandomDelayMaxSeconds,
|
||||||
mailProvider: prevState.mailProvider,
|
mailProvider: prevState.mailProvider,
|
||||||
emailGenerator: prevState.emailGenerator,
|
emailGenerator: prevState.emailGenerator,
|
||||||
inbucketHost: prevState.inbucketHost,
|
inbucketHost: prevState.inbucketHost,
|
||||||
|
|||||||
@@ -192,6 +192,19 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="data-row">
|
||||||
|
<span class="data-label">步间随机</span>
|
||||||
|
<div class="data-inline auto-delay-inline">
|
||||||
|
<div class="auto-delay-controls">
|
||||||
|
<input type="number" id="input-auto-step-delay-min-seconds" class="data-input auto-delay-input" value="12" min="0"
|
||||||
|
max="600" step="1" title="自动运行每一步执行前的最小随机等待秒数" />
|
||||||
|
<span class="data-unit">~</span>
|
||||||
|
<input type="number" id="input-auto-step-delay-max-seconds" class="data-input auto-delay-input" value="18" min="0"
|
||||||
|
max="600" step="1" title="自动运行每一步执行前的最大随机等待秒数" />
|
||||||
|
<span class="data-unit">秒</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<div class="data-row">
|
<div class="data-row">
|
||||||
<span class="data-label">OAuth</span>
|
<span class="data-label">OAuth</span>
|
||||||
<span id="display-oauth-url" class="data-value mono truncate">等待中...</span>
|
<span id="display-oauth-url" class="data-value mono truncate">等待中...</span>
|
||||||
|
|||||||
@@ -85,6 +85,8 @@ const inputRunCount = document.getElementById('input-run-count');
|
|||||||
const inputAutoSkipFailures = document.getElementById('input-auto-skip-failures');
|
const inputAutoSkipFailures = document.getElementById('input-auto-skip-failures');
|
||||||
const inputAutoDelayEnabled = document.getElementById('input-auto-delay-enabled');
|
const inputAutoDelayEnabled = document.getElementById('input-auto-delay-enabled');
|
||||||
const inputAutoDelayMinutes = document.getElementById('input-auto-delay-minutes');
|
const inputAutoDelayMinutes = document.getElementById('input-auto-delay-minutes');
|
||||||
|
const inputAutoStepDelayMinSeconds = document.getElementById('input-auto-step-delay-min-seconds');
|
||||||
|
const inputAutoStepDelayMaxSeconds = document.getElementById('input-auto-step-delay-max-seconds');
|
||||||
const autoStartModal = document.getElementById('auto-start-modal');
|
const autoStartModal = document.getElementById('auto-start-modal');
|
||||||
const autoStartTitle = autoStartModal?.querySelector('.modal-title');
|
const autoStartTitle = autoStartModal?.querySelector('.modal-title');
|
||||||
const autoStartMessage = document.getElementById('auto-start-message');
|
const autoStartMessage = document.getElementById('auto-start-message');
|
||||||
@@ -108,6 +110,10 @@ const SKIPPABLE_STEPS = new Set([1, 2, 3, 4, 5, 6, 7, 8, 9]);
|
|||||||
const AUTO_DELAY_MIN_MINUTES = 1;
|
const AUTO_DELAY_MIN_MINUTES = 1;
|
||||||
const AUTO_DELAY_MAX_MINUTES = 1440;
|
const AUTO_DELAY_MAX_MINUTES = 1440;
|
||||||
const AUTO_DELAY_DEFAULT_MINUTES = 30;
|
const AUTO_DELAY_DEFAULT_MINUTES = 30;
|
||||||
|
const AUTO_STEP_DELAY_MIN_SECONDS = 0;
|
||||||
|
const AUTO_STEP_DELAY_MAX_SECONDS = 600;
|
||||||
|
const AUTO_STEP_DELAY_DEFAULT_MIN_SECONDS = 12;
|
||||||
|
const AUTO_STEP_DELAY_DEFAULT_MAX_SECONDS = 18;
|
||||||
const DEFAULT_LOCAL_CPA_STEP9_MODE = 'submit';
|
const DEFAULT_LOCAL_CPA_STEP9_MODE = 'submit';
|
||||||
|
|
||||||
let latestState = null;
|
let latestState = null;
|
||||||
@@ -447,6 +453,23 @@ function normalizeAutoDelayMinutes(value) {
|
|||||||
return Math.min(AUTO_DELAY_MAX_MINUTES, Math.max(AUTO_DELAY_MIN_MINUTES, Math.floor(numeric)));
|
return Math.min(AUTO_DELAY_MAX_MINUTES, Math.max(AUTO_DELAY_MIN_MINUTES, Math.floor(numeric)));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function normalizeAutoStepDelaySeconds(value, fallback = AUTO_STEP_DELAY_DEFAULT_MIN_SECONDS) {
|
||||||
|
const numeric = Number(value);
|
||||||
|
if (!Number.isFinite(numeric)) {
|
||||||
|
return fallback;
|
||||||
|
}
|
||||||
|
return Math.min(AUTO_STEP_DELAY_MAX_SECONDS, Math.max(AUTO_STEP_DELAY_MIN_SECONDS, Math.floor(numeric)));
|
||||||
|
}
|
||||||
|
|
||||||
|
function normalizeAutoStepDelayRange(minValue, maxValue) {
|
||||||
|
const minSeconds = normalizeAutoStepDelaySeconds(minValue, AUTO_STEP_DELAY_DEFAULT_MIN_SECONDS);
|
||||||
|
const maxSeconds = Math.max(
|
||||||
|
minSeconds,
|
||||||
|
normalizeAutoStepDelaySeconds(maxValue, AUTO_STEP_DELAY_DEFAULT_MAX_SECONDS)
|
||||||
|
);
|
||||||
|
return { minSeconds, maxSeconds };
|
||||||
|
}
|
||||||
|
|
||||||
function updateAutoDelayInputState() {
|
function updateAutoDelayInputState() {
|
||||||
const scheduled = isAutoRunScheduledPhase();
|
const scheduled = isAutoRunScheduledPhase();
|
||||||
inputAutoDelayEnabled.disabled = scheduled;
|
inputAutoDelayEnabled.disabled = scheduled;
|
||||||
@@ -598,6 +621,10 @@ function collectSettingsPayload() {
|
|||||||
const selectedCloudflareDomain = normalizeCloudflareDomainValue(
|
const selectedCloudflareDomain = normalizeCloudflareDomainValue(
|
||||||
!cloudflareDomainEditMode ? selectCfDomain.value : activeDomain
|
!cloudflareDomainEditMode ? selectCfDomain.value : activeDomain
|
||||||
) || activeDomain;
|
) || activeDomain;
|
||||||
|
const autoStepDelayRange = normalizeAutoStepDelayRange(
|
||||||
|
inputAutoStepDelayMinSeconds.value,
|
||||||
|
inputAutoStepDelayMaxSeconds.value
|
||||||
|
);
|
||||||
return {
|
return {
|
||||||
panelMode: selectPanelMode.value,
|
panelMode: selectPanelMode.value,
|
||||||
vpsUrl: inputVpsUrl.value.trim(),
|
vpsUrl: inputVpsUrl.value.trim(),
|
||||||
@@ -617,6 +644,8 @@ function collectSettingsPayload() {
|
|||||||
autoRunSkipFailures: inputAutoSkipFailures.checked,
|
autoRunSkipFailures: inputAutoSkipFailures.checked,
|
||||||
autoRunDelayEnabled: inputAutoDelayEnabled.checked,
|
autoRunDelayEnabled: inputAutoDelayEnabled.checked,
|
||||||
autoRunDelayMinutes: normalizeAutoDelayMinutes(inputAutoDelayMinutes.value),
|
autoRunDelayMinutes: normalizeAutoDelayMinutes(inputAutoDelayMinutes.value),
|
||||||
|
autoStepRandomDelayMinSeconds: autoStepDelayRange.minSeconds,
|
||||||
|
autoStepRandomDelayMaxSeconds: autoStepDelayRange.maxSeconds,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -858,6 +887,12 @@ function applySettingsState(state) {
|
|||||||
inputAutoSkipFailures.checked = Boolean(state?.autoRunSkipFailures);
|
inputAutoSkipFailures.checked = Boolean(state?.autoRunSkipFailures);
|
||||||
inputAutoDelayEnabled.checked = Boolean(state?.autoRunDelayEnabled);
|
inputAutoDelayEnabled.checked = Boolean(state?.autoRunDelayEnabled);
|
||||||
inputAutoDelayMinutes.value = String(normalizeAutoDelayMinutes(state?.autoRunDelayMinutes));
|
inputAutoDelayMinutes.value = String(normalizeAutoDelayMinutes(state?.autoRunDelayMinutes));
|
||||||
|
const autoStepDelayRange = normalizeAutoStepDelayRange(
|
||||||
|
state?.autoStepRandomDelayMinSeconds,
|
||||||
|
state?.autoStepRandomDelayMaxSeconds
|
||||||
|
);
|
||||||
|
inputAutoStepDelayMinSeconds.value = String(autoStepDelayRange.minSeconds);
|
||||||
|
inputAutoStepDelayMaxSeconds.value = String(autoStepDelayRange.maxSeconds);
|
||||||
if (state?.autoRunTotalRuns) {
|
if (state?.autoRunTotalRuns) {
|
||||||
inputRunCount.value = String(state.autoRunTotalRuns);
|
inputRunCount.value = String(state.autoRunTotalRuns);
|
||||||
}
|
}
|
||||||
@@ -2380,6 +2415,33 @@ inputAutoDelayMinutes.addEventListener('blur', () => {
|
|||||||
saveSettings({ silent: true }).catch(() => { });
|
saveSettings({ silent: true }).catch(() => { });
|
||||||
});
|
});
|
||||||
|
|
||||||
|
function syncAutoStepDelayInputs() {
|
||||||
|
const range = normalizeAutoStepDelayRange(
|
||||||
|
inputAutoStepDelayMinSeconds.value,
|
||||||
|
inputAutoStepDelayMaxSeconds.value
|
||||||
|
);
|
||||||
|
inputAutoStepDelayMinSeconds.value = String(range.minSeconds);
|
||||||
|
inputAutoStepDelayMaxSeconds.value = String(range.maxSeconds);
|
||||||
|
}
|
||||||
|
|
||||||
|
inputAutoStepDelayMinSeconds.addEventListener('input', () => {
|
||||||
|
markSettingsDirty(true);
|
||||||
|
scheduleSettingsAutoSave();
|
||||||
|
});
|
||||||
|
inputAutoStepDelayMinSeconds.addEventListener('blur', () => {
|
||||||
|
syncAutoStepDelayInputs();
|
||||||
|
saveSettings({ silent: true }).catch(() => { });
|
||||||
|
});
|
||||||
|
|
||||||
|
inputAutoStepDelayMaxSeconds.addEventListener('input', () => {
|
||||||
|
markSettingsDirty(true);
|
||||||
|
scheduleSettingsAutoSave();
|
||||||
|
});
|
||||||
|
inputAutoStepDelayMaxSeconds.addEventListener('blur', () => {
|
||||||
|
syncAutoStepDelayInputs();
|
||||||
|
saveSettings({ silent: true }).catch(() => { });
|
||||||
|
});
|
||||||
|
|
||||||
// ============================================================
|
// ============================================================
|
||||||
// Listen for Background broadcasts
|
// Listen for Background broadcasts
|
||||||
// ============================================================
|
// ============================================================
|
||||||
@@ -2477,6 +2539,14 @@ chrome.runtime.onMessage.addListener((message) => {
|
|||||||
if (message.payload.autoRunDelayMinutes !== undefined) {
|
if (message.payload.autoRunDelayMinutes !== undefined) {
|
||||||
inputAutoDelayMinutes.value = String(normalizeAutoDelayMinutes(message.payload.autoRunDelayMinutes));
|
inputAutoDelayMinutes.value = String(normalizeAutoDelayMinutes(message.payload.autoRunDelayMinutes));
|
||||||
}
|
}
|
||||||
|
if (message.payload.autoStepRandomDelayMinSeconds !== undefined || message.payload.autoStepRandomDelayMaxSeconds !== undefined) {
|
||||||
|
const autoStepDelayRange = normalizeAutoStepDelayRange(
|
||||||
|
message.payload.autoStepRandomDelayMinSeconds ?? inputAutoStepDelayMinSeconds.value,
|
||||||
|
message.payload.autoStepRandomDelayMaxSeconds ?? inputAutoStepDelayMaxSeconds.value
|
||||||
|
);
|
||||||
|
inputAutoStepDelayMinSeconds.value = String(autoStepDelayRange.minSeconds);
|
||||||
|
inputAutoStepDelayMaxSeconds.value = String(autoStepDelayRange.maxSeconds);
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,105 @@
|
|||||||
|
const assert = require('assert');
|
||||||
|
const fs = require('fs');
|
||||||
|
|
||||||
|
const source = fs.readFileSync('background.js', 'utf8');
|
||||||
|
|
||||||
|
function extractFunction(name) {
|
||||||
|
const start = source.indexOf(`function ${name}(`);
|
||||||
|
if (start < 0) {
|
||||||
|
throw new Error(`missing function ${name}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const paramsStart = source.indexOf('(', start);
|
||||||
|
let paramsDepth = 0;
|
||||||
|
let paramsEnd = paramsStart;
|
||||||
|
for (; paramsEnd < source.length; paramsEnd += 1) {
|
||||||
|
const ch = source[paramsEnd];
|
||||||
|
if (ch === '(') paramsDepth += 1;
|
||||||
|
if (ch === ')') {
|
||||||
|
paramsDepth -= 1;
|
||||||
|
if (paramsDepth === 0) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const braceStart = source.indexOf('{', paramsEnd);
|
||||||
|
let depth = 0;
|
||||||
|
let end = braceStart;
|
||||||
|
for (; end < source.length; end++) {
|
||||||
|
const ch = source[end];
|
||||||
|
if (ch === '{') depth += 1;
|
||||||
|
if (ch === '}') {
|
||||||
|
depth -= 1;
|
||||||
|
if (depth === 0) {
|
||||||
|
end += 1;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return source.slice(start, end);
|
||||||
|
}
|
||||||
|
|
||||||
|
const bundle = [
|
||||||
|
'const AUTO_STEP_RANDOM_DELAY_MIN_ALLOWED_SECONDS = 0;',
|
||||||
|
'const AUTO_STEP_RANDOM_DELAY_MAX_ALLOWED_SECONDS = 600;',
|
||||||
|
'const AUTO_STEP_RANDOM_DELAY_DEFAULT_MIN_SECONDS = 12;',
|
||||||
|
'const AUTO_STEP_RANDOM_DELAY_DEFAULT_MAX_SECONDS = 18;',
|
||||||
|
'const PERSISTED_SETTING_DEFAULTS = { autoStepRandomDelayMinSeconds: 12, autoStepRandomDelayMaxSeconds: 18 };',
|
||||||
|
extractFunction('normalizeAutoStepRandomDelaySeconds'),
|
||||||
|
extractFunction('normalizeAutoStepRandomDelayRange'),
|
||||||
|
extractFunction('getAutoStepRandomDelayMs'),
|
||||||
|
].join('\n');
|
||||||
|
|
||||||
|
const api = new Function(`${bundle}; return { normalizeAutoStepRandomDelaySeconds, normalizeAutoStepRandomDelayRange, getAutoStepRandomDelayMs };`)();
|
||||||
|
|
||||||
|
const defaultRange = api.normalizeAutoStepRandomDelayRange({});
|
||||||
|
assert.deepStrictEqual(defaultRange, { minSeconds: 12, maxSeconds: 18 }, 'missing settings should fall back to the default 12-18 second range');
|
||||||
|
|
||||||
|
for (let i = 0; i < 200; i += 1) {
|
||||||
|
const delay = api.getAutoStepRandomDelayMs(defaultRange.minSeconds * 1000, defaultRange.maxSeconds * 1000);
|
||||||
|
assert.ok(delay >= 12000, `delay ${delay} should respect the lower bound`);
|
||||||
|
assert.ok(delay <= 18000, `delay ${delay} should respect the upper bound`);
|
||||||
|
assert.ok(Number.isInteger(delay), `delay ${delay} should be an integer`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const samples = new Set(Array.from({ length: 50 }, () => api.getAutoStepRandomDelayMs(defaultRange.minSeconds * 1000, defaultRange.maxSeconds * 1000)));
|
||||||
|
assert.ok(samples.size > 1, 'delay helper should produce randomized values');
|
||||||
|
|
||||||
|
const customRange = api.normalizeAutoStepRandomDelayRange({
|
||||||
|
autoStepRandomDelayMinSeconds: 5,
|
||||||
|
autoStepRandomDelayMaxSeconds: 7,
|
||||||
|
});
|
||||||
|
assert.deepStrictEqual(customRange, { minSeconds: 5, maxSeconds: 7 }, 'configured ranges should be preserved when already valid');
|
||||||
|
|
||||||
|
const collapsedRange = api.normalizeAutoStepRandomDelayRange({
|
||||||
|
autoStepRandomDelayMinSeconds: 20,
|
||||||
|
autoStepRandomDelayMaxSeconds: 10,
|
||||||
|
});
|
||||||
|
assert.deepStrictEqual(collapsedRange, { minSeconds: 20, maxSeconds: 20 }, 'max delay should collapse up to min delay when user input is inverted');
|
||||||
|
|
||||||
|
assert.strictEqual(
|
||||||
|
api.getAutoStepRandomDelayMs(15000, 15000),
|
||||||
|
15000,
|
||||||
|
'equal bounds should produce a deterministic delay'
|
||||||
|
);
|
||||||
|
|
||||||
|
const originalRandom = Math.random;
|
||||||
|
try {
|
||||||
|
Math.random = () => 0.6;
|
||||||
|
assert.strictEqual(
|
||||||
|
api.normalizeAutoStepRandomDelaySeconds(-50, 12),
|
||||||
|
0,
|
||||||
|
'negative configured delay should clamp to zero seconds'
|
||||||
|
);
|
||||||
|
assert.strictEqual(
|
||||||
|
api.getAutoStepRandomDelayMs(-50, 10),
|
||||||
|
6,
|
||||||
|
'lower bound should be clamped to zero and still allow random output'
|
||||||
|
);
|
||||||
|
} finally {
|
||||||
|
Math.random = originalRandom;
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('auto step random delay tests passed');
|
||||||
Reference in New Issue
Block a user