gemini-reverse 1.0.5 → 1.0.7

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/constants.js CHANGED
@@ -9,11 +9,11 @@ const ARTIFACTS_RE = /http:\/\/googleusercontent\.com\/\w+\/\d+\n*/g;
9
9
  const DEFAULT_METADATA = ['', '', '', null, null, null, null, null, null, ''];
10
10
  const MODEL_HEADER_KEY = 'x-goog-ext-525001261-jspb';
11
11
 
12
- function buildModelHeader(modelId, capacityTail) {
12
+ function buildModelHeader(modelId, capacityTail, modelNumber = 1) {
13
13
  return {
14
- [MODEL_HEADER_KEY]: `[1,null,null,null,"${modelId}",null,null,0,[4],null,null,${capacityTail}]`,
14
+ [MODEL_HEADER_KEY]: `[1,null,null,null,"${modelId}",null,null,0,[4,5,6,8],null,null,${capacityTail},null,null,${modelNumber}]`,
15
15
  'x-goog-ext-73010989-jspb': '[0]',
16
- 'x-goog-ext-73010990-jspb': '[0]',
16
+ 'x-goog-ext-73010990-jspb': '[0,0,0]',
17
17
  };
18
18
  }
19
19
 
@@ -29,22 +29,56 @@ const Endpoint = {
29
29
  const GRPC = {
30
30
  LIST_CHATS: 'MaZiqc',
31
31
  READ_CHAT: 'hNvQHb',
32
+ GET_CONVERSATION_TURN: 'EqPOKe',
32
33
  DELETE_CHAT_1: 'GzXR5e',
33
34
  DELETE_CHAT_2: 'qWymEb',
35
+ UPDATE_CONVERSATION: 'MUAZcd',
36
+ MARK_LAST_CONVERSATION_TURN: 'kOWVAe',
37
+ GENERATE_HEADLINE: 'ukz1Fe',
38
+
34
39
  LIST_GEMS: 'CNgdBe',
35
40
  CREATE_GEM: 'oMH3Zd',
41
+ GET_GEM: 'HcT8bb',
36
42
  UPDATE_GEM: 'kHv0Vd',
37
43
  DELETE_GEM: 'UXcSJb',
44
+ DELETE_GEM_AND_CONVERSATIONS: 'Nwkn9',
45
+
46
+ CREATE_TASK: 'Jba3ib',
47
+ GET_TASK: 'kwDCne',
48
+ GET_ALL_TASKS: 'XPSWpd',
49
+ GET_TASKS_IN_CONVERSATION: 'qWymEb',
50
+ GET_CANDIDATES: 'PCck7e',
51
+ LIST_DISCOVERY_CARDS: 'ku4Jyf',
52
+ GET_DISCOVERY_CARD: 'oApPWc',
53
+ LIST_DISCOVERY_BANNERS: 'Te6DCf',
54
+
38
55
  DEEP_RESEARCH_STATUS: 'kwDCne',
39
56
  DEEP_RESEARCH_PREFS: 'L5adhe',
40
57
  DEEP_RESEARCH_BOOTSTRAP: 'ku4Jyf',
41
58
  DEEP_RESEARCH_MODEL_STATE: 'qpEbW',
42
59
  DEEP_RESEARCH_CAPS: 'aPya6c',
43
60
  DEEP_RESEARCH_ACK: 'PCck7e',
61
+
62
+ LIST_GEMINI_APP_ARTIFACTS: 'jGArJ',
63
+ DELETE_GEMINI_APP_ARTIFACTS: 'PGX16d',
64
+
65
+ LIST_MEMORIES: 'ZKcapf',
66
+ CREATE_MEMORY: 'xVRQX',
67
+ UPDATE_MEMORY: 'gSnMcd',
68
+ DELETE_MEMORY: 'Ok9j9b',
69
+ DELETE_ALL_MEMORIES: 'YgU2Cc',
70
+
44
71
  GET_USER_STATUS: 'otAQ7b',
45
72
  LIST_MODELS: 'otAQ7b',
73
+ CHECK_GEMINI_QUOTA: 'qpEbW',
74
+ CHECK_QUOTA: 'aPya6c',
46
75
  GET_FULL_SIZE_IMAGE: 'c8o8Fe',
76
+ GET_ABUSE_STATUS: 'GPRiHf',
77
+ UPDATE_USER_PREFERENCES: 'L5adhe',
78
+ READ_USER_PREFERENCES: 'ESY5D',
47
79
  BARD_SETTINGS: 'ESY5D',
80
+ CONTINUE_SHARED_CONVERSATION: 'ra9Swb',
81
+ GET_USAGE_INFO: 'jSf9Qc',
48
82
  };
49
83
 
50
84
  const Headers = {
@@ -66,67 +100,69 @@ const Headers = {
66
100
  },
67
101
  UPLOAD: { 'X-Tenant-Id': 'bard-storage' },
68
102
  BATCH_EXEC: {
69
- 'x-goog-ext-525001261-jspb': '[1,null,null,null,null,null,null,null,[4]]',
103
+ 'x-goog-ext-525001261-jspb': '[1,null,null,null,null,null,null,null,[4,5,6,8],null,null,null,null,null,null,null]',
70
104
  'x-goog-ext-73010989-jspb': '[0]',
71
105
  },
72
106
  };
73
107
 
74
108
  const _MODEL_KEYS = [
75
- 'UNSPECIFIED', 'BASIC_PRO', 'BASIC_FLASH', 'BASIC_THINKING',
76
- 'PLUS_PRO', 'PLUS_FLASH', 'PLUS_THINKING',
77
- 'ADVANCED_PRO', 'ADVANCED_FLASH', 'ADVANCED_THINKING',
109
+ 'UNSPECIFIED',
110
+ 'BASIC_PRO', 'BASIC_FLASH', 'BASIC_LITE',
111
+ 'PLUS_PRO', 'PLUS_FLASH', 'PLUS_LITE',
112
+ 'ADVANCED_PRO', 'ADVANCED_FLASH', 'ADVANCED_LITE',
78
113
  ];
79
114
 
80
115
  const Model = {
81
116
  UNSPECIFIED: { model_name: 'unspecified', model_header: {}, advanced_only: false },
82
117
  BASIC_PRO: {
83
118
  model_name: 'gemini-3-pro',
84
- model_header: buildModelHeader('9d8ca3786ebdfbea', 1),
119
+ model_header: buildModelHeader('9d8ca3786ebdfbea', 1, 3),
85
120
  advanced_only: false,
86
121
  },
87
122
  BASIC_FLASH: {
88
123
  model_name: 'gemini-3-flash',
89
- model_header: buildModelHeader('fbb127bbb056c959', 1),
124
+ model_header: buildModelHeader('fbb127bbb056c959', 1, 1),
90
125
  advanced_only: false,
91
126
  },
92
- BASIC_THINKING: {
93
- model_name: 'gemini-3-flash-thinking',
94
- model_header: buildModelHeader('5bf011840784117a', 1),
127
+ BASIC_LITE: {
128
+ model_name: 'gemini-3-lite',
129
+ model_header: buildModelHeader('cf41b0e0dd7d53e5', 1, 6),
95
130
  advanced_only: false,
96
131
  },
97
132
  PLUS_PRO: {
98
133
  model_name: 'gemini-3-pro-plus',
99
- model_header: buildModelHeader('e6fa609c3fa255c0', 4),
134
+ model_header: buildModelHeader('e6fa609c3fa255c0', 4, 3),
100
135
  advanced_only: true,
101
136
  },
102
137
  PLUS_FLASH: {
103
138
  model_name: 'gemini-3-flash-plus',
104
- model_header: buildModelHeader('56fdd199312815e2', 4),
139
+ model_header: buildModelHeader('56fdd199312815e2', 4, 1),
105
140
  advanced_only: true,
106
141
  },
107
- PLUS_THINKING: {
108
- model_name: 'gemini-3-flash-thinking-plus',
109
- model_header: buildModelHeader('e051ce1aa80aa576', 4),
142
+ PLUS_LITE: {
143
+ model_name: 'gemini-3-lite-plus',
144
+ model_header: buildModelHeader('8c46e95b1a07cecc', 4, 6),
110
145
  advanced_only: true,
111
146
  },
112
147
  ADVANCED_PRO: {
113
148
  model_name: 'gemini-3-pro-advanced',
114
- model_header: buildModelHeader('e6fa609c3fa255c0', 2),
149
+ model_header: buildModelHeader('e6fa609c3fa255c0', 2, 3),
115
150
  advanced_only: true,
116
151
  },
117
152
  ADVANCED_FLASH: {
118
153
  model_name: 'gemini-3-flash-advanced',
119
- model_header: buildModelHeader('56fdd199312815e2', 2),
154
+ model_header: buildModelHeader('56fdd199312815e2', 2, 1),
120
155
  advanced_only: true,
121
156
  },
122
- ADVANCED_THINKING: {
123
- model_name: 'gemini-3-flash-thinking-advanced',
124
- model_header: buildModelHeader('e051ce1aa80aa576', 2),
157
+ ADVANCED_LITE: {
158
+ model_name: 'gemini-3-lite-advanced',
159
+ model_header: buildModelHeader('8c46e95b1a07cecc', 2, 6),
125
160
  advanced_only: true,
126
161
  },
127
162
  fromName(name) {
163
+ const lower = name.toLowerCase();
128
164
  for (const k of _MODEL_KEYS) {
129
- if (Model[k].model_name === name) return Model[k];
165
+ if (Model[k].model_name === lower) return Model[k];
130
166
  }
131
167
  const names = _MODEL_KEYS.map(k => Model[k].model_name).join(', ');
132
168
  throw new Error(`Unknown model name: ${name}. Available: ${names}`);
package/index.d.ts CHANGED
@@ -18,7 +18,7 @@ export interface ModelDict {
18
18
 
19
19
  export type ModelInput = string | ModelDef | ModelDict | AvailableModel | null;
20
20
 
21
- export declare function buildModelHeader(modelId: string, capacityTail: string | number): ModelHeader;
21
+ export declare function buildModelHeader(modelId: string, capacityTail: string | number, modelNumber?: number): ModelHeader;
22
22
 
23
23
  export declare const MODEL_HEADER_KEY: string;
24
24
  export declare const STREAMING_FLAG_INDEX: number;
@@ -125,6 +125,7 @@ export declare class AvailableModel {
125
125
  description: string;
126
126
  capacity: number;
127
127
  capacity_field: number;
128
+ model_number: number;
128
129
  is_available: boolean;
129
130
  constructor(opts: {
130
131
  model_id: string;
@@ -133,6 +134,7 @@ export declare class AvailableModel {
133
134
  description: string;
134
135
  capacity: number;
135
136
  capacity_field?: number;
137
+ model_number?: number;
136
138
  is_available?: boolean;
137
139
  });
138
140
  get model_header(): ModelHeader;
@@ -141,6 +143,7 @@ export declare class AvailableModel {
141
143
  repr(): string;
142
144
  static computeCapacity(tierFlags: number[], capabilityFlags: number[]): [number, number];
143
145
  static buildModelIdNameMapping(): Record<string, string>;
146
+ static buildModelIdNumberMapping(): Record<string, number>;
144
147
  }
145
148
 
146
149
  export interface ImageSaveOptions {
@@ -417,6 +420,7 @@ export interface GenerateOptions {
417
420
  chat?: ChatSession | null;
418
421
  temporary?: boolean;
419
422
  deep_research?: boolean;
423
+ extended_thinking?: boolean;
420
424
  }
421
425
 
422
426
  export interface StartChatOptions {
@@ -430,7 +434,6 @@ export interface StartChatOptions {
430
434
 
431
435
  export interface FetchGemsOptions {
432
436
  includeHidden?: boolean;
433
- language?: string;
434
437
  }
435
438
 
436
439
  export interface CreateGemOptions {
@@ -462,6 +465,9 @@ export declare class GeminiClient {
462
465
  refreshInterval: number;
463
466
  verbose: boolean;
464
467
  watchdogTimeout: number;
468
+ readonly quotas: Record<string, Record<string, unknown>>;
469
+ readonly usageInfo: Record<string, unknown>;
470
+ readonly abuseStatus: { is_clean: boolean; status_code: number | null; signal: unknown } | null;
465
471
 
466
472
  constructor(opts?: GeminiClientOptions);
467
473
 
@@ -509,6 +515,7 @@ export interface SendMessageOptions {
509
515
  files?: (string | Buffer)[] | null;
510
516
  temporary?: boolean;
511
517
  deep_research?: boolean;
518
+ extended_thinking?: boolean;
512
519
  }
513
520
 
514
521
  export declare class ChatSession {
package/index.js CHANGED
@@ -11,6 +11,7 @@ const {
11
11
  AuthError, APIError, ImageGenerationError, GeminiError, TimeoutError,
12
12
  UsageLimitExceeded, ModelInvalid, TemporarilyBlocked,
13
13
  } = require('./exceptions');
14
+ const { StreamingFrameParser } = require('./utils/parsing');
14
15
  const {
15
16
  Candidate, ChatHistory, ChatTurn, ChatInfo, Gem, GemJar, RPCData,
16
17
  Image, WebImage, GeneratedImage, ModelOutput,
@@ -61,4 +62,5 @@ module.exports = {
61
62
  GeneratedVideo,
62
63
  GeneratedMedia,
63
64
  AvailableModel,
65
+ StreamingFrameParser,
64
66
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gemini-reverse",
3
- "version": "1.0.5",
3
+ "version": "1.0.7",
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",
@@ -3,13 +3,14 @@
3
3
  const { buildModelHeader, MODEL_HEADER_KEY, Model } = require('../constants');
4
4
 
5
5
  class AvailableModel {
6
- constructor({ model_id, model_name, display_name, description, capacity, capacity_field = 12, is_available = true } = {}) {
6
+ constructor({ model_id, model_name, display_name, description, capacity, capacity_field = 12, model_number = 1, is_available = true } = {}) {
7
7
  this.model_id = model_id;
8
8
  this.model_name = model_name;
9
9
  this.display_name = display_name;
10
10
  this.description = description;
11
11
  this.capacity = capacity;
12
12
  this.capacity_field = capacity_field;
13
+ this.model_number = model_number;
13
14
  this.is_available = is_available;
14
15
  }
15
16
 
@@ -20,7 +21,7 @@ class AvailableModel {
20
21
  } else {
21
22
  tail = String(this.capacity);
22
23
  }
23
- return buildModelHeader(this.model_id, tail);
24
+ return buildModelHeader(this.model_id, tail, this.model_number);
24
25
  }
25
26
 
26
27
  get advanced_only() {
@@ -47,9 +48,9 @@ class AvailableModel {
47
48
  static buildModelIdNameMapping() {
48
49
  const result = {};
49
50
  const keys = [
50
- 'BASIC_PRO', 'BASIC_FLASH', 'BASIC_THINKING',
51
- 'PLUS_PRO', 'PLUS_FLASH', 'PLUS_THINKING',
52
- 'ADVANCED_PRO', 'ADVANCED_FLASH', 'ADVANCED_THINKING',
51
+ 'BASIC_PRO', 'BASIC_FLASH', 'BASIC_LITE',
52
+ 'PLUS_PRO', 'PLUS_FLASH', 'PLUS_LITE',
53
+ 'ADVANCED_PRO', 'ADVANCED_FLASH', 'ADVANCED_LITE',
53
54
  ];
54
55
  for (const key of keys) {
55
56
  const member = Model[key];
@@ -71,6 +72,32 @@ class AvailableModel {
71
72
  }
72
73
  return result;
73
74
  }
75
+
76
+ static buildModelIdNumberMapping() {
77
+ const result = {};
78
+ const keys = [
79
+ 'BASIC_PRO', 'BASIC_FLASH', 'BASIC_LITE',
80
+ 'PLUS_PRO', 'PLUS_FLASH', 'PLUS_LITE',
81
+ 'ADVANCED_PRO', 'ADVANCED_FLASH', 'ADVANCED_LITE',
82
+ ];
83
+ for (const key of keys) {
84
+ const member = Model[key];
85
+ if (!member) continue;
86
+ const headerValue = member.model_header[MODEL_HEADER_KEY];
87
+ if (!headerValue) continue;
88
+ try {
89
+ const parsed = JSON.parse(headerValue);
90
+ const modelId = parsed && parsed[4];
91
+ const modelNumber = parsed && parsed[parsed.length - 1];
92
+ if (modelId && typeof modelNumber === 'number' && !(modelId in result)) {
93
+ result[modelId] = modelNumber;
94
+ }
95
+ } catch {
96
+ continue;
97
+ }
98
+ }
99
+ return result;
100
+ }
74
101
  }
75
102
 
76
103
  module.exports = { AvailableModel };
@@ -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(/&amp;/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/index.js CHANGED
@@ -1,7 +1,7 @@
1
1
  'use strict';
2
2
 
3
3
  const { getAccessToken, sendInitRequest, cookieStr, parseCookies, parseProxy, cacheDir } = require('./accessToken');
4
- const { getDeltaByFpLen, getCleanText, getNestedValue, parseResponseByFrame, extractJsonFromResponse } = require('./parsing');
4
+ const { getDeltaByFpLen, getCleanText, getNestedValue, parseResponseByFrame, extractJsonFromResponse, StreamingFrameParser } = require('./parsing');
5
5
  const { rotate1psidts } = require('./rotate');
6
6
  const { uploadFile, parseFileName, generateRandomName } = require('./upload');
7
7
  const {
@@ -28,6 +28,7 @@ module.exports = {
28
28
  getNestedValue,
29
29
  parseResponseByFrame,
30
30
  extractJsonFromResponse,
31
+ StreamingFrameParser,
31
32
  rotate1psidts,
32
33
  uploadFile,
33
34
  parseFileName,
package/utils/parsing.js CHANGED
@@ -1,6 +1,7 @@
1
1
  'use strict';
2
2
 
3
3
  const FLICKER_ESC_RE = /\\+[`*_~].*$/;
4
+ const LENGTH_MARKER_RE = /^(\d+)\n/;
4
5
 
5
6
  function getCleanText(s) {
6
7
  if (!s) return '';
@@ -90,45 +91,121 @@ function getNestedValue(data, path, defaultVal = null) {
90
91
  return cur != null ? cur : defaultVal;
91
92
  }
92
93
 
93
- function parseResponseByFrame(content) {
94
- let pos = 0;
95
- const frames = [];
96
- while (pos < content.length) {
97
- while (pos < content.length && /\s/.test(content[pos])) pos++;
98
- if (pos >= content.length) break;
99
- const m = /^(\d+)\n/.exec(content.slice(pos));
100
- if (!m) break;
101
- const len = parseInt(m[1]);
102
- const start = pos + m[1].length;
103
- let chars = 0, units = 0, idx = start;
104
- while (units < len && idx < content.length) {
105
- const cp = content.codePointAt(idx);
94
+ class StreamingFrameParser {
95
+ constructor() {
96
+ this.buffer = '';
97
+ this.expectedUnits = null;
98
+ this.payloadStart = 0;
99
+ this.scannedChars = 0;
100
+ this.scannedUnits = 0;
101
+ this.prefixChecked = false;
102
+ }
103
+
104
+ reset() {
105
+ this.buffer = '';
106
+ this._resetFrameState();
107
+ this.prefixChecked = false;
108
+ }
109
+
110
+ feed(content) {
111
+ if (typeof content !== 'string') throw new TypeError(`Expected string, got ${typeof content}`);
112
+ if (content) this.buffer += content;
113
+ this._stripPrefixOnce();
114
+
115
+ const parsed = [];
116
+ while (true) {
117
+ if (this.expectedUnits === null && !this._readLengthMarker()) break;
118
+ if (this.expectedUnits === null) break;
119
+
120
+ this._scanAvailablePayload();
121
+ if (this.scannedUnits < this.expectedUnits) break;
122
+
123
+ const endPos = this.payloadStart + this.scannedChars;
124
+ const chunk = this.buffer.slice(this.payloadStart, endPos);
125
+ this.buffer = this.buffer.slice(endPos);
126
+ this._resetFrameState();
127
+
128
+ if (!chunk.trim()) continue;
129
+
130
+ try {
131
+ const p = JSON.parse(chunk);
132
+ if (Array.isArray(p)) parsed.push(...p);
133
+ else parsed.push(p);
134
+ } catch {}
135
+ }
136
+ return parsed;
137
+ }
138
+
139
+ flush() {
140
+ return this.feed('');
141
+ }
142
+
143
+ _resetFrameState() {
144
+ this.expectedUnits = null;
145
+ this.payloadStart = 0;
146
+ this.scannedChars = 0;
147
+ this.scannedUnits = 0;
148
+ }
149
+
150
+ _stripPrefixOnce() {
151
+ if (this.prefixChecked) return;
152
+ const prefix = ")]}'";
153
+ if (this.buffer.length < prefix.length && prefix.startsWith(this.buffer)) return;
154
+ if (this.buffer.startsWith(prefix)) {
155
+ this.buffer = this.buffer.slice(prefix.length).trimStart();
156
+ }
157
+ this.prefixChecked = true;
158
+ }
159
+
160
+ _readLengthMarker() {
161
+ let pos = 0;
162
+ while (pos < this.buffer.length && /\s/.test(this.buffer[pos])) pos++;
163
+ if (pos) {
164
+ this.buffer = this.buffer.slice(pos);
165
+ }
166
+ if (!this.buffer.length) return false;
167
+
168
+ const m = LENGTH_MARKER_RE.exec(this.buffer);
169
+ if (!m) return false;
170
+
171
+ const lenStr = m[1];
172
+ this.expectedUnits = parseInt(lenStr, 10);
173
+ this.payloadStart = lenStr.length;
174
+ this.scannedChars = 0;
175
+ this.scannedUnits = 0;
176
+ return true;
177
+ }
178
+
179
+ _scanAvailablePayload() {
180
+ if (this.expectedUnits === null) return;
181
+ let idx = this.payloadStart + this.scannedChars;
182
+ const limit = this.buffer.length;
183
+
184
+ while (this.scannedUnits < this.expectedUnits && idx < limit) {
185
+ const cp = this.buffer.codePointAt(idx);
106
186
  const u = cp > 0xFFFF ? 2 : 1;
107
- if (units + u > len) break;
108
- units += u;
109
- chars += cp > 0xFFFF ? 2 : 1;
187
+ if (this.scannedUnits + u > this.expectedUnits) break;
188
+ this.scannedUnits += u;
189
+ this.scannedChars += cp > 0xFFFF ? 2 : 1;
110
190
  idx += cp > 0xFFFF ? 2 : 1;
111
191
  }
112
- if (units < len) break;
113
- const end = start + chars;
114
- const chunk = content.slice(start, end).trim();
115
- pos = end;
116
- if (!chunk) continue;
117
- try {
118
- const parsed = JSON.parse(chunk);
119
- if (Array.isArray(parsed)) frames.push(...parsed);
120
- else frames.push(parsed);
121
- } catch {}
122
192
  }
123
- return [frames, content.slice(pos)];
193
+ }
194
+
195
+ function parseResponseByFrame(content) {
196
+ const parser = new StreamingFrameParser();
197
+ const frames = parser.feed(content);
198
+ frames.push(...parser.flush());
199
+ return [frames, parser.buffer];
124
200
  }
125
201
 
126
202
  function extractJsonFromResponse(text) {
127
203
  if (typeof text !== 'string') throw new TypeError(`Expected string, got ${typeof text}`);
128
- let c = text.startsWith(")]}'") ? text.slice(4) : text;
129
- c = c.trimStart();
130
- const [r] = parseResponseByFrame(c);
131
- if (r.length) return r;
204
+ const parser = new StreamingFrameParser();
205
+ const result = parser.feed(text);
206
+ result.push(...parser.flush());
207
+ if (result.length) return result;
208
+ const c = text.startsWith(")]}'") ? text.slice(4).trimStart() : text.trimStart();
132
209
  try {
133
210
  const p = JSON.parse(c.trim());
134
211
  return Array.isArray(p) ? p : [p];
@@ -145,4 +222,4 @@ function extractJsonFromResponse(text) {
145
222
  throw new Error('Could not find valid JSON in response.');
146
223
  }
147
224
 
148
- module.exports = { getCleanText, getDeltaByFpLen, getNestedValue, parseResponseByFrame, extractJsonFromResponse };
225
+ module.exports = { getCleanText, getDeltaByFpLen, getNestedValue, parseResponseByFrame, extractJsonFromResponse, StreamingFrameParser };
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 };