#!/usr/bin/env python3 """ FC Porto fixtures API Endpoints: GET /health — liveness GET /data — raw JSON GET /next — next match (for Homepage customapi) GET /widget — self-contained HTML widget for Homepage iframe """ import json from datetime import datetime from pathlib import Path from zoneinfo import ZoneInfo from fastapi import FastAPI, HTTPException from fastapi.middleware.cors import CORSMiddleware from fastapi.responses import HTMLResponse DATA_FILE = Path("/data/porto.json") PT_TZ = ZoneInfo("Europe/Lisbon") app = FastAPI(title="FC Porto API", version="1.0.0") app.add_middleware(CORSMiddleware, allow_origins=["*"], allow_methods=["GET"], allow_headers=["*"]) def load() -> dict: if not DATA_FILE.exists(): raise HTTPException(503, "Data not yet available — scraper hasn't run yet") return json.loads(DATA_FILE.read_text()) @app.get("/health") def health(): return {"status": "ok", "data_exists": DATA_FILE.exists()} @app.get("/data") def data(): return load() @app.get("/next") def next_match(): d = load() future = d.get("all_future", []) if not future: return {"opponent": "No upcoming matches", "date": "—", "competition": "—", "channel": "—"} m = future[0] return { "opponent": m["opponent"], "date": f"{m['date']} {m['time']}", "competition": m["competition"], "channel": m["channel"], "venue": "Home" if m["is_home"] else "Away", } @app.get("/widget", response_class=HTMLResponse) def widget(): """Self-contained HTML widget for Homepage iframe top bar — 2 row layout.""" try: d = load() matches = d.get("display", []) except HTTPException: matches = [] # Competition abbreviations ABBR = { "Liga Portugal": "LP", "Champions League": "CL", "Europa League": "UE", "Taça de Portugal": "TP", "Taça da Liga": "TL", "Supertaça": "ST", } # Pad to exactly 4 empty = {"home": "—", "away": "—", "competition": "—", "date": "—", "time": "—", "score": None, "is_past": False, "channel": "—"} while len(matches) < 4: matches.append(empty) matches = matches[:4] def match_html(m: dict, idx: int) -> str: is_past = m.get("is_past", False) score = m.get("score") home = m.get("home", "—") away = m.get("away", "—") comp = m.get("competition", "—") date = m.get("date", "—") time_ = m.get("time", "") channel = m.get("channel", "") abbr = ABBR.get(comp, comp[:2].upper() if comp != "—" else "—") # Row 1 — "Porto vs Benfica (LP)" row1 = f"{home} vs {away} ({abbr})" # Row 2 — score or date · channel if is_past and score: row2 = f'{score}' card_class = "match past" else: # shorten date: dd/mm/yy → dd/mm short_date = "/".join(date.split("/")[:2]) if "/" in date else date time_part = f" {time_}" if time_ and time_ != "TBD" else "" ch_part = f" · {channel}" if channel and channel not in ("TBD", "—", "") else "" row2 = f'{short_date}{time_part}{ch_part}' card_class = "match future" divider = '
' if idx == 1 else "" return f""" {divider}