amirkabiri commited on
Commit
a9a72e1
·
unverified ·
2 Parent(s): 578e0c9 c4004bc

Merge pull request #4 from Brioch/main

Browse files

fix(api): fix error: Missing VQD headers: vqd=false, hash=true

Files changed (2) hide show
  1. README.md +5 -4
  2. src/duckai.ts +50 -54
README.md CHANGED
@@ -72,10 +72,11 @@ DuckAI OpenAI Server bridges the gap between DuckDuckGo's free AI chat service a
72
  ### Supported Models
73
 
74
  - `gpt-4o-mini` (Default)
75
- - `o3-mini`
76
- - `claude-3-haiku-20240307`
77
- - `meta-llama/Llama-3.3-70B-Instruct-Turbo`
78
- - `mistralai/Mixtral-8x7B-Instruct-v0.1`
 
79
 
80
  ### Features
81
 
 
72
  ### Supported Models
73
 
74
  - `gpt-4o-mini` (Default)
75
+ - `gpt-5-mini`
76
+ - `claude-3-5-haiku-latest`
77
+ - `meta-llama/Llama-4-Scout-17B-16E-Instruct`
78
+ - `mistralai/Mistral-Small-24B-Instruct-2501`
79
+ - `openai/gpt-oss-120b`
80
 
81
  ### Features
82
 
src/duckai.ts CHANGED
@@ -7,6 +7,8 @@ import type {
7
  VQDResponse,
8
  DuckAIRequest,
9
  } from "./types";
 
 
10
 
11
  // Rate limiting tracking with sliding window
12
  interface RateLimitInfo {
@@ -180,6 +182,44 @@ export class DuckAI {
180
  }
181
  }
