From 01bd81cf3f943b4aff4e79f93910ab764a873548 Mon Sep 17 00:00:00 2001 From: Christian Cleberg Date: Wed, 7 Jan 2026 20:18:27 -0600 Subject: MASSIVE UPDATE TO PYTHON STATIC SERVICE --- hn.py | 109 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 109 insertions(+) create mode 100644 hn.py (limited to 'hn.py') diff --git a/hn.py b/hn.py new file mode 100644 index 0000000..1a9f880 --- /dev/null +++ b/hn.py @@ -0,0 +1,109 @@ +#!/usr/bin/env python3 +""" +Static Hacker News page generator. + +* Top stories → output/index.html (site root) +* Best, New, Ask, Show, Job → output/
/index.html +""" + +import json +import urllib.request +from pathlib import Path +from datetime import datetime +from typing import List, Dict + +BASE_URL = "https://hacker-news.firebaseio.com/v0" +OUTPUT_DIR = Path(__file__).parent / "output" +TEMPLATE_PATH = Path(__file__).parent / "templates" / "base.html" + + +# ---------------------------------------------------------------------- +# Helper functions +# ---------------------------------------------------------------------- +def fetch_json(url: str) -> dict: + """GET a JSON endpoint and return the parsed object.""" + with urllib.request.urlopen(url) as resp: + return json.load(resp) + + +def get_story_ids(endpoint: str, limit: int = 10) -> List[int]: + """Return the first ``limit`` IDs for a given endpoint.""" + url = f"{BASE_URL}/{endpoint}.json" + all_ids = fetch_json(url) + return all_ids[:limit] + + +def get_item(item_id: int) -> Dict: + """Fetch a single Hacker News item.""" + url = f"{BASE_URL}/item/{item_id}.json" + return fetch_json(url) + + +def render_page(title: str, items_html: str, build_time: str) -> str: + """ + Insert title, items, and the build timestamp into the base template. + """ + template = TEMPLATE_PATH.read_text(encoding="utf-8") + rendered = ( + template + .replace("{{title}}", title) + .replace("{{items}}", items_html) + .replace("{{build}}", build_time) + ) + return rendered + + +def build_list_item(story: Dict) -> str: + """Turn a story dict into a single
  • element.""" + url = story.get("url") or f"https://news.ycombinator.com/item?id={story['id']}" + title = story.get("title", "(no title)") + score = story.get("score", 0) + by = story.get("by", "unknown") + return f'
  • {title} ({score} points) by {by}
  • ' + + +# ---------------------------------------------------------------------- +# Main generation logic +# ---------------------------------------------------------------------- +def generate_static_pages(): + # Make sure the top‑level output folder exists + OUTPUT_DIR.mkdir(parents=True, exist_ok=True) + + # One timestamp for the whole run + build_timestamp = datetime.utcnow().strftime("%Y-%m-%d %H:%M UTC") + + # Mapping: endpoint → (human‑readable title, sub‑folder name or None for root) + sections = { + "topstories": ("Top Stories", None), # None → write to root index.html + "beststories": ("Best Stories", "best"), + "newstories": ("New Stories", "new"), + "askstories": ("Ask HN", "ask"), + "showstories": ("Show HN", "show"), + "jobstories": ("Jobs", "job"), + } + + for endpoint, (title, subdir) in sections.items(): + print(f"Fetching {title}…") + ids = get_story_ids(endpoint, limit=10) + stories = [get_item(i) for i in ids] + + items_html = "\n".join(build_list_item(s) for s in stories) + + page_html = render_page(title, items_html, build_timestamp) + + # Determine where to write the file + if subdir is None: + target_path = OUTPUT_DIR / "index.html" + else: + target_dir = OUTPUT_DIR / subdir + target_dir.mkdir(parents=True, exist_ok=True) + target_path = target_dir / "index.html" + + target_path.write_text(page_html, encoding="utf-8") + print(f" → wrote {target_path}") + + print("All pages generated (built at", build_timestamp, ")") + + +if __name__ == "__main__": + generate_static_pages() \ No newline at end of file -- cgit v1.2.3