- Pre-game: No scores will be shown (scores are 0-0) - Live games: Current scores will display next to team names - Completed games: Final scores will be shown Co-authored-by: Eduardo Figueroa <eduardo_figueroa@ucsb.edu> Reviewed-on: https://codeberg.org/edfig/RosterHash/pulls/3
245 lines
11 KiB
Python
245 lines
11 KiB
Python
import requests
|
|
from datetime import datetime, timedelta, timezone
|
|
import zoneinfo
|
|
|
|
class ESPNAPI:
|
|
BASE_URL = 'https://site.api.espn.com/apis/site/v2/sports/football/nfl'
|
|
|
|
# Team abbreviation mapping - ESPN to Sleeper format
|
|
TEAM_MAP = {
|
|
# AFC East
|
|
'BUF': 'BUF', # Buffalo Bills
|
|
'MIA': 'MIA', # Miami Dolphins
|
|
'NE': 'NE', # New England Patriots
|
|
'NYJ': 'NYJ', # New York Jets
|
|
|
|
# AFC North
|
|
'BAL': 'BAL', # Baltimore Ravens
|
|
'CIN': 'CIN', # Cincinnati Bengals
|
|
'CLE': 'CLE', # Cleveland Browns
|
|
'PIT': 'PIT', # Pittsburgh Steelers
|
|
|
|
# AFC South
|
|
'HOU': 'HOU', # Houston Texans
|
|
'IND': 'IND', # Indianapolis Colts
|
|
'JAX': 'JAX', # Jacksonville Jaguars
|
|
'TEN': 'TEN', # Tennessee Titans
|
|
|
|
# AFC West
|
|
'DEN': 'DEN', # Denver Broncos
|
|
'KC': 'KC', # Kansas City Chiefs
|
|
'LV': 'LV', # Las Vegas Raiders
|
|
'LAC': 'LAC', # Los Angeles Chargers
|
|
|
|
# NFC East
|
|
'DAL': 'DAL', # Dallas Cowboys
|
|
'NYG': 'NYG', # New York Giants
|
|
'PHI': 'PHI', # Philadelphia Eagles
|
|
'WSH': 'WAS', # Washington Commanders (ESPN uses WSH, Sleeper uses WAS)
|
|
|
|
# NFC North
|
|
'CHI': 'CHI', # Chicago Bears
|
|
'DET': 'DET', # Detroit Lions
|
|
'GB': 'GB', # Green Bay Packers
|
|
'MIN': 'MIN', # Minnesota Vikings
|
|
|
|
# NFC South
|
|
'ATL': 'ATL', # Atlanta Falcons
|
|
'CAR': 'CAR', # Carolina Panthers
|
|
'NO': 'NO', # New Orleans Saints
|
|
'TB': 'TB', # Tampa Bay Buccaneers
|
|
|
|
# NFC West
|
|
'ARI': 'ARI', # Arizona Cardinals
|
|
'LAR': 'LAR', # Los Angeles Rams
|
|
'SF': 'SF', # San Francisco 49ers
|
|
'SEA': 'SEA', # Seattle Seahawks
|
|
}
|
|
|
|
def __init__(self):
|
|
self.session = requests.Session()
|
|
|
|
def normalize_team_abbreviation(self, espn_team):
|
|
"""Convert ESPN team abbreviation to Sleeper format"""
|
|
return self.TEAM_MAP.get(espn_team, espn_team)
|
|
|
|
def get_player_injury_status(self, player_name):
|
|
"""Get injury status for a specific player from ESPN"""
|
|
try:
|
|
# Try to get player info from ESPN API
|
|
url = f"{self.BASE_URL}/athletes"
|
|
response = self.session.get(url, params={'search': player_name})
|
|
response.raise_for_status()
|
|
data = response.json()
|
|
|
|
print(f"ESPN API: Searching for {player_name}", flush=True)
|
|
|
|
if 'athletes' in data and len(data['athletes']) > 0:
|
|
athlete = data['athletes'][0]
|
|
print(f"ESPN API: Found athlete data for {player_name}: {athlete}", flush=True)
|
|
|
|
# Check for injury status
|
|
if 'injuries' in athlete:
|
|
return athlete['injuries']
|
|
elif 'status' in athlete:
|
|
return athlete['status']
|
|
|
|
return None
|
|
|
|
except Exception as e:
|
|
print(f"ESPN API: Error fetching injury status for {player_name}: {e}", flush=True)
|
|
return None
|
|
|
|
def get_week_schedule(self, week, season, user_timezone='America/Los_Angeles', debug_current_time=None):
|
|
"""Get NFL schedule for a specific week"""
|
|
try:
|
|
print(f"ESPN API: Fetching schedule for week {week}, season {season}", flush=True)
|
|
|
|
# Get NFL scoreboard for specific week and season
|
|
url = f"{self.BASE_URL}/scoreboard"
|
|
params = {
|
|
'seasontype': 2, # Regular season
|
|
'week': week,
|
|
'year': season
|
|
}
|
|
response = self.session.get(url, params=params)
|
|
response.raise_for_status()
|
|
data = response.json()
|
|
|
|
print(f"ESPN API: Response status: {response.status_code}", flush=True)
|
|
|
|
games = []
|
|
if 'events' in data:
|
|
print(f"ESPN API: Found {len(data['events'])} events", flush=True)
|
|
for i, event in enumerate(data['events']):
|
|
try:
|
|
# Parse the game date as UTC
|
|
game_date_utc = datetime.strptime(event['date'], '%Y-%m-%dT%H:%MZ').replace(tzinfo=timezone.utc)
|
|
|
|
# Convert to user's timezone (default PST)
|
|
try:
|
|
user_tz = zoneinfo.ZoneInfo(user_timezone)
|
|
except:
|
|
# Fallback to PST if timezone is invalid
|
|
user_tz = zoneinfo.ZoneInfo('America/Los_Angeles')
|
|
user_timezone = 'America/Los_Angeles'
|
|
|
|
game_date_local = game_date_utc.astimezone(user_tz)
|
|
|
|
# Get timezone abbreviation
|
|
tz_abbr = game_date_local.strftime('%Z')
|
|
if not tz_abbr: # Fallback if %Z doesn't work
|
|
if 'Los_Angeles' in user_timezone or 'Pacific' in user_timezone:
|
|
tz_abbr = 'PST' if game_date_local.dst() == timedelta(0) else 'PDT'
|
|
elif 'New_York' in user_timezone or 'Eastern' in user_timezone:
|
|
tz_abbr = 'EST' if game_date_local.dst() == timedelta(0) else 'EDT'
|
|
else:
|
|
tz_abbr = 'Local'
|
|
|
|
# Extract team information and game status
|
|
competition = event['competitions'][0]
|
|
competitors = competition['competitors']
|
|
|
|
# Get game status information
|
|
status = competition.get('status', {})
|
|
game_state = status.get('type', {}).get('state', 'pre') # pre, in, post
|
|
is_live = game_state == 'in'
|
|
status_display = status.get('type', {}).get('name', 'Scheduled')
|
|
|
|
home_team = None
|
|
away_team = None
|
|
home_score = None
|
|
away_score = None
|
|
|
|
# Identify home and away teams and extract scores
|
|
for comp in competitors:
|
|
team_abbrev = comp['team']['abbreviation']
|
|
# Normalize team abbreviation to match Sleeper format
|
|
normalized_team = self.normalize_team_abbreviation(team_abbrev)
|
|
# Get score if available
|
|
score = comp.get('score')
|
|
|
|
if comp['homeAway'] == 'home':
|
|
home_team = normalized_team
|
|
home_score = score
|
|
else:
|
|
away_team = normalized_team
|
|
away_score = score
|
|
|
|
print(f"ESPN API: {away_team} @ {home_team} at {game_date_local.strftime('%I:%M %p')} {tz_abbr}", flush=True)
|
|
|
|
# Store game data with user's local times
|
|
games.append({
|
|
'date': game_date_utc, # Keep UTC for reference
|
|
'date_local': game_date_local,
|
|
'day_of_week': game_date_local.strftime('%A'),
|
|
'date_formatted': game_date_local.strftime('%m/%d'),
|
|
'time': f"{game_date_local.strftime('%I:%M %p')} {tz_abbr}",
|
|
'home_team': home_team,
|
|
'away_team': away_team,
|
|
'home_score': home_score,
|
|
'away_score': away_score,
|
|
'teams': [home_team, away_team],
|
|
'is_past': game_date_local < (debug_current_time.astimezone(game_date_local.tzinfo) if debug_current_time else datetime.now(game_date_local.tzinfo)),
|
|
'is_live': is_live,
|
|
'game_state': game_state,
|
|
'status_display': status_display
|
|
})
|
|
except Exception as e:
|
|
print(f"ESPN API: Error processing event {i+1}: {str(e)}", flush=True)
|
|
else:
|
|
print("ESPN API: No 'events' key found in response", flush=True)
|
|
|
|
print(f"ESPN API: Processed {len(games)} games total", flush=True)
|
|
schedule = self._organize_by_day(games, debug_current_time)
|
|
|
|
# Log games by day
|
|
for day, day_games in schedule.items():
|
|
print(f"ESPN API: {day}: {len(day_games)} games", flush=True)
|
|
|
|
return schedule
|
|
|
|
except Exception as e:
|
|
print(f"ESPN API: Error fetching schedule: {e}", flush=True)
|
|
return {}
|
|
|
|
def _organize_by_day(self, games, debug_current_time=None):
|
|
"""Organize games by day, sorted chronologically with date info"""
|
|
schedule = {}
|
|
current_time_for_comparison = debug_current_time or datetime.now()
|
|
|
|
# Sort games by date first
|
|
games_sorted = sorted(games, key=lambda x: x['date_local'])
|
|
|
|
# Group games by day
|
|
for game in games_sorted:
|
|
day_key = f"{game['day_of_week']} {game['date_formatted']}"
|
|
if day_key not in schedule:
|
|
schedule[day_key] = {
|
|
'day_name': game['day_of_week'],
|
|
'date': game['date_formatted'],
|
|
'date_obj': game['date_local'].date(),
|
|
'is_past': game['date_local'].date() < current_time_for_comparison.astimezone(game['date_local'].tzinfo).date(),
|
|
'games': []
|
|
}
|
|
schedule[day_key]['games'].append(game)
|
|
|
|
# Sort games within each day: live/future first, then completed, then by time within each group
|
|
for day_info in schedule.values():
|
|
def game_sort_key(game):
|
|
# Priority: live games first (0), future games (1), completed games (2)
|
|
if game['is_live']:
|
|
priority = 0
|
|
elif not game['is_past']:
|
|
priority = 1 # future games
|
|
else:
|
|
priority = 2 # completed games
|
|
return (priority, game['date_local'])
|
|
|
|
day_info['games'].sort(key=game_sort_key)
|
|
|
|
# Add day-level status info for day sorting
|
|
day_info['has_live_games'] = any(game['is_live'] for game in day_info['games'])
|
|
day_info['has_future_games'] = any(not game['is_past'] and not game['is_live'] for game in day_info['games'])
|
|
|
|
return schedule
|