K12 工作空间 Session 导出 为中转需要的json 脚本

yyjeqhc 2026-07-05 21:53 1



软件截图如图所示,可以手动复制,也可以直接下载为json,然后在sub2api上导入。


手搓玩家,也希望能减轻加号的繁琐过程,此脚本可以在账号批量加入空间以后,刷新网页,再批量导出所需格式的 session 作为号池。本人仅测试了 sub2api,其他的未测试。

脚本在下,新人不同如何作为附件上传。

实测可用,相信 codex 的质量。


/*
Run this in the DevTools console on https://chatgpt.com while signed in.

What it does:
1. Loads the ChatGPT account/workspace list.
2. Switches the _account cookie to each workspace id.
3. Fetches /api/auth/session for that workspace context.
4. Converts each session into sub2api / CPA / Cockpit / 9router JSON.
5. Shows a copy/download panel in the page.
*/
(async () => {
const CONFIG = {
// Change this to: "sub2api", "cpa", "cockpit", or "9router".
format: "sub2api",

// Empty array means all workspace accounts found in the account switcher.
// Example: ["workspace-id-1", "workspace-id-2"]
onlyWorkspaceIds: [],

includePersonalAccount: false,
includeActiveWorkspaces: true,
includeDeactivatedWorkspaces: false,
settleDelayMs: 500,
accountApiVersion: "v4-2023-04-27",

// For cpa / cockpit / 9router, this script always returns an array.
// The original pasted webpage returned a single object when only one account existed.
alwaysArrayForAccountFormats: true,
};

const COOKIE_NAMES = [
"_account",
"_account_residency_region",
"_account_routing_override",
"_account_is_fedramp",
];

const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));

const isPlainObject = (value) =>
Boolean(value) && typeof value === "object" && !Array.isArray(value);

function firstNonEmpty(...values) {
for (const value of values) {
if (typeof value === "string" && value.trim() !== "") {
return value.trim();
}
}
return undefined;
}

function readCookie(name) {
const prefix = `${encodeURIComponent(name)}=`;
const item = document.cookie
.split(";")
.map((part) => part.trim())
.find((part) => part.startsWith(prefix) || part.startsWith(`${name}=`));
if (!item) return undefined;
const raw = item.slice(item.indexOf("=") + 1);
try {
return decodeURIComponent(raw);
} catch {
return raw;
}
}

function writeCookie(name, value) {
const encodedName = encodeURIComponent(name);
const encodedValue = encodeURIComponent(String(value));
const base = `${encodedName}=${encodedValue}; Path=/; SameSite=Lax; Secure`;
document.cookie = base;
document.cookie = `${base}; Domain=.chatgpt.com`;
}

function clearCookie(name) {
const encodedName = encodeURIComponent(name);
const base = `${encodedName}=; Path=/; Max-Age=0; SameSite=Lax; Secure`;
document.cookie = base;
document.cookie = `${base}; Domain=.chatgpt.com`;
}

function snapshotCookies() {
return Object.fromEntries(COOKIE_NAMES.map((name) => [name, readCookie(name)]));
}

function restoreCookies(snapshot) {
for (const name of COOKIE_NAMES) {
clearCookie(name);
if (snapshot[name] !== undefined) {
writeCookie(name, snapshot[name]);
}
}
}

function setWorkspaceCookies(workspace) {
const cookieValues = {
_account: workspace.id,
_account_residency_region: workspace.residencyRegion,
_account_routing_override: workspace.routingOverride,
_account_is_fedramp:
typeof workspace.isFedramp === "boolean" ? String(workspace.isFedramp) : workspace.isFedramp,
};

for (const name of COOKIE_NAMES) {
const value = cookieValues[name];
if (value === undefined || value === null || value === "") {
clearCookie(name);
} else {
writeCookie(name, value);
}
}
}

function readClientBootstrap() {
const text = document.getElementById("client-bootstrap")?.textContent;
if (!text) return {};
try {
return JSON.parse(text);
} catch {
return {};
}
}

