▍ humdrum codex / soft

TUI: sync sportsball favorites into sports source on startup

d2c999e6a1ea9a10d88b3e46189c77644c6db0ac
humdrum <me@humdrum.me> · 2026-07-08 16:24

parent 27f29920

1 files changed

tui/dashboard.py +52 −0
@@ -26,6 +26,7 @@ in the terminal (dash accepts a key argument to skip its gum menu).
 """
 from __future__ import annotations
 
+import json
 import re
 import shlex
 import subprocess
@@ -54,6 +55,17 @@ BASE_URL = "http://localhost:4317"
 REFRESH_SECS = 30
 SYS_REFRESH_SECS = 2.5
 REPO = Path(__file__).parent.parent
+
+# sportsball (~/.config/sportsball/config.json) league keys → ESPN sport/league
+# paths used by this dashboard's sports source. Mirrors sportsball's league catalog.
+SPORTSBALL_LEAGUE_PATH = {
+    "worldcup": "soccer/fifa.world",
+    "mlb": "baseball/mlb",
+    "nba": "basketball/nba",
+    "wnba": "basketball/wnba",
+    "nhl": "hockey/nhl",
+    "nfl": "football/nfl",
+}
 
 PAGES = [
     ("page-personal", "Personal"),
@@ -1079,6 +1091,7 @@
         self.refresh_data()
         self.set_interval(REFRESH_SECS, self.refresh_data)
         self.set_interval(60, self.poll_sports_live)
+        self.sync_favorites()
 
     def _q(self, selector: str, expect_type):
         """query_one against the main screen — app.query_one resolves against
@@ -1217,6 +1230,45 @@             render_sports(p) if p else Text("No games.", style="dim italic")
         )
         games = (p or {}).get("games", [])
         self._sports_live = any(g.get("state") == "in" for g in games)
+
+    @work(exclusive=True, group="favsync")
+    async def sync_favorites(self) -> None:
+        # On app open, mirror sportsball's favorite teams into this dashboard's
+        # sports source config (once — not per fetch). Silently no-op if the file
+        # is absent, has no favorites, or the server is unreachable.
+        cfg = Path.home() / ".config" / "sportsball" / "config.json"
+        try:
+            favs = json.loads(cfg.read_text()).get("favorites") or []
+        except Exception:
+            return
+        teams = []
+        for f in favs:
+            path = SPORTSBALL_LEAGUE_PATH.get(f.get("league"))
+            team = f.get("abbr") or f.get("name")
+            if path and team:
+                teams.append({"league": path, "team": team})
+        if not teams:
+            return
+        try:
+            async with httpx.AsyncClient(timeout=15) as c:
+                r = await c.get(f"{BASE_URL}/api/sources")
+                r.raise_for_status()
+                src = next(
+                    (s for s in r.json().get("sources", [])
+                     if (s.get("source") or {}).get("kind") == "sports"),
+                    None,
+                )
+                if not src:
+                    return
+                sid = (src.get("source") or {}).get("id")
+                cur = (src.get("source") or {}).get("config") or {}
+                await c.patch(
+                    f"{BASE_URL}/api/sources/{sid}",
+                    json={"enabled": True, "config": {**cur, "teams": teams}},
+                )
+        except Exception:
+            return
+        self.refresh_data()
 
     @work(exclusive=True, thread=True, group="sys")
     def refresh_system(self) -> None: