Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
8 changes: 5 additions & 3 deletions plugins/basketball-scoreboard/basketball.py
Original file line number Diff line number Diff line change
Expand Up @@ -240,11 +240,12 @@ def format_score(score):
# Display away team info
if away_abbr:
if self.show_ranking and self.show_records:
# Rankings take priority, fall back to record for unranked teams
away_rank = self._team_rankings_cache.get(away_abbr, 0)
if away_rank > 0:
away_text = f"#{away_rank}"
else:
away_text = ''
away_text = game.get('away_record', '')
elif self.show_ranking:
away_rank = self._team_rankings_cache.get(away_abbr, 0)
if away_rank > 0:
Expand All @@ -255,7 +256,7 @@ def format_score(score):
away_text = game.get('away_record', '')
else:
away_text = ''

if away_text:
away_record_x = 3
self.logger.debug(f"Drawing away ranking '{away_text}' at ({away_record_x}, {record_y})")
Expand All @@ -264,11 +265,12 @@ def format_score(score):
# Display home team info
if home_abbr:
if self.show_ranking and self.show_records:
# Rankings take priority, fall back to record for unranked teams
home_rank = self._team_rankings_cache.get(home_abbr, 0)
if home_rank > 0:
home_text = f"#{home_rank}"
else:
home_text = ''
home_text = game.get('home_record', '')
elif self.show_ranking:
home_rank = self._team_rankings_cache.get(home_abbr, 0)
if home_rank > 0:
Expand Down
114 changes: 54 additions & 60 deletions plugins/basketball-scoreboard/game_renderer.py
Original file line number Diff line number Diff line change
Expand Up @@ -66,10 +66,19 @@ def __init__(
'ncaaw': config.get('ncaaw', {}).get('logo_dir', 'assets/sports/ncaa_logos'),
}

# Display options
defaults = config.get('defaults', {})
self.show_records = defaults.get('show_records', config.get('show_records', False))
self.show_ranking = defaults.get('show_ranking', config.get('show_ranking', False))
# Display options - check per-league display_options in config
# The config structure is: config[league].display_options.show_records/show_ranking
# Enable if ANY enabled league has the option enabled
self.show_records = False
self.show_ranking = False
for league_key in ('nba', 'wnba', 'ncaam', 'ncaaw'):
league_config = config.get(league_key, {})
if league_config.get('enabled', False):
display_options = league_config.get('display_options', {})
if display_options.get('show_records', False):
self.show_records = True
if display_options.get('show_ranking', False):
self.show_ranking = True

