iper / src /duckai.ts
GeminiBot
Simplify eval context to fix undefined document error
add57ca
raw
history blame
10.4 kB
import { JSDOM } from "jsdom";
import { createHash } from "node:crypto";
import UserAgent from "user-agents";
export class DuckAI {
private async solveChallenge(vqdHash: string, ua: string): Promise<string> {
try {
const jsScript = Buffer.from(vqdHash, 'base64').toString('utf-8');
const dom = new JSDOM(`<!DOCTYPE html><html><body></body></html>`, {
url: "https://duckduckgo.com/",
referrer: "https://duckduckgo.com/",
runScripts: "dangerously",
resources: "usable"
});
const mockWindow = dom.window as any;
// Self-reference fixes
mockWindow.self = mockWindow;
mockWindow.parent = mockWindow;
mockWindow.top = mockWindow;
// IMMORTAL DOM Strategy
const originalGetElementById = mockWindow.document.getElementById;
mockWindow.document.getElementById = function(id: string) {
const el = originalGetElementById.call(this, id);
if (el) return el;
return mockWindow.document.createElement('div');
};
const originalQuerySelector = mockWindow.document.querySelector;
mockWindow.document.querySelector = function(selector: string) {
try {
const el = originalQuerySelector.call(this, selector);
if (el) return el;
} catch(e) {}
return mockWindow.document.createElement('div');
};
// ГЛУБОКАЯ ЭМУЛЯЦИЯ
Object.defineProperties(mockWindow.navigator, {
userAgent: { value: ua },
platform: { value: 'Win32' },
webdriver: { value: false },
languages: { value: ['en-US', 'en'] }
});
mockWindow.screen = { width: 1920, height: 1080, availWidth: 1920, availHeight: 1080 };
mockWindow.chrome = { runtime: {} };
// Aggressive Patch for iframe contentDocument
Object.defineProperty(mockWindow.HTMLIFrameElement.prototype, 'contentDocument', {
get: function() { return mockWindow.document; },
configurable: true
});
Object.defineProperty(mockWindow.HTMLIFrameElement.prototype, 'contentWindow', {
get: function() { return mockWindow; },
configurable: true
});
// Also keep the createElement hook just in case
const originalCreateElement = mockWindow.document.createElement;
mockWindow.document.createElement = function(tagName: string) {
const element = originalCreateElement.call(this, tagName);
if (tagName.toLowerCase() === 'iframe') {
try {
Object.defineProperty(element, 'contentDocument', {
get: () => mockWindow.document,
configurable: true
});
Object.defineProperty(element, 'contentWindow', {
get: () => mockWindow,
configurable: true
});
} catch (e) {}
}
return element;
};
// Robust Canvas Mock
const originalGetContext = mockWindow.HTMLCanvasElement.prototype.getContext;
mockWindow.HTMLCanvasElement.prototype.getContext = function (type: string, options?: any) {
try {
const ctx = originalGetContext.call(this, type, options);
if (ctx) return ctx;
} catch (e) {}
return {
canvas: this,
fillRect: () => {},
clearRect: () => {},
getImageData: (x: number, y: number, w: number, h: number) => ({
data: new Uint8ClampedArray(w * h * 4),
width: w,
height: h
}),
putImageData: () => {},
createImageData: () => ({ data: new Uint8ClampedArray(4) }),
setTransform: () => {},
drawImage: () => {},
save: () => {},
restore: () => {},
beginPath: () => {},
moveTo: () => {},
lineTo: () => {},
closePath: () => {},
stroke: () => {},
translate: () => {},
scale: () => {},
rotate: () => {},
arc: () => {},
fill: () => {},
measureText: () => ({ width: 0, actualBoundingBoxAscent: 0, actualBoundingBoxDescent: 0 }),
transform: () => {},
rect: () => {},
clip: () => {},
createLinearGradient: () => ({ addColorStop: () => {} }),
createRadialGradient: () => ({ addColorStop: () => {} }),
createPattern: () => ({}),
bezierCurveTo: () => {},
quadraticCurveTo: () => {},
fillText: () => {},
strokeText: () => {},
globalAlpha: 1,
globalCompositeOperation: 'source-over',
fillStyle: '#000000',
strokeStyle: '#000000',
lineWidth: 1,
lineCap: 'butt',
lineJoin: 'miter',
miterLimit: 10,
shadowOffsetX: 0,
shadowOffsetY: 0,
shadowBlur: 0,
shadowColor: 'rgba(0, 0, 0, 0)',
font: '10px sans-serif',
textAlign: 'start',
textBaseline: 'alphabetic'
};
} as any;
const originalToDataURL = mockWindow.HTMLCanvasElement.prototype.toDataURL;
mockWindow.HTMLCanvasElement.prototype.toDataURL = function(type?: string, quality?: any) {
try {
const result = originalToDataURL.call(this, type, quality);
if (result && result !== "data:,") return result;
} catch(e) {}
return "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+M9QDwADhgGAWjR9awAAAABJRU5ErkJggg==";
};
// --- CONTEXT PREPARATION ---
// Inject everything directly into the window context to avoid scope issues
mockWindow.document = mockWindow.document;
mockWindow.window = mockWindow;
mockWindow.self = mockWindow;
mockWindow.parent = mockWindow;
mockWindow.top = mockWindow;
// Immortal DOM patches (already applied above) are good.
// Execute script directly in the window context
// We wrap it in a try-catch block INSIDE the eval to capture script errors
const result = mockWindow.eval(`
try {
${jsScript}
} catch(e) {
console.error("Script Eval Error:", e);
null;
}
`);
if (!result || !result.client_hashes) {
// If result is null, maybe the script put the result in a global variable?
// Sometimes DDG scripts do that. Let's check window.result or similar if needed.
// But for now, throw detailed error
throw new Error("Script executed but returned invalid result (no client_hashes)");
}
result.client_hashes[0] = ua;
result.client_hashes = result.client_hashes.map((t: string) => {
const hash = createHash('sha256');
hash.update(t);
return hash.digest('base64');
});
return btoa(JSON.stringify(result));
} catch (e: any) {
console.error(`[DuckAI] Challenge Solver Failed: ${e.message}`);
console.log("[DuckAI] Attempting fallback with original VQD...");
return vqdHash;
}
}
getAvailableModels(): string[] {
return [
"gpt-4o-mini",
"gpt-5-mini",
"openai/gpt-oss-120b"
];
}
getRateLimitStatus() {
return { status: "unknown" };
}
async chat(request: any, retries = 1): Promise<string> {
const ua = new UserAgent({ deviceCategory: 'desktop' }).toString();
const headers: any = {
"User-Agent": ua,
"Accept": "text/event-stream",
"x-vqd-accept": "1"
};
try {
console.log("[DuckAI] Fetching status...");
const statusRes = await fetch("https://duckduckgo.com/duckchat/v1/status?q=1", { headers });
console.log(`[DuckAI] Status response: ${statusRes.status}`);
const hashHeader = statusRes.headers.get("x-vqd-hash-1");
console.log(`[DuckAI] x-vqd-hash-1: ${hashHeader ? "FOUND" : "MISSING"}`);
if (!hashHeader) throw new Error("Missing x-vqd-hash-1 - DuckDuckGo might be blocking this IP or Challenge failed.");
console.log("[DuckAI] Solving challenge...");
const solvedVqd = await this.solveChallenge(hashHeader, ua);
console.log("[DuckAI] Challenge solved (or fallback used).");
const response = await fetch("https://duckduckgo.com/duckchat/v1/chat", {
method: "POST",
headers: { ...headers, "x-vqd-hash-1": solvedVqd, "Content-Type": "application/json" },
body: JSON.stringify(request)
});
console.log(`[DuckAI] Chat response status: ${response.status}`);
if (!response.ok) {
const errorText = await response.text();
// Если ошибка 418 (Challenge Failed) и есть попытки - пробуем снова
if (response.status === 418 && retries > 0) {
console.log(`[DuckAI] Got 418 Error. Retrying... (${retries} attempts left)`);
return this.chat(request, retries - 1);
}
console.log(`[DuckAI] Error body: ${errorText}`);
throw new Error(`DuckDuckGo API Error (${response.status}): ${errorText.substring(0, 100)}`);
}
const text = await response.text();
let llmResponse = "";
const lines = text.split("\n");
for (const line of lines) {
if (line.startsWith("data: ")) {
try {
const chunk = line.slice(6);
if (chunk === "[DONE]") break;
const json = JSON.parse(chunk);
if (json.message) llmResponse += json.message;
} catch (e) {}
}
}
if (!llmResponse) {
console.log("[DuckAI] Warning: Empty LLM response extracted.");
throw new Error("Empty response from DuckDuckGo");
}
return llmResponse.trim();
} catch (error: any) {
// Если была ошибка сети или другая, и есть попытки - тоже ретраим
if (retries > 0) {
console.log(`[DuckAI] Error: ${error.message}. Retrying... (${retries} attempts left)`);
return this.chat(request, retries - 1);
}
throw error;
}
}
}