GeminiBot commited on
Commit
e286b80
·
1 Parent(s): 5a97b06

Implement pacing (400ms start interval) to prevent DDG burst bans

Browse files
Files changed (1) hide show
  1. src/duckai.ts +35 -53
src/duckai.ts CHANGED
@@ -4,28 +4,37 @@ import { Buffer } from "node:buffer";
4
  import UserAgent from "user-agents";
5
 
6
  let activeRequests = 0;
7
- const MAX_CONCURRENT_CHATS = 5; // Максимум 5 одновременных запросов к DDG
8
  const requestQueue: (() => void)[] = [];
9
 
 
 
 
 
10
  export class DuckAI {
11
 
12
- // Метод для ожидания своей очереди
13
  private async acquireSlot(reqId: string): Promise<void> {
14
- if (activeRequests < MAX_CONCURRENT_CHATS) {
15
- activeRequests++;
16
- return;
 
17
  }
 
 
 
 
 
 
18
 
19
- console.log(`[${reqId}] [Queue] Space busy (${activeRequests} active). Waiting for slot...`);
20
- return new Promise((resolve) => {
21
- requestQueue.push(() => {
22
- activeRequests++;
23
- resolve();
24
- });
25
- });
26
  }
27
 
28
- // Освобождение слота
29
  private releaseSlot() {
30
  activeRequests--;
31
  if (requestQueue.length > 0) {
@@ -37,13 +46,10 @@ export class DuckAI {
37
  private async solveChallenge(vqdHash: string, reqId: string): Promise<string> {
38
  const start = Date.now();
39
  const memBefore = process.memoryUsage().heapUsed / 1024 / 1024;
40
-
41
- console.log(`[${reqId}] [Challenge] START. Mem: ${memBefore.toFixed(2)}MB`);
42
 
43
  try {
44
  const jsScript = Buffer.from(vqdHash, 'base64').toString('utf-8');
45
 
46
- console.log(`[${reqId}] [Challenge] Creating JSDOM...`);
47
  const dom = new JSDOM(
48
  `<iframe id="jsa" sandbox="allow-scripts allow-same-origin" srcdoc="<!DOCTYPE html>
49
  <html>
@@ -52,11 +58,7 @@ export class DuckAI {
52
  </head>
53
  <body></body>
54
  </html>" style="position: absolute; left: -9999px; top: -9999px;"></iframe>`,
55
- {
56
- runScripts: 'dangerously',
57
- resources: "usable",
58
- url: "https://duckduckgo.com/"
59
- }
60
  );
61
 
62
  const window = dom.window as any;
@@ -72,12 +74,8 @@ export class DuckAI {
72
  meta.setAttribute('content', "default-src 'none'; script-src 'unsafe-inline';");
73
  contentDoc.head.appendChild(meta);
74
 
75
- console.log(`[${reqId}] [Challenge] Running Eval...`);
76
- const evalStart = Date.now();
77
  const result = await window.eval(jsScript) as any;
78
- console.log(`[${reqId}] [Challenge] Eval finished in ${Date.now() - evalStart}ms`);
79
-
80
- if (!result) throw new Error("Challenge script returned nothing (possible JSDOM crash)");
81
 
82
  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';
83
 
@@ -90,16 +88,10 @@ export class DuckAI {
90
  })
91
  }));
92
 
93
- const duration = Date.now() - start;
94
- const memAfter = process.memoryUsage().heapUsed / 1024 / 1024;
95
- console.log(`[${reqId}] [Challenge] SUCCESS. Time: ${duration}ms. Mem Delta: +( ${(memAfter - memBefore).toFixed(2)} )MB`);
96
-
97
- // Очистка JSDOM для экономии памяти
98
  dom.window.close();
99
-
100
  return solved;
101
  } catch (e: any) {
102
- console.error(`[${reqId}] [Challenge] CRITICAL ERROR: ${e.message}\n${e.stack}`);
103
  throw e;
104
  }
105
  }
@@ -107,15 +99,10 @@ export class DuckAI {
107
  async chat(request: any): Promise<string> {
108
  const reqId = Math.random().toString(36).substring(7).toUpperCase();
109
 
110
- // Ждем свободный слот (максимум 5 параллельно)
111
  await this.acquireSlot(reqId);
112
 
113
  const startTime = Date.now();
114
- console.log(`[${reqId}] [Chat] STARTING (Active: ${activeRequests}, Queued: ${requestQueue.length})`);
115
-
116
- // Добавляем Jitter (случайную задержку 0-500мс), чтобы не долбить DDG пачкой
117
- await new Promise(r => setTimeout(resolve => r(resolve), Math.random() * 500));
118
-
119
  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';
120
  const headers = {
121
  "User-Agent": userAgent,
@@ -125,43 +112,38 @@ export class DuckAI {
125
  };
126
 
127
  try {
 
 
128
  let hashHeader = null;
129
  let statusAttempt = 0;
130
 
131
- // Ретрай для получения статуса (VQD)
132
- while (!hashHeader && statusAttempt < 3) {
133
- console.log(`[${reqId}] [Chat] Fetching status (Attempt ${statusAttempt + 1})...`);
134
  const statusRes = await fetch("https://duckduckgo.com/duckchat/v1/status?q=1", { headers });
135
  hashHeader = statusRes.headers.get("x-vqd-hash-1");
136
 
137
  if (!hashHeader) {
138
  if (statusRes.status === 429) {
139
- console.log(`[${reqId}] [Chat] Status 429 detected. Waiting...`);
140
- await new Promise(r => setTimeout(resolve => r(resolve), 1000 * (statusAttempt + 1)));
141
  } else {
142
- throw new Error(`Status ${statusRes.status}: Missing VQD hash`);
143
  }
144
  }
145
  statusAttempt++;
146
  }
147
 
148
- if (!hashHeader) throw new Error("Failed to obtain VQD hash after retries");
149
 
150
  const solvedVqd = await this.solveChallenge(hashHeader, reqId);
151
 
152
- console.log(`[${reqId}] [Chat] Sending request to DDG...`);
153
  const response = await fetch("https://duckduckgo.com/duckchat/v1/chat", {
154
  method: "POST",
155
  headers: { ...headers, "Content-Type": "application/json", "x-vqd-hash-1": solvedVqd },
156
  body: JSON.stringify(request)
157
  });
158
 
159
- console.log(`[${reqId}] [Chat] DDG response status: ${response.status}`);
160
-
161
  if (!response.ok) {
162
  const body = await response.text();
163
- console.error(`[${reqId}] [Chat] DDG ERROR BODY: ${body}`);
164
- throw new Error(`DDG Error ${response.status}: ${body.substring(0, 100)}`);
165
  }
166
 
167
  const text = await response.text();
@@ -178,7 +160,7 @@ export class DuckAI {
178
  }
179
  }
180
 
181
- console.log(`[${reqId}] [Chat] SUCCESS. Total time: ${Date.now() - startTime}ms`);
182
  return llmResponse.trim() || "Empty response";
183
 
184
  } catch (error: any) {
@@ -190,4 +172,4 @@ export class DuckAI {
190
  }
191
 
192
  getAvailableModels() { return ["gpt-4o-mini", "gpt-5-mini", "openai/gpt-oss-120b"]; }
193
- }
 
4
  import UserAgent from "user-agents";
5
 
6
  let activeRequests = 0;
7
+ const MAX_CONCURRENT_CHATS = 20; // Увеличиваем до 20 одновременных
8
  const requestQueue: (() => void)[] = [];
9
 
10
+ // Переменные для контроля темпа (Rate Limit)
11
+ let lastStartTime = 0;
12
+ const MIN_START_INTERVAL = 400; // Начинать новый запрос не чаще чем раз в 400мс (~2.5 зап/сек)
13
+
14
  export class DuckAI {
15
 
16
+ // Ожидание слота + соблюдение темпа
17
  private async acquireSlot(reqId: string): Promise<void> {
18
+ // 1. Ждем свободный слот из 20
19
+ if (activeRequests >= MAX_CONCURRENT_CHATS) {
20
+ console.log(`[${reqId}] [Queue] All slots full (${activeRequests}). Waiting...`);
21
+ await new Promise((resolve) => requestQueue.push(() => resolve()));
22
  }
23
+ activeRequests++;
24
+
25
+ // 2. Соблюдаем темп старта (чтобы не пугать DDG всплесками)
26
+ const now = Date.now();
27
+ const timeSinceLastStart = now - lastStartTime;
28
+ const wait = Math.max(0, MIN_START_INTERVAL - timeSinceLastStart);
29
 
30
+ lastStartTime = now + wait; // Бронируем время старта
31
+
32
+ if (wait > 0) {
33
+ // console.log(`[${reqId}] [Queue] Pacing: waiting ${wait}ms...`);
34
+ await new Promise(r => setTimeout(r, wait));
35
+ }
 
36
  }
37
 
 
38
  private releaseSlot() {
39
  activeRequests--;
40
  if (requestQueue.length > 0) {
 
46
  private async solveChallenge(vqdHash: string, reqId: string): Promise<string> {
47
  const start = Date.now();
48
  const memBefore = process.memoryUsage().heapUsed / 1024 / 1024;
 
 
49
 
50
  try {
51
  const jsScript = Buffer.from(vqdHash, 'base64').toString('utf-8');
52
 
 
53
  const dom = new JSDOM(
54
  `<iframe id="jsa" sandbox="allow-scripts allow-same-origin" srcdoc="<!DOCTYPE html>
55
  <html>
 
58
  </head>
59
  <body></body>
60
  </html>" style="position: absolute; left: -9999px; top: -9999px;"></iframe>`,
61
+ { runScripts: 'dangerously', resources: "usable", url: "https://duckduckgo.com/" }
 
 
 
 
62
  );
63
 
64
  const window = dom.window as any;
 
74
  meta.setAttribute('content', "default-src 'none'; script-src 'unsafe-inline';");
75
  contentDoc.head.appendChild(meta);
76
 
 
 
77
  const result = await window.eval(jsScript) as any;
78
+ if (!result) throw new Error("Challenge script returned nothing");
 
 
79
 
80
  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';
81
 
 
88
  })
89
  }));
90
 
 
 
 
 
 
91
  dom.window.close();
 
92
  return solved;
93
  } catch (e: any) {
94
+ console.error(`[${reqId}] [Challenge] ERROR: ${e.message}`);
95
  throw e;
96
  }
97
  }
 
99
  async chat(request: any): Promise<string> {
100
  const reqId = Math.random().toString(36).substring(7).toUpperCase();
101
 
102
+ // Плавный вход в систему
103
  await this.acquireSlot(reqId);
104
 
105
  const startTime = Date.now();
 
 
 
 
 
106
  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';
107
  const headers = {
108
  "User-Agent": userAgent,
 
112
  };
113
 
114
  try {
115
+ console.log(`[${reqId}] [Chat] START (Active: ${activeRequests}, Queued: ${requestQueue.length})`);
116
+
117
  let hashHeader = null;
118
  let statusAttempt = 0;
119
 
120
+ while (!hashHeader && statusAttempt < 2) {
 
 
121
  const statusRes = await fetch("https://duckduckgo.com/duckchat/v1/status?q=1", { headers });
122
  hashHeader = statusRes.headers.get("x-vqd-hash-1");
123
 
124
  if (!hashHeader) {
125
  if (statusRes.status === 429) {
126
+ await new Promise(r => setTimeout(r, 1000));
 
127
  } else {
128
+ throw new Error(`Status ${statusRes.status}: No VQD`);
129
  }
130
  }
131
  statusAttempt++;
132
  }
133
 
134
+ if (!hashHeader) throw new Error("Failed VQD after retries");
135
 
136
  const solvedVqd = await this.solveChallenge(hashHeader, reqId);
137
 
 
138
  const response = await fetch("https://duckduckgo.com/duckchat/v1/chat", {
139
  method: "POST",
140
  headers: { ...headers, "Content-Type": "application/json", "x-vqd-hash-1": solvedVqd },
141
  body: JSON.stringify(request)
142
  });
143
 
 
 
144
  if (!response.ok) {
145
  const body = await response.text();
146
+ throw new Error(`DDG Error ${response.status}: ${body.substring(0, 50)}`);
 
147
  }
148
 
149
  const text = await response.text();
 
160
  }
161
  }
162
 
163
+ console.log(`[${reqId}] [Chat] SUCCESS (${Date.now() - startTime}ms)`);
164
  return llmResponse.trim() || "Empty response";
165
 
166
  } catch (error: any) {
 
172
  }
173
 
174
  getAvailableModels() { return ["gpt-4o-mini", "gpt-5-mini", "openai/gpt-oss-120b"]; }
175
+ }