写了个油猴脚本强开(AI搓的,这总不能截图发送吧)
// ==UserScript==
// @name ChatGPT 外部域名邀请设置
// @namespace https://github.com/openai-tools/userscripts
// @version 1.1.0
// @description 通过悬浮面板开关 allow_external_domain_invites 设置
// @author local
// @match https://chatgpt.com/admin/identity*
// @grant GM_addStyle
// @grant GM_getValue
// @grant GM_setValue
// @noframes
// @run-at document-idle
// ==/UserScript==
(function () {
"use strict";
if (
location.pathname !== "/admin/identity" ||
!document.body ||
document.getElementById("aed-invites-root")
) {
return;
}
const STORAGE_KEYS = {
enable: "allowExternalDomainInvites.enable",
};
const initialEnable = GM_getValue(STORAGE_KEYS.enable, true);
GM_addStyle(`
#aed-invites-root {
position: fixed;
right: 20px;
bottom: 20px;
z-index: 2147483647;
color: #1f2937;
font: 14px/1.4 -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
}
#aed-invites-root * { box-sizing: border-box; }
#aed-invites-toggle {
border: 0;
border-radius: 999px;
padding: 10px 14px;
color: #fff;
background: #10a37f;
box-shadow: 0 4px 18px rgb(0 0 0 / 22%);
cursor: pointer;
font: inherit;
}
#aed-invites-toggle:hover { background: #0d8e6f; }
#aed-invites-panel {
display: none;
width: min(360px, calc(100vw - 32px));
margin-bottom: 10px;
padding: 16px;
border: 1px solid rgb(0 0 0 / 12%);
border-radius: 12px;
background: #fff;
box-shadow: 0 8px 30px rgb(0 0 0 / 20%);
}
#aed-invites-panel.aed-open { display: block; }
#aed-invites-title {
display: flex;
align-items: center;
justify-content: space-between;
gap: 8px;
margin-bottom: 12px;
font-weight: 600;
}
#aed-invites-close {
border: 0;
padding: 0 4px;
color: #6b7280;
background: transparent;
cursor: pointer;
font-size: 20px;
line-height: 1;
}
#aed-invites-panel label { display: block; margin: 10px 0 6px; }
#aed-invites-workspace {
display: block;
min-height: 36px;
padding: 8px 10px;
border: 1px solid #d1d5db;
border-radius: 6px;
overflow-wrap: anywhere;
color: inherit;
background: #f9fafb;
font: 13px/1.4 ui-monospace, SFMono-Regular, Menlo, monospace;
}
#aed-invites-refresh {
margin-top: 7px;
padding: 0;
border: 0;
color: #0d8e6f;
background: transparent;
cursor: pointer;
font: inherit;
font-size: 12px;
}
#aed-invites-refresh:disabled { cursor: wait; opacity: .65; }
#aed-invites-enable-row {
display: flex;
align-items: center;
gap: 8px;
margin: 12px 0;
cursor: pointer;
}
#aed-invites-enable { width: 16px; height: 16px; margin: 0; }
#aed-invites-submit {
width: 100%;
padding: 9px 12px;
border: 0;
border-radius: 6px;
color: #fff;
background: #10a37f;
cursor: pointer;
font: inherit;
}
#aed-invites-submit:disabled { cursor: wait; opacity: .65; }
#aed-invites-status {
min-height: 20px;
margin-top: 10px;
white-space: pre-wrap;
overflow-wrap: anywhere;
font-size: 12px;
}
#aed-invites-status.aed-success { color: #087f5b; }
#aed-invites-status.aed-error { color: #c92a2a; }
#aed-invites-hint { margin-top: 8px; color: #6b7280; font-size: 12px; }
@media (prefers-color-scheme: dark) {
#aed-invites-panel { border-color: rgb(255 255 255 / 15%); color: #f3f4f6; background: #202123; }
#aed-invites-workspace { border-color: #4b5563; color: inherit; background: #343541; }
#aed-invites-close { color: #9ca3af; }
}
`);
const root = document.createElement("div");
root.id = "aed-invites-root";
root.innerHTML = `
<section id="aed-invites-panel" aria-label="外部域名邀请设置">
<div id="aed-invites-title">
<span>外部域名邀请设置</span>
<button id="aed-invites-close" type="button" title="关闭">×</button>
</div>
<label for="aed-invites-workspace">Workspace ID(自动获取)</label>
<div id="aed-invites-workspace" role="textbox" aria-readonly="true">正在获取…</div>
<button id="aed-invites-refresh" type="button">重新获取 Workspace ID</button>
<label id="aed-invites-enable-row" for="aed-invites-enable">
<input id="aed-invites-enable" type="checkbox" ${initialEnable ? "checked" : ""}>
<span>允许外部域名邀请</span>
</label>
<button id="aed-invites-submit" type="button" disabled>保存设置</button>
<div id="aed-invites-status" role="status" aria-live="polite"></div>
<div id="aed-invites-hint">请求使用当前页面的登录会话发送。</div>
</section>
<button id="aed-invites-toggle" type="button">外部邀请</button>
`;
document.body.appendChild(root);
const panel = root.querySelector("#aed-invites-panel");
const toggle = root.querySelector("#aed-invites-toggle");
const close = root.querySelector("#aed-invites-close");
const workspaceValue = root.querySelector("#aed-invites-workspace");
const refresh = root.querySelector("#aed-invites-refresh");
const enableInput = root.querySelector("#aed-invites-enable");
const submit = root.querySelector("#aed-invites-submit");
const status = root.querySelector("#aed-invites-status");
let workspaceId = "";
toggle.addEventListener("click", () => {
panel.classList.toggle("aed-open");
if (panel.classList.contains("aed-open")) refresh.focus();
});
close.addEventListener("click", () => panel.classList.remove("aed-open"));
refresh.addEventListener("click", refreshWorkspaceId);
submit.addEventListener("click", async () => {
const enable = enableInput.checked;
if (!workspaceId) {
showStatus("尚未获取到 Workspace ID,请点击“重新获取”。", "error");
return;
}
GM_setValue(STORAGE_KEYS.enable, enable);
submit.disabled = true;
showStatus("正在提交…");
try {
await updateSetting(workspaceId, enable);
showStatus("保存成功,正在刷新页面…", "success");
window.location.reload();
} catch (error) {
showStatus(`设置失败:${error.message}`, "error");
} finally {
submit.disabled = false;
}
});
refreshWorkspaceId();
function showStatus(message, type) {
status.textContent = message;
status.className = type ? `aed-${type}` : "";
}
async function refreshWorkspaceId() {
refresh.disabled = true;
submit.disabled = true;
workspaceValue.textContent = "正在获取…";
showStatus("正在自动获取 Workspace ID…");
try {
const detected = await detectWorkspaceId();
workspaceId = detected.id;
workspaceValue.textContent = workspaceId;
submit.disabled = false;
showStatus(`已获取 Workspace ID(${detected.source})。`, "success");
} catch (error) {
workspaceId = "";
workspaceValue.textContent = "未找到";
showStatus(`自动获取失败:${error.message}`, "error");
} finally {
refresh.disabled = false;
}
}
async function updateSetting(workspaceId, enable) {
const authorization = await getAuthorization();
const encodedWorkspaceId = encodeURIComponent(workspaceId);
const apiPath =
`/backend-api/accounts/${encodedWorkspaceId}` +
"/settings/allow_external_domain_invites";
const response = await fetch(apiPath, {
method: "POST",
credentials: "include",
headers: {
Accept: "application/json",
"Content-Type": "application/json",
Authorization: authorization,
"chatgpt-account-id": workspaceId,
"x-openai-target-path": apiPath,
"x-openai-target-route":
"/backend-api/accounts/{account_id}/settings/allow_external_domain_invites",
},
body: JSON.stringify({ value: enable }),
});
const result = await response.text();
if (!response.ok) throw new Error(`请求失败 (${response.status})${result ? `:${result}` : ""}`);
}
async function detectWorkspaceId() {
const pageId = findWorkspaceId(readPageData());
if (pageId) return { id: pageId, source: "当前页面" };
const { session, authorization } = await getSession();
const sessionId = findWorkspaceId(session);
if (sessionId) return { id: sessionId, source: "登录 session" };
const endpoints = ["/backend-api/accounts", "/backend-api/accounts/check"];
for (const endpoint of endpoints) {
let response;
try {
response = await fetch(endpoint, {
method: "GET",
credentials: "include",
headers: { Accept: "application/json", Authorization: authorization },
});
} catch {
continue;
}
if (!response.ok) continue;
let data;
try {
data = await response.json();
} catch {
continue;
}
const id = findWorkspaceId(data);
if (id) return { id, source: endpoint };
}
throw new Error("页面数据和账号接口都没有返回可用的 Workspace ID");
}
async function getSession() {
const sessionResponse = await fetch("/api/auth/session", {
method: "GET",
credentials: "include",
cache: "no-store",
});
if (!sessionResponse.ok) {
throw new Error(`获取 session 失败 (${sessionResponse.status})`);
}
const session = await sessionResponse.json();
const accessToken = session?.accessToken;
if (!accessToken) {
throw new Error("session 中没有 accessToken,请确认当前页面已登录");
}
const authorization = /^Bearer\s/i.test(accessToken)
? accessToken
: `Bearer ${accessToken}`;
return { session, authorization };
}
async function getAuthorization() {
return (await getSession()).authorization;
}
function readPageData() {
const data = [];
if (window.__NEXT_DATA__) data.push(window.__NEXT_DATA__);
if (window.__INITIAL_STATE__) data.push(window.__INITIAL_STATE__);
for (const script of document.scripts) {
if (script.id !== "__NEXT_DATA__" && script.type !== "application/json") continue;
const text = script.textContent?.trim();
if (!text) continue;
try {
data.push(JSON.parse(text));
} catch {
// 某些页面脚本不是 JSON,跳过即可。
}
}
const text = document.body?.innerText || "";
const labeledId = text.match(
/(?:workspace|account)\s*id\s*[::]?\s*([A-Za-z0-9][A-Za-z0-9_-]{5,})/i,
);
if (labeledId) data.push({ workspace_id: labeledId[1] });
return data;
}
function findWorkspaceId(value) {
if (!value || typeof value !== "object") return "";
const mapId = findIdInAccountMap(value, "", new Set());
if (mapId) return mapId;
const currentObject = findCurrentObject(value, new Set());
const currentId = currentObject && findIdByKey(currentObject, new Set());
if (currentId) return currentId;
const directId = findIdByKey(value, new Set());
if (directId) return directId;
return collectIds(value, [], new Set())[0] || "";
}
function findIdInAccountMap(value, parentKey, seen) {
if (!value || typeof value !== "object" || seen.has(value)) return "";
seen.add(value);
const isAccountMap = /^(?:accounts?|workspaces?)$/i.test(parentKey);
const isAccountObject = /^(?:account|workspace)$/i.test(parentKey);
if (isAccountObject && typeof value.id === "string" && value.id.trim()) {
return value.id.trim();
}
if (isAccountMap) {
if (Array.isArray(value)) {
for (const child of value) {
if (child && typeof child.id === "string" && child.id.trim()) {
return child.id.trim();
}
}
} else {
for (const key of Object.keys(value)) {
if (isLikelyWorkspaceId(key)) return key;
}
}
}
for (const [key, child] of Object.entries(value)) {
const id = findIdInAccountMap(child, key, seen);
if (id) return id;
}
return "";
}
function findIdByKey(value, seen) {
if (!value || typeof value !== "object" || seen.has(value)) return "";
seen.add(value);
for (const [key, child] of Object.entries(value)) {
if (isWorkspaceIdKey(key) && typeof child === "string" && child.trim()) {
return child.trim();
}
}
for (const child of Object.values(value)) {
const id = findIdByKey(child, seen);
if (id) return id;
}
return "";
}
function collectIds(value, result, seen) {
if (!value || typeof value !== "object" || seen.has(value)) return result;
seen.add(value);
for (const [key, child] of Object.entries(value)) {
if (isWorkspaceIdKey(key) && typeof child === "string" && child.trim()) {
result.push(child.trim());
}
collectIds(child, result, seen);
}
return result;
}
function findCurrentObject(value, seen) {
if (!value || typeof value !== "object" || seen.has(value)) return null;
seen.add(value);
if (
value.is_current === true ||
value.isCurrent === true ||
value.current === true ||
value.selected === true ||
value.active === true
) {
return value;
}
for (const child of Object.values(value)) {
const current = findCurrentObject(child, seen);
if (current) return current;
}
return null;
}
function isWorkspaceIdKey(key) {
return /^(?:current[_-]?)?(?:workspace|account)[_-]?id$/i.test(key);
}
function isLikelyWorkspaceId(value) {
return (
/^(?:org|workspace|account|team)[_-][A-Za-z0-9_-]{4,}$/i.test(value) ||
/^[0-9a-f]{8}-[0-9a-f-]{27,}$/i.test(value)
);
}
})();