<div align="center">
  <img src="docs/siren.png" alt="Siren" width="200"/>
  <h1 align="center">Siren</h1>
  <p>A D&D-focused Discord bot written in Rust</p>
</div>

---

## Features

- Music playback from YouTube via slash commands
- Dice rolling with D&D notation (e.g. `/roll 2d6+3`)
- Session scheduling
- REST API with OAuth2 authentication

---

## Prerequisites

### Running with Docker (recommended)

| Tool                              | Notes                                       |
|-----------------------------------|---------------------------------------------|
| [Docker](https://www.docker.com/) | Required                                    |
| [Task](https://taskfile.dev/)     | Optional — used to run convenience commands |

### Running locally (development)

| Tool                                              | Notes                                       |
|---------------------------------------------------|---------------------------------------------|
| [Rust](https://www.rust-lang.org/tools/install)   | Stable toolchain                            |
| [yt-dlp](https://github.com/yt-dlp/yt-dlp)        | Audio source extraction                     |
| [ffmpeg](https://github.com/yt-dlp/FFmpeg-Builds) | Audio transcoding                           |
| [Docker](https://www.docker.com/)                 | Used to run PostgreSQL and Valkey           |
| [Task](https://taskfile.dev/)                     | Optional — used to run convenience commands |

> **yt-dlp note:** Keep yt-dlp up to date. YouTube frequently rotates its player,
> and an outdated yt-dlp will fail to resolve stream URLs.

---

## Configuration

Copy `.env.example` to `.env` and fill in the required values:

```bash
cp .env.example .env
# or using Task:
task setup
```

### Environment variables

| Variable                 | Required | Description                                                               |
|--------------------------|----------|---------------------------------------------------------------------------|
| `DISCORD_BOT_TOKEN`      | Yes      | Bot token from the Discord Developer Portal                               |
| `DISCORD_CLIENT_SECRET`  | Yes      | OAuth2 client secret                                                      |
| `JWT_SECRET`             | Yes      | Secret used to sign JWT tokens — change from default                      |
| `POSTGRES_USER`          | Yes      | PostgreSQL username                                                       |
| `POSTGRES_PASSWORD`      | Yes      | PostgreSQL password — change from default                                 |
| `POSTGRES_DB`            | Yes      | PostgreSQL database name                                                  |
| `POSTGRES_HOST`          | Yes      | PostgreSQL host (`localhost` for local dev, `siren-postgres` in Docker)   |
| `POSTGRES_PORT`          |          | PostgreSQL port (default `5432`)                                          |
| `VALKEY_HOST`            |          | Valkey host (`localhost` for local dev, `siren-valkey` in Docker)         |
| `VALKEY_PORT`            |          | Valkey port (default `6379`)                                              |
| `API_BASE_URL`           | Yes      | Base URL of the API (e.g. `http://localhost:3000`)                        |
| `API_PORT`               |          | Port the REST API listens on (default `3000`)                             |
| `API_SESSION_TTL`        |          | OAuth2 session TTL in seconds (default `86400`)                           |
| `CORS_ORIGIN`            |          | Allowed CORS origin (`*` for dev, specific URL for production)            |
| `UI_PORT`                |          | Port the UI dev server listens on (default `5173`)                        |
| `RUST_LOG`               |          | Log filter (e.g. `warn,siren=info`)                                       |
| `FORCE_COMMAND_REGISTER` |          | Re-register slash commands with Discord on every startup (`true`/`false`) |
| `DATA_DIR_PATH`          |          | Path to optional local data directory                                     |
| `DEFAULT_API_KEY`        |          | Seed API key created on startup                                           |
| `DEFAULT_GUILD_ID`       |          | Seed Discord guild (server) ID                                            |
| `DEFAULT_USER_ID`        |          | Seed Discord user ID                                                      |

---

## Discord Application Setup

1. Visit the [Discord Developer Portal](https://discord.com/developers/applications) and create a new application.
2. Go to the **Bot** tab:
   - Click **Reset Token** to generate your bot token — this is your `DISCORD_BOT_TOKEN`.
   - Enable **Message Content Intent** under Privileged Gateway Intents.

   ![Token location](docs/discord_token_example.png)

3. Go to the **OAuth2** tab to find your **Client Secret** (`DISCORD_CLIENT_SECRET`).

### Invite the bot to your server

**Required scopes:** `bot`, `applications.commands`

**Required permissions:**

| Category | Permissions                                                                                                                                                                                                                           |
|----------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| General  | Manage Roles, Change Nickname, View Channels, Manage Events, Create Events                                                                                                                                                            |
| Text     | Send Messages, Create Public/Private Threads, Send Messages in Threads, Manage Messages, Manage Threads, Embed Links, Attach Files, Read Message History, Mention Everyone, Use External Emojis/Stickers, Add Reactions, Create Polls |
| Voice    | Connect, Speak                                                                                                                                                                                                                        |

Use this invite URL (replace `<CLIENT_ID>` with your Application ID from the General Information tab):

```
https://discord.com/oauth2/authorize?client_id=<CLIENT_ID>&permissions=581083641408576&integration_type=0&scope=bot+applications.commands
```

---

## Running

### Docker (recommended)

Build and start all containers:

```bash
# Build the Docker image
task docker:build
# or: docker build -f Dockerfile -t siren:latest .

# Start all services (postgres, valkey, and the app)
task docker:up:all
# or: docker compose --profile app up -d
```

To stop everything:

```bash
task docker:down
# or: docker compose --profile app down
```

### Local development

Start the backing services (PostgreSQL and Valkey) in Docker, then run the bot natively:

```bash
# Start only the infrastructure containers
task docker:up
# or: docker compose up -d

# Run the bot locally with trace-level logging
task run
# or: cargo run
```

---

## Commands

Siren uses Discord slash commands.

### Music

| Command           | Description                         | Status  |
|-------------------|-------------------------------------|---------|
| `/play <track>`   | Play a track from YouTube           | Done    |
| `/pause`          | Pause the current track             | Done    |
| `/resume`         | Resume the current track            | Done    |
| `/skip`           | Skip to the next track              | Done    |
| `/stop`           | Stop playback and clear the queue   | Done    |
| `/mute`           | Mute/unmute the bot                 | Done    |
| `/volume <0–100>` | Set the playback volume             | Done    |
| `/queue`          | Display the current queue           | Planned |
| `/nowplaying`     | Display the currently playing track | Planned |
| `/shuffle`        | Shuffle the queue                   | Planned |
| `/loop`           | Toggle looping the current track    | Planned |
| `/clear`          | Clear the queue                     | Planned |

### Events

| Command     | Description                  | Status  |
|-------------|------------------------------|---------|
| `/schedule` | Schedule a new event         | Planned |
| `/events`   | Display all scheduled events | Planned |

### Fun

| Command                      | Description                           | Status |
|------------------------------|---------------------------------------|--------|
| `/roll <dice>`               | Roll dice (e.g. `2d6+3`)              | Done   |
| `/requestroll <user> <dice>` | Request a dice roll from another user | Done   |

### Utility

| Command | Description                | Status  |
|---------|----------------------------|---------|
| `/ping` | Check the bot's latency    | Done    |
| `/help` | Display available commands | Planned |

---

## Development

| Task                   | Command        |
|------------------------|----------------|
| Type-check (fast)      | `task check`   |
| Debug build            | `task build`   |
| Release build          | `task release` |
| Run with trace logging | `task run`     |
| Format code            | `task format`  |
| Lint (Clippy)          | `task lint`    |
| Clean build artifacts  | `task clean`   |
| Connect to database    | `task psql`    |

Run `task` with no arguments to list all available tasks.
