Skip to content
Open
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
20 changes: 10 additions & 10 deletions plugins.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@
"last_updated": "2026-07-17",
"verified": true,
"screenshot": "",
"latest_version": "1.0.4"
"latest_version": "1.1.1"
},
{
"id": "7-segment-clock",
Expand Down Expand Up @@ -76,7 +76,7 @@
"last_updated": "2026-08-05",
"verified": true,
"screenshot": "",
"latest_version": "1.24.4"
"latest_version": "1.25.1"
},
{
"id": "basketball-scoreboard",
Expand All @@ -101,7 +101,7 @@
"last_updated": "2026-08-05",
"verified": true,
"screenshot": "",
"latest_version": "1.12.1"
"latest_version": "1.13.1"
},
{
"id": "calendar",
Expand Down Expand Up @@ -240,7 +240,7 @@
"last_updated": "2026-08-05",
"verified": true,
"screenshot": "",
"latest_version": "2.14.2"
"latest_version": "2.15.1"
},
{
"id": "geochron",
Expand Down Expand Up @@ -335,7 +335,7 @@
"last_updated": "2026-08-05",
"verified": true,
"screenshot": "",
"latest_version": "1.9.2",
"latest_version": "1.10.1",
"icon": "fas fa-hockey-puck"
},
{
Expand All @@ -359,7 +359,7 @@
"last_updated": "2026-08-05",
"verified": true,
"screenshot": "",
"latest_version": "1.9.2",
"latest_version": "1.10.1",
"icon": "fas fa-baseball-ball"
},
{
Expand Down Expand Up @@ -760,7 +760,7 @@
"last_updated": "2026-08-05",
"verified": true,
"screenshot": "",
"latest_version": "2.9.2"
"latest_version": "2.10.1"
},
{
"id": "static-image",
Expand Down Expand Up @@ -905,7 +905,7 @@
"last_updated": "2026-08-05",
"verified": true,
"screenshot": "",
"latest_version": "1.3.5",
"latest_version": "1.4.1",
"icon": "fas fa-fist-raised"
},
{
Expand Down Expand Up @@ -1048,7 +1048,7 @@
"downloads": 0,
"verified": true,
"screenshot": "",
"latest_version": "1.6.2",
"latest_version": "1.7.1",
"last_updated": "2026-08-05"
},
{
Expand Down Expand Up @@ -1095,7 +1095,7 @@
"last_updated": "2026-08-05",
"verified": true,
"screenshot": "",
"latest_version": "1.6.2"
"latest_version": "1.7.1"
},
{
"id": "jellyfin-now-playing",
Expand Down
39 changes: 39 additions & 0 deletions plugins/afl-scoreboard/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -192,3 +192,42 @@ a loss.
internet access and logo cache directory permissions.
- **Slow updates**: Adjust `update_interval_seconds` / `live_update_interval`.
- **API errors**: Check your internet connection and ESPN API availability.

## Vegas ticker: seeing live games more often

By default a live game **takes over** the display: the Vegas ticker stops and
this scoreboard shows full screen until the game ends. If you would rather keep
the marquee scrolling and still see scores, set this in the core config:

```json
{
"display": {
"vegas_scroll": {
"live_in_ticker": true,
"live_weight": 3,
"favorite_live_weight": 5
}
}
}
```

The ticker is otherwise a strict round robin — every plugin appears once per
cycle — so with a dozen plugins enabled a score comes round once a lap. These
weights let this plugin claim several slots per cycle, spaced evenly through
it rather than bunched together.

`live_weight` applies whenever this scoreboard has a live game.
`favorite_live_weight` applies when one of your `favorite_teams` is playing, so
your team's game comes round more often than other live games. That distinction
has to be made here rather than in the core, which can tell *that* a game is
live but not *whose*.

Two things to keep in mind:

- The weight is per **plugin**, not per game. With four games live this
scoreboard still occupies one slot at a time and picks between its own games
using `favorite_live_boost`; these weights control how often the scoreboard
itself comes round.
- More slots make the cycle **longer**, not faster — everything else appears
proportionally less often. And appearing more often only helps if the data is
fresh, which is governed by this plugin's own live update interval.
109 changes: 109 additions & 0 deletions plugins/afl-scoreboard/manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -727,6 +727,115 @@ def _live_manager_has_favorite_live(self, live_manager) -> bool:
)
return False

# --- Vegas ticker weighting ------------------------------------------
#
# With display.vegas_scroll.live_in_ticker set, the marquee keeps running
# through a live game and plugins can claim more than one slot per cycle.
# The core already gives any plugin with live content `live_weight`; this
# exists for the one thing the core cannot work out for itself, which is
# *whose* game is live. See the core's PLUGIN_API_REFERENCE, "Vegas scroll
# hooks", and ADVANCED_FEATURES, "Live content in the ticker".