# Rankings cache (populated externally)
self._team_rankings_cache: Dict[str, int] = {}
Expand Down Expand Up @@ -274,15 +283,13 @@ def render_game_card(
league = game.get('league', 'nba')
logo_dir = Path(self.logo_dirs.get(league, 'assets/sports/nba_logos'))

# Get team info (basketball uses home_team/away_team dicts)
home_team = game.get('home_team', {})
away_team = game.get('away_team', {})
home_abbr = home_team.get('abbrev', '')
away_abbr = away_team.get('abbrev', '')

# Get logo paths from team data if available, otherwise construct from logo_dir
home_logo_path = home_team.get('logo_path', logo_dir / f"{home_abbr}.png")
away_logo_path = away_team.get('logo_path', logo_dir / f"{away_abbr}.png")
# Get team info - support flat format from sports.py game dicts
home_abbr = game.get('home_abbr', '')
away_abbr = game.get('away_abbr', '')

# Get logo paths from game data, otherwise construct from logo_dir
home_logo_path = game.get('home_logo_path', logo_dir / f"{home_abbr}.png")
away_logo_path = game.get('away_logo_path', logo_dir / f"{away_abbr}.png")

# Load logos (using league+abbrev for cache key)
home_logo = self._load_and_resize_logo(
Expand Down Expand Up @@ -319,8 +326,8 @@ def render_game_card(
main_img.paste(away_logo, (away_x, away_y), away_logo)

# Draw scores (centered)
home_score = str(home_team.get("score", "0"))
away_score = str(away_team.get("score", "0"))
home_score = str(game.get("home_score", "0"))
away_score = str(game.get("away_score", "0"))
Comment on lines +329 to +330
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Minor: str(None) produces "None" if score value is explicitly None.

game.get("home_score", "0") returns None (not "0") when the key exists with a None value, so str(None)"None" would render on screen. In practice sports.py always normalizes scores to strings, but a small guard would improve robustness:

Proposed fix
-        home_score = str(game.get("home_score", "0"))
-        away_score = str(game.get("away_score", "0"))
+        home_score = str(game.get("home_score") or "0")
+        away_score = str(game.get("away_score") or "0")
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
home_score = str(game.get("home_score", "0"))
away_score = str(game.get("away_score", "0"))
home_score = str(game.get("home_score") or "0")
away_score = str(game.get("away_score") or "0")
🤖 Prompt for AI Agents
In `@plugins/basketball-scoreboard/game_renderer.py` around lines 329 - 330, The
current conversion uses str(game.get("home_score", "0")) which still yields
"None" when the key exists but its value is None; update the handling in
game_renderer.py so you first retrieve the raw value (e.g., val =
game.get("home_score")) and then set home_score = str(val) if val is not None
else "0" (and do the same for away_score) to ensure explicit None values render
as "0" instead of "None".

score_text = f"{away_score}-{home_score}"
score_width = draw_overlay.textlength(score_text, font=self.fonts['score'])
score_x = (self.display_width - score_width) // 2
Expand All @@ -345,18 +352,16 @@ def render_game_card(

def _draw_live_game_status(self, draw: ImageDraw.Draw, game: Dict) -> None:
"""Draw status elements for a live basketball game."""
# Period and Clock (Top center)
status = game.get('status', {})
period = status.get('period', 0)
clock = status.get('display_clock', '')
state = status.get('state', '')

if state == 'in':
period_clock_text = f"P{period} {clock}".strip()
elif state == 'post':
period_clock_text = "Final"
# Period and Clock (Top center) - use flat game dict format from sports.py
period_text = game.get('period_text', '')
clock = game.get('clock', '')

if game.get('is_halftime'):
period_clock_text = "Halftime"
elif period_text and clock:
period_clock_text = f"{period_text} {clock}".strip()
else:
period_clock_text = status.get('short_detail', '')
period_clock_text = game.get('status_text', '')

status_width = draw.textlength(period_clock_text, font=self.fonts['time'])
status_x = (self.display_width - status_width) // 2
Expand Down Expand Up @@ -391,31 +396,22 @@ def _draw_upcoming_game_status(self, draw: ImageDraw.Draw, game: Dict) -> None:
status_x = (self.display_width - status_width) // 2
status_y = 1
self._draw_text_with_outline(draw, status_text, (status_x, status_y), status_font)

# Game date and time
start_time = game.get("start_time", "")
if start_time:
try:
from datetime import datetime
import pytz

dt = datetime.fromisoformat(start_time.replace('Z', '+00:00'))
local_dt = dt.astimezone(pytz.utc) # Use UTC for now

game_date = local_dt.strftime("%b %d")
game_time = local_dt.strftime("%I:%M %p")

date_width = draw.textlength(game_date, font=self.fonts['time'])
date_x = (self.display_width - date_width) // 2
date_y = (self.display_height // 2) - 7
self._draw_text_with_outline(draw, game_date, (date_x, date_y), self.fonts['time'])

time_width = draw.textlength(game_time, font=self.fonts['time'])
time_x = (self.display_width - time_width) // 2
time_y = date_y + 9
self._draw_text_with_outline(draw, game_time, (time_x, time_y), self.fonts['time'])
except Exception:
pass # Skip date/time if parsing fails

# Game date and time - use flat format from sports.py
game_date = game.get("game_date", "")
game_time = game.get("game_time", "")

if game_date:
date_width = draw.textlength(game_date, font=self.fonts['time'])
date_x = (self.display_width - date_width) // 2
date_y = (self.display_height // 2) - 7
self._draw_text_with_outline(draw, game_date, (date_x, date_y), self.fonts['time'])

if game_time:
time_width = draw.textlength(game_time, font=self.fonts['time'])
time_x = (self.display_width - time_width) // 2
time_y = (self.display_height // 2) - 7 + 9
self._draw_text_with_outline(draw, game_time, (time_x, time_y), self.fonts['time'])

def _draw_dynamic_odds(self, draw: ImageDraw.Draw, odds: Dict[str, Any]) -> None:
"""Draw odds with dynamic positioning."""
Expand Down Expand Up @@ -492,13 +488,11 @@ def _draw_records_or_rankings(self, draw: ImageDraw.Draw, game: Dict) -> None:
except IOError:
record_font = ImageFont.load_default()

# Get team info (basketball uses home_team/away_team dicts)
home_team = game.get('home_team', {})
away_team = game.get('away_team', {})
away_abbr = away_team.get('abbrev', '')
home_abbr = home_team.get('abbrev', '')
away_record = away_team.get('record', '')
home_record = home_team.get('record', '')
# Get team info - support both flat format (from sports.py) and nested format
away_abbr = game.get('away_abbr', '')
home_abbr = game.get('home_abbr', '')
away_record = game.get('away_record', '')
home_record = game.get('home_record', '')

record_bbox = draw.textbbox((0, 0), "0-0", font=record_font)
record_height = record_bbox[3] - record_bbox[1]
Expand All @@ -523,11 +517,11 @@ def _draw_records_or_rankings(self, draw: ImageDraw.Draw, game: Dict) -> None:
def _get_team_display_text(self, abbr: str, record: str) -> str:
"""Get the display text for a team (ranking or record)."""
if self.show_ranking and self.show_records:
# Rankings replace records when both are enabled
# Rankings take priority when both are enabled, fall back to record
rank = self._team_rankings_cache.get(abbr, 0)
if rank > 0:
return f"#{rank}"
return ''
return record
elif self.show_ranking:
rank = self._team_rankings_cache.get(abbr, 0)
if rank > 0:
Expand Down
10 changes: 5 additions & 5 deletions plugins/basketball-scoreboard/scroll_display.py
Original file line number Diff line number Diff line change
Expand Up @@ -268,17 +268,17 @@ def _determine_game_type(self, game: Dict) -> str:
Determine the game type from the game's status.

Args:
game: Game dictionary
game: Game dictionary (flat format from sports.py)

Returns:
Game type: 'live', 'recent', or 'upcoming'
"""
state = game.get('status', {}).get('state', '')
if state == 'in':
# Use flat game dict flags from sports.py
if game.get('is_live'):
return 'live'
elif state == 'post':
elif game.get('is_final'):
return 'recent'
elif state == 'pre':
elif game.get('is_upcoming'):
return 'upcoming'
else:
# Default to upcoming if state is unknown
Expand Down
26 changes: 9 additions & 17 deletions plugins/basketball-scoreboard/sports.py
Original file line number Diff line number Diff line change
Expand Up @@ -195,7 +195,7 @@ def display(self, force_clear: bool = False) -> bool:
if not hasattr(self, "_last_warning_time"):
self._last_warning_time = 0
if current_time - getattr(self, "_last_warning_time", 0) > 300:
self.logger.warning(
self.logger.debug(
f"No game data available to display in {self.__class__.__name__}"
)
setattr(self, "_last_warning_time", current_time)
Expand Down Expand Up @@ -1485,13 +1485,12 @@ def _draw_scorebug_layout(self, game: Dict, force_clear: bool = False) -> None:
# Display away team info
if away_abbr:
if self.show_ranking and self.show_records:
# When both rankings and records are enabled, rankings replace records completely
# Rankings take priority, fall back to record for unranked teams
away_rank = self._team_rankings_cache.get(away_abbr, 0)
if away_rank > 0:
away_text = f"#{away_rank}"
else:
# Show nothing for unranked teams when rankings are prioritized
away_text = ""
away_text = game.get("away_record", "")
elif self.show_ranking:
# Show ranking only if available
away_rank = self._team_rankings_cache.get(away_abbr, 0)
Expand All @@ -1500,7 +1499,6 @@ def _draw_scorebug_layout(self, game: Dict, force_clear: bool = False) -> None:
else:
away_text = ""
elif self.show_records:
# Show record only when rankings are disabled
away_text = game.get("away_record", "")
else:
away_text = ""
Expand All @@ -1520,13 +1518,12 @@ def _draw_scorebug_layout(self, game: Dict, force_clear: bool = False) -> None:
# Display home team info
if home_abbr:
if self.show_ranking and self.show_records:
# When both rankings and records are enabled, rankings replace records completely
# Rankings take priority, fall back to record for unranked teams
home_rank = self._team_rankings_cache.get(home_abbr, 0)
if home_rank > 0:
home_text = f"#{home_rank}"
else:
# Show nothing for unranked teams when rankings are prioritized
home_text = ""
home_text = game.get("home_record", "")
elif self.show_ranking:
# Show ranking only if available
home_rank = self._team_rankings_cache.get(home_abbr, 0)
Expand All @@ -1535,7 +1532,6 @@ def _draw_scorebug_layout(self, game: Dict, force_clear: bool = False) -> None:
else:
home_text = ""
elif self.show_records:
# Show record only when rankings are disabled
home_text = game.get("home_record", "")
else:
home_text = ""
Expand Down Expand Up @@ -2008,13 +2004,12 @@ def format_score(score):
# Display away team info
if away_abbr:
if self.show_ranking and self.show_records:
# When both rankings and records are enabled, rankings replace records completely
# Rankings take priority, fall back to record for unranked teams
away_rank = self._team_rankings_cache.get(away_abbr, 0)
if away_rank > 0:
away_text = f"#{away_rank}"
else:
# Show nothing for unranked teams when rankings are prioritized
away_text = ""
away_text = game.get("away_record", "")
elif self.show_ranking:
# Show ranking only if available
away_rank = self._team_rankings_cache.get(away_abbr, 0)
Expand All @@ -2023,7 +2018,6 @@ def format_score(score):
else:
away_text = ""
elif self.show_records:
# Show record only when rankings are disabled
away_text = game.get("away_record", "")
else:
away_text = ""
Expand All @@ -2043,13 +2037,12 @@ def format_score(score):
# Display home team info
if home_abbr:
if self.show_ranking and self.show_records:
# When both rankings and records are enabled, rankings replace records completely
# Rankings take priority, fall back to record for unranked teams
home_rank = self._team_rankings_cache.get(home_abbr, 0)
if home_rank > 0:
home_text = f"#{home_rank}"
else:
# Show nothing for unranked teams when rankings are prioritized
home_text = ""
home_text = game.get("home_record", "")
elif self.show_ranking:
# Show ranking only if available
home_rank = self._team_rankings_cache.get(home_abbr, 0)
Expand All @@ -2058,7 +2051,6 @@ def format_score(score):
else:
home_text = ""
elif self.show_records:
# Show record only when rankings are disabled
home_text = game.get("home_record", "")
else:
home_text = ""
Expand Down