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/README.md +96 -15
- package/client.js +287 -41
- package/components/chatMixin.js +1 -0
- package/components/gemMixin.js +2 -1
- package/constants.js +59 -23
- package/index.d.ts +9 -2
- package/index.js +2 -0
- package/package.json +1 -1
- package/types/availablemodel.js +32 -5
- package/types/candidate.js +16 -2
- package/types/video.js +4 -0
- package/utils/index.js +2 -1
- package/utils/parsing.js +109 -32
- package/utils/upload.js +17 -6
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',
|
|
76
|
-
'
|
|
77
|
-
'
|
|
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
|
-
|
|
93
|
-
model_name: 'gemini-3-
|
|
94
|
-
model_header: buildModelHeader('
|
|
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
|
-
|
|
108
|
-
model_name: 'gemini-3-
|
|
109
|
-
model_header: buildModelHeader('
|
|
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
|
-
|
|
123
|
-
model_name: 'gemini-3-
|
|
124
|
-
model_header: buildModelHeader('
|
|
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 ===
|
|
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.
|
|
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",
|
package/types/availablemodel.js
CHANGED
|
@@ -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', '
|
|
51
|
-
'PLUS_PRO', 'PLUS_FLASH', '
|
|
52
|
-
'ADVANCED_PRO', 'ADVANCED_FLASH', '
|
|
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 };
|
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/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
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
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 (
|
|
108
|
-
|
|
109
|
-
|
|
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
|
-
|
|
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
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
if (
|
|
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,
|
|
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 };
|