Skip to content
Merged
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
8 changes: 7 additions & 1 deletion AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -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`:
Expand All @@ -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`.
Expand Down Expand Up @@ -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.

Expand Down Expand Up @@ -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
190 changes: 42 additions & 148 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,195 +1,89 @@
# 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`
- `Add Reactions`
- `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
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=<your 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.
Loading