Rich Messages Example
This example demonstrates how to create rich, interactive messages using embeds, attachments, and various formatting options in BotRS.
Overview
Rich messages enhance user experience by providing visually appealing content with embeds, images, files, and interactive elements. This guide shows various techniques for creating engaging bot responses.
Basic Embed Messages
rust
use botrs::{Client, Context, EventHandler, Message, Ready, Intents, MessageEmbed};
struct RichMessageBot;
#[async_trait::async_trait]
impl EventHandler for RichMessageBot {
async fn ready(&self, _ctx: Context, ready: Ready) {
println!("Rich message 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 {
match content.as_str() {
"!info" => {
self.send_info_embed(&ctx, &msg.channel_id).await;
}
"!status" => {
self.send_status_embed(&ctx, &msg.channel_id).await;
}
"!help" => {
self.send_help_embed(&ctx, &msg.channel_id).await;
}
_ => {}
}
}
}
}
impl RichMessageBot {
async fn send_info_embed(&self, ctx: &Context, channel_id: &str) {
let embed = MessageEmbed::new()
.title("Bot Information")
.description("This is a demonstration of rich message capabilities")
.color(0x3498db) // Blue color
.field("Version", "1.0.0", true)
.field("Language", "Rust", true)
.field("Framework", "BotRS", true)
.field("Uptime", "24 hours", false)
.thumbnail("https://example.com/bot-icon.png")
.footer("Powered by BotRS", Some("https://example.com/logo.png"))
.timestamp(chrono::Utc::now().to_rfc3339());
if let Err(e) = ctx.send_message_with_embed(channel_id, None, &embed).await {
eprintln!("Failed to send info embed: {}", e);
}
}
async fn send_status_embed(&self, ctx: &Context, channel_id: &str) {
let embed = MessageEmbed::new()
.title("System Status")
.color(0x2ecc71) // Green color for healthy status
.field("CPU Usage", "15%", true)
.field("Memory Usage", "512 MB", true)
.field("Network", "Stable", true)
.field("Database", "✅ Connected", true)
.field("Cache", "✅ Active", true)
.field("API", "✅ Responding", true)
.footer("Last updated", None)
.timestamp(chrono::Utc::now().to_rfc3339());
if let Err(e) = ctx.send_message_with_embed(channel_id, None, &embed).await {
eprintln!("Failed to send status embed: {}", e);
}
}
async fn send_help_embed(&self, ctx: &Context, channel_id: &str) {
let embed = MessageEmbed::new()
.title("Available Commands")
.description("Here are all the commands you can use:")
.color(0xe74c3c) // Red color
.field("!info", "Show bot information", false)
.field("!status", "Display system status", false)
.field("!help", "Show this help message", false)
.field("!image", "Send an image example", false)
.field("!file", "Send a file example", false)
.footer("Use commands without the quotes", None);
if let Err(e) = ctx.send_message_with_embed(channel_id, None, &embed).await {
eprintln!("Failed to send help embed: {}", e);
}
}
}
1
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
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
Advanced Rich Messages
rust
use botrs::{Client, Context, EventHandler, Message, Ready, Intents, MessageEmbed, MessageParams};
use std::path::Path;
struct AdvancedRichBot;
#[async_trait::async_trait]
impl EventHandler for AdvancedRichBot {
async fn ready(&self, _ctx: Context, ready: Ready) {
println!("Advanced rich 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 {
match content.as_str() {
"!weather" => {
self.send_weather_info(&ctx, &msg.channel_id).await;
}
"!profile" => {
self.send_user_profile(&ctx, &msg).await;
}
"!gallery" => {
self.send_image_gallery(&ctx, &msg.channel_id).await;
}
"!markdown" => {
self.send_markdown_message(&ctx, &msg.channel_id).await;
}
_ => {}
}
}
}
}
impl AdvancedRichBot {
async fn send_weather_info(&self, ctx: &Context, channel_id: &str) {
let embed = MessageEmbed::new()
.title("Weather Report")
.description("Current weather conditions")
.color(0x87ceeb) // Sky blue
.field("Location", "Beijing, China", true)
.field("Temperature", "22°C", true)
.field("Condition", "Partly Cloudy", true)
.field("Humidity", "65%", true)
.field("Wind Speed", "15 km/h", true)
.field("Visibility", "10 km", true)
.thumbnail("https://example.com/weather-icon.png")
.image("https://example.com/weather-map.png")
.footer("Data updated 5 minutes ago", None)
.timestamp(chrono::Utc::now().to_rfc3339());
if let Err(e) = ctx.send_message_with_embed(channel_id, None, &embed).await {
eprintln!("Failed to send weather embed: {}", e);
}
}
async fn send_user_profile(&self, ctx: &Context, msg: &Message) {
if let Some(author) = &msg.author {
let embed = MessageEmbed::new()
.title("User Profile")
.description(format!("Profile information for {}",
author.username.as_deref().unwrap_or("Unknown")))
.color(0x9b59b6) // Purple
.field("Username",
author.username.as_deref().unwrap_or("Unknown"), true)
.field("User ID",
author.id.as_deref().unwrap_or("Unknown"), true)
.field("Bot Account",
if author.bot.unwrap_or(false) { "Yes" } else { "No" }, true)
.thumbnail(author.avatar.as_deref().unwrap_or(""))
.footer("Profile requested", None)
.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 profile embed: {}", e);
}
}
}
async fn send_image_gallery(&self, ctx: &Context, channel_id: &str) {
// Send multiple embeds for a gallery effect
let images = vec![
("Sunset", "https://example.com/sunset.jpg", 0xff6b35),
("Ocean", "https://example.com/ocean.jpg", 0x006ba6),
("Mountains", "https://example.com/mountains.jpg", 0x0f3460),
];
for (title, url, color) in images {
let embed = MessageEmbed::new()
.title(title)
.color(color)
.image(url)
.footer("Image Gallery", None);
if let Err(e) = ctx.send_message_with_embed(channel_id, None, &embed).await {
eprintln!("Failed to send gallery image: {}", e);
}
// Small delay between images
tokio::time::sleep(tokio::time::Duration::from_millis(500)).await;
}
}
async fn send_markdown_message(&self, ctx: &Context, channel_id: &str) {
let markdown_content = r#"
# Markdown Support
BotRS supports **rich text formatting** with markdown!
## Text Formatting
- **Bold text**
- *Italic text*
- ~~Strikethrough text~~
- `Inline code`
## Lists
1. First ordered item
2. Second ordered item
3. Third ordered item
- Unordered item
- Another item
- Last item
## Code Blocks
```rust
fn main() {
println!("Hello, BotRS!");
}
1
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
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
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
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
File and Media Messages
rust
use botrs::{Client, Context, EventHandler, Message, Ready, Intents, MessageParams};
use std::path::Path;
struct MediaBot;
#[async_trait::async_trait]
impl EventHandler for MediaBot {
async fn ready(&self, _ctx: Context, ready: Ready) {
println!("Media 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 {
match content.as_str() {
"!image" => {
self.send_image(&ctx, &msg.channel_id).await;
}
"!audio" => {
self.send_audio(&ctx, &msg.channel_id).await;
}
"!video" => {
self.send_video(&ctx, &msg.channel_id).await;
}
"!document" => {
self.send_document(&ctx, &msg.channel_id).await;
}
_ => {}
}
}
}
}
impl MediaBot {
async fn send_image(&self, ctx: &Context, channel_id: &str) {
// Method 1: Send from local file
let image_path = Path::new("./assets/example.png");
if image_path.exists() {
let params = MessageParams::new_text("Here's an image from file!")
.with_image_file(image_path);
if let Ok(params) = params {
if let Err(e) = ctx.api.post_message_with_params(&ctx.token, channel_id, params).await {
eprintln!("Failed to send image from file: {}", e);
}
}
} else {
// Method 2: Send from URL
let params = MessageParams::new_text("Here's an image from URL!")
.with_image("https://example.com/sample-image.png");
if let Err(e) = ctx.api.post_message_with_params(&ctx.token, channel_id, params).await {
eprintln!("Failed to send image from URL: {}", e);
}
}
}
async fn send_audio(&self, ctx: &Context, channel_id: &str) {
let audio_path = Path::new("./assets/sample.mp3");
if audio_path.exists() {
let params = MessageParams::new_text("Here's an audio file!")
.with_audio_file(audio_path);
if let Ok(params) = params {
if let Err(e) = ctx.api.post_message_with_params(&ctx.token, channel_id, params).await {
eprintln!("Failed to send audio: {}", e);
}
}
} else {
let _ = ctx.send_message(channel_id, "Audio file not found!").await;
}
}
async fn send_video(&self, ctx: &Context, channel_id: &str) {
let video_path = Path::new("./assets/sample.mp4");
if video_path.exists() {
let params = MessageParams::new_text("Here's a video!")
.with_video_file(video_path);
if let Ok(params) = params {
if let Err(e) = ctx.api.post_message_with_params(&ctx.token, channel_id, params).await {
eprintln!("Failed to send video: {}", e);
}
}
} else {
let _ = ctx.send_message(channel_id, "Video file not found!").await;
}
}
async fn send_document(&self, ctx: &Context, channel_id: &str) {
let doc_path = Path::new("./assets/document.pdf");
if doc_path.exists() {
let params = MessageParams::new_text("Here's a document!")
.with_file(doc_path);
if let Ok(params) = params {
if let Err(e) = ctx.api.post_message_with_params(&ctx.token, channel_id, params).await {
eprintln!("Failed to send document: {}", e);
}
}
} else {
let _ = ctx.send_message(channel_id, "Document not found!").await;
}
}
}
1
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
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
Interactive Rich Messages
rust
use botrs::{Client, Context, EventHandler, Message, Ready, Intents, MessageEmbed};
struct InteractiveBot;
#[async_trait::async_trait]
impl EventHandler for InteractiveBot {
async fn ready(&self, _ctx: Context, ready: Ready) {
println!("Interactive 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 {
match content.as_str() {
"!poll" => {
self.create_poll(&ctx, &msg.channel_id).await;
}
"!menu" => {
self.create_menu(&ctx, &msg.channel_id).await;
}
"!progress" => {
self.show_progress(&ctx, &msg.channel_id).await;
}
_ => {}
}
}
}
}
impl InteractiveBot {
async fn create_poll(&self, ctx: &Context, channel_id: &str) {
let embed = MessageEmbed::new()
.title("📊 Community Poll")
.description("What's your favorite programming language?")
.color(0xf39c12) // Orange
.field("🦀 Rust", "React with 🦀", false)
.field("🐍 Python", "React with 🐍", false)
.field("☕ Java", "React with ☕", false)
.field("⚡ JavaScript", "React with ⚡", false)
.footer("Poll expires in 24 hours", None)
.timestamp(chrono::Utc::now().to_rfc3339());
if let Ok(sent_msg) = ctx.send_message_with_embed(channel_id, None, &embed).await {
// Add reactions for voting
let reactions = ["🦀", "🐍", "☕", "⚡"];
for emoji in &reactions {
// Note: Actual reaction adding would require emoji handling
// This is a simplified example
tokio::time::sleep(tokio::time::Duration::from_millis(100)).await;
}
}
}
async fn create_menu(&self, ctx: &Context, channel_id: &str) {
let embed = MessageEmbed::new()
.title("🍽️ Restaurant Menu")
.description("Welcome to our virtual restaurant!")
.color(0x27ae60) // Green
.field("🍕 Pizza", "$12.99 - Margherita with fresh basil", false)
.field("🍔 Burger", "$9.99 - Classic beef burger with fries", false)
.field("🍜 Ramen", "$11.50 - Authentic Japanese ramen", false)
.field("🥗 Salad", "$8.99 - Fresh garden salad", false)
.field("🍰 Dessert", "$5.99 - Chocolate cake slice", false)
.thumbnail("https://example.com/restaurant-logo.png")
.footer("Use reactions to place your order!", None);
if let Err(e) = ctx.send_message_with_embed(channel_id, None, &embed).await {
eprintln!("Failed to send menu: {}", e);
}
}
async fn show_progress(&self, ctx: &Context, channel_id: &str) {
let progress_bars = vec![
("Download", 85, 0x3498db),
("Installation", 60, 0xf39c12),
("Configuration", 30, 0xe74c3c),
];
for (task, progress, color) in progress_bars {
let bar_length = 20;
let filled = (progress * bar_length) / 100;
let empty = bar_length - filled;
let progress_bar = format!(
"[{}{}] {}%",
"█".repeat(filled),
"░".repeat(empty),
progress
);
let embed = MessageEmbed::new()
.title(format!("⚙️ {} Progress", task))
.description(format!("```\n{}\n```", progress_bar))
.color(color)
.field("Status",
if progress == 100 { "✅ Complete" }
else if progress > 0 { "🔄 In Progress" }
else { "⏳ Pending" },
true)
.field("ETA",
if progress == 100 { "Complete" }
else { "2 minutes" },
true)
.timestamp(chrono::Utc::now().to_rfc3339());
if let Err(e) = ctx.send_message_with_embed(channel_id, None, &embed).await {
eprintln!("Failed to send progress: {}", e);
}
tokio::time::sleep(tokio::time::Duration::from_millis(1000)).await;
}
}
}
1
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
116
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
116
Configuration
Add these dependencies to your 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"] }
1
2
3
4
5
6
7
2
3
4
5
6
7
Set up your environment:
bash
export QQ_BOT_APP_ID=your_app_id
export QQ_BOT_SECRET=your_secret
1
2
2
Asset Files
Create an assets
directory with sample files for testing:
assets/
├── example.png
├── sample.mp3
├── sample.mp4
└── document.pdf
1
2
3
4
5
2
3
4
5
Best Practices
- Image Optimization: Use appropriate image sizes to avoid large uploads
- Color Consistency: Use consistent color schemes across embeds
- Content Limits: Be aware of message length and embed field limits
- Accessibility: Provide alt text and clear descriptions
- Performance: Don't send too many rich messages in quick succession
- User Experience: Use rich messages to enhance, not overwhelm
Key Features Demonstrated
- Embed Creation: Rich formatted messages with multiple fields
- Media Handling: Images, audio, video, and document uploads
- Markdown Support: Text formatting and structure
- Interactive Elements: Polls, menus, and progress indicators
- Visual Design: Colors, thumbnails, and layout
- Timestamps: Dynamic time information
- Error Handling: Graceful fallbacks for missing assets
Next Steps
- Command Handler - Structure your commands
- Interactive Messages - Add buttons and forms
- File Uploads - Advanced file handling
- Event Handling - React to user interactions