'use strict'; /* ── Instancias públicas SearXNG con JSON API (fallback si no hay Google CSE) ── */ const SEARX_INSTANCES = [ 'https://searx.be', 'https://priv.au', 'https://search.mdosch.de', 'https://searxng.site', ]; async function searchSearX(instance, query) { const url = `${instance}/search?` + new URLSearchParams({ q: query, format: 'json', categories: 'general', language: 'es-ES', }); const res = await fetch(url, { headers: { 'User-Agent': 'resetea.net/1.0 (egosurfing privacy tool)', 'Accept': 'application/json', }, signal: AbortSignal.timeout(7000), }); if (!res.ok) throw new Error(`HTTP ${res.status}`); const data = await res.json(); return (data.results || []).map(r => ({ title: r.title || '', url: r.url || '', snippet: r.content || '', engine: r.engine || 'web', })); } async function searchGoogle(query) { const url = 'https://www.googleapis.com/customsearch/v1?' + new URLSearchParams({ key: process.env.GOOGLE_API_KEY, cx: process.env.GOOGLE_CSE_ID, q: query, num: 10, hl: 'es', }); const res = await fetch(url, { signal: AbortSignal.timeout(7000) }); if (!res.ok) { const err = await res.json().catch(() => ({})); throw new Error(err.error?.message || `HTTP ${res.status}`); } const data = await res.json(); return (data.items || []).map(item => ({ title: item.title || '', url: item.link || '', snippet: item.snippet || '', engine: 'google', })); } function domainOf(rawUrl) { try { return new URL(rawUrl).hostname.replace(/^www\./, ''); } catch { return ''; } } module.exports = async (req, res) => { const { q } = req.query; if (!q || q.trim().length < 2) return res.status(400).json({ error: 'Introduce al menos 2 caracteres.' }); if (q.trim().length > 300) return res.status(400).json({ error: 'Búsqueda demasiado larga.' }); /* Si la query no tiene operadores especiales la ponemos entre comillas para forzar coincidencia exacta (ideal para nombre/email/alias). */ const query = /[:"()]/.test(q) ? q.trim() : `"${q.trim()}"`; let raw = []; try { if (process.env.GOOGLE_API_KEY && process.env.GOOGLE_CSE_ID) { raw = await searchGoogle(query); } else { for (const inst of SEARX_INSTANCES) { try { raw = await searchSearX(inst, query); if (raw.length) break; } catch (e) { console.warn(`[egosearch] ${inst} falló:`, e.message); } } } } catch (e) { console.error('[egosearch] error final:', e.message); return res.status(502).json({ error: 'El servicio de búsqueda no está disponible ahora. Inténtalo de nuevo.' }); } const results = raw.slice(0, 5).map(r => ({ title: r.title, url: r.url, snippet: r.snippet, domain: domainOf(r.url), engine: r.engine, })); res.json({ results, query, total: raw.length }); };