Update api/main.py
All checks were successful
Build & Push Football Docker Images / build-push-update (push) Successful in 7s
All checks were successful
Build & Push Football Docker Images / build-push-update (push) Successful in 7s
Clean up
This commit is contained in:
117
api/main.py
117
api/main.py
@@ -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 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
|
||||
abbr = m.get("abbr", "—")
|
||||
score = m.get("score")
|
||||
date = m.get("date", "—")
|
||||
time_ = m.get("time", "")
|
||||
time_str = m.get("time", "")
|
||||
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"
|
||||
|
||||
|
||||
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)
|
||||
def card(m: dict, is_past: bool) -> str:
|
||||
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_raw = m.get("time", "")
|
||||
|
||||
if is_past and score:
|
||||
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 ""
|
||||
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>'
|
||||
|
||||
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; }}
|
||||
.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;
|
||||
|
||||
Reference in New Issue
Block a user