Update api/main.py
All checks were successful
Build & Push Football Docker Images / build-push-update (push) Successful in 7s

Clean up
This commit is contained in:
2026-04-12 15:22:32 +00:00
parent 6603142e0b
commit 4509850a29

View File

@@ -4,11 +4,12 @@ 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
GET /next — next match
GET /widget — HTML widget for Homepage iframe
"""
import json
import re
from datetime import datetime
from pathlib import Path
from zoneinfo import ZoneInfo
@@ -94,87 +95,66 @@ def next_match():
@app.get("/widget", response_class=HTMLResponse)
def widget():
"""
Layout: 3 past results + 2 upcoming fixtures
For each match card:
[ home badge ]
[ home name ]
[ score/date ]
[ away badge ]
[ away name ]
[ comp abbr ]
"""
try:
d = load()
past, future = sorted_matches(d)
except HTTPException:
past, future = [], []
# Last 3 past + next 2 future
last3 = past[-3:] if len(past) >= 3 else past
# Most recent 3 past (newest leftmost = reversed) + next 2 future
last3 = list(reversed(past[-3:])) if len(past) >= 3 else list(reversed(past))
next2 = future[:2] if len(future) >= 2 else future
# Pad with empties
empty = {
"home": "", "away": "",
"home_logo": None, "away_logo": None,
"competition": "", "abbr": "",
"date": "", "time": "", "score": None, "is_past": True,
}
while len(last3) < 3:
last3.insert(0, dict(empty))
while len(next2) < 2:
next2.append(dict(empty, is_past=False))
empty_past = {"home": "", "away": "", "home_logo": None, "away_logo": None,
"abbr": "", "date": "", "time": "", "score": None}
empty_future = {**empty_past, "score": None}
all_cards = last3 + next2
while len(last3) < 3:
last3.append(dict(empty_past))
while len(next2) < 2:
next2.append(dict(empty_future))
PORTO_LOGO = "https://a.espncdn.com/i/teamlogos/soccer/500/437.png"
def clean_time(t: str) -> str:
"""Remove ESPN artifacts like 'v', 'v2nd Leg' etc from time field."""
t = t.strip()
# If it looks like a time (contains :) keep it, otherwise TBD
if re.search(r'\d+:\d+', t):
return re.search(r'\d+:\d+\s*(?:AM|PM)?', t).group(0).strip()
return "TBD"
def shorten(name: str) -> str:
if len(name) <= 10:
return name
parts = name.split()
return parts[-1] if parts else name[:10]
def card(m: dict, is_past: bool) -> str:
home = m.get("home", "")
away = m.get("away", "")
home_logo = m.get("home_logo") or PORTO_LOGO
away_logo = m.get("away_logo") or PORTO_LOGO
home_s = shorten(m.get("home", ""))
away_s = shorten(m.get("away", ""))
abbr = m.get("abbr", "")
score = m.get("score")
date = m.get("date", "")
time_ = m.get("time", "")
time_str = m.get("time", "")
time_str = re.sub(r'^v\s*', '', time_str).strip()
time_str = time_str if time_str not in ("", "TBD", "v") else "TBD"
# Shorten names for display
def shorten(name: str) -> str:
if len(name) <= 10:
return name
parts = name.split()
return parts[-1] if parts else name[:10]
home_s = shorten(home)
away_s = shorten(away)
time_raw = m.get("time", "")
if is_past and score:
middle = f'<div class="score">{score}</div>'
cls = "card past"
middle = f'<div class="score">{score}</div>'
cls = "card past"
else:
date_s = "/".join(date.split("/")[:2]) if "/" in date else date
time_part = f"<br>{time_str}" if time_str and time_str != "TBD" else ""
date_s = "/".join(date.split("/")[:2]) if "/" in date else date
time_clean = clean_time(time_raw)
time_part = f"<br>{time_clean}" if time_clean != "TBD" else ""
middle = f'<div class="fixture">{date_s}{time_part}</div>'
cls = "card future"
time_s = f"<br>{time_}" if time_ and time_ != "TBD" else ""
cls = "card future"
return f"""
<div class="{cls}">
<img src="{home_logo}" class="badge" alt="{home}">
return f"""<div class="{cls}">
<img src="{home_logo}" class="badge" alt="">
<div class="name home-name">{home_s}</div>
{middle}
<img src="{away_logo}" class="badge" alt="{away}">
<img src="{away_logo}" class="badge" alt="">
<div class="name away-name">{away_s}</div>
<div class="comp">{abbr}</div>
</div>"""
@@ -197,6 +177,12 @@ def widget():
align-items: center;
overflow: hidden;
}}
.container {{
display: flex;
align-items: center;
width: 100%;
padding: 0 4px;
}}
.section {{
display: flex;
align-items: stretch;
@@ -205,33 +191,26 @@ def widget():
.divider {{
width: 1px;
background: #334155;
margin: 4px 6px;
margin: 4px 8px;
align-self: stretch;
flex-shrink: 0;
}}
.container {{
display: flex;
align-items: center;
width: 100%;
padding: 0 4px;
}}
.card {{
flex: 1;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 2px 4px;
gap: 1px;
min-width: 0;
padding: 2px 6px;
gap: 2px;
min-width: 60px;
}}
.past {{ opacity: 0.7; }}
.past {{ opacity: 0.7; }}
.future {{ opacity: 1; }}
.badge {{
width: 22px;
height: 22px;
width: 24px;
height: 24px;
object-fit: contain;
flex-shrink: 0;
}}
.name {{
font-size: 9px;
@@ -245,16 +224,16 @@ def widget():
.home-name {{ color: #cbd5e1; }}
.away-name {{ color: #94a3b8; }}
.score {{
font-size: 11px;
font-size: 12px;
font-weight: 800;
color: #4ade80;
padding: 1px 0;
padding: 2px 0;
}}
.fixture {{
font-size: 9px;
color: #93c5fd;
text-align: center;
line-height: 1.3;
line-height: 1.4;
}}
.comp {{
font-size: 8px;