From 63c53accfed49a48f513111bf633e537d6202197 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9C=B4=E5=9C=A3=E4=BD=91?= Date: Sun, 26 Apr 2026 22:23:25 +0800 Subject: [PATCH] =?UTF-8?q?feat(sub2api):=20=E6=94=AF=E6=8C=81=E5=A4=9A?= =?UTF-8?q?=E5=88=86=E7=BB=84=E5=88=9B=E5=BB=BA=E8=B4=A6=E5=8F=B7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit (cherry picked from commit cc7671e6d2863b68527134008301e14fb86a8666) --- background.js | 3 ++ background/message-router.js | 21 ++++++++ background/steps/platform-verify.js | 1 + content/sub2api-panel.js | 79 +++++++++++++++++++++++++---- sidepanel/sidepanel.html | 2 +- tests/sub2api-panel-proxy.test.js | 35 ++++++++++++- 6 files changed, 130 insertions(+), 11 deletions(-) diff --git a/background.js b/background.js index cafe74e..cb80c39 100644 --- a/background.js +++ b/background.js @@ -7776,6 +7776,9 @@ async function handleStepData(step, payload) { if (payload.cpaManagementOrigin !== undefined) updates.cpaManagementOrigin = payload.cpaManagementOrigin || null; if (payload.codex2apiSessionId !== undefined) updates.codex2apiSessionId = payload.codex2apiSessionId || null; if (payload.codex2apiOAuthState !== undefined) updates.codex2apiOAuthState = payload.codex2apiOAuthState || null; + if (payload.sub2apiGroupIds !== undefined) updates.sub2apiGroupIds = Array.isArray(payload.sub2apiGroupIds) + ? payload.sub2apiGroupIds + : []; if (Object.keys(updates).length) { await setState(updates); } diff --git a/background/message-router.js b/background/message-router.js index 6497faa..410a9a1 100644 --- a/background/message-router.js +++ b/background/message-router.js @@ -292,6 +292,27 @@ } switch (step) { + case 1: { + const updates = {}; + if (payload.oauthUrl) { + updates.oauthUrl = payload.oauthUrl; + broadcastDataUpdate({ oauthUrl: payload.oauthUrl }); + } + if (payload.sub2apiSessionId !== undefined) updates.sub2apiSessionId = payload.sub2apiSessionId || null; + if (payload.sub2apiOAuthState !== undefined) updates.sub2apiOAuthState = payload.sub2apiOAuthState || null; + if (payload.sub2apiGroupId !== undefined) updates.sub2apiGroupId = payload.sub2apiGroupId || null; + if (payload.sub2apiGroupIds !== undefined) updates.sub2apiGroupIds = Array.isArray(payload.sub2apiGroupIds) + ? payload.sub2apiGroupIds + : []; + if (payload.sub2apiDraftName !== undefined) updates.sub2apiDraftName = payload.sub2apiDraftName || null; + if (payload.sub2apiProxyId !== undefined) updates.sub2apiProxyId = payload.sub2apiProxyId || null; + if (payload.codex2apiSessionId !== undefined) updates.codex2apiSessionId = payload.codex2apiSessionId || null; + if (payload.codex2apiOAuthState !== undefined) updates.codex2apiOAuthState = payload.codex2apiOAuthState || null; + if (Object.keys(updates).length) { + await setState(updates); + } + break; + } case 2: if (payload.email) { await setEmailState(payload.email); diff --git a/background/steps/platform-verify.js b/background/steps/platform-verify.js index af7023f..cbf26bf 100644 --- a/background/steps/platform-verify.js +++ b/background/steps/platform-verify.js @@ -364,6 +364,7 @@ sub2apiSessionId: state.sub2apiSessionId, sub2apiOAuthState: state.sub2apiOAuthState, sub2apiGroupId: state.sub2apiGroupId, + sub2apiGroupIds: state.sub2apiGroupIds, sub2apiDraftName: state.sub2apiDraftName, }, }; diff --git a/content/sub2api-panel.js b/content/sub2api-panel.js index dabfefb..7798a73 100644 --- a/content/sub2api-panel.js +++ b/content/sub2api-panel.js @@ -191,6 +191,53 @@ async function getGroupByName(origin, token, groupName) { return group; } +function normalizeSub2ApiGroupNames(value) { + const source = Array.isArray(value) + ? value + : String(value || '').split(/[\r\n,,;;]+/); + const seen = new Set(); + const names = []; + for (const item of source) { + const name = String(item || '').trim(); + const key = name.toLowerCase(); + if (!name || seen.has(key)) continue; + seen.add(key); + names.push(name); + } + return names.length ? names : [SUB2API_DEFAULT_GROUP_NAME]; +} + +async function getGroupsByNames(origin, token, groupNames) { + const targetNames = normalizeSub2ApiGroupNames(groupNames); + const groups = await requestJson(origin, '/api/v1/admin/groups/all', { + method: 'GET', + token, + }); + const matched = []; + const missing = []; + + for (const targetName of targetNames) { + const normalized = targetName.toLowerCase(); + const group = (groups || []).find((item) => { + const itemName = String(item?.name || '').trim().toLowerCase(); + if (!itemName) return false; + if (itemName !== normalized) return false; + return !item.platform || item.platform === 'openai'; + }); + if (group) { + matched.push(group); + } else { + missing.push(targetName); + } + } + + if (missing.length) { + throw new Error(`SUB2API 中未找到以下 openai 分组:${missing.join('、')}。`); + } + + return matched; +} + function normalizeSub2ApiProxyPreference(value) { return String(value || '').trim(); } @@ -448,16 +495,19 @@ async function step1_generateOpenAiAuthUrl(payload = {}, options = {}) { const { report = true } = options; const logStep = Number.isInteger(payload?.logStep) ? payload.logStep : 1; const redirectUri = normalizeRedirectUri(); - const groupName = (payload.sub2apiGroupName || SUB2API_DEFAULT_GROUP_NAME).trim() || SUB2API_DEFAULT_GROUP_NAME; + const groupNames = normalizeSub2ApiGroupNames(payload.sub2apiGroupName || SUB2API_DEFAULT_GROUP_NAME); + const groupName = groupNames[0] || SUB2API_DEFAULT_GROUP_NAME; const { origin, token } = await loginSub2Api(payload); - const group = await getGroupByName(origin, token, groupName); + const groups = await getGroupsByNames(origin, token, groupNames); + const group = groups[0]; const proxyPreference = resolveSub2ApiProxyPreference(payload); const proxy = proxyPreference ? await resolveSub2ApiProxy(origin, token, proxyPreference) : null; const proxyId = normalizeProxyId(proxy?.id); const draftName = buildDraftAccountName(group.name || groupName); + const groupLabel = groups.map((item) => `${item.name}(#${item.id})`).join('、'); - log(`步骤 ${logStep}:已登录 SUB2API,使用分组 ${group.name}(#${group.id})。`); + log(`步骤 ${logStep}:已登录 SUB2API,使用分组 ${groupLabel}。`); if (proxy) { log(`步骤 ${logStep}:已选择 SUB2API 默认代理 ${buildProxyDisplayName(proxy)}。`); } else { @@ -492,6 +542,7 @@ async function step1_generateOpenAiAuthUrl(payload = {}, options = {}) { sub2apiSessionId: sessionId, sub2apiOAuthState: oauthState, sub2apiGroupId: group.id, + sub2apiGroupIds: groups.map((item) => item.id), sub2apiDraftName: draftName, sub2apiProxyId: proxyId, }; @@ -517,9 +568,17 @@ async function step9_submitOpenAiCallback(payload = {}) { const proxySelector = preferredProxyId || proxyPreference; const proxy = proxySelector ? await resolveSub2ApiProxy(origin, token, proxySelector) : null; const proxyId = normalizeProxyId(proxy?.id); - const group = payload.sub2apiGroupId - ? { id: payload.sub2apiGroupId, name: payload.sub2apiGroupName || backgroundState.sub2apiGroupName || SUB2API_DEFAULT_GROUP_NAME } - : await getGroupByName(origin, token, payload.sub2apiGroupName || backgroundState.sub2apiGroupName || SUB2API_DEFAULT_GROUP_NAME); + const storedGroupIds = Array.isArray(payload.sub2apiGroupIds) + ? payload.sub2apiGroupIds + : (Array.isArray(backgroundState.sub2apiGroupIds) ? backgroundState.sub2apiGroupIds : []); + const groupIdsFromState = storedGroupIds + .map((id) => Number(id)) + .filter((id) => Number.isFinite(id) && id > 0); + const groups = groupIdsFromState.length + ? groupIdsFromState.map((id) => ({ id })) + : (payload.sub2apiGroupId + ? [{ id: payload.sub2apiGroupId, name: payload.sub2apiGroupName || backgroundState.sub2apiGroupName || SUB2API_DEFAULT_GROUP_NAME }] + : await getGroupsByNames(origin, token, payload.sub2apiGroupName || backgroundState.sub2apiGroupName || SUB2API_DEFAULT_GROUP_NAME)); if (!sessionId) { throw new Error('缺少 SUB2API session_id,请重新执行步骤 1。'); @@ -551,8 +610,10 @@ 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) { + const groupIds = groups + .map((group) => Number(group.id)) + .filter((id) => Number.isFinite(id) && id > 0); + if (!groupIds.length) { throw new Error('SUB2API 返回的目标分组 ID 无效。'); } const accountName = resolvedEmail @@ -568,7 +629,7 @@ async function step9_submitOpenAiCallback(payload = {}) { concurrency: SUB2API_DEFAULT_CONCURRENCY, priority: SUB2API_DEFAULT_PRIORITY, rate_multiplier: SUB2API_DEFAULT_RATE_MULTIPLIER, - group_ids: [groupId], + group_ids: groupIds, auto_pause_on_expired: true, }; if (proxyId) { diff --git a/sidepanel/sidepanel.html b/sidepanel/sidepanel.html index d29e83b..f828332 100644 --- a/sidepanel/sidepanel.html +++ b/sidepanel/sidepanel.html @@ -194,7 +194,7 @@