botrs 0.2.9

A Rust QQ Bot framework based on QQ Guild Bot API
Documentation
# Echo Bot Example

This example demonstrates how to create a simple echo bot that responds to messages by repeating them back to the user.

## Overview

An echo bot is the simplest type of bot that demonstrates basic message handling. When a user sends a message, the bot responds with the same message content.

## Basic Echo Bot

```rust
use botrs::{Client, Context, EventHandler, Message, Ready, Intents};

struct EchoBot;

#[async_trait::async_trait]
impl EventHandler for EchoBot {
    async fn ready(&self, _ctx: Context, ready: Ready) {
        println!("Echo bot {} is ready!", ready.user.username);
    }

    async fn message_create(&self, ctx: Context, msg: Message) {
        // Skip bot messages to avoid loops
        if msg.author.as_ref().map_or(false, |a| a.bot.unwrap_or(false)) {
            return;
        }

        // Echo the message content
        if let Some(content) = &msg.content {
            let echo_msg = format!("Echo: {}", content);
            if let Err(e) = ctx.send_message(&msg.channel_id, &echo_msg).await {
                eprintln!("Failed to send echo message: {}", e);
            }
        }
    }
}

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    // Initialize logging
    tracing_subscriber::init();

    let bot = EchoBot;
    
    let mut client = Client::new("your_app_id", bot)
        .intents(Intents::PUBLIC_GUILD_MESSAGES | Intents::DIRECT_MESSAGE | Intents::GUILDS)
        .build()
        .await?;

    client.start().await?;
    Ok(())
}
```

## Enhanced Echo Bot with Commands

```rust
use botrs::{Client, Context, EventHandler, Message, Ready, Intents};

struct SmartEchoBot;

#[async_trait::async_trait]
impl EventHandler for SmartEchoBot {
    async fn ready(&self, _ctx: Context, ready: Ready) {
        println!("Smart echo bot {} is ready!", ready.user.username);
    }

    async fn message_create(&self, ctx: Context, msg: Message) {
        // Skip bot messages
        if msg.author.as_ref().map_or(false, |a| a.bot.unwrap_or(false)) {
            return;
        }

        if let Some(content) = &msg.content {
            match content.as_str() {
                "!ping" => {
                    let _ = ctx.send_message(&msg.channel_id, "Pong!").await;
                }
                "!help" => {
                    let help_text = "Available commands:\n• `!ping` - Test bot responsiveness\n• `!echo <message>` - Echo a custom message\n• Any other message will be echoed back";
                    let _ = ctx.send_message(&msg.channel_id, help_text).await;
                }
                _ if content.starts_with("!echo ") => {
                    let echo_content = &content[6..]; // Remove "!echo " prefix
                    let echo_msg = format!("You said: {}", echo_content);
                    let _ = ctx.send_message(&msg.channel_id, &echo_msg).await;
                }
                _ => {
                    // Echo regular messages
                    let echo_msg = format!("Echo: {}", content);
                    let _ = ctx.send_message(&msg.channel_id, &echo_msg).await;
                }
            }
        }
    }
}

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    tracing_subscriber::init();

    let bot = SmartEchoBot;
    
    let mut client = Client::new("your_app_id", bot)
        .intents(Intents::PUBLIC_GUILD_MESSAGES | Intents::DIRECT_MESSAGE | Intents::GUILDS)
        .build()
        .await?;

    client.start().await?;
    Ok(())
}
```

## Echo Bot with Reply Support

