Skip to content

Prefix Commands

rustycord provides a powerful prefix-based command system that allows you to easily create and manage bot commands with specific prefixes (like !, ?, or custom prefixes).

Overview

The prefix command system consists of:

  • PrefixListener - Manages commands for a specific prefix
  • PrefixCommand trait - Define your own commands
  • Built-in commands (HelpCommand, PingCommand, EchoPrefixCommand)
  • Integration with the existing message handler system

Basic Usage

1. Create a Prefix Listener

use rustycord::prefix::PrefixListener;
use std::sync::Arc;

// Create a listener for "!" prefix
let listener = Arc::new(PrefixListener::new("!"));

// For case-sensitive commands
let case_listener = Arc::new(PrefixListener::new_case_sensitive("!"));

2. Register Built-in Commands

use rustycord::prefix::{HelpCommand, PingCommand, EchoPrefixCommand};

// Register built-in commands
listener.register_command("help", Box::new(HelpCommand::new(listener.clone()))).await;
listener.register_command("ping", Box::new(PingCommand)).await;
listener.register_command("echo", Box::new(EchoPrefixCommand)).await;

3. Create Custom Commands

use async_trait::async_trait;
use rustycord::prefix::PrefixCommand;
use rustycord::message::ChannelMessage;

struct InfoCommand;

#[async_trait]
impl PrefixCommand for InfoCommand {
    async fn execute(&self, message: &ChannelMessage, _args: Vec<&str>) 
        -> Result<Option<String>, Box<dyn std::error::Error + Send + Sync>> 
    {
        let response = format!(
            "**Server Information**\\n\\
             Channel ID: {}\\n\\
             Your ID: {}",
            message.channel_id,
            message.author.id
        );
        Ok(Some(response))
    }

    fn description(&self) -> &str {
        "Show information about the current server and channel"
    }

    fn aliases(&self) -> Vec<&str> {
        vec!["server", "guild"]
    }
}

// Register the custom command
listener.register_command("info", Box::new(InfoCommand)).await;

4. Integrate with Message Handler

use rustycord::handlers::message_handler::MessageHandler;

struct PrefixMessageHandler {
    listener: Arc<PrefixListener>,
}

impl PrefixMessageHandler {
    fn new(listener: Arc<PrefixListener>) -> Self {
        Self { listener }
    }
}

#[async_trait]
impl MessageHandler for PrefixMessageHandler {
    async fn on_message_create(&self, message: &ChannelMessage, client: &Client) 
        -> Result<(), Box<dyn std::error::Error + Send + Sync>> 
    {
        // Skip messages from bots
        if message.author.bot.unwrap_or(false) {
            return Ok(());
        }

        // Try to handle the message with the prefix listener
        if let Some(response) = self.listener.handle_message(message).await? {
            client.send_text_message(&message.channel_id, &response).await?;
        }

        Ok(())
    }
}

5. Register with Event Dispatcher

// Register with the bot's event dispatcher
if let Some(client) = &bot.client {
    let event_dispatcher = client.get_event_dispatcher();
    let message_handlers = event_dispatcher.get_message_handlers();

    message_handlers.add_handler(PrefixMessageHandler::new(listener.clone())).await;
}

Built-in Commands

Help Command

The HelpCommand automatically lists all registered commands and provides detailed help.

// Usage in Discord:
// !help          - Lists all commands
// !help <command> - Shows help for specific command

Ping Command

A simple responsiveness test command.

// Usage: !ping
// Response: "Pong! 🏓"

Echo Command

Echoes back the provided text.

// Usage: !echo Hello World
// Response: "Hello World"

Advanced Examples

Math Calculator Command

struct MathCommand;

