""" HackerNews -- open Firebase API + Algolia search, no key. """ import httpx FIREBASE_BASE = "https://hacker-news.firebaseio.com/v0" ALGOLIA_BASE = "https://hn.algolia.com/api/v1/search" def run(params: dict) -> dict: mode = params.get("mode", "top") limit = int(params.get("limit", 30)) if mode == "search": query = params.get("query") if not query: raise ValueError("query is required for mode='search'") min_points = int(params.get("min_points", 50)) resp = httpx.get( ALGOLIA_BASE, params={"query": query, "tags": "story", "numericFilters": f"points>{min_points}"}, timeout=15.0, ) resp.raise_for_status() hits = resp.json().get("hits", [])[:limit] results = [ { "title": h.get("title"), "score": h.get("points"), "comments": h.get("num_comments"), "date": h.get("created_at"), "url": h.get("url") or f"https://news.ycombinator.com/item?id={h.get('objectID')}", } for h in hits ] return {"mode": "search", "count": len(results), "results": results} # top stories snapshot resp = httpx.get(f"{FIREBASE_BASE}/topstories.json", timeout=15.0) resp.raise_for_status() ids = resp.json()[:limit] results = [] for story_id in ids: item_resp = httpx.get(f"{FIREBASE_BASE}/item/{story_id}.json", timeout=10.0) if item_resp.status_code != 200: continue item = item_resp.json() results.append( { "title": item.get("title"), "score": item.get("score"), "comments": item.get("descendants"), "author": item.get("by"), "url": item.get("url") or f"https://news.ycombinator.com/item?id={story_id}", } ) return {"mode": "top", "count": len(results), "results": results}