aboutsummaryrefslogtreecommitdiffstats
path: root/README.md
blob: f314793087d00a63fdea1a17107ae954512971cf (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
# Senator Sperma (Rust)

A Discord bot rebuilt in Rust from the original Python implementation. Music
playback via songbird, AI chat via OpenRouter or anything OpenAI compatible (ollama.cpp and such), TTS, web/file search
scheduled jobs, per-server triggers, QR codes, media conversion, and misc utilities.

Built on poise 0.6 + serenity 0.12 + songbird 0.6, with async SQLite (sqlx 0.9)
for persistence.

## Setup

### 1. Clone

```bash
git clone https://codeberg.org/salmonik/senator-sperma
cd senator-sperma
```

### 2. System Dependencies

The following binaries must be available on `PATH`:

- `ffmpeg` — audio decoding/encoding for playback, conversion, and compression
- `imagemagick` (`magick`) — image compression and conversion for `/compress` and `/convert`
- `qalc` (Qalculate) — used by `/math` to evaluate expressions
- `yt-dlp` (optional) — used by `/download`
- `piper` model files (optional) — local TTS for `/tts` and `/senator ... tts_vc`; TTS commands fail when unset

Unlike the Python version, QR encode/decode is handled by the pure-Rust
`qrcode`/`rxing` crates — no `zbar` system library required.

### 3. Build

Requires a recent Rust toolchain (edition 2024).

```bash
cargo build --release
```

### 4. Configure Environment

Create a `.env` in the project root
look at `.env.example` on what to put there

On launch, missing variables are reported via tracing (`error!` for required,
`warn!` for optional with defaults). The database and schema migrations are
created automatically on first run.

For local TTS, set `TTS_MODEL_PATH` to a Piper `.onnx` model file (or a
directory of models, in which case `TTS_VOICE` selects the model, defaulting to
`pl_PL-bass-high`). There is no external TTS fallback: TTS commands fail when
the model is missing.

### 5. Run

```bash
cargo run --release
```

## Docker

The image builds and runs on Debian trixie, so it does not depend on the
host's gcc/glibc version. This is the recommended way to deploy on Debian
when developing on a rolling-release distro.

```bash
docker compose up -d --build
```

Or without Compose:

```bash
docker buildx build --load -t senator-sperma:latest .
docker run -d --name senator-sperma --restart unless-stopped \
    --env-file .env \
    -e TTS_MODEL_PATH=/app/piper \
    -e PIPER_ESPEAKNG_DATA_DIRECTORY=/usr/share \
    -v "$PWD/data:/app/data" \
    -v "$PWD/piper:/app/piper:ro" \
    senator-sperma
```

Notes:

- `data/` holds the SQLite database and is mounted for persistence. Create it
  first (`mkdir -p data`) so it is owned by your user rather than root.
- `piper/` holds the Piper model and is mounted read-only; the container
  overrides `TTS_MODEL_PATH` to `/app/piper`, so the absolute host path in
  `.env` is harmless. Put the `.onnx` and `.onnx.json` files there (the same
  directory used for local runs).
- The espeak-ng data used by Piper is baked into the image at
  `/usr/share/espeak-ng-data`; the container sets
  `PIPER_ESPEAKNG_DATA_DIRECTORY=/usr/share`. This overrides the host path in
  `.env`.
- The container runs as uid 1000. Make sure `data/` is writable by that uid.
- `TZ` defaults to `Europe/Warsaw` for cron jobs; override it in
  `compose.yaml` or with `-e TZ=...` if needed.
- If Copyparty or a local LLM runs on the host, use the host's LAN IP (for
  example `http://10.0.0.3:2137/...`) and make sure the service listens on
  `0.0.0.0` or that LAN IP, not only `127.0.0.1`. `localhost` inside the
  container is the container itself.
- After editing `.env`, recreate the container:
  `docker compose up -d --force-recreate`. `docker compose restart` does not
  reload `env_file` values. Verify what the container actually sees with:
  `docker compose exec senator-sperma env | grep -E 'LLM_API_URL|TTS_MODEL_PATH|PIPER_ESPEAKNG'`
  and check the resolved LLM URL with `docker compose logs | grep 'LLM client'`.