const boot = readClientBootstrap();
const bootAccessToken = firstNonEmpty(boot.session?.accessToken, boot.accessToken);

function makeHeaders(extra = {}) {
const headers = new Headers(extra);
headers.set("accept", "application/json");
headers.set("OAI-Language", boot.locale || navigator.language || "zh-CN");
if (bootAccessToken && !headers.has("Authorization")) {
headers.set("Authorization", `Bearer ${bootAccessToken}`);
}
return headers;
}

async function readResponse(response) {
const text = await response.text();
if (!text) return null;
try {
return JSON.parse(text);
} catch {
return text;
}
}

async function apiFetch(path, options = {}) {
return fetch(path, {
credentials: "include",
cache: "no-store",
...options,
headers: makeHeaders(options.headers),
});
}

async function sessionFetch() {
const response = await fetch(`/api/auth/session?workspace_export_ts=${Date.now()}`, {
method: "GET",
credentials: "include",
cache: "no-store",
headers: {
accept: "application/json",
"cache-control": "no-cache",
pragma: "no-cache",
},
});
return {
ok: response.ok,
status: response.status,
body: await readResponse(response),
};
}

function compactName(value) {
return value == null || value === "" ? "(unnamed account)" : String(value);
}

function normalizeAccountItem(item, key = "") {
const account = item?.account || item || {};
const id = firstNonEmpty(
account.account_id,
account.id,
item?.account_id,
item?.id,
item?.workspace_id,
key,
);
if (!id) return undefined;

const structure = firstNonEmpty(account.structure, item?.structure, account.type, item?.type);
const status = firstNonEmpty(
account.status,
item?.status,
account.workspace_status,
item?.workspace_status,
);
const isDeactivated = Boolean(
account.is_deactivated ??
account.deactivated ??
item?.is_deactivated ??
item?.deactivated ??
status === "deactivated",
);

return {
id,
name: compactName(
firstNonEmpty(
account.name,
account.display_name,
item?.name,
item?.display_name,
item?.workspace_name,
),
),
structure,
role: firstNonEmpty(account.account_user_role, account.role, item?.role),
isDeactivated,
planType: firstNonEmpty(account.plan_type, account.planType, item?.plan_type, item?.planType),
residencyRegion: firstNonEmpty(
account.residency_region,
account.residencyRegion,
item?.residency_region,
item?.residencyRegion,
),
routingOverride: firstNonEmpty(
account.routing_override,
account.routingOverride,
item?.routing_override,
item?.routingOverride,
),
isFedramp: account.is_fedramp ?? account.isFedramp ?? item?.is_fedramp ?? item?.isFedramp,
raw: item,
};
}

function extractAccounts(body) {
const result = [];
const seen = new Set();
const push = (account) => {
if (!account?.id || seen.has(account.id)) return;
seen.add(account.id);
result.push(account);
};

if (isPlainObject(body?.accounts)) {
const ordering = Array.isArray(body.account_ordering)
? body.account_ordering
: Object.keys(body.accounts);
for (const key of ordering) {
push(normalizeAccountItem(body.accounts[key], key));
}
}

for (const container of [body?.accounts, body?.items, body?.data, body?.workspaces]) {
if (!Array.isArray(container)) continue;
for (const item of container) {
push(normalizeAccountItem(item));
}
}

return result;
}

function filterTargets(accounts) {
const selected = new Set(CONFIG.onlyWorkspaceIds.filter(Boolean).map(String));
return accounts.filter((account) => {
const isWorkspace = account.structure === "workspace";
const isPersonal = account.structure === "personal" || account.structure === "personal_account";
if (selected.size && !selected.has(account.id)) return false;
if (!CONFIG.includePersonalAccount && isPersonal) return false;
if (!CONFIG.includePersonalAccount && !isWorkspace && !selected.size) return false;
if (account.isDeactivated && !CONFIG.includeDeactivatedWorkspaces) return false;
if (!account.isDeactivated && !CONFIG.includeActiveWorkspaces) return false;
return true;
});
}

