Add Plus SUB2API session access strategy
This commit is contained in:
@@ -592,6 +592,18 @@
|
||||
return method === 'gopay' ? 'GoPay' : 'PayPal';
|
||||
}
|
||||
|
||||
function normalizePlusAccountAccessStrategyForDisplay(value = '') {
|
||||
return String(value || '').trim().toLowerCase() === 'sub2api_codex_session'
|
||||
? 'sub2api_codex_session'
|
||||
: 'oauth';
|
||||
}
|
||||
|
||||
function getPlusAccountAccessStrategyLabel(value = '') {
|
||||
return normalizePlusAccountAccessStrategyForDisplay(value) === 'sub2api_codex_session'
|
||||
? '导入当前 ChatGPT 会话到 SUB2API'
|
||||
: 'OAuth';
|
||||
}
|
||||
|
||||
async function handlePlatformVerifyStepData(payload) {
|
||||
if (payload.localhostUrl) {
|
||||
await closeLocalhostCallbackTabs(payload.localhostUrl);
|
||||
@@ -1421,6 +1433,9 @@
|
||||
const plusPaymentChanged = Object.prototype.hasOwnProperty.call(updates, 'plusPaymentMethod')
|
||||
&& normalizePlusPaymentMethodForDisplay(currentState?.plusPaymentMethod || 'paypal')
|
||||
!== normalizePlusPaymentMethodForDisplay(updates.plusPaymentMethod || 'paypal');
|
||||
const plusAccountAccessStrategyChanged = Object.prototype.hasOwnProperty.call(updates, 'plusAccountAccessStrategy')
|
||||
&& normalizePlusAccountAccessStrategyForDisplay(currentState?.plusAccountAccessStrategy || 'oauth')
|
||||
!== normalizePlusAccountAccessStrategyForDisplay(updates.plusAccountAccessStrategy || 'oauth');
|
||||
const phoneSignupReloginAfterBindEmailChanged = Object.prototype.hasOwnProperty.call(updates, 'phoneSignupReloginAfterBindEmailEnabled')
|
||||
&& Boolean(currentState?.phoneSignupReloginAfterBindEmailEnabled) !== Boolean(updates.phoneSignupReloginAfterBindEmailEnabled);
|
||||
const nextPlusModeEnabled = Object.prototype.hasOwnProperty.call(updates, 'plusModeEnabled')
|
||||
@@ -1428,6 +1443,7 @@
|
||||
: Boolean(currentState?.plusModeEnabled);
|
||||
const stepModeChanged = modeChanged
|
||||
|| (nextPlusModeEnabled && plusPaymentChanged)
|
||||
|| (nextPlusModeEnabled && plusAccountAccessStrategyChanged)
|
||||
|| phoneSignupReloginAfterBindEmailChanged;
|
||||
const oauthFlowTimeoutDisabled = Object.prototype.hasOwnProperty.call(updates, 'oauthFlowTimeoutEnabled')
|
||||
&& updates.oauthFlowTimeoutEnabled === false;
|
||||
@@ -1450,11 +1466,45 @@
|
||||
? nextHostPreference
|
||||
: '';
|
||||
}
|
||||
if (stepModeChanged && typeof getStepIdsForState === 'function') {
|
||||
const nextStateForSteps = { ...currentState, ...stateUpdates };
|
||||
const nextNodeIds = typeof getNodeIdsForState === 'function'
|
||||
? getNodeIdsForState(nextStateForSteps)
|
||||
: getStepIdsForState(nextStateForSteps).map((stepId) => getStepKeyForState(stepId, nextStateForSteps)).filter(Boolean);
|
||||
const currentNodeIds = typeof getNodeIdsForState === 'function'
|
||||
? getNodeIdsForState(currentState)
|
||||
: (typeof getStepIdsForState === 'function'
|
||||
? getStepIdsForState(currentState).map((stepId) => getStepKeyForState(stepId, currentState)).filter(Boolean)
|
||||
: []);
|
||||
const nextStateForSteps = { ...currentState, ...stateUpdates };
|
||||
const nextNodeIds = typeof getNodeIdsForState === 'function'
|
||||
? getNodeIdsForState(nextStateForSteps)
|
||||
: (typeof getStepIdsForState === 'function'
|
||||
? getStepIdsForState(nextStateForSteps).map((stepId) => getStepKeyForState(stepId, nextStateForSteps)).filter(Boolean)
|
||||
: []);
|
||||
const nodeTopologyChanged = currentNodeIds.length !== nextNodeIds.length
|
||||
|| currentNodeIds.some((nodeId, index) => nodeId !== nextNodeIds[index]);
|
||||
const shouldRebuildNodeStatuses = stepModeChanged || nodeTopologyChanged;
|
||||
if (shouldRebuildNodeStatuses && nextNodeIds.length > 0) {
|
||||
Object.assign(stateUpdates, {
|
||||
oauthUrl: null,
|
||||
localhostUrl: null,
|
||||
oauthFlowDeadlineAt: null,
|
||||
oauthFlowDeadlineSourceUrl: null,
|
||||
cpaOAuthState: null,
|
||||
cpaManagementOrigin: null,
|
||||
sub2apiSessionId: null,
|
||||
sub2apiOAuthState: null,
|
||||
sub2apiGroupId: null,
|
||||
sub2apiGroupIds: [],
|
||||
sub2apiDraftName: null,
|
||||
sub2apiProxyId: null,
|
||||
codex2apiSessionId: null,
|
||||
codex2apiOAuthState: null,
|
||||
plusManualConfirmationPending: false,
|
||||
plusManualConfirmationRequestId: '',
|
||||
plusManualConfirmationStep: 0,
|
||||
plusManualConfirmationMethod: '',
|
||||
plusManualConfirmationTitle: '',
|
||||
plusManualConfirmationMessage: '',
|
||||
});
|
||||
}
|
||||
if (shouldRebuildNodeStatuses && nextNodeIds.length > 0) {
|
||||
stateUpdates.nodeStatuses = Object.fromEntries(nextNodeIds.map((nodeId) => [nodeId, 'pending']));
|
||||
stateUpdates.currentNodeId = '';
|
||||
}
|
||||
@@ -1510,9 +1560,12 @@
|
||||
const selectedPlusPaymentMethod = getPlusPaymentMethodLabel(
|
||||
stateUpdates.plusPaymentMethod ?? currentState?.plusPaymentMethod ?? 'paypal'
|
||||
);
|
||||
const selectedPlusAccountAccessStrategy = getPlusAccountAccessStrategyLabel(
|
||||
stateUpdates.plusAccountAccessStrategy ?? currentState?.plusAccountAccessStrategy ?? 'oauth'
|
||||
);
|
||||
await addLog(
|
||||
Boolean(updates.plusModeEnabled)
|
||||
? `Plus 模式已开启,已切换为 Plus Checkout 步骤,当前支付方式:${selectedPlusPaymentMethod}。`
|
||||
? `Plus 模式已开启,已切换为 Plus Checkout 步骤,当前支付方式:${selectedPlusPaymentMethod},账号接入策略:${selectedPlusAccountAccessStrategy}。`
|
||||
: 'Plus 模式已关闭,已恢复普通注册授权步骤。',
|
||||
'info'
|
||||
);
|
||||
@@ -1521,6 +1574,11 @@
|
||||
stateUpdates.plusPaymentMethod ?? currentState?.plusPaymentMethod ?? 'paypal'
|
||||
);
|
||||
await addLog(`Plus 支付方式已切换为 ${selectedPlusPaymentMethod},已更新对应的 Plus 步骤。`, 'info');
|
||||
} else if (plusAccountAccessStrategyChanged && nextPlusModeEnabled) {
|
||||
const selectedPlusAccountAccessStrategy = getPlusAccountAccessStrategyLabel(
|
||||
stateUpdates.plusAccountAccessStrategy ?? currentState?.plusAccountAccessStrategy ?? 'oauth'
|
||||
);
|
||||
await addLog(`Plus 账号接入策略已切换为 ${selectedPlusAccountAccessStrategy},已更新对应的 Plus 尾链。`, 'info');
|
||||
}
|
||||
return {
|
||||
ok: true,
|
||||
|
||||
@@ -2,8 +2,42 @@
|
||||
root.MultiPageBackgroundGoPayManualConfirm = factory();
|
||||
})(typeof self !== 'undefined' ? self : globalThis, function createBackgroundGoPayManualConfirmModule() {
|
||||
const PLUS_CHECKOUT_SOURCE = 'plus-checkout';
|
||||
const GOPAY_CONFIRM_NODE_ID = 'gopay-subscription-confirm';
|
||||
const SUB2API_SESSION_IMPORT_NODE_ID = 'sub2api-session-import';
|
||||
const DEFAULT_CONFIRM_TITLE = 'GoPay 订阅确认';
|
||||
const DEFAULT_CONFIRM_MESSAGE = 'GoPay 订阅页已打开。请先手动完成订阅,完成后确认继续 OAuth 登录。';
|
||||
const OAUTH_CONTINUATION_LABEL = 'OAuth 登录';
|
||||
const SUB2API_SESSION_CONTINUATION_LABEL = '导入当前 ChatGPT 会话到 SUB2API';
|
||||
|
||||
function normalizeString(value = '') {
|
||||
return String(value || '').trim();
|
||||
}
|
||||
|
||||
function getContinuationActionLabel(state = {}, options = {}) {
|
||||
const { getNodeIdsForState = null } = options;
|
||||
if (typeof getNodeIdsForState === 'function') {
|
||||
const nodeIds = getNodeIdsForState(state)
|
||||
.map((nodeId) => normalizeString(nodeId))
|
||||
.filter(Boolean);
|
||||
const currentNodeId = normalizeString(state?.nodeId || GOPAY_CONFIRM_NODE_ID) || GOPAY_CONFIRM_NODE_ID;
|
||||
const currentNodeIndex = nodeIds.indexOf(currentNodeId);
|
||||
const nextNodeId = currentNodeIndex >= 0
|
||||
? normalizeString(nodeIds[currentNodeIndex + 1])
|
||||
: '';
|
||||
|
||||
if (
|
||||
nextNodeId === SUB2API_SESSION_IMPORT_NODE_ID
|
||||
|| (currentNodeIndex < 0 && nodeIds.includes(SUB2API_SESSION_IMPORT_NODE_ID))
|
||||
) {
|
||||
return SUB2API_SESSION_CONTINUATION_LABEL;
|
||||
}
|
||||
}
|
||||
return OAUTH_CONTINUATION_LABEL;
|
||||
}
|
||||
|
||||
function buildDefaultConfirmMessage(state = {}, options = {}) {
|
||||
const continuationActionLabel = getContinuationActionLabel(state, options);
|
||||
return `GoPay 订阅页已打开。请先手动完成订阅,完成后确认继续${continuationActionLabel}。`;
|
||||
}
|
||||
|
||||
function createGoPayManualConfirmExecutor(deps = {}) {
|
||||
const {
|
||||
@@ -12,6 +46,7 @@
|
||||
chrome,
|
||||
createAutomationTab = null,
|
||||
getTabId,
|
||||
getNodeIdsForState = null,
|
||||
isTabAlive,
|
||||
registerTab,
|
||||
setState,
|
||||
@@ -40,7 +75,7 @@
|
||||
}
|
||||
}
|
||||
|
||||
const checkoutUrl = String(state?.plusCheckoutUrl || '').trim();
|
||||
const checkoutUrl = normalizeString(state?.plusCheckoutUrl);
|
||||
if (!checkoutUrl) {
|
||||
throw new Error('步骤 7:未检测到 GoPay 订阅页,请先执行步骤 6。');
|
||||
}
|
||||
@@ -63,19 +98,21 @@
|
||||
}
|
||||
|
||||
async function executeGoPayManualConfirm(state = {}) {
|
||||
const visibleStep = Number(state?.visibleStep) || 7;
|
||||
const tabId = await resolveCheckoutTabId(state);
|
||||
if (chrome?.tabs?.update && tabId) {
|
||||
await chrome.tabs.update(tabId, { active: true }).catch(() => {});
|
||||
}
|
||||
|
||||
const continuationActionLabel = getContinuationActionLabel(state, { getNodeIdsForState });
|
||||
const payload = {
|
||||
plusCheckoutTabId: tabId,
|
||||
plusManualConfirmationPending: true,
|
||||
plusManualConfirmationRequestId: buildRequestId(),
|
||||
plusManualConfirmationStep: 7,
|
||||
plusManualConfirmationStep: visibleStep,
|
||||
plusManualConfirmationMethod: 'gopay',
|
||||
plusManualConfirmationTitle: DEFAULT_CONFIRM_TITLE,
|
||||
plusManualConfirmationMessage: DEFAULT_CONFIRM_MESSAGE,
|
||||
plusManualConfirmationMessage: buildDefaultConfirmMessage(state, { getNodeIdsForState }),
|
||||
};
|
||||
|
||||
await setState(payload);
|
||||
@@ -83,7 +120,10 @@
|
||||
broadcastDataUpdate(payload);
|
||||
}
|
||||
|
||||
await addLog('步骤 7:正在等待手动完成 GoPay 订阅,确认后继续 OAuth 登录。', 'info');
|
||||
await addLog(
|
||||
`步骤 ${visibleStep}:正在等待手动完成 GoPay 订阅,确认后继续${continuationActionLabel}。`,
|
||||
'info'
|
||||
);
|
||||
}
|
||||
|
||||
return {
|
||||
|
||||
@@ -0,0 +1,185 @@
|
||||
(function attachBackgroundSub2ApiSessionImport(root, factory) {
|
||||
root.MultiPageBackgroundSub2ApiSessionImport = factory();
|
||||
})(typeof self !== 'undefined' ? self : globalThis, function createBackgroundSub2ApiSessionImportModule() {
|
||||
const PLUS_CHECKOUT_SOURCE = 'plus-checkout';
|
||||
const PLUS_CHECKOUT_INJECT_FILES = ['content/utils.js', 'content/operation-delay.js', 'content/plus-checkout.js'];
|
||||
|
||||
function createSub2ApiSessionImportExecutor(deps = {}) {
|
||||
const {
|
||||
addLog: rawAddLog = async () => {},
|
||||
chrome,
|
||||
completeNodeFromBackground,
|
||||
ensureContentScriptReadyOnTabUntilStopped,
|
||||
getTabId,
|
||||
isTabAlive,
|
||||
normalizeSub2ApiUrl = (value) => value,
|
||||
registerTab,
|
||||
sendTabMessageUntilStopped,
|
||||
sleepWithStop = async () => {},
|
||||
throwIfStopped = () => {},
|
||||
waitForTabCompleteUntilStopped = async () => {},
|
||||
DEFAULT_SUB2API_GROUP_NAME = 'codex',
|
||||
} = deps;
|
||||
|
||||
let sub2ApiApi = null;
|
||||
|
||||
function addStepLog(step, message, level = 'info') {
|
||||
return rawAddLog(message, level, {
|
||||
step,
|
||||
stepKey: 'sub2api-session-import',
|
||||
});
|
||||
}
|
||||
|
||||
function getSub2ApiApi() {
|
||||
if (sub2ApiApi) {
|
||||
return sub2ApiApi;
|
||||
}
|
||||
const factory = deps.createSub2ApiApi
|
||||
|| self.MultiPageBackgroundSub2ApiApi?.createSub2ApiApi;
|
||||
if (typeof factory !== 'function') {
|
||||
throw new Error('SUB2API 接口模块未加载,无法导入当前 ChatGPT 会话。');
|
||||
}
|
||||
sub2ApiApi = factory({
|
||||
addLog: rawAddLog,
|
||||
normalizeSub2ApiUrl,
|
||||
DEFAULT_SUB2API_GROUP_NAME,
|
||||
});
|
||||
return sub2ApiApi;
|
||||
}
|
||||
|
||||
function normalizeString(value = '') {
|
||||
return String(value || '').trim();
|
||||
}
|
||||
|
||||
function resolveVisibleStep(state = {}) {
|
||||
const visibleStep = Math.floor(Number(state?.visibleStep) || 0);
|
||||
return visibleStep > 0 ? visibleStep : 10;
|
||||
}
|
||||
|
||||
function isSupportedChatGptSessionUrl(url = '') {
|
||||
try {
|
||||
const parsed = new URL(String(url || ''));
|
||||
if (!/^https?:$/i.test(parsed.protocol)) {
|
||||
return false;
|
||||
}
|
||||
const hostname = String(parsed.hostname || '').trim().toLowerCase();
|
||||
return /(^|\.)chatgpt\.com$/.test(hostname)
|
||||
|| hostname === 'chat.openai.com'
|
||||
|| /(^|\.)openai\.com$/.test(hostname);
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
async function resolveSessionTabId(state = {}) {
|
||||
const registeredTabId = typeof getTabId === 'function'
|
||||
? await getTabId(PLUS_CHECKOUT_SOURCE)
|
||||
: null;
|
||||
if (registeredTabId && typeof isTabAlive === 'function' && await isTabAlive(PLUS_CHECKOUT_SOURCE)) {
|
||||
return Number(registeredTabId) || 0;
|
||||
}
|
||||
|
||||
const storedTabId = Number(state?.plusCheckoutTabId) || 0;
|
||||
if (storedTabId && chrome?.tabs?.get) {
|
||||
const tab = await chrome.tabs.get(storedTabId).catch(() => null);
|
||||
if (tab?.id) {
|
||||
if (typeof registerTab === 'function') {
|
||||
await registerTab(PLUS_CHECKOUT_SOURCE, tab.id);
|
||||
}
|
||||
return tab.id;
|
||||
}
|
||||
}
|
||||
|
||||
throw new Error('未找到可读取 ChatGPT 会话的 Plus 标签页,请先完成当前 Plus 支付链路。');
|
||||
}
|
||||
|
||||
async function getResolvedSessionTab(tabId, visibleStep) {
|
||||
const tab = await chrome?.tabs?.get?.(tabId).catch(() => null);
|
||||
if (!tab?.id) {
|
||||
throw new Error(`步骤 ${visibleStep}:Plus 会话标签页不存在或已关闭,无法继续导入 SUB2API。`);
|
||||
}
|
||||
if (!isSupportedChatGptSessionUrl(tab.url)) {
|
||||
throw new Error(`步骤 ${visibleStep}:当前标签页不在 ChatGPT / OpenAI 页面,无法读取当前登录会话。`);
|
||||
}
|
||||
return tab;
|
||||
}
|
||||
|
||||
async function readCurrentChatGptSession(tabId, visibleStep) {
|
||||
await waitForTabCompleteUntilStopped(tabId);
|
||||
await sleepWithStop(1000);
|
||||
await ensureContentScriptReadyOnTabUntilStopped(PLUS_CHECKOUT_SOURCE, tabId, {
|
||||
inject: PLUS_CHECKOUT_INJECT_FILES,
|
||||
injectSource: PLUS_CHECKOUT_SOURCE,
|
||||
logMessage: `步骤 ${visibleStep}:正在等待 ChatGPT 会话页完成加载,再继续读取当前登录会话...`,
|
||||
});
|
||||
|
||||
const sessionResult = await sendTabMessageUntilStopped(tabId, PLUS_CHECKOUT_SOURCE, {
|
||||
type: 'PLUS_CHECKOUT_GET_STATE',
|
||||
source: 'background',
|
||||
payload: {
|
||||
includeSession: true,
|
||||
includeAccessToken: true,
|
||||
},
|
||||
});
|
||||
if (sessionResult?.error) {
|
||||
throw new Error(sessionResult.error);
|
||||
}
|
||||
|
||||
const session = sessionResult?.session && typeof sessionResult.session === 'object' && !Array.isArray(sessionResult.session)
|
||||
? sessionResult.session
|
||||
: null;
|
||||
const accessToken = normalizeString(
|
||||
sessionResult?.accessToken
|
||||
|| session?.accessToken
|
||||
);
|
||||
if (!session && !accessToken) {
|
||||
throw new Error(`步骤 ${visibleStep}:未读取到有效的 ChatGPT 会话或 accessToken,请确认当前标签页仍处于已登录状态。`);
|
||||
}
|
||||
|
||||
return {
|
||||
session,
|
||||
accessToken,
|
||||
};
|
||||
}
|
||||
|
||||
async function executeSub2ApiSessionImport(state = {}) {
|
||||
throwIfStopped();
|
||||
const visibleStep = resolveVisibleStep(state);
|
||||
const api = getSub2ApiApi();
|
||||
|
||||
await addStepLog(visibleStep, '正在定位当前 Plus 会话页并准备导入 SUB2API...', 'info');
|
||||
const tabId = await resolveSessionTabId(state);
|
||||
const tab = await getResolvedSessionTab(tabId, visibleStep);
|
||||
if (chrome?.tabs?.update) {
|
||||
await chrome.tabs.update(tab.id, { active: true }).catch(() => {});
|
||||
}
|
||||
|
||||
await addStepLog(visibleStep, '正在读取当前 ChatGPT 登录会话...', 'info');
|
||||
const sessionState = await readCurrentChatGptSession(tab.id, visibleStep);
|
||||
throwIfStopped();
|
||||
|
||||
const result = await api.importCurrentChatGptSession({
|
||||
...state,
|
||||
session: sessionState.session,
|
||||
accessToken: sessionState.accessToken,
|
||||
}, {
|
||||
visibleStep,
|
||||
logLabel: `步骤 ${visibleStep}`,
|
||||
logOptions: { step: visibleStep, stepKey: 'sub2api-session-import' },
|
||||
timeoutMs: 120000,
|
||||
importTimeoutMs: 120000,
|
||||
});
|
||||
|
||||
await completeNodeFromBackground(state?.nodeId || 'sub2api-session-import', result);
|
||||
}
|
||||
|
||||
return {
|
||||
executeSub2ApiSessionImport,
|
||||
isSupportedChatGptSessionUrl,
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
createSub2ApiSessionImportExecutor,
|
||||
};
|
||||
});
|
||||
@@ -345,6 +345,83 @@
|
||||
return `${prefix}-${stamp}-${random}`;
|
||||
}
|
||||
|
||||
function normalizeCodexSessionObject(value) {
|
||||
return value && typeof value === 'object' && !Array.isArray(value) ? value : null;
|
||||
}
|
||||
|
||||
function buildCodexSessionImportContent(session, accessToken = '') {
|
||||
const normalizedAccessToken = normalizeString(accessToken);
|
||||
const sessionObject = normalizeCodexSessionObject(session);
|
||||
|
||||
if (sessionObject) {
|
||||
const contentObject = normalizedAccessToken
|
||||
? {
|
||||
...sessionObject,
|
||||
accessToken: normalizedAccessToken,
|
||||
}
|
||||
: sessionObject;
|
||||
return JSON.stringify(contentObject);
|
||||
}
|
||||
|
||||
if (normalizedAccessToken) {
|
||||
return normalizedAccessToken;
|
||||
}
|
||||
|
||||
throw new Error('未读取到可导入的 ChatGPT 会话或 accessToken。');
|
||||
}
|
||||
|
||||
function resolveCodexSessionImportExpiresAt(session) {
|
||||
const sessionObject = normalizeCodexSessionObject(session);
|
||||
const expiresValue = normalizeString(sessionObject?.expires);
|
||||
if (!expiresValue) {
|
||||
return null;
|
||||
}
|
||||
const expiresAtMs = Date.parse(expiresValue);
|
||||
if (!Number.isFinite(expiresAtMs) || expiresAtMs <= 0) {
|
||||
return null;
|
||||
}
|
||||
return Math.floor(expiresAtMs / 1000);
|
||||
}
|
||||
|
||||
function normalizeCodexSessionImportMessages(messages) {
|
||||
return (Array.isArray(messages) ? messages : [])
|
||||
.map((item, index) => ({
|
||||
index: Number(item?.index) || index + 1,
|
||||
name: normalizeString(item?.name),
|
||||
message: normalizeString(item?.message),
|
||||
}))
|
||||
.filter((item) => item.message);
|
||||
}
|
||||
|
||||
function normalizeCodexSessionImportResult(result) {
|
||||
return {
|
||||
total: Math.max(0, Number(result?.total) || 0),
|
||||
created: Math.max(0, Number(result?.created) || 0),
|
||||
updated: Math.max(0, Number(result?.updated) || 0),
|
||||
skipped: Math.max(0, Number(result?.skipped) || 0),
|
||||
failed: Math.max(0, Number(result?.failed) || 0),
|
||||
items: Array.isArray(result?.items) ? result.items : [],
|
||||
warnings: normalizeCodexSessionImportMessages(result?.warnings),
|
||||
errors: normalizeCodexSessionImportMessages(result?.errors),
|
||||
};
|
||||
}
|
||||
|
||||
function buildCodexSessionImportSummary(result) {
|
||||
const normalized = normalizeCodexSessionImportResult(result);
|
||||
return `SUB2API 会话导入完成:新建 ${normalized.created},更新 ${normalized.updated},跳过 ${normalized.skipped},失败 ${normalized.failed}`;
|
||||
}
|
||||
|
||||
function getCodexSessionImportFailureMessage(result) {
|
||||
const normalized = normalizeCodexSessionImportResult(result);
|
||||
const detail = normalized.errors.map((item) => item.message).find(Boolean)
|
||||
|| normalized.warnings.map((item) => item.message).find(Boolean)
|
||||
|| normalized.items
|
||||
.map((item) => normalizeString(item?.message))
|
||||
.find(Boolean)
|
||||
|| buildCodexSessionImportSummary(normalized);
|
||||
return detail || 'SUB2API 会话导入失败。';
|
||||
}
|
||||
|
||||
function parseLocalhostCallback(rawUrl, visibleStep = 10) {
|
||||
let parsed;
|
||||
try {
|
||||
@@ -580,14 +657,93 @@
|
||||
};
|
||||
}
|
||||
|
||||
async function importCurrentChatGptSession(state = {}, options = {}) {
|
||||
const logLabel = normalizeString(options.logLabel) || 'SUB2API 会话导入';
|
||||
const session = normalizeCodexSessionObject(state?.session);
|
||||
const accessToken = normalizeString(
|
||||
state?.accessToken
|
||||
|| session?.accessToken
|
||||
);
|
||||
const importContent = buildCodexSessionImportContent(session, accessToken);
|
||||
const importExpiresAt = resolveCodexSessionImportExpiresAt(session);
|
||||
|
||||
await logWithOptions(`${logLabel}:正在通过 SUB2API 管理接口登录并准备导入当前 ChatGPT 会话...`, 'info', options);
|
||||
const { origin, token } = await loginSub2Api(state, options);
|
||||
const groupNames = state.sub2apiGroupName || DEFAULT_SUB2API_GROUP_NAME;
|
||||
const groups = await getGroupsByNames(origin, token, groupNames, options);
|
||||
const groupLabel = groups.map((item) => `${item.name}(${item.id})`).join('、');
|
||||
const proxyPreference = resolveSub2ApiProxyPreference(state);
|
||||
const proxy = proxyPreference ? await resolveSub2ApiProxy(origin, token, proxyPreference, options) : null;
|
||||
const proxyId = normalizeProxyId(proxy?.id);
|
||||
const accountPriority = resolveSub2ApiAccountPriority(state);
|
||||
|
||||
await logWithOptions(`${logLabel}:已登录 SUB2API,使用分组 ${groupLabel}。`, 'info', options);
|
||||
if (proxy) {
|
||||
await logWithOptions(`${logLabel}:已选择 SUB2API 默认代理 ${buildProxyDisplayName(proxy)}。`, 'info', options);
|
||||
} else {
|
||||
await logWithOptions(`${logLabel}:未配置 SUB2API 默认代理,本次将不使用代理。`, 'info', options);
|
||||
}
|
||||
|
||||
const importPayload = {
|
||||
content: importContent,
|
||||
group_ids: groups
|
||||
.map((group) => Number(group?.id))
|
||||
.filter((id) => Number.isFinite(id) && id > 0),
|
||||
priority: accountPriority,
|
||||
auto_pause_on_expired: true,
|
||||
update_existing: true,
|
||||
};
|
||||
if (!importPayload.group_ids.length) {
|
||||
throw new Error('SUB2API 返回的目标分组 ID 无效。');
|
||||
}
|
||||
if (proxyId) {
|
||||
importPayload.proxy_id = proxyId;
|
||||
}
|
||||
if (importExpiresAt) {
|
||||
importPayload.expires_at = importExpiresAt;
|
||||
}
|
||||
|
||||
await logWithOptions(`${logLabel}:正在导入当前 ChatGPT 会话到 SUB2API...`, 'info', options);
|
||||
const importResult = normalizeCodexSessionImportResult(await requestJson(origin, '/api/v1/admin/accounts/import/codex-session', {
|
||||
method: 'POST',
|
||||
token,
|
||||
timeoutMs: options.importTimeoutMs || options.timeoutMs,
|
||||
body: importPayload,
|
||||
}));
|
||||
|
||||
for (const warning of importResult.warnings) {
|
||||
await logWithOptions(`${logLabel}:${warning.message}`, 'warn', options);
|
||||
}
|
||||
|
||||
if (importResult.failed > 0) {
|
||||
throw new Error(getCodexSessionImportFailureMessage(importResult));
|
||||
}
|
||||
if (importResult.created <= 0 && importResult.updated <= 0) {
|
||||
throw new Error(getCodexSessionImportFailureMessage(importResult));
|
||||
}
|
||||
|
||||
const verifiedStatus = buildCodexSessionImportSummary(importResult);
|
||||
await logWithOptions(verifiedStatus, 'ok', options);
|
||||
return {
|
||||
verifiedStatus,
|
||||
sub2apiImportTotal: importResult.total,
|
||||
sub2apiImportCreated: importResult.created,
|
||||
sub2apiImportUpdated: importResult.updated,
|
||||
sub2apiImportSkipped: importResult.skipped,
|
||||
sub2apiImportFailed: importResult.failed,
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
buildDraftAccountName,
|
||||
buildCodexSessionImportContent,
|
||||
buildOpenAiCredentials,
|
||||
buildOpenAiExtra,
|
||||
buildProxyDisplayName,
|
||||
extractStateFromAuthUrl,
|
||||
generateOpenAiAuthUrl,
|
||||
getGroupsByNames,
|
||||
importCurrentChatGptSession,
|
||||
loginSub2Api,
|
||||
normalizeProxyId,
|
||||
normalizeRedirectUri,
|
||||
|
||||
Reference in New Issue
Block a user