gemini-reverse 1.0.4 → 1.0.5
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/package.json +1 -1
- package/types/image.js +116 -29
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "gemini-reverse",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.5",
|
|
4
4
|
"description": "Unofficial Node.js client for gemini.google.com — inspired by Gemini-API (Python). Supports streaming, chat sessions, gems, file uploads, and TypeScript.",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"types": "index.d.ts",
|
package/types/image.js
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
const axios = require('axios');
|
|
4
4
|
const fs = require('fs');
|
|
5
5
|
const path = require('path');
|
|
6
|
+
const crypto = require('crypto');
|
|
6
7
|
|
|
7
8
|
class Image {
|
|
8
9
|
constructor({ url, title = '[Image]', alt = '', proxy = null } = {}) {
|
|
@@ -12,43 +13,76 @@ class Image {
|
|
|
12
13
|
this.proxy = proxy;
|
|
13
14
|
}
|
|
14
15
|
|
|
16
|
+
_getUrlForHash() {
|
|
17
|
+
return this.url;
|
|
18
|
+
}
|
|
19
|
+
|
|
15
20
|
toString() {
|
|
16
21
|
const short = this.url.length <= 20 ? this.url : this.url.slice(0, 8) + '...' + this.url.slice(-12);
|
|
17
22
|
return `Image(title='${this.title}', alt='${this.alt}', url='${short}')`;
|
|
18
23
|
}
|
|
19
24
|
|
|
20
|
-
async save({ path: savePath = 'temp', filename = null,
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
if (verbose) console.warn(`Invalid filename: ${fname}`);
|
|
27
|
-
if (skipInvalidFilename) return null;
|
|
25
|
+
async save({ path: savePath = 'temp', filename = null, verbose = false } = {}) {
|
|
26
|
+
if (!filename || !path.extname(filename)) {
|
|
27
|
+
const timestamp = new Date().toISOString().replace(/[-:.TZ]/g, '').slice(0, 14);
|
|
28
|
+
const urlHash = crypto.createHash('sha256').update(this._getUrlForHash()).digest('hex').slice(0, 10);
|
|
29
|
+
const baseName = filename ? path.parse(filename).name : 'image';
|
|
30
|
+
filename = `${timestamp}_${urlHash}_${baseName}`;
|
|
28
31
|
}
|
|
29
32
|
|
|
30
|
-
|
|
33
|
+
fs.mkdirSync(savePath, { recursive: true });
|
|
34
|
+
return await this._performSave(savePath, filename, verbose);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
async _performSave(savePath, filename, verbose) {
|
|
38
|
+
const imgUrl = this.url;
|
|
39
|
+
const proxyConfig = this._parseProxy(this.proxy);
|
|
40
|
+
const clientRef = this.client_ref || null;
|
|
41
|
+
const cookies = clientRef ? clientRef.cookies : null;
|
|
31
42
|
|
|
32
|
-
const res = await axios.get(
|
|
43
|
+
const res = await axios.get(imgUrl, {
|
|
33
44
|
responseType: 'arraybuffer',
|
|
34
|
-
headers:
|
|
45
|
+
headers: {
|
|
46
|
+
'Origin': 'https://gemini.google.com',
|
|
47
|
+
'Referer': 'https://gemini.google.com/',
|
|
48
|
+
...(cookies ? { 'Cookie': Object.entries(cookies).map(([k, v]) => `${k}=${v}`).join('; ') } : {}),
|
|
49
|
+
},
|
|
35
50
|
maxRedirects: 5,
|
|
36
51
|
...(proxyConfig ? { proxy: proxyConfig } : {}),
|
|
37
52
|
});
|
|
38
53
|
|
|
39
|
-
if (
|
|
54
|
+
if (verbose) console.debug(`HTTP Request: GET ${imgUrl} [${res.status}]`);
|
|
40
55
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
console.warn(`Content type of ${fname} is not image, but ${contentType}.`);
|
|
56
|
+
if (res.status !== 200) {
|
|
57
|
+
throw new Error(`Error downloading image: ${res.status}`);
|
|
44
58
|
}
|
|
45
59
|
|
|
46
|
-
|
|
47
|
-
|
|
60
|
+
const contentType = (res.headers['content-type'] || '').split(';')[0].trim().toLowerCase();
|
|
61
|
+
let ext = path.extname(filename);
|
|
62
|
+
if (!ext) {
|
|
63
|
+
if (contentType.includes('jpeg') || contentType.includes('jpg')) ext = '.jpg';
|
|
64
|
+
else if (contentType.includes('png')) ext = '.png';
|
|
65
|
+
else if (contentType.includes('webp')) ext = '.webp';
|
|
66
|
+
else if (contentType.includes('gif')) ext = '.gif';
|
|
67
|
+
else ext = '.png';
|
|
68
|
+
filename = filename + ext;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const dest = path.join(savePath, filename);
|
|
48
72
|
fs.writeFileSync(dest, Buffer.from(res.data));
|
|
49
|
-
if (verbose) console.
|
|
73
|
+
if (verbose) console.info(`Image saved as ${path.resolve(dest)}`);
|
|
50
74
|
return path.resolve(dest);
|
|
51
75
|
}
|
|
76
|
+
|
|
77
|
+
_parseProxy(proxyStr) {
|
|
78
|
+
if (!proxyStr) return undefined;
|
|
79
|
+
try {
|
|
80
|
+
const u = new URL(proxyStr);
|
|
81
|
+
return { protocol: u.protocol.replace(':', ''), host: u.hostname, port: parseInt(u.port) };
|
|
82
|
+
} catch {
|
|
83
|
+
return undefined;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
52
86
|
}
|
|
53
87
|
|
|
54
88
|
class WebImage extends Image {
|
|
@@ -56,19 +90,72 @@ class WebImage extends Image {
|
|
|
56
90
|
}
|
|
57
91
|
|
|
58
92
|
class GeneratedImage extends Image {
|
|
59
|
-
constructor({
|
|
60
|
-
super(
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
this.
|
|
93
|
+
constructor({ url, title = '[Image]', alt = '', proxy = null, client_ref = null, cid = '', rid = '', rcid = '', image_id = '' } = {}) {
|
|
94
|
+
super({ url, title, alt, proxy });
|
|
95
|
+
this.client_ref = client_ref;
|
|
96
|
+
this.cid = cid;
|
|
97
|
+
this.rid = rid;
|
|
98
|
+
this.rcid = rcid;
|
|
99
|
+
this.image_id = image_id;
|
|
64
100
|
}
|
|
65
101
|
|
|
66
|
-
async
|
|
67
|
-
if (fullSize)
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
102
|
+
async _performSave(savePath, filename, verbose, fullSize = true) {
|
|
103
|
+
if (fullSize) {
|
|
104
|
+
if (this.client_ref && this.cid && this.rid && this.rcid && this.image_id) {
|
|
105
|
+
try {
|
|
106
|
+
const originalUrl = await this.client_ref._getFullSizeImage(
|
|
107
|
+
this.cid, this.rid, this.rcid, this.image_id,
|
|
108
|
+
);
|
|
109
|
+
if (originalUrl) {
|
|
110
|
+
const proxyConfig = this._parseProxy(this.proxy);
|
|
111
|
+
const cookies = this.client_ref ? this.client_ref.cookies : null;
|
|
112
|
+
const headers = {
|
|
113
|
+
'Origin': 'https://gemini.google.com',
|
|
114
|
+
'Referer': 'https://gemini.google.com/',
|
|
115
|
+
...(cookies ? { 'Cookie': Object.entries(cookies).map(([k, v]) => `${k}=${v}`).join('; ') } : {}),
|
|
116
|
+
};
|
|
117
|
+
|
|
118
|
+
const r1 = await axios.get(`${originalUrl}=d-I?alr=yes`, {
|
|
119
|
+
headers, maxRedirects: 5, ...(proxyConfig ? { proxy: proxyConfig } : {}),
|
|
120
|
+
});
|
|
121
|
+
const r2 = await axios.get(r1.data.trim(), {
|
|
122
|
+
headers, maxRedirects: 5, ...(proxyConfig ? { proxy: proxyConfig } : {}),
|
|
123
|
+
});
|
|
124
|
+
this.url = r2.data.trim();
|
|
125
|
+
return await super._performSave(savePath, filename, verbose);
|
|
126
|
+
}
|
|
127
|
+
} catch (e) {
|
|
128
|
+
if (verbose) console.debug(`Failed to fetch full size image via RPC: ${e.message}, falling back.`);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
if (this.url.includes('=s1024-rj')) {
|
|
133
|
+
this.url = this.url.replace('=s1024-rj', '=s2048-rj');
|
|
134
|
+
} else if (!this.url.includes('=s2048-rj')) {
|
|
135
|
+
this.url += '=s2048-rj';
|
|
136
|
+
}
|
|
137
|
+
} else {
|
|
138
|
+
if (this.url.includes('=s2048-rj')) {
|
|
139
|
+
this.url = this.url.replace('=s2048-rj', '=s1024-rj');
|
|
140
|
+
} else if (!this.url.includes('=s1024-rj')) {
|
|
141
|
+
this.url += '=s1024-rj';
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
return await super._performSave(savePath, filename, verbose);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
async save({ path: savePath = 'temp', filename = null, verbose = false, fullSize = true } = {}) {
|
|
149
|
+
if (!filename || !path.extname(filename)) {
|
|
150
|
+
const timestamp = new Date().toISOString().replace(/[-:.TZ]/g, '').slice(0, 14);
|
|
151
|
+
const urlHash = crypto.createHash('sha256').update(this._getUrlForHash()).digest('hex').slice(0, 10);
|
|
152
|
+
const baseName = filename ? path.parse(filename).name : 'image';
|
|
153
|
+
filename = `${timestamp}_${urlHash}_${baseName}`;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
fs.mkdirSync(savePath, { recursive: true });
|
|
157
|
+
return await this._performSave(savePath, filename, verbose, fullSize);
|
|
71
158
|
}
|
|
72
159
|
}
|
|
73
160
|
|
|
74
|
-
module.exports = { Image, WebImage, GeneratedImage };
|
|
161
|
+
module.exports = { Image, WebImage, GeneratedImage };
|