A Hu Tao-themed Discord bot built with Serenity and Poise. Responds to both slash and prefix commands (hu, ht), with persistent XP levelling backed by PostgreSQL and an optional AI persona that stays in character across a full channel conversation window.
The minimal build needs only a Discord token and PostgreSQL; AI, Redis, and telemetry are opt-in Cargo features. Run it with:
cargo run --releaseFor deeper detail, see the documentation: architecture, configuration, deployment, AI features, and observability.
/help— lists every registered command- Embed interaction commands: tieup, pat, hug, kiss, slap, punch, bonk, nom, kill, kick, bury, selfbury, peek, avatar, drive, chair, boom, quote
- XP levelling with a 60-second cooldown, stored in PostgreSQL —
/levelfor a member's level,/toplevelsfor the server leaderboard /reminder— schedule a DM for later (create/list/search/delete), with a saveable default timezone (/reminder timezone, per-server or global) and browsable, paginated history/util— utility commands:/util avatar,/util uptime,/util age, and/util download(whenutil-downloadfeature is enabled)/age— your or another member's account creation date/cookie— give someone a cookie/uptime— bot uptime/custom reaction add url:<url> pattern:<regex> [anywhere:<bool>]— register an image + Rust regex; whenever a guild message matches, the bot replies with the image in an embed.anywhere: false(default) anchors to the full message;anywhere: truematches anywhere. The confirmation shows the reaction's per-guild number (#1,#2, … within the server). Tenor/Giphy page links (tenor.com/view/…,giphy.com/gifs/…) are rejected — paste the direct media URL (media.tenor.com/....gif) instead. Requires Manage Channels. Per-guild cap: 25/custom reaction list— list the server's reactions (ephemeral) with their per-guild numbers, patterns, andanywhereflags (Manage Channels)/custom reaction remove name:<autocomplete>— soft-delete the reaction picked by its per-guild number from autocomplete (Manage Channels, cache-backed). Numbers are positional, so removing one renumbers the rest- Levenshtein-distance typo correction on unrecognised prefix commands
Everything optional is a Cargo feature. ai is a meta-feature — enabling it
also enables redis — and it requires picking exactly one ai-<backend>.
| Feature | What it adds | Notes |
|---|---|---|
| (core) | Commands, XP, reminders, custom reactions | Needs only BOT_TOKEN + PostgreSQL. |
redis |
Cross-instance AI context, locks, rate limits | Standalone. Single instance works without it. |
ai |
The AI persona chat | Meta-feature: also enables redis. Needs a backend (below). |
ai-<backend> |
The AI provider | Exactly one of ai-deepseek, ai-ollama, ai-anthropic, ai-openai, ai-google, ai-groq, ai-openrouter. Mandatory when ai is on. |
opentelemetry |
OTLP trace export | Point at any OTLP collector; compose ships Tempo + Grafana. |
util-download |
/util download — yt-dlp + ffmpeg 2-pass encode targeting 8 MB |
Needs yt-dlp and ffmpeg (with ffprobe) on PATH. |
tokio_console |
Tokio Console runtime inspection | Needs RUSTFLAGS="--cfg tokio_unstable". |
Building with --features ai and no backend stops at a compile_error! by
design. See docs/configuration.md for the
environment variables each feature reads.
An in-character Hu Tao persona powered by the llm crate, which supports every mainstream provider. The backend is chosen at compile time — enable exactly one of: ai-deepseek, ai-ollama, ai-anthropic, ai-openai, ai-google, ai-groq, ai-openrouter.
Set AI_MODEL and AI_API_KEY (hosted backends) in .env — see .env.example for all variables.
/ai— one-off prompt in any channel or DM/aichannel— toggle a channel where the bot auto-replies to every message (requires Manage Channels)/custom prompt add|show|remove— set a per-server instruction block appended to the AI's system prompt, applied to both/aiand auto-replies (requires Manage Channels)- Set
REDIS_URLto keep conversation context in Redis and to share AI locks and rate limits across bot instances; without it the bot re-fetches recent messages from Discord on every reply and coordination is per-instance
Task-level async runtime inspection via Tokio Console:
RUSTFLAGS="--cfg tokio_unstable" cargo run --features tokio_consoleDistributed tracing via OpenTelemetry — backend-agnostic, so you can point it at any OTLP-compatible collector. The compose setup ships with Grafana Tempo and Grafana pre-wired as the UI. See docs/observability.md for the tracing layers and the category span field. To run Tempo manually (create /var/tempo once with your user as owner):
sudo mkdir -p /var/tempo && sudo chown $USER /var/tempo
tempo -config.file=./tempo.yaml- Copy
.env.exampleto.envand fill in the values. - Have PostgreSQL running and reachable at
DATABASE_URL— migrations run automatically at startup. - Run:
cargo run --release
# or, to enable specific features:
cargo run --release --features='<your-features>'To run the telemetry stack (Grafana Tempo + Grafana) in containers while running the bot natively:
docker-compose -f docker-compose.infra.yml up -dThe compose file brings up PostgreSQL, Redis, Grafana Tempo, and Grafana alongside the bot:
docker-compose up -dImportant
Make sure you aren't running PostgreSQL or Grafana Tempo locally due to port conflicts!
Note
The Dockerfile builds with the features listed in its FEATURES
arg (defaults to ai-deepseek opentelemetry tokio_console). Override via the
compose build args to change provider or feature set.
- docs/architecture.md — crate layout, startup, message flow, the two AI paths, and the Redis-optional model.
- docs/configuration.md — full environment-variable reference.
- docs/deployment.md — native, Docker Compose, sharding, and the production topology.
- docs/ai.md — enabling AI and the context window.
- docs/observability.md — tracing, Tokio Console, and OpenTelemetry.
See SECURITY.md for the secrets inventory, and CONTRIBUTORS.md to set up a dev environment.







