From 40a006bd1e3bfd588a89a8e76b7a17b4c9955783 Mon Sep 17 00:00:00 2001 From: QLHazyCoder <2825305047@qq.com> Date: Wed, 8 Apr 2026 13:15:29 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=B7=BB=E5=8A=A0=E8=B7=B3=E8=BF=87?= =?UTF-8?q?=E6=AD=A5=E9=AA=A4=E5=8A=9F=E8=83=BD=EF=BC=8C=E4=BC=98=E5=8C=96?= =?UTF-8?q?=E6=89=8B=E5=8A=A8=E5=AE=8C=E6=88=90=E4=BA=A4=E4=BA=92=EF=BC=8C?= =?UTF-8?q?=E6=9B=B4=E6=96=B0=E7=8A=B6=E6=80=81=E6=98=BE=E7=A4=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 10 ++-- background.js | 126 ++++------------------------------------ content/signup-page.js | 61 ------------------- sidepanel/sidepanel.css | 12 ++-- sidepanel/sidepanel.js | 61 +++++++------------ 5 files changed, 44 insertions(+), 226 deletions(-) diff --git a/README.md b/README.md index 96f6fcc..e64c46b 100644 --- a/README.md +++ b/README.md @@ -160,7 +160,7 @@ Step 3 使用的注册邮箱。 - 如果 Duck 邮箱可自动获取,整套流程更接近全自动 - 如果不能自动获取,Auto 会在邮箱阶段暂停 - Auto 的暂停状态会保存在会话状态中,重新打开侧边栏后仍可继续 -- 如果你在 Auto 暂停时改为手动点步骤或手动同步步骤,面板会先确认并停止 Auto,再切回手动控制 +- 如果你在 Auto 暂停时改为手动点步骤或跳过步骤,面板会先确认并停止 Auto,再切回手动控制 ## 详细步骤说明 @@ -363,11 +363,11 @@ sidepanel/ 侧边栏 UI - 手动点 `Step 3` 时,如果邮箱为空,脚本会先自动尝试获取 Duck 邮箱;失败后再改为手填 - Auto 暂停时,仍可手动粘贴邮箱后点击 `Continue` -### 4. 页面已手动完成时同步面板状态 +### 4. 跳过步骤 -- 每个可同步的步骤右侧会出现一个小按钮,用来把该步标记为“已手动完成” -- 点击后会先弹窗确认;它不会真正执行脚本,只会在校验当前页面已进入下一阶段后,放行后续步骤 -- 该按钮只会在合理边界内显示;例如最后一步不会显示,依赖数据缺失时会禁用 +- 每个步骤右侧都会在满足顺序条件时出现一个小按钮,用来直接跳过该步骤 +- 点击后会先弹窗确认;它不会真正执行脚本,只会把该步骤状态改为“已跳过”,从而放行后续步骤 +- 跳过按钮的规则很简单:只要上一步已完成、当前步骤没在运行,就可以使用;Step 1 没有前置步骤,也可直接跳过 - 如果 Auto 处于暂停状态,点击该按钮会先确认是否接管 Auto ### 5. Step 8 失败时重点检查 diff --git a/background.js b/background.js index 04a49c8..b396eab 100644 --- a/background.js +++ b/background.js @@ -537,7 +537,7 @@ function isStopError(error) { } function isStepDoneStatus(status) { - return status === 'completed' || status === 'manual_completed'; + return status === 'completed' || status === 'manual_completed' || status === 'skipped'; } function clearStopRequest() { @@ -595,136 +595,32 @@ async function ensureManualInteractionAllowed(actionLabel) { return state; } -async function getSignupManualCompletionState() { - const signupTabId = await getTabId('signup-page'); - if (!signupTabId) { - throw new Error('认证页标签页不存在,请先打开认证页并手动完成相应操作。'); - } - - const result = await sendToContentScript('signup-page', { - type: 'GET_MANUAL_COMPLETION_STATE', - source: 'background', - payload: {}, - }); - - if (result?.error) { - throw new Error(result.error); - } - - return result || {}; -} - -function ensureKnownPassword(state, step) { - const resolvedPassword = state.password || state.customPassword; - if (!resolvedPassword) { - throw new Error(`步骤 ${step} 需要已知密码,请先在侧边栏填写固定密码后再手动同步。`); - } - return resolvedPassword; -} - -async function validateManualStepCompletion(step, state) { - switch (step) { - case 2: { - const authState = await getSignupManualCompletionState(); - if (!['credentials', 'verification', 'profile', 'add_phone', 'consent'].includes(authState.stage)) { - throw new Error(`认证页当前还没有进入注册流程后续页面,不能将步骤 2 标记为手动完成。当前状态:${authState.summary || authState.stage || '未知'}`); - } - return {}; - } - - case 3: { - if (!state.email) { - throw new Error('步骤 3 需要邮箱地址,请先在侧边栏填写邮箱。'); - } - const password = ensureKnownPassword(state, 3); - const authState = await getSignupManualCompletionState(); - if (!['verification', 'profile', 'add_phone', 'consent'].includes(authState.stage)) { - throw new Error(`认证页当前还没有离开邮箱/密码页面,不能将步骤 3 标记为手动完成。当前状态:${authState.summary || authState.stage || '未知'}`); - } - return state.password ? {} : { stateUpdates: { password } }; - } - - case 4: { - const authState = await getSignupManualCompletionState(); - if (!['profile', 'add_phone', 'consent'].includes(authState.stage)) { - throw new Error(`认证页当前还没有进入验证码后的下一阶段,不能将步骤 4 标记为手动完成。当前状态:${authState.summary || authState.stage || '未知'}`); - } - return {}; - } - - case 5: { - const authState = await getSignupManualCompletionState(); - if (!['add_phone', 'consent'].includes(authState.stage)) { - throw new Error(`认证页当前还没有离开姓名/生日页面,不能将步骤 5 标记为手动完成。当前状态:${authState.summary || authState.stage || '未知'}`); - } - return {}; - } - - case 6: { - if (!state.email) { - throw new Error('步骤 6 需要已知邮箱地址,请先在侧边栏填写邮箱。'); - } - const authState = await getSignupManualCompletionState(); - if (!['verification', 'add_phone', 'consent'].includes(authState.stage)) { - throw new Error(`认证页当前还没有进入登录后的下一阶段,不能将步骤 6 标记为手动完成。当前状态:${authState.summary || authState.stage || '未知'}`); - } - return {}; - } - - case 7: { - const authState = await getSignupManualCompletionState(); - if (!['add_phone', 'consent'].includes(authState.stage)) { - throw new Error(`认证页当前还没有离开登录验证码页面,不能将步骤 7 标记为手动完成。当前状态:${authState.summary || authState.stage || '未知'}`); - } - return {}; - } - - case 8: { - if (!state.localhostUrl) { - throw new Error('尚未捕获 localhost 回调地址,不能将步骤 8 标记为手动完成。'); - } - return {}; - } - - default: - throw new Error(`步骤 ${step} 不支持手动完成同步。`); - } -} - -async function markStepManualComplete(step) { - const state = await ensureManualInteractionAllowed('手动同步步骤'); +async function skipStep(step) { + const state = await ensureManualInteractionAllowed('跳过步骤'); if (!Number.isInteger(step) || step < 1 || step > 9) { throw new Error(`无效步骤:${step}`); } - if (step === 1 || step === 9) { - throw new Error(`步骤 ${step} 不支持手动完成同步。`); - } const statuses = { ...(state.stepStatuses || {}) }; const currentStatus = statuses[step]; if (currentStatus === 'running') { - throw new Error(`步骤 ${step} 正在运行中,不能手动同步。`); + throw new Error(`步骤 ${step} 正在运行中,不能跳过。`); } if (isStepDoneStatus(currentStatus)) { - throw new Error(`步骤 ${step} 已完成,无需再手动同步。`); + throw new Error(`步骤 ${step} 已完成,无需再跳过。`); } if (step > 1) { const prevStatus = statuses[step - 1]; if (!isStepDoneStatus(prevStatus)) { - throw new Error(`请先完成步骤 ${step - 1},再同步步骤 ${step}。`); + throw new Error(`请先完成步骤 ${step - 1},再跳过步骤 ${step}。`); } } - const validation = await validateManualStepCompletion(step, state); - if (validation?.stateUpdates) { - await setState(validation.stateUpdates); - } - - await setStepStatus(step, 'manual_completed'); - await addLog(`步骤 ${step} 已标记为手动完成`, 'warn'); - return { ok: true, step, status: 'manual_completed' }; + await setStepStatus(step, 'skipped'); + await addLog(`步骤 ${step} 已跳过`, 'warn'); + return { ok: true, step, status: 'skipped' }; } function throwIfStopped() { @@ -924,9 +820,9 @@ async function handleMessage(message, sender) { return { ok: true }; } - case 'MARK_STEP_MANUAL_COMPLETE': { + case 'SKIP_STEP': { const step = Number(message.payload?.step); - return await markStepManualComplete(step); + return await skipStep(step); } case 'SAVE_SETTING': { diff --git a/content/signup-page.js b/content/signup-page.js index 71ebfa6..00cffe6 100644 --- a/content/signup-page.js +++ b/content/signup-page.js @@ -11,7 +11,6 @@ chrome.runtime.onMessage.addListener((message, sender, sendResponse) => { || message.type === 'STEP8_FIND_AND_CLICK' || message.type === 'PREPARE_LOGIN_CODE' || message.type === 'RESEND_VERIFICATION_CODE' - || message.type === 'GET_MANUAL_COMPLETION_STATE' ) { resetStopState(); handleCommand(message).then((result) => { @@ -23,11 +22,6 @@ chrome.runtime.onMessage.addListener((message, sender, sendResponse) => { return; } - if (message.type === 'GET_MANUAL_COMPLETION_STATE') { - sendResponse({ error: err.message }); - return; - } - if (message.type === 'STEP8_FIND_AND_CLICK') { log(`步骤 8:${err.message}`, 'error'); sendResponse({ error: err.message }); @@ -59,8 +53,6 @@ async function handleCommand(message) { return await prepareLoginCodeFlow(); case 'RESEND_VERIFICATION_CODE': return await resendVerificationCode(message.step); - case 'GET_MANUAL_COMPLETION_STATE': - return getManualCompletionState(); case 'STEP8_FIND_AND_CLICK': return await step8_findAndClick(); } @@ -153,25 +145,6 @@ function findOneTimeCodeLoginTrigger() { return null; } -function findRegisterEntryTrigger() { - const candidates = document.querySelectorAll( - 'a, button, [role="button"], [role="link"], input[type="button"], input[type="submit"]' - ); - - for (const el of candidates) { - if (!isVisibleElement(el)) continue; - if (el.disabled || el.getAttribute('aria-disabled') === 'true') continue; - - const text = getActionText(el); - const href = el.getAttribute('href') || ''; - if ((text && /sign\s*up|register|create\s*account|注册/i.test(text)) || /signup|register/i.test(href)) { - return el; - } - } - - return null; -} - function findResendVerificationCodeTrigger({ allowDisabled = false } = {}) { const candidates = document.querySelectorAll( 'button, a, [role="button"], [role="link"], input[type="button"], input[type="submit"]' @@ -421,18 +394,6 @@ function isStep5Ready() { ); } -function isCredentialsPageReady() { - const emailInput = document.querySelector( - 'input[type="email"], input[name="email"], input[name="username"], input[id*="email"], input[placeholder*="email" i]' - ); - if (emailInput && isVisibleElement(emailInput)) { - return true; - } - - const passwordInput = document.querySelector('input[type="password"]'); - return Boolean(passwordInput && isVisibleElement(passwordInput)); -} - function getPageTextSnapshot() { return (document.body?.innerText || document.body?.textContent || '') .replace(/\s+/g, ' ') @@ -482,28 +443,6 @@ function isStep8Ready() { return OAUTH_CONSENT_PAGE_PATTERN.test(getPageTextSnapshot()); } -function getManualCompletionState() { - if (isStep8Ready()) { - return { stage: 'consent', summary: '已进入 OAuth 授权同意页', url: location.href }; - } - if (isAddPhonePageReady()) { - return { stage: 'add_phone', summary: '已进入手机号页面', url: location.href }; - } - if (isStep5Ready()) { - return { stage: 'profile', summary: '已进入姓名/生日页面', url: location.href }; - } - if (isVerificationPageStillVisible()) { - return { stage: 'verification', summary: '仍停留在验证码页面', url: location.href }; - } - if (isCredentialsPageReady()) { - return { stage: 'credentials', summary: '已进入邮箱或密码输入页面', url: location.href }; - } - if (findRegisterEntryTrigger()) { - return { stage: 'register', summary: '仍停留在注册入口页面', url: location.href }; - } - - return { stage: 'unknown', summary: '无法识别当前认证页状态', url: location.href }; -} async function waitForVerificationSubmitOutcome(step, timeout) { const resolvedTimeout = timeout ?? (step === 7 ? 30000 : 12000); diff --git a/sidepanel/sidepanel.css b/sidepanel/sidepanel.css index e075b78..41907b0 100644 --- a/sidepanel/sidepanel.css +++ b/sidepanel/sidepanel.css @@ -521,8 +521,10 @@ header { .step-row.stopped .step-indicator { border-color: var(--cyan); background: rgba(8, 145, 178, 0.08); } .step-row.stopped .step-num { color: var(--cyan); } -.step-row.manual_completed .step-indicator { border-color: var(--blue); background: var(--blue-soft); } -.step-row.manual_completed .step-num { color: var(--blue); } +.step-row.manual_completed .step-indicator, +.step-row.skipped .step-indicator { border-color: var(--blue); background: var(--blue-soft); } +.step-row.manual_completed .step-num, +.step-row.skipped .step-num { color: var(--blue); } /* Step Button */ .step-btn { @@ -547,7 +549,8 @@ header { .step-row.completed .step-btn { border-color: var(--border-subtle); color: var(--text-secondary); opacity: 0.7; } .step-row.failed .step-btn { border-color: var(--red); color: var(--red); } .step-row.stopped .step-btn { border-color: var(--cyan); color: var(--cyan); } -.step-row.manual_completed .step-btn { border-color: rgba(37, 99, 235, 0.25); color: var(--blue); opacity: 0.82; } +.step-row.manual_completed .step-btn, +.step-row.skipped .step-btn { border-color: rgba(37, 99, 235, 0.25); color: var(--blue); opacity: 0.82; } .step-actions { display: flex; @@ -595,7 +598,8 @@ header { .step-row.completed .step-status { color: var(--green); } .step-row.failed .step-status { color: var(--red); } .step-row.stopped .step-status { color: var(--cyan); } -.step-row.manual_completed .step-status { color: var(--blue); } +.step-row.manual_completed .step-status, +.step-row.skipped .step-status { color: var(--blue); } /* ============================================================ Log / Console Section diff --git a/sidepanel/sidepanel.js b/sidepanel/sidepanel.js index 959debb..e9fa40a 100644 --- a/sidepanel/sidepanel.js +++ b/sidepanel/sidepanel.js @@ -6,7 +6,8 @@ const STATUS_ICONS = { completed: '\u2713', // ✓ failed: '\u2717', // ✗ stopped: '\u25A0', // ■ - manual_completed: '手', + manual_completed: '跳', + skipped: '跳', }; const logArea = document.getElementById('log-area'); @@ -46,7 +47,7 @@ const STEP_DEFAULT_STATUSES = { 8: 'pending', 9: 'pending', }; -const MANUAL_COMPLETION_STEPS = new Set([2, 3, 4, 5, 6, 7, 8]); +const SKIPPABLE_STEPS = new Set([1, 2, 3, 4, 5, 6, 7, 8, 9]); let latestState = null; let currentAutoRun = { @@ -103,7 +104,7 @@ function dismissToast(toast) { } function isDoneStatus(status) { - return status === 'completed' || status === 'manual_completed'; + return status === 'completed' || status === 'manual_completed' || status === 'skipped'; } function getStepStatuses(state = latestState) { @@ -282,13 +283,13 @@ function initializeManualStepActions() { manualBtn.type = 'button'; manualBtn.className = 'step-manual-btn'; manualBtn.dataset.step = String(step); - manualBtn.title = '标记此步已手动完成'; - manualBtn.setAttribute('aria-label', `标记步骤 ${step} 已手动完成`); + manualBtn.title = '跳过此步'; + manualBtn.setAttribute('aria-label', `跳过步骤 ${step}`); manualBtn.innerHTML = ''; manualBtn.addEventListener('click', async (event) => { event.stopPropagation(); try { - await handleManualComplete(step); + await handleSkipStep(step); } catch (err) { showToast(err.message, 'error'); } @@ -426,10 +427,10 @@ function updateButtonStates() { const currentStatus = statuses[step]; const prevStatus = statuses[step - 1]; - if (!MANUAL_COMPLETION_STEPS.has(step) || anyRunning || autoLocked || currentStatus === 'running' || isDoneStatus(currentStatus)) { + if (!SKIPPABLE_STEPS.has(step) || anyRunning || autoLocked || currentStatus === 'running' || isDoneStatus(currentStatus)) { btn.style.display = 'none'; btn.disabled = true; - btn.title = '当前不可手动同步'; + btn.title = '当前不可跳过'; return; } @@ -440,22 +441,9 @@ function updateButtonStates() { return; } - let disabledReason = ''; - if (step === 3) { - if (!(latestState?.email || inputEmail.value.trim())) { - disabledReason = '请先填写邮箱'; - } else if (!(latestState?.password || latestState?.customPassword || inputPassword.value)) { - disabledReason = '请先在侧边栏填写固定密码'; - } - } else if (step === 6 && !(latestState?.email || inputEmail.value.trim())) { - disabledReason = '请先填写邮箱'; - } else if (step === 8 && !latestState?.localhostUrl) { - disabledReason = '尚未捕获 localhost 回调地址'; - } - btn.style.display = ''; - btn.disabled = Boolean(disabledReason); - btn.title = disabledReason || `将步骤 ${step} 标记为已手动完成`; + btn.disabled = false; + btn.title = `跳过步骤 ${step}`; }); updateStopButtonState(anyRunning || isAutoRunPausedPhase() || autoLocked); @@ -509,11 +497,11 @@ function updateStatusDisplay(state) { .sort((a, b) => b - a)[0]; if (lastCompleted === 9) { - displayStatus.textContent = isDoneStatus(state.stepStatuses[9]) && state.stepStatuses[9] === 'manual_completed' ? '全部步骤已手动完成' : '全部步骤已完成'; + displayStatus.textContent = (state.stepStatuses[9] === 'manual_completed' || state.stepStatuses[9] === 'skipped') ? '全部步骤已跳过/完成' : '全部步骤已完成'; statusBar.classList.add('completed'); } else if (lastCompleted) { - displayStatus.textContent = state.stepStatuses[lastCompleted] === 'manual_completed' - ? `步骤 ${lastCompleted} 已手动完成` + displayStatus.textContent = (state.stepStatuses[lastCompleted] === 'manual_completed' || state.stepStatuses[lastCompleted] === 'skipped') + ? `步骤 ${lastCompleted} 已跳过` : `步骤 ${lastCompleted} 已完成`; } else { displayStatus.textContent = '就绪'; @@ -602,27 +590,18 @@ async function maybeTakeoverAutoRun(actionLabel) { return true; } -async function handleManualComplete(step) { - if (!(await maybeTakeoverAutoRun(`手动同步步骤 ${step}`))) { +async function handleSkipStep(step) { + if (!(await maybeTakeoverAutoRun(`跳过步骤 ${step}`))) { return; } - if (step === 3 && inputPassword.value !== (latestState?.customPassword || '')) { - await chrome.runtime.sendMessage({ - type: 'SAVE_SETTING', - source: 'sidepanel', - payload: { customPassword: inputPassword.value }, - }); - syncLatestState({ customPassword: inputPassword.value }); - } - - const confirmed = confirm(`这不会真正执行步骤 ${step},只会将面板状态同步为“已手动完成”。请确认你已经在网页中手动完成该步骤。`); + const confirmed = confirm(`这不会真正执行步骤 ${step},只会直接跳过该步骤并放行后续步骤。是否继续?`); if (!confirmed) { return; } const response = await chrome.runtime.sendMessage({ - type: 'MARK_STEP_MANUAL_COMPLETE', + type: 'SKIP_STEP', source: 'sidepanel', payload: { step }, }); @@ -631,7 +610,7 @@ async function handleManualComplete(step) { throw new Error(response.error); } - showToast(`步骤 ${step} 已同步为手动完成`, 'success', 2200); + showToast(`步骤 ${step} 已跳过`, 'success', 2200); } // ============================================================ @@ -839,7 +818,7 @@ chrome.runtime.onMessage.addListener((message) => { syncAutoRunState(state); updateStatusDisplay(latestState); updateButtonStates(); - if (status === 'completed' || status === 'manual_completed') { + if (status === 'completed' || status === 'manual_completed' || status === 'skipped') { syncPasswordField(state); if (state.oauthUrl) { displayOauthUrl.textContent = state.oauthUrl;