import type { NormalizedExaResponse, NormalizedExaResult } from "./exa.js"; const DUCKDUCKGO_HTML_URL = "https://html.duckduckgo.com/html/"; const USER_AGENT = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36"; export interface DuckDuckGoSearchParams { query: string; numResults?: number; includeDomains?: string[]; excludeDomains?: string[]; } export interface DuckDuckGoClientOptions { fetchImpl?: typeof fetch; } function decodeHtml(value: string): string { return value .replace(/&/g, "&") .replace(/</g, "<") .replace(/>/g, ">") .replace(/"/g, '"') .replace(/'|'/g, "'") .replace(/ /g, " ") .replace(/(\d+);/g, (_match, code) => String.fromCharCode(Number(code))) .replace(/([0-9a-f]+);/gi, (_match, code) => String.fromCharCode(Number.parseInt(code, 16))); } function stripTags(value: string): string { return decodeHtml(value.replace(/<[^>]+>/g, " ")).replace(/\s+/g, " ").trim(); } function normalizeDuckDuckGoUrl(rawUrl: string): string { const decoded = decodeHtml(rawUrl).trim(); try { const absolute = decoded.startsWith("//") ? `https:${decoded}` : new URL(decoded, DUCKDUCKGO_HTML_URL).toString(); const parsed = new URL(absolute); if (parsed.hostname.endsWith("duckduckgo.com") && parsed.pathname === "/l/") { const target = parsed.searchParams.get("uddg"); if (target) return decodeURIComponent(target); } return absolute; } catch { return decoded; } } function addDomainHints(query: string, includeDomains?: string[], excludeDomains?: string[]): string { const parts = [query]; for (const domain of includeDomains ?? []) { const clean = domain.trim(); if (clean) parts.push(`site:${clean}`); } for (const domain of excludeDomains ?? []) { const clean = domain.trim(); if (clean) parts.push(`-site:${clean}`); } return parts.join(" "); } function looksBlocked(html: string): boolean { return /captcha|challenge|verify you are human|are you a robot|unusual traffic/i.test(html); } export function parseDuckDuckGoHtml(html: string, limit = 10): NormalizedExaResult[] { const results: NormalizedExaResult[] = []; const blocks = html.split(/