function decodeBase64Url(value) {
const normalized = value.replace(/-/g, "+").replace(/_/g, "/");
const padded = normalized.padEnd(Math.ceil(normalized.length / 4) * 4, "=");
const binary = atob(padded);
const bytes = Uint8Array.from(binary, (char) => char.charCodeAt(0));
return new TextDecoder().decode(bytes);
}

function bytesToBase64Url(bytes) {
let binary = "";
for (let index = 0; index < bytes.length; index += 0x8000) {
binary += String.fromCharCode(...bytes.subarray(index, index + 0x8000));
}
return btoa(binary).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/g, "");
}

function encodeBase64UrlJson(value) {
return bytesToBase64Url(new TextEncoder().encode(JSON.stringify(value)));
}

function parseJwtPayload(token) {
if (typeof token !== "string" || token.trim() === "") return undefined;
const segments = token.split(".");
if (segments.length < 2) return undefined;
try {
return JSON.parse(decodeBase64Url(segments[1]));
} catch {
return undefined;
}
}

function getOpenAIAuthSection(payload) {
if (!isPlainObject(payload)) return {};
const auth = payload["https://api.openai.com/auth"];
return isPlainObject(auth) ? auth : {};
}

function getOpenAIProfileSection(payload) {
if (!isPlainObject(payload)) return {};
const profile = payload["https://api.openai.com/profile"];
return isPlainObject(profile) ? profile : {};
}

function normalizeTimestamp(value) {
if (value instanceof Date && !Number.isNaN(value.getTime())) {
return value.toISOString();
}
if (typeof value === "number" && Number.isFinite(value)) {
const milliseconds = value > 1e11 ? value : value * 1000;
const date = new Date(milliseconds);
return Number.isNaN(date.getTime()) ? undefined : date.toISOString();
}
if (typeof value !== "string" || value.trim() === "") return undefined;
const date = new Date(value);
return Number.isNaN(date.getTime()) ? undefined : date.toISOString();
}

function timestampFromUnixSeconds(value) {
const numeric = Number(value);
if (!Number.isFinite(numeric)) return undefined;
const date = new Date(numeric * 1000);
return Number.isNaN(date.getTime()) ? undefined : date.toISOString();
}

function epochSecondsFromValue(value) {
if (value === undefined || value === null || value === "") return 0;
const numeric = Number(value);
if (Number.isFinite(numeric)) {
return Math.trunc(numeric > 1e11 ? numeric / 1000 : numeric);
}
const parsed = Date.parse(String(value));
return Number.isFinite(parsed) ? Math.trunc(parsed / 1000) : 0;
}

function buildSyntheticCodexIdToken(email, accountId, planType, userId, expiresAt) {
if (!accountId) return undefined;

const now = Math.trunc(Date.now() / 1000);
const authInfo = { chatgpt_account_id: accountId };
const expires = epochSecondsFromValue(expiresAt) || now + 90 * 24 * 60 * 60;

if (planType) authInfo.chatgpt_plan_type = planType;
if (userId) {
authInfo.chatgpt_user_id = userId;
authInfo.user_id = userId;
}

const payload = {
iat: now,
exp: expires,
"https://api.openai.com/auth": authInfo,
};
if (email) payload.email = email;

return `${encodeBase64UrlJson({ alg: "none", typ: "JWT", cpa_synthetic: true })}.${encodeBase64UrlJson(payload)}.`;
}

function getExpiresIn(expiresAt, now = new Date()) {
if (!expiresAt) return undefined;
const expiresMs = new Date(expiresAt).getTime();
if (Number.isNaN(expiresMs)) return undefined;
return Math.max(0, Math.floor((expiresMs - now.getTime()) / 1000));
}

