很多玩 AI 的朋友应该都遇到过这种情况:为了贪图免费额度或者一时手滑,把学校提供的 K12 教育版空间(Workspace)绑定到了自己的个人大号上。结果就是账号环境变得极其“脏”,不仅功能受限,看着也不舒服。更重要的是,很多过期的或被停用的空间,在界面上根本没有退出按钮,想删都删不掉,挂在列表里极其碍眼。

最近看到有开发者改写了一个超实用的 JS 脚本,专门解决这个问题。它不需要你安装任何软件,直接在浏览器控制台运行,就能批量把当前账号关联的所有空间“踢”出去。下面我就把这个“一键下车”的教程和原理解析一下。

⚠️ 核心原理:利用官方 DELETE API

其实官方并没有提供“一键退出所有空间”的界面按钮,但在后端 API 层面是支持操作的。这个脚本的核心逻辑就是:

  1. 获取当前会话 Token:先拿到你登录状态的 AccessToken。
  2. 遍历账号列表:调用 /backend-api/accounts/check 接口,获取当前账号绑定的所有空间 ID 列表。
  3. 发送删除请求:循环遍历这些 ID,向 /backend-api/accounts/{account_id}/users/{user_id} 发送 DELETE 请求。

只要请求返回 200,你就成功从该空间“下车”了。

🔧 使用教程:只需三步

第一步:准备环境 打开 ChatGPT 官网,确认你已经登录需要清理的那个“大号”。建议在操作前先切换到你的主个人账户(Personal),因为如果你正在某个受限空间的会话中,直接跑脚本可能会导致 Token 失效(401 错误)。

第二步:打开控制台 在网页上按 F12(或者右键点击“检查”),切换到 Console(控制台)标签页。

第三步:粘贴并运行 将下面的完整代码复制,粘贴到控制台的输入框里,按回车键执行。

(async function runrunrun() {
    try {
        // 1. 获取基础会话信息
        const session = await fetch('/api/auth/session').then((r) => r.json());
        const accessToken = session?.accessToken;
        const user_id = session?.user?.id;
        if (!accessToken || !user_id) {
            console.log('获取 Token 或用户 ID 失败,请确认登录状态。');
            return;
        }

        // 2. 获取账号列表
        console.log('正在获取账号列表...');
        const checkResponse = await fetch(
            'https://chatgpt.com/backend-api/accounts/check/v4-2023-04-27?timezone_offset_min=-480',
            {
                headers: {
                    Authorization: `Bearer ${accessToken}`,
                },
            }
        );
        if (!checkResponse.ok) {
            console.log(`无法获取账号列表,HTTP 状态码: ${checkResponse.status}`);
            return;
        }
        const checkData = await checkResponse.json();
        const accountOrdering = checkData?.account_ordering || [];
        if (accountOrdering.length === 0) {
            console.log('未检测到可处理的账号列表。');
            return;
        }
        console.log(`获取成功,共检测到 ${accountOrdering.length} 个账号。开始处理...`);

        const results = { success: [], failed: [] };

        // 3. 循环发送 DELETE 请求
        for (const account_id of accountOrdering) {
            try {
                const response = await fetch(
                    `https://chatgpt.com/backend-api/accounts/${account_id}/users/${user_id}`,
                    {
                        method: 'DELETE',
                        headers: {
                            Authorization: `Bearer ${accessToken}`,
                            'Content-Type': 'application/json',
                        },
                    }
                );
                let responseText = '';
                try { responseText = await response.text(); } catch (e) {}

                let isSuccess = false;
                if (response.status === 200) {
                    try {
                        const data = JSON.parse(responseText);
                        if (data?.success) {
                            isSuccess = true;
                            results.success.push(account_id);
                            console.log(`[成功] 账号 ${account_id} 已成功下车`);
                        }
                    } catch (e) {}
                }

                // 4. 智能重试机制:处理 Workspace Token 限制
                if (!isSuccess) {
                    const isWorkspaceError = responseText.includes('Must use workspace account for this operation');
                    if (isWorkspaceError) {
                        console.log(`[检测到工作区限制] 账号 ${account_id} 需要切换空间 Token,正在换取...`);
                        try {
                            // 换取该空间的专有 token
                            const wsSessionRes = await fetch(
                                `https://chatgpt.com/api/auth/session?exchange_workspace_token=true&workspace_id=${account_id}&reason=setCurrentAccount`
                            );
                            if (wsSessionRes.ok) {
                                const wsSession = await wsSessionRes.json();
                                const wsAccessToken = wsSession?.accessToken;
                                if (wsAccessToken) {
                                    console.log(`[成功] 已获取空间 ${account_id} 的临时 Token,开始重新尝试删除...`);
                                    // 使用空间 token 重试删除请求
                                    const retryResponse = await fetch(
                                        `https://chatgpt.com/backend-api/accounts/${account_id}/users/${user_id}`,
                                        {
                                            method: 'DELETE',
                                            headers: {
                                                Authorization: `Bearer ${wsAccessToken}`,
                                                'Content-Type': 'application/json',
                                            },
                                        }
                                    );
                                    let retryText = '';
                                    try { retryText = await retryResponse.text(); } catch (e) {}
                                    if (retryResponse.status === 200) {
                                        try {
                                            const retryData = JSON.parse(retryText);
                                            if (retryData?.success) {
                                                isSuccess = true;
                                                results.success.push(account_id);
                                                console.log(`[成功] 切换临时 Token 后,账号 ${account_id} 下车成功`);
                                            }
                                        } catch (e) {}
                                    }
                                    if (!isSuccess) {
                                        results.failed.push({ account_id, status: retryResponse.status, reason: '切换 Token 后重试失败', detail: retryText });
                                        console.log(`[失败] 切换临时 Token 重试后依然失败,状态码: ${retryResponse.status}`);
                                    }
                                } else {
                                    results.failed.push({ account_id, reason: '空间 session 返回数据中未包含 accessToken' });
                                    console.log(`[失败] 无法从空间 ${account_id} 的 session 响应中提取到 Token`);
                                }
                            } else {
                                results.failed.push({ account_id, status: wsSessionRes.status, reason: '请求空间 Token 接口失败' });
                                console.log(`[失败] 请求空间临时 Token 失败,状态码: ${wsSessionRes.status}`);
                            }
                        } catch (wsErr) {
                            results.failed.push({ account_id, reason: '获取空间 Token 过程中发生错误', error: wsErr.message });
                            console.log(`[错误] 获取账号 ${account_id} 空间 Token 时发生异常: ${wsErr.message}`);
                        }
                    } else {
                        // 非工作区错误,直接记录并跳过
                        results.failed.push({ account_id, status: response.status, detail: responseText });
                        console.log(`[跳过] 账号 ${account_id} 失败(非工作区错误),状态码: ${response.status}`);
                    }
                }
            } catch (err) {
                results.failed.push({ account_id, error: err.message || String(err) });
                console.log(`[错误] 处理账号 ${account_id} 时发生异常,已跳过并继续: ${err.message || err}`);
            }
        }

        // 5. 输出汇总结果
        console.log('\n========= 处理结果汇总 =========');
        console.log(`成功下车数: ${results.success.length}`);
        if (results.success.length > 0) console.log('成功账号 ID 列表:', results.success);
        console.log(`失败/跳过数: ${results.failed.length}`);
        if (results.failed.length > 0) console.log('失败详情列表:', results.failed);
        console.log('=================================');
    } catch (globalErr) {
        console.error('脚本初始化阶段发生未捕获的错误:', globalErr);
    }
})();

