GeminiBot
commited on
Commit
·
783fb1f
1
Parent(s):
a521d64
Update diagnostic logs and stabilize fetch
Browse files- src/duckai.ts +50 -35
src/duckai.ts
CHANGED
|
@@ -1,51 +1,66 @@
|
|
| 1 |
-
import { gotScraping } from 'got-scraping';
|
| 2 |
import { JSDOM } from "jsdom";
|
| 3 |
import { createHash } from "node:crypto";
|
|
|
|
| 4 |
|
| 5 |
export class DuckAI {
|
| 6 |
private async solveChallenge(vqdHash: string, ua: string): Promise<string> {
|
| 7 |
-
|
| 8 |
-
|
| 9 |
-
|
| 10 |
-
|
| 11 |
-
|
| 12 |
-
|
| 13 |
-
|
| 14 |
-
|
| 15 |
-
|
| 16 |
-
|
| 17 |
-
|
| 18 |
-
|
| 19 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 20 |
}
|
| 21 |
|
| 22 |
async chat(request: any): Promise<string> {
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 23 |
try {
|
| 24 |
-
//
|
| 25 |
-
|
| 26 |
-
|
| 27 |
-
|
| 28 |
-
const
|
| 29 |
-
|
| 30 |
-
});
|
| 31 |
|
| 32 |
-
const ua = statusRes.request.options.headers['user-agent'] as string;
|
| 33 |
-
const hashHeader = statusRes.headers["x-vqd-hash-1"] as string;
|
| 34 |
-
|
| 35 |
const solvedVqd = await this.solveChallenge(hashHeader, ua);
|
| 36 |
|
| 37 |
// 2. Делаем запрос в чат
|
| 38 |
-
const chatRes = await
|
|
|
|
| 39 |
headers: {
|
|
|
|
| 40 |
"x-vqd-hash-1": solvedVqd,
|
| 41 |
-
"
|
| 42 |
-
"accept": "text/event-stream"
|
| 43 |
},
|
| 44 |
-
|
| 45 |
-
retry: { limit: 2 } // Авто-повтор при сбоях
|
| 46 |
});
|
| 47 |
|
| 48 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 49 |
let llmResponse = "";
|
| 50 |
const lines = text.split("\n");
|
| 51 |
for (const line of lines) {
|
|
@@ -56,11 +71,11 @@ export class DuckAI {
|
|
| 56 |
} catch (e) {}
|
| 57 |
}
|
| 58 |
}
|
| 59 |
-
return llmResponse.trim() || "⚠️ Пустой ответ от
|
| 60 |
|
| 61 |
-
} catch (error) {
|
| 62 |
-
console.error("Scraping
|
| 63 |
-
return
|
| 64 |
}
|
| 65 |
}
|
| 66 |
-
}
|
|
|
|
|
|
|
| 1 |
import { JSDOM } from "jsdom";
|
| 2 |
import { createHash } from "node:crypto";
|
| 3 |
+
import UserAgent from "user-agents";
|
| 4 |
|
| 5 |
export class DuckAI {
|
| 6 |
private async solveChallenge(vqdHash: string, ua: string): Promise<string> {
|
| 7 |
+
try {
|
| 8 |
+
const jsScript = Buffer.from(vqdHash, 'base64').toString('utf-8');
|
| 9 |
+
const dom = new JSDOM(`<!DOCTYPE html><html><body></body></html>`, { runScripts: 'dangerously' });
|
| 10 |
+
dom.window.top.__DDG_BE_VERSION__ = 1;
|
| 11 |
+
dom.window.top.__DDG_FE_CHAT_HASH__ = 1;
|
| 12 |
+
|
| 13 |
+
const result = await dom.window.eval(jsScript) as any;
|
| 14 |
+
result.client_hashes[0] = ua;
|
| 15 |
+
result.client_hashes = result.client_hashes.map((t: string) => {
|
| 16 |
+
const hash = createHash('sha256');
|
| 17 |
+
hash.update(t);
|
| 18 |
+
return hash.digest('base64');
|
| 19 |
+
});
|
| 20 |
+
return btoa(JSON.stringify(result));
|
| 21 |
+
} catch (e: any) {
|
| 22 |
+
throw new Error("Challenge Solver Error: " + e.message);
|
| 23 |
+
}
|
| 24 |
}
|
| 25 |
|
| 26 |
async chat(request: any): Promise<string> {
|
| 27 |
+
const ua = new UserAgent({ deviceCategory: 'desktop' }).toString();
|
| 28 |
+
const headers: any = {
|
| 29 |
+
"User-Agent": ua,
|
| 30 |
+
"Accept": "text/event-stream",
|
| 31 |
+
"Accept-Language": "en-US,en;q=0.9",
|
| 32 |
+
"Referer": "https://duckduckgo.com/",
|
| 33 |
+
"Origin": "https://duckduckgo.com",
|
| 34 |
+
"x-vqd-accept": "1"
|
| 35 |
+
};
|
| 36 |
+
|
| 37 |
try {
|
| 38 |
+
// 1. Получаем статус
|
| 39 |
+
const statusRes = await fetch("https://duckduckgo.com/duckchat/v1/status?q=1", { headers });
|
| 40 |
+
if (!statusRes.ok) throw new Error(`DDG Status Fail: ${statusRes.status}`);
|
| 41 |
+
|
| 42 |
+
const hashHeader = statusRes.headers.get("x-vqd-hash-1");
|
| 43 |
+
if (!hashHeader) throw new Error("Missing x-vqd-hash-1 header");
|
|
|
|
| 44 |
|
|
|
|
|
|
|
|
|
|
| 45 |
const solvedVqd = await this.solveChallenge(hashHeader, ua);
|
| 46 |
|
| 47 |
// 2. Делаем запрос в чат
|
| 48 |
+
const chatRes = await fetch("https://duckduckgo.com/duckchat/v1/chat", {
|
| 49 |
+
method: "POST",
|
| 50 |
headers: {
|
| 51 |
+
...headers,
|
| 52 |
"x-vqd-hash-1": solvedVqd,
|
| 53 |
+
"Content-Type": "application/json"
|
|
|
|
| 54 |
},
|
| 55 |
+
body: JSON.stringify(request)
|
|
|
|
| 56 |
});
|
| 57 |
|
| 58 |
+
if (!chatRes.ok) {
|
| 59 |
+
const errText = await chatRes.text();
|
| 60 |
+
throw new Error(`DDG Chat Fail: ${chatRes.status} - ${errText.substring(0, 100)}`);
|
| 61 |
+
}
|
| 62 |
+
|
| 63 |
+
const text = await chatRes.text();
|
| 64 |
let llmResponse = "";
|
| 65 |
const lines = text.split("\n");
|
| 66 |
for (const line of lines) {
|
|
|
|
| 71 |
} catch (e) {}
|
| 72 |
}
|
| 73 |
}
|
| 74 |
+
return llmResponse.trim() || "⚠️ Пустой ответ от DDG.";
|
| 75 |
|
| 76 |
+
} catch (error: any) {
|
| 77 |
+
console.error("Scraping Detail:", error.message);
|
| 78 |
+
return `⚠️ Ошибка бэкенда: ${error.message}`;
|
| 79 |
}
|
| 80 |
}
|
| 81 |
+
}
|