gemini-reverse 1.0.5 → 1.0.6
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/client.js +27 -3
- package/components/gemMixin.js +2 -1
- package/index.d.ts +0 -1
- package/package.json +1 -1
- package/types/candidate.js +16 -2
- package/types/video.js +4 -0
- package/utils/upload.js +17 -6
package/client.js
CHANGED
|
@@ -341,13 +341,25 @@ class GeminiClient {
|
|
|
341
341
|
temporary = false,
|
|
342
342
|
deep_research = false,
|
|
343
343
|
} = {}) {
|
|
344
|
+
if (!this._running) {
|
|
345
|
+
await this.init({
|
|
346
|
+
timeout: this.timeout,
|
|
347
|
+
autoClose: this.autoClose,
|
|
348
|
+
closeDelay: this.closeDelay,
|
|
349
|
+
autoRefresh: this.autoRefresh,
|
|
350
|
+
refreshInterval: this.refreshInterval,
|
|
351
|
+
verbose: this.verbose,
|
|
352
|
+
watchdogTimeout: this.watchdogTimeout,
|
|
353
|
+
});
|
|
354
|
+
if (!this._running) throw new APIError(`Invalid function call: GeminiClient.generateContent. Client initialization failed.`);
|
|
355
|
+
}
|
|
344
356
|
if (this.autoClose) this._resetCloseTask();
|
|
345
357
|
|
|
346
358
|
let fileData = null;
|
|
347
359
|
if (files && files.length) {
|
|
348
360
|
await this._sendBardActivity();
|
|
349
361
|
const uploaded = await Promise.all(
|
|
350
|
-
files.map(f => uploadFile(f, this.proxy, this.pushId)),
|
|
362
|
+
files.map(f => uploadFile(f, this.proxy, this.pushId, this.cookies)),
|
|
351
363
|
);
|
|
352
364
|
fileData = uploaded.map((url, i) => [[url], parseFileName(files[i])]);
|
|
353
365
|
}
|
|
@@ -375,13 +387,25 @@ class GeminiClient {
|
|
|
375
387
|
temporary = false,
|
|
376
388
|
deep_research = false,
|
|
377
389
|
} = {}) {
|
|
390
|
+
if (!this._running) {
|
|
391
|
+
await this.init({
|
|
392
|
+
timeout: this.timeout,
|
|
393
|
+
autoClose: this.autoClose,
|
|
394
|
+
closeDelay: this.closeDelay,
|
|
395
|
+
autoRefresh: this.autoRefresh,
|
|
396
|
+
refreshInterval: this.refreshInterval,
|
|
397
|
+
verbose: this.verbose,
|
|
398
|
+
watchdogTimeout: this.watchdogTimeout,
|
|
399
|
+
});
|
|
400
|
+
if (!this._running) throw new APIError(`Invalid function call: GeminiClient.generateContentStream. Client initialization failed.`);
|
|
401
|
+
}
|
|
378
402
|
if (this.autoClose) this._resetCloseTask();
|
|
379
403
|
|
|
380
404
|
let fileData = null;
|
|
381
405
|
if (files && files.length) {
|
|
382
406
|
await this._sendBardActivity();
|
|
383
407
|
const uploaded = await Promise.all(
|
|
384
|
-
files.map(f => uploadFile(f, this.proxy, this.pushId)),
|
|
408
|
+
files.map(f => uploadFile(f, this.proxy, this.pushId, this.cookies)),
|
|
385
409
|
);
|
|
386
410
|
fileData = uploaded.map((url, i) => [[url], parseFileName(files[i])]);
|
|
387
411
|
}
|
|
@@ -536,7 +560,7 @@ class GeminiClient {
|
|
|
536
560
|
case ErrorCode.TEMPORARY_ERROR_1013:
|
|
537
561
|
throw new APIError('Temporary error (1013). Retrying...');
|
|
538
562
|
default:
|
|
539
|
-
throw new APIError(`Unknown API error code: ${ec}.`);
|
|
563
|
+
throw new APIError(`Failed to generate contents. Unknown API error code: ${ec}. This might be a content policy rejection or a temporary Google service issue.`);
|
|
540
564
|
}
|
|
541
565
|
}
|
|
542
566
|
|
package/components/gemMixin.js
CHANGED
|
@@ -16,7 +16,8 @@ class GemMixin {
|
|
|
16
16
|
return this._gems;
|
|
17
17
|
}
|
|
18
18
|
|
|
19
|
-
async fetchGems({ includeHidden = false
|
|
19
|
+
async fetchGems({ includeHidden = false } = {}) {
|
|
20
|
+
const language = this.language || 'en';
|
|
20
21
|
const response = await this._batchExecute([
|
|
21
22
|
new RPCData({ rpcid: GRPC.LIST_GEMS, payload: includeHidden ? `[4,['${language}'],0]` : `[3,['${language}'],0]`, identifier: 'system' }),
|
|
22
23
|
new RPCData({ rpcid: GRPC.LIST_GEMS, payload: `[2,['${language}'],0]`, identifier: 'custom' }),
|
package/index.d.ts
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "gemini-reverse",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.6",
|
|
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/candidate.js
CHANGED
|
@@ -5,12 +5,26 @@ const { WebImage, GeneratedImage } = require('./image');
|
|
|
5
5
|
function decodeHtml(str) {
|
|
6
6
|
if (!str) return str;
|
|
7
7
|
return str
|
|
8
|
+
.replace(/&#(\d+);/g, (_, n) => String.fromCodePoint(parseInt(n, 10)))
|
|
9
|
+
.replace(/&#x([0-9a-f]+);/gi, (_, h) => String.fromCodePoint(parseInt(h, 16)))
|
|
8
10
|
.replace(/&/g, '&')
|
|
9
11
|
.replace(/</g, '<')
|
|
10
12
|
.replace(/>/g, '>')
|
|
11
13
|
.replace(/"/g, '"')
|
|
12
|
-
.replace(
|
|
13
|
-
.replace(/&
|
|
14
|
+
.replace(/'/g, "'")
|
|
15
|
+
.replace(/ /g, ' ')
|
|
16
|
+
.replace(/©/g, '©')
|
|
17
|
+
.replace(/®/g, '®')
|
|
18
|
+
.replace(/™/g, '™')
|
|
19
|
+
.replace(/—/g, '—')
|
|
20
|
+
.replace(/–/g, '–')
|
|
21
|
+
.replace(/…/g, '…')
|
|
22
|
+
.replace(/«/g, '«')
|
|
23
|
+
.replace(/»/g, '»')
|
|
24
|
+
.replace(/“/g, '“')
|
|
25
|
+
.replace(/”/g, '”')
|
|
26
|
+
.replace(/‘/g, '‘')
|
|
27
|
+
.replace(/’/g, '’');
|
|
14
28
|
}
|
|
15
29
|
|
|
16
30
|
class Candidate {
|
package/types/video.js
CHANGED
|
@@ -45,6 +45,10 @@ class Video {
|
|
|
45
45
|
const proxyConfig = this.proxy ? this._parseProxy(this.proxy) : undefined;
|
|
46
46
|
const res = await axios.get(url, {
|
|
47
47
|
responseType: 'arraybuffer',
|
|
48
|
+
headers: {
|
|
49
|
+
'Origin': 'https://gemini.google.com',
|
|
50
|
+
'Referer': 'https://gemini.google.com/',
|
|
51
|
+
},
|
|
48
52
|
...(proxyConfig ? { proxy: proxyConfig } : {}),
|
|
49
53
|
validateStatus: null,
|
|
50
54
|
});
|
package/utils/upload.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 mime = require('mime-types');
|
|
6
7
|
const FormData = require('form-data');
|
|
7
8
|
const { Endpoint, Headers } = require('../constants');
|
|
8
9
|
const { parseProxy } = require('./accessToken');
|
|
@@ -21,26 +22,36 @@ function parseFileName(file) {
|
|
|
21
22
|
return generateRandomName();
|
|
22
23
|
}
|
|
23
24
|
|
|
24
|
-
async function uploadFile(file, proxy = null,
|
|
25
|
+
async function uploadFile(file, proxy = null, pushId = '', cookies = {}) {
|
|
25
26
|
let content, fname;
|
|
26
27
|
|
|
27
28
|
if (typeof file === 'string') {
|
|
28
29
|
const fp = path.resolve(file);
|
|
29
30
|
if (!fs.existsSync(fp)) throw new Error(`${fp} is not a valid file.`);
|
|
30
|
-
fname =
|
|
31
|
+
fname = path.basename(fp);
|
|
31
32
|
content = fs.readFileSync(fp);
|
|
32
33
|
} else if (Buffer.isBuffer(file)) {
|
|
33
34
|
content = file;
|
|
34
|
-
fname =
|
|
35
|
+
fname = generateRandomName();
|
|
35
36
|
} else {
|
|
36
37
|
throw new Error(`Unsupported file type: ${typeof file}`);
|
|
37
38
|
}
|
|
38
39
|
|
|
40
|
+
const contentType = mime.lookup(fname) || 'application/octet-stream';
|
|
41
|
+
|
|
39
42
|
const form = new FormData();
|
|
40
|
-
form.append('file', content, { filename: fname });
|
|
43
|
+
form.append('file', content, { filename: fname, contentType });
|
|
44
|
+
|
|
45
|
+
const cookieStr = Object.entries(cookies).map(([k, v]) => `${k}=${v}`).join('; ');
|
|
41
46
|
|
|
42
47
|
const res = await axios.post(Endpoint.UPLOAD, form, {
|
|
43
|
-
headers: {
|
|
48
|
+
headers: {
|
|
49
|
+
...Headers.REFERER,
|
|
50
|
+
...Headers.UPLOAD,
|
|
51
|
+
...form.getHeaders(),
|
|
52
|
+
'Push-ID': pushId,
|
|
53
|
+
...(cookieStr ? { 'Cookie': cookieStr } : {}),
|
|
54
|
+
},
|
|
44
55
|
maxRedirects: 5,
|
|
45
56
|
...(proxy ? { proxy: parseProxy(proxy) } : {}),
|
|
46
57
|
});
|
|
@@ -48,4 +59,4 @@ async function uploadFile(file, proxy = null, filename = null) {
|
|
|
48
59
|
return res.data;
|
|
49
60
|
}
|
|
50
61
|
|
|
51
|
-
module.exports = { uploadFile, parseFileName, generateRandomName };
|
|
62
|
+
module.exports = { uploadFile, parseFileName, generateRandomName };
|