Message Handler Example¶
This example shows how to create custom message handlers for more complex bot interactions.
Overview¶
Message handlers in rustycord allow you to create modular, reusable components that process Discord messages. Each handler implements the MessageHandler
trait and can be registered with your bot.
Basic Message Handler¶
use rustycord::{Bot, MessageHandler, logger};
use rustycord::models::Message;
use async_trait::async_trait;
use std::collections::HashMap;
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
logger::setup_logger("info".to_string())?;
let token = std::env::var("DISCORD_TOKEN")?;
let mut bot = Bot::new(None).await;
bot.login(token).await;
// Register multiple handlers
bot.register_message_handler(Box::new(MathHandler));
bot.register_message_handler(Box::new(InfoHandler));
bot.register_message_handler(Box::new(ModeratorHandler));
bot.start().await?;
Ok(())
}
Math Handler¶
A handler that performs basic math operations:
struct MathHandler;
#[async_trait]
impl MessageHandler for MathHandler {
async fn handle_message(&self, message: &Message) -> Result<Option<String>, Box<dyn std::error::Error + Send + Sync>> {
if message.content.starts_with("!math ") {
let expression = message.content.strip_prefix("!math ").unwrap_or("");
match evaluate_expression(expression) {
Ok(result) => Ok(Some(format!("๐งฎ Result: {}", result))),
Err(e) => Ok(Some(format!("โ Error: {}", e)))
}
} else {
Ok(None)
}
}
}
fn evaluate_expression(expr: &str) -> Result<f64, String> {
// Simple expression evaluator (you might want to use a proper parser)
let parts: Vec<&str> = expr.split_whitespace().collect();
if parts.len() != 3 {
return Err("Usage: !math <number> <operator> <number>".to_string());
}
let a: f64 = parts[0].parse().map_err(|_| "Invalid first number")?;
let op = parts[1];
let b: f64 = parts[2].parse().map_err(|_| "Invalid second number")?;
match op {
"+" => Ok(a + b),
"-" => Ok(a - b),
"*" => Ok(a * b),
"/" => {
if b == 0.0 {
Err("Division by zero".to_string())
} else {
Ok(a / b)
}
},
"%" => Ok(a % b),
"^" => Ok(a.powf(b)),
_ => Err("Supported operators: +, -, *, /, %, ^".to_string())
}
}
Info Handler¶
A handler that provides server and user information:
struct InfoHandler;
#[async_trait]
impl MessageHandler for InfoHandler {
async fn handle_message(&self, message: &Message) -> Result<Option<String>, Box<dyn std::error::Error + Send + Sync>> {
match message.content.as_str() {
"!info server" => {
Ok(Some(format!(
"๐ **Server Information**\n\
Guild ID: {}\n\
Channel ID: {}",
message.guild_id.unwrap_or_default(),
message.channel_id
)))
},
"!info user" => {
Ok(Some(format!(
"๐ค **User Information**\n\
Username: {}\n\
User ID: {}\n\
Mention: <@{}>",
message.author.username,
message.author.id,
message.author.id
)))
},
"!help" => {
Ok(Some(
"๐ **Available Commands**\n\
`!info server` - Server information\n\
`!info user` - Your user information\n\
`!math <a> <op> <b>` - Basic math operations\n\
`!help` - Show this help message".to_string()
))
},
_ => Ok(None)
}
}
}
Stateful Handler¶
A handler that maintains state between messages:
use std::sync::{Arc, Mutex};
struct CounterHandler {
counters: Arc<Mutex<HashMap<String, u32>>>,
}
impl CounterHandler {
fn new() -> Self {
Self {
counters: Arc::new(Mutex::new(HashMap::new())),
}
}
}
#[async_trait]
impl MessageHandler for CounterHandler {
async fn handle_message(&self, message: &Message) -> Result<Option<String>, Box<dyn std::error::Error + Send + Sync>> {
if message.content.starts_with("!count") {
let mut counters = self.counters.lock().unwrap();
let user_id = message.author.id.to_string();
let count = counters.entry(user_id.clone()).or_insert(0);
*count += 1;
Ok(Some(format!("๐ {}, your message count: {}", message.author.username, count)))
} else {
Ok(None)
}
}
}
Moderator Handler¶
A handler with permission checks:
struct ModeratorHandler;
#[async_trait]
impl MessageHandler for ModeratorHandler {
async fn handle_message(&self, message: &Message) -> Result<Option<String>, Box<dyn std::error::Error + Send + Sync>> {
if message.content.starts_with("!mod ") {
// Note: In a real implementation, you'd check actual Discord permissions
if !self.is_moderator(&message.author) {
return Ok(Some("โ You don't have permission to use moderator commands.".to_string()));
}
let command = message.content.strip_prefix("!mod ").unwrap_or("");
match command {
"clean" => {
Ok(Some("๐งน Cleaned up recent messages.".to_string()))
},
"warn" => {
Ok(Some("โ ๏ธ Warning issued.".to_string()))
},
_ => {
Ok(Some("โ Unknown moderator command. Available: clean, warn".to_string()))
}
}
} else {
Ok(None)
}
}
}
impl ModeratorHandler {
fn is_moderator(&self, user: &rustycord::models::User) -> bool {
// Simplified check - in reality, you'd check Discord roles/permissions
// For demo purposes, we'll check if the username contains "mod"
user.username.to_lowercase().contains("mod")
}
}
Handler Registration¶
// Register handlers in your main function
async fn setup_bot() -> Result<Bot, Box<dyn std::error::Error>> {
let mut bot = Bot::new(None).await;
// Basic handlers
bot.register_message_handler(Box::new(MathHandler));
bot.register_message_handler(Box::new(InfoHandler));
bot.register_message_handler(Box::new(ModeratorHandler));
// Stateful handler
bot.register_message_handler(Box::new(CounterHandler::new()));
Ok(bot)
}
Best Practices¶
- Keep handlers focused: Each handler should have a single responsibility
- Use async operations: Make database calls or HTTP requests async
- Handle errors gracefully: Return meaningful error messages to users
- Validate input: Always validate user input before processing
- Use proper permissions: Check user permissions for sensitive commands
- State management: Use
Arc<Mutex<T>>
for shared state between handler instances
Advanced Examples¶
- Embeds Example - Rich embed responses
- Database Integration - Persistent data storage
- API Integration - Calling external APIs
Testing¶
#[cfg(test)]
mod tests {
use super::*;
use rustycord::models::{User, Message};
#[tokio::test]
async fn test_math_handler() {
let handler = MathHandler;
let message = Message {
content: "!math 5 + 3".to_string(),
author: User {
id: "123".to_string(),
username: "testuser".to_string(),
},
// ... other fields
};
let result = handler.handle_message(&message).await.unwrap();
assert_eq!(result, Some("๐งฎ Result: 8".to_string()));
}
}