#[async_trait]
impl PrefixCommand for MathCommand {
    async fn execute(&self, _message: &ChannelMessage, args: Vec<&str>) 
        -> Result<Option<String>, Box<dyn std::error::Error + Send + Sync>> 
    {
        if args.len() < 3 {
            return Ok(Some("Usage: `!math <number> <operator> <number>`\\nExample: `!math 5 + 3`".to_string()));
        }

        let num1: f64 = match args[0].parse() {
            Ok(n) => n,
            Err(_) => return Ok(Some("Invalid first number".to_string())),
        };

        let operator = args[1];

        let num2: f64 = match args[2].parse() {
            Ok(n) => n,
            Err(_) => return Ok(Some("Invalid second number".to_string())),
        };

        let result = match operator {
            "+" => num1 + num2,
            "-" => num1 - num2,
            "*" | "x" => num1 * num2,
            "/" => {
                if num2 == 0.0 {
                    return Ok(Some("Cannot divide by zero!".to_string()));
                }
                num1 / num2
            }
            "%" => num1 % num2,
            "^" | "**" => num1.powf(num2),
            _ => return Ok(Some("Unknown operator. Supported: +, -, *, /, %, ^".to_string())),
        };

        Ok(Some(format!("{} {} {} = {}", num1, operator, num2, result)))
    }

    fn description(&self) -> &str {
        "Perform basic math calculations"
    }

    fn aliases(&self) -> Vec<&str> {
        vec!["calc", "calculate"]
    }
}

User Information Command

struct UserCommand;

#[async_trait]
impl PrefixCommand for UserCommand {
    async fn execute(&self, message: &ChannelMessage, _args: Vec<&str>) 
        -> Result<Option<String>, Box<dyn std::error::Error + Send + Sync>> 
    {
        let user = &message.author;

        let response = format!(
            "**User Information**\\n\\
             Username: {}#{}\\n\\
             ID: {}\\n\\
             Bot: {}\\n\\
             MFA Enabled: {}",
            user.name,
            user.discriminator,
            user.id,
            user.bot.unwrap_or(false),
            user.mfa_enabled
        );

        Ok(Some(response))
    }

    fn description(&self) -> &str {
        "Show information about a user (defaults to yourself)"
    }

    fn aliases(&self) -> Vec<&str> {
        vec!["whois", "profile"]
    }
}

Complete Example

See the prefix commands example for a complete working bot that demonstrates all these features.

API Reference

PrefixListener

  • new(prefix: &str) - Create a new case-insensitive prefix listener
  • new_case_sensitive(prefix: &str) - Create a case-sensitive prefix listener
  • register_command(name: &str, command: Box<dyn PrefixCommand>) - Register a command
  • unregister_command(name: &str) - Remove a command
  • handle_message(message: &ChannelMessage) - Process a message for commands
  • list_commands() - Get all registered command names
  • get_command_help(command_name: &str) - Get help for a specific command
  • prefix() - Get the prefix being used

PrefixCommand Trait

  • execute(message: &ChannelMessage, args: Vec<&str>) - Execute the command
  • description() - Get command description for help
  • aliases() - Get command aliases (optional)

Best Practices

  1. Use descriptive command names - Make commands easy to discover
  2. Provide good help text - Users should understand what commands do
  3. Handle errors gracefully - Return helpful error messages
  4. Use aliases for common commands - Make frequently used commands easy to type
  5. Skip bot messages - Always check message.author.bot to avoid infinite loops
  6. Validate arguments - Check argument count and types before processing
  7. Use case-insensitive commands - Unless you specifically need case sensitivity

Error Handling

Commands should return Result<Option<String>, Box<dyn std::error::Error + Send + Sync>>:

  • Ok(Some(response)) - Command executed successfully with a response
  • Ok(None) - Command executed successfully but no response needed
  • Err(error) - Command failed with an error

The prefix system will log errors and continue processing other messages.

Limitations

  • Commands are processed sequentially, not in parallel
  • Aliases are logged but not currently stored (feature limitation)
  • No built-in cooldown or rate limiting (implement in your command)
  • No built-in permission system (implement in your command)

Migration from Simple Message Handlers

If you're currently using simple message handlers with manual prefix checking:

// Old approach
if message.content.starts_with("!ping") {
    client.send_text_message(&message.channel_id, "Pong!").await?;
}

// New approach with prefix system
struct PingCommand;

#[async_trait]
impl PrefixCommand for PingCommand {
    async fn execute(&self, _message: &ChannelMessage, _args: Vec<&str>) 
        -> Result<Option<String>, Box<dyn std::error::Error + Send + Sync>> 
    {
        Ok(Some("Pong!".to_string()))
    }

    fn description(&self) -> &str {
        "Test if the bot is responding"
    }
}

The prefix system provides better organization, automatic help generation, and easier command management.