Text Messages Examples
This guide demonstrates how to handle and send text messages using BotRS. Text messages are the most common type of interaction in QQ Guild bots, supporting various scenarios from simple replies to complex command systems.
Basic Text Handling
Simple Echo Bot
The most basic text message handling involves echoing user input back to them.
use botrs::{Client, Context, EventHandler, Intents, Message, Ready, Token};
struct EchoHandler;
#[async_trait::async_trait]
impl EventHandler for EchoHandler {
async fn ready(&self, _ctx: Context, ready: Ready) {
println!("Bot {} is ready!", ready.user.username);
}
async fn message_create(&self, ctx: Context, message: Message) {
// Ignore bot messages to prevent loops
if message.is_from_bot() {
return;
}
// Check if message has content
if let Some(content) = &message.content {
// Simple echo - reply with the same content
if let Err(e) = message.reply(&ctx.api, &ctx.token, content).await {
eprintln!("Failed to send reply: {}", e);
}
}
}
}
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let token = Token::new("your_app_id", "your_secret");
let intents = Intents::default().with_public_guild_messages();
let handler = EchoHandler;
let mut client = Client::new(token, intents, handler, true)?;
client.start().await?;
Ok(())
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
@ Mention Replies
Responding specifically to messages that mention the bot.
use botrs::{Client, Context, EventHandler, Intents, Message, Ready, Token};
struct MentionHandler;
#[async_trait::async_trait]
impl EventHandler for MentionHandler {
async fn ready(&self, _ctx: Context, ready: Ready) {
println!("Bot {} is ready for mentions!", ready.user.username);
}
async fn message_create(&self, ctx: Context, message: Message) {
if message.is_from_bot() {
return;
}
if let Some(content) = &message.content {
// Get bot name for personalized responses
let bot_name = ctx
.bot_info
.as_ref()
.map(|info| info.username.as_str())
.unwrap_or("Bot");
let reply_content = format!(
"Hello! Bot {} received your mention: {}",
bot_name, content
);
if let Err(e) = message.reply(&ctx.api, &ctx.token, &reply_content).await {
eprintln!("Failed to reply to mention: {}", e);
}
}
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
Command Systems
Simple Command Handler
Building a basic command system that responds to prefixed messages.
use botrs::{Client, Context, EventHandler, Intents, Message, Ready, Token};
use std::collections::HashMap;
type CommandHandler = fn(&str) -> String;
struct CommandBot {
commands: HashMap<String, CommandHandler>,
}
impl CommandBot {
fn new() -> Self {
let mut commands = HashMap::new();
// Register commands
commands.insert("ping".to_string(), ping_command as CommandHandler);
commands.insert("hello".to_string(), hello_command as CommandHandler);
commands.insert("echo".to_string(), echo_command as CommandHandler);
commands.insert("help".to_string(), help_command as CommandHandler);
Self { commands }
}
fn handle_command(&self, content: &str) -> Option<String> {
// Commands start with !
if !content.starts_with('!') {
return None;
}
let content = &content[1..]; // Remove !
let parts: Vec<&str> = content.splitn(2, ' ').collect();
let command = parts[0];
let args = parts.get(1).unwrap_or(&"");
self.commands.get(command).map(|handler| handler(args))
}
}
// Command implementations
fn ping_command(_args: &str) -> String {
"Pong!".to_string()
}
fn hello_command(args: &str) -> String {
if args.is_empty() {
"Hello there!".to_string()
} else {
format!("Hello, {}!", args)
}
}
fn echo_command(args: &str) -> String {
if args.is_empty() {
"Nothing to echo!".to_string()
} else {
format!("Echo: {}", args)
}
}
fn help_command(_args: &str) -> String {
"Available commands:\n!ping - Responds with Pong!\n!hello [name] - Greets you\n!echo <text> - Echoes your text\n!help - Shows this help".to_string()
}
#[async_trait::async_trait]
impl EventHandler for CommandBot {
async fn ready(&self, _ctx: Context, ready: Ready) {
println!("Command bot {} is ready!", ready.user.username);
}
async fn message_create(&self, ctx: Context, message: Message) {
if message.is_from_bot() {
return;
}
if let Some(content) = &message.content {
if let Some(response) = self.handle_command(content) {
if let Err(e) = message.reply(&ctx.api, &ctx.token, &response).await {
eprintln!("Failed to send command response: {}", e);
}
}
}
}
}
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let token = Token::new("your_app_id", "your_secret");
let intents = Intents::default().with_public_guild_messages();
let handler = CommandBot::new();
let mut client = Client::new(token, intents, handler, true)?;
client.start().await?;
Ok(())
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
Advanced Command System with Aliases
More sophisticated command handling with command aliases and argument parsing.
use botrs::{Client, Context, EventHandler, Intents, Message, Ready, Token};
struct CommandRegistry {
commands: Vec<(Vec<String>, fn(&str) -> Option<String>)>,
}
impl CommandRegistry {
fn new() -> Self {
let mut registry = Self {
commands: Vec::new(),
};
// Register commands with aliases
registry.register(vec!["hello", "hi", "hey"], hello_handler);
registry.register(vec!["goodbye", "bye", "cya"], goodbye_handler);
registry.register(vec!["time", "clock"], time_handler);
registry.register(vec!["random", "rand"], random_handler);
registry
}
fn register(&mut self, aliases: Vec<&str>, handler: fn(&str) -> Option<String>) {
let aliases: Vec<String> = aliases.iter().map(|s| s.to_string()).collect();
self.commands.push((aliases, handler));
}
fn execute(&self, content: &str) -> Option<String> {
let trimmed = content.trim();
for (aliases, handler) in &self.commands {
for alias in aliases {
if trimmed.starts_with(alias) {
let params = if trimmed.len() > alias.len() {
trimmed[alias.len()..].trim()
} else {
""
};
return handler(params);
}
}
}
None
}
}
// Command handlers
fn hello_handler(params: &str) -> Option<String> {
Some(if params.is_empty() {
"Hello! How are you today?".to_string()
} else {
format!("Hello, {}! Nice to meet you!", params)
})
}
fn goodbye_handler(params: &str) -> Option<String> {
Some(if params.is_empty() {
"Goodbye! Have a great day!".to_string()
} else {
format!("Goodbye, {}! See you later!", params)
})
}
fn time_handler(_params: &str) -> Option<String> {
use chrono::Utc;
let now = Utc::now();
Some(format!("Current UTC time: {}", now.format("%Y-%m-%d %H:%M:%S")))
}
fn random_handler(params: &str) -> Option<String> {
use rand::Rng;
if params.is_empty() {
let num = rand::thread_rng().gen_range(1..=100);
Some(format!("Random number: {}", num))
} else if let Ok(max) = params.parse::<u32>() {
let num = rand::thread_rng().gen_range(1..=max);
Some(format!("Random number (1-{}): {}", max, num))
} else {
Some("Invalid number format. Usage: random [max_number]".to_string())
}
}
struct AdvancedCommandHandler {
registry: CommandRegistry,
}
impl AdvancedCommandHandler {
fn new() -> Self {
Self {
registry: CommandRegistry::new(),
}
}
}
#[async_trait::async_trait]
impl EventHandler for AdvancedCommandHandler {
async fn ready(&self, _ctx: Context, ready: Ready) {
println!("Advanced command bot {} is ready!", ready.user.username);
}
async fn message_create(&self, ctx: Context, message: Message) {
if message.is_from_bot() {
return;
}
if let Some(content) = &message.content {
if let Some(response) = self.registry.execute(content) {
if let Err(e) = message.reply(&ctx.api, &ctx.token, &response).await {
eprintln!("Failed to send response: {}", e);
}
}
}
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
Different Message Types
Group Messages
Handling text messages in QQ groups.
use botrs::{Client, Context, EventHandler, GroupMessage, Intents, Ready, Token};
struct GroupTextHandler;
#[async_trait::async_trait]
impl EventHandler for GroupTextHandler {
async fn ready(&self, _ctx: Context, ready: Ready) {
println!("Group bot {} is ready!", ready.user.username);
}
async fn group_message_create(&self, ctx: Context, message: GroupMessage) {
if let Some(content) = &message.content {
println!("Received group message: {}", content);
// Handle specific group commands
let response = if content.contains("hello") {
Some("Hello everyone in the group!")
} else if content.contains("help") {
Some("Group commands: hello, help, info")
} else if content.contains("info") {
Some("This is a QQ group bot built with BotRS")
} else {
None
};
if let Some(reply_text) = response {
// Use the convenience reply method
if let Err(e) = message.reply(&ctx.api, &ctx.token, reply_text).await {
eprintln!("Failed to reply to group message: {}", e);
}
}
}
}
}
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let token = Token::new("your_app_id", "your_secret");
let intents = Intents::default().with_public_messages();
let handler = GroupTextHandler;
let mut client = Client::new(token, intents, handler, true)?;
client.start().await?;
Ok(())
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
C2C Messages
Handling client-to-client text messages.
use botrs::{C2CMessage, Client, Context, EventHandler, Intents, Ready, Token};
struct C2CTextHandler;
#[async_trait::async_trait]
impl EventHandler for C2CTextHandler {
async fn ready(&self, _ctx: Context, ready: Ready) {
println!("C2C bot {} is ready!", ready.user.username);
}
async fn c2c_message_create(&self, ctx: Context, message: C2CMessage) {
if let Some(content) = &message.content {
println!("Received C2C message: {}", content);
// Create a personalized response
let reply_content = format!("I received your private message: {}", content);
// Reply to the C2C message
if let Err(e) = message.reply(&ctx.api, &ctx.token, &reply_content).await {
eprintln!("Failed to reply to C2C message: {}", e);
}
}
}
}
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let token = Token::new("your_app_id", "your_secret");
let intents = Intents::default().with_public_messages();
let handler = C2CTextHandler;
let mut client = Client::new(token, intents, handler, true)?;
client.start().await?;
Ok(())
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
Direct Messages
Handling direct messages in guild contexts.
use botrs::{Client, Context, DirectMessage, EventHandler, Intents, Ready, Token};
struct DirectMessageHandler;
#[async_trait::async_trait]
impl EventHandler for DirectMessageHandler {
async fn ready(&self, _ctx: Context, ready: Ready) {
println!("DM bot {} is ready!", ready.user.username);
}
async fn direct_message_create(&self, ctx: Context, message: DirectMessage) {
if let Some(content) = &message.content {
println!("Received direct message: {}", content);
// Handle DM-specific commands
let response = match content.to_lowercase().as_str() {
"help" => "DM Commands: help, status, info",
"status" => "Bot is running normally",
"info" => "This is a private conversation with the bot",
_ => "Thanks for your message! Type 'help' for available commands.",
};
if let Err(e) = message.reply(&ctx.api, &ctx.token, response).await {
eprintln!("Failed to reply to direct message: {}", e);
}
}
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
Advanced Text Processing
Text Analysis and Response
Analyzing message content for intelligent responses.
use botrs::{Client, Context, EventHandler, Intents, Message, Ready, Token};
struct AnalysisHandler;
impl AnalysisHandler {
fn analyze_sentiment(&self, text: &str) -> &'static str {
let positive_words = ["good", "great", "awesome", "love", "happy", "excellent"];
let negative_words = ["bad", "terrible", "hate", "sad", "awful", "horrible"];
let text_lower = text.to_lowercase();
let positive_count = positive_words.iter()
.map(|word| text_lower.matches(word).count())
.sum::<usize>();
let negative_count = negative_words.iter()
.map(|word| text_lower.matches(word).count())
.sum::<usize>();
if positive_count > negative_count {
"positive"
} else if negative_count > positive_count {
"negative"
} else {
"neutral"
}
}
fn generate_response(&self, content: &str) -> String {
let sentiment = self.analyze_sentiment(content);
let word_count = content.split_whitespace().count();
match sentiment {
"positive" => format!("I'm glad to hear positive things! Your message ({} words) sounds great!", word_count),
"negative" => format!("I hope things get better! Thanks for sharing your {} words with me.", word_count),
_ => format!("Thanks for your message! I received {} words from you.", word_count),
}
}
}
#[async_trait::async_trait]
impl EventHandler for AnalysisHandler {
async fn ready(&self, _ctx: Context, ready: Ready) {
println!("Analysis bot {} is ready!", ready.user.username);
}
async fn message_create(&self, ctx: Context, message: Message) {
if message.is_from_bot() {
return;
}
if let Some(content) = &message.content {
// Skip short messages
if content.len() < 10 {
return;
}
let response = self.generate_response(content);
if let Err(e) = message.reply(&ctx.api, &ctx.token, &response).await {
eprintln!("Failed to send analysis response: {}", e);
}
}
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
Rate Limiting and Spam Protection
Implementing basic rate limiting for text responses.
use botrs::{Client, Context, EventHandler, Intents, Message, Ready, Token};
use std::collections::HashMap;
use std::time::{Duration, Instant};
struct RateLimitedHandler {
last_response: HashMap<String, Instant>,
cooldown: Duration,
}
impl RateLimitedHandler {
fn new(cooldown_seconds: u64) -> Self {
Self {
last_response: HashMap::new(),
cooldown: Duration::from_secs(cooldown_seconds),
}
}
fn can_respond(&mut self, user_id: &str) -> bool {
let now = Instant::now();
if let Some(&last_time) = self.last_response.get(user_id) {
if now.duration_since(last_time) < self.cooldown {
return false;
}
}
self.last_response.insert(user_id.to_string(), now);
true
}
}
#[async_trait::async_trait]
impl EventHandler for RateLimitedHandler {
async fn ready(&self, _ctx: Context, ready: Ready) {
println!("Rate-limited bot {} is ready!", ready.user.username);
}
async fn message_create(&mut self, ctx: Context, message: Message) {
if message.is_from_bot() {
return;
}
if let (Some(content), Some(author)) = (&message.content, &message.author) {
// Check rate limit
if !self.can_respond(&author.id) {
return; // Silently ignore if user is rate limited
}
// Simple echo with rate limiting
let response = format!("Echo (rate limited): {}", content);
if let Err(e) = message.reply(&ctx.api, &ctx.token, &response).await {
eprintln!("Failed to send rate-limited response: {}", e);
}
}
}
}
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let token = Token::new("your_app_id", "your_secret");
let intents = Intents::default().with_public_guild_messages();
let handler = RateLimitedHandler::new(5); // 5 second cooldown
let mut client = Client::new(token, intents, handler, true)?;
client.start().await?;
Ok(())
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
Error Handling
Robust Error Handling
Implementing comprehensive error handling for text message operations.
use botrs::{BotError, Client, Context, EventHandler, Intents, Message, Ready, Token};
struct RobustHandler;
impl RobustHandler {
async fn safe_reply(&self, ctx: &Context, message: &Message, content: &str) -> Result<(), BotError> {
match message.reply(&ctx.api, &ctx.token, content).await {
Ok(_) => {
println!("Successfully sent reply");
Ok(())
}
Err(BotError::Http(status)) => {
eprintln!("HTTP error {}: Failed to send message", status);
Err(BotError::Http(status))
}
Err(BotError::RateLimit(retry_after)) => {
eprintln!("Rate limited, retry after {} seconds", retry_after);
// Could implement retry logic here
Err(BotError::RateLimit(retry_after))
}
Err(e) => {
eprintln!("Other error: {}", e);
Err(e)
}
}
}
}
#[async_trait::async_trait]
impl EventHandler for RobustHandler {
async fn ready(&self, _ctx: Context, ready: Ready) {
println!("Robust bot {} is ready!", ready.user.username);
}
async fn message_create(&self, ctx: Context, message: Message) {
if message.is_from_bot() {
return;
}
if let Some(content) = &message.content {
// Validate message content
if content.len() > 2000 {
let _ = self.safe_reply(&ctx, &message, "Message too long! Please keep it under 2000 characters.").await;
return;
}
if content.trim().is_empty() {
return; // Ignore empty messages
}
// Process the message
let response = format!("Processed: {}", content);
let _ = self.safe_reply(&ctx, &message, &response).await;
}
}
async fn error(&self, error: BotError) {
eprintln!("Handler error: {}", error);
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
Best Practices
Performance Considerations
use botrs::{Client, Context, EventHandler, Intents, Message, Ready, Token};
use tokio::sync::Semaphore;
use std::sync::Arc;
struct PerformantHandler {
// Limit concurrent message processing
semaphore: Arc<Semaphore>,
}
impl PerformantHandler {
fn new(max_concurrent: usize) -> Self {
Self {
semaphore: Arc::new(Semaphore::new(max_concurrent)),
}
}
async fn process_message(&self, ctx: Context, message: Message) {
// Acquire permit for processing
let _permit = self.semaphore.acquire().await.unwrap();
if let Some(content) = &message.content {
// Simulate processing time
tokio::time::sleep(tokio::time::Duration::from_millis(100)).await;
let response = format!("Processed: {}", content);
if let Err(e) = message.reply(&ctx.api, &ctx.token, &response).await {
eprintln!("Failed to reply: {}", e);
}
}
}
}
#[async_trait::async_trait]
impl EventHandler for PerformantHandler {
async fn ready(&self, _ctx: Context, ready: Ready) {
println!("Performant bot {} is ready!", ready.user.username);
}
async fn message_create(&self, ctx: Context, message: Message) {
if message.is_from_bot() {
return;
}
// Spawn task for concurrent processing
let handler = Arc::new(self.clone());
tokio::spawn(async move {
handler.process_message(ctx, message).await;
});
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
This comprehensive guide covers the essential patterns for handling text messages in BotRS, from basic echo bots to sophisticated command systems with error handling and performance optimizations.
See Also
- Rich Messages - Working with embeds, attachments, and interactive content
- Message Models - Detailed API reference for message types
- Event Handling - Complete guide to event handling patterns
- Error Recovery - Advanced error handling strategies