Loading source
Pulling the file list, source metadata, and syntax-aware rendering for this listing.
Source from repo
Generate text and images via the reverse-engineered Gemini Web API with multi-turn conversation support.
Files
Skill
Size
Entrypoint
Format
Open file
Syntax-highlighted preview of this file as included in the skill package.
scripts/gemini-webapi/utils/get-access-token.ts
1import fs from 'node:fs';2import path from 'node:path';3import process from 'node:process';45import { Endpoint, Headers } from '../constants.js';6import { AuthError } from '../exceptions.js';7import { cookie_header, extract_set_cookie_value, fetch_with_timeout } from './http.js';8import { logger } from './logger.js';9import { read_cookie_file, write_cookie_file } from './cookie-file.js';10import { resolveGeminiWebDataDir, resolveGeminiWebCookiePath } from './paths.js';11import { load_browser_cookies } from './load-browser-cookies.js';1213async function send_request(cookies: Record<string, string>, verbose: boolean): Promise<[string, Record<string, string>]> {14const res = await fetch_with_timeout(Endpoint.INIT, {15method: 'GET',16headers: { ...Headers.GEMINI, Cookie: cookie_header(cookies) },17redirect: 'follow',18timeout_ms: 30_000,19});2021if (!res.ok) throw new Error(`Init failed: ${res.status} ${res.statusText}`);22const text = await res.text();23const m = text.match(/\"SNlM0e\":\"(.*?)\"/);24if (!m) throw new Error('Missing SNlM0e in response');25if (verbose) logger.debug('Init succeeded. Initializing client...');26return [m[1]!, cookies];27}2829function merge_cookie_maps(...maps: Array<Record<string, string> | null | undefined>): Record<string, string> {30const out: Record<string, string> = {};31for (const m of maps) {32if (!m) continue;33for (const [k, v] of Object.entries(m)) {34if (typeof v === 'string' && v.length > 0) out[k] = v;35}36}37return out;38}3940function read_cached_1psidts_file(dir: string, sid: string): string | null {41try {42const p = path.join(dir, `.cached_1psidts_${sid}.txt`);43if (!fs.existsSync(p) || !fs.statSync(p).isFile()) return null;44const v = fs.readFileSync(p, 'utf8').trim();45return v || null;46} catch {47return null;48}49}5051function list_cached_1psidts(dir: string): Array<{ sid: string; sidts: string }> {52const out: Array<{ sid: string; sidts: string }> = [];53try {54if (!fs.existsSync(dir) || !fs.statSync(dir).isDirectory()) return out;55for (const f of fs.readdirSync(dir)) {56if (!f.startsWith('.cached_1psidts_') || !f.endsWith('.txt')) continue;57const sid = f.slice('.cached_1psidts_'.length, -'.txt'.length);58if (!sid) continue;59const sidts = read_cached_1psidts_file(dir, sid);60if (sidts) out.push({ sid, sidts });61}62} catch {}63return out;64}6566async function fetch_google_extra_cookies(proxy: string | null, verbose: boolean): Promise<Record<string, string>> {67void proxy;68try {69const res = await fetch_with_timeout(Endpoint.GOOGLE, { timeout_ms: 15_000 });70const setCookie = res.headers.get('set-cookie');71const nid = extract_set_cookie_value(setCookie, 'NID');72if (nid) return { NID: nid };73} catch (e) {74if (verbose) logger.debug(`Skipping google.com preflight: ${e instanceof Error ? e.message : String(e)}`);75}76return {};77}7879export async function get_access_token(80base_cookies: Record<string, string>,81proxy: string | null = null,82verbose: boolean = false,83): Promise<[string, Record<string, string>]> {84const extra = await fetch_google_extra_cookies(proxy, verbose);8586const cacheDir = resolveGeminiWebDataDir();87const candidates: Record<string, string>[] = [];8889const cookieFilePath = resolveGeminiWebCookiePath();90const cachedFile = await read_cookie_file(cookieFilePath);91const forceLogin = !!(process.env.GEMINI_WEB_LOGIN?.trim() || process.env.GEMINI_WEB_FORCE_LOGIN?.trim());92const shouldUseChromeFirst = forceLogin || (!cachedFile && !base_cookies['__Secure-1PSID'] && !base_cookies['__Secure-1PSIDTS']);9394if (shouldUseChromeFirst) {95try {96const browser = await load_browser_cookies('google.com', verbose);97for (const cookies of Object.values(browser)) {98candidates.push(merge_cookie_maps(extra, cookies));99}100} catch (e) {101if (verbose) logger.warning(`Failed to load cookies via Chrome CDP: ${e instanceof Error ? e.message : String(e)}`);102}103}104105if (base_cookies['__Secure-1PSID'] && base_cookies['__Secure-1PSIDTS']) {106candidates.push(merge_cookie_maps(extra, base_cookies));107} else if (verbose) {108logger.debug('Skipping loading base cookies. Either __Secure-1PSID or __Secure-1PSIDTS is not provided.');109}110111if (cachedFile) {112candidates.push(merge_cookie_maps(extra, cachedFile));113}114115if (base_cookies['__Secure-1PSID'] && !base_cookies['__Secure-1PSIDTS']) {116const sid = base_cookies['__Secure-1PSID'];117const sidts = read_cached_1psidts_file(cacheDir, sid);118if (sidts) {119candidates.push(merge_cookie_maps(extra, base_cookies, { '__Secure-1PSIDTS': sidts }));120} else if (verbose) {121logger.debug('Skipping loading cached cookies. Cache file not found or empty.');122}123} else if (!base_cookies['__Secure-1PSID']) {124const caches = list_cached_1psidts(cacheDir);125for (const c of caches) {126candidates.push(merge_cookie_maps(extra, { '__Secure-1PSID': c.sid, '__Secure-1PSIDTS': c.sidts }));127}128if (caches.length === 0 && verbose) {129logger.debug('Skipping loading cached cookies. Cookies will be cached after successful initialization.');130}131}132133const unique: Record<string, string>[] = [];134const seen = new Set<string>();135for (const c of candidates) {136const key = `${c['__Secure-1PSID'] ?? ''}:${c['__Secure-1PSIDTS'] ?? ''}:${c.NID ?? ''}`;137if (seen.has(key)) continue;138seen.add(key);139unique.push(c);140}141142const try_candidates = async (): Promise<[string, Record<string, string>]> => {143if (unique.length === 0) throw new Error('no candidates');144const attempts = unique.map(async (c, i) => {145try {146if (verbose) logger.debug(`Init attempt (${i + 1}/${unique.length})...`);147return await send_request(c, verbose);148} catch (e) {149if (verbose) logger.debug(`Init attempt (${i + 1}/${unique.length}) failed: ${e instanceof Error ? e.message : String(e)}`);150throw e;151}152});153return (await Promise.any(attempts)) as [string, Record<string, string>];154};155156try {157const [token, cookies] = await try_candidates();158await write_cookie_file(cookies, resolveGeminiWebCookiePath(), 'init').catch(() => {});159return [token, cookies];160} catch {161if (verbose) logger.debug('Cookie attempts failed. Falling back to Chrome CDP cookie load...');162}163164const browser = await load_browser_cookies('google.com', verbose);165let valid = 0;166for (const cookies of Object.values(browser)) {167if (cookies['__Secure-1PSID']) valid++;168if (base_cookies['__Secure-1PSID'] && cookies['__Secure-1PSID'] && cookies['__Secure-1PSID'] !== base_cookies['__Secure-1PSID']) {169if (verbose) logger.debug('Skipping loaded browser cookies: __Secure-1PSID does not match the one provided.');170continue;171}172unique.push(merge_cookie_maps(extra, cookies));173}174175if (valid === 0) {176throw new AuthError(177'No valid cookies available for initialization. Please pass __Secure-1PSID and __Secure-1PSIDTS manually.',178);179}180181try {182const [token, cookies] = await try_candidates();183await write_cookie_file(cookies, resolveGeminiWebCookiePath(), 'init').catch(() => {});184return [token, cookies];185} catch {186throw new AuthError(187`Failed to initialize client. SECURE_1PSIDTS could get expired frequently, please make sure cookie values are up to date. (Failed initialization attempts: ${unique.length})`,188);189}190}191192export const getAccessToken = get_access_token;193