GeminiBot commited on
Commit
1ec87c7
·
1 Parent(s): df592cd

Hardcore logging: Request IDs, Memory tracking, Concurrency monitoring

Browse files
Files changed (1) hide show
  1. src/duckai.ts +104 -135
src/duckai.ts CHANGED
@@ -3,174 +3,143 @@ import { createHash } from "node:crypto";
3
  import { Buffer } from "node:buffer";
4
  import UserAgent from "user-agents";
5
 
 
 
6
  export class DuckAI {
7
 
8
- // Логика из старого рабочего кода
9
- private async solveChallenge(vqdHash: string): Promise<string> {
10
- const jsScript = Buffer.from(vqdHash, 'base64').toString('utf-8');
11
-
12
- // 1. Создаем JSDOM с правильной структурой (iframe #jsa)
13
- const dom = new JSDOM(
14
- `<iframe id="jsa" sandbox="allow-scripts allow-same-origin" srcdoc="<!DOCTYPE html>
15
- <html>
16
- <head>
17
- <meta http-equiv="Content-Security-Policy" content="default-src 'none'; script-src 'unsafe-inline'">
18
- </head>
19
- <body></body>
20
- </html>" style="position: absolute; left: -9999px; top: -9999px;"></iframe>`,
21
- {
22
- runScripts: 'dangerously',
23
- resources: "usable",
24
- url: "https://duckduckgo.com/"
25
- }
26
- );
27
-
28
- const window = dom.window as any;
29
-
30
- // Моки, которые нужны JSDOM, чтобы чувствовать себя браузером
31
- window.screen = { width: 1920, height: 1080, availWidth: 1920, availHeight: 1080 };
32
- window.chrome = { runtime: {} };
33
 
34
- // Глобальные переменные для eval
35
- window.top.__DDG_BE_VERSION__ = 1;
36
- window.top.__DDG_FE_CHAT_HASH__ = 1;
37
 
38
- // 2. Получаем доступ к iframe и вставляем CSP (как в старом коде)
39
- const jsa = window.document.querySelector('#jsa');
40
- if (!jsa) throw new Error("Iframe #jsa not found in JSDOM");
41
-
42
- const contentDoc = jsa.contentDocument || jsa.contentWindow.document;
43
- const meta = contentDoc.createElement('meta');
44
- meta.setAttribute('http-equiv', 'Content-Security-Policy');
45
- meta.setAttribute('content', "default-src 'none'; script-src 'unsafe-inline';");
46
- contentDoc.head.appendChild(meta);
 
 
 
 
 
 
 
 
 
47
 
48
- // 3. Выполняем скрипт
49
- const result = await window.eval(jsScript) as any;
 
 
 
50
 
51
- if (!result) throw new Error("Challenge script returned nothing");
 
 
 
 
 
52
 
53
- // 4. Жестко задаем User-Agent, который проходит проверку (из старого кода)
54
- // Chrome/138.0.0.0 - это фейковый будущий UA, который использует скрипт DDG
55
- result.client_hashes[0] = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.0.0 Safari/537.36';
56
-
57
- result.client_hashes = result.client_hashes.map((t: string) => {
58
- const hash = createHash('sha256');
59
- hash.update(t);
60
- return hash.digest('base64');
61
- });
62
 
63
- return btoa(JSON.stringify(result));
64
- }
65
 
66
- getAvailableModels(): string[] {
67
- return [
68
- "gpt-4o-mini",
69
- "gpt-5-mini",
70
- "openai/gpt-oss-120b"
71
- ];
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
72
  }
73
 
74
  async chat(request: any): Promise<string> {
75
- // Используем тот же UA для запросов, что и в хеше, для консистентности
76
- const userAgent = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.0.0 Safari/537.36';
 
77
 
78
- const headers: any = {
 
 
 
79
  "User-Agent": userAgent,
80
  "Accept": "text/event-stream",
81
  "x-vqd-accept": "1",
82
- "accept-language": "en-US,en;q=0.9",
83
- "cache-control": "no-cache",
84
- "pragma": "no-cache",
85
- "sec-fetch-dest": "empty",
86
- "sec-fetch-mode": "cors",
87
- "sec-fetch-site": "same-origin",
88
- // Важный хедер из старого кода!
89
  "x-fe-version": "serp_20250401_100419_ET-19d438eb199b2bf7c300"
90
  };
91
 
92
  try {
93
- console.log("[DuckAI] Fetching status...");
94
  const statusRes = await fetch("https://duckduckgo.com/duckchat/v1/status?q=1", { headers });
95
- console.log(`[DuckAI] Status response: ${statusRes.status}`);
96
-
97
  const hashHeader = statusRes.headers.get("x-vqd-hash-1");
98
 
99
- if (!hashHeader) {
100
- // Иногда DDG не выдает challenge, а сразу дает x-vqd-4 (редко, но бывает)
101
- const vqd4 = statusRes.headers.get("x-vqd-4");
102
- if (vqd4) {
103
- console.log("[DuckAI] No challenge, got x-vqd-4 directly.");
104
- return await this.sendChatRequest(request, vqd4, headers);
105
- }
106
- throw new Error("Missing x-vqd-hash-1 and x-vqd-4");
107
- }
108
 
109
- console.log("[DuckAI] Solving challenge (Old Method)...");
110
- const solvedVqd = await this.solveChallenge(hashHeader);
111
- console.log("[DuckAI] Challenge solved.");
112
 
113
- // Отправляем запрос с решенной капчей
114
  const response = await fetch("https://duckduckgo.com/duckchat/v1/chat", {
115
  method: "POST",
116
- headers: {
117
- ...headers,
118
- "Content-Type": "application/json",
119
- "x-vqd-hash-1": solvedVqd
120
- },
121
  body: JSON.stringify(request)
122
  });
123
 
124
- return await this.processResponse(response);
125
 
126
- } catch (error: any) {
127
- console.error(`[DuckAI] Error: ${error.message}`);
128
- throw error; // Пробрасываем ошибку, чтобы сервер вернул 500 JSON, а не упал
129
- }
130
- }
131
-
132
- // Вспомогательный метод для обработки ответа (чтобы не дублировать код)
133
- private async processResponse(response: Response): Promise<string> {
134
- console.log(`[DuckAI] Chat response status: ${response.status}`);
135
 
136
- if (!response.ok) {
137
- const errorText = await response.text();
138
- console.log(`[DuckAI] Error body: ${errorText}`);
139
- if (response.status === 429) {
140
- throw new Error("Rate limit exceeded (429)");
 
 
 
 
 
 
141
  }
142
- throw new Error(`DuckDuckGo API Error (${response.status}): ${errorText.substring(0, 100)}`);
143
- }
144
-
145
- const text = await response.text();
146
- let llmResponse = "";
147
- const lines = text.split("\n");
148
- for (const line of lines) {
149
- if (line.startsWith("data: ")) {
150
- try {
151
- const chunk = line.slice(6);
152
- if (chunk === "[DONE]") break;
153
- const json = JSON.parse(chunk);
154
- if (json.message) llmResponse += json.message;
155
- } catch (e) {}
156
  }
157
- }
158
-
159
- if (!llmResponse) throw new Error("Empty response from DuckDuckGo");
160
- return llmResponse.trim();
161
- }
162
 
163
- // Метод для отправки запроса, если вдруг капча не нужна (на будущее)
164
- private async sendChatRequest(request: any, vqd4: string, headers: any): Promise<string> {
165
- const response = await fetch("https://duckduckgo.com/duckchat/v1/chat", {
166
- method: "POST",
167
- headers: {
168
- ...headers,
169
- "Content-Type": "application/json",
170
- "x-vqd-4": vqd4
171
- },
172
- body: JSON.stringify(request)
173
- });
174
- return await this.processResponse(response);
175
  }
176
- }
 
 
 
