| """ |
| 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} |
|
|
| |
| 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} |
|
|