瀏覽代碼

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

QLHazyCoder 4 月之前
父節點
當前提交
9ba958b1a2
共有 10 個文件被更改,包括 227 次插入218 次删除
  1. 5 5
      README.md
  2. 51 61
      background.js
  3. 二進制
      docs/images/五轮结果.jpg
  4. 二進制
      docs/images/五轮自动.png
  5. 二進制
      docs/images/交流群.jpg
  6. 1 1
      manifest.json
  7. 31 4
      sidepanel/sidepanel.css
  8. 45 44
      sidepanel/sidepanel.html
  9. 23 53
      sidepanel/sidepanel.js
  10. 71 50
      tests/auto-step-random-delay.test.js

+ 5 - 5
README.md

@@ -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
background.js

@@ -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,

二進制
docs/images/五轮结果.jpg


二進制
docs/images/五轮自动.png


二進制
docs/images/交流群.jpg


+ 1 - 1
manifest.json

@@ -1,7 +1,7 @@
 {
   "manifest_version": 3,
   "name": "多页面自动化",
-  "version": "6.4.0",
+  "version": "7.0.0",
   "description": "用于自动执行多步骤 OAuth 注册流程",
   "permissions": [
     "sidePanel",

+ 31 - 4
sidepanel/sidepanel.css

@@ -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-check {
+.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 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 {

+ 45 - 44
sidepanel/sidepanel.html

@@ -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>
-      </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 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">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>

+ 23 - 53
sidepanel/sidepanel.js

@@ -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);
+function normalizeAutoStepDelaySeconds(value) {
+  const rawValue = String(value ?? '').trim();
+  if (!rawValue) {
+    return null;
+  }
+
+  const numeric = Number(rawValue);
   if (!Number.isFinite(numeric)) {
-    return fallback;
+    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', () => {
-  markSettingsDirty(true);
-  scheduleSettingsAutoSave();
-});
-inputAutoStepDelayMinSeconds.addEventListener('blur', () => {
-  syncAutoStepDelayInputs();
-  saveSettings({ silent: true }).catch(() => { });
-});
-
-inputAutoStepDelayMaxSeconds.addEventListener('input', () => {
+inputAutoStepDelaySeconds.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;
     }

+ 71 - 50
tests/auto-step-random-delay.test.js

@@ -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 api = new Function(`${bundle}; return { normalizeAutoStepDelaySeconds, resolveLegacyAutoStepDelaySeconds };`)();
 
-const defaultRange = api.normalizeAutoStepRandomDelayRange({});
-assert.deepStrictEqual(defaultRange, { minSeconds: 12, maxSeconds: 18 }, 'missing settings should fall back to the default 12-18 second range');
+assert.strictEqual(
+  api.normalizeAutoStepDelaySeconds(''),
+  null,
+  'empty input should remain empty instead of being forced to a default delay'
+);
 
-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`);
-}
+assert.strictEqual(
+  api.normalizeAutoStepDelaySeconds(null, null),
+  null,
+  'null input should stay null'
+);
 
-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');
+assert.strictEqual(
+  api.normalizeAutoStepDelaySeconds('0'),
+  0,
+  'zero seconds should be kept so the UI can explicitly show no extra delay'
+);
 
-const customRange = api.normalizeAutoStepRandomDelayRange({
-  autoStepRandomDelayMinSeconds: 5,
-  autoStepRandomDelayMaxSeconds: 7,
-});
-assert.deepStrictEqual(customRange, { minSeconds: 5, maxSeconds: 7 }, 'configured ranges should be preserved when already valid');
+assert.strictEqual(
+  api.normalizeAutoStepDelaySeconds('12.9'),
+  12,
+  'delay seconds should be floored to an integer'
+);
 
-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.normalizeAutoStepDelaySeconds('-50'),
+  0,
+  'negative delay should clamp to zero seconds'
+);
 
 assert.strictEqual(
-  api.getAutoStepRandomDelayMs(15000, 15000),
-  15000,
-  'equal bounds should produce a deterministic delay'
+  api.normalizeAutoStepDelaySeconds('999'),
+  600,
+  'delay should clamp to the configured upper bound'
 );
 
-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.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 random delay tests passed');
+console.log('auto step delay tests passed');