Skip to main content
This guide walks through creating a new slash command in Annie Mei, following the project’s conventions and patterns.

Command Structure

All commands follow a consistent pattern with two main functions:
  • register() - Defines the slash command structure for Discord
  • run() - Executes the command logic when invoked
Commands can be simple single-file modules or complex multi-file modules with subcommands.

Creating a Simple Command

Let’s create a /ping style command as an example.
1

Create the Command Module

Create a new file in src/commands/:
For complex commands, create a directory:
2

Implement register() Function

Define the slash command structure that Discord will display to users:
src/commands/example.rs
This creates a basic command with no options.
3

Implement run() Function

Add the command execution logic:
src/commands/example.rs
4

Export in mod.rs

Add your new command to src/commands/mod.rs:
src/commands/mod.rs
5

Register in main.rs ready Event

Add your command to the registration list in src/main.rs at the ready event handler:
src/main.rs
See src/main.rs for the full implementation.
6

Add Match Arm in interaction_create

Route the command to your handler in src/main.rs at the interaction_create event:
src/main.rs
See src/main.rs for the full implementation.

Adding Command Options

Commands can accept user input through options:

Accessing Option Values

Extract option values in your run() function:
See src/commands/anime/command.rs:66-80 for a real-world example.

Deferring Long Operations

Discord requires responses within 3 seconds. For long-running operations, defer the response immediately:
Always defer before operations that take longer than 3 seconds, including external API calls and database queries.

Calling external services

External API clients use async reqwest::Client. Await those requests directly after deferring the Discord interaction:
Use spawn_blocking only for synchronous work that can block the Tokio runtime, such as the current Redis cache helper calls. See src/utils/requests/anilist.rs for the AniList HTTP client and src/commands/recommend/command.rs for a bounded spawn_blocking cache example.

Adding Instrumentation

Use the #[instrument] attribute for tracing and observability:
This creates a tracing span that appears in logs and Sentry:
  • name - Span name for filtering
  • skip - Fields to exclude from tracing (avoid logging sensitive data)

Code Style Conventions

Follow these conventions when writing commands:

Logging

Use tracing macros: info!, debug!, error!Never use println! or eprintln!

Error Handling

Prefer ? operator over .unwrap()Handle errors gracefully with user-friendly messages

Privacy

Hash user IDs before logging:

Sentry

Configure Sentry scope for commands:

Testing Your Command

Unit Tests

Add tests in the same file:
See src/commands/ping.rs:50-78 for a complete test example.

Running Tests

Run tests for a specific module:

Manual Testing

  1. Run the bot locally:
  2. Use the command in Discord:
  3. Check logs for tracing output:

Example: Complete Command

Here’s a complete command implementation from src/commands/ping.rs:

File Organization Patterns

Depending on complexity, organize your command:

Simple Command (Single File)

Use for commands with minimal logic and no external queries.

Complex Command (Module)

Use for commands with:
  • GraphQL queries
  • Multiple helper functions
  • Complex business logic
See src/commands/anime/ for a complete example.

Next Steps

Architecture

Understand the project structure

Database

Add database models for persistent data

Testing

Write comprehensive tests