添加交流群,修改界面样式,增加步间间隔功能

This commit is contained in:
QLHazyCoder
2026-04-13 20:25:53 +08:00
parent b487c2c33c
commit 9ba958b1a2
10 changed files with 234 additions and 225 deletions
+5 -5
View File
@@ -4,14 +4,14 @@
当前版本基于侧边栏控制,支持单步执行、整套自动执行、停止当前流程、保存常用配置,以及通过 DuckDuckGo / QQ / 163 / Inbucket / Hotmail 协助获取验证码。
## 最新版本测试结
## 插件效
最新版本实测了一个 5 轮自动,0 次失重试;睡前挂了一个十轮自动,1次重试
一百五十个号,一个401
<table>
<tr>
<td align="center" width="50%">
<img src="docs/images/五轮自动.png" alt="最新版本五轮测试结果" width="100%" />
<img src="docs/images/交流群.jpg" alt="QQ交流群,便于大家交流" width="100%" />
</td>
<td align="center" width="50%">
<img src="docs/images/十轮自动.png" alt="最新版本运行日志" width="100%" />
@@ -326,7 +326,7 @@ Cloudflare 模式下,插件不会再调用 Cloudflare API 创建路由。
`延迟` 里的“启动前倒计时”只控制整轮 Auto 开始前要不要先倒计时多少分钟。
`步间随机` 控制 Auto 流程里**每一步真正执行前**的随机等待秒数,默认是 `12 ~ 18`。这个设置只影响 Auto,不影响你手动单步点击执行。
`步间随机` 控制 Auto 流程里**每一步真正执行前**的额外等待秒数。这个设置只影响 Auto,不影响你手动单步点击执行;填 `0` 或留空表示不延迟
如果当前面板里已经存在未完成进度,点击 `Auto` 时会弹出选择:
@@ -370,7 +370,7 @@ Cloudflare 模式下,插件不会再调用 Cloudflare API 创建路由。
- Auto 的暂停状态会保存在会话状态中,重新打开侧边栏后仍可继续
- 如果你在 Auto 暂停时改为手动点步骤或跳过步骤,面板会先确认并停止 Auto,再切回手动控制
- 选择 `继续当前` 时,后台不会先做大而全的前置校验,而是从当前步骤状态直接继续;缺什么条件,就在运行到那一步时再报错或暂停
- 除了现有的页面切换等待外,Auto 还会在每一步执行前额外随机等待一段时间;默认是 `12 ~ 18` 秒,可在侧边栏 `步间随机` 中改成你自己的范围
- 除了现有的页面切换等待外,Auto 还会在每一步执行前`步间随机` 的秒数额外等待;填 `0` 或留空表示不延迟
## 详细步骤说明
+51 -61
View File
@@ -39,10 +39,9 @@ const DEFAULT_SUB2API_REDIRECT_URI = 'http://localhost:1455/auth/callback';
const AUTO_RUN_ALARM_NAME = 'scheduled-auto-run';
const AUTO_RUN_DELAY_MIN_MINUTES = 1;
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 AUTO_STEP_DELAY_MIN_ALLOWED_SECONDS = 0;
const AUTO_STEP_DELAY_MAX_ALLOWED_SECONDS = 600;
const LEGACY_AUTO_STEP_DELAY_KEYS = ['autoStepRandomDelayMinSeconds', 'autoStepRandomDelayMaxSeconds'];
const DEFAULT_LOCAL_CPA_STEP9_MODE = 'submit';
initializeSessionStorageAccess();
@@ -64,8 +63,7 @@ const PERSISTED_SETTING_DEFAULTS = {
autoRunSkipFailures: false, // 自动运行遇到失败步骤后,是否继续执行后续流程。
autoRunDelayEnabled: false, // 自动运行是否启用启动前倒计时。
autoRunDelayMinutes: 30, // 自动运行倒计时分钟数。
autoStepRandomDelayMinSeconds: AUTO_STEP_RANDOM_DELAY_DEFAULT_MIN_SECONDS, // 自动运行每一步执行前的最小随机等待秒数
autoStepRandomDelayMaxSeconds: AUTO_STEP_RANDOM_DELAY_DEFAULT_MAX_SECONDS, // 自动运行每一步执行前的最大随机等待秒数。
autoStepDelaySeconds: null, // 自动运行每一步执行前的额外等待秒数;0 或空表示不延迟
mailProvider: '163', // 验证码邮箱来源(163 / 163-vip / qq / inbucket)。
emailGenerator: 'duck', // 注册邮箱生成方式:duck / cloudflare。
inbucketHost: '', // 仅当 mailProvider 为 inbucket 时填写 Inbucket 地址,其他情况保持为空。
@@ -125,30 +123,42 @@ function normalizeAutoRunDelayMinutes(value) {
);
}
function normalizeAutoStepRandomDelaySeconds(value, fallback = AUTO_STEP_RANDOM_DELAY_DEFAULT_MIN_SECONDS) {
const numeric = Number(value);
function normalizeAutoStepDelaySeconds(value, fallback = null) {
const rawValue = String(value ?? '').trim();
if (!rawValue) {
return fallback;
}
const numeric = Number(rawValue);
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))
AUTO_STEP_DELAY_MAX_ALLOWED_SECONDS,
Math.max(AUTO_STEP_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 resolveLegacyAutoStepDelaySeconds(input = {}) {
const hasLegacyMin = input.autoStepRandomDelayMinSeconds !== undefined;
const hasLegacyMax = input.autoStepRandomDelayMaxSeconds !== undefined;
if (!hasLegacyMin && !hasLegacyMax) {
return undefined;
}
const minSeconds = normalizeAutoStepDelaySeconds(input.autoStepRandomDelayMinSeconds, null);
const maxSeconds = normalizeAutoStepDelaySeconds(input.autoStepRandomDelayMaxSeconds, null);
if (minSeconds === null && maxSeconds === null) {
return null;
}
if (minSeconds === null) {
return maxSeconds;
}
if (maxSeconds === null) {
return minSeconds;
}
return Math.round((minSeconds + maxSeconds) / 2);
}
function normalizeRunCount(value) {
@@ -248,16 +258,8 @@ function normalizePersistentSettingValue(key, value) {
return Boolean(value);
case 'autoRunDelayMinutes':
return normalizeAutoRunDelayMinutes(value);
case 'autoStepRandomDelayMinSeconds':
return normalizeAutoStepRandomDelaySeconds(
value,
PERSISTED_SETTING_DEFAULTS.autoStepRandomDelayMinSeconds
);
case 'autoStepRandomDelayMaxSeconds':
return normalizeAutoStepRandomDelaySeconds(
value,
PERSISTED_SETTING_DEFAULTS.autoStepRandomDelayMaxSeconds
);
case 'autoStepDelaySeconds':
return normalizeAutoStepDelaySeconds(value, PERSISTED_SETTING_DEFAULTS.autoStepDelaySeconds);
case 'mailProvider':
return normalizeMailProvider(value);
case 'emailGenerator':
@@ -283,11 +285,19 @@ function buildPersistentSettingsPayload(input = {}, options = {}) {
throw new Error('\u914d\u7f6e\u5185\u5bb9\u683c\u5f0f\u65e0\u6548\u3002');
}
const normalizedInput = { ...input };
if (normalizedInput.autoStepDelaySeconds === undefined) {
const legacyAutoStepDelaySeconds = resolveLegacyAutoStepDelaySeconds(normalizedInput);
if (legacyAutoStepDelaySeconds !== undefined) {
normalizedInput.autoStepDelaySeconds = legacyAutoStepDelaySeconds;
}
}
const payload = {};
let matchedKeyCount = 0;
for (const key of PERSISTED_SETTING_KEYS) {
if (input[key] !== undefined) {
payload[key] = normalizePersistentSettingValue(key, input[key]);
if (normalizedInput[key] !== undefined) {
payload[key] = normalizePersistentSettingValue(key, normalizedInput[key]);
matchedKeyCount += 1;
} else if (fillDefaults) {
payload[key] = normalizePersistentSettingValue(key, PERSISTED_SETTING_DEFAULTS[key]);
@@ -306,20 +316,11 @@ function buildPersistentSettingsPayload(input = {}, options = {}) {
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;
}
async function getPersistedSettings() {
const stored = await chrome.storage.local.get(PERSISTED_SETTING_KEYS);
const stored = await chrome.storage.local.get([...PERSISTED_SETTING_KEYS, ...LEGACY_AUTO_STEP_DELAY_KEYS]);
return buildPersistentSettingsPayload(stored, { fillDefaults: true });
}
@@ -2473,12 +2474,6 @@ async function humanStepDelay(min = HUMAN_STEP_DELAY_MIN, max = HUMAN_STEP_DELAY
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) {
throwIfStopped();
if (!tabId) {
@@ -3085,17 +3080,13 @@ async function executeStep(step) {
async function executeStepAndWait(step, delayAfter = 2000) {
throwIfStopped();
const delaySettings = normalizeAutoStepRandomDelayRange(await getState());
const randomDelayMs = getAutoStepRandomDelayMs(
delaySettings.minSeconds * 1000,
delaySettings.maxSeconds * 1000
);
if (randomDelayMs > 0) {
const delaySeconds = normalizeAutoStepDelaySeconds((await getState()).autoStepDelaySeconds, null);
if (delaySeconds > 0) {
await addLog(
`自动运行:步骤 ${step} 执行前随机等待 ${delaySettings.minSeconds}-${delaySettings.maxSeconds} 秒,本次约 ${Math.round(randomDelayMs / 1000)} 秒,避免节奏过快。`,
`自动运行:步骤 ${step} 执行前额外等待 ${delaySeconds} 秒,避免节奏过快。`,
'info'
);
await sleepWithStop(randomDelayMs);
await sleepWithStop(delaySeconds * 1000);
}
if (AUTO_RUN_BACKGROUND_COMPLETED_STEPS.has(step)) {
@@ -3446,8 +3437,7 @@ async function autoRunLoop(totalRuns, options = {}) {
autoRunSkipFailures: prevState.autoRunSkipFailures,
autoRunDelayEnabled: prevState.autoRunDelayEnabled,
autoRunDelayMinutes: prevState.autoRunDelayMinutes,
autoStepRandomDelayMinSeconds: prevState.autoStepRandomDelayMinSeconds,
autoStepRandomDelayMaxSeconds: prevState.autoStepRandomDelayMaxSeconds,
autoStepDelaySeconds: prevState.autoStepDelaySeconds,
mailProvider: prevState.mailProvider,
emailGenerator: prevState.emailGenerator,
inbucketHost: prevState.inbucketHost,
Binary file not shown.

Before

Width:  |  Height:  |  Size: 89 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 49 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 172 KiB

+1 -1
View File
@@ -1,7 +1,7 @@
{
"manifest_version": 3,
"name": "多页面自动化",
"version": "6.4.0",
"version": "7.0.0",
"description": "用于自动执行多步骤 OAuth 注册流程",
"permissions": [
"sidePanel",
+31 -4
View File
@@ -496,10 +496,10 @@ header {
box-shadow: 0 0 0 1px var(--blue-glow);
}
#btn-fetch-email,
#btn-save-settings {
padding-inline: 10px;
.data-inline-btn {
flex-shrink: 0;
min-width: 56px;
justify-content: center;
}
.hotmail-card {
@@ -724,10 +724,37 @@ header {
.auto-delay-inline {
justify-content: space-between;
gap: 12px;
flex-wrap: nowrap;
}
.auto-delay-side {
display: flex;
align-items: center;
gap: 12px;
flex: 0 1 auto;
min-width: 0;
}
.auto-delay-side-right {
margin-left: auto;
justify-content: flex-end;
}
.auto-delay-check {
flex: 0 1 auto;
flex: 0 0 auto;
align-items: center;
}
.auto-delay-check span {
white-space: nowrap;
}
.auto-delay-caption {
font-size: 12px;
font-weight: 600;
color: var(--text-muted);
flex: 0 0 auto;
white-space: nowrap;
}
.auto-delay-controls {
+46 -45
View File
@@ -90,7 +90,8 @@
<div class="data-row" id="row-vps-password">
<span class="data-label">管理密钥</span>
<div class="input-with-icon">
<input type="password" id="input-vps-password" class="data-input data-input-with-icon" placeholder="请输入 CPA 管理密钥" />
<input type="password" id="input-vps-password" class="data-input data-input-with-icon"
placeholder="请输入 CPA 管理密钥" />
<button id="btn-toggle-vps-password" class="input-icon-btn" type="button" aria-label="显示管理密钥"
title="显示管理密钥"></button>
</div>
@@ -104,7 +105,8 @@
</div>
<div class="data-row" id="row-sub2api-url" style="display:none;">
<span class="data-label">SUB2API</span>
<input type="text" id="input-sub2api-url" class="data-input" placeholder="https://sub2api.hisence.fun/admin/accounts" />
<input type="text" id="input-sub2api-url" class="data-input"
placeholder="https://sub2api.hisence.fun/admin/accounts" />
</div>
<div class="data-row" id="row-sub2api-email" style="display:none;">
<span class="data-label">账号</span>
@@ -119,12 +121,11 @@
<input type="text" id="input-sub2api-group" class="data-input" placeholder="默认 codex" />
</div>
<div class="data-row">
<span class="data-label">codex密码</span>
<span class="data-label">账户密码</span>
<div class="input-with-icon">
<input type="password" id="input-password" class="data-input data-input-with-icon"
placeholder="codex密码,留空则自动生成" />
<button id="btn-toggle-password" class="input-icon-btn" type="button" aria-label="显示密码"
title="显示密码"></button>
placeholder="codex密码,留空则自动生成" />
<button id="btn-toggle-password" class="input-icon-btn" type="button" aria-label="显示密码" title="显示密码"></button>
</div>
</div>
<div class="data-row">
@@ -133,11 +134,11 @@
<select id="select-mail-provider" class="data-select">
<option value="hotmail-api">Hotmail(微软 Graph</option>
<option value="163">163 邮箱 (mail.163.com)</option>
<option value="163-vip">163 VIP 邮箱 (webmail.vip.163.com)</option>
<option value="163-vip">163 VIP 邮箱 (vip.163.com)</option>
<option value="qq">QQ 邮箱 (wx.mail.qq.com)</option>
<option value="inbucket">Inbucket(自定义主机)</option>
</select>
<button id="btn-mail-login" class="btn btn-outline btn-sm" type="button" disabled>登录</button>
<button id="btn-mail-login" class="btn btn-outline btn-sm data-inline-btn" type="button" disabled>登录</button>
</div>
</div>
<div class="data-row" id="row-email-generator">
@@ -153,7 +154,7 @@
<select id="select-cf-domain" class="data-select"></select>
<input type="text" id="input-cf-domain" class="data-input" placeholder="例如 yourdomain.xyz"
style="display:none;" />
<button id="btn-cf-domain-mode" class="btn btn-outline btn-sm" type="button">添加</button>
<button id="btn-cf-domain-mode" class="btn btn-outline btn-sm data-inline-btn" type="button">添加</button>
</div>
</div>
<div class="data-row" id="row-inbucket-host" style="display:none;">
@@ -168,7 +169,7 @@
<span class="data-label">注册邮箱</span>
<div class="data-inline">
<input type="text" id="input-email" class="data-input" placeholder="自动生成或手动粘贴邮箱" />
<button id="btn-fetch-email" class="btn btn-outline btn-sm" type="button">获取</button>
<button id="btn-fetch-email" class="btn btn-outline btn-sm data-inline-btn" type="button">获取</button>
</div>
</div>
<div class="data-row data-check-row">
@@ -181,42 +182,39 @@
<div class="data-row">
<span class="data-label">延迟</span>
<div class="data-inline auto-delay-inline">
<label class="data-check auto-delay-check" for="input-auto-delay-enabled">
<input type="checkbox" id="input-auto-delay-enabled" />
<span>启动前倒计时</span>
</label>
<div class="auto-delay-controls">
<input type="number" id="input-auto-delay-minutes" class="data-input auto-delay-input" value="30" min="1"
max="1440" step="1" title="启动前倒计时分钟数" />
<span class="data-unit">分钟</span>
<div class="auto-delay-side auto-delay-side-left">
<label class="data-check auto-delay-check" for="input-auto-delay-enabled">
<input type="checkbox" id="input-auto-delay-enabled" />
</label>
<div class="auto-delay-controls">
<input type="number" id="input-auto-delay-minutes" class="data-input auto-delay-input" value="30" min="1"
max="1440" step="1" title="启动前倒计时分钟数" />
<span class="data-unit">分钟</span>
</div>
</div>
<div class="auto-delay-side auto-delay-side-right">
<span class="auto-delay-caption">步间间隔</span>
<div class="auto-delay-controls">
<input type="number" id="input-auto-step-delay-seconds" class="data-input auto-delay-input" value=""
min="0" max="600" step="1" title="步间延迟秒数,0 或留空则不延迟" />
<span class="data-unit"></span>
</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">
<span class="data-label">OAuth</span>
<span id="display-oauth-url" class="data-value mono truncate">等待中...</span>
</div>
<div class="data-row">
<span class="data-label">回调</span>
<div class="data-inline data-value-actions">
<span id="display-localhost-url" class="data-value data-value-fill mono truncate">等待中...</span>
<button id="btn-save-settings" class="btn btn-outline btn-sm" type="button">保存</button>
</div>
</div>
<div class="data-row">
<span class="data-label">OAuth</span>
<span id="display-oauth-url" class="data-value mono truncate">等待中...</span>
</div>
<div class="data-row">
<span class="data-label">回调</span>
<div class="data-inline data-value-actions">
<span id="display-localhost-url" class="data-value data-value-fill mono truncate">等待中...</span>
<button id="btn-save-settings" class="btn btn-outline btn-sm data-inline-btn" type="button">保存</button>
</div>
</div>
</div>
<div id="hotmail-section" class="data-card hotmail-card" style="display:none;">
<div class="section-mini-header">
<div class="section-mini-copy">
@@ -226,7 +224,8 @@
<div class="section-mini-actions">
<button id="btn-clear-used-hotmail-accounts" class="btn btn-ghost btn-xs" type="button">清空已用</button>
<button id="btn-delete-all-hotmail-accounts" class="btn btn-ghost btn-xs" type="button">全部删除</button>
<button id="btn-toggle-hotmail-list" class="btn btn-ghost btn-xs" type="button" aria-expanded="false">展开列表</button>
<button id="btn-toggle-hotmail-list" class="btn btn-ghost btn-xs" type="button"
aria-expanded="false">展开列表</button>
</div>
</div>
<div class="data-row">
@@ -243,7 +242,8 @@
</div>
<div class="data-row">
<span class="data-label">刷新令牌</span>
<input type="password" id="input-hotmail-refresh-token" class="data-input mono" placeholder="必填,粘贴刷新令牌(refresh token" />
<input type="password" id="input-hotmail-refresh-token" class="data-input mono"
placeholder="必填,粘贴刷新令牌(refresh token" />
</div>
<div class="data-row hotmail-actions-row">
<span class="data-label"></span>
@@ -252,7 +252,8 @@
<div class="data-row hotmail-import-row">
<span class="data-label">批量导入</span>
<div class="hotmail-import-box">
<textarea id="input-hotmail-import" class="data-textarea mono" placeholder="账号----密码----客户端ID----刷新令牌&#10;name@hotmail.com----password----client-id----refresh-token"></textarea>
<textarea id="input-hotmail-import" class="data-textarea mono"
placeholder="账号----密码----客户端ID----刷新令牌&#10;name@hotmail.com----password----client-id----refresh-token"></textarea>
<button id="btn-import-hotmail-accounts" class="btn btn-outline btn-sm" type="button">导入</button>
</div>
</div>
@@ -372,4 +373,4 @@
<script src="sidepanel.js"></script>
</body>
</html>
</html>
+24 -54
View File
@@ -85,8 +85,7 @@ const inputRunCount = document.getElementById('input-run-count');
const inputAutoSkipFailures = document.getElementById('input-auto-skip-failures');
const inputAutoDelayEnabled = document.getElementById('input-auto-delay-enabled');
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 inputAutoStepDelaySeconds = document.getElementById('input-auto-step-delay-seconds');
const autoStartModal = document.getElementById('auto-start-modal');
const autoStartTitle = autoStartModal?.querySelector('.modal-title');
const autoStartMessage = document.getElementById('auto-start-message');
@@ -112,8 +111,6 @@ const AUTO_DELAY_MAX_MINUTES = 1440;
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';
let latestState = null;
@@ -154,7 +151,7 @@ const MAIL_PROVIDER_LOGIN_CONFIGS = {
},
'163-vip': {
label: '163 VIP 邮箱',
url: 'https://webmail.vip.163.com/',
url: 'https://vip.163.com/',
},
qq: {
label: 'QQ 邮箱',
@@ -453,21 +450,23 @@ function normalizeAutoDelayMinutes(value) {
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;
function normalizeAutoStepDelaySeconds(value) {
const rawValue = String(value ?? '').trim();
if (!rawValue) {
return null;
}
const numeric = Number(rawValue);
if (!Number.isFinite(numeric)) {
return null;
}
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 formatAutoStepDelayInputValue(value) {
const normalized = normalizeAutoStepDelaySeconds(value);
return normalized === null ? '' : String(normalized);
}
function updateAutoDelayInputState() {
@@ -621,10 +620,6 @@ function collectSettingsPayload() {
const selectedCloudflareDomain = normalizeCloudflareDomainValue(
!cloudflareDomainEditMode ? selectCfDomain.value : activeDomain
) || activeDomain;
const autoStepDelayRange = normalizeAutoStepDelayRange(
inputAutoStepDelayMinSeconds.value,
inputAutoStepDelayMaxSeconds.value
);
return {
panelMode: selectPanelMode.value,
vpsUrl: inputVpsUrl.value.trim(),
@@ -644,8 +639,7 @@ function collectSettingsPayload() {
autoRunSkipFailures: inputAutoSkipFailures.checked,
autoRunDelayEnabled: inputAutoDelayEnabled.checked,
autoRunDelayMinutes: normalizeAutoDelayMinutes(inputAutoDelayMinutes.value),
autoStepRandomDelayMinSeconds: autoStepDelayRange.minSeconds,
autoStepRandomDelayMaxSeconds: autoStepDelayRange.maxSeconds,
autoStepDelaySeconds: normalizeAutoStepDelaySeconds(inputAutoStepDelaySeconds.value),
};
}
@@ -887,12 +881,7 @@ function applySettingsState(state) {
inputAutoSkipFailures.checked = Boolean(state?.autoRunSkipFailures);
inputAutoDelayEnabled.checked = Boolean(state?.autoRunDelayEnabled);
inputAutoDelayMinutes.value = String(normalizeAutoDelayMinutes(state?.autoRunDelayMinutes));
const autoStepDelayRange = normalizeAutoStepDelayRange(
state?.autoStepRandomDelayMinSeconds,
state?.autoStepRandomDelayMaxSeconds
);
inputAutoStepDelayMinSeconds.value = String(autoStepDelayRange.minSeconds);
inputAutoStepDelayMaxSeconds.value = String(autoStepDelayRange.maxSeconds);
inputAutoStepDelaySeconds.value = formatAutoStepDelayInputValue(state?.autoStepDelaySeconds);
if (state?.autoRunTotalRuns) {
inputRunCount.value = String(state.autoRunTotalRuns);
}
@@ -948,7 +937,7 @@ function getSelectedEmailGenerator() {
function getEmailGeneratorUiCopy() {
if (getSelectedEmailGenerator() === 'cloudflare') {
return {
buttonLabel: '生成 Cloudflare',
buttonLabel: '生成',
placeholder: '点击生成 Cloudflare 邮箱,或手动粘贴邮箱',
successVerb: '生成',
label: 'Cloudflare 邮箱',
@@ -956,7 +945,7 @@ function getEmailGeneratorUiCopy() {
}
return {
buttonLabel: '获取 Duck',
buttonLabel: '获取',
placeholder: '点击获取 DuckDuckGo 邮箱,或手动粘贴邮箱',
successVerb: '获取',
label: 'Duck 邮箱',
@@ -2416,28 +2405,14 @@ inputAutoDelayMinutes.addEventListener('blur', () => {
});
function syncAutoStepDelayInputs() {
const range = normalizeAutoStepDelayRange(
inputAutoStepDelayMinSeconds.value,
inputAutoStepDelayMaxSeconds.value
);
inputAutoStepDelayMinSeconds.value = String(range.minSeconds);
inputAutoStepDelayMaxSeconds.value = String(range.maxSeconds);
inputAutoStepDelaySeconds.value = formatAutoStepDelayInputValue(inputAutoStepDelaySeconds.value);
}
inputAutoStepDelayMinSeconds.addEventListener('input', () => {
inputAutoStepDelaySeconds.addEventListener('input', () => {
markSettingsDirty(true);
scheduleSettingsAutoSave();
});
inputAutoStepDelayMinSeconds.addEventListener('blur', () => {
syncAutoStepDelayInputs();
saveSettings({ silent: true }).catch(() => { });
});
inputAutoStepDelayMaxSeconds.addEventListener('input', () => {
markSettingsDirty(true);
scheduleSettingsAutoSave();
});
inputAutoStepDelayMaxSeconds.addEventListener('blur', () => {
inputAutoStepDelaySeconds.addEventListener('blur', () => {
syncAutoStepDelayInputs();
saveSettings({ silent: true }).catch(() => { });
});
@@ -2539,13 +2514,8 @@ chrome.runtime.onMessage.addListener((message) => {
if (message.payload.autoRunDelayMinutes !== undefined) {
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);
if (message.payload.autoStepDelaySeconds !== undefined) {
inputAutoStepDelaySeconds.value = formatAutoStepDelayInputValue(message.payload.autoStepDelaySeconds);
}
break;
}
+76 -55
View File
@@ -26,7 +26,7 @@ function extractFunction(name) {
const braceStart = source.indexOf('{', paramsEnd);
let depth = 0;
let end = braceStart;
for (; end < source.length; end++) {
for (; end < source.length; end += 1) {
const ch = source[end];
if (ch === '{') depth += 1;
if (ch === '}') {
@@ -42,64 +42,85 @@ function extractFunction(name) {
}
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'),
'const AUTO_STEP_DELAY_MIN_ALLOWED_SECONDS = 0;',
'const AUTO_STEP_DELAY_MAX_ALLOWED_SECONDS = 600;',
'const PERSISTED_SETTING_DEFAULTS = { autoStepDelaySeconds: null };',
extractFunction('normalizeAutoStepDelaySeconds'),
extractFunction('resolveLegacyAutoStepDelaySeconds'),
].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');
const api = new Function(`${bundle}; return { normalizeAutoStepDelaySeconds, resolveLegacyAutoStepDelaySeconds };`)();
assert.strictEqual(
api.getAutoStepRandomDelayMs(15000, 15000),
15000,
'equal bounds should produce a deterministic delay'
api.normalizeAutoStepDelaySeconds(''),
null,
'empty input should remain empty instead of being forced to a default 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;
}
assert.strictEqual(
api.normalizeAutoStepDelaySeconds(null, null),
null,
'null input should stay null'
);
console.log('auto step random delay tests passed');
assert.strictEqual(
api.normalizeAutoStepDelaySeconds('0'),
0,
'zero seconds should be kept so the UI can explicitly show no extra delay'
);
assert.strictEqual(
api.normalizeAutoStepDelaySeconds('12.9'),
12,
'delay seconds should be floored to an integer'
);
assert.strictEqual(
api.normalizeAutoStepDelaySeconds('-50'),
0,
'negative delay should clamp to zero seconds'
);
assert.strictEqual(
api.normalizeAutoStepDelaySeconds('999'),
600,
'delay should clamp to the configured upper bound'
);
assert.strictEqual(
api.resolveLegacyAutoStepDelaySeconds({}),
undefined,
'missing legacy fields should not synthesize a migrated delay'
);
assert.strictEqual(
api.resolveLegacyAutoStepDelaySeconds({ autoStepRandomDelayMinSeconds: 12 }),
12,
'legacy min-only settings should migrate to that same delay'
);
assert.strictEqual(
api.resolveLegacyAutoStepDelaySeconds({ autoStepRandomDelayMaxSeconds: 18 }),
18,
'legacy max-only settings should migrate to that same delay'
);
assert.strictEqual(
api.resolveLegacyAutoStepDelaySeconds({
autoStepRandomDelayMinSeconds: 12,
autoStepRandomDelayMaxSeconds: 18,
}),
15,
'legacy min/max ranges should migrate to their rounded midpoint'
);
assert.strictEqual(
api.resolveLegacyAutoStepDelaySeconds({
autoStepRandomDelayMinSeconds: '',
autoStepRandomDelayMaxSeconds: '',
}),
null,
'empty legacy settings should migrate to no delay'
);
console.log('auto step delay tests passed');