function stripUnavailable(value) {
if (Array.isArray(value)) {
return value.map(stripUnavailable).filter((item) => item !== undefined);
}
if (isPlainObject(value)) {
const entries = Object.entries(value)
.map(([key, item]) => [key, stripUnavailable(item)])
.filter(([, item]) => item !== undefined);
return entries.length ? Object.fromEntries(entries) : undefined;
}
if (value === undefined || value === null || value === "") return undefined;
return value;
}

function toEmailKey(email) {
if (typeof email !== "string") return undefined;
return email
.trim()
.toLowerCase()
.replace(/[^a-z0-9]+/g, "_")
.replace(/^_+|_+$/g, "");
}

function convertSession(record, options = {}) {
if (!isPlainObject(record)) {
throw new Error("session is not a JSON object");
}

const accessToken = firstNonEmpty(
record.accessToken,
record.access_token,
record.token?.accessToken,
record.token?.access_token,
record.credentials?.accessToken,
record.credentials?.access_token,
);
if (!accessToken) throw new Error("missing accessToken");

const sessionToken = firstNonEmpty(
record.sessionToken,
record.session_token,
record.token?.sessionToken,
record.token?.session_token,
record.credentials?.session_token,
);
const refreshToken = firstNonEmpty(
record.refreshToken,
record.refresh_token,
record.token?.refreshToken,
record.token?.refresh_token,
record.credentials?.refresh_token,
);
const inputIdToken = firstNonEmpty(
record.idToken,
record.id_token,
record.token?.idToken,
record.token?.id_token,
record.credentials?.id_token,
);

const payload = parseJwtPayload(accessToken);
const idPayload = parseJwtPayload(inputIdToken);
const auth = getOpenAIAuthSection(payload);
const idAuth = getOpenAIAuthSection(idPayload);
const profile = getOpenAIProfileSection(payload);
const expiresAt = firstNonEmpty(
payload ? timestampFromUnixSeconds(payload.exp) : undefined,
normalizeTimestamp(record.expires),
normalizeTimestamp(record.expiresAt),
normalizeTimestamp(record.expired),
normalizeTimestamp(record.expires_at),
);
const email = firstNonEmpty(
record.user?.email,
record.email,
record.credentials?.email,
record.providerSpecificData?.email,
profile.email,
idPayload?.email,
payload?.email,
);
const accountId = firstNonEmpty(
record.account?.id,
record.account_id,
record.chatgptAccountId,
record.providerSpecificData?.chatgptAccountId,
record.providerSpecificData?.chatgpt_account_id,
record.credentials?.chatgpt_account_id,
auth.chatgpt_account_id,
idAuth.chatgpt_account_id,
record.provider === "codex" ? record.id : undefined,
);
const userId = firstNonEmpty(
record.user?.id,
record.user_id,
record.chatgptUserId,
record.providerSpecificData?.chatgptUserId,
record.providerSpecificData?.chatgpt_user_id,
auth.chatgpt_user_id,
auth.user_id,
idAuth.chatgpt_user_id,
idAuth.user_id,
);
const planType = firstNonEmpty(
record.account?.planType,
record.account?.plan_type,
record.planType,
record.plan_type,
record.providerSpecificData?.chatgptPlanType,
record.providerSpecificData?.chatgpt_plan_type,
record.credentials?.plan_type,
auth.chatgpt_plan_type,
idAuth.chatgpt_plan_type,
);
const exportedAt = normalizeTimestamp(options.now || new Date());
const expiresIn = getExpiresIn(expiresAt, options.now || new Date());
const sourceName = firstNonEmpty(options.sourceName, "chatgpt-workspace");
const sourceType =
record.provider === "codex" && record.authType === "oauth"
? "9router"
: "chatgpt_web_session";
const name = firstNonEmpty(email, sourceName, "ChatGPT Account");
const syntheticIdToken = !inputIdToken
? buildSyntheticCodexIdToken(email, accountId, planType, userId, expiresAt)
: undefined;
const idToken = firstNonEmpty(inputIdToken, syntheticIdToken);

const cpa = Object.fromEntries(
Object.entries({
type: "codex",
account_id: accountId,
chatgpt_account_id: accountId,
email,
name,
plan_type: planType,
chatgpt_plan_type: planType,
id_token: idToken,
id_token_synthetic: Boolean(syntheticIdToken) || undefined,
access_token: accessToken,
refresh_token: refreshToken || "",
session_token: sessionToken,
last_refresh: exportedAt,
expired: expiresAt,
disabled: Boolean(record.disabled) || undefined,
}).filter(([, value]) => value !== undefined && value !== null),
);

const cockpit = {
type: "codex",
id_token: idToken,
access_token: accessToken,
refresh_token: refreshToken || "",
account_id: accountId,
last_refresh: exportedAt,
email,
expired: expiresAt,
account_note: firstNonEmpty(
record.account_note,
record.accountInfo,
record.account_info,
record.note,
record.notes,
record.remark,
),
};

const sub2apiAccount = stripUnavailable({
name: firstNonEmpty(name, email, sourceName, "ChatGPT Account"),
platform: "openai",
type: "oauth",
concurrency: 10,
priority: 1,
credentials: {
access_token: accessToken,
chatgpt_account_id: accountId,
chatgpt_user_id: userId,
email,
expires_at: expiresAt,
expires_in: expiresIn,
plan_type: planType,
},
extra: {
email,
email_key: toEmailKey(email),
name,
auth_provider: firstNonEmpty(record.authProvider, record.auth_provider),
source: sourceType,
last_refresh: exportedAt,
},
});

const priority = Number.isFinite(Number(record.priority)) ? Number(record.priority) : 9;
const isActive = typeof record.isActive === "boolean" ? record.isActive : !Boolean(record.disabled);
const createdAt = normalizeTimestamp(record.createdAt) || exportedAt;
const updatedAt = normalizeTimestamp(record.updatedAt) || exportedAt;
const nineRouter = stripUnavailable({
accessToken,
refreshToken,
expiresAt,
testStatus: firstNonEmpty(record.testStatus, record.test_status, "active"),
expiresIn,
providerSpecificData: {
chatgptAccountId: accountId,
chatgptPlanType: planType,
},
id: accountId,
provider: "codex",
authType: "oauth",
name,
email,
priority,
isActive,
createdAt,
updatedAt,
});

return {
sourceName,
sourcePath: options.sourcePath,
email,
name,
expiresAt,
accountId,
cpa,
cockpit,
nineRouter,
sub2apiAccount,
};
}

