"""TRMNL e-ink display feed. Serves today's jokes in the JSON shape TRMNL's Private Plugin "polling" strategy expects, so a device can render them with the TRMNL design system. Polling URL: https://wiki-jokes.obahan.xyz/trmnl.json If TRMNL_TOKEN is set, the device must send it as ?token=*** header (polling headers: trmnl-token=). """ import os from fastapi import Request from fastapi.responses import JSONResponse from . import db from .generator import today_local REFRESH_INTERVAL_SECONDS = 900 # jokes only change once a day; match TRMNL cadence def _batch(): """Today's batch, falling back to the latest available (stale flag set).""" day = today_local() batch = db.get_day(day) stale = False if batch is None: batch = db.get_latest() stale = True return batch, stale def payload(batch: dict, stale: bool) -> dict: return { "refresh_interval": REFRESH_INTERVAL_SECONDS, "day": batch["day"], "stale": stale, "article_title": batch["article_title"], "article_url": batch["article_url"], "jokes": [{"text": j} for j in batch["jokes"]], } def trmnl_endpoint(request: Request): token = os.environ.get("TRMNL_TOKEN", "") if token: supplied = request.query_params.get("token") or request.headers.get("trmnl-token") if supplied != token: return JSONResponse({"error": "unauthorized"}, status_code=403) batch, stale = _batch() if batch is None: # Keep the last screen on the device instead of blanking it. return JSONResponse( {"status": "no_data", "refresh_interval": REFRESH_INTERVAL_SECONDS}, status_code=204, ) return JSONResponse(payload(batch, stale))