diff --git a/README.md b/README.md index 4cd6638..f2a995c 100644 --- a/README.md +++ b/README.md @@ -4,27 +4,57 @@

Alertle-V2

-

Sports alert notifications powered by ESPN, EPG, and your preferred push service.

+

He's slow. Your alerts aren't.

+ +

A self-hosted sports alert system. NHL, NBA, NFL, Soccer, Golf, F1, and more — straight to your phone.

+ +--- + +## Meet the Alertle Turtle 🐢 + +The Alertle Turtle is our mascot, our spirit animal, and our greatest irony. He is notoriously slow. He does not rush for anyone. And yet somehow, he always makes sure your game alerts arrive on time. + +Don't miss a game. The Alertle Turtle's got you. + +## ⚡ Vibe Coded + +This entire project was built through conversation with Claude AI — no code was written by hand. No documentation was read. No Stack Overflow tabs were opened. Just vibes, prompts, and one very good turtle idea. + +If it works, great. If something breaks, that's also the vibes. PRs are welcome. So is asking an AI about it. --- ## Quick Start -```bash -# Pull and run with Docker Compose -curl -o docker-compose.yml https://raw.githubusercontent.com/Deekerman/Alertle-V2/main/docker-compose.yml -curl -o config.yaml https://raw.githubusercontent.com/Deekerman/Alertle-V2/main/config.yaml.example -# Edit config.yaml with your settings, then: -docker compose up -d +```yaml +services: + alertle-v2: + image: ghcr.io/deekerman/alertle-v2:b4f36b8 + container_name: alertle-v2 + pull_policy: always + restart: unless-stopped + environment: + TZ: America/Toronto + ports: + - 8888:8888 + volumes: + - ./alertle-v2/config.yaml:/config/config.yaml + - alertle_data:/app/data +volumes: + alertle_data: null +networks: {} ``` -Or pull the image directly: - ```bash -docker pull ghcr.io/deekerman/alertle-v2:latest +# Create the config directory and grab the example config +mkdir -p alertle-v2 +curl -o alertle-v2/config.yaml https://raw.githubusercontent.com/Deekerman/Alertle-V2/main/config.yaml.example + +# Edit config.yaml, then: +docker compose up -d ``` -The web UI will be available at `http://localhost:8888`. +The web UI will be available at `http://localhost:8888`. The first-start wizard will walk you through the rest. --- @@ -39,6 +69,8 @@ The web UI will be available at `http://localhost:8888`. - Fully configurable notification templates with per-endpoint overrides - 7-day lookahead scan with configurable daily scan time - Web UI for managing subscriptions, endpoints, settings, and viewing upcoming alerts +- First-start setup wizard so you're not staring at a blank config +- Backup and restore — including import from V1 ## How It Works @@ -80,22 +112,15 @@ sudo journalctl -u alertle-v2 -f ### Docker ```bash -git clone https://github.com/Deekerman/Alertle-V2.git -cd Alertle-V2 -cp config.yaml.example config.yaml -# Edit config.yaml with your settings +mkdir -p alertle-v2 +curl -o alertle-v2/config.yaml https://raw.githubusercontent.com/Deekerman/Alertle-V2/main/config.yaml.example +curl -o docker-compose.yml https://raw.githubusercontent.com/Deekerman/Alertle-V2/main/docker-compose.yml +# Edit alertle-v2/config.yaml, then: docker compose up -d ``` The web UI will be available at `http://localhost:8888`. -```yaml -# docker-compose.yml mounts -volumes: - - ./config.yaml:/config/config.yaml - - alertle_data:/data -``` - ## Configuration Copy `config.yaml.example` to `config.yaml` and edit. The web UI at `/settings` can manage most options after first launch. diff --git a/config.py b/config.py index 6d19d52..9758a23 100644 --- a/config.py +++ b/config.py @@ -17,13 +17,19 @@ def _load_raw() -> dict: - if not CONFIG_PATH.exists(): + if not CONFIG_PATH.exists() or CONFIG_PATH.is_dir(): return {} with CONFIG_PATH.open() as f: return yaml.safe_load(f) or {} def _save_raw(data: dict) -> None: + if CONFIG_PATH.is_dir(): + raise RuntimeError( + f"Config path '{CONFIG_PATH}' is a directory, not a file. " + "On the host, run: rm -rf ./alertle-v2/config.yaml && " + "curl -o alertle-v2/config.yaml " + ) with CONFIG_PATH.open("w") as f: yaml.dump(data, f, default_flow_style=False, allow_unicode=True) diff --git a/docker-compose.yml b/docker-compose.yml index ae858f6..6fccc81 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,18 +1,17 @@ -version: "3.8" - services: alertle-v2: image: ghcr.io/deekerman/alertle-v2:latest - build: . # optional: remove this line to always pull from GHCR container_name: alertle-v2 + pull_policy: always + restart: unless-stopped + environment: + TZ: America/Toronto + ALERTLE_CONFIG: /config/config.yaml ports: - "8888:8888" volumes: - - ./config.yaml:/config/config.yaml + - ./alertle-v2/config.yaml:/config/config.yaml - alertle_data:/app/data - environment: - - ALERTLE_CONFIG=/config/config.yaml - restart: unless-stopped volumes: alertle_data: diff --git a/notifiers/base.py b/notifiers/base.py index 811ad3f..b2ec1a2 100644 --- a/notifiers/base.py +++ b/notifiers/base.py @@ -20,24 +20,25 @@ _GAME_THUMBS_DEFAULT = "https://game-thumbs.swvn.io" -DEFAULT_TEMPLATE = """{time} -📺 {channels} -📍 {venue} -{context} -{odds} -{score}""" - -DEFAULT_GAME_SUMMARY_TEMPLATE = """🏆 {score} +DEFAULT_TEMPLATE = """{description} {time} -📺 {channels} -{description}""" +{venue} +{channels}""" -DEFAULT_DIGEST_GAME_TEMPLATE = """{context} +DEFAULT_GAME_SUMMARY_TEMPLATE = """{time} +{score}""" + +DEFAULT_DIGEST_GAME_TEMPLATE = """{away} at {home} +Game Start: {time} -📺 {channels}""" +{context} +Times & Channels +{channels}""" -DEFAULT_DIGEST_EVENT_TEMPLATE = """{context} +DEFAULT_DIGEST_EVENT_TEMPLATE = """{home} +Event Start: {time} +Times & Channels: {channels}""" diff --git a/templates/settings.html b/templates/settings.html index 826e537..37f04aa 100644 --- a/templates/settings.html +++ b/templates/settings.html @@ -37,45 +37,48 @@