💡 技术细节解读:为什么需要“换 Token”?

你可能在代码里注意到了一个很关键的处理逻辑:Workspace Exchange Token

当你用个人账号的主 Token 去请求删除某些企业空间时,官方 API 会报错:“Must use workspace account for this operation”(必须使用工作区账号进行此操作)。这是因为部分安全策略要求“谁的人谁来踢”。

这个脚本的高明之处就在于它不仅会报错,还能自动解决。当检测到这个特定错误时,它会利用 exchange_workspace_token 参数,向服务端申请一个该空间的临时管理员 Token,拿到新 Token 后再立马发一次 DELETE 请求。这种“曲线救国”的方法大大提高了脚本的成功率。

❓ 常见问题与限制

Q: 退出后能不能重新加回来以此“重置”额度? A: 不太行。根据技术大牛的推测,Codex 后端的计费逻辑绑定的是 workspace_id + user_id。即便你退出了再重新加入,因为你的 user_id 没变,系统依然记得你在该空间的消耗记录,想通过此法薅羊毛大概率是行不通的。

Q: 那些早就过期或者被封禁(402/停用)的空间能退吗? A: 很遗憾,不能。如果你的空间因为违规被官方风控封禁,或者已经被彻底停用,API 接口会拒绝执行操作。这类空间只能挂在列表里当摆设了,不过好消息是它们通常也不会计费或影响主号正常使用。

Q: 脚本会把我的个人主账号也删了吗? A: 不会。脚本是针对“关联/加入的空间”进行操作,个人主账户是你的根 ID,无法对自己执行退出操作。但为了稳妥起见,操作前记得备份数据,虽然目前看这个脚本非常安全。

总结

这大概是目前清理 ChatGPT 账号环境最高效的方法了。如果你也深受那些乱七八糟的教育空间骚扰,不妨赶紧试试这个脚本,一键清爽,从此告别“账号精神污染”。

标签: none

AI Skills Smart Station on Nick Launches

评论已关闭