"""Hacker News client using the Algolia HN Search API. No authentication required. Docs: https://hn.algolia.com/api """ import httpx HN_API_BASE = "https://hn.algolia.com/api/v1" async def search_stories(query: str, limit: int = 25) -> list[dict]: """Search HN for stories matching a query. Returns a list of story dicts with: title, url, author, points, num_comments, created_at, objectID, story_text. """ async with httpx.AsyncClient(timeout=15) as client: resp = await client.get( f"{HN_API_BASE}/search", params={ "query": query, "tags": "story", "hitsPerPage": min(limit, 50), }, ) resp.raise_for_status() data = resp.json() results = [] for hit in data.get("hits", []): results.append( { "title": hit.get("title", ""), "url": hit.get("url", ""), "author": hit.get("author", ""), "points": hit.get("points", 0), "num_comments": hit.get("num_comments", 0), "created_at": hit.get("created_at", ""), "object_id": hit.get("objectID", ""), "story_text": hit.get("story_text") or "", "hn_url": f"https://news.ycombinator.com/item?id={hit.get('objectID', '')}", } ) return results async def search_comments(query: str, limit: int = 25) -> list[dict]: """Search HN for comments matching a query. Returns a list of comment dicts with: comment_text, author, points, created_at, story_title, story_url. """ async with httpx.AsyncClient(timeout=15) as client: resp = await client.get( f"{HN_API_BASE}/search", params={ "query": query, "tags": "comment", "hitsPerPage": min(limit, 50), }, ) resp.raise_for_status() data = resp.json() results = [] for hit in data.get("hits", []): results.append( { "comment_text": hit.get("comment_text", ""), "author": hit.get("author", ""), "points": hit.get("points", 0), "created_at": hit.get("created_at", ""), "story_title": hit.get("story_title", ""), "story_url": hit.get("story_url", ""), "hn_url": f"https://news.ycombinator.com/item?id={hit.get('objectID', '')}", } ) return results