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 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
 
@@ -16,7 +16,8 @@ class GemMixin {
16
16
  return this._gems;
17
17
  }
18
18
 
19
- async fetchGems({ includeHidden = false, language = 'en' } = {}) {
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
@@ -430,7 +430,6 @@ export interface StartChatOptions {
430
430
 
431
431
  export interface FetchGemsOptions {
432
432
  includeHidden?: boolean;
433
- language?: string;
434
433
  }
435
434
 
436
435
  export interface CreateGemOptions {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gemini-reverse",
3
- "version": "1.0.5",
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",
@@ -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(/&lt;/g, '<')
10
12
  .replace(/&gt;/g, '>')
11
13
  .replace(/&quot;/g, '"')
12
- .replace(/&#39;/g, "'")
13
- .replace(/&apos;/g, "'");
14
+ .replace(/&apos;/g, "'")
15
+ .replace(/&nbsp;/g, ' ')
16
+ .replace(/&copy;/g, '©')
17
+ .replace(/&reg;/g, '®')
18
+ .replace(/&trade;/g, '™')
19
+ .replace(/&mdash;/g, '—')
20
+ .replace(/&ndash;/g, '–')
21
+ .replace(/&hellip;/g, '…')
22
+ .replace(/&laquo;/g, '«')
23
+ .replace(/&raquo;/g, '»')
24
+ .replace(/&ldquo;/g, '“')
25
+ .replace(/&rdquo;/g, '”')
26
+ .replace(/&lsquo;/g, '‘')
27
+ .replace(/&rsquo;/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, filename = 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 = filename || path.basename(fp);
31
+ fname = path.basename(fp);
31
32
  content = fs.readFileSync(fp);
32
33
  } else if (Buffer.isBuffer(file)) {
33
34
  content = file;
34
- fname = filename || generateRandomName();
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: { ...Headers.UPLOAD, ...form.getHeaders() },
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 };