Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
0672214
rebase
Sep 18, 2025
301237f
Update NFL and NCAA FB fetch
Sep 18, 2025
b6932a0
update FB updates
Sep 18, 2025
5cf266b
kinda working, kinda broken
Sep 18, 2025
0760035
Fixed and update loggers
Sep 18, 2025
abc45fd
move to individual files
Sep 18, 2025
fb3c244
timeout updates
Sep 19, 2025
7248401
seems to work well
Sep 19, 2025
2a6eda5
Leaderboard overestimates time
Sep 19, 2025
c458e6e
Merge remote-tracking branch 'origin/main' into leaderboard-overestim…
Sep 19, 2025
4ca7767
ignore that
Sep 19, 2025
64e0764
Merge remote-tracking branch 'origin/main' into fixes
Sep 19, 2025
084aa3e
minor syntax updates
Sep 19, 2025
f2e4ab4
Merge remote-tracking branch 'origin/leaderboard-overestimate-time' i…
Sep 19, 2025
09a1953
More consolidation but i broke something
Sep 19, 2025
5ffa778
fixed
Sep 19, 2025
77e3438
Merge remote-tracking branch 'upstream/main' into fixes
Sep 20, 2025
6672ccd
Hockey seems to work
Sep 20, 2025
33eabb8
Fix my changes to logo downloader
Sep 20, 2025
b7f3843
even more consolidation
Sep 20, 2025
c8d49d4
fixes
Sep 20, 2025
b43cd9f
more cleanup
Sep 20, 2025
d0ace2c
inheritance stuff
Sep 20, 2025
068e399
Change football to ESPN down text, it does what ur already doing. Cha…
Sep 20, 2025
6fb8196
Fix leaderboard
Sep 20, 2025
6cffd2c
Update football.py
legoguy1000 Sep 21, 2025
5b508ca
Minor fixes
Sep 21, 2025
2e793eb
don't want that
Sep 21, 2025
4e84de9
background fetch
Sep 21, 2025
66b4f2a
whoops
Sep 21, 2025
795292a
Merge PR #39: Created Base Sports Classes
ChuckBuilds Sep 24, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ ENV/
.idea/
*.swp
*.swo
emulator_config.json

# Cache directory
cache/
Binary file modified assets/sports/ncaa_logos/ncaam.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 0 additions & 2 deletions config/config.template.json
Original file line number Diff line number Diff line change
Expand Up @@ -328,7 +328,6 @@
"live_update_interval": 30,
"live_odds_update_interval": 3600,
"odds_update_interval": 3600,
"season_cache_duration_seconds": 86400,
"recent_games_to_show": 1,
"upcoming_games_to_show": 1,
"show_favorite_teams_only": true,
Expand Down Expand Up @@ -410,7 +409,6 @@
"live_update_interval": 30,
"live_odds_update_interval": 3600,
"odds_update_interval": 3600,
"season_cache_duration_seconds": 86400,
"recent_games_to_show": 1,
"upcoming_games_to_show": 1,
"show_favorite_teams_only": true,
Expand Down
4 changes: 2 additions & 2 deletions src/background_data_service.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@
import queue
from concurrent.futures import ThreadPoolExecutor, Future
import weakref

from src.cache_manager import CacheManager
# Configure logging
logger = logging.getLogger(__name__)

Expand Down Expand Up @@ -78,7 +78,7 @@ class BackgroundDataService:
with intelligent caching, retry logic, and progress tracking.
"""

def __init__(self, cache_manager, max_workers: int = 3, request_timeout: int = 30):
def __init__(self, cache_manager: CacheManager, max_workers: int = 3, request_timeout: int = 30):
"""
Initialize the background data service.

Expand Down
545 changes: 545 additions & 0 deletions src/base_classes/football.py

Large diffs are not rendered by default.

328 changes: 328 additions & 0 deletions src/base_classes/hockey.py

Large diffs are not rendered by default.

1,135 changes: 1,135 additions & 0 deletions src/base_classes/sports.py

Large diffs are not rendered by default.

163 changes: 22 additions & 141 deletions src/display_controller.py

Large diffs are not rendered by default.

395 changes: 246 additions & 149 deletions src/leaderboard_manager.py

Large diffs are not rendered by default.

122 changes: 56 additions & 66 deletions src/logo_downloader.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,9 +51,10 @@ class LogoDownloader:
'nba': 'assets/sports/nba_logos',
'mlb': 'assets/sports/mlb_logos',
'nhl': 'assets/sports/nhl_logos',
# NCAA sports use same directory
'ncaa_fb': 'assets/sports/ncaa_logos',
'ncaa_fb_all': 'assets/sports/ncaa_logos', # FCS teams go in same directory
'fcs': 'assets/sports/ncaa_logos', # FCS teams go in same directory
'ncaa_fb_all': 'assets/sports/ncaa_logos',
'fcs': 'assets/sports/ncaa_logos',
'ncaam_basketball': 'assets/sports/ncaa_logos',
'ncaa_baseball': 'assets/sports/ncaa_logos',
'ncaam_hockey': 'assets/sports/ncaa_logos',
Expand Down Expand Up @@ -95,7 +96,8 @@ def __init__(self, request_timeout: int = 30, retry_attempts: int = 3):
'Connection': 'keep-alive'
}

