retronews

a featureful fork of the luke8086/retronews hn+lobste.rs tui
Log | Files | Refs | README | LICENSE

commit ecd2476398687ac818f90fc33ee0d8f32b27c89f
parent 303463ca602a776146b178c629ad1b29f63ddea2
Author: luke8086 <55237178+luke8086@users.noreply.github.com>
Date:   Sat, 16 Jul 2022 16:41:45 +0000

Initial support for fetching stories from the Algolia API

Diffstat:
Mretronews.py | 52+++++++++++++++++++++++++++++++++++++++++++++++++---
1 file changed, 49 insertions(+), 3 deletions(-)

diff --git a/retronews.py b/retronews.py @@ -11,10 +11,11 @@ import curses import json import logging import sys +import urllib.request from dataclasses import dataclass, field from datetime import datetime from textwrap import wrap -from typing import List, Optional, TypedDict, TypeVar +from typing import Any, List, Optional, TypedDict, TypeVar, Union T = TypeVar("T") @@ -42,6 +43,7 @@ class Colors: @dataclass class Message: msg_id: str + story_id: str author: str title: str date: datetime @@ -68,6 +70,28 @@ class HNSearchHit(TypedDict): url: Optional[str] +class HNEntry(TypedDict): + author: Optional[str] + # FIXME: Recursive declarations are not yet supported in TypedDicts + children: List[Any] + created_at_i: int + id: int + parent_id: Optional[int] + text: Optional[str] + title: Optional[str] + url: Optional[str] + + +def fetch(url: str) -> str: + logging.debug(f"Fetching '{url}'...") + + headers = {"User-Agent": "retronews"} + req = urllib.request.Request(url, headers=headers) + resp = urllib.request.urlopen(req).read().decode() + + return resp + + def list_get(lst: List[T], index: int, default: Optional[T] = None) -> Optional[T]: return lst[index] if 0 <= index < len(lst) else default @@ -101,7 +125,13 @@ def cmd_page_down(app: AppState) -> None: def cmd_open(app: AppState) -> None: - app.pager_visible = app.selected_message is not None + if (msg := app.selected_message) is None: + return + + if msg.msg_id == msg.story_id: + app_load_story(app, msg) + + app.pager_visible = True def cmd_close(app: AppState) -> None: @@ -133,6 +163,16 @@ def app_load_messages(app: AppState, messages: List[Message]) -> None: app.selected_message = messages[0] if len(messages) > 0 else None +def app_load_story(app: AppState, story_msg: Message) -> None: + source_id = story_msg.story_id.split("@")[0] + + app.flash = f"Fetching story '{story_msg.story_id}'..." + app_render(app) + + entry = hn_fetch_entry(source_id) + logging.debug(entry) + + def app_render_pager(app: AppState, top: int, height: int) -> None: message = app.selected_message @@ -199,6 +239,7 @@ def app_render(app: AppState) -> None: app_render_pager(app, pager_top, pager_height) app.screen.refresh() + app.flash = "" def app_init_logging() -> None: @@ -221,6 +262,7 @@ def app_init(screen: "curses._CursesWindow") -> AppState: def hn_parse_search_hit(hit: HNSearchHit) -> Message: return Message( msg_id=f"{hit['objectID']}@hn", + story_id=f"{hit['objectID']}@hn", author=hit["author"], title=hit["title"], date=datetime.fromtimestamp(hit["created_at_i"]), @@ -233,12 +275,16 @@ def hn_search_stories() -> List[Message]: return [hn_parse_search_hit(hit) for hit in hits] +def hn_fetch_entry(entry_id: Union[str, int]) -> HNEntry: + resp = fetch(f"http://hn.algolia.com/api/v1/items/{entry_id}") + return json.loads(resp) + + def main(screen: "curses._CursesWindow") -> None: app = app_init(screen) while True: app_render(app) - app.flash = None c = app.screen.getch() KEY_BINDINGS.get(c, cmd_unknown)(app)