182
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
183
  private async getVQD(userAgent: string): Promise<VQDResponse> {
184
  const response = await fetch("https://duckduckgo.com/duckchat/v1/status", {
185
  headers: {
@@ -207,23 +247,17 @@ export class DuckAI {
207
  );
208
  }
209
 
210
- const vqd = response.headers.get("x-Vqd-4");
211
  const hashHeader = response.headers.get("x-Vqd-hash-1");
212
 
213
- if (!vqd || !hashHeader) {
214
  throw new Error(
215
- `Missing VQD headers: vqd=${!!vqd}, hash=${!!hashHeader}`
216
  );
217
  }
218
 
219
- let hash: string;
220
- try {
221
- hash = atob(hashHeader);
222
- } catch (e) {
223
- throw new Error(`Failed to decode VQD hash: ${e}`);
224
- }
225
 
226
- return { vqd, hash };
227
  }
228
 
229
  private async hashClientHashes(clientHashes: string[]): Promise<string[]> {
@@ -247,18 +281,6 @@ export class DuckAI {
247
  const userAgent = new UserAgent().toString();
248
  const vqd = await this.getVQD(userAgent);
249
 
250
- const { window } = new JSDOM(
251
- `<html><body><script>window.hash = ${vqd.hash}</script></body></html>`,
252
- { runScripts: "dangerously" }
253
- );
254
- const hash = (window as any).hash;
255
-
256
- if (!hash || !hash.client_hashes || !Array.isArray(hash.client_hashes)) {
257
- throw new Error(`Invalid hash structure: ${JSON.stringify(hash)}`);
258
- }
259
-
260
- const clientHashes = await this.hashClientHashes(hash.client_hashes);
261
-
262
  // Update rate limit tracking BEFORE making the request
263
  const now = Date.now();
264
  this.rateLimitInfo.requestTimestamps.push(now);
@@ -280,15 +302,8 @@ export class DuckAI {
280
  "sec-fetch-mode": "cors",
281
  "sec-fetch-site": "same-origin",
282
  "x-fe-version": "serp_20250401_100419_ET-19d438eb199b2bf7c300",
283
- "x-vqd-4": vqd.vqd,
284
  "User-Agent": userAgent,
285
- "x-vqd-hash-1": btoa(
286
- JSON.stringify({
287
- server_hashes: hash.server_hashes,
288
- client_hashes: clientHashes,
289
- signals: hash.signal,
290
- })
291
- ),
292
  },
293
  referrer: "https://duckduckgo.com/",
294
  referrerPolicy: "origin",
@@ -357,21 +372,8 @@ export class DuckAI {
357
  await this.waitIfNeeded();
358
 
359
  const userAgent = new UserAgent().toString();
360
-
361
  const vqd = await this.getVQD(userAgent);
362
 
363
- const { window } = new JSDOM(
364
- `<html><body><script>window.hash = ${vqd.hash}</script></body></html>`,
365
- { runScripts: "dangerously" }
366
- );
367
- const hash = (window as any).hash;
368
-
369
- if (!hash || !hash.client_hashes || !Array.isArray(hash.client_hashes)) {
370
- throw new Error(`Invalid hash structure: ${JSON.stringify(hash)}`);
371
- }
372
-
373
- const clientHashes = await this.hashClientHashes(hash.client_hashes);
374
-
375
  // Update rate limit tracking BEFORE making the request
376
  const now = Date.now();
377
  this.rateLimitInfo.requestTimestamps.push(now);
@@ -393,15 +395,8 @@ export class DuckAI {
393
  "sec-fetch-mode": "cors",
394
  "sec-fetch-site": "same-origin",
395
  "x-fe-version": "serp_20250401_100419_ET-19d438eb199b2bf7c300",
396
- "x-vqd-4": vqd.vqd,
397
  "User-Agent": userAgent,
398
- "x-vqd-hash-1": btoa(
399
- JSON.stringify({
400
- server_hashes: hash.server_hashes,
401
- client_hashes: clientHashes,
402
- signals: hash.signal,
403
- })
404
- ),
405
  },
406
  referrer: "https://duckduckgo.com/",
407
  referrerPolicy: "origin",
@@ -470,10 +465,11 @@ export class DuckAI {
470
  getAvailableModels(): string[] {
471
  return [
472
  "gpt-4o-mini",
473
- "o3-mini",
474
- "claude-3-haiku-20240307",
475
- "meta-llama/Llama-3.3-70B-Instruct-Turbo",
476
  "mistralai/Mistral-Small-24B-Instruct-2501",
 
477
  ];
478
  }
479
  }
 
7
  VQDResponse,
8
  DuckAIRequest,
9
  } from "./types";
10
+ import { createHash } from "node:crypto";
11
+ import { Buffer } from "node:buffer";
12
 
13
  // Rate limiting tracking with sliding window
14
  interface RateLimitInfo {
 
182
  }
183
  }
184
 
185
+ private async getEncodedVqdHash(vqdHash: string): Promise<string> {
186
+ const jsScript = Buffer.from(vqdHash, 'base64').toString('utf-8');
187
+
188
+ const dom = new JSDOM(
189
+ `<iframe id="jsa" sandbox="allow-scripts allow-same-origin" srcdoc="<!DOCTYPE html>
190
+ <html>
191
+ <head>
192
+ <meta http-equiv="Content-Security-Policy"; content="default-src 'none'; script-src 'unsafe-inline'">
193
+ </head>
194
+ <body></body>
195
+ </html>" style="position: absolute; left: -9999px; top: -9999px;"></iframe>`,
196
+ { runScripts: 'dangerously' }
197
+ );
198
+ dom.window.top.__DDG_BE_VERSION__ = 1;
199
+ dom.window.top.__DDG_FE_CHAT_HASH__ = 1;
200
+ const jsa = dom.window.top.document.querySelector('#jsa') as HTMLIFrameElement;
201
+ const contentDoc = jsa.contentDocument || jsa.contentWindow!.document;
202
+
203
+ const meta = contentDoc.createElement('meta');
204
+ meta.setAttribute('http-equiv', 'Content-Security-Policy');
205
+ meta.setAttribute('content', "default-src 'none'; script-src 'unsafe-inline';");
206
+ contentDoc.head.appendChild(meta);
207
+ const result = await dom.window.eval(jsScript) as {
208
+ client_hashes: string[];
209
+ [key: string]: any;
210
+ };
211
+
212
+ 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';
213
+ result.client_hashes = result.client_hashes.map((t) => {
214
+ const hash = createHash('sha256');
215
+ hash.update(t);
216
+
217
+ return hash.digest('base64');
218
+ });
219
+
220
+ return btoa(JSON.stringify(result));
221
+ }
222
+
223
  private async getVQD(userAgent: string): Promise<VQDResponse> {
224
  const response = await fetch("https://duckduckgo.com/duckchat/v1/status", {
225
  headers: {
 
247
  );
248
  }
249
 
 
250
  const hashHeader = response.headers.get("x-Vqd-hash-1");
251
 
252
+ if (!hashHeader) {
253
  throw new Error(
254
+ `Missing VQD headers: hash=${!!hashHeader}`
255
  );
256
  }
257
 
258
+ const encodedHash = await this.getEncodedVqdHash(hashHeader);
 
 
 
 
 
259
 
260
+ return { hash: encodedHash };
261
  }
262
 
263
  private async hashClientHashes(clientHashes: string[]): Promise<string[]> {
 
281
  const userAgent = new UserAgent().toString();
282
  const vqd = await this.getVQD(userAgent);
283
 
 
 
 
 
 
 
 
 
 
 
 
 
284
  // Update rate limit tracking BEFORE making the request
285
  const now = Date.now();
286
  this.rateLimitInfo.requestTimestamps.push(now);
 
302
  "sec-fetch-mode": "cors",
303
  "sec-fetch-site": "same-origin",
304
  "x-fe-version": "serp_20250401_100419_ET-19d438eb199b2bf7c300",
 
305
  "User-Agent": userAgent,
306
+ "x-vqd-hash-1": vqd.hash,
 
 
 
 
 
 
307
  },
308
  referrer: "https://duckduckgo.com/",
309
  referrerPolicy: "origin",
 
372
  await this.waitIfNeeded();
373
 
374
  const userAgent = new UserAgent().toString();
 
375
  const vqd = await this.getVQD(userAgent);
376
 
 
 
 
 
 
 
 
 
 
 
 
 
377
  // Update rate limit tracking BEFORE making the request
378
  const now = Date.now();
379
  this.rateLimitInfo.requestTimestamps.push(now);
 
395
  "sec-fetch-mode": "cors",
396
  "sec-fetch-site": "same-origin",
397
  "x-fe-version": "serp_20250401_100419_ET-19d438eb199b2bf7c300",
 
398
  "User-Agent": userAgent,
399
+ "x-vqd-hash-1": vqd.hash,
 
 
 
 
 
 
400
  },
401
  referrer: "https://duckduckgo.com/",
402
  referrerPolicy: "origin",
 
465
  getAvailableModels(): string[] {
466
  return [
467
  "gpt-4o-mini",
468
+ "gpt-5-mini",
469
+ "claude-3-5-haiku-latest",
470
+ "meta-llama/Llama-4-Scout-17B-16E-Instruct",
471
  "mistralai/Mistral-Small-24B-Instruct-2501",
472
+ "openai/gpt-oss-120b"
473
  ];
474
  }
475
  }