function mergeWorkspaceIntoSession(session, workspace) {
const account = isPlainObject(session.account) ? { ...session.account } : {};
if (!account.id && workspace.id) account.id = workspace.id;
if (!account.planType && workspace.planType) account.planType = workspace.planType;

return {
...session,
account,
account_note: firstNonEmpty(session.account_note, workspace.name),
disabled: Boolean(session.disabled ?? workspace.isDeactivated),
workspace,
};
}

function buildOutputDocument(converted, format, now = new Date()) {
if (format === "sub2api") {
return {
exported_at: normalizeTimestamp(now),
proxies: [],
accounts: converted.map((item) => item.sub2apiAccount),
};
}
if (format === "cpa") {
return CONFIG.alwaysArrayForAccountFormats || converted.length !== 1
? converted.map((item) => item.cpa)
: converted[0].cpa;
}
if (format === "cockpit") {
return CONFIG.alwaysArrayForAccountFormats || converted.length !== 1
? converted.map((item) => item.cockpit)
: converted[0].cockpit;
}
if (format === "9router") {
return CONFIG.alwaysArrayForAccountFormats || converted.length !== 1
? converted.map((item) => item.nineRouter)
: converted[0].nineRouter;
}
throw new Error(`Unsupported output format: ${format}`);
}

