gemini-reverse 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/types/image.js ADDED
@@ -0,0 +1,74 @@
1
+ 'use strict';
2
+
3
+ const axios = require('axios');
4
+ const fs = require('fs');
5
+ const path = require('path');
6
+
7
+ class Image {
8
+ constructor({ url, title = '[Image]', alt = '', proxy = null } = {}) {
9
+ this.url = url;
10
+ this.title = title;
11
+ this.alt = alt;
12
+ this.proxy = proxy;
13
+ }
14
+
15
+ toString() {
16
+ const short = this.url.length <= 20 ? this.url : this.url.slice(0, 8) + '...' + this.url.slice(-12);
17
+ return `Image(title='${this.title}', alt='${this.alt}', url='${short}')`;
18
+ }
19
+
20
+ async save({ path: savePath = 'temp', filename = null, cookies = null, verbose = false, skipInvalidFilename = false } = {}) {
21
+ let fname = filename || this.url.split('/').pop().split('?')[0];
22
+ const match = fname.match(/^(.*\.\w+)/);
23
+ if (match) {
24
+ fname = match[1];
25
+ } else {
26
+ if (verbose) console.warn(`Invalid filename: ${fname}`);
27
+ if (skipInvalidFilename) return null;
28
+ }
29
+
30
+ const proxyConfig = this.proxy ? (() => { try { const u = new URL(this.proxy); return { protocol: u.protocol.replace(':', ''), host: u.hostname, port: parseInt(u.port) }; } catch { return undefined; } })() : undefined;
31
+
32
+ const res = await axios.get(this.url, {
33
+ responseType: 'arraybuffer',
34
+ headers: cookies ? { 'Cookie': Object.entries(cookies).map(([k, v]) => `${k}=${v}`).join('; ') } : {},
35
+ maxRedirects: 5,
36
+ ...(proxyConfig ? { proxy: proxyConfig } : {}),
37
+ });
38
+
39
+ if (res.status !== 200) throw new Error(`Error downloading image: ${res.status}`);
40
+
41
+ const contentType = res.headers['content-type'] || '';
42
+ if (contentType && !contentType.includes('image')) {
43
+ console.warn(`Content type of ${fname} is not image, but ${contentType}.`);
44
+ }
45
+
46
+ fs.mkdirSync(savePath, { recursive: true });
47
+ const dest = path.join(savePath, fname);
48
+ fs.writeFileSync(dest, Buffer.from(res.data));
49
+ if (verbose) console.log(`Image saved as ${path.resolve(dest)}`);
50
+ return path.resolve(dest);
51
+ }
52
+ }
53
+
54
+ class WebImage extends Image {
55
+ constructor(opts) { super(opts); }
56
+ }
57
+
58
+ class GeneratedImage extends Image {
59
+ constructor({ cookies, ...opts } = {}) {
60
+ super(opts);
61
+ if (!cookies || Object.keys(cookies).length === 0)
62
+ throw new Error('GeneratedImage requires cookies from GeminiClient.');
63
+ this.cookies = cookies;
64
+ }
65
+
66
+ async save({ path: savePath = 'temp', filename = null, cookies = null, verbose = false, skipInvalidFilename = false, fullSize = true } = {}) {
67
+ if (fullSize) this.url += '=s2048';
68
+ const ts = new Date().toISOString().replace(/[-:T]/g, '').slice(0, 14);
69
+ const fname = filename || `${ts}_${this.url.slice(-10)}.png`;
70
+ return super.save({ path: savePath, filename: fname, cookies: cookies || this.cookies, verbose, skipInvalidFilename });
71
+ }
72
+ }
73
+
74
+ module.exports = { Image, WebImage, GeneratedImage };
package/types/index.js ADDED
@@ -0,0 +1,10 @@
1
+ 'use strict';
2
+
3
+ const { Candidate } = require('./candidate');
4
+ const { ConversationTurn } = require('./conversation');
5
+ const { Gem, GemJar } = require('./gem');
6
+ const { RPCData } = require('./grpc');
7
+ const { Image, WebImage, GeneratedImage } = require('./image');
8
+ const { ModelOutput } = require('./modeloutput');
9
+
10
+ module.exports = { Candidate, ConversationTurn, Gem, GemJar, RPCData, Image, WebImage, GeneratedImage, ModelOutput };
@@ -0,0 +1,39 @@
1
+ 'use strict';
2
+
3
+ const { WebImage, GeneratedImage } = require('./image');
4
+
5
+ class Candidate {
6
+ constructor({ rcid, text, text_delta = null, thoughts = null, thoughts_delta = null, web_images = [], generated_images = [] } = {}) {
7
+ this.rcid = rcid;
8
+ this.text = this._decodeHtml(text);
9
+ this.text_delta = text_delta;
10
+ this.thoughts = thoughts ? this._decodeHtml(thoughts) : null;
11
+ this.thoughts_delta = thoughts_delta;
12
+ this.web_images = web_images;
13
+ this.generated_images = generated_images;
14
+ }
15
+
16
+ _decodeHtml(str) {
17
+ if (!str) return str;
18
+ return str
19
+ .replace(/&amp;/g, '&')
20
+ .replace(/&lt;/g, '<')
21
+ .replace(/&gt;/g, '>')
22
+ .replace(/&quot;/g, '"')
23
+ .replace(/&#39;/g, '\'')
24
+ .replace(/&apos;/g, '\'');
25
+ }
26
+
27
+ get images() {
28
+ return [...this.web_images, ...this.generated_images];
29
+ }
30
+
31
+ toString() { return this.text; }
32
+
33
+ repr() {
34
+ const preview = this.text.length <= 20 ? this.text : this.text.slice(0, 20) + '...';
35
+ return `Candidate(rcid='${this.rcid}', text='${preview}', images=${this.images.length})`;
36
+ }
37
+ }
38
+
39
+ module.exports = { Candidate };
@@ -0,0 +1,105 @@
1
+ 'use strict';
2
+
3
+ const axios = require('axios');
4
+ const fs = require('fs');
5
+ const path = require('path');
6
+ const { Endpoint, Headers } = require('../constants');
7
+ const { AuthError } = require('../exceptions');
8
+
9
+ function cookieStr(c) {
10
+ return Object.entries(c).map(([k, v]) => `${k}=${v}`).join('; ');
11
+ }
12
+
13
+ function parseCookies(headers, base = {}) {
14
+ const out = { ...base };
15
+ const raw = headers['set-cookie'] || headers['Set-Cookie'];
16
+ const arr = Array.isArray(raw) ? raw : raw ? [raw] : [];
17
+ for (const s of arr) {
18
+ const p = s.split(';')[0].trim();
19
+ const eq = p.indexOf('=');
20
+ if (eq !== -1) out[p.slice(0, eq).trim()] = p.slice(eq + 1).trim();
21
+ }
22
+ return out;
23
+ }
24
+
25
+ function parseProxy(str) {
26
+ if (!str) return undefined;
27
+ try { const u = new URL(str); return { protocol: u.protocol.replace(':', ''), host: u.hostname, port: parseInt(u.port) }; }
28
+ catch { return undefined; }
29
+ }
30
+
31
+ function cacheDir() {
32
+ return process.env.GEMINI_COOKIE_PATH
33
+ ? path.resolve(process.env.GEMINI_COOKIE_PATH)
34
+ : path.join(__dirname, 'temp');
35
+ }
36
+
37
+ async function sendInitRequest(cookies, proxy = null) {
38
+ const res = await axios.get(Endpoint.INIT, {
39
+ headers: { ...Headers.GEMINI, 'Cookie': cookieStr(cookies) },
40
+ maxRedirects: 5,
41
+ ...(proxy ? { proxy: parseProxy(proxy) } : {}),
42
+ });
43
+ const t = res.data;
44
+ const snlm0e = (t.match(/"SNlM0e":\s*"(.*?)"/) || [])[1] || null;
45
+ const cfb2h = (t.match(/"cfb2h":\s*"(.*?)"/) || [])[1] || null;
46
+ const fdrfje = (t.match(/"FdrFJe":\s*"(.*?)"/) || [])[1] || null;
47
+ if (!snlm0e && !cfb2h && !fdrfje) throw new AuthError('Cookies invalid.');
48
+ return [snlm0e, cfb2h, fdrfje, parseCookies(res.headers, cookies)];
49
+ }
50
+
51
+ async function getAccessToken(baseCookies, proxy = null, verbose = false) {
52
+ let extraCookies = {};
53
+ try {
54
+ const r = await axios.get(Endpoint.GOOGLE, { maxRedirects: 5, ...(proxy ? { proxy: parseProxy(proxy) } : {}) });
55
+ if (r.status === 200) extraCookies = parseCookies(r.headers);
56
+ } catch {}
57
+
58
+ const tasks = [];
59
+ const jar = (base, extra = {}) => ({ ...extraCookies, ...base, ...extra });
60
+ const dir = cacheDir();
61
+
62
+ if (baseCookies['__Secure-1PSID'])
63
+ tasks.push(sendInitRequest(jar(baseCookies), proxy));
64
+
65
+ const psid = baseCookies['__Secure-1PSID'];
66
+ if (psid) {
67
+ const f = path.join(dir, `.cached_1psidts_${psid}.txt`);
68
+ if (fs.existsSync(f)) {
69
+ const cached = fs.readFileSync(f, 'utf8');
70
+ if (cached) tasks.push(sendInitRequest(jar(baseCookies, { '__Secure-1PSIDTS': cached }), proxy));
71
+ else if (verbose) console.debug('Skipping cached cookies. Cache file is empty.');
72
+ } else if (verbose) {
73
+ console.debug('Skipping cached cookies. Cache file not found.');
74
+ }
75
+ } else if (fs.existsSync(dir)) {
76
+ let validCaches = 0;
77
+ for (const file of fs.readdirSync(dir).filter(f => f.startsWith('.cached_1psidts_'))) {
78
+ const cached = fs.readFileSync(path.join(dir, file), 'utf8');
79
+ if (cached) {
80
+ tasks.push(sendInitRequest(jar({}, { '__Secure-1PSID': file.slice(16), '__Secure-1PSIDTS': cached }), proxy));
81
+ validCaches++;
82
+ }
83
+ }
84
+ if (validCaches === 0 && verbose) console.debug('Skipping cached cookies. No valid caches found.');
85
+ }
86
+
87
+ if (!tasks.length)
88
+ throw new AuthError('No valid cookies available. Please pass __Secure-1PSID and optionally __Secure-1PSIDTS.');
89
+
90
+ let lastErr;
91
+ for (let i = 0; i < tasks.length; i++) {
92
+ try {
93
+ const result = await tasks[i];
94
+ if (verbose) console.debug(`Init attempt (${i + 1}/${tasks.length}) succeeded.`);
95
+ return result;
96
+ } catch (e) {
97
+ lastErr = e;
98
+ if (verbose) console.debug(`Init attempt (${i + 1}/${tasks.length}) failed: ${e.message}`);
99
+ }
100
+ }
101
+
102
+ throw new AuthError(`Failed to initialize client. (Failed attempts: ${tasks.length})`);
103
+ }
104
+
105
+ module.exports = { getAccessToken, sendInitRequest, cookieStr, parseCookies, parseProxy, cacheDir };
package/utils/index.js ADDED
@@ -0,0 +1,25 @@
1
+ 'use strict';
2
+
3
+ const { getAccessToken, sendInitRequest, cookieStr, parseCookies, parseProxy, cacheDir } = require('./accessToken');
4
+ const { getDeltaByFpLen, getCleanText, getFpLen, getNestedValue, parseResponseByFrame, extractJsonFromResponse } = require('./parsing');
5
+ const { rotate1psidts } = require('./rotate');
6
+ const { uploadFile, parseFileName, generateRandomName } = require('./upload');
7
+
8
+ module.exports = {
9
+ getAccessToken,
10
+ sendInitRequest,
11
+ cookieStr,
12
+ parseCookies,
13
+ parseProxy,
14
+ cacheDir,
15
+ getDeltaByFpLen,
16
+ getCleanText,
17
+ getFpLen,
18
+ getNestedValue,
19
+ parseResponseByFrame,
20
+ extractJsonFromResponse,
21
+ rotate1psidts,
22
+ uploadFile,
23
+ parseFileName,
24
+ generateRandomName,
25
+ };
@@ -0,0 +1,119 @@
1
+ 'use strict';
2
+
3
+ const VOLATILE_RE = /[\s!"#$%&'()*+,\-./:;<=>?@[\\\]^_`{|}~]/g;
4
+ const VOLATILE_SET = new Set(' \t\n\r\x0b\x0c!"#$%&\'()*+,-./:;<=>?@[\\]^_`{|}~');
5
+ const FLICKER_ESC_RE = /\\+[`*_~].*$/;
6
+
7
+ function getCleanText(s) {
8
+ if (!s) return '';
9
+ if (s.endsWith('\n```')) s = s.slice(0, -4);
10
+ return s.replace(FLICKER_ESC_RE, '');
11
+ }
12
+
13
+ function getFpLen(s) {
14
+ return s.replace(VOLATILE_RE, '').length;
15
+ }
16
+
17
+ function getDeltaByFpLen(newRaw, lastSentClean, isFinal) {
18
+ const newC = isFinal ? newRaw : getCleanText(newRaw);
19
+ if (newC.startsWith(lastSentClean)) return [newC.slice(lastSentClean.length), newC];
20
+
21
+ const targetFpLen = getFpLen(lastSentClean);
22
+ let pLow = 0;
23
+
24
+ if (targetFpLen > 0) {
25
+ let cur = 0, found = false;
26
+ for (let i = 0; i < newC.length; i++) {
27
+ if (!VOLATILE_SET.has(newC[i])) cur++;
28
+ if (cur === targetFpLen) { pLow = i + 1; found = true; break; }
29
+ }
30
+ if (!found) {
31
+ let n = 0;
32
+ for (let i = 0; i < Math.min(lastSentClean.length, newC.length); i++) {
33
+ if (lastSentClean[i] === newC[i]) n++; else break;
34
+ }
35
+ return [newC.slice(n), newC];
36
+ }
37
+ }
38
+
39
+ let lastIdx = -1;
40
+ for (let i = lastSentClean.length - 1; i >= 0; i--) {
41
+ if (!VOLATILE_SET.has(lastSentClean[i])) { lastIdx = i; break; }
42
+ }
43
+ const suffix = lastSentClean.slice(lastIdx + 1);
44
+
45
+ let i = 0, j = 0;
46
+ while (i < suffix.length && (pLow + j) < newC.length) {
47
+ const cs = suffix[i], cn = newC[pLow + j];
48
+ if (cs === cn) { i++; j++; }
49
+ else if (cn === '\\' && (pLow + j + 1) < newC.length && newC[pLow + j + 1] === cs) { j += 2; i++; }
50
+ else if (cs === '\\' && (i + 1) < suffix.length && suffix[i + 1] === cn) { i += 2; j++; }
51
+ else break;
52
+ }
53
+ return [newC.slice(pLow + j), newC];
54
+ }
55
+
56
+ function getNestedValue(data, path, defaultVal = null) {
57
+ let cur = data;
58
+ for (const k of path) {
59
+ if (cur == null) return defaultVal;
60
+ if (typeof k === 'number') {
61
+ if (!Array.isArray(cur) || k < -cur.length || k >= cur.length) return defaultVal;
62
+ cur = cur[k < 0 ? cur.length + k : k];
63
+ } else {
64
+ if (typeof cur !== 'object' || !(k in cur)) return defaultVal;
65
+ cur = cur[k];
66
+ }
67
+ }
68
+ return cur != null ? cur : defaultVal;
69
+ }
70
+
71
+ function parseResponseByFrame(content) {
72
+ let pos = 0;
73
+ const frames = [];
74
+ while (pos < content.length) {
75
+ while (pos < content.length && /\s/.test(content[pos])) pos++;
76
+ if (pos >= content.length) break;
77
+ const m = /^(\d+)\n/.exec(content.slice(pos));
78
+ if (!m) break;
79
+ const len = parseInt(m[1]);
80
+ const start = pos + m[1].length;
81
+ let chars = 0, units = 0, idx = start;
82
+ while (units < len && idx < content.length) {
83
+ const cp = content.codePointAt(idx);
84
+ const u = cp > 0xFFFF ? 2 : 1;
85
+ if (units + u > len) break;
86
+ units += u; chars += cp > 0xFFFF ? 2 : 1; idx += cp > 0xFFFF ? 2 : 1;
87
+ }
88
+ if (units < len) break;
89
+ const end = start + chars;
90
+ const chunk = content.slice(start, end).trim();
91
+ pos = end;
92
+ if (!chunk) continue;
93
+ try {
94
+ const parsed = JSON.parse(chunk);
95
+ if (Array.isArray(parsed)) frames.push(...parsed); else frames.push(parsed);
96
+ } catch {}
97
+ }
98
+ return [frames, content.slice(pos)];
99
+ }
100
+
101
+ function extractJsonFromResponse(text) {
102
+ if (typeof text !== 'string') throw new TypeError(`Expected string, got ${typeof text}`);
103
+ let c = text.startsWith(")]}'") ? text.slice(4) : text;
104
+ c = c.trimStart();
105
+ const [r] = parseResponseByFrame(c);
106
+ if (r.length) return r;
107
+ try { const p = JSON.parse(c.trim()); return Array.isArray(p) ? p : [p]; } catch {}
108
+ const lines = [];
109
+ for (const line of c.trim().split('\n')) {
110
+ try {
111
+ const p = JSON.parse(line.trim());
112
+ if (Array.isArray(p)) lines.push(...p); else if (p && typeof p === 'object') lines.push(p);
113
+ } catch {}
114
+ }
115
+ if (lines.length) return lines;
116
+ throw new Error('Could not find valid JSON in response.');
117
+ }
118
+
119
+ module.exports = { getCleanText, getFpLen, getDeltaByFpLen, getNestedValue, parseResponseByFrame, extractJsonFromResponse };
@@ -0,0 +1,43 @@
1
+ 'use strict';
2
+
3
+ const axios = require('axios');
4
+ const fs = require('fs');
5
+ const path = require('path');
6
+ const { Endpoint, Headers } = require('../constants');
7
+ const { AuthError } = require('../exceptions');
8
+ const { cookieStr, parseCookies, parseProxy, cacheDir } = require('./accessToken');
9
+
10
+ async function rotate1psidts(cookies, proxy = null) {
11
+ const dir = cacheDir();
12
+ if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
13
+
14
+ const psid = cookies['__Secure-1PSID'];
15
+ if (!psid) return [null, null];
16
+
17
+ const cachePath = path.join(dir, `.cached_1psidts_${psid}.txt`);
18
+
19
+ if (fs.existsSync(cachePath) && Date.now() - fs.statSync(cachePath).mtimeMs <= 60000) {
20
+ return [fs.readFileSync(cachePath, 'utf8'), null];
21
+ }
22
+
23
+ const res = await axios.post(Endpoint.ROTATE_COOKIES, '[000,"-0000000000000000000"]', {
24
+ headers: { ...Headers.ROTATE_COOKIES, 'Cookie': cookieStr(cookies) },
25
+ ...(proxy ? { proxy: parseProxy(proxy) } : {}),
26
+ validateStatus: null,
27
+ });
28
+
29
+ if (res.status === 401) throw new AuthError('Unauthorized while rotating cookies.');
30
+ if (res.status >= 400) throw new Error(`HTTP ${res.status} while rotating cookies.`);
31
+
32
+ const newCookies = parseCookies(res.headers);
33
+ const new1psidts = newCookies['__Secure-1PSIDTS'] || null;
34
+
35
+ if (new1psidts) {
36
+ fs.writeFileSync(cachePath, new1psidts, { mode: 0o600 });
37
+ return [new1psidts, newCookies];
38
+ }
39
+
40
+ return [null, newCookies];
41
+ }
42
+
43
+ module.exports = { rotate1psidts };
@@ -0,0 +1,51 @@
1
+ 'use strict';
2
+
3
+ const axios = require('axios');
4
+ const fs = require('fs');
5
+ const path = require('path');
6
+ const FormData = require('form-data');
7
+ const { Endpoint, Headers } = require('../constants');
8
+ const { parseProxy } = require('./accessToken');
9
+
10
+ function generateRandomName(ext = '.txt') {
11
+ return `input_${Math.floor(Math.random() * 9000000) + 1000000}${ext}`;
12
+ }
13
+
14
+ function parseFileName(file) {
15
+ if (typeof file === 'string') {
16
+ const fp = path.resolve(file);
17
+ if (!fs.existsSync(fp)) throw new Error(`${fp} is not a valid file.`);
18
+ return path.basename(fp);
19
+ }
20
+ if (Buffer.isBuffer(file)) return generateRandomName();
21
+ return generateRandomName();
22
+ }
23
+
24
+ async function uploadFile(file, proxy = null, filename = null) {
25
+ let content, fname;
26
+
27
+ if (typeof file === 'string') {
28
+ const fp = path.resolve(file);
29
+ if (!fs.existsSync(fp)) throw new Error(`${fp} is not a valid file.`);
30
+ fname = filename || path.basename(fp);
31
+ content = fs.readFileSync(fp);
32
+ } else if (Buffer.isBuffer(file)) {
33
+ content = file;
34
+ fname = filename || generateRandomName();
35
+ } else {
36
+ throw new Error(`Unsupported file type: ${typeof file}`);
37
+ }
38
+
39
+ const form = new FormData();
40
+ form.append('file', content, { filename: fname });
41
+
42
+ const res = await axios.post(Endpoint.UPLOAD, form, {
43
+ headers: { ...Headers.UPLOAD, ...form.getHeaders() },
44
+ maxRedirects: 5,
45
+ ...(proxy ? { proxy: parseProxy(proxy) } : {}),
46
+ });
47
+
48
+ return res.data;
49
+ }
50
+
51
+ module.exports = { uploadFile, parseFileName, generateRandomName };