""" SearXNG Service Adapter Discovers prior art, candidate sources, and web references via SearXNG JSON API. """ import json import time import urllib.parse import urllib.request import asyncio from typing import List, Dict, Any, Optional from .base import BaseServiceAdapter, ServiceHealth class SearXNGAdapter(BaseServiceAdapter): def __init__(self, endpoint: str = "https://sx.godno.de"): super().__init__(service_id="searxng", endpoint=endpoint) async def check_health(self) -> ServiceHealth: start = time.time() try: url = f"{self.endpoint}/search?format=json&q=ping" req = urllib.request.Request( url, headers={"User-Agent": "ThinkStorm-Orchestrator/0.1"} ) # Run in executor to avoid blocking async event loop loop = asyncio.get_running_loop() def fetch(): with urllib.request.urlopen(req, timeout=8.0) as resp: return resp.read() raw = await loop.run_in_executor(None, fetch) data = json.loads(raw.decode("utf-8")) elapsed = int((time.time() - start) * 1000) return ServiceHealth( service_id=self.service_id, healthy=True, endpoint=self.endpoint, message=f"SearXNG online (returned {len(data.get('results', []))} results)", response_time_ms=elapsed ) except Exception as e: elapsed = int((time.time() - start) * 1000) return ServiceHealth( service_id=self.service_id, healthy=False, endpoint=self.endpoint, message=f"SearXNG connection error: {str(e)}", response_time_ms=elapsed ) async def search(self, query: str, limit: int = 8) -> List[Dict[str, Any]]: """Executes a search query and returns structured results.""" encoded_query = urllib.parse.quote_plus(query) url = f"{self.endpoint}/search?format=json&q={encoded_query}&language=en" req = urllib.request.Request( url, headers={"User-Agent": "ThinkStorm-Orchestrator/0.1"} ) loop = asyncio.get_running_loop() def fetch(): with urllib.request.urlopen(req, timeout=3.0) as resp: return resp.read() try: raw = await asyncio.wait_for(loop.run_in_executor(None, fetch), timeout=3.0) data = json.loads(raw.decode("utf-8")) raw_results = data.get("results", []) results = [] for r in raw_results[:limit]: results.append({ "title": r.get("title", "Untitled"), "url": r.get("url", ""), "content": r.get("content", ""), "engine": r.get("engine", "searxng"), "score": r.get("score", 0.0) }) return results except Exception as e: print(f"[SearXNG] Search error for query '{query}': {e}") return []