function createPanel() {
document.getElementById("codex-workspace-export-panel")?.remove();
const panel = document.createElement("div");
panel.id = "codex-workspace-export-panel";
panel.style.cssText = [
"position:fixed",
"right:18px",
"bottom:18px",
"width:min(760px,calc(100vw - 36px))",
"height:min(620px,calc(100vh - 36px))",
"z-index:2147483647",
"background:#101114",
"color:#f5f7fb",
"border:1px solid rgba(255,255,255,.18)",
"box-shadow:0 18px 60px rgba(0,0,0,.42)",
"border-radius:8px",
"font:13px/1.45 ui-sans-serif,system-ui,-apple-system,Segoe UI,sans-serif",
"display:flex",
"flex-direction:column",
"overflow:hidden",
].join(";");

panel.innerHTML = `
<div style="display:flex;align-items:center;gap:8px;padding:10px 12px;border-bottom:1px solid rgba(255,255,255,.12);">
<strong style="font-size:14px;">ChatGPT Workspace Session Export</strong>
<span data-role="summary" style="opacity:.72;margin-left:auto;">starting...</span>
<button data-role="copy" style="padding:6px 10px;border:0;border-radius:6px;background:#2f6fed;color:white;cursor:pointer;">Copy</button>
<button data-role="download" style="padding:6px 10px;border:0;border-radius:6px;background:#343941;color:white;cursor:pointer;">Download</button>
<button data-role="close" style="padding:6px 10px;border:0;border-radius:6px;background:#343941;color:white;cursor:pointer;">Close</button>
</div>
<textarea data-role="output" spellcheck="false" style="flex:1;margin:0;border:0;outline:0;resize:none;padding:12px;background:#0b0c0f;color:#e8eefb;font:12px/1.5 ui-monospace,SFMono-Regular,Consolas,Liberation Mono,monospace;">Loading...</textarea>
<div data-role="log" style="max-height:120px;overflow:auto;padding:8px 12px;border-top:1px solid rgba(255,255,255,.12);color:#c9d3e6;white-space:pre-wrap;"></div>
`;

document.body.appendChild(panel);

const output = panel.querySelector('[data-role="output"]');
const summary = panel.querySelector('[data-role="summary"]');
const log = panel.querySelector('[data-role="log"]');
const copyButton = panel.querySelector('[data-role="copy"]');
const downloadButton = panel.querySelector('[data-role="download"]');
const closeButton = panel.querySelector('[data-role="close"]');

copyButton.addEventListener("click", async () => {
await navigator.clipboard.writeText(output.value);
copyButton.textContent = "Copied";
setTimeout(() => {
copyButton.textContent = "Copy";
}, 1200);
});

downloadButton.addEventListener("click", () => {
const blob = new Blob([output.value], { type: "application/json" });
const url = URL.createObjectURL(blob);
const link = document.createElement("a");
const stamp = new Date().toISOString().replace(/[:.]/g, "-");
link.href = url;
link.download = `chatgpt-workspace-sessions-${CONFIG.format}-${stamp}.json`;
link.click();
URL.revokeObjectURL(url);
});

closeButton.addEventListener("click", () => panel.remove());

return {
setSummary(text) {
summary.textContent = text;
},
setOutput(value) {
output.value = value;
},
appendLog(line) {
log.textContent += `${line}\n`;
log.scrollTop = log.scrollHeight;
},
};
}

const panel = createPanel();
const originalCookies = snapshotCookies();