3
  import { Buffer } from "node:buffer";
4
  import UserAgent from "user-agents";
5
 
6
+ let activeRequests = 0;
7
+
8
  export class DuckAI {
9
 
10
+ private async solveChallenge(vqdHash: string, reqId: string): Promise<string> {
11
+ const start = Date.now();
12
+ const memBefore = process.memoryUsage().heapUsed / 1024 / 1024;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
13
 
14
+ console.log(`[${reqId}] [Challenge] START. Mem: ${memBefore.toFixed(2)}MB`);
 
 
15
 
16
+ try {
17
+ const jsScript = Buffer.from(vqdHash, 'base64').toString('utf-8');
18
+
19
+ console.log(`[${reqId}] [Challenge] Creating JSDOM...`);
20
+ const dom = new JSDOM(
21
+ `<iframe id="jsa" sandbox="allow-scripts allow-same-origin" srcdoc="<!DOCTYPE html>
22
+ <html>
23
+ <head>
24
+ <meta http-equiv="Content-Security-Policy" content="default-src 'none'; script-src 'unsafe-inline'">
25
+ </head>
26
+ <body></body>
27
+ </html>" style="position: absolute; left: -9999px; top: -9999px;"></iframe>`,
28
+ {
29
+ runScripts: 'dangerously',
30
+ resources: "usable",
31
+ url: "https://duckduckgo.com/"
32
+ }
33
+ );
34
 
35
+ const window = dom.window as any;
36
+ window.screen = { width: 1920, height: 1080, availWidth: 1920, availHeight: 1080 };
37
+ window.chrome = { runtime: {} };
38
+ window.top.__DDG_BE_VERSION__ = 1;
39
+ window.top.__DDG_FE_CHAT_HASH__ = 1;
40
 
41
+ const jsa = window.document.querySelector('#jsa');
42
+ const contentDoc = jsa.contentDocument || jsa.contentWindow.document;
43
+ const meta = contentDoc.createElement('meta');
44
+ meta.setAttribute('http-equiv', 'Content-Security-Policy');
45
+ meta.setAttribute('content', "default-src 'none'; script-src 'unsafe-inline';");
46
+ contentDoc.head.appendChild(meta);
47
 
48
+ console.log(`[${reqId}] [Challenge] Running Eval...`);
49
+ const evalStart = Date.now();
50
+ const result = await window.eval(jsScript) as any;
51
+ console.log(`[${reqId}] [Challenge] Eval finished in ${Date.now() - evalStart}ms`);
 
 
 
 
 
52
 
53
+ if (!result) throw new Error("Challenge script returned nothing (possible JSDOM crash)");
 
54
 
55
+ result.client_hashes[0] = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.0.0 Safari/537.36';
56
+
57
+ const solved = btoa(JSON.stringify({
58
+ ...result,
59
+ client_hashes: result.client_hashes.map((t: string) => {
60
+ const hash = createHash('sha256');
61
+ hash.update(t);
62
+ return hash.digest('base64');
63
+ })
64
+ }));
65
+
66
+ const duration = Date.now() - start;
67
+ const memAfter = process.memoryUsage().heapUsed / 1024 / 1024;
68
+ console.log(`[${reqId}] [Challenge] SUCCESS. Time: ${duration}ms. Mem Delta: +( ${(memAfter - memBefore).toFixed(2)} )MB`);
69
+
70
+ // Очистка JSDOM для экономии памяти
71
+ dom.window.close();
72
+
73
+ return solved;
74
+ } catch (e: any) {
75
+ console.error(`[${reqId}] [Challenge] CRITICAL ERROR: ${e.message}\n${e.stack}`);
76
+ throw e;
77
+ }
78
  }
79
 
80
  async chat(request: any): Promise<string> {
81
+ const reqId = Math.random().toString(36).substring(7).toUpperCase();
82
+ activeRequests++;
83
+ const startTime = Date.now();
84
 
85
+ console.log(`[${reqId}] [Chat] NEW REQUEST. Parallel active: ${activeRequests}`);
86
+
87
+ const userAgent = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.0.0 Safari/537.36';
88
+ const headers = {
89
  "User-Agent": userAgent,
90
  "Accept": "text/event-stream",
91
  "x-vqd-accept": "1",
 
 
 
 
 
 
 
92
  "x-fe-version": "serp_20250401_100419_ET-19d438eb199b2bf7c300"
93
  };
94
 
95
  try {
96
+ console.log(`[${reqId}] [Chat] Fetching status...`);
97
  const statusRes = await fetch("https://duckduckgo.com/duckchat/v1/status?q=1", { headers });
 
 
98
  const hashHeader = statusRes.headers.get("x-vqd-hash-1");
99
 
100
+ if (!hashHeader) throw new Error(`Status ${statusRes.status}: Missing VQD hash`);
 
 
 
 
 
 
 
 
101
 
102
+ const solvedVqd = await this.solveChallenge(hashHeader, reqId);
 
 
103
 
104
+ console.log(`[${reqId}] [Chat] Sending request to DDG...`);
105
  const response = await fetch("https://duckduckgo.com/duckchat/v1/chat", {
106
  method: "POST",
107
+ headers: { ...headers, "Content-Type": "application/json", "x-vqd-hash-1": solvedVqd },
 
 
 
 
108
  body: JSON.stringify(request)
109
  });
110
 
111
+ console.log(`[${reqId}] [Chat] DDG response status: ${response.status}`);
112
 
113
+ if (!response.ok) {
114
+ const body = await response.text();
115
+ console.error(`[${reqId}] [Chat] DDG ERROR BODY: ${body}`);
116
+ throw new Error(`DDG Error ${response.status}: ${body.substring(0, 100)}`);
117
+ }
 
 
 
 
118
 
119
+ const text = await response.text();
120
+ let llmResponse = "";
121
+ const lines = text.split("\n");
122
+ for (const line of lines) {
123
+ if (line.startsWith("data: ")) {
124
+ try {
125
+ const chunk = line.slice(6);
126
+ if (chunk === "[DONE]") break;
127
+ const json = JSON.parse(chunk);
128
+ if (json.message) llmResponse += json.message;
129
+ } catch (e) {}
130
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
131
  }
132
+
133
+ console.log(`[${reqId}] [Chat] SUCCESS. Total time: ${Date.now() - startTime}ms`);
134
+ return llmResponse.trim() || "Empty response";
 
 
135
 
136
+ } catch (error: any) {
137
+ console.error(`[${reqId}] [Chat] FAILED: ${error.message}`);
138
+ throw error;
139
+ } finally {
140
+ activeRequests--;
141
+ }
 
 
 
 
 
 
142
  }
143
+
144
+ getAvailableModels() { return ["gpt-4o-mini", "gpt-5-mini", "openai/gpt-oss-120b"]; }
145
+ }