From 596bf38d5cfd9b43ae995d8d3dead818bacb7325 Mon Sep 17 00:00:00 2001 From: QLHazycoder <2825305047@qq.com> Date: Sun, 26 Apr 2026 00:28:25 +0000 Subject: [PATCH] =?UTF-8?q?feat(contribution):=20=E5=A2=9E=E5=BC=BA?= =?UTF-8?q?=E8=B4=A1=E7=8C=AE=E6=A8=A1=E5=BC=8F=E9=80=BB=E8=BE=91=EF=BC=8C?= =?UTF-8?q?=E6=94=AF=E6=8C=81=20SUB2API=20=E5=92=8C=20CPA=20=E6=BA=90?= =?UTF-8?q?=EF=BC=8C=E4=BC=98=E5=8C=96=E7=8A=B6=E6=80=81=E5=A4=84=E7=90=86?= =?UTF-8?q?=E5=92=8C=E7=95=8C=E9=9D=A2=E6=98=BE=E7=A4=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- background.js | 63 +++++++-- background/contribution-oauth.js | 64 ++++++++- background/message-router.js | 3 + content/sub2api-panel.js | 8 +- sidepanel/contribution-mode.js | 47 ++++++- sidepanel/sidepanel.html | 10 +- sidepanel/sidepanel.js | 6 +- tests/background-contribution-mode.test.js | 148 ++++++++++++++++++++- tests/sidepanel-contribution-mode.test.js | 25 +++- 9 files changed, 334 insertions(+), 40 deletions(-) diff --git a/background.js b/background.js index 7f1056c..8406c39 100644 --- a/background.js +++ b/background.js @@ -183,6 +183,10 @@ const DEFAULT_SUB2API_URL = 'https://sub2api.hisence.fun/admin/accounts'; const DEFAULT_CODEX2API_URL = 'http://localhost:8080/admin/accounts'; const DEFAULT_SUB2API_GROUP_NAME = 'codex'; const DEFAULT_SUB2API_PROXY_NAME = ''; +const CONTRIBUTION_SOURCE_CPA = 'cpa'; +const CONTRIBUTION_SOURCE_SUB2API = 'sub2api'; +const CONTRIBUTION_SUB2API_DEFAULT_GROUP_NAME = 'codex号池'; +const CONTRIBUTION_SUB2API_PLUS_GROUP_NAME = 'openai-plus'; const DEFAULT_SUB2API_REDIRECT_URI = 'http://localhost:1455/auth/callback'; const AUTO_RUN_TIMER_ALARM_NAME = 'auto-run-timer'; const AUTO_RUN_TIMER_KIND_SCHEDULED_START = 'scheduled_start'; @@ -222,6 +226,8 @@ const ACCOUNT_RUN_HISTORY_STORAGE_KEY = 'accountRunHistory'; const CONTRIBUTION_RUNTIME_DEFAULTS = self.MultiPageBackgroundContributionOAuth?.RUNTIME_DEFAULTS || { contributionMode: false, contributionModeExpected: false, + contributionSource: CONTRIBUTION_SOURCE_SUB2API, + contributionTargetGroupName: CONTRIBUTION_SUB2API_DEFAULT_GROUP_NAME, contributionNickname: '', contributionQq: '', contributionSessionId: '', @@ -243,6 +249,40 @@ function isPlusModeState(state = {}) { return Boolean(state?.plusModeEnabled); } +function normalizeContributionModeSource(value = '') { + const normalized = String(value || '').trim().toLowerCase(); + return normalized === CONTRIBUTION_SOURCE_SUB2API + ? CONTRIBUTION_SOURCE_SUB2API + : CONTRIBUTION_SOURCE_CPA; +} + +function resolveContributionModeRoutingState(state = {}) { + const currentStatus = String(state?.contributionStatus || '').trim().toLowerCase(); + const currentSource = normalizeContributionModeSource(state?.contributionSource); + const hasActiveSession = Boolean( + String(state?.contributionSessionId || '').trim() + && currentStatus + && !['auto_approved', 'auto_rejected', 'expired', 'error'].includes(currentStatus) + ); + + if (hasActiveSession) { + return { + source: currentSource, + targetGroupName: currentSource === CONTRIBUTION_SOURCE_SUB2API + ? (String(state?.contributionTargetGroupName || '').trim() || CONTRIBUTION_SUB2API_DEFAULT_GROUP_NAME) + : '', + }; + } + + const source = CONTRIBUTION_SOURCE_SUB2API; + return { + source, + targetGroupName: isPlusModeState(state) + ? CONTRIBUTION_SUB2API_PLUS_GROUP_NAME + : (String(state?.contributionTargetGroupName || '').trim() || CONTRIBUTION_SUB2API_DEFAULT_GROUP_NAME), + }; +} + function getStepDefinitionsForState(state = {}) { return isPlusModeState(state) ? PLUS_STEP_DEFINITIONS : NORMAL_STEP_DEFINITIONS; } @@ -1343,11 +1383,18 @@ function buildContributionModeState(enabled, persistedSettings = {}, currentStat } if (enabled) { + const routing = resolveContributionModeRoutingState({ + ...persistedSettings, + ...currentState, + ...currentContributionState, + }); return { ...currentContributionState, contributionMode: true, contributionModeExpected: true, - panelMode: 'cpa', + contributionSource: routing.source, + contributionTargetGroupName: routing.targetGroupName, + panelMode: routing.source, customPassword: '', accountRunHistoryTextEnabled: false, }; @@ -1370,14 +1417,7 @@ async function setContributionMode(enabled) { getState(), ]); - if (normalizedEnabled) { - await setPersistentSettings({ panelMode: 'cpa' }); - } - - const updates = buildContributionModeState(normalizedEnabled, { - ...persistedSettings, - ...(normalizedEnabled ? { panelMode: 'cpa' } : {}), - }, currentState); + const updates = buildContributionModeState(normalizedEnabled, persistedSettings, currentState); await setState(updates); const nextState = await getState(); @@ -1550,10 +1590,7 @@ async function resetState() { getPersistedSettings(), getPersistedAliasState(), ]); - const contributionModeState = buildContributionModeState(Boolean(prev.contributionMode), { - ...persistedSettings, - ...(prev.contributionMode ? { panelMode: 'cpa' } : {}), - }, prev); + const contributionModeState = buildContributionModeState(Boolean(prev.contributionMode), persistedSettings, prev); await chrome.storage.session.clear(); await chrome.storage.session.set({ ...DEFAULT_STATE, diff --git a/background/contribution-oauth.js b/background/contribution-oauth.js index 08c8a40..6aed66f 100644 --- a/background/contribution-oauth.js +++ b/background/contribution-oauth.js @@ -6,10 +6,16 @@ const FINAL_STATUSES = new Set(['auto_approved', 'auto_rejected', 'expired', 'error']); const CALLBACK_FINAL_STATUSES = new Set(['submitted']); const CALLBACK_WAITING_STATUSES = new Set(['idle', 'waiting', 'captured', 'failed', 'submitting']); + const CONTRIBUTION_SOURCE_CPA = 'cpa'; + const CONTRIBUTION_SOURCE_SUB2API = 'sub2api'; + const CONTRIBUTION_SUB2API_DEFAULT_GROUP_NAME = 'codex号池'; + const CONTRIBUTION_SUB2API_PLUS_GROUP_NAME = 'openai-plus'; const RUNTIME_DEFAULTS = { contributionMode: false, contributionModeExpected: false, + contributionSource: CONTRIBUTION_SOURCE_SUB2API, + contributionTargetGroupName: CONTRIBUTION_SUB2API_DEFAULT_GROUP_NAME, contributionNickname: '', contributionQq: '', contributionSessionId: '', @@ -104,6 +110,40 @@ return FINAL_STATUSES.has(normalizeContributionStatus(status)); } + function normalizeContributionSource(value = '') { + const normalized = normalizeString(value).toLowerCase(); + return normalized === CONTRIBUTION_SOURCE_SUB2API + ? CONTRIBUTION_SOURCE_SUB2API + : CONTRIBUTION_SOURCE_CPA; + } + + function resolveContributionRouting(state = {}) { + const currentStatus = normalizeContributionStatus(state.contributionStatus); + const currentSource = normalizeContributionSource(state.contributionSource); + const hasActiveSession = Boolean( + normalizeString(state.contributionSessionId) + && currentStatus + && !FINAL_STATUSES.has(currentStatus) + ); + + if (hasActiveSession) { + return { + source: currentSource, + targetGroupName: currentSource === CONTRIBUTION_SOURCE_SUB2API + ? (normalizeString(state.contributionTargetGroupName) || CONTRIBUTION_SUB2API_DEFAULT_GROUP_NAME) + : '', + }; + } + + const source = CONTRIBUTION_SOURCE_SUB2API; + return { + source, + targetGroupName: Boolean(state.plusModeEnabled) + ? CONTRIBUTION_SUB2API_PLUS_GROUP_NAME + : (normalizeString(state.contributionTargetGroupName) || CONTRIBUTION_SUB2API_DEFAULT_GROUP_NAME), + }; + } + function getStatusLabel(status = '') { switch (normalizeContributionStatus(status)) { case 'started': @@ -111,9 +151,9 @@ case 'waiting': return '等待提交回调'; case 'processing': - return '已提交回调,等待 CPA 确认'; + return '已提交回调,等待服务端确认'; case 'auto_approved': - return '贡献成功,CPA 已确认'; + return '贡献成功,服务端已确认'; case 'auto_rejected': return '贡献未通过确认'; case 'expired': @@ -531,6 +571,16 @@ const callbackState = deriveCallbackState(mergedPayload, currentState); const updates = { contributionLastPollAt: Date.now(), + contributionSource: normalizeContributionSource( + mergedPayload.source + || mergedPayload.source_kind + || currentState.contributionSource + ), + contributionTargetGroupName: normalizeString( + mergedPayload.target_group_name + || mergedPayload.group_name + || currentState.contributionTargetGroupName + ), contributionStatus: normalizedStatus, contributionStatusMessage: buildStatusMessage(normalizedStatus, mergedPayload), contributionCallbackUrl: callbackState.callbackUrl, @@ -572,6 +622,7 @@ async function startContributionFlow(options = {}) { const currentState = options.stateOverride || await getState(); const shouldOpenAuthTab = options.openAuthTab !== false; + const routing = resolveContributionRouting(currentState); if (!currentState.contributionMode) { throw new Error('请先进入贡献模式。'); } @@ -595,7 +646,8 @@ nickname: buildNickname(currentState, options.nickname), qq: buildContributionQq(currentState, options.qq), email: normalizeString(currentState.email), - source: 'cpa', + source: routing.source, + target_group_name: routing.targetGroupName, channel: 'codex-extension', }, }); @@ -608,6 +660,12 @@ } await applyRuntimeUpdates({ + contributionSource: normalizeContributionSource(payload.source || routing.source), + contributionTargetGroupName: normalizeString( + payload.target_group_name + || payload.group_name + || routing.targetGroupName + ), contributionSessionId: sessionId, contributionAuthUrl: authUrl, contributionAuthState: authState, diff --git a/background/message-router.js b/background/message-router.js index a95bf59..56cfa36 100644 --- a/background/message-router.js +++ b/background/message-router.js @@ -644,6 +644,9 @@ stateUpdates.currentStep = 0; } await setState(stateUpdates); + if (Boolean(currentState?.contributionMode) && typeof setContributionMode === 'function') { + await setContributionMode(true); + } if (modeChanged) { await addLog( Boolean(updates.plusModeEnabled) diff --git a/content/sub2api-panel.js b/content/sub2api-panel.js index c078202..6920ff8 100644 --- a/content/sub2api-panel.js +++ b/content/sub2api-panel.js @@ -509,9 +509,6 @@ async function step9_submitOpenAiCallback(payload = {}) { const sessionId = String(payload.sub2apiSessionId || backgroundState.sub2apiSessionId || '').trim(); const expectedState = String(payload.sub2apiOAuthState || backgroundState.sub2apiOAuthState || '').trim(); - const accountName = flowEmail - || String(payload.sub2apiDraftName || backgroundState.sub2apiDraftName || '').trim() - || buildDraftAccountName(payload.sub2apiGroupName || backgroundState.sub2apiGroupName || SUB2API_DEFAULT_GROUP_NAME); const { origin, token } = await loginSub2Api(payload); const proxyPreference = resolveSub2ApiProxyPreference(payload, backgroundState); @@ -552,10 +549,15 @@ async function step9_submitOpenAiCallback(payload = {}) { const credentials = buildOpenAiCredentials(exchangeData); const extra = buildOpenAiExtra(exchangeData); + const resolvedEmail = String(exchangeData?.email || credentials?.email || '').trim(); const groupId = Number(group.id); if (!Number.isFinite(groupId) || groupId <= 0) { throw new Error('SUB2API 返回的目标分组 ID 无效。'); } + const accountName = resolvedEmail + || flowEmail + || String(payload.sub2apiDraftName || backgroundState.sub2apiDraftName || '').trim() + || buildDraftAccountName(payload.sub2apiGroupName || backgroundState.sub2apiGroupName || SUB2API_DEFAULT_GROUP_NAME); const createPayload = { name: accountName, notes: '', diff --git a/sidepanel/contribution-mode.js b/sidepanel/contribution-mode.js index 8e12299..891caa1 100644 --- a/sidepanel/contribution-mode.js +++ b/sidepanel/contribution-mode.js @@ -1,7 +1,10 @@ -(function attachSidepanelContributionMode(globalScope) { - const ACTIVE_STATUSES = new Set(['started', 'waiting', 'processing']); - const FINAL_STATUSES = new Set(['auto_approved', 'auto_rejected', 'expired', 'error']); - const DEFAULT_COPY = '当前账号将用于支持项目维护。扩展会自动申请贡献登录地址并持续跟踪授权状态;如检测到回调地址,会自动提交,并继续等待 CPA 最终确认。'; + (function attachSidepanelContributionMode(globalScope) { + const ACTIVE_STATUSES = new Set(['started', 'waiting', 'processing']); + const FINAL_STATUSES = new Set(['auto_approved', 'auto_rejected', 'expired', 'error']); + const DEFAULT_COPY = '当前账号将用于支持项目维护。扩展会自动申请贡献登录地址并持续跟踪授权状态;如检测到回调地址,会自动提交,并继续等待服务端确认。'; + const CONTRIBUTION_SOURCE_CPA = 'cpa'; + const CONTRIBUTION_SOURCE_SUB2API = 'sub2api'; + const CONTRIBUTION_SUB2API_DEFAULT_GROUP_NAME = 'codex号池'; function createContributionModeManager(context = {}) { const { @@ -66,6 +69,21 @@ } } + function normalizeContributionSource(value = '') { + const normalized = normalizeString(value).toLowerCase(); + return normalized === CONTRIBUTION_SOURCE_SUB2API + ? CONTRIBUTION_SOURCE_SUB2API + : CONTRIBUTION_SOURCE_CPA; + } + + function getContributionSource(currentState = getLatestState()) { + return normalizeContributionSource(currentState.contributionSource || currentState.panelMode); + } + + function getContributionSourceLabel(currentState = getLatestState()) { + return getContributionSource(currentState) === CONTRIBUTION_SOURCE_SUB2API ? 'SUB2API' : 'CPA'; + } + function isContributionModeEnabled(currentState = getLatestState()) { return Boolean(currentState.contributionMode); } @@ -173,7 +191,15 @@ } function getSummaryText(currentState = getLatestState()) { - return normalizeString(currentState.contributionStatusMessage) || DEFAULT_COPY; + const statusMessage = normalizeString(currentState.contributionStatusMessage); + if (statusMessage) { + return statusMessage; + } + if (getContributionSource(currentState) === CONTRIBUTION_SOURCE_SUB2API) { + const groupName = normalizeString(currentState.contributionTargetGroupName) || CONTRIBUTION_SUB2API_DEFAULT_GROUP_NAME; + return `当前账号将用于支持项目维护。贡献会通过 SUB2API 完成,并固定写入 ${groupName} 分组;如检测到回调地址,扩展会自动提交并等待服务端确认。`; + } + return DEFAULT_COPY; } function getContributionPortalPageUrl() { @@ -310,9 +336,10 @@ const enabled = isContributionModeEnabled(currentState); const blocked = isModeSwitchBlocked(); const activeElement = typeof document !== 'undefined' ? document.activeElement : null; + const sourceLabel = getContributionSourceLabel(currentState); if (enabled && dom.selectPanelMode) { - dom.selectPanelMode.value = 'cpa'; + dom.selectPanelMode.value = getContributionSource(currentState); } helpers.updatePanelModeUI?.(); @@ -322,7 +349,13 @@ dom.contributionModePanel.hidden = !enabled; } if (dom.contributionModeText) { - dom.contributionModeText.textContent = DEFAULT_COPY; + dom.contributionModeText.textContent = getSummaryText({ + contributionSource: currentState.contributionSource, + contributionTargetGroupName: currentState.contributionTargetGroupName, + }); + } + if (dom.contributionModeBadge) { + dom.contributionModeBadge.textContent = enabled ? sourceLabel : ''; } if (dom.inputContributionNickname && activeElement !== dom.inputContributionNickname) { const nextNickname = normalizeString(currentState.contributionNickname); diff --git a/sidepanel/sidepanel.html b/sidepanel/sidepanel.html index 1cf6c96..adf2fa0 100644 --- a/sidepanel/sidepanel.html +++ b/sidepanel/sidepanel.html @@ -115,11 +115,11 @@ -
当前账号将用于支持项目维护。扩展会自动申请贡献登录地址并持续跟踪授权状态;如检测到回调地址,会自动提交,无需手动复制。