From d3ab27b2210586879d4a14710885225584188115 Mon Sep 17 00:00:00 2001 From: Chuck <33324927+ChuckBuilds@users.noreply.github.com> Date: Mon, 21 Jul 2025 15:07:36 -0500 Subject: [PATCH] visual improvements - separator between match up info, MILB team record logic to hide if no record found. Customization settings to see more information in the odds ticker manager --- README.md | 12 +++++- config/config.json | 9 ++++- src/milb_manager.py | 24 ++++++++---- src/odds_ticker_manager.py | 77 ++++++++++++++++++++++++++------------ 4 files changed, 87 insertions(+), 35 deletions(-) diff --git a/README.md b/README.md index e52204cf..c3a93f26 100644 --- a/README.md +++ b/README.md @@ -361,12 +361,22 @@ The odds ticker displays betting odds for upcoming sports games. To configure it - **`enabled`**: Enable/disable the odds ticker (default: false) - **`show_favorite_teams_only`**: Show only games involving favorite teams (default: false) +- **`games_per_favorite_team`**: Number of upcoming games to show per favorite team, per league (default: 1) +- **`max_games_per_league`**: Maximum number of games to show per league (default: 5) +- **`show_odds_only`**: If true, only show games that have odds available (default: false) +- **`sort_order`**: How to sort games in the ticker. Options: `soonest` (default; by start time). (More options can be added in the future.) - **`enabled_leagues`**: Array of leagues to display (options: "nfl", "nba", "mlb", "ncaa_fb") - **`update_interval`**: How often to fetch new odds data in seconds (default: 3600) -- **`scroll_speed`**: Pixels to scroll per update (default: 2) +- **`scroll_speed`**: Pixels to scroll per update (default: 1) - **`scroll_delay`**: Delay between scroll updates in seconds (default: 0.05) - **`display_duration`**: How long to show each game in seconds (default: 30) +**How it works:** +- If `show_favorite_teams_only` is true, the ticker will show the next `games_per_favorite_team` games for each favorite team in each enabled league, deduplicated and capped at `max_games_per_league` per league. +- If `show_favorite_teams_only` is false, the ticker will show all games for all teams (up to `max_games_per_league` per league). +- If `show_odds_only` is true, only games with odds will be shown. +- Games are sorted by `sort_order` (default: soonest). + ### Display Format The odds ticker shows information in this format: diff --git a/config/config.json b/config/config.json index 35466d84..b3fcabaf 100644 --- a/config/config.json +++ b/config/config.json @@ -110,10 +110,15 @@ "odds_ticker": { "enabled": true, "show_favorite_teams_only": true, - "enabled_leagues": ["mlb", "nfl", "ncaa_fb"], + "games_per_favorite_team": 1, + "max_games_per_league": 5, + "show_odds_only": false, + "sort_order": "soonest", + "enabled_leagues": ["nfl","mlb", "ncaa_fb"], "update_interval": 3600, "scroll_speed": 1, - "scroll_delay": 0.01 + "scroll_delay": 0.05, + "display_duration": 30 }, "calendar": { "enabled": true, diff --git a/src/milb_manager.py b/src/milb_manager.py index d6e810a6..d3a4e4b9 100644 --- a/src/milb_manager.py +++ b/src/milb_manager.py @@ -367,12 +367,20 @@ class BaseMiLBManager: self.logger.debug(f"Could not find team abbreviation for '{away_team_name}'. Using '{away_abbr}'.") # Get team records - away_record = event['teams']['away'].get('record', {}).get('wins', 0) - away_losses = event['teams']['away'].get('record', {}).get('losses', 0) - home_record = event['teams']['home'].get('record', {}).get('wins', 0) - home_losses = event['teams']['home'].get('record', {}).get('losses', 0) - away_record_str = f"{away_record}-{away_losses}" - home_record_str = f"{home_record}-{home_losses}" + away_record_data = event['teams']['away'].get('record', {}) + home_record_data = event['teams']['home'].get('record', {}) + away_record = away_record_data.get('wins') + away_losses = away_record_data.get('losses') + home_record = home_record_data.get('wins') + home_losses = home_record_data.get('losses') + if away_record is not None and away_losses is not None and (away_record != 0 or away_losses != 0): + away_record_str = f"{away_record}-{away_losses}" + else: + away_record_str = '' + if home_record is not None and home_losses is not None and (home_record != 0 or home_losses != 0): + home_record_str = f"{home_record}-{home_losses}" + else: + home_record_str = '' is_favorite_game = (home_abbr in self.favorite_teams or away_abbr in self.favorite_teams) @@ -399,8 +407,8 @@ class BaseMiLBManager: 'status': mapped_status, 'status_state': mapped_status_state, 'start_time': event['gameDate'], - 'away_record': f"{event['teams']['away'].get('record', {}).get('wins', 0)}-{event['teams']['away'].get('record', {}).get('losses', 0)}", - 'home_record': f"{event['teams']['home'].get('record', {}).get('wins', 0)}-{event['teams']['home'].get('record', {}).get('losses', 0)}" + 'away_record': away_record_str, + 'home_record': home_record_str } if status_state == 'Live': diff --git a/src/odds_ticker_manager.py b/src/odds_ticker_manager.py index ec290ce5..ae644885 100644 --- a/src/odds_ticker_manager.py +++ b/src/odds_ticker_manager.py @@ -24,6 +24,10 @@ class OddsTickerManager: self.odds_ticker_config = config.get('odds_ticker', {}) self.is_enabled = self.odds_ticker_config.get('enabled', False) self.show_favorite_teams_only = self.odds_ticker_config.get('show_favorite_teams_only', False) + self.games_per_favorite_team = self.odds_ticker_config.get('games_per_favorite_team', 1) + self.max_games_per_league = self.odds_ticker_config.get('max_games_per_league', 5) + self.show_odds_only = self.odds_ticker_config.get('show_odds_only', False) + self.sort_order = self.odds_ticker_config.get('sort_order', 'soonest') self.enabled_leagues = self.odds_ticker_config.get('enabled_leagues', ['nfl', 'nba', 'mlb']) self.update_interval = self.odds_ticker_config.get('update_interval', 3600) self.scroll_speed = self.odds_ticker_config.get('scroll_speed', 2) @@ -144,7 +148,7 @@ class OddsTickerManager: return None def _fetch_upcoming_games(self) -> List[Dict[str, Any]]: - """Fetch upcoming games with odds for all enabled leagues.""" + """Fetch upcoming games with odds for all enabled leagues, with user-defined granularity.""" games_data = [] now = datetime.now(timezone.utc) @@ -163,31 +167,50 @@ class OddsTickerManager: continue try: - # Fetch upcoming games for this league - games = self._fetch_league_games(league_config, now) - logger.debug(f"Found {len(games)} games for {league_key}") - games_data.extend(games) + # Fetch all upcoming games for this league + all_games = self._fetch_league_games(league_config, now) + logger.debug(f"Found {len(all_games)} games for {league_key}") + league_games = [] + + if self.show_favorite_teams_only: + # For each favorite team, find their next N games + favorite_teams = league_config.get('favorite_teams', []) + seen_game_ids = set() + for team in favorite_teams: + # Find games where this team is home or away + team_games = [g for g in all_games if (g['home_team'] == team or g['away_team'] == team)] + # Sort by start_time + team_games.sort(key=lambda x: x.get('start_time', datetime.max)) + # Only keep games with odds if show_odds_only is set + if self.show_odds_only: + team_games = [g for g in team_games if g.get('odds')] + # Take the next N games for this team + for g in team_games[:self.games_per_favorite_team]: + if g['id'] not in seen_game_ids: + league_games.append(g) + seen_game_ids.add(g['id']) + # Cap at max_games_per_league + league_games = league_games[:self.max_games_per_league] + else: + # Show all games, optionally only those with odds + league_games = all_games + if self.show_odds_only: + league_games = [g for g in league_games if g.get('odds')] + # Sort by start_time + league_games.sort(key=lambda x: x.get('start_time', datetime.max)) + league_games = league_games[:self.max_games_per_league] + + # Sorting (default is soonest) + if self.sort_order == 'soonest': + league_games.sort(key=lambda x: x.get('start_time', datetime.max)) + # (Other sort options can be added here) + + games_data.extend(league_games) except Exception as e: logger.error(f"Error fetching games for {league_key}: {e}") - # Sort games by start time - games_data.sort(key=lambda x: x.get('start_time', datetime.max)) - - # Filter for favorite teams if enabled (now handled in _fetch_league_games) - # This filtering is now redundant since we filter before fetching odds - if self.show_favorite_teams_only: - logger.debug("Favorite team filtering already applied during game fetching") - logger.debug(f"Total games found: {len(games_data)}") - - # Log details about found games - if games_data: - logger.debug("Games found:") - for i, game in enumerate(games_data): - odds_status = "Has odds" if game.get('odds') else "No odds" - logger.debug(f" {i+1}. {game['away_team']} @ {game['home_team']} - {odds_status}") - return games_data def _fetch_league_games(self, league_config: Dict[str, Any], now: datetime) -> List[Dict[str, Any]]: @@ -518,7 +541,7 @@ class OddsTickerManager: odds_y_home = height - odds_font_height - 2 # Use a consistent color for all odds text - odds_color = (255, 255, 0) # Yellow + odds_color = (0, 255, 0) # Green draw.text((current_x, odds_y_away), away_odds_text, font=odds_font, fill=odds_color) draw.text((current_x, odds_y_home), home_odds_text, font=odds_font, fill=odds_color) @@ -557,9 +580,15 @@ class OddsTickerManager: self.ticker_image = Image.new('RGB', (total_width, height), color=(0, 0, 0)) current_x = 0 - for img in game_images: + for idx, img in enumerate(game_images): self.ticker_image.paste(img, (current_x, 0)) - current_x += img.width + gap_width + current_x += img.width + # Draw a 1px white vertical bar between games, except after the last one + if idx < len(game_images) - 1: + bar_x = current_x + gap_width // 2 + for y in range(height): + self.ticker_image.putpixel((bar_x, y), (255, 255, 255)) + current_x += gap_width if self.ticker_image and self.scroll_speed > 0 and self.scroll_delay > 0: # Duration for the ticker to scroll its full width