summaryrefslogtreecommitdiff
path: root/hn.py
blob: 1a9f880a1680dbc9b952d992138b521df78c72fe (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
#!/usr/bin/env python3
"""
Static Hacker News page generator.

* Top stories → output/index.html (site root)
* Best, New, Ask, Show, Job → output/<section>/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 <li> 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'<li><a href="{url}">{title}</a> ({score} points) by {by}</li>'


# ----------------------------------------------------------------------
# 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()