1实现原理 · 为什么它能做到
实现基础是一份手写的 Gemini Web 客户端(TypeScript 移植自开源 gemini_webapi),把网页端私有接口当 SDK 用——这就是它能在没有官方 API Key 的情况下出文本与图片的原因。
| `scripts/gemini-webapi/*` | TypeScript port of `gemini_webapi` (GeminiClient, types, utils) |
端点集中在 constants.ts:INIT 取页面令牌、GENERATE 走 StreamGenerate 私有接口、ROTATE_COOKIES 轮换会话 cookie、UPLOAD 传参考图、BATCH_EXEC 走 batchexecute。
'https://gemini.google.com/_/BardChatUi/data/assistant.lamda.BardFrontendService/StreamGenerate',
认证第一步是抓网页里的 SNlM0e 访问令牌:GET gemini.google.com/app,用正则从 HTML 中提取,随后所有生成请求都带上它。
const m = text.match(/\"SNlM0e\":\"(.*?)\"/);
凭据来源是浏览器会话 cookie(__Secure-1PSID / __Secure-1PSIDTS):先读本地 cookie 文件,失败则通过 CDP 连浏览器读 cookie,且默认会复用已运行的本地 Chrome 调试会话。
const existingCookies = hasExplicitProfile ? null : await fetch_cookies_from_existing_chrome(30_000, verbose);
Cookie 与令牌被持久化:cookies.json 存 __Secure-1PSID/1PSIDTS 等,且后台定时任务用 RotateCookies 刷新 1PSIDTS 并再次落盘。
this.cookies['__Secure-1PSIDTS'] = newTs;
生成请求是表单 POST:body 里带 at(访问令牌)与 f.req(嵌套 JSON 数组),响应是网页版的流式分段文本,需要自行拆包取正文段。
const body = new URLSearchParams({ at: this.access_token, 'f.req': f_req }).toString();
多轮会话靠把 Gemini 的 chat metadata 存进本地 session 文件实现:--sessionId 复用同一 metadata 继续对话,消息史也写进 JSON。
Session files stored in data directory under `sessions/<id>.json`.
逆向使用被显式要求“先同意”:首次使用要写 consent.json(accepted + disclaimerVersion 1.0),拒绝则直接退出。
1. Check if consent file exists with `accepted: true` and `disclaimerVersion: "1.0"`
它同时是图像后端:--image 出图(默认 generated.png)、--reference/--ref 做 vision 输入、--promptfiles 从文件读 prompt,因此被仓库定位为“自身即后端”的例外 skill。
Skills that **are themselves** image-generation backends — currently `baoyu-image-gen`, `baoyu-image-gen` (deprecated), and `baoyu-danger-gemini-web` — do NOT include a `## Image Generation Tools` section.
2核心能力
3外部依赖
| 类型 | 依赖 |
|---|---|
| network | gemini.google.com(INIT 页面 + StreamGenerate 私有接口 + batchexecute) |
| network | accounts.google.com(RotateCookies 轮换 1PSIDTS) |
| network | www.google.com(基础页/常量) |
| network | content-push.googleapis.com(参考图上传) |
| package | baoyu-chrome-cdp ^0.1.1(npm,用于发现/启动/接管 Chrome 调试会话并读 cookie) |
| cli | bun / npx(${BUN_X} 运行时) |
| cli | 本机 Chrome/Chromium/Edge 可执行文件(CDP 取 cookie;路径可由 GEMINI_WEB_CHROME_PATH 覆盖) |
4风险提醒 风险提醒:红色 · 谨慎使用
- 账号风险(逆向接口 + 网页会话冒充) — 私有端点与固定 header 指纹随时可能失效或触发风控;README 明确 'Account restrictions possible if API usage detected',代码也处理 IP_TEMPORARILY_BLOCKED。建议只用小号/非关键账号。
- 浏览器凭据被读取并落盘 — 经 CDP 从真实 Chrome 会话取 __Secure-1PSID/1PSIDTS,并写入本地 cookies.json 与 .cached_1psidts_*.txt;这些文件等价于账号登录态,需按密钥对待(勿放进同步盘/仓库)。
- 会接管已运行的浏览器调试会话 — 无显式 profile 时复用现有 Chrome 调试会话;若用户正好在调试其它站点,可能干扰本机浏览器状态。用 --profile-dir / GEMINI_WEB_CHROME_PROFILE_DIR 可隔离。
- --promptfiles 是无过滤的本地文件读取入口 — 任何被指向的文件内容都会被拼进 prompt 发往 Google;上层 skill 或用户若不慎指向密钥文件,等于主动外泄。
- consent 只是文档级约束 — 脚本自身不校验 consent.json,靠 agent 按 SKILL.md 执行;在无 skill 规范的宿主里可能被直接调用。
- 内容出境与合规 — 提示词与参考图都会离开本机(含上传到 content-push.googleapis.com),企业内网/涉密内容不适用。
5第二遍独立确认
- [ok] 逆向 API 而非官方 API(red 主因之一) — constants.ts 直指 gemini.google.com 的 StreamGenerate 私有路径;get-access-token.ts 用正则从页面抓 SNlM0e;SKILL.md/description 自述 reverse-engineered,README 有 Disclaimer 段落。
- [ok] 读取浏览器 cookie(red 主因之二) — load-browser-cookies.ts 走 CDP(引用 'Network.getCookies')读 cookie;get-access-token.ts 优先本地 cookie 文件,失败回落 CDP;client.ts 在 init/refresh 两处写 cookie 文件。
- [ok] CDP 复用已运行 Chrome 调试会话 — SKILL.md 原文与 load-browser-cookies.ts 的 fetch_cookies_from_existing_chrome(30_000, ...) 一致;另有 hasExplicitProfile 判断(设了 --profile-dir 或 GEMINI_WEB_CHROME_PROFILE_DIR 才跳过复用)。
- [ok] token 落盘 — cookie-file.ts write_cookie_file → writeFile(p, JSON.stringify(payload, null, 2));rotate-1psidts.ts 另写 .cached_1psidts_<sid>.txt。paths.ts 默认数据目录可用 GEMINI_WEB_DATA_DIR/GEMINI_WEB_COOKIE_PATH 覆盖。
- [ok] 无 TLS 降级 / 无 stealth 反检测 / 无任意代码执行 — grep rejectUnauthorized / NODE_TLS / --ignore-certificate 与 webdriver / patchright / blink-features 均零命中;无 eval/new Function/远程脚本加载;Chrome 以本机真实 profile 启动(--remote-debugging-port),未做指纹伪装。
- [ok] consent 门禁存在但为“软门禁” — SKILL.md 要求先建 consent.json 再使用;但门禁由 agent 按文档执行,脚本自身不校验该文件——属文档级约束,已在 risks 写明。
- [ok] meta 元数据 — GitHub API:MIT、25926 stars、pushed_at 2026-09-10T15:13:43Z;本地 HEAD 与 pin 1567581c26ec… 一致。
6结论
076923f8aed80181…1567581c26