def normalize_abbreviation(self, abbreviation: str) -> str:
@staticmethod
def normalize_abbreviation(abbreviation: str) -> str:
"""Normalize team abbreviation for consistent filename usage."""
# Handle special characters that can cause filesystem issues
normalized = abbreviation.upper()
Expand Down Expand Up @@ -125,7 +127,7 @@ def ensure_logo_directory(self, logo_dir: str) -> bool:
logger.error(f"Failed to create logo directory {logo_dir}: {e}")
return False

def download_logo(self, logo_url: str, filepath: Path, team_name: str) -> bool:
def download_logo(self, logo_url: str, filepath: Path, team_abbreviation: str) -> bool:
"""Download a single logo from URL and save to filepath."""
try:
response = self.session.get(logo_url, headers=self.headers, timeout=self.request_timeout)
Expand All @@ -134,7 +136,7 @@ def download_logo(self, logo_url: str, filepath: Path, team_name: str) -> bool:
# Verify it's actually an image
content_type = response.headers.get('content-type', '').lower()
if not any(img_type in content_type for img_type in ['image/png', 'image/jpeg', 'image/jpg', 'image/gif']):
logger.warning(f"Downloaded content for {team_name} is not an image: {content_type}")
logger.warning(f"Downloaded content for {team_abbreviation} is not an image: {content_type}")
return False

with open(filepath, 'wb') as f:
Expand All @@ -157,21 +159,21 @@ def download_logo(self, logo_url: str, filepath: Path, team_name: str) -> bool:
# Save the converted image
img.save(filepath, 'PNG')

logger.info(f"Successfully downloaded and converted logo for {team_name} -> {filepath.name}")
logger.info(f"Successfully downloaded and converted logo for {team_abbreviation} -> {filepath.name}")
return True
except Exception as e:
logger.error(f"Downloaded file for {team_name} is not a valid image or conversion failed: {e}")
logger.error(f"Downloaded file for {team_abbreviation} is not a valid image or conversion failed: {e}")
try:
os.remove(filepath) # Remove invalid file
except:
pass
return False

except requests.exceptions.RequestException as e:
logger.error(f"Failed to download logo for {team_name}: {e}")
logger.error(f"Failed to download logo for {team_abbreviation}: {e}")
return False
except Exception as e:
logger.error(f"Unexpected error downloading logo for {team_name}: {e}")
logger.error(f"Unexpected error downloading logo for {team_abbreviation}: {e}")
return False

def fetch_teams_data(self, league: str) -> Optional[Dict]:
Expand All @@ -197,6 +199,29 @@ def fetch_teams_data(self, league: str) -> Optional[Dict]:
logger.error(f"Error parsing JSON response for {league}: {e}")
return None

def fetch_single_team(self, league: str, team_id: str) -> Optional[Dict]:
"""Fetch team data from ESPN API for a specific league."""
api_url = self.API_ENDPOINTS.get(league)
if not api_url:
logger.error(f"No API endpoint configured for league: {league}")
return None

try:
logger.info(f"Fetching team data for team {team_id} in {league} from ESPN API...")
response = self.session.get(f"{api_url}/{team_id}", headers=self.headers, timeout=self.request_timeout)
response.raise_for_status()
data = response.json()

logger.info(f"Successfully fetched team data for {team_id} in {league}")
return data

except requests.exceptions.RequestException as e:
logger.error(f"Error fetching team data for {team_id} in {league}: {e}")
return None
except json.JSONDecodeError as e:
logger.error(f"Error parsing JSON response for{team_id} in {league}: {e}")
return None

