diff --git a/README.md b/README.md index 705663f..2ae6dd0 100644 --- a/README.md +++ b/README.md @@ -2,9 +2,11 @@ SMSBower Phone Fill Tool 用途:点击扩展图标后,在当前网页右上角打开一个常驻悬浮窗。点一次“获取并填入”就获取一个 SMSBower 手机号,自动填入当前页面最像手机号的输入框,并复制到剪贴板。再次获取会先释放上一个激活,再拿新号替换页面输入框。 -本版已集成 FlowPilot 同款切号机制: -- 页面提示“该电话/手机号已被使用、已绑定、已注册”时,可点“检测报错换号”自动识别,或直接点“已使用换号”;旧号按被拒原因释放,新号自动填入并复制。 -- 页面提示“无法向此电话号码发送验证码”等发送失败时,“检测报错换号”会按可恢复失败换号。 +本版已集成 FlowPilot 同款自动切号机制: +- 悬浮窗打开后默认开启“自动切号:开”。 +- 页面提示“该电话/手机号已被使用、已绑定、已注册”时,插件会自动识别并切换下一个号码;旧号按被拒原因释放,新号自动填入并复制。 +- 页面提示“无法向此电话号码发送验证码”等发送失败时,插件会自动按可恢复失败换号。 +- “检测报错换号”和“已使用换号”仍保留为手动兜底按钮。 - 仍然只负责填号/查码/复制,不自动点击目标页面继续按钮。 安装/更新: @@ -20,9 +22,10 @@ SMSBower Phone Fill Tool 4. 填 SMSBower API Key、项目代码、价格上下限、国家顺序。 5. 顶部“获取并填入”会立即拿号并填入。 6. 再点一次“获取并填入”会释放上一个激活,替换输入框为新手机号。 -7. 如果目标页面拒绝号码: - - 先点“检测报错换号”,插件会扫描当前页面文本,识别“已使用/发送验证码失败”等错误并换号; - - 如果你已经确认是“号码已使用”,可直接点“已使用换号”。 +7. 如果目标页面拒绝号码,保持悬浮窗打开即可:插件会自动扫描页面错误文本并切换下一个号码。 + - “自动切号:开/关”可以临时启停自动扫描; + - “检测报错换号”用于手动触发一次扫描; + - “已使用换号”用于你已经确认号码被使用时立即换号。 8. 点“获取验证码”后会每 1 秒查询一次当前激活的验证码状态;如果 SMSBower 已收到 STATUS_OK,会显示验证码并停止。点获取新号、保存、刷新国家或取消当前会停止这轮验证码轮询。 9. 看到验证码后,点“复制验证码”会复制当前显示的验证码。 10. 国家搜索支持中文别名,例如搜“印度”会出现 India 和 Indonesia。 diff --git a/content.js b/content.js index c3083df..0aa1c2b 100644 --- a/content.js +++ b/content.js @@ -29,6 +29,14 @@ let panel = null; let dragState = null; let codePollToken = 0; + let autoRotateObserver = null; + let autoRotateTimer = null; + let autoRotateInFlight = false; + let autoRotateEnabled = true; + let lastAutoRotateSignature = ''; + let lastAutoRotateAt = 0; + const AUTO_ROTATE_DEBOUNCE_MS = 800; + const AUTO_ROTATE_COOLDOWN_MS = 8000; function stopCodePolling() { codePollToken += 1; @@ -153,7 +161,7 @@ const btn = $('.sb-fetch'); if (btn) btn.disabled = true; try { - await saveSettings(); + if (!options.skipSave) await saveSettings(); const reason = options.reason || 'manual_replace'; const releaseAction = options.releaseAction || (reason === 'phone_number_used' ? 'ban' : 'cancel'); setStatus(releaseAction === 'ban' ? '正在释放被拒号码并获取新号...' : '正在取消上一个激活并获取新号...'); @@ -180,14 +188,70 @@ return String(clone.innerText || clone.textContent || '').slice(-12000); } - async function detectAndRotate() { + function updateAutoRotateToggle() { + const btn = $('.sb-auto-toggle'); + if (!btn) return; + btn.textContent = autoRotateEnabled ? '自动切号:开' : '自动切号:关'; + btn.classList.toggle('sb-auto-on', autoRotateEnabled); + } + + function buildAutoRotateSignature(reason, text) { + const compact = String(text || '').replace(/\s+/g, ' ').trim(); + return `${reason}:${compact.slice(-220)}`; + } + + async function rotateFromClassifiedError(classified, source = 'manual') { + const text = getPageTextForPhoneError(); + const signature = buildAutoRotateSignature(classified.reason, text); + const now = Date.now(); + if (autoRotateInFlight) return false; + if (source === 'auto' && signature === lastAutoRotateSignature && now - lastAutoRotateAt < AUTO_ROTATE_COOLDOWN_MS) { + return false; + } + autoRotateInFlight = true; + lastAutoRotateSignature = signature; + lastAutoRotateAt = now; + try { + setStatus(source === 'auto' + ? `检测到${classified.reason === 'phone_number_used' ? '号码已使用' : '验证码发送失败'},正在自动切号...` + : '检测到手机号错误,正在切号...'); + await fetchAndFill({ reason: classified.reason, releaseAction: classified.releaseAction, skipSave: true }); + return true; + } finally { + autoRotateInFlight = false; + } + } + + async function detectAndRotate(source = 'manual') { const text = getPageTextForPhoneError(); const classified = await send('CLASSIFY_PHONE_ERROR', { text }); if (!classified.matched) { - setStatus('未检测到明确的手机号错误,可用“手动换号”。', true); - return; + if (source !== 'auto') setStatus('未检测到明确的手机号错误。', true); + return false; } - await fetchAndFill({ reason: classified.reason, releaseAction: classified.releaseAction }); + return rotateFromClassifiedError(classified, source); + } + + function scheduleAutoRotateCheck() { + if (!autoRotateEnabled || autoRotateInFlight) return; + if (autoRotateTimer) clearTimeout(autoRotateTimer); + autoRotateTimer = setTimeout(() => { + autoRotateTimer = null; + detectAndRotate('auto').catch((error) => setStatus(error.message || String(error), true)); + }, AUTO_ROTATE_DEBOUNCE_MS); + } + + function startAutoRotateObserver() { + if (autoRotateObserver) return; + autoRotateObserver = new MutationObserver((mutations) => { + if (!autoRotateEnabled) return; + if (mutations.some((m) => m.target && panel && panel.contains(m.target))) return; + scheduleAutoRotateCheck(); + }); + if (document.body) { + autoRotateObserver.observe(document.body, { childList: true, subtree: true, characterData: true }); + } + window.addEventListener('focus', scheduleAutoRotateCheck); } async function cancelActive() { @@ -328,6 +392,7 @@ #${PANEL_ID} code { color: #67e8f9; user-select: all; } #${PANEL_ID} .sb-hint { margin: 7px 0 0; color: #94a3b8; font-size: 12px; } #${PANEL_ID} .sb-status { color: #bbf7d0; } + #${PANEL_ID} .sb-auto-on { border-color: #22c55e; color: #bbf7d0; } `; document.documentElement.appendChild(style); } @@ -342,6 +407,7 @@ +