- To test the LLM endpoint from inside the container:
  `docker compose exec senator-sperma python3 -c 'import urllib.request; print(urllib.request.urlopen("http://10.0.0.3:2137/v1/models", timeout=5).status)'`
  If that fails, the host service is probably listening only on `127.0.0.1`;
  bind it to `0.0.0.0` (or the LAN IP) and allow the port through the firewall.
- If the build fails to resolve `crates.io` or `deb.debian.org`, run
  `docker buildx build --load --network=host -t senator-sperma:latest .`;
  `compose.yaml` already sets `network: host` for builds on this host.
- yt-dlp is pinned to a specific upstream release and verified against the
  release checksum at image build time. Bump `YTDLP_VERSION` in the Dockerfile
  to update it; pick a release that is at least a week old.

## Commands

Slash commands are autoregistered globally on startup. Some are registered
under their original Polish verb names to match the UI expected by existing
servers.

### AI
- `/senator` — chat with the OpenRouter model

### Music (songbird)
- `/music grajcuj` — play from Copyparty search, a direct URL, or an uploaded file; optional `position` (`now`, `next`, `last`, default `last`)
- `/music queue` — show the current track followed by the pending queue
- `/music skip` — stop the current track and play the next queued one
- `/music stop` — stop playback, clear the queue, and leave voice
- `/music join` — join the caller's voice channel

### TTS
- `/tts` — local Piper TTS (Polish), sent as a file or played in voice

### Search & Web
- `/web` — DuckDuckGo HTML scrape (soon to use 4get)
- `/search` — search Copyparty and return a direct download URL
- `/download` — fetch media from any yt-dlp-supported URL

### Media Tools
- `/compress` (alias `kompresja`) — compress a media attachment to low quality
- `/convert` (alias `konwersja`) — convert media between formats

### Utilities
- `/upload` — push Discord attachments into Copyparty
- `/say` — repeat the given text as the bot
- `/math` — evaluate an ASCII math expression through `qalc`
- `/qr generowanie` / `/qr dekodowanie` — QR encode / decode
- `/record` — upload the last recording window (requires `MANAGE_GUILD`)
- `/leaderboard` — `me`, `messages`, `active`, `afk`, `total`

### Triggers
- `/trigger create` — new trigger group
- `/trigger add` — add keywords/responses to a group
- `/trigger remove` — remove a keyword/response
- `/trigger delete` — delete a group
- `/trigger list` — list a group's keywords and responses
- `/trigger help` — usage info

### Cron
- `/cron dodaj_audio` — schedule audio playback
- `/cron dodaj_wiadomość` — schedule a periodic message
- `/cron pokaż` — list all cron jobs
- `/cron usuń` — remove a cron job
- `/cron pauza` — pause a job
- `/cron wznów` — resume a job

### Message Context Menu
- "Wyślij załączniki" (right-click message) — upload attachments to Copyparty
- "kompresja" (right-click message) — compress all attachments
- "Pobierz z linku" (right-click message) — download every URL in the message

## Architecture

The runtime is organised around a shared `Data` struct (poise `user_data`)
that holds the DB pool, HTTP client, Copyparty client, voice manager, the
in-memory trigger cache, autocomplete cache, and per-guild music queue.

- `src/main.rs` — boot: tracing, dotenvy, Config, DB pool, framework, cron task
- `src/config.rs` — env-driven `Config` + variable validation
- `src/commands/` — one module per command group
- `src/copyparty/` — Copyparty search/upload client
- `src/openrouter/` — OpenRouter chat client
- `src/database/` — sqlx pool, migrations, query helpers (6 tables)
- `src/voice/` — `VoiceManager`, ffmpeg/symphonia → songbird input
- `src/net.rs` — blocks private/link-local hosts for user-supplied URLs
- `src/recording/` — per-guild rolling PCM buffer for `/record`
- `src/events/` — `handle_ready`, voice-state, message (trigger matching)
- `src/tasks/` — 60s cron scheduler poll loop
- `src/lang.rs` — user-facing Polish strings

All temporary files go through `crate::config::temp_path()`
(`$TMPDIR/senator_sperma`, created with mode `0700` at startup).