def extract_teams_from_data(self, data: Dict, league: str) -> List[Dict[str, str]]:
"""Extract team information from ESPN API response."""
teams = []
Expand Down Expand Up @@ -450,66 +475,24 @@ def download_all_ncaa_football_logos(self, include_fcs: bool = True, force_downl
logger.info(f"Comprehensive NCAA football logo download complete: {downloaded_count} downloaded, {failed_count} failed")
return downloaded_count, failed_count

def download_missing_logo_for_team(self, team_abbreviation: str, league: str, team_name: str = None) -> bool:
def download_missing_logo_for_team(self, league: str, team_id: str, team_abbreviation: str, logo_path: Path) -> bool:
"""Download a specific team's logo if it's missing."""
logo_dir = self.get_logo_directory(league)
if not self.ensure_logo_directory(logo_dir):
return False

filename = f"{self.normalize_abbreviation(team_abbreviation)}.png"
filepath = Path(logo_dir) / filename

# Return True if logo already exists
if filepath.exists():
logger.debug(f"Logo already exists for {team_abbreviation}")
return True

# Fetch team data to find the logo URL
data = self.fetch_teams_data(league)
data = self.fetch_single_team(league, team_id)
if not data:
return False

teams = self.extract_teams_from_data(data, league)

# Find the specific team with improved matching
target_team = None
normalized_search = self.normalize_abbreviation(team_abbreviation)

# First try exact match
for team in teams:
if team['abbreviation'].upper() == team_abbreviation.upper():
target_team = team
break

# If not found, try normalized match
if not target_team:
for team in teams:
normalized_team_abbr = self.normalize_abbreviation(team['abbreviation'])
if normalized_team_abbr == normalized_search:
target_team = team
break

# If still not found, try partial matching for common variations
if not target_team:
search_variations = self._get_team_name_variations(team_abbreviation)
for team in teams:
team_variations = self._get_team_name_variations(team['abbreviation'])
if any(var in team_variations for var in search_variations):
target_team = team
logger.info(f"Found team {team_abbreviation} as {team['abbreviation']} ({team['display_name']})")
break

if not target_team:
logger.warning(f"Team {team_abbreviation} not found in {league} data")
try:
logo_url = data["team"]["logos"][0]["href"]
except KeyError:
return False

# Download the logo
success = self.download_logo(target_team['logo_url'], filepath, target_team['display_name'])
success = self.download_logo(logo_url, logo_path, team_abbreviation)
if success:
time.sleep(0.1) # Small delay
return success

def download_all_missing_logos(self, leagues: List[str] = None, force_download: bool = False) -> Dict[str, Tuple[int, int]]:
def download_all_missing_logos(self, leagues: List[str] | None = None, force_download: bool = False) -> Dict[str, Tuple[int, int]]:
"""Download missing logos for all specified leagues."""
if leagues is None:
leagues = list(self.API_ENDPOINTS.keys())
Expand All @@ -531,7 +514,7 @@ def download_all_missing_logos(self, leagues: List[str] = None, force_download:
logger.info(f"Overall logo download results: {total_downloaded} downloaded, {total_failed} failed")
return results

def create_placeholder_logo(self, team_abbreviation: str, logo_dir: str, team_name: str = None) -> bool:
def create_placeholder_logo(self, team_abbreviation: str, logo_dir: str) -> bool:
"""Create a placeholder logo when real logo cannot be downloaded."""
try:
# Ensure the logo directory exists
Expand Down Expand Up @@ -642,7 +625,7 @@ def get_soccer_league_key(league_code: str) -> str:


# Convenience function for easy integration
def download_missing_logo(team_abbreviation: str, league: str, team_name: str = None, create_placeholder: bool = True) -> bool:
def download_missing_logo(league: str, team_id: str, team_abbreviation: str, logo_path: Path, logo_url: str | None = None, create_placeholder: bool = True) -> bool:
"""
Convenience function to download a missing team logo.

Expand All @@ -659,6 +642,7 @@ def download_missing_logo(team_abbreviation: str, league: str, team_name: str =

# Check if logo already exists
logo_dir = downloader.get_logo_directory(league)
downloader.ensure_logo_directory(logo_dir)
filename = f"{downloader.normalize_abbreviation(team_abbreviation)}.png"
filepath = Path(logo_dir) / filename

Expand All @@ -667,18 +651,24 @@ def download_missing_logo(team_abbreviation: str, league: str, team_name: str =
return True

# Try to download the real logo first
logger.info(f"Attempting to download logo for {team_abbreviation} ({team_name or 'Unknown'}) from {league}")
success = downloader.download_missing_logo_for_team(team_abbreviation, league, team_name)
logger.info(f"Attempting to download logo for {team_abbreviation} from {league}")
if logo_url:
success = downloader.download_logo(logo_url, filepath, team_abbreviation)
if success:
time.sleep(0.1) # Small delay
return success

success = downloader.download_missing_logo_for_team(league, team_id, team_abbreviation, logo_path)

if not success and create_placeholder:
logger.info(f"Creating placeholder logo for {team_abbreviation} ({team_name or 'Unknown'})")
logger.info(f"Creating placeholder logo for {team_abbreviation}")
# Create placeholder as fallback
success = downloader.create_placeholder_logo(team_abbreviation, logo_dir, team_name)
success = downloader.create_placeholder_logo(team_abbreviation, logo_dir)

if success:
logger.info(f"Successfully handled logo for {team_abbreviation} ({team_name or 'Unknown'})")
logger.info(f"Successfully handled logo for {team_abbreviation}")
else:
logger.warning(f"Failed to download or create logo for {team_abbreviation} ({team_name or 'Unknown'})")
logger.warning(f"Failed to download or create logo for {team_abbreviation}")

return success

Expand Down
Loading