diff --git a/AGENTS.md b/AGENTS.md index 301fb49..b8ebec0 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -9,7 +9,10 @@ You are working on `TyperBot`, a Discord bot for football prediction leagues. - **Tech:** Python 3.13+, discord.py, aiosqlite, portable container hosting. ## 2. Critical Constraints +- **Product Model:** One hosted TyperBot application serves many Discord guilds. Server admins invite/configure the bot; they do not self-host it. +- **League Scope:** Keep v3 simple: one league, one active season, one configured league channel, and one active scoring-rule set per guild. - **Persistence:** The database defaults to `./data/typer.db` locally. On production, set `DATA_DIR=/app/data` so the live DB stays on the mounted data volume. +- **Schema Changes:** Do not add broad startup compatibility migrations or backfills for historical schemas. Existing production DB changes should be verified and ported manually when needed; startup should validate/fail fast for unsafe current-schema violations. - **Transaction Safety:** Critical operations use atomic transactions (BEGIN/COMMIT/ROLLBACK) to ensure data consistency. Never modify transaction logic without understanding rollback implications. - **Prediction Contract:** Fixture threads are the public source of truth. `/predict` is a structured composer that posts publicly into the selected fixture thread. - **Configuration:** All data paths configurable via env vars in `utils/config.py`: @@ -24,6 +27,7 @@ You are working on `TyperBot`, a Discord bot for football prediction leagues. - **Logging:** Use `typer_bot.utils.logger.setup_logging()` early. Do not use `print()`. - **Timezones:** All datetime operations use timezone-aware objects. Use `utils.timezone.now()` instead of `datetime.now()`. Configure via `TZ` env var (default: `UTC`). - **Permissions:** Bot requires `Send Messages`, `Send Messages in Threads`, `Read Message History`, `Add Reactions`, `Create Public Threads`, and `Use Slash Commands`. +- **Discord Application:** Production application needs `Message Content Intent` and `Server Members Intent` enabled in the Discord Developer Portal. ## 3. Database Schema SQLite. Tables are initialized in `typer_bot/database/connection.py`. @@ -121,7 +125,7 @@ guild_config ( - **Admin Panel UI:** Edit `commands/admin_panel/`. - **Workflow/Cooldown State:** Thread prediction cooldowns live in `handlers/thread_prediction_handler.py`; admin calculate cooldowns live in `commands/admin_commands.py`. Keep them process-local instead of introducing module-level globals. - **New Commands:** Add Cog to `commands/` folder, load in `bot.py`. -- **Database Changes:** Edit `typer_bot/database/connection.py` `initialize()` and the focused repositories in `typer_bot/database/` (handle migrations manually if needed). +- **Database Changes:** Edit `typer_bot/database/connection.py` `initialize()` and the focused repositories in `typer_bot/database/`. Keep fresh schema creation and startup validation explicit; handle production data ports manually when needed. - **Debugging:** Check `utils/logger.py` for config. Set `LOG_LEVEL=DEBUG` in env. - **Database Restore:** Use `scripts/restore_db.py` from the host or container shell for manual database restoration from backups. The script restores into a temporary SQLite file first, then atomically replaces the live DB only after success. @@ -203,4 +207,6 @@ prek run ruff # Run specific hook - **Runtime:** The bot still connects to Discord in non-production environments; use a separate token for manual testing and previews - **Token Safety:** Any deployment with a valid token will connect; never run multiple environments against the same live token - **Production:** Set `ENVIRONMENT=production` in deployment variables for production deployments +- **Production Data:** Set `DATA_DIR=/app/data` on the persistent volume. `DB_PATH` defaults to `{DATA_DIR}/typer.db`; `BACKUP_DIR` defaults to `{DATA_DIR}/backups`. +- **Backups:** Automatic SQL backups are named `backup_YYYY-MM-DD_HH-MM-SS-ms.sql`. `scripts/restore_db.py` accepts one explicit backup file path, not a wildcard. - **Portability:** Works on any platform (Coolify, Railway, local, etc.) - just set the variable accordingly diff --git a/README.md b/README.md index 6169c5e..981e338 100644 --- a/README.md +++ b/README.md @@ -1,39 +1,21 @@ # TyperBot -Discord bot for weekly football prediction leagues. Admins create fixtures and enter results. Players submit score predictions in fixture threads or through `/predict`, which posts publicly into those threads. The bot stores picks, calculates points, and posts standings. - -## Features -- Thread predictions -- `/predict` -- Flexible score parsing -- Deadlines and late-pick handling -- Standings and results -- SQLite backups - -## Commands -### Player commands -- `/predict` - open a modal and post predictions publicly into the fixture thread -- `/fixtures` - show open fixtures and deadlines -- `/mypredictions` - show your saved predictions for open fixtures -- `/standings` - show the leaderboard and latest scored fixture - -### Admin commands -- `/admin panel` - open the main admin surface, or start first-time setup when the server is not configured yet - -The panel handles: -- create fixtures -- delete fixtures -- jump to older open weeks not shown in the quick list -- enter or correct results -- calculate scores -- re-post the latest completed results with optional mentions -- replace predictions -- review late partial predictions -- toggle late waivers - -After setup, admins need the configured TyperBot admin role. Setup from `/admin panel` requires Discord `Administrator` or `Manage Server` permission. - -## Permissions +Discord bot for football prediction leagues. One hosted TyperBot application can be invited into multiple Discord servers; each server gets its own isolated league, active season, fixtures, predictions, scoring rules, and standings. + +Server admins invite and configure the bot. They do not self-host it. + +## What It Does + +- Admins create fixture threads, enter results, calculate scores, and manage seasons. +- Players predict in fixture threads or with `/predict`, which posts publicly into the selected thread. +- Standings use the server's active season only. +- Scoring rules are stored per season and lock once scores exist. +- SQLite stores league data; backups run after successful score calculation. + +## Server Admin Setup + +Ask the repo owner for the invite link. The bot needs: + - `Send Messages` - `Send Messages in Threads` - `Read Message History` @@ -41,22 +23,21 @@ After setup, admins need the configured TyperBot admin role. Setup from `/admin - `Create Public Threads` - `Use Slash Commands` -## Privileged Intents -- Enable `Message Content Intent` in the Discord Developer Portal -- Enable `Server Members Intent` in the Discord Developer Portal +After inviting TyperBot, run `/admin panel`. First-time setup requires Discord `Administrator` or `Manage Server` permission and stores: + +- admin role: who can use league admin actions +- league channel: where fixture announcements, threads, and reminders go + +After setup, members with the configured admin role use `/admin panel` to create fixtures, enter results, calculate scores, review late partial predictions, edit scoring rules, and start new seasons. -## Prediction flow -- Reply in the fixture thread with one line per match. -- Or run `/predict` anywhere in the server, choose the week if needed, fill the modal, and let the bot post it publicly in the fixture thread. -- To replace a saved prediction, use `/predict` again; the bot posts an updated public message in the fixture thread. -- Partial predictions are allowed. Each partial line must name the game it applies to. -- Missing games count as no prediction. -- Late predictions with missing games stay under admin review until an admin approves or rejects them. -- Approved late submissions count the submitted lines normally, and missing games still count as no prediction. -- Rejected late submissions are discarded. -- Public review status stays visible in the fixture thread. +## Player Commands -Example: +- `/predict` - submit or replace predictions in a fixture thread +- `/fixtures` - show open fixtures +- `/mypredictions` - show your open-fixture predictions +- `/standings` - show active-season standings and latest scored fixture + +Thread predictions use one line per match: ```text Team A - Team B 2:1 @@ -64,132 +45,45 @@ Team C - Team D 0:0 Team E - Team F 3:2 ``` -## Scoring -- Scoring rules are stored per season. The defaults are: -- Exact score: 3 points -- Correct outcome: 1 point -- Wrong outcome: 0 points -- Late full predictions: 0 points unless an admin waives the penalty -- Late predictions with missing games: excluded from scoring until reviewed by an admin -- Rule values must be whole numbers greater than or equal to zero. -- Changing rules is blocked after scores exist for that season, so stored scores do not silently go stale. -- Starting a new season resets its scoring rules to the defaults. - -## Operational constraints -- Match data, predictions, results, and scores are stored in SQLite. -- Short-lived cooldowns are kept in memory, including the thread-post rate limiter and the score-calculation cooldown. -- The bot is intentionally single-process. If the process restarts, in-memory cooldowns reset. +Partial predictions are allowed if each line names the game. Missing games count as no prediction. Late partial predictions wait for admin approval. -## Configuration +## Seasons And Scoring -### Required -- `DISCORD_TOKEN` - Discord bot token +Each server has one active season. Starting a new season archives the old active season and resets scoring rules to defaults: -### Optional -- `ENVIRONMENT` - environment label; use `production` for production deploys, default is `development` -- `DATA_DIR` - base data directory; default `./data` locally, set `/app/data` on production deployments -- `DB_PATH` - database path; default `{DATA_DIR}/typer.db` -- `BACKUP_DIR` - backup directory; default `{DATA_DIR}/backups` -- `TZ` - timezone for admin deadline input; default `UTC` -- `LOG_LEVEL` - logging level; default `INFO` +- exact score: 3 +- correct outcome: 1 +- wrong outcome: 0 +- late full prediction: 0 unless waived -Run `/admin panel` in each server to store or update the admin role and league channel. Fixture announcements and deadline reminders both use that league channel. +Admins can edit scoring rules before scores exist in the active season. Rule values must be whole numbers greater than or equal to zero. -## Deployment - -This bot runs anywhere you can deploy a persistent container. - -### Coolify - -1. Create a new Coolify worker/background service from this repo. -2. Use the included Dockerfile. -3. Disable HTTP/port health checks if Coolify enables them by default for the service. -4. Mount a persistent volume at `/app/data`. -5. Set variables: - - `DISCORD_TOKEN=` - - `ENVIRONMENT=production` - - `DATA_DIR=/app/data` - - optional: `TZ=Europe/Warsaw` - -Use a separate token for previews and manual testing. Do not run multiple deployments against the same live token. - -### Data - -Routine host migration is a direct copy of the live SQLite file at `DB_PATH` (default: `{DATA_DIR}/typer.db`). The `scripts/restore_db.py` helper is for restoring SQL dump backups during recovery, not for the normal host-to-host move. - -If you override `DB_PATH` or `BACKUP_DIR`, keep them on the persistent volume too. - -## Running locally - -Local runs default to `ENVIRONMENT=development`, `DATA_DIR=./data`, and `TZ=UTC`. +## Local Development ```bash -git clone https://github.com/adrunkhuman/TyperBot -cd TyperBot uv sync --group dev - -export DISCORD_TOKEN="your_token" -export ENVIRONMENT=development +export DISCORD_TOKEN="your_test_bot_token" uv run python -m typer_bot ``` -Windows PowerShell: - -```powershell -$env:DISCORD_TOKEN="your_token" -$env:ENVIRONMENT="development" -uv run python -m typer_bot -``` - -## Manual Discord Testing - -Use a separate bot token in a private test guild. Point it at an isolated data directory, not your normal local or deployed database. +Manual Discord testing can seed an isolated local database: ```powershell $env:DISCORD_TOKEN="your_test_bot_token" -$env:ENVIRONMENT="development" $env:DATA_DIR="./.local/manual-discord-test" uv run python -m typer_bot.dev.seed_test_data --tester-user-id "your_discord_user_id" --guild-id "your_discord_server_id" uv run python -m typer_bot ``` -Enable Discord Developer Mode, right-click your test server, and copy the server ID for `--guild-id`. - -The seed command resets that local test database and creates: -- one scored past fixture for standings/history -- one open fixture with saved predictions -- one late open fixture with a late prediction - -Outside `./.local/manual-discord-test`, add `--force-reset`. - -`--force-reset` deletes the target DB, its `-wal` and `-shm` files, and the configured backup directory before reseeding. - -In a deployment shell, use the same command against that deployment's `DB_PATH` and `BACKUP_DIR`. -Those paths usually are not `./.local/manual-discord-test`, so add `--force-reset`. - -Create a real fixture when you need to test posting, thread creation, reactions, or modal-to-thread prediction posting. - -## Development +Run checks: ```bash -uv sync --group dev uv run pytest uv run ruff check . uv run ruff format --check . uv run ty check typer_bot ``` -## Backup and Restore - -- Automatic: the database is backed up after each successful score calculation. The bot keeps the latest 10 backups in `BACKUP_DIR`. -- Manual restore: run from the host or container shell where the live data volume is mounted. - -```bash -ls /app/data/backups/ -python scripts/restore_db.py /app/data/backups/backup_*.sql -``` - -The restore script asks for confirmation, restores into a temporary SQLite file first, and only replaces the live database after success. - ## License + MIT.