import express from 'express'; const router = express.Router(); // DuckDuckGo Instant Answer API - no key required async function ddgInstantAnswer(query) { const url = `https://api.duckduckgo.com/?q=${encodeURIComponent(query)}&format=json&no_html=1&skip_disambig=1`; const res = await fetch(url, { headers: { 'User-Agent': 'IQAI-Dashboard/1.0' } }); if (!res.ok) throw new Error('DDG search failed'); return res.json(); } // DuckDuckGo HTML search scraper for more results async function ddgWebSearch(query) { const url = `https://html.duckduckgo.com/html/?q=${encodeURIComponent(query)}`; const res = await fetch(url, { headers: { 'User-Agent': 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36', 'Accept': 'text/html' } }); if (!res.ok) return []; const html = await res.text(); // Parse result links and snippets from DDG HTML const results = []; const resultRegex = /]+class="result__a"[^>]*href="([^"]*)"[^>]*>([\s\S]*?)<\/a>/gi; const snippetRegex = /]+class="result__snippet"[^>]*>([\s\S]*?)<\/a>/gi; const links = [...html.matchAll(resultRegex)].slice(0, 8); const snippets = [...html.matchAll(snippetRegex)].slice(0, 8); for (let i = 0; i < Math.min(links.length, 5); i++) { const title = links[i][2].replace(/<[^>]+>/g, '').trim(); const snippet = snippets[i] ? snippets[i][1].replace(/<[^>]+>/g, '').trim() : ''; const href = links[i][1]; // DDG redirects through their own URLs, extract the real URL const realUrl = href.startsWith('//duckduckgo.com/l/?uddg=') ? decodeURIComponent(href.split('uddg=')[1]?.split('&')[0] || href) : href; if (title && !title.includes('DuckDuckGo')) { results.push({ title, snippet, url: realUrl }); } } return results; } router.get('/', async (req, res) => { const { q } = req.query; if (!q) return res.status(400).json({ error: 'Query parameter q is required' }); try { const [instant, webResults] = await Promise.allSettled([ ddgInstantAnswer(q), ddgWebSearch(q) ]); const answer = instant.status === 'fulfilled' ? instant.value : null; const results = webResults.status === 'fulfilled' ? webResults.value : []; // Format for injection into AI prompt const formatted = formatSearchResults(q, answer, results); res.json({ query: q, answer, results, formatted }); } catch (err) { res.status(500).json({ error: err.message }); } }); function formatSearchResults(query, instant, webResults) { const lines = [`[WEB SEARCH RESULTS FOR: "${query}"]`, '']; if (instant?.AbstractText) { lines.push(`Summary: ${instant.AbstractText}`); if (instant.AbstractSource) lines.push(`Source: ${instant.AbstractSource}`); lines.push(''); } if (instant?.Answer) { lines.push(`Direct Answer: ${instant.Answer}`); lines.push(''); } if (webResults.length > 0) { lines.push('Web Results:'); webResults.forEach((r, i) => { lines.push(`${i + 1}. ${r.title}`); if (r.snippet) lines.push(` ${r.snippet}`); lines.push(` URL: ${r.url}`); }); lines.push(''); } if (instant?.RelatedTopics?.length > 0) { const topics = instant.RelatedTopics .filter(t => t.Text) .slice(0, 3) .map(t => `- ${t.Text}`); if (topics.length > 0) { lines.push('Related Topics:'); lines.push(...topics); lines.push(''); } } lines.push('[END SEARCH RESULTS]'); return lines.join('\n'); } export { router as searchRouter };