# senator-sperma-rust — Agent Guide ## Project Map ``` src/ ├── main.rs — Entry: tracing, dotenvy, temp dir, Config/Data, poise+serenity ├── config.rs — Config struct (env-driven), temp dir init/access ├── error.rs — Unified Error enum + From impls, pub type Result ├── net.rs — public-IP check for user-supplied URLs (SSRF guard) ├── music.rs — MusicState: per-guild queue, now/next/last, skip, auto-advance ├── recording/mod.rs — per-guild rolling PCM recorder for /record ├── tts.rs — TtsState (piper-rs in-process, bounded DashMap cache, resampler, WAV encoder) ├── commands/ │ ├── mod.rs — all_commands() aggregates every submodule │ ├── ai.rs — /senator (LLM chat + optional TTS) │ ├── autocomplete.rs — Copyparty autocomplete + trigger_name │ ├── cron.rs — /cron group (6 subcommands) │ ├── leaderboard.rs — /leaderboard group (me/messages/active/afk/total) │ ├── math.rs — /math (qalc subprocess, 5s timeout) │ ├── music.rs — /music group (grajcuj, skip, queue, stop, join) + context menu │ ├── qr.rs — /qr encode + decode (dimension/allocation limits) │ ├── record.rs — /record (flush this guild's buffer to Copyparty) │ ├── trigger.rs — /trigger group (6 subcommands) │ ├── tts.rs — /tts (local Piper TTS → file/vc) │ ├── utilities.rs — compress, convert, upload, search, download, say │ └── web.rs — /web (DuckDuckGo scrape) ├── copyparty/mod.rs — CopypartyClient (search, upload, credential-safe URLs) ├── database/mod.rs — DbPool, migrations, query helpers (7 tables) ├── events/mod.rs — handle_ready, voice-state, message (triggers + LLM) ├── openrouter/mod.rs — OpenRouterClient (chat, chat_with_history, timeouts) ├── tasks/mod.rs — cron_scheduler_loop (60s polling, real execution) └── voice/mod.rs — VoiceManager (connect, disconnect, get_call), ffmpeg/symphonia inputs ``` ## Key Conventions - **Framework**: poise 0.6 + serenity 0.12 + songbird 0.6 - **DB**: sqlx 0.9 async SQLite (pool stored in `Data.db_pool`) - **Errors**: `crate::error::Error` enum with `?` propagation; use `Error::Custom(string)` for ad-hoc - **Shared state**: `Data` struct in `main.rs` — passed as poise `user_data` - **Commands**: each submodule exports `pub fn commands() -> Vec>` - **Permissions**: admin commands use `required_permissions`; recording/cron/trigger are `guild_only` and scoped by `guild_id` in the DB - **Temp path**: always use `crate::config::temp_path()` (initialized with mode `0700`; never hardcode `/tmp/senator_sperma`) - **Outbound URLs**: user-supplied `http(s)` URLs go through `crate::net::ensure_public_http_url` unless they belong to the configured Copyparty origin - **TTS**: piper-rs in-process (no subprocess). `TtsState` holds `OnceLock>>` + a bounded `DashMap` cache. Synthesize via `data.tts.synthesize(text)`; work runs on `spawn_blocking`. No external TTS fallback. - **Recording**: opt-in (`RECORDING_ENABLED=false` by default), one buffer per guild. ## Build - `build.rs` — compiles C stubs for espeak-rs-sys missing sonic/audio_object symbols ## Database Tables `settings`, `voice_activity`, `user_stats`, `cron_jobs`, `triggers`, `trigger_keywords`, `trigger_responses` ## Commands `/senator`, `/cron` (6), `/leaderboard` (5), `/math`, `/music` (5: grajcuj, skip, queue, stop, join), `/qr` (2), `/trigger` (6), `/tts`, `/record`, `/compress`, `/convert`, `/upload`, `/search`, `/download`, `/say`, `/web`. Context menus: "Odtwórz", "Wyślij załączniki", "kompresja", "Pobierz z linku".