Various pieces to create a dashboard to track various sporting events using the ESPN API for data collection.
  • PHP 83.4%
  • Python 14.2%
  • Shell 2.4%
Find a file
2026-06-23 22:18:20 -07:00
game_canceled Add readOnly key/value pair for "system" lists of certain teams to use as examples of custom user filtered lists. 2026-06-17 21:11:32 -07:00
zabbix Bug fix: the traffic stats now represent unique visits (best effort) instead of every load. 2026-06-19 16:45:10 -07:00
.gitignore Improve overlay to include 3rd place race for World Cup 2026. Create new helper directory and script to calculate league-wide results. 2026-06-19 16:20:31 -07:00
add_game.php Add readOnly key/value pair for "system" lists of certain teams to use as examples of custom user filtered lists. 2026-06-17 21:11:32 -07:00
database_migration_plan.md Migration plan to use a database for the future. 2026-06-21 00:32:52 -07:00
deploy.sh Make sure deployment script pulls the latest changes first. 2026-06-21 18:22:31 -07:00
espn_proxy.php Improve overlay to include 3rd place race for World Cup 2026. Create new helper directory and script to calculate league-wide results. 2026-06-19 16:20:31 -07:00
game-watcher.service Move the log files to the standard on linux /var/log 2026-06-19 17:03:10 -07:00
game_watcher.py Slow down API calls. Live games, 1 min; scheduled games, 2 min. 2026-06-23 20:57:21 -07:00
get_watchlist.php Add readOnly key/value pair for "system" lists of certain teams to use as examples of custom user filtered lists. 2026-06-17 21:11:32 -07:00
index.php Remove update timer from UI 2026-06-23 22:18:20 -07:00
README.md Improve overlay to include 3rd place race for World Cup 2026. Create new helper directory and script to calculate league-wide results. 2026-06-19 16:20:31 -07:00

Game Watcher

A self-hosted ESPN game monitoring dashboard. A Python service polls the ESPN public API for live game updates and writes a JSON file that a PHP/JS frontend reads to display scores.

How it works

  1. Drop a game file into game_scheduled/ — one JSON file per game you want to track.
  2. The watcher service (game_watcher.py) runs in a loop:
    • Every 60 seconds it checks all scheduled games against the ESPN API.
    • When a game goes live it moves the file to game_live/ and polls it every 10 seconds.
    • When a game ends it moves the file to game_completed/.
    • Completed games older than 3 days are automatically deleted.
  3. Every tick the watcher writes games_data.json — a snapshot of all games across all three folders.
  4. The frontend (index.php) polls games_data.json every 10 seconds and renders the scoreboard.
  5. On card click, the frontend fetches richer data on-demand from espn_proxy.php and displays it in an overlay.

Directory structure

game_watcher/
├── game_watcher.py       # background service
├── index.php             # web frontend
├── espn_proxy.php        # server-side ESPN API proxy (avoids CORS)
├── add_game.php          # API endpoint: add a game to the watch list
├── get_watchlist.php     # API endpoint: read a user's watch list
├── games_data.json       # generated — do not edit
├── game_scheduled/       # drop new game files here
├── game_live/            # managed by the service
├── game_completed/       # managed by the service
├── game_canceled/        # managed by the service
├── user_data/            # per-user watch list JSON files
├── league_helper/        # cached ESPN API data (generated, 10-min TTL)
├── game_watcher.log      # service log
└── zabbix/               # Zabbix monitoring integration
    ├── game_watcher_stats.sh
    ├── game_watcher.conf
    └── template_game_watcher.yaml

Adding a game

Create a JSON file in game_scheduled/ with the event ID, sport, and league:

{
    "event_id": "401696853",
    "sport": "baseball",
    "league": "mlb"
}

The filename can be anything ending in .json — a descriptive name like mlb_401696853.json works well. The event_id is the number at the end of the ESPN scoreboard URL for the game (e.g. espn.com/mlb/game/_/gameId/401696853).

Favorite teams

Teams listed in FAVORITE_ABBRS in game_watcher.py get a gold left border in the UI. Abbreviations are league-qualified (ABBR:league) to avoid collisions between teams that share an abbreviation across sports.

Current favorites:

Team Entry
Bay FC (NWSL) BAY:usa.nwsl

Game card overlays

Clicking any game card opens a full-screen overlay with richer detail. Overlays are sport-aware:

  • Soccer (FIFA World Cup): goal timeline, halftime score, match stats (possession, shots, saves, fouls, cards), group standings with advancement indicators, and a cross-group 3rd-place race table showing which third-place teams are currently on track to advance.
  • All other sports: scoreboard, linescore (if available), venue, and broadcast info.

The overlay fetches data on-demand from espn_proxy.php when opened, so live game stats are always current. Group standings for all 12 World Cup groups are cached in league_helper/ for 10 minutes to avoid redundant API calls.

June 28 note (World Cup)

Starting June 28, ESPN's API transitions from group-stage to knockout format. Knockout-stage games (identifiable by having no group_name) skip the group advancement UI automatically.

ESPN proxy

espn_proxy.php is a thin server-side proxy that forwards requests to ESPN's public API endpoints. It exists to avoid CORS issues with direct browser fetches. Supported actions:

Action Description
groups Division/conference hierarchy for a league (24h cache)
teams Team list, optionally filtered by group/conference
schedule Team schedule, with scoreboard supplement for tournament leagues
summary Full event summary (box score, stats, standings) for a single game
group_standings All-group standings for a tournament league (10-min cache)

Running the service

sudo systemctl start game-watcher
sudo systemctl enable game-watcher
sudo systemctl status game-watcher

Logs are written to game_watcher.log and also to stdout (captured by journald).

Zabbix monitoring

The zabbix/ directory contains a Zabbix Agent2 integration that exposes service metrics:

  • Game counts (live, scheduled, completed, total)
  • ESPN API call and error counters
  • Age of games_data.json (staleness check)
  • nginx visitor hits to /game_watcher/ in the last hour

Deploy

sudo mkdir -p /etc/zabbix/scripts
sudo cp zabbix/game_watcher_stats.sh /etc/zabbix/scripts/
sudo chmod +x /etc/zabbix/scripts/game_watcher_stats.sh
sudo cp zabbix/game_watcher.conf /etc/zabbix/zabbix_agent2.d/
sudo usermod -aG adm zabbix
sudo systemctl restart zabbix-agent2

Then import zabbix/template_game_watcher.yaml into Zabbix (Configuration → Templates → Import) and link it to the host.


Development

This project is actively developed with assistance from Claude (Anthropic). Current version: Claude Sonnet 4.6.