try {
if (!location.hostname.endsWith("chatgpt.com")) {
throw new Error("Open https://chatgpt.com first, then run this script.");
}

panel.setSummary("loading accounts...");
panel.appendLog("Loading account list...");

const checkUrl = `/backend-api/accounts/check/${CONFIG.accountApiVersion}?timezone_offset_min=${encodeURIComponent(
String(new Date().getTimezoneOffset()),
)}`;
const checkResponse = await apiFetch(checkUrl, { method: "GET" });
const checkBody = await readResponse(checkResponse);

if (!checkResponse.ok) {
console.error("Account check failed:", checkResponse.status, checkBody);
throw new Error(`Could not load account list. HTTP ${checkResponse.status}`);
}

const accounts = extractAccounts(checkBody);
const targets = filterTargets(accounts);

console.table(
targets.map((account) => ({
name: account.name,
account_id: account.id,
structure: account.structure,
deactivated: account.isDeactivated,
role: account.role,
})),
);

if (!targets.length) {
throw new Error(
`No matching workspace found. Accounts detected: ${accounts.length}. Try setting includePersonalAccount=true or onlyWorkspaceIds=[...].`,
);
}

panel.appendLog(`Found ${targets.length} target account(s).`);
panel.setSummary(`0/${targets.length}`);

const converted = [];
const sessions = [];
const failures = [];
const now = new Date();

for (let index = 0; index < targets.length; index += 1) {
const workspace = targets[index];
panel.setSummary(`${index}/${targets.length}`);
panel.appendLog(`Fetching session: ${workspace.name} (${workspace.id})`);

try {
setWorkspaceCookies(workspace);
await sleep(CONFIG.settleDelayMs);

const sessionResult = await sessionFetch();
if (!sessionResult.ok) {
failures.push({
workspace,
status: sessionResult.status,
response: sessionResult.body,
});
panel.appendLog(` failed: HTTP ${sessionResult.status}`);
continue;
}

if (!isPlainObject(sessionResult.body)) {
failures.push({
workspace,
status: sessionResult.status,
response: sessionResult.body,
error: "session response was not an object",
});
panel.appendLog(" failed: session response was not an object");
continue;
}

const sessionRecord = mergeWorkspaceIntoSession(sessionResult.body, workspace);
const item = convertSession(sessionRecord, {
now,
sourceName: workspace.name,
sourcePath: workspace.id,
});

if (item.accountId && item.accountId !== workspace.id) {
failures.push({
workspace,
status: sessionResult.status,
error: `session account id mismatch: got ${item.accountId}`,
});
panel.appendLog(` warning: session account id mismatch, got ${item.accountId}`);
}

sessions.push({
workspace,
session: sessionResult.body,
});
converted.push(item);
panel.appendLog(" ok");
} catch (error) {
failures.push({
workspace,
error: String(error?.message || error),
});
panel.appendLog(` failed: ${String(error?.message || error)}`);
}
}

restoreCookies(originalCookies);

const output = buildOutputDocument(converted, CONFIG.format, now);
const outputText = JSON.stringify(output, null, 2);
panel.setOutput(outputText);
panel.setSummary(`${converted.length}/${targets.length} exported`);

window.__chatgptWorkspaceSessionExport = {
config: CONFIG,
accounts,
targets,
converted,
sessions,
failures,
output,
outputText,
};

console.log("ChatGPT workspace session export finished.");
console.log("Result object: window.__chatgptWorkspaceSessionExport");
console.table(
converted.map((item) => ({
source: item.sourceName,
account_id: item.accountId,
email: item.email,
expires_at: item.expiresAt,
})),
);
if (failures.length) {
console.warn("Some workspaces failed:", failures);
}

panel.appendLog(`Done. Exported ${converted.length}/${targets.length}.`);
if (failures.length) {
panel.appendLog(`Failures: ${failures.length}. See window.__chatgptWorkspaceSessionExport.failures`);
}
} catch (error) {
restoreCookies(originalCookies);
panel.setSummary("failed");
panel.setOutput(String(error?.stack || error));
console.error(error);
}
})();
最新回复 (3)
  • 大笨牛 07-12 18:57
    1

    感谢,还有2个号活着呢,还能再蹭蹭

  • sunney 07-12 18:58
    2

    可以直接到我这个转换网站,除非使用登录模式,否则不会上传任何信息

    https://ex.cccy.me/

  • 夕凪 07-12 19:01
    3

    问一下在CPA上面如果一个K12账号的5h用完后也把周限用完了,继续请求这个账号会被封吗?

* 帖子来源Linux.do
返回