一键提取 Google AI Studio 完整认证信息,下载或直接上传到 studiogw / AIStudioToAPI 服务端
// ==UserScript==
// @name AI Studio Auth Extractor
// @author xjetry
// @namespace https://github.com/iBUHub/AIStudioToAPI
// @version 3.0.0
// @description 一键提取 Google AI Studio 完整认证信息,下载或直接上传到 studiogw / AIStudioToAPI 服务端
// @match https://aistudio.google.com/*
// @grant GM_cookie
// @grant GM_xmlhttpRequest
// @grant GM_setValue
// @grant GM_getValue
// @grant GM_registerMenuCommand
// @connect *
// @run-at document-idle
// @license MIT
// @noframes
// ==/UserScript==
/**
* 使用前请在 Tampermonkey 中完成以下设置(仅需一次):
*
* 1. 点击 Tampermonkey 图标 → 管理面板 → 设置
* 2. 将「配置模式」切换为「高级」
* 3. 找到「安全」区域 → 将「允许脚本访问 Cookie」设置为「All」
* 4. 保存设置并刷新 AI Studio 页面
*
* 原因:核心认证 Cookie(如 __Secure-1PSID)标记了 httpOnly,
* 浏览器禁止 JS 直接读取。上述设置授权 Tampermonkey 的
* GM_cookie API 读取这些 httpOnly Cookie。
*
* v3 变更:
* - 补齐认证 cookie 集(含 3P 变体 / APISID / NID / SIDCC 三件套 / accounts 登录态),
* 解决旧版只抓部分 cookie 导致会话很快过期的问题。
* - 支持直接上传到服务端的 POST /admin/auth(菜单里配置地址 + API Key),
* 免去手动下载再上传。未配置时回退为下载 {email}.json。
*/
(function () {
"use strict";
// 抓取所有"与 Google 登录态相关"的 cookie,而不是固定子集——缺 cookie 是会话
// 很快失效的根因。保留:任意 *.google.com(含 accounts 登录态、3P 变体)+ Canvas
// 预览域 *.run.app 的 token。排除 youtube 等无关服务。
function isRelevantDomain(domain) {
const d = (domain || "").toLowerCase();
if (d.includes("youtube")) return false;
return d.includes("google.com") || d.includes(".run.app");
}
function mapSameSite(v) {
if (v === "lax") return "Lax";
if (v === "strict") return "Strict";
return "None";
}
function normalizeCookie(c) {
return {
name: c.name,
value: c.value,
domain: c.domain,
path: c.path || "/",
expires: c.expirationDate != null ? c.expirationDate : -1,
httpOnly: !!c.httpOnly,
secure: !!c.secure,
sameSite: mapSameSite(c.sameSite),
};
}
function extractEmail() {
const re = /[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}/;
for (const el of document.querySelectorAll('script[type="application/json"]')) {
const m = (el.textContent || "").match(re);
if (m) return m[0];
}
return null;
}
function listCookies(filter) {
return new Promise(resolve => {
if (typeof GM_cookie === "undefined" || !GM_cookie || !GM_cookie.list) {
return resolve([]);
}
GM_cookie.list(filter || {}, (cookies, error) => {
resolve(error ? [] : cookies || []);
});
});
}
// accounts.google.com 的登录 cookie 通常是 host-only,在 aistudio 页面用空 filter
// 可能拿不到,所以额外按域显式查询一次,再按 (name|domain|path) 去重合并。
async function gatherCookies() {
const groups = await Promise.all([
listCookies({}),
listCookies({ domain: "google.com" }),
listCookies({ domain: "accounts.google.com" }),
]);
const seen = new Set();
const merged = [];
for (const group of groups) {
for (const c of group) {
if (!isRelevantDomain(c.domain)) continue;
const key = `${c.name}|${c.domain}|${c.path || "/"}`;
if (seen.has(key)) continue;
seen.add(key);
merged.push(normalizeCookie(c));
}
}
return merged;
}
function downloadJSON(data, filename) {
const a = document.createElement("a");
a.href = URL.createObjectURL(new Blob([JSON.stringify(data)], { type: "application/json" }));
a.download = filename;
document.body.appendChild(a);
a.click();
a.remove();
URL.revokeObjectURL(a.href);
}
function uploadToServer(state, baseUrl, apiKey) {
const url = baseUrl.replace(/\/+$/, "") + "/admin/auth";
return new Promise((resolve, reject) => {
GM_xmlhttpRequest({
method: "POST",
url,
headers: { Authorization: "Bearer " + apiKey, "Content-Type": "application/json" },
data: JSON.stringify(state),
onload: res => {
if (res.status >= 200 && res.status < 300) {
try {
resolve(JSON.parse(res.responseText));
} catch {
resolve({});
}
} else {
reject(new Error(`HTTP ${res.status}: ${(res.responseText || "").slice(0, 200)}`));
}
},
onerror: () => reject(new Error("网络错误(检查服务器地址 / @connect / CORS)")),
ontimeout: () => reject(new Error("请求超时")),
});
});
}
async function extract() {
const cookies = await gatherCookies();
if (!cookies.some(c => c.name === "SAPISID")) {
throw new Error("缺少 SAPISID,请确保已登录 AI Studio(并已开启 Cookie 访问=All)。");
}
if (!cookies.some(c => c.name === "__Secure-1PSID")) {
throw new Error("缺少 __Secure-1PSID(httpOnly)。请在 Tampermonkey 设置里把 Cookie 访问设为 All。");
}
let email = extractEmail();
if (!email) {
email = prompt("未检测到邮箱,请输入账号标签:");
if (!email) return null;
}
return {
email,
state: {
accountName: email,
cookies,
origins: [{ origin: "https://aistudio.google.com", localStorage: [] }],
},
count: cookies.length,
};
}
// ---- 服务端配置(持久化)----
function getServerConfig() {
return {
url: GM_getValue("studiogw_url", ""),
key: GM_getValue("studiogw_key", ""),
};
}
function configureServer() {
const cur = getServerConfig();
const url = prompt("studiogw 服务端地址(如 http://127.0.0.1:7860)。留空则清除并改用下载模式:", cur.url);
if (url === null) return;
if (!url.trim()) {
GM_setValue("studiogw_url", "");
GM_setValue("studiogw_key", "");
alert("已清除服务端配置,将改用下载模式。");
return;
}
const key = prompt("API Key(对应服务端 API_KEYS 之一):", cur.key);
if (key === null) return;
GM_setValue("studiogw_url", url.trim());
GM_setValue("studiogw_key", key.trim());
alert("已保存。点击「Extract Auth」将直接上传到服务端。");
}
if (typeof GM_registerMenuCommand !== "undefined") {
GM_registerMenuCommand("⚙️ 配置 studiogw 上传地址 / API Key", configureServer);
}
// ---- UI ----
const btn = document.createElement("button");
btn.textContent = "\u{1F4E6} Extract Auth";
Object.assign(btn.style, {
position: "fixed",
bottom: "20px",
right: "20px",
zIndex: "99999",
padding: "10px 18px",
background: "#1a73e8",
color: "#fff",
border: "none",
borderRadius: "24px",
fontSize: "14px",
fontWeight: "500",
cursor: "pointer",
boxShadow: "0 2px 8px rgba(0,0,0,0.25)",
fontFamily: "Google Sans, Roboto, Arial, sans-serif",
transition: "all 0.2s",
});
btn.onmouseenter = () => ((btn.style.background = "#1557b0"), (btn.style.transform = "translateY(-1px)"));
btn.onmouseleave = () => ((btn.style.background = "#1a73e8"), (btn.style.transform = ""));
btn.onclick = async () => {
if (btn.disabled) return;
btn.disabled = true;
btn.textContent = "⏳ 提取中...";
try {
const r = await extract();
if (!r) {
btn.textContent = "❌ 取消";
} else {
const server = getServerConfig();
if (server.url && server.key) {
btn.textContent = "⬆️ 上传中...";
const resp = await uploadToServer(r.state, server.url, server.key);
btn.textContent = `✅ 已上传 (${r.count})`;
console.log(`[Auth Extractor] uploaded ${r.email}: ${r.count} cookies →`, resp);
} else {
downloadJSON(r.state, `${r.email}.json`);
btn.textContent = `✅ ${r.count} cookies`;
console.log(`[Auth Extractor] downloaded ${r.email}: ${r.count} cookies (配置服务端可直传)`);
}
}
} catch (e) {
btn.textContent = "❌ 失败";
alert(e.message);
}
setTimeout(() => {
btn.textContent = "\u{1F4E6} Extract Auth";
btn.disabled = false;
}, 2500);
};
document.body.appendChild(btn);
})();