mirror of
https://github.com/Death916/deathclock.git
synced 2026-04-10 03:04:40 -07:00
nfl stats
This commit is contained in:
parent
a1b15b86aa
commit
bdbc55a425
2 changed files with 381 additions and 195 deletions
|
|
@ -1,50 +1,55 @@
|
||||||
# deathclock.py (or your main app file name)
|
# deathclock.py
|
||||||
|
import asyncio
|
||||||
|
|
||||||
|
# from utils.alarm import Alarm # Commented out import
|
||||||
|
import logging
|
||||||
|
import time
|
||||||
|
from datetime import datetime, timezone
|
||||||
|
|
||||||
|
# --- Import typing for hints ---
|
||||||
|
from typing import Any, Dict, List
|
||||||
|
|
||||||
import reflex as rx
|
import reflex as rx
|
||||||
from datetime import datetime, timezone
|
|
||||||
import asyncio
|
from utils.news import News
|
||||||
import time
|
from utils.scores import NBAScores, mlbScores
|
||||||
# --- Import typing for hints ---
|
|
||||||
from typing import List, Dict, Any
|
|
||||||
from rxconfig import config
|
|
||||||
# --- Import your Weather utility ---
|
# --- Import your Weather utility ---
|
||||||
from utils.weather import Weather
|
from utils.weather import Weather
|
||||||
|
|
||||||
from utils.scores import NBAScores, mlbScores
|
|
||||||
from utils.news import News
|
|
||||||
# from utils.alarm import Alarm # Commented out import
|
|
||||||
import logging
|
|
||||||
|
|
||||||
# --- Constants ---
|
# --- Constants ---
|
||||||
WEATHER_IMAGE_PATH = "/weather.jpg" # Web path in assets folder
|
WEATHER_IMAGE_PATH = "/weather.jpg" # Web path in assets folder
|
||||||
WEATHER_FETCH_INTERVAL = 360
|
WEATHER_FETCH_INTERVAL = 360
|
||||||
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
|
logging.basicConfig(
|
||||||
|
level=logging.INFO, format="%(asctime)s - %(levelname)s - %(message)s"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class State(rx.State):
|
class State(rx.State):
|
||||||
|
|
||||||
# --- State Variables ---
|
# --- State Variables ---
|
||||||
|
|
||||||
current_time: str = "" # Note: rx.moment replaces the need for this if used for display
|
current_time: str = (
|
||||||
|
"" # Note: rx.moment replaces the need for this if used for display
|
||||||
|
)
|
||||||
alarm_time: str = ""
|
alarm_time: str = ""
|
||||||
alarms: list = []
|
alarms: list = []
|
||||||
news: List[Dict[str, Any]] = []
|
news: List[Dict[str, Any]] = []
|
||||||
nba_scores: List[Dict[str, Any]] = []
|
nba_scores: List[Dict[str, Any]] = []
|
||||||
mlb_scores: List[Dict[str, Any]] = []
|
mlb_scores: List[Dict[str, Any]] = []
|
||||||
_news_client: News | None = None # This will be set in the constructor
|
nfl_scores: List[Dict[str, Any]] = []
|
||||||
|
_news_client: News | None = None # This will be set in the constructor
|
||||||
last_weather_update: str = "Never"
|
last_weather_update: str = "Never"
|
||||||
weather_img: str = WEATHER_IMAGE_PATH
|
weather_img: str = WEATHER_IMAGE_PATH
|
||||||
_weather_client: Weather | None = None # This will be set in the constructor
|
_weather_client: Weather | None = None # This will be set in the constructor
|
||||||
_mlb_client: mlbScores | None = None
|
_mlb_client: mlbScores | None = None
|
||||||
_nba_client: NBAScores | None = None
|
_nba_client: NBAScores | None = None
|
||||||
last_sports_update: float = 0.0
|
last_sports_update: float = 0.0
|
||||||
|
|
||||||
|
|
||||||
# --- Initialize Utility Client ---
|
# --- Initialize Utility Client ---
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
super().__init__(*args, **kwargs)
|
super().__init__(*args, **kwargs)
|
||||||
# Initialize background clients
|
# Initialize background clients
|
||||||
try:
|
try:
|
||||||
|
|
||||||
self._weather_client = Weather()
|
self._weather_client = Weather()
|
||||||
self._news_client = News()
|
self._news_client = News()
|
||||||
self._mlb_client = mlbScores()
|
self._mlb_client = mlbScores()
|
||||||
|
|
@ -52,9 +57,9 @@ class State(rx.State):
|
||||||
logging.info("Weather client initialized successfully.")
|
logging.info("Weather client initialized successfully.")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logging.error(f"Failed to initialize Weather client: {e}", exc_info=True)
|
logging.error(f"Failed to initialize Weather client: {e}", exc_info=True)
|
||||||
self._weather_client = None # Mark as unusable
|
self._weather_client = None
|
||||||
# Set error state if needed
|
# Set error state if needed
|
||||||
self.weather_img = "/error_placeholder.png" # Provide a placeholder error image
|
self.weather_img = "/error_placeholder.png"
|
||||||
self.last_weather_update = "Client Init Error"
|
self.last_weather_update = "Client Init Error"
|
||||||
self.mlb_scores = ""
|
self.mlb_scores = ""
|
||||||
self.nba_scores = ""
|
self.nba_scores = ""
|
||||||
|
|
@ -63,7 +68,9 @@ class State(rx.State):
|
||||||
# --- on_load Handler ---
|
# --- on_load Handler ---
|
||||||
async def start_background_tasks(self):
|
async def start_background_tasks(self):
|
||||||
"""Starts the weather background task when the page loads."""
|
"""Starts the weather background task when the page loads."""
|
||||||
rx.remove_local_storage("chakra-ui-color-mode") # trying to test themes remove after
|
rx.remove_local_storage(
|
||||||
|
"chakra-ui-color-mode"
|
||||||
|
) # trying to test themes remove after
|
||||||
logging.info("Triggering background tasks: Weather")
|
logging.info("Triggering background tasks: Weather")
|
||||||
# Return a list containing the handler references
|
# Return a list containing the handler references
|
||||||
return [State.fetch_weather, State.fetch_sports]
|
return [State.fetch_weather, State.fetch_sports]
|
||||||
|
|
@ -72,15 +79,19 @@ class State(rx.State):
|
||||||
|
|
||||||
@rx.event(background=True)
|
@rx.event(background=True)
|
||||||
async def fetch_sports(self):
|
async def fetch_sports(self):
|
||||||
|
|
||||||
# Fetches sports scores periodically
|
# Fetches sports scores periodically
|
||||||
while True:
|
while True:
|
||||||
try:
|
try:
|
||||||
logging.info("Fetching sports scores...")
|
logging.info("Fetching sports scores...")
|
||||||
# Fetch MLB and NBA scores
|
# Fetch MLB and NBA scores
|
||||||
# check if sports has updated in last 5 minutes if so skip
|
# check if sports has updated in last 5 minutes if so skip
|
||||||
if self.last_sports_update and (time.time() - self.last_sports_update) < 300:
|
if (
|
||||||
logging.info("Sports scores already updated within the last 5 minutes. Skipping fetch.")
|
self.last_sports_update
|
||||||
|
and (time.time() - self.last_sports_update) < 300
|
||||||
|
):
|
||||||
|
logging.info(
|
||||||
|
"Sports scores already updated within the last 5 minutes. Skipping fetch."
|
||||||
|
)
|
||||||
await asyncio.sleep(300)
|
await asyncio.sleep(300)
|
||||||
continue
|
continue
|
||||||
|
|
||||||
|
|
@ -105,19 +116,25 @@ class State(rx.State):
|
||||||
async with self:
|
async with self:
|
||||||
self.mlb_scores = mlb_scores
|
self.mlb_scores = mlb_scores
|
||||||
self.nba_scores = nba_scores
|
self.nba_scores = nba_scores
|
||||||
self.last_sports_update = time.time() # Update last sports update time
|
self.last_sports_update = (
|
||||||
logging.info(f"Fetched {len(mlb_scores)} MLB scores and {len(nba_scores)} NBA scores.")
|
time.time()
|
||||||
|
) # Update last sports update time
|
||||||
|
logging.info(
|
||||||
|
f"Fetched {len(mlb_scores)} MLB scores and {len(nba_scores)} NBA scores."
|
||||||
|
)
|
||||||
yield # Update frontend
|
yield # Update frontend
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logging.error(f"Error in fetch_sports background task: {e}", exc_info=True)
|
logging.error(
|
||||||
|
f"Error in fetch_sports background task: {e}", exc_info=True
|
||||||
|
)
|
||||||
await asyncio.sleep(500)
|
await asyncio.sleep(500)
|
||||||
|
|
||||||
# (Commented out news fetcher)
|
# (Commented out news fetcher)
|
||||||
"""
|
|
||||||
@rx.event(background=True)
|
@rx.event(background=True)
|
||||||
async def fetch_news(self):
|
async def fetch_news(self):
|
||||||
#Fetches news periodically
|
# Fetches news periodically
|
||||||
# Placeholder for the actual news fetching logic
|
# Placeholder for the actual news fetching logic
|
||||||
while True:
|
while True:
|
||||||
try:
|
try:
|
||||||
|
|
@ -134,25 +151,26 @@ class State(rx.State):
|
||||||
self.news = news_items
|
self.news = news_items
|
||||||
yield
|
yield
|
||||||
|
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logging.error(f"Error in fetch_news background task: {e}", exc_info=True)
|
logging.error(
|
||||||
|
f"Error in fetch_news background task: {e}", exc_info=True
|
||||||
|
)
|
||||||
await asyncio.sleep(500)
|
await asyncio.sleep(500)
|
||||||
|
|
||||||
|
|
||||||
"""
|
|
||||||
# --- Weather Background Task ---
|
# --- Weather Background Task ---
|
||||||
@rx.event(background=True)
|
@rx.event(background=True)
|
||||||
async def fetch_weather(self):
|
async def fetch_weather(self):
|
||||||
"""Fetches the weather screenshot periodically."""
|
"""Fetches the weather screenshot periodically."""
|
||||||
# Check if the client initialized correctly
|
# Check if the client initialized correctly
|
||||||
if not hasattr(self, '_weather_client') or self._weather_client is None:
|
if not hasattr(self, "_weather_client") or self._weather_client is None:
|
||||||
logging.warning("Weather client not initialized. Stopping fetch_weather task.")
|
logging.warning(
|
||||||
|
"Weather client not initialized. Stopping fetch_weather task."
|
||||||
|
)
|
||||||
|
|
||||||
async with self:
|
async with self:
|
||||||
self.last_weather_update = "Error: Weather client unavailable"
|
self.last_weather_update = "Error: Weather client unavailable"
|
||||||
yield
|
yield
|
||||||
return # Exit the task permanently if client init failed
|
return # Exit the task permanently if client init failed
|
||||||
|
|
||||||
while True:
|
while True:
|
||||||
try:
|
try:
|
||||||
|
|
@ -162,132 +180,187 @@ class State(rx.State):
|
||||||
|
|
||||||
if img_web_path:
|
if img_web_path:
|
||||||
async with self:
|
async with self:
|
||||||
timestamp = int(time.time()) # Unused timestamp, kept as per instruction
|
timestamp = int(
|
||||||
|
time.time()
|
||||||
|
) # Unused timestamp, kept as per instruction
|
||||||
self.weather_img = f"{img_web_path}"
|
self.weather_img = f"{img_web_path}"
|
||||||
self.last_weather_update = datetime.now(timezone.utc).strftime('%Y-%m-%d %H:%M:%S UTC')
|
self.last_weather_update = datetime.now(timezone.utc).strftime(
|
||||||
logging.info(f"State.weather_img updated to: {self.weather_img}")
|
"%Y-%m-%d %H:%M:%S UTC"
|
||||||
yield # Update frontend
|
)
|
||||||
|
logging.info(
|
||||||
|
f"State.weather_img updated to: {self.weather_img}"
|
||||||
|
)
|
||||||
|
yield # Update frontend
|
||||||
|
|
||||||
else:
|
else:
|
||||||
logging.warning("get_weather_screenshot returned None. State not updated.")
|
logging.warning(
|
||||||
|
"get_weather_screenshot returned None. State not updated."
|
||||||
|
)
|
||||||
# Optionally update status
|
# Optionally update status
|
||||||
async with self:
|
async with self:
|
||||||
self.last_weather_update = f"Failed fetch @ {datetime.now(timezone.utc).strftime('%H:%M:%S UTC')}"
|
self.last_weather_update = f"Failed fetch @ {datetime.now(timezone.utc).strftime('%H:%M:%S UTC')}"
|
||||||
yield
|
yield
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logging.error(f"Error in fetch_weather background task: {e}", exc_info=True)
|
logging.error(
|
||||||
async with self: # Update state to show error
|
f"Error in fetch_weather background task: {e}", exc_info=True
|
||||||
self.last_weather_update = f"Error @ {datetime.now(timezone.utc).strftime('%H:%M:%S UTC')}"
|
)
|
||||||
|
async with self: # Update state to show error
|
||||||
|
self.last_weather_update = (
|
||||||
|
f"Error @ {datetime.now(timezone.utc).strftime('%H:%M:%S UTC')}"
|
||||||
|
)
|
||||||
yield
|
yield
|
||||||
|
|
||||||
await asyncio.sleep(WEATHER_FETCH_INTERVAL)
|
await asyncio.sleep(WEATHER_FETCH_INTERVAL)
|
||||||
|
|
||||||
|
|
||||||
def index() -> rx.Component:
|
def index() -> rx.Component:
|
||||||
|
# Build NBA scores list (safe access with .get where appropriate)
|
||||||
return rx.container(
|
nba_scores_list = rx.vstack(
|
||||||
rx.theme_panel(default_open=False),
|
rx.foreach(
|
||||||
|
State.nba_scores,
|
||||||
rx.center(
|
lambda score: rx.card(
|
||||||
rx.vstack(
|
rx.text(
|
||||||
|
f"{score.get('away_team', '?')} {score.get('away_score', '-')} @ "
|
||||||
rx.button(
|
f"{score.get('home_team', '?')} {score.get('home_score', '-')} "
|
||||||
rx.moment(interval=1000, format="HH:mm:ss"),
|
f"(Status: {score.get('status', '?')})"
|
||||||
font_size="4xl",
|
)
|
||||||
font_weight="bold",
|
|
||||||
color="white",
|
|
||||||
background_color="#6f42c1",
|
|
||||||
),
|
|
||||||
|
|
||||||
|
|
||||||
rx.flex(
|
|
||||||
rx.card(
|
|
||||||
rx.box(
|
|
||||||
rx.text("NBA Scores"),
|
|
||||||
rx.foreach(
|
|
||||||
State.nba_scores,
|
|
||||||
lambda score: rx.vstack(
|
|
||||||
rx.card(
|
|
||||||
rx.text(f"{score['away_team']} {score['away_score']} @ "
|
|
||||||
f"{score['home_team']} {score['home_score']} "
|
|
||||||
f"(Status: {score['status']})"),
|
|
||||||
),
|
|
||||||
spacing="1",
|
|
||||||
padding="2",
|
|
||||||
),
|
|
||||||
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
|
|
||||||
rx.card(
|
|
||||||
rx.vstack(
|
|
||||||
rx.heading("Weather", size="4"),
|
|
||||||
|
|
||||||
rx.image(
|
|
||||||
|
|
||||||
src=State.weather_img,
|
|
||||||
alt="Current weather conditions for Sacramento",
|
|
||||||
width="100%",
|
|
||||||
height="auto",
|
|
||||||
|
|
||||||
object_fit="contain",
|
|
||||||
border_radius="var(--radius-3)",
|
|
||||||
),
|
|
||||||
rx.text(
|
|
||||||
f"Last Update: {State.last_weather_update}",
|
|
||||||
size="1",
|
|
||||||
color_scheme="gray",
|
|
||||||
padding_top="0.5em"
|
|
||||||
),
|
|
||||||
align="center",
|
|
||||||
spacing="2",
|
|
||||||
)
|
|
||||||
),
|
|
||||||
|
|
||||||
rx.card(
|
|
||||||
rx.box(
|
|
||||||
rx.text("MLB Scores"),
|
|
||||||
# Add rx.vstack here to control spacing of foreach items
|
|
||||||
rx.vstack(
|
|
||||||
rx.foreach(
|
|
||||||
State.mlb_scores,
|
|
||||||
# Lambda now returns the styled card directly
|
|
||||||
lambda score: rx.card(
|
|
||||||
rx.text(f"{score.get('away_team','?')} {score.get('away_score','-')} @ " # Use .get() for safety
|
|
||||||
f"{score.get('home_team','?')} {score.get('home_score','-')} "
|
|
||||||
f"({score.get('status','?')})",
|
|
||||||
size="1",
|
|
||||||
),
|
|
||||||
size="1",
|
|
||||||
padding="1",
|
|
||||||
variant="surface",
|
|
||||||
width="100%",
|
|
||||||
),
|
|
||||||
),
|
|
||||||
spacing="1",
|
|
||||||
align_items="stretch",
|
|
||||||
width="100%",
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
|
|
||||||
spacing="3",
|
|
||||||
width="100%",
|
|
||||||
justify="center",
|
|
||||||
align="stretch",
|
|
||||||
),
|
|
||||||
|
|
||||||
align="center",
|
|
||||||
spacing="4",
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
spacing="1",
|
||||||
|
padding="2",
|
||||||
|
align_items="stretch",
|
||||||
|
width="100%",
|
||||||
|
)
|
||||||
|
|
||||||
padding="2rem",
|
nba_card = rx.card(
|
||||||
max_width="1200px",
|
rx.box(
|
||||||
margin="0 auto",
|
rx.text("NBA Scores"),
|
||||||
),
|
nba_scores_list,
|
||||||
''' # Commented out style block
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
# Weather card
|
||||||
|
weather_card = rx.card(
|
||||||
|
rx.vstack(
|
||||||
|
rx.heading("Weather", size="4"),
|
||||||
|
rx.image(
|
||||||
|
src=State.weather_img,
|
||||||
|
alt="Current weather conditions for Sacramento",
|
||||||
|
width="100%",
|
||||||
|
height="auto",
|
||||||
|
object_fit="contain",
|
||||||
|
border_radius="var(--radius-3)",
|
||||||
|
),
|
||||||
|
rx.text(
|
||||||
|
f"Last Update: {State.last_weather_update}",
|
||||||
|
size="1",
|
||||||
|
color_scheme="gray",
|
||||||
|
padding_top="0.5em",
|
||||||
|
),
|
||||||
|
align="center",
|
||||||
|
spacing="2",
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
# MLB scores list
|
||||||
|
mlb_scores_list = rx.vstack(
|
||||||
|
rx.foreach(
|
||||||
|
State.mlb_scores,
|
||||||
|
lambda score: rx.card(
|
||||||
|
rx.text(
|
||||||
|
f"{score.get('away_team', '?')} {score.get('away_score', '-')} @ "
|
||||||
|
f"{score.get('home_team', '?')} {score.get('home_score', '-')} "
|
||||||
|
f"({score.get('status', '?')})",
|
||||||
|
size="1",
|
||||||
|
),
|
||||||
|
size="1",
|
||||||
|
padding="1",
|
||||||
|
variant="surface",
|
||||||
|
width="100%",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
spacing="1",
|
||||||
|
align_items="stretch",
|
||||||
|
width="100%",
|
||||||
|
)
|
||||||
|
|
||||||
|
mlb_card = rx.card(
|
||||||
|
rx.box(
|
||||||
|
rx.text("MLB Scores"),
|
||||||
|
mlb_scores_list,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
nfl_scores_list = rx.vstack(
|
||||||
|
rx.foreach(
|
||||||
|
State.nfl_scores,
|
||||||
|
lambda score: rx.card(
|
||||||
|
rx.text(
|
||||||
|
f"{score.get('away_team', '?')} {score.get('away_score', '-')} @ "
|
||||||
|
f"{score.get('home_team', '?')} {score.get('home_score', '-')} "
|
||||||
|
f"({score.get('status', '?')})",
|
||||||
|
size="1",
|
||||||
|
),
|
||||||
|
size="1",
|
||||||
|
padding="1",
|
||||||
|
variant="surface",
|
||||||
|
width="100%",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
spacing="1",
|
||||||
|
align_items="stretch",
|
||||||
|
width="100%",
|
||||||
|
)
|
||||||
|
|
||||||
|
nfl_card = rx.card(
|
||||||
|
rx.box(
|
||||||
|
rx.text("NFL Scores"),
|
||||||
|
nfl_scores_list,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
# Main flexible content area
|
||||||
|
main_flex = rx.flex(
|
||||||
|
nba_card,
|
||||||
|
weather_card,
|
||||||
|
mlb_card,
|
||||||
|
nfl_card,
|
||||||
|
spacing="3",
|
||||||
|
width="100%",
|
||||||
|
justify="center",
|
||||||
|
align="stretch",
|
||||||
|
)
|
||||||
|
|
||||||
|
# Top clock button
|
||||||
|
clock_button = rx.button(
|
||||||
|
rx.moment(interval=1000, format="HH:mm:ss"),
|
||||||
|
font_size="4xl",
|
||||||
|
font_weight="bold",
|
||||||
|
color="white",
|
||||||
|
background_color="#6f42c1",
|
||||||
|
)
|
||||||
|
|
||||||
|
# Compose the page
|
||||||
|
page = rx.container( # pyright: ignore[reportReturnType]
|
||||||
|
rx.theme_panel(default_open=False),
|
||||||
|
rx.center(
|
||||||
|
rx.vstack(
|
||||||
|
clock_button,
|
||||||
|
main_flex,
|
||||||
|
align="center",
|
||||||
|
spacing="4",
|
||||||
|
)
|
||||||
|
),
|
||||||
|
padding="2rem",
|
||||||
|
max_width="1200px",
|
||||||
|
margin="0 auto",
|
||||||
|
)
|
||||||
|
|
||||||
|
return page
|
||||||
|
|
||||||
|
|
||||||
|
""" # Commented out style block
|
||||||
style = {
|
style = {
|
||||||
|
|
||||||
"background_color": "black",
|
"background_color": "black",
|
||||||
|
|
@ -299,7 +372,7 @@ style = {
|
||||||
"color": "#ffffff",
|
"color": "#ffffff",
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
''' # Commented out style block
|
"""
|
||||||
app = rx.App(
|
app = rx.App(
|
||||||
theme=rx.theme(
|
theme=rx.theme(
|
||||||
appearance="dark",
|
appearance="dark",
|
||||||
|
|
@ -309,14 +382,12 @@ app = rx.App(
|
||||||
gray_color="mauve",
|
gray_color="mauve",
|
||||||
has_background=True,
|
has_background=True,
|
||||||
),
|
),
|
||||||
#style=style # using theme instead
|
# style=style # using theme instead
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
app.add_page(
|
app.add_page(
|
||||||
index,
|
index,
|
||||||
title="DeathClock", # Example title
|
title="DeathClock", # Example title
|
||||||
on_load=State.start_background_tasks # Trigger tasks when this page loads
|
on_load=State.start_background_tasks, # Trigger tasks when this page loads
|
||||||
)
|
)
|
||||||
|
|
|
||||||
197
utils/scores.py
197
utils/scores.py
|
|
@ -3,26 +3,29 @@ from nba_api.live.nba.endpoints import scoreboard
|
||||||
from datetime import datetime, timedelta
|
from datetime import datetime, timedelta
|
||||||
import statsapi
|
import statsapi
|
||||||
import reflex as rx
|
import reflex as rx
|
||||||
import logging # Use logging for consistency
|
import logging # Use logging for consistency
|
||||||
|
|
||||||
|
|
||||||
|
logging.basicConfig(
|
||||||
|
level=logging.INFO, format="%(asctime)s - %(levelname)s - %(message)s"
|
||||||
|
)
|
||||||
|
|
||||||
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
|
|
||||||
|
|
||||||
class NBAScores(rx.Base):
|
class NBAScores(rx.Base):
|
||||||
|
async def get_scores(self): # Make async to match usage in State
|
||||||
async def get_scores(self): # Make async to match usage in State
|
|
||||||
"""Fetches NBA scores and returns them as a list of dicts."""
|
"""Fetches NBA scores and returns them as a list of dicts."""
|
||||||
try:
|
try:
|
||||||
# Get scoreboard data
|
# Get scoreboard data
|
||||||
# Consider running blocking IO in a thread pool if it becomes an issue
|
|
||||||
board = scoreboard.ScoreBoard()
|
board = scoreboard.ScoreBoard()
|
||||||
data = board.get_dict()
|
data = board.get_dict()
|
||||||
|
|
||||||
# Check if we have a valid scoreboard response
|
# Check if we have a valid scoreboard response
|
||||||
if 'scoreboard' not in data:
|
if "scoreboard" not in data:
|
||||||
logging.warning("No NBA scoreboard data found in response")
|
logging.warning("No NBA scoreboard data found in response")
|
||||||
return []
|
return []
|
||||||
|
|
||||||
games = data['scoreboard'].get('games', [])
|
games = data["scoreboard"].get("games", [])
|
||||||
if not games:
|
if not games:
|
||||||
logging.info("No active NBA games found in scoreboard")
|
logging.info("No active NBA games found in scoreboard")
|
||||||
return []
|
return []
|
||||||
|
|
@ -31,87 +34,199 @@ class NBAScores(rx.Base):
|
||||||
for game in games:
|
for game in games:
|
||||||
try:
|
try:
|
||||||
game_data = {
|
game_data = {
|
||||||
'home_team': game['homeTeam']['teamTricode'],
|
"home_team": game["homeTeam"]["teamTricode"],
|
||||||
'home_score': game['homeTeam']['score'],
|
"home_score": game["homeTeam"]["score"],
|
||||||
'away_team': game['awayTeam']['teamTricode'],
|
"away_team": game["awayTeam"]["teamTricode"],
|
||||||
'away_score': game['awayTeam']['score'],
|
"away_score": game["awayTeam"]["score"],
|
||||||
'period': game['period'],
|
"period": game["period"],
|
||||||
'status': game['gameStatusText']
|
"status": game["gameStatusText"],
|
||||||
}
|
}
|
||||||
scores_list.append(game_data)
|
scores_list.append(game_data)
|
||||||
except KeyError as e:
|
except KeyError as e:
|
||||||
logging.error(f"Error processing NBA game data: {e} for game: {game.get('gameId', 'N/A')}")
|
logging.error(
|
||||||
continue # Skip this game
|
f"Error processing NBA game data: {e} for game: {game.get('gameId', 'N/A')}"
|
||||||
|
)
|
||||||
|
continue # Skip this game
|
||||||
|
|
||||||
# No need to store in self._scores if this is just a utility
|
|
||||||
return scores_list
|
return scores_list
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logging.error(f"Error fetching NBA scores: {e}", exc_info=True)
|
logging.error(f"Error fetching NBA scores: {e}", exc_info=True)
|
||||||
return [] # Return empty list on error
|
return [] # Return empty list on error
|
||||||
|
|
||||||
|
|
||||||
class mlbScores(rx.Base):
|
class mlbScores(rx.Base):
|
||||||
|
async def get_games(self): # Make async
|
||||||
async def get_games(self): # Make async
|
|
||||||
"""Fetches MLB games data."""
|
"""Fetches MLB games data."""
|
||||||
try:
|
try:
|
||||||
games = statsapi.schedule() # Assuming sync for now
|
games = statsapi.schedule() # Assuming sync for now
|
||||||
return games
|
return games
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logging.error(f"Error fetching MLB games: {e}", exc_info=True)
|
logging.error(f"Error fetching MLB games: {e}", exc_info=True)
|
||||||
return []
|
return []
|
||||||
|
|
||||||
async def get_scores(self): # Make async to match usage in State
|
async def get_scores(self): # Make async to match usage in State
|
||||||
"""Fetches and formats MLB scores."""
|
"""Fetches and formats MLB scores."""
|
||||||
games = await self.get_games() # Await the async get_games
|
games = await self.get_games() # Await the async get_games
|
||||||
scores_list = []
|
scores_list = []
|
||||||
if not games:
|
if not games:
|
||||||
logging.info("No MLB games found today.")
|
logging.info("No MLB games found today.")
|
||||||
return []
|
return []
|
||||||
for game in games:
|
for game in games:
|
||||||
try:
|
try:
|
||||||
# Ensure keys exist, provide defaults if necessary
|
# Ensure keys exist, provide defaults if necessary
|
||||||
game_data = {
|
game_data = {
|
||||||
'home_team': game.get('home_name', 'N/A'),
|
"home_team": game.get("home_name", "N/A"),
|
||||||
'home_score': game.get('home_score', '-'),
|
"home_score": game.get("home_score", "-"),
|
||||||
'away_team': game.get('away_name', 'N/A'),
|
"away_team": game.get("away_name", "N/A"),
|
||||||
'away_score': game.get('away_score', '-'),
|
"away_score": game.get("away_score", "-"),
|
||||||
'status': game.get('status', 'Scheduled') # Provide default status
|
"status": game.get("status", "Scheduled"), # Provide default status
|
||||||
}
|
}
|
||||||
scores_list.append(game_data)
|
scores_list.append(game_data)
|
||||||
except KeyError as e:
|
except KeyError as e:
|
||||||
# This block might be less necessary with .get() above
|
# This block might be less necessary with .get() above
|
||||||
logging.error(f"Error processing MLB game data: {e} for game: {game.get('game_id', 'N/A')}")
|
logging.error(
|
||||||
continue # Skip this game
|
f"Error processing MLB game data: {e} for game: {game.get('game_id', 'N/A')}"
|
||||||
return scores_list # RETURN THE LIST
|
)
|
||||||
|
continue # Skip this game
|
||||||
|
return scores_list # RETURN THE LIST
|
||||||
|
|
||||||
|
|
||||||
|
class nflScores:
|
||||||
|
async def get_scores(self):
|
||||||
|
# get nfl scores from espn
|
||||||
|
nfl_scores = []
|
||||||
|
try:
|
||||||
|
import aiohttp
|
||||||
|
|
||||||
|
# use current local date in YYYYMMDD format
|
||||||
|
date_str = datetime.now().strftime("%Y%m%d")
|
||||||
|
url = f"https://site.api.espn.com/apis/site/v2/sports/football/nfl/scoreboard?dates={date_str}"
|
||||||
|
|
||||||
|
async with aiohttp.ClientSession() as session:
|
||||||
|
async with session.get(url, timeout=15) as resp:
|
||||||
|
if resp.status != 200:
|
||||||
|
logging.error(
|
||||||
|
f"ESPN NFL scoreboard returned status {resp.status}"
|
||||||
|
)
|
||||||
|
return []
|
||||||
|
data = await resp.json()
|
||||||
|
|
||||||
|
events = data.get("events", [])
|
||||||
|
if not events:
|
||||||
|
logging.info("No NFL games found for date %s", date_str)
|
||||||
|
return []
|
||||||
|
|
||||||
|
nfl_scores = []
|
||||||
|
for ev in events:
|
||||||
|
try:
|
||||||
|
competitions = ev.get("competitions", [])
|
||||||
|
if not competitions:
|
||||||
|
logging.warning(
|
||||||
|
"No competitions found for event: %s", ev.get("id")
|
||||||
|
)
|
||||||
|
continue
|
||||||
|
comp = competitions[0]
|
||||||
|
competitors = comp.get("competitors", [])
|
||||||
|
home_team = away_team = "N/A"
|
||||||
|
home_score = away_score = "-"
|
||||||
|
|
||||||
|
for c in competitors:
|
||||||
|
team = c.get("team", {}) or {}
|
||||||
|
abbr = (
|
||||||
|
team.get("abbreviation")
|
||||||
|
or team.get("displayName")
|
||||||
|
or team.get("shortDisplayName")
|
||||||
|
or "N/A"
|
||||||
|
)
|
||||||
|
score = (
|
||||||
|
c.get("score", "-") if c.get("score") is not None else "-"
|
||||||
|
)
|
||||||
|
homeAway = c.get("homeAway", "").lower()
|
||||||
|
if homeAway == "home":
|
||||||
|
home_team = abbr
|
||||||
|
home_score = score
|
||||||
|
else:
|
||||||
|
away_team = abbr
|
||||||
|
away_score = score
|
||||||
|
|
||||||
|
status = comp.get("status", {}) or {}
|
||||||
|
status_type = status.get("type", {}) or {}
|
||||||
|
status_text = (
|
||||||
|
status_type.get("shortDetail")
|
||||||
|
or status_type.get("description")
|
||||||
|
or status_type.get("state")
|
||||||
|
or status.get("type")
|
||||||
|
or status.get("detail")
|
||||||
|
or "Unknown"
|
||||||
|
)
|
||||||
|
|
||||||
|
game_data = {
|
||||||
|
"home_team": home_team,
|
||||||
|
"home_score": home_score,
|
||||||
|
"away_team": away_team,
|
||||||
|
"away_score": away_score,
|
||||||
|
"status": status_text,
|
||||||
|
}
|
||||||
|
nfl_scores.append(game_data)
|
||||||
|
except Exception as e:
|
||||||
|
logging.error(
|
||||||
|
f"Error processing NFL event: {e} for event {ev.get('id')}",
|
||||||
|
exc_info=True,
|
||||||
|
)
|
||||||
|
continue
|
||||||
|
print(nfl_scores)
|
||||||
|
return nfl_scores
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logging.error(f"Error fetching NFL scores: {e}", exc_info=True)
|
||||||
|
return []
|
||||||
|
|
||||||
|
|
||||||
# Keep the __main__ block for testing if desired, but update calls to be async if needed
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
import asyncio
|
import asyncio
|
||||||
|
|
||||||
async def test_scores():
|
async def test_scores():
|
||||||
nba_client = NBAScores()
|
nba_client = NBAScores()
|
||||||
nba_results = await nba_client.get_scores() # await async method
|
nba_results = await nba_client.get_scores() # await async method
|
||||||
|
|
||||||
print("\nNBA Scores:")
|
print("\nNBA Scores:")
|
||||||
if nba_results:
|
if nba_results:
|
||||||
for game in nba_results:
|
for game in nba_results:
|
||||||
print(f"{game['away_team']} {game['away_score']} @ "
|
print(
|
||||||
f"{game['home_team']} {game['home_score']} "
|
f"{game['away_team']} {game['away_score']} @ "
|
||||||
f"(Status: {game['status']})")
|
f"{game['home_team']} {game['home_score']} "
|
||||||
|
f"(Status: {game['status']})"
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
print("No NBA games/scores available")
|
print("No NBA games/scores available")
|
||||||
|
|
||||||
mlb_client = mlbScores()
|
mlb_client = mlbScores()
|
||||||
mlb_results = await mlb_client.get_scores() # await async method
|
mlb_results = await mlb_client.get_scores() # await async method
|
||||||
|
|
||||||
print("\nMLB Scores:")
|
print("\nMLB Scores:")
|
||||||
if mlb_results:
|
if mlb_results:
|
||||||
for game in mlb_results:
|
for game in mlb_results:
|
||||||
print(f"{game['away_team']} {game['away_score']} @ "
|
print(
|
||||||
f"{game['home_team']} {game['home_score']} "
|
f"{game['away_team']} {game['away_score']} @ "
|
||||||
f"(Status: {game['status']})")
|
f"{game['home_team']} {game['home_score']} "
|
||||||
|
f"(Status: {game['status']})"
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
print("No MLB games/scores available")
|
print("No MLB games/scores available")
|
||||||
|
|
||||||
|
nfl_client = nflScores()
|
||||||
|
nfl_results = await nfl_client.get_scores() # await async method
|
||||||
|
|
||||||
|
print("\nNFL Scores:")
|
||||||
|
if nfl_results:
|
||||||
|
for game in nfl_results:
|
||||||
|
print(
|
||||||
|
f"{game['away_team']} {game['away_score']} @ "
|
||||||
|
f"{game['home_team']} {game['home_score']} "
|
||||||
|
f"(Status: {game['status']})"
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
print("No NFL games/scores available")
|
||||||
|
|
||||||
asyncio.run(test_scores())
|
asyncio.run(test_scores())
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue