gemini-reverse 1.0.1 → 1.0.3

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.
@@ -24,8 +24,12 @@ function parseCookies(headers, base = {}) {
24
24
 
25
25
  function parseProxy(str) {
26
26
  if (!str) return undefined;
27
- try { const u = new URL(str); return { protocol: u.protocol.replace(':', ''), host: u.hostname, port: parseInt(u.port) }; }
28
- catch { return undefined; }
27
+ try {
28
+ const u = new URL(str);
29
+ return { protocol: u.protocol.replace(':', ''), host: u.hostname, port: parseInt(u.port) };
30
+ } catch {
31
+ return undefined;
32
+ }
29
33
  }
30
34
 
31
35
  function cacheDir() {
@@ -44,8 +48,10 @@ async function sendInitRequest(cookies, proxy = null) {
44
48
  const snlm0e = (t.match(/"SNlM0e":\s*"(.*?)"/) || [])[1] || null;
45
49
  const cfb2h = (t.match(/"cfb2h":\s*"(.*?)"/) || [])[1] || null;
46
50
  const fdrfje = (t.match(/"FdrFJe":\s*"(.*?)"/) || [])[1] || null;
47
- if (!snlm0e && !cfb2h && !fdrfje) throw new AuthError('Cookies invalid.');
48
- return [snlm0e, cfb2h, fdrfje, parseCookies(res.headers, cookies)];
51
+ const language = (t.match(/"TuX5cc":\s*"(.*?)"/) || [])[1] || null;
52
+ const pushId = (t.match(/"qKIAYe":\s*"(.*?)"/) || [])[1] || null;
53
+ if (!snlm0e && !cfb2h && !fdrfje && !language && !pushId) throw new AuthError('Cookies invalid.');
54
+ return [snlm0e, cfb2h, fdrfje, language, pushId, parseCookies(res.headers, cookies)];
49
55
  }
50
56
 
51
57
  async function getAccessToken(baseCookies, proxy = null, verbose = false) {
@@ -59,8 +65,9 @@ async function getAccessToken(baseCookies, proxy = null, verbose = false) {
59
65
  const jar = (base, extra = {}) => ({ ...extraCookies, ...base, ...extra });
60
66
  const dir = cacheDir();
61
67
 
62
- if (baseCookies['__Secure-1PSID'])
68
+ if (baseCookies['__Secure-1PSID']) {
63
69
  tasks.push(sendInitRequest(jar(baseCookies), proxy));
70
+ }
64
71
 
65
72
  const psid = baseCookies['__Secure-1PSID'];
66
73
  if (psid) {
@@ -84,8 +91,9 @@ async function getAccessToken(baseCookies, proxy = null, verbose = false) {
84
91
  if (validCaches === 0 && verbose) console.debug('Skipping cached cookies. No valid caches found.');
85
92
  }
86
93
 
87
- if (!tasks.length)
94
+ if (!tasks.length) {
88
95
  throw new AuthError('No valid cookies available. Please pass __Secure-1PSID and optionally __Secure-1PSIDTS.');
96
+ }
89
97
 
90
98
  let lastErr;
91
99
  for (let i = 0; i < tasks.length; i++) {
@@ -102,4 +110,4 @@ async function getAccessToken(baseCookies, proxy = null, verbose = false) {
102
110
  throw new AuthError(`Failed to initialize client. (Failed attempts: ${tasks.length})`);
103
111
  }
104
112
 
105
- module.exports = { getAccessToken, sendInitRequest, cookieStr, parseCookies, parseProxy, cacheDir };
113
+ module.exports = { getAccessToken, sendInitRequest, cookieStr, parseCookies, parseProxy, cacheDir };
package/utils/index.js CHANGED
@@ -1,9 +1,20 @@
1
1
  'use strict';
2
2
 
3
3
  const { getAccessToken, sendInitRequest, cookieStr, parseCookies, parseProxy, cacheDir } = require('./accessToken');
4
- const { getDeltaByFpLen, getCleanText, getFpLen, getNestedValue, parseResponseByFrame, extractJsonFromResponse } = require('./parsing');
4
+ const { getDeltaByFpLen, getCleanText, getNestedValue, parseResponseByFrame, extractJsonFromResponse } = require('./parsing');
5
5
  const { rotate1psidts } = require('./rotate');
6
6
  const { uploadFile, parseFileName, generateRandomName } = require('./upload');
7
+ const {
8
+ iterNested,
9
+ findFirstMatch,
10
+ findFirstString,
11
+ extractResearchId,
12
+ extractChatId,
13
+ collectResearchNotes,
14
+ findFirstDictKey,
15
+ extractDeepResearchPlan,
16
+ extractDeepResearchStatusPayload,
17
+ } = require('./research');
7
18
 
8
19
  module.exports = {
9
20
  getAccessToken,
@@ -14,7 +25,6 @@ module.exports = {
14
25
  cacheDir,
15
26
  getDeltaByFpLen,
16
27
  getCleanText,
17
- getFpLen,
18
28
  getNestedValue,
19
29
  parseResponseByFrame,
20
30
  extractJsonFromResponse,
@@ -22,4 +32,13 @@ module.exports = {
22
32
  uploadFile,
23
33
  parseFileName,
24
34
  generateRandomName,
25
- };
35
+ iterNested,
36
+ findFirstMatch,
37
+ findFirstString,
38
+ extractResearchId,
39
+ extractChatId,
40
+ collectResearchNotes,
41
+ findFirstDictKey,
42
+ extractDeepResearchPlan,
43
+ extractDeepResearchStatusPayload,
44
+ };
package/utils/parsing.js CHANGED
@@ -1,7 +1,5 @@
1
1
  'use strict';
2
2
 
3
- const VOLATILE_RE = /[\s!"#$%&'()*+,\-./:;<=>?@[\\\]^_`{|}~]/g;
4
- const VOLATILE_SET = new Set(' \t\n\r\x0b\x0c!"#$%&\'()*+,-./:;<=>?@[\\]^_`{|}~');
5
3
  const FLICKER_ESC_RE = /\\+[`*_~].*$/;
6
4
 
7
5
  function getCleanText(s) {
@@ -10,47 +8,71 @@ function getCleanText(s) {
10
8
  return s.replace(FLICKER_ESC_RE, '');
11
9
  }
12
10
 
13
- function getFpLen(s) {
14
- return s.replace(VOLATILE_RE, '').length;
15
- }
11
+ function longestCommonSubsequenceBlocks(a, b) {
12
+ const blocks = [];
13
+ if (!a.length || !b.length) return blocks;
16
14
 
17
- function getDeltaByFpLen(newRaw, lastSentClean, isFinal) {
18
- const newC = isFinal ? newRaw : getCleanText(newRaw);
19
- if (newC.startsWith(lastSentClean)) return [newC.slice(lastSentClean.length), newC];
15
+ const m = a.length, n = b.length;
16
+ const dp = Array.from({ length: m + 1 }, () => new Int32Array(n + 1));
20
17
 
21
- const targetFpLen = getFpLen(lastSentClean);
22
- let pLow = 0;
23
-
24
- if (targetFpLen > 0) {
25
- let cur = 0, found = false;
26
- for (let i = 0; i < newC.length; i++) {
27
- if (!VOLATILE_SET.has(newC[i])) cur++;
28
- if (cur === targetFpLen) { pLow = i + 1; found = true; break; }
18
+ for (let i = m - 1; i >= 0; i--) {
19
+ for (let j = n - 1; j >= 0; j--) {
20
+ if (a[i] === b[j]) {
21
+ dp[i][j] = dp[i + 1][j + 1] + 1;
22
+ }
29
23
  }
30
- if (!found) {
31
- let n = 0;
32
- for (let i = 0; i < Math.min(lastSentClean.length, newC.length); i++) {
33
- if (lastSentClean[i] === newC[i]) n++; else break;
24
+ }
25
+
26
+ let i = 0, j = 0;
27
+ while (i < m && j < n) {
28
+ if (a[i] === b[j]) {
29
+ let size = 0;
30
+ const ai = i, bj = j;
31
+ while (i < m && j < n && a[i] === b[j]) { i++; j++; size++; }
32
+ if (size > 0) blocks.push({ a: ai, b: bj, size });
33
+ } else {
34
+ let bestLen = 0, bestI = i + 1, bestJ = j + 1;
35
+ for (let ni = i; ni < Math.min(i + 50, m); ni++) {
36
+ for (let nj = j; nj < Math.min(j + 50, n); nj++) {
37
+ if (dp[ni][nj] > bestLen) { bestLen = dp[ni][nj]; bestI = ni; bestJ = nj; }
38
+ }
34
39
  }
35
- return [newC.slice(n), newC];
40
+ i = bestI; j = bestJ;
36
41
  }
37
42
  }
43
+ return blocks;
44
+ }
45
+
46
+ function getDeltaByFpLen(newRaw, lastSentClean, isFinal) {
47
+ const newC = isFinal ? newRaw : getCleanText(newRaw);
48
+
49
+ if (newC.startsWith(lastSentClean)) {
50
+ return [newC.slice(lastSentClean.length), newC];
51
+ }
38
52
 
39
- let lastIdx = -1;
40
- for (let i = lastSentClean.length - 1; i >= 0; i--) {
41
- if (!VOLATILE_SET.has(lastSentClean[i])) { lastIdx = i; break; }
53
+ const searchLen = Math.min(3000, Math.max(1000, lastSentClean.length));
54
+ const actualLen = Math.min(searchLen, lastSentClean.length, newC.length);
55
+
56
+ if (actualLen === 0) return [newC, newC];
57
+
58
+ const tailLast = lastSentClean.slice(-actualLen);
59
+ const tailNew = newC.slice(-actualLen);
60
+
61
+ const blocks = longestCommonSubsequenceBlocks(tailLast, tailNew);
62
+ if (blocks.length > 0) {
63
+ const lastMatch = blocks[blocks.length - 1];
64
+ const matchEnd = lastMatch.b + lastMatch.size;
65
+ return [tailNew.slice(matchEnd), newC];
42
66
  }
43
- const suffix = lastSentClean.slice(lastIdx + 1);
44
67
 
45
- let i = 0, j = 0;
46
- while (i < suffix.length && (pLow + j) < newC.length) {
47
- const cs = suffix[i], cn = newC[pLow + j];
48
- if (cs === cn) { i++; j++; }
49
- else if (cn === '\\' && (pLow + j + 1) < newC.length && newC[pLow + j + 1] === cs) { j += 2; i++; }
50
- else if (cs === '\\' && (i + 1) < suffix.length && suffix[i + 1] === cn) { i += 2; j++; }
51
- else break;
68
+ const blocksAll = longestCommonSubsequenceBlocks(lastSentClean, newC);
69
+ if (blocksAll.length > 0) {
70
+ const lastMatch = blocksAll[blocksAll.length - 1];
71
+ const matchEnd = lastMatch.b + lastMatch.size;
72
+ return [newC.slice(matchEnd), newC];
52
73
  }
53
- return [newC.slice(pLow + j), newC];
74
+
75
+ return [newC, newC];
54
76
  }
55
77
 
56
78
  function getNestedValue(data, path, defaultVal = null) {
@@ -83,7 +105,9 @@ function parseResponseByFrame(content) {
83
105
  const cp = content.codePointAt(idx);
84
106
  const u = cp > 0xFFFF ? 2 : 1;
85
107
  if (units + u > len) break;
86
- units += u; chars += cp > 0xFFFF ? 2 : 1; idx += cp > 0xFFFF ? 2 : 1;
108
+ units += u;
109
+ chars += cp > 0xFFFF ? 2 : 1;
110
+ idx += cp > 0xFFFF ? 2 : 1;
87
111
  }
88
112
  if (units < len) break;
89
113
  const end = start + chars;
@@ -92,7 +116,8 @@ function parseResponseByFrame(content) {
92
116
  if (!chunk) continue;
93
117
  try {
94
118
  const parsed = JSON.parse(chunk);
95
- if (Array.isArray(parsed)) frames.push(...parsed); else frames.push(parsed);
119
+ if (Array.isArray(parsed)) frames.push(...parsed);
120
+ else frames.push(parsed);
96
121
  } catch {}
97
122
  }
98
123
  return [frames, content.slice(pos)];
@@ -104,16 +129,20 @@ function extractJsonFromResponse(text) {
104
129
  c = c.trimStart();
105
130
  const [r] = parseResponseByFrame(c);
106
131
  if (r.length) return r;
107
- try { const p = JSON.parse(c.trim()); return Array.isArray(p) ? p : [p]; } catch {}
132
+ try {
133
+ const p = JSON.parse(c.trim());
134
+ return Array.isArray(p) ? p : [p];
135
+ } catch {}
108
136
  const lines = [];
109
137
  for (const line of c.trim().split('\n')) {
110
138
  try {
111
139
  const p = JSON.parse(line.trim());
112
- if (Array.isArray(p)) lines.push(...p); else if (p && typeof p === 'object') lines.push(p);
140
+ if (Array.isArray(p)) lines.push(...p);
141
+ else if (p && typeof p === 'object') lines.push(p);
113
142
  } catch {}
114
143
  }
115
144
  if (lines.length) return lines;
116
145
  throw new Error('Could not find valid JSON in response.');
117
146
  }
118
147
 
119
- module.exports = { getCleanText, getFpLen, getDeltaByFpLen, getNestedValue, parseResponseByFrame, extractJsonFromResponse };
148
+ module.exports = { getCleanText, getDeltaByFpLen, getNestedValue, parseResponseByFrame, extractJsonFromResponse };
@@ -0,0 +1,175 @@
1
+ 'use strict';
2
+
3
+ const { getNestedValue } = require('./parsing');
4
+
5
+ const _RESEARCH_ID_RE = /\b[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}\b/i;
6
+ const _CHAT_ID_RE = /\bc_[A-Za-z0-9_]+\b/;
7
+ const _URL_RE = /^https?:\/\//;
8
+
9
+ function* iterNested(data) {
10
+ yield data;
11
+ if (Array.isArray(data)) {
12
+ for (const item of data) yield* iterNested(item);
13
+ } else if (data && typeof data === 'object') {
14
+ for (const item of Object.values(data)) yield* iterNested(item);
15
+ }
16
+ }
17
+
18
+ function findFirstMatch(data, pattern) {
19
+ for (const item of iterNested(data)) {
20
+ if (typeof item === 'string') {
21
+ const match = pattern.exec(item);
22
+ if (match) return match[0];
23
+ }
24
+ }
25
+ return null;
26
+ }
27
+
28
+ function findFirstString(data, exclude = new Set()) {
29
+ for (const item of iterNested(data)) {
30
+ if (typeof item === 'string' && item && !exclude.has(item)) return item;
31
+ }
32
+ return null;
33
+ }
34
+
35
+ function extractResearchId(data) {
36
+ return findFirstMatch(data, _RESEARCH_ID_RE);
37
+ }
38
+
39
+ function extractChatId(data) {
40
+ return findFirstMatch(data, _CHAT_ID_RE);
41
+ }
42
+
43
+ function collectResearchNotes(data, exclude = new Set()) {
44
+ const notes = [];
45
+ const seen = new Set();
46
+ for (const item of iterNested(data)) {
47
+ if (typeof item !== 'string') continue;
48
+ const text = item.trim();
49
+ if (!text || exclude.has(text) || seen.has(text) || _URL_RE.test(text) || text.length < 12) continue;
50
+ seen.add(text);
51
+ notes.push(text);
52
+ if (notes.length >= 12) break;
53
+ }
54
+ return notes;
55
+ }
56
+
57
+ function findFirstDictKey(data, key) {
58
+ for (const item of iterNested(data)) {
59
+ if (item && typeof item === 'object' && !Array.isArray(item) && key in item) return item;
60
+ }
61
+ return null;
62
+ }
63
+
64
+ function extractDeepResearchPlan(candidateData, fallbackText = '') {
65
+ let metaDict = null;
66
+ let payload = null;
67
+
68
+ for (const key of ['56', '57']) {
69
+ metaDict = findFirstDictKey(candidateData, key);
70
+ if (metaDict && Array.isArray(metaDict[key])) {
71
+ payload = metaDict[key];
72
+ break;
73
+ }
74
+ }
75
+
76
+ if (!metaDict || !payload) return null;
77
+
78
+ const researchId = extractResearchId(candidateData);
79
+ const title = getNestedValue(payload, [0]);
80
+ const stepsPayload = getNestedValue(payload, [1], []);
81
+ const steps = [];
82
+ if (Array.isArray(stepsPayload)) {
83
+ for (const step of stepsPayload) {
84
+ if (Array.isArray(step)) {
85
+ const label = step.length > 1 && typeof step[1] === 'string' ? step[1] : null;
86
+ const body = step.length > 2 && typeof step[2] === 'string' ? step[2] : null;
87
+ if (label && body) steps.push(`${label}: ${body}`);
88
+ else if (body) steps.push(body);
89
+ else if (label) steps.push(label);
90
+ }
91
+ }
92
+ }
93
+
94
+ const modifyPayload = getNestedValue(payload, [5]);
95
+ let modifyPrompt = null;
96
+ if (Array.isArray(modifyPayload)) modifyPrompt = findFirstString(modifyPayload);
97
+
98
+ const queryVal = getNestedValue(payload, [1, 0, 2]);
99
+ const query = typeof queryVal === 'string' ? queryVal : null;
100
+ const etaVal = getNestedValue(payload, [2]);
101
+ const eta_text = typeof etaVal === 'string' ? etaVal : null;
102
+ const confirmVal = getNestedValue(payload, [3, 0]);
103
+ const confirm_prompt = typeof confirmVal === 'string' ? confirmVal : null;
104
+ const confirmUrlVal = getNestedValue(payload, [4, 0]);
105
+ const confirmation_url = typeof confirmUrlVal === 'string' ? confirmUrlVal : null;
106
+ const rawState70 = metaDict['70'];
107
+ const raw_state = typeof rawState70 === 'number' ? rawState70 : null;
108
+
109
+ if (!title && !query && !steps.length && !eta_text && !confirm_prompt && !confirmation_url && !modifyPrompt) {
110
+ return null;
111
+ }
112
+
113
+ return {
114
+ research_id: researchId,
115
+ title: typeof title === 'string' ? title : null,
116
+ query,
117
+ steps,
118
+ eta_text,
119
+ confirm_prompt,
120
+ confirmation_url,
121
+ modify_prompt: modifyPrompt,
122
+ raw_state,
123
+ response_text: fallbackText || null,
124
+ };
125
+ }
126
+
127
+ function extractDeepResearchStatusPayload(payload) {
128
+ const data = Array.isArray(payload) && payload.length > 0 && Array.isArray(payload[0]) ? payload[0] : payload;
129
+ const researchId = extractResearchId(data);
130
+ if (!researchId) return null;
131
+
132
+ const title = getNestedValue(data, [1, 4, 0]);
133
+ const query = getNestedValue(data, [1, 4, 1]);
134
+ const cid = getNestedValue(data, [1, 3, 0]) || extractChatId(data);
135
+
136
+ let raw_state = null;
137
+ const metaDict = findFirstDictKey(data, '70');
138
+ if (metaDict && typeof metaDict['70'] === 'number') raw_state = metaDict['70'];
139
+
140
+ const markerStrings = [];
141
+ for (const item of iterNested(data)) {
142
+ if (typeof item === 'string' && item) markerStrings.push(item);
143
+ }
144
+
145
+ const done = markerStrings.some(s => s.includes('immersive_entry_chip'));
146
+ const awaitingConfirmation = markerStrings.some(s => s.includes('deep_research_confirmation_content'));
147
+ const state = done ? 'completed' : awaitingConfirmation ? 'awaiting_confirmation' : 'running';
148
+
149
+ const exclude = new Set([title, query, researchId, cid].filter(s => typeof s === 'string'));
150
+ const notes = collectResearchNotes(data, exclude);
151
+
152
+ return {
153
+ research_id: researchId,
154
+ state,
155
+ title: typeof title === 'string' ? title : null,
156
+ query: typeof query === 'string' ? query : null,
157
+ cid: typeof cid === 'string' ? cid : null,
158
+ notes,
159
+ done,
160
+ raw_state,
161
+ raw: payload,
162
+ };
163
+ }
164
+
165
+ module.exports = {
166
+ iterNested,
167
+ findFirstMatch,
168
+ findFirstString,
169
+ extractResearchId,
170
+ extractChatId,
171
+ collectResearchNotes,
172
+ findFirstDictKey,
173
+ extractDeepResearchPlan,
174
+ extractDeepResearchStatusPayload,
175
+ };