def get_vegas_priority_weight(self):
"""Slots per Vegas cycle: more when a favorite team is playing.

Returns None when nothing is live, which leaves the decision to the
core rather than asserting a weight of 1 -- the core may have its own
reason to boost this plugin later.
"""
try:
if not (self.has_live_priority() and self.has_live_content()):
return None
vegas = (self.global_config or {}).get('display', {}).get(
'vegas_scroll', {})
if self._favorite_team_is_live():
return vegas.get('favorite_live_weight', 5)
return vegas.get('live_weight', 3)
except Exception:
# Never let a weighting question break the rotation; the core
# treats an exception as weight 1 anyway, and None says the same
# thing more cheaply.
return None

def _favorite_team_is_live(self):
"""Whether any live game or fight involves a configured favorite.

The sports plugins do not share one data shape, so this enumerates the
real ones rather than assuming. An earlier version looked only for an
attribute holding `live_games` alongside `favorite_teams`, which was
true of five plugins and quietly false for four others -- they simply
never reported a favorite, and no test noticed because the tests used
the assumed shape rather than each plugin's own.

Handled:

* managers held directly on the plugin *and* inside a dict such as
``self._managers`` (nrl, afl)
* ``live_games`` (most) and ``live_matches`` (cricket)
* ``favorite_teams`` (most) and ``favorite_fighters`` (ufc)
* identifiers ``home_abbr``/``away_abbr``, ``home_id``/``away_id``,
``fighter1_name``/``fighter2_name``, and cricket's nested
``teams: [{name, abbr, short_name}]``
* ``active_celebration["game"]``, a snapshot the live manager keeps
precisely because the game leaves ``live_games`` while the
celebration is still on screen
"""
for holder in self._favorite_scan_targets():
favorites = (getattr(holder, 'favorite_teams', None)
or getattr(holder, 'favorite_fighters', None))
if not favorites:
continue
wanted = {str(f).strip().lower() for f in favorites if f}
if not wanted:
continue
for game in self._favorite_scan_games(holder):
if self._game_involves(game, wanted):
return True
return False

def _favorite_scan_targets(self):
"""Objects that might carry live content: attributes, and dict values.

nrl and afl keep their per-league managers in a ``self._managers``
dict, so walking attribute values alone finds the dict and stops.
"""
for value in list(vars(self).values()):
yield value
if isinstance(value, dict):
for nested in list(value.values()):
yield nested

@staticmethod
def _favorite_scan_games(holder):
"""Every game/fight on a holder that a favorite could be playing in."""
for attr in ('live_games', 'live_matches'):
for game in (getattr(holder, attr, None) or []):
if isinstance(game, dict):
yield game
celebration = getattr(holder, 'active_celebration', None)
if isinstance(celebration, dict) and isinstance(celebration.get('game'), dict):
yield celebration['game']

@staticmethod
def _game_involves(game, wanted):
"""Whether a game/fight involves one of the wanted names."""
for field in ('home_abbr', 'away_abbr', 'home_id', 'away_id',
'fighter1_name', 'fighter2_name'):
value = game.get(field)
if value is not None and str(value).strip().lower() in wanted:
return True
# Cricket nests its sides and matches on any of three names, by
# substring -- "india" should match "India Women". Mirrors that
# plugin's own _match_has_team rather than inventing a second rule.
for team in (game.get('teams') or []):
if not isinstance(team, dict):
continue
hay = " ".join(str(team.get(k) or '') for k in
('name', 'abbr', 'short_name')).lower()
if any(name in hay for name in wanted):
return True
return False

def has_live_content(self) -> bool:
"""Whether there is live content worth showing."""
if not self.is_enabled:
Expand Down
14 changes: 13 additions & 1 deletion plugins/afl-scoreboard/manifest.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"id": "afl-scoreboard",
"name": "AFL Scoreboard",
"version": "1.6.2",
"version": "1.7.1",
"author": "ChuckBuilds",
"description": "Live, recent, and upcoming AFL (Australian Football League) games with real-time scores and game status.",
"category": "sports",
Expand All @@ -18,6 +18,18 @@
"afl_upcoming"
],
"versions": [
{
"version": "1.7.1",
"released": "2026-08-12",
"ledmatrix_min_version": "2.0.0",
"notes": "Match favorites against this plugin's real live-data shape. The first cut looked for an attribute holding live_games beside favorite_teams, which is true of five scoreboards and quietly false for four: cricket keeps live_matches, nrl and afl hold their managers in a dict rather than as attributes, and ufc has favorite_fighters and fighter names. Those four reported every live game as non-favorite. A celebrating game is searched too, since the live manager snapshots it out of live_games while the celebration is on screen."
},
{
"version": "1.7.0",
"released": "2026-08-12",
"ledmatrix_min_version": "2.0.0",
"notes": "Ask for more turns in the Vegas ticker while a game is live, and more again when a favorite team is playing. Only takes effect with the core's display.vegas_scroll.live_in_ticker set, which keeps the marquee running instead of handing the display to a full-screen scoreboard. The core grants a live plugin live_weight on its own; this reports the favorite case, which the core cannot see -- it knows a game is live but not whose."
},
{
"version": "1.6.2",
"released": "2026-08-12",
Expand Down
Loading
Loading