```rust
use botrs::{Client, Context, EventHandler, Message, Ready, Intents, MessageParams};

struct ReplyEchoBot;

#[async_trait::async_trait]
impl EventHandler for ReplyEchoBot {
    async fn ready(&self, _ctx: Context, ready: Ready) {
        println!("Reply echo bot {} is ready!", ready.user.username);
    }

    async fn message_create(&self, ctx: Context, msg: Message) {
        if msg.author.as_ref().map_or(false, |a| a.bot.unwrap_or(false)) {
            return;
        }

        if let Some(content) = &msg.content {
            // Create a reply to the original message
            let echo_content = format!("You said: {}", content);
            
            // Use reply functionality
            if let Err(e) = ctx.reply_message(&msg.channel_id, &msg.id, &echo_content).await {
                eprintln!("Failed to reply to message: {}", e);
            }
        }
    }
}

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    tracing_subscriber::init();

    let bot = ReplyEchoBot;
    
    let mut client = Client::new("your_app_id", bot)
        .intents(Intents::PUBLIC_GUILD_MESSAGES | Intents::DIRECT_MESSAGE | Intents::GUILDS)
        .build()
        .await?;

    client.start().await?;
    Ok(())
}
```

## Echo Bot with Rich Embeds

```rust
use botrs::{Client, Context, EventHandler, Message, Ready, Intents, MessageEmbed};

struct RichEchoBot;

#[async_trait::async_trait]
impl EventHandler for RichEchoBot {
    async fn ready(&self, _ctx: Context, ready: Ready) {
        println!("Rich echo bot {} is ready!", ready.user.username);
    }

    async fn message_create(&self, ctx: Context, msg: Message) {
        if msg.author.as_ref().map_or(false, |a| a.bot.unwrap_or(false)) {
            return;
        }

        if let Some(content) = &msg.content {
            // Create an embed for the echo response
            let embed = MessageEmbed::new()
                .title("Echo Response")
                .description(format!("You said: {}", content))
                .color(0x00ff00) // Green color
                .field("Original Message", content, false)
                .field("Channel", &msg.channel_id, true)
                .timestamp(chrono::Utc::now().to_rfc3339());

            if let Err(e) = ctx.send_message_with_embed(&msg.channel_id, None, &embed).await {
                eprintln!("Failed to send embed message: {}", e);
            }
        }
    }
}

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    tracing_subscriber::init();

    let bot = RichEchoBot;
    
    let mut client = Client::new("your_app_id", bot)
        .intents(Intents::PUBLIC_GUILD_MESSAGES | Intents::DIRECT_MESSAGE | Intents::GUILDS)
        .build()
        .await?;

    client.start().await?;
    Ok(())
}
```

## Multi-Channel Echo Bot

```rust
use botrs::{Client, Context, EventHandler, Message, Ready, Intents, DirectMessage, GroupMessage};

struct MultiChannelEchoBot;

#[async_trait::async_trait]
impl EventHandler for MultiChannelEchoBot {
    async fn ready(&self, _ctx: Context, ready: Ready) {
        println!("Multi-channel echo bot {} is ready!", ready.user.username);
    }

    async fn message_create(&self, ctx: Context, msg: Message) {
        if msg.author.as_ref().map_or(false, |a| a.bot.unwrap_or(false)) {
            return;
        }

        if let Some(content) = &msg.content {
            let echo_msg = format!("Guild Echo: {}", content);
            let _ = ctx.send_message(&msg.channel_id, &echo_msg).await;
        }
    }

    async fn direct_message_create(&self, ctx: Context, msg: DirectMessage) {
        if let Some(content) = &msg.content {
            let echo_msg = format!("DM Echo: {}", content);
            // For direct messages, we reply to the same channel
            if let Some(channel_id) = &msg.channel_id {
                let _ = ctx.send_message(channel_id, &echo_msg).await;
            }
        }
    }

    async fn group_message_create(&self, ctx: Context, msg: GroupMessage) {
        if let Some(content) = &msg.content {
            let echo_msg = format!("Group Echo: {}", content);
            if let Some(group_id) = &msg.group_openid {
                let _ = ctx.send_group_message(group_id, &echo_msg).await;
            }
        }
    }
}

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    tracing_subscriber::init();

    let bot = MultiChannelEchoBot;
    
    let mut client = Client::new("your_app_id", bot)
        .intents(
            Intents::PUBLIC_GUILD_MESSAGES 
            | Intents::DIRECT_MESSAGE 
            | Intents::PUBLIC_MESSAGES 
            | Intents::GUILDS
        )
        .build()
        .await?;

    client.start().await?;
    Ok(())
}
```

