Annie Mei is a Rust Discord bot that fetches anime and manga data from AniList and theme songs from MyAnimeList and Spotify. Users interact via Discord slash commands.
This page focuses on the bot service codebase. For the self-hosted service layout, including the separate AniList OAuth companion service, see Self-hosting architecture.
Technology Stack
Annie Mei uses modern Rust ecosystem tools for building a reliable and performant Discord bot:
| Component | Technology | Purpose |
|---|
| Language | Rust 2024 Edition | Core programming language |
| Discord | Serenity 0.12 | Discord API client and gateway |
| Database | PostgreSQL + SQLx | User data persistence and queries |
| Cache | Redis | Fast API response caching |
| HTTP | Reqwest async client | External API requests (AniList, MAL, LLM, PostHog) |
| Async | Tokio | Asynchronous runtime for event handling |
| LLM | Google Gemini | Natural-language search |
| Analytics | PostHog | Analytics and usage tracking |
| Logging | tracing + tracing-subscriber | Structured logging and observability |
| Errors | Sentry | Error tracking and monitoring |
Project Layout
The codebase follows a modular structure organized by functionality:
src/
├── commands/ # Slash command implementations
├── models/ # Data types, DB models, API responses
├── utils/ # Shared utilities, API clients, DB helpers
└── main.rs # Bot entry point and event routing
migrations/ # Bot-owned SQLx migrations, currently settings tables
Commands Directory
Each command is organized as a module in src/commands/. Example structure:
src/commands/
├── anime/
│ ├── mod.rs
│ ├── command.rs # Command registration and execution
│ └── queries.rs # GraphQL queries for AniList API
├── character/
│ ├── mod.rs
│ ├── command.rs
│ └── queries.rs
├── manga/
│ ├── mod.rs
│ ├── command.rs
│ └── queries.rs
├── recommend/
│ ├── mod.rs
│ ├── command.rs
│ └── queries.rs
├── search/
│ ├── mod.rs
│ ├── command.rs
│ └── prompts.rs
├── settings/
│ ├── mod.rs
│ └── command.rs # Interactive settings panel and component handling
├── songs/
│ ├── command.rs
│ ├── fetcher.rs
│ └── mod.rs
├── register/
│ └── command.rs # OAuth link command and response handling
├── help.rs
├── input_validation.rs
├── ping.rs # Simple single-file command
├── response.rs # CommandResponse enum
├── traits.rs # Shared data source traits
├── unregister.rs
├── whoami.rs # Shows linked AniList account for the caller
└── mod.rs # Module exports
Models Directory
Data structures for API responses and database models:
src/models/
├── db/
│ ├── oauth_credential.rs # SQLx model for auth-service oauth_credentials
│ ├── settings.rs # SQLx persistence for user and guild settings
│ └── mod.rs
├── anilist_anime.rs # AniList anime API response types
├── anilist_character.rs # AniList character API response types
├── anilist_common.rs # Shared AniList types
├── anilist_manga.rs # AniList manga API response types
├── anilist_recommendation.rs # AniList recommendation API response types
├── character_id_response.rs # Character ID-only lookup response
├── character_response.rs # Character full-lookup response
├── fetcher.rs # External API client helpers
├── id_response.rs # AniList ID-only response types
├── mal_response.rs # MyAnimeList API response types
├── media_response.rs # Generic media search response
├── media_type.rs # Anime / Manga type enum
├── transformers.rs # Convert API responses to Discord embeds
├── user_media_list.rs # Guild member media list data
└── mod.rs
Utils Directory
Shared functionality and external integrations:
src/utils/
├── requests/
│ ├── anilist.rs # AniList API client
│ ├── my_anime_list.rs # MAL API client
│ └── mod.rs
├── channel.rs # NSFW channel detection
├── database.rs # SQLx connection pool and helpers
├── fetch_by_arguments.rs # Shared fetch-by-ID and fetch-by-name logic
├── formatter.rs # Text formatting utilities
├── fuzzy.rs # Fuzzy string matching
├── guild.rs # Guild member media list queries
├── llm/ # OpenAI-compatible LLM client
│ ├── mod.rs
│ └── tests.rs
├── oauth/
│ └── mod.rs # OAuth context configuration and URL building
├── posthog.rs # PostHog LLM analytics telemetry
├── privacy.rs # User ID hashing for Sentry
├── redis.rs # Redis caching layer
├── settings.rs # Runtime settings resolution helpers
├── spotify.rs # Spotify API integration
├── statics.rs # Constants and environment variables
├── tls.rs # TLS provider setup
└── mod.rs
Database access
Annie Mei uses SQLx for PostgreSQL queries. The bot shares a database with the auth service, reads from the auth-service-owned oauth_credentials table, and owns settings tables under the annie_mei schema.
The bot previously used Diesel ORM. The current bot uses SQLx migrations at startup for annie_mei.user_settings and annie_mei.guild_settings. OAuth schema migrations still live in the companion auth service repo and run during auth-service startup.
Entry Points
The bot’s execution flow starts in src/main.rs:
-
Initialization (
main.rs)
- Parse CLI arguments (including a
hash subcommand)
- Install Rustls crypto provider
- Initialize Sentry error tracking
- Set up tracing/logging
- Create SQLx connection pool
- Run bot-owned SQLx migrations
- Create Discord client with data (pool, LLM client, OAuth config)
-
Event Handler (
main.rs)
interaction_create - Routes slash commands to handlers
ready - Registers slash commands with Discord
-
Command Routing (
main.rs)
- Matches command names to handler functions
- Configures tracing spans for observability
- Commands:
ping, help, songs, manga, anime, search, recommend, character, register, unregister, whoami, settings
Environment Configuration
The bot requires these environment variables to run:
| Variable | Description |
|---|
AUTH_SERVICE_BASE_URL | Auth service origin for OAuth start URLs |
DATABASE_URL | PostgreSQL connection string |
DISCORD_TOKEN | Bot token from Discord Developer Portal |
ENV | Environment name (development, staging, production) |
GEMINI_API_KEY | Optional: enables LLM features |
LLM_BASE_URL | Optional: overrides the default LLM base URL |
LLM_MODEL | Optional: overrides the default LLM model (default gemini-3.1-flash-lite) |
MAL_CLIENT_ID | MyAnimeList API client ID |
OAUTH_CONTEXT_SIGNING_SECRET | Shared secret for OAuth context signing |
OAUTH_CONTEXT_TTL_SECONDS | Optional: OAuth context expiry in seconds (default 300) |
POSTHOG_CAPTURE_LLM_CONTENT | Optional: capture LLM prompts and outputs in PostHog (default true) |
POSTHOG_HOST | Optional: PostHog ingestion host (default https://us.i.posthog.com) |
POSTHOG_PROJECT_API_KEY | Optional: enables PostHog analytics events |
REDIS_URL | Redis connection URL |
RUST_LOG | Optional: tracing/log level filter |
SENTRY_DSN | Sentry project DSN for error tracking |
SENTRY_TRACES_SAMPLE_RATE | Optional: Sentry trace sampling (0.0–1.0) |
SPOTIFY_CLIENT_ID | Spotify API client ID |
SPOTIFY_CLIENT_SECRET | Spotify API client secret |
USERID_HASH_SALT | Secret salt for hashing user IDs in logs |
See src/utils/statics.rs for constant definitions.