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:
Set up environment variables:
bashexport QQ_BOT_APP_ID=your_app_id export QQ_BOT_SECRET=your_secret
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"] }
Enable required intents in QQ Developer Portal:
- Public Guild Messages
- Direct Messages (if using DM features)
- Guild information
Key Concepts Demonstrated
- Basic Message Handling: Responding to incoming messages
- Bot Message Filtering: Avoiding infinite loops with bot messages
- Command Processing: Handling specific command patterns
- Reply Functionality: Using message replies for better UX
- Rich Content: Creating embed messages for enhanced presentation
- Multi-Channel Support: Handling different message types
- Rate Limiting: Preventing spam and abuse
Next Steps
- Command Handler - Learn about structured command handling
- Rich Messages - Explore advanced message formatting
- Event Handling - Handle more event types beyond messages
- Error Recovery - Implement robust error handling