## Echo Bot with Rate Limiting

```rust
use botrs::{Client, Context, EventHandler, Message, Ready, Intents};
use std::collections::HashMap;
use std::sync::Arc;
use tokio::sync::Mutex;
use tokio::time::{Duration, Instant};

struct RateLimitedEchoBot {
    last_message: Arc<Mutex<HashMap<String, Instant>>>,
}

impl RateLimitedEchoBot {
    fn new() -> Self {
        Self {
            last_message: Arc::new(Mutex::new(HashMap::new())),
        }
    }

    async fn can_respond(&self, user_id: &str) -> bool {
        let mut last_messages = self.last_message.lock().await;
        let now = Instant::now();
        
        if let Some(last_time) = last_messages.get(user_id) {
            if now.duration_since(*last_time) < Duration::from_secs(5) {
                return false; // Rate limited
            }
        }
        
        last_messages.insert(user_id.to_string(), now);
        true
    }
}

#[async_trait::async_trait]
impl EventHandler for RateLimitedEchoBot {
    async fn ready(&self, _ctx: Context, ready: Ready) {
        println!("Rate-limited echo bot {} is ready!", ready.user.username);
    }

    async fn message_create(&self, ctx: Context, msg: Message) {
        if msg.author.as_ref().map_or(false, |a| a.bot.unwrap_or(false)) {
            return;
        }

        if let Some(author) = &msg.author {
            if let Some(user_id) = &author.id {
                // Check rate limit
                if !self.can_respond(user_id).await {
                    return; // Skip if rate limited
                }

                if let Some(content) = &msg.content {
                    let echo_msg = format!("Echo: {}", content);
                    if let Err(e) = ctx.send_message(&msg.channel_id, &echo_msg).await {
                        eprintln!("Failed to send echo message: {}", e);
                    }
                }
            }
        }
    }
}

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    tracing_subscriber::init();

    let bot = RateLimitedEchoBot::new();
    
    let mut client = Client::new("your_app_id", bot)
        .intents(Intents::PUBLIC_GUILD_MESSAGES | Intents::DIRECT_MESSAGE | Intents::GUILDS)
        .build()
        .await?;

    client.start().await?;
    Ok(())
}
```

## Configuration

Before running any of these examples, make sure to:

1. **Set up environment variables:**
   ```bash
   export QQ_BOT_APP_ID=your_app_id
   export QQ_BOT_SECRET=your_secret
   ```

2. **Add dependencies to Cargo.toml:**
   ```toml
   [dependencies]
   botrs = "0.2"
   tokio = { version = "1.0", features = ["full"] }
   async-trait = "0.1"
   tracing = "0.1"
   tracing-subscriber = "0.3"
   chrono = { version = "0.4", features = ["serde"] }
   ```

3. **Enable required intents in QQ Developer Portal:**
   - Public Guild Messages
   - Direct Messages (if using DM features)
   - Guild information

## Key Concepts Demonstrated

1. **Basic Message Handling**: Responding to incoming messages
2. **Bot Message Filtering**: Avoiding infinite loops with bot messages
3. **Command Processing**: Handling specific command patterns
4. **Reply Functionality**: Using message replies for better UX
5. **Rich Content**: Creating embed messages for enhanced presentation
6. **Multi-Channel Support**: Handling different message types
7. **Rate Limiting**: Preventing spam and abuse

## Next Steps

- [Command Handler]./command-handler.md - Learn about structured command handling
- [Rich Messages]./rich-messages.md - Explore advanced message formatting
- [Event Handling]./event-handling.md - Handle more event types beyond messages
- [Error Recovery]./error-recovery.md - Implement robust error handling