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
6 changes: 3 additions & 3 deletions plugins.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"version": "1.0.0",
"last_updated": "2026-02-15",
"last_updated": "2026-02-17",
"plugins": [
{
"id": "hello-world",
Expand Down Expand Up @@ -296,10 +296,10 @@
"plugin_path": "plugins/baseball-scoreboard",
"stars": 0,
"downloads": 0,
"last_updated": "2026-02-15",
"last_updated": "2026-02-17",
"verified": true,
"screenshot": "",
"latest_version": "1.3.0"
"latest_version": "1.3.1"
},
{
"id": "soccer-scoreboard",
Expand Down
191 changes: 102 additions & 89 deletions plugins/baseball-scoreboard/baseball.py
Original file line number Diff line number Diff line change
Expand Up @@ -225,8 +225,13 @@ def _extract_game_details(self, game_event: Dict) -> Optional[Dict]:
# Get count and bases from situation
situation = game_event["competitions"][0].get("situation", {})

# NCAA baseball API doesn't provide count/outs data (only onFirst/onSecond/onThird)
# Use league identifier for deterministic detection instead of key-presence heuristic
has_count_data = self.league != "college-baseball"

if is_favorite_game:
self.logger.debug(f"Full situation data: {situation}")
self.logger.debug(f"has_count_data: {has_count_data}")
Comment on lines +228 to +234
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 | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Verify self.league is set in a parent class (SportsCore or similar)
echo "=== Searching for 'self.league' assignments ==="
rg -n --type=py 'self\.league\s*=' -C 3

echo ""
echo "=== Searching for 'college-baseball' string usage ==="
rg -n --type=py 'college-baseball' -C 2

echo ""
echo "=== Searching for class definition and inheritance ==="
rg -n --type=py 'class Baseball' -C 5

echo ""
echo "=== Searching for SportsCore class ==="
rg -n --type=py 'class SportsCore' -C 5

Repository: ChuckBuilds/ledmatrix-plugins

Length of output: 33978


🏁 Script executed:

#!/bin/bash
# Check for version information in baseball-scoreboard plugin
echo "=== Looking for version files in baseball-scoreboard plugin ==="
find plugins/baseball-scoreboard -type f -name "*version*" -o -name "setup.py" -o -name "pyproject.toml" -o -name "__init__.py" | head -20

echo ""
echo "=== Checking for __version__ string in baseball-scoreboard plugin files ==="
rg -n --type=py '__version__' plugins/baseball-scoreboard/

echo ""
echo "=== Checking the main plugin manager file for version info ==="
head -50 plugins/baseball-scoreboard/manager.py | grep -i version

Repository: ChuckBuilds/ledmatrix-plugins

Length of output: 277


🏁 Script executed:

#!/bin/bash
# Check for version management at repository and plugin root level
echo "=== Checking repository root for version files ==="
ls -la | grep -i version || echo "No version files in root"

echo ""
echo "=== Checking plugins directory for version.py or similar ==="
find plugins -maxdepth 2 -name "*version*" -type f

echo ""
echo "=== Check if version is in baseball plugin __init__.py ==="
if [ -f "plugins/baseball-scoreboard/__init__.py" ]; then
  cat plugins/baseball-scoreboard/__init__.py
else
  echo "No __init__.py in baseball-scoreboard"
fi

echo ""
echo "=== Check parent of plugins directory ==="
ls -la plugins/ | head -20

echo ""
echo "=== Search for version pattern in any config files ==="
rg -n '"version":|version\s*=' plugins/baseball-scoreboard/ | head -10

Repository: ChuckBuilds/ledmatrix-plugins

Length of output: 2269


🏁 Script executed:

#!/bin/bash
# Get the full manifest.json to see version history and changelog
echo "=== Full manifest.json content ==="
cat plugins/baseball-scoreboard/manifest.json

Repository: ChuckBuilds/ledmatrix-plugins

Length of output: 1786


Correct the version bump to 1.2.1 per coding guidelines for bug fixes.

The version was bumped to 1.3.0 (a MINOR version increment), but the coding guideline for **/plugins/**/*.py requires PATCH version bumps (1.2.x) for bug fixes. Since this PR fixes the NCAA baseball scorebug missing count/outs data, the version should be 1.2.1, not 1.3.0. Update manifest.json to reflect this.

(Note: The self.league attribute is properly defined in SportsCore.__init__ and correctly set to "college-baseball" by the NCAA Baseball manager, so the detection logic is sound.)

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@plugins/baseball-scoreboard/baseball.py` around lines 228 - 234, Update the
plugin manifest.json version from 1.3.0 to the correct PATCH bump 1.2.1 for this
bug fix (this plugin's manifest in the baseball-scoreboard package), ensuring
the version string follows semantic versioning "1.2.1" and commit the change; no
code changes are needed in baseball.py (the detection logic using self.league is
correct).


# Get count from the correct location in the API response
count = situation.get("count", {})
Expand Down Expand Up @@ -289,6 +294,7 @@ def _extract_game_details(self, game_event: Dict) -> Optional[Dict]:
strikes = 0
outs = 0
bases_occupied = [False, False, False]
has_count_data = False

details.update(
{
Expand All @@ -300,6 +306,7 @@ def _extract_game_details(self, game_event: Dict) -> Optional[Dict]:
"strikes": strikes,
"outs": outs,
"bases_occupied": bases_occupied,
"has_count_data": has_count_data,
"start_time": game_event["date"],
"series_summary": series_summary,
}
Expand Down Expand Up @@ -434,14 +441,14 @@ def _draw_scorebug_layout(self, game: Dict, force_clear: bool = False) -> None:

center_y = self.display_height // 2

# Draw logos (shifted slightly more inward than NHL perhaps)
# Draw logos with slight edge bleed
home_x = (
self.display_width - home_logo.width + 10
self.display_width - home_logo.width + 2
)
home_y = center_y - (home_logo.height // 2)
main_img.paste(home_logo, (home_x, home_y), home_logo)

away_x = -10
away_x = -2
away_y = center_y - (away_logo.height // 2)
main_img.paste(away_logo, (away_x, away_y), away_logo)

Expand Down Expand Up @@ -506,19 +513,22 @@ def _draw_scorebug_layout(self, game: Dict, force_clear: bool = False) -> None:
bases_origin_x = (self.display_width - base_cluster_width) // 2

# Determine relative positions for outs based on inning half
if inning_half == "top": # Away batting, outs on left
outs_column_x = (
bases_origin_x - spacing_between_bases_outs - out_cluster_width
)
else: # Home batting, outs on right
outs_column_x = (
bases_origin_x + base_cluster_width + spacing_between_bases_outs
)
# Only compute outs column position when count data is available
has_count_data = game.get("has_count_data", True)
if has_count_data:
if inning_half == "top": # Away batting, outs on left
outs_column_x = (
bases_origin_x - spacing_between_bases_outs - out_cluster_width
)
else: # Home batting, outs on right
outs_column_x = (
bases_origin_x + base_cluster_width + spacing_between_bases_outs
)

# Calculate vertical alignment offset for outs column (center align with bases cluster)
outs_column_start_y = (
overall_start_y + (base_cluster_height // 2) - (out_cluster_height // 2)
)
# Calculate vertical alignment offset for outs column (center align with bases cluster)
outs_column_start_y = (
overall_start_y + (base_cluster_height // 2) - (out_cluster_height // 2)
)

# --- Draw Bases (Diamonds) ---
base_color_occupied = (255, 255, 255)
Expand Down Expand Up @@ -570,84 +580,87 @@ def _draw_scorebug_layout(self, game: Dict, force_clear: bool = False) -> None:
draw_overlay.polygon(poly1, outline=base_color_empty)

# --- Draw Outs (Vertical Circles) ---
circle_color_out = (255, 255, 255)
circle_color_empty_outline = (100, 100, 100)

for i in range(3):
cx = outs_column_x
cy = outs_column_start_y + i * (
out_circle_diameter + out_vertical_spacing
)
coords = [cx, cy, cx + out_circle_diameter, cy + out_circle_diameter]
if i < outs:
draw_overlay.ellipse(coords, fill=circle_color_out)
else:
draw_overlay.ellipse(coords, outline=circle_color_empty_outline)
# Only render outs and count when data is available (ESPN NCAA doesn't provide these)
if has_count_data:
circle_color_out = (255, 255, 255)
circle_color_empty_outline = (100, 100, 100)

for i in range(3):
cx = outs_column_x
cy = outs_column_start_y + i * (
out_circle_diameter + out_vertical_spacing
)
coords = [cx, cy, cx + out_circle_diameter, cy + out_circle_diameter]
if i < outs:
draw_overlay.ellipse(coords, fill=circle_color_out)
else:
draw_overlay.ellipse(coords, outline=circle_color_empty_outline)

# --- Draw Balls-Strikes Count (BDF Font) ---
balls = game.get("balls", 0)
strikes = game.get("strikes", 0)

# Add debug logging for count with cooldown
current_time = time.time()
if (
game["home_abbr"] in self.favorite_teams
or game["away_abbr"] in self.favorite_teams
) and current_time - self.last_count_log_time >= self.count_log_interval:
self.logger.debug(f"Displaying count: {balls}-{strikes}")
self.logger.debug(
f"Raw count data: balls={game.get('balls')}, strikes={game.get('strikes')}"
)
self.last_count_log_time = current_time

count_text = f"{balls}-{strikes}"
bdf_font = self.display_manager.calendar_font
if not hasattr(self, '_bdf_font_sized'):
bdf_font.set_char_size(height=7 * 64) # Set 7px height once
self._bdf_font_sized = True
count_text_width = self.display_manager.get_text_width(count_text, bdf_font)

# Position below the base/out cluster
cluster_bottom_y = (
overall_start_y + base_cluster_height
) # Find the bottom of the taller part (bases)
count_y = cluster_bottom_y + 2 # Start 2 pixels below cluster

# Center horizontally within the BASE cluster width
count_x = bases_origin_x + (base_cluster_width - count_text_width) // 2

# Temporarily set draw object for BDF text rendering, then restore
original_draw = self.display_manager.draw
self.display_manager.draw = draw_overlay
try:
# Draw Balls-Strikes Count with outline using BDF font
outline_color_for_bdf = (0, 0, 0)

# Draw outline
for dx_offset, dy_offset in [
(-1, -1),
(-1, 0),
(-1, 1),
(0, -1),
(0, 1),
(1, -1),
(1, 0),
(1, 1),
]:
self.display_manager._draw_bdf_text(
count_text,
count_x + dx_offset,
count_y + dy_offset,
color=outline_color_for_bdf,
font=bdf_font,
if has_count_data:
balls = game.get("balls", 0)
strikes = game.get("strikes", 0)

# Add debug logging for count with cooldown
current_time = time.time()
if (
game["home_abbr"] in self.favorite_teams
or game["away_abbr"] in self.favorite_teams
) and current_time - self.last_count_log_time >= self.count_log_interval:
self.logger.debug(f"Displaying count: {balls}-{strikes}")
self.logger.debug(
f"Raw count data: balls={game.get('balls')}, strikes={game.get('strikes')}"
)
self.last_count_log_time = current_time

count_text = f"{balls}-{strikes}"
bdf_font = self.display_manager.calendar_font
if not hasattr(self, '_bdf_font_sized'):
bdf_font.set_char_size(height=7 * 64) # Set 7px height once
self._bdf_font_sized = True
count_text_width = self.display_manager.get_text_width(count_text, bdf_font)

# Position below the base/out cluster
cluster_bottom_y = (
overall_start_y + base_cluster_height
) # Find the bottom of the taller part (bases)
count_y = cluster_bottom_y + 2 # Start 2 pixels below cluster

# Center horizontally within the BASE cluster width
count_x = bases_origin_x + (base_cluster_width - count_text_width) // 2

# Temporarily set draw object for BDF text rendering, then restore
original_draw = self.display_manager.draw
self.display_manager.draw = draw_overlay
try:
# Draw Balls-Strikes Count with outline using BDF font
outline_color_for_bdf = (0, 0, 0)

# Draw outline
for dx_offset, dy_offset in [
(-1, -1),
(-1, 0),
(-1, 1),
(0, -1),
(0, 1),
(1, -1),
(1, 0),
(1, 1),
]:
self.display_manager._draw_bdf_text(
count_text,
count_x + dx_offset,
count_y + dy_offset,
color=outline_color_for_bdf,
font=bdf_font,
)

# Draw main text
self.display_manager._draw_bdf_text(
count_text, count_x, count_y, color=text_color, font=bdf_font
)
finally:
self.display_manager.draw = original_draw
# Draw main text
self.display_manager._draw_bdf_text(
count_text, count_x, count_y, color=text_color, font=bdf_font
)
finally:
self.display_manager.draw = original_draw

# Draw Team:Score at the bottom (matching main branch format)
score_font = self.display_manager.font # Use PressStart2P
Expand Down
54 changes: 29 additions & 25 deletions plugins/baseball-scoreboard/game_renderer.py
Original file line number Diff line number Diff line change
Expand Up @@ -210,13 +210,15 @@ def _render_live_game(self, game: Dict) -> Image.Image:
overall_start_y = inning_bbox[3] + 1
bases_origin_x = (self.display_width - base_cluster_width) // 2

# Outs column position
# Outs column position (only needed when count data is available)
has_count_data = game.get('has_count_data', True)
out_cluster_height = 3 * out_circle_diameter + 2 * out_vertical_spacing
if inning_half == 'top':
outs_column_x = bases_origin_x - spacing_between_bases_outs - out_circle_diameter
else:
outs_column_x = bases_origin_x + base_cluster_width + spacing_between_bases_outs
outs_column_start_y = overall_start_y + (base_cluster_height // 2) - (out_cluster_height // 2)
if has_count_data:
if inning_half == 'top':
outs_column_x = bases_origin_x - spacing_between_bases_outs - out_circle_diameter
else:
outs_column_x = bases_origin_x + base_cluster_width + spacing_between_bases_outs
outs_column_start_y = overall_start_y + (base_cluster_height // 2) - (out_cluster_height // 2)

# Draw bases as diamond polygons
h_d = base_diamond_size // 2
Expand All @@ -243,25 +245,27 @@ def _render_live_game(self, game: Dict) -> Image.Image:
poly1 = [(c1x, base_bottom_y + base_vert_spacing), (c1x + h_d, c1y), (c1x, c1y + h_d), (c1x - h_d, c1y)]
draw.polygon(poly1, fill=base_fill if bases_occupied[0] else None, outline=base_outline)

# Outs circles
for i in range(3):
cx = outs_column_x
cy = outs_column_start_y + i * (out_circle_diameter + out_vertical_spacing)
coords = [cx, cy, cx + out_circle_diameter, cy + out_circle_diameter]
if i < outs:
draw.ellipse(coords, fill=(255, 255, 255))
else:
draw.ellipse(coords, outline=(100, 100, 100))

# Balls-strikes count (below bases)
balls = game.get('balls', 0)
strikes = game.get('strikes', 0)
count_text = f"{balls}-{strikes}"
count_font = self.fonts['detail']
count_width = draw.textlength(count_text, font=count_font)
count_y = overall_start_y + base_cluster_height + 2
count_x = bases_origin_x + (base_cluster_width - count_width) // 2
self._draw_text_with_outline(draw, count_text, (int(count_x), count_y), count_font)
# Outs circles (only when count data is available)
if has_count_data:
for i in range(3):
cx = outs_column_x
cy = outs_column_start_y + i * (out_circle_diameter + out_vertical_spacing)
coords = [cx, cy, cx + out_circle_diameter, cy + out_circle_diameter]
if i < outs:
draw.ellipse(coords, fill=(255, 255, 255))
else:
draw.ellipse(coords, outline=(100, 100, 100))

# Balls-strikes count (below bases, only when count data is available)
if has_count_data:
balls = game.get('balls', 0)
strikes = game.get('strikes', 0)
count_text = f"{balls}-{strikes}"
count_font = self.fonts['detail']
count_width = draw.textlength(count_text, font=count_font)
count_y = overall_start_y + base_cluster_height + 2
count_x = bases_origin_x + (base_cluster_width - count_width) // 2
self._draw_text_with_outline(draw, count_text, (int(count_x), count_y), count_font)

# Team:Score at bottom corners
score_font = self.fonts['score']
Expand Down
7 changes: 6 additions & 1 deletion plugins/baseball-scoreboard/manifest.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"id": "baseball-scoreboard",
"name": "Baseball Scoreboard",
"version": "1.3.0",
"version": "1.3.1",
"author": "ChuckBuilds",
"description": "Live, recent, and upcoming baseball games across MLB, MiLB, and NCAA Baseball with real-time scores and schedules",
"category": "sports",
Expand Down Expand Up @@ -30,6 +30,11 @@
"branch": "main",
"plugin_path": "plugins/baseball-scoreboard",
"versions": [
{
"released": "2026-02-17",
"version": "1.3.1",
"ledmatrix_min": "2.0.0"
},
{
"released": "2026-02-15",
"version": "1.3.0",
Expand Down
1 change: 1 addition & 0 deletions plugins/baseball-scoreboard/ncaa_baseball_managers.py
Original file line number Diff line number Diff line change
Expand Up @@ -209,6 +209,7 @@ def __init__(self, config: Dict[str, Any], display_manager, cache_manager):
"away_logo_url": "",
"status_text": "Bot 7th",
"series_summary": "",
"has_count_data": False,
}
self.live_games = [self.current_game]
self.logger.info(
Expand Down