Skip to content

Your First Bot

This guide will walk you through creating your first Discord bot with rustycord, from setup to deployment.

Development Library Notice

Remember that rustycord is in heavy development. This tutorial is for learning and experimentation only. Do not use the resulting bot in production environments.

Prerequisites

Make sure you have completed the installation guide before proceeding.

Creating a Simple Echo Bot

Let's start with a basic bot that echoes messages back to users.

Step 1: Set Up Your Project

cargo new my-first-bot
cd my-first-bot

Step 2: Add Dependencies

Edit your Cargo.toml:

[package]
name = "my-first-bot"
version = "0.1.0"
edition = "2021"

[dependencies]
rustycord = "0.1.0"
tokio = { version = "1.40", features = ["full"] }
async-trait = "0.1"
dotenv = "0.15"

Step 3: Create Your Bot

Replace the contents of src/main.rs:

use async_trait::async_trait;
use rustycord::{
    bot::BotBase,
    handlers::message_handler::MessageHandler,
    message::ChannelMessage,
    client::Client,
    logger
};

// Simple echo handler
struct EchoHandler;

#[async_trait]
impl MessageHandler for EchoHandler {
    async fn on_message_create(
        &self, 
        message: &ChannelMessage, 
        client: &Client
    ) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
        // Don't respond to bot messages (avoid infinite loops!)
        if message.author.bot.unwrap_or(false) {
            return Ok(());
        }

        // Only respond to messages that start with "!echo"
        if message.content.starts_with("!echo ") {
            let echo_text = &message.content[6..]; // Remove "!echo " prefix
            client.send_text_message(&message.channel_id, echo_text).await?;
        }

        Ok(())
    }
}

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    // Load environment variables
    dotenv::dotenv().ok();

    // Initialize logging
    logger::setup_logger("info".to_string())?;

    // Get bot token
    let token = std::env::var("DISCORD_TOKEN")
        .expect("DISCORD_TOKEN environment variable not set");

    println!("🚀 Starting your first Discord bot!");

    // Create and login bot
    let mut bot = BotBase::new(None).await;
    let user_info = bot.login(token).await;
    println!("🔑 Logged in as: {}", user_info.username);

    // Register message handler
    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(EchoHandler).await;
        println!("📝 Echo handler registered!");
    }

    println!("🤖 Bot is running! Try typing '!echo Hello World' in Discord");

    // Connect to Discord
    bot.connect(bot.intents, Some(true)).await;

    // Keep the bot running
    loop {
        tokio::time::sleep(tokio::time::Duration::from_secs(60)).await;
    }
}

Step 4: Set Up Environment Variables

Create a .env file:

DISCORD_TOKEN=your_bot_token_here

Remember to replace your_bot_token_here with your actual bot token from the Discord Developer Portal.

Step 5: Run Your Bot

cargo run

You should see output like:

🚀 Starting your first Discord bot!
🔑 Logged in as: YourBot#1234
📝 Echo handler registered!
🤖 Bot is running! Try typing '!echo Hello World' in Discord

Step 6: Test Your Bot

  1. Go to your Discord server where you invited the bot
  2. Type: !echo Hello World
  3. Your bot should respond with: Hello World

Adding More Commands

Let's enhance our bot with multiple commands using the prefix system:

use async_trait::async_trait;
use rustycord::{
    bot::BotBase,
    handlers::message_handler::MessageHandler,
    prefix::{PrefixListener, PrefixCommand, HelpCommand, PingCommand},
    message::ChannelMessage,
    client::Client,
    logger
};
use std::sync::Arc;

// Custom greeting command
struct GreetCommand;

#[async_trait]
impl PrefixCommand for GreetCommand {
    async fn execute(&self, message: &ChannelMessage, args: Vec<&str>) 
        -> Result<Option<String>, Box<dyn std::error::Error + Send + Sync>> 
    {
        let name = if args.is_empty() {
            &message.author.name
        } else {
            args[0]
        };

        Ok(Some(format!("Hello, {}! 👋 Welcome to the server!", name)))
    }

    fn description(&self) -> &str {
        "Greet someone (or yourself if no name provided)"
    }
}

// Message handler using prefix system
struct PrefixHandler {
    listener: Arc<PrefixListener>,
}

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

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

        // Handle prefix commands
        if let Some(response) = self.listener.handle_message(message).await? {
            client.send_text_message(&message.channel_id, &response).await?;
        }

        Ok(())
    }
}

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    dotenv::dotenv().ok();
    logger::setup_logger("info".to_string())?;

    let token = std::env::var("DISCORD_TOKEN")
        .expect("DISCORD_TOKEN environment variable not set");

    println!("🚀 Starting enhanced Discord bot!");

    // Create bot
    let mut bot = BotBase::new(None).await;
    let user_info = bot.login(token).await;
    println!("🔑 Logged in as: {}", user_info.username);

    // Set up prefix system
    let listener = Arc::new(PrefixListener::new("!"));

    // Register commands
    listener.register_command("help", Box::new(HelpCommand::new(listener.clone()))).await;
    listener.register_command("ping", Box::new(PingCommand)).await;
    listener.register_command("greet", Box::new(GreetCommand)).await;

    // Register message handler
    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(PrefixHandler::new(listener)).await;
        println!("📝 Prefix handler registered!");
    }

    println!("🤖 Enhanced bot is running! Available commands:");
    println!("  • !help - Show all commands");
    println!("  • !ping - Test bot responsiveness");
    println!("  • !greet [name] - Greet someone");

    // Connect and run
    bot.connect(bot.intents, Some(true)).await;

    loop {
        tokio::time::sleep(tokio::time::Duration::from_secs(60)).await;
    }
}

Now your bot supports: - !help - Lists all available commands - !ping - Responds with "Pong! 🏓" - !greet - Greets you - !greet Alice - Greets Alice

Common Issues and Solutions

Bot Token Invalid

Error: HTTP error: 401 Unauthorized
Solution: Check your bot token in the .env file and ensure it's correct.

Bot Not Responding

Bot connects but doesn't respond to commands
Solutions: 1. Ensure your bot has "Send Messages" permission 2. Check that Message Content Intent is enabled in Discord Developer Portal 3. Verify the bot is invited to the server

Permission Denied

Error: Missing Access
Solution: Re-invite your bot with proper permissions using the OAuth2 URL generator.

Next Steps

Now that you have a working bot:

  1. Add More Commands - Follow the prefix commands guide
  2. Add Rich Embeds - Learn about embed messages
  3. Handle Events - Explore event handling
  4. Deploy Your Bot - Check out deployment options

Complete Example Repository

You can find complete working examples in the examples directory of the rustycord repository.

Getting Help