Notification Template

+ placeholder="{description} {time} {venue} {channels}">{{ cfg.get('notification_defaults', {}).get('template', '') }} Available variables: {time} {channels} {broadcast} + {venue} {context} {odds} {score} + {description} {home} {away} {home_abbrev} {away_abbrev} + {league} {sport} {venue} {context} {odds} {odds_spread} {odds_ou} {odds_home_ml} {odds_away_ml} {score} {home} {away} {home_abbrev} {away_abbrev} {league} {sport} - Leave empty to use default: time, channels, venue, context, odds, score + Leave empty to use default: description, time, venue, channels
+ placeholder="{time} {score}">{{ cfg.get('notification_defaults', {}).get('game_summary_template', '') }} - Used for Final Score notifications. Same variables as above, plus {description} (EPG program description). - Leave empty to use default: score, time, channels, description. + Used for Final Score notifications. Same variables as above, plus {score} for the final result. + Leave empty to use default: time, score.
- + placeholder="{away} at {home} Game Start: {time} {context} Times & Channels {channels}">{{ cfg.get('notification_defaults', {}).get('digest_game_template', '') }} Formats each game in the digest for team sports (NHL, NBA, MLB, etc.). - Leave empty to use default: context, time, 📺 channels. + Leave empty to use default: matchup, game start time, context, channels.
- + placeholder="{home} Event Start: {time} Times & Channels: {channels}">{{ cfg.get('notification_defaults', {}).get('digest_event_template', '') }} Formats each event in the digest for event series (Golf, F1, Tennis, etc.). The {channels} variable includes the full broadcast schedule with time slots. - Leave empty to use default: context, time, channels. + Leave empty to use default: event name, start time, channels.