feat(schedule): per-plugin schedules and hide-while-live

Users asked to turn off news and stocks on gameday so only scores show.
Each plugin can now have its own schedule (global or per-day window in
which it may show) plus hide_when_live, which hides it whenever any
plugin reports a live game. A hidden plugin is skipped in the rotation,
the live takeover and the Vegas ticker; it stays loaded, and on-demand
still shows it.

- src/plugin_schedule.py: the window rule, now shared with the display
  schedule (_check_schedule calls schedule_state)
- core config key plugin_schedules; GET/POST /api/v3/config/plugin-schedules
- Schedule page: Plugin Schedules card, one picker per enabled plugin

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Chuck
2026-10-17 19:29:29 -04:00
co-authored by Claude Opus 5.5
parent 7c51c93707
commit 1c733352b5
15 changed files with 693 additions and 210 deletions
+22 -1
View File
@@ -615,7 +615,28 @@ def _load_schedule_partial():
return render_template('v3/partials/schedule.html',
schedule_config=schedule_config,
dim_schedule_config=dim_schedule_config,
normal_brightness=normal_brightness)
normal_brightness=normal_brightness,
plugin_schedule_rows=_plugin_schedule_rows(main_config))
def _plugin_schedule_rows(main_config):
"""One row per enabled plugin, or plugin with a saved schedule, for the
Plugin Schedules card: ``{id, name, schedule}``, sorted by name."""
schedules = main_config.get('plugin_schedules') or {}
if not pages_v3.plugin_catalog:
return []
try:
pages_v3.plugin_catalog.discover_plugins()
infos = pages_v3.plugin_catalog.get_all_plugin_info()
except Exception:
logger.warning("schedule: could not enumerate plugins", exc_info=True)
return []
rows = [{'id': info['id'], 'name': info.get('name') or info['id'],
'schedule': schedules.get(info['id']) or {}}
for info in infos
if info.get('id') and ((main_config.get(info['id']) or {}).get('enabled', False)
or info['id'] in schedules)]
return sorted(rows, key=lambda r: r['name'].lower())
def _load_plugins_partial():