富文本消息示例
本示例展示如何使用 BotRS 发送各种类型的富文本消息,包括嵌入内容(Embed)、Ark 消息、Markdown 格式和交互式组件。
概述
QQ 频道支持多种富文本消息格式,让机器人能够发送更加丰富和交互性的内容:
- Embed 消息: 结构化的富文本卡片
- Ark 消息: 基于模板的结构化消息
- Markdown 消息: 支持 Markdown 语法的文本
- 交互式消息: 包含按钮和选择菜单的消息
基础嵌入消息
简单嵌入消息
rust
use botrs::{Context, EventHandler, Message, MessageParams, Embed};
async fn send_simple_embed(
ctx: &Context,
channel_id: &str
) -> Result<(), botrs::BotError> {
let embed = Embed::new()
.title("欢迎使用机器人")
.description("这是一个简单的嵌入消息示例")
.color(0x3498db); // 蓝色
let params = MessageParams::new_embed(embed);
ctx.api.post_message_with_params(&ctx.token, channel_id, params).await?;
Ok(())
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
带字段的嵌入消息
rust
async fn send_detailed_embed(
ctx: &Context,
channel_id: &str
) -> Result<(), botrs::BotError> {
let embed = Embed::new()
.title("服务器状态")
.description("当前服务器运行状态信息")
.color(0x00ff00) // 绿色
.field("CPU 使用率", "25%", true)
.field("内存使用率", "60%", true)
.field("磁盘使用率", "45%", true)
.field("网络延迟", "12ms", true)
.field("运行时间", "7天 3小时 25分钟", false)
.field("活跃用户", "1,234 人在线", false)
.timestamp(chrono::Utc::now())
.footer("系统监控", Some("https://example.com/icon.png"));
let params = MessageParams::new_embed(embed);
ctx.api.post_message_with_params(&ctx.token, channel_id, params).await?;
Ok(())
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
带图片的嵌入消息
rust
async fn send_embed_with_image(
ctx: &Context,
channel_id: &str
) -> Result<(), botrs::BotError> {
let embed = Embed::new()
.title("每日图片")
.description("今日推荐的精美图片")
.color(0xff6b6b) // 红色
.image("https://example.com/daily-image.jpg")
.thumbnail("https://example.com/thumbnail.jpg")
.author("图片机器人", Some("https://example.com/bot-avatar.png"))
.url("https://example.com/full-gallery");
let params = MessageParams::new_embed(embed);
ctx.api.post_message_with_params(&ctx.token, channel_id, params).await?;
Ok(())
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
Ark 消息
链接卡片 Ark
rust
use serde_json::json;
async fn send_link_ark(
ctx: &Context,
channel_id: &str,
url: &str,
title: &str,
description: &str
) -> Result<(), botrs::BotError> {
let ark_data = json!({
"template_id": 23, // 链接模板 ID
"kv": [
{
"key": "#DESC#",
"value": description
},
{
"key": "#PROMPT#",
"value": title
},
{
"key": "#URL#",
"value": url
}
]
});
let params = MessageParams::new_ark(ark_data);
ctx.api.post_message_with_params(&ctx.token, channel_id, params).await?;
Ok(())
}
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
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
自定义 Ark 消息
rust
async fn send_custom_ark(
ctx: &Context,
channel_id: &str
) -> Result<(), botrs::BotError> {
let ark_data = json!({
"template_id": 37, // 自定义模板 ID
"kv": [
{
"key": "#TITLE#",
"value": "重要通知"
},
{
"key": "#CONTENT#",
"value": "系统将在今晚进行维护,预计持续2小时"
},
{
"key": "#TIME#",
"value": "2024-01-15 22:00 - 24:00"
},
{
"key": "#LEVEL#",
"value": "高"
}
]
});
let params = MessageParams::new_ark(ark_data);
ctx.api.post_message_with_params(&ctx.token, channel_id, params).await?;
Ok(())
}
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
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
Markdown 消息
基础 Markdown
rust
async fn send_markdown_message(
ctx: &Context,
channel_id: &str
) -> Result<(), botrs::BotError> {
let markdown_content = r#"
# 机器人帮助文档
欢迎使用我们的多功能机器人!
## 主要功能
### 消息功能
- **文本消息**: 发送普通文本
- **富文本**: 支持 Markdown 格式
- **图片分享**: 上传和分享图片
- **文件传输**: 支持多种文件格式
### 管理功能
- **成员管理**: 查看和管理频道成员
- **权限控制**: 角色和权限分配
- **频道设置**: 自定义频道配置
### 娱乐功能
- **小游戏**: 内置多种小游戏
- **音乐播放**: 语音频道音乐播放
- **表情包**: 丰富的表情包资源
## 使用方法
1. 使用 `!help` 查看命令列表
2. 使用 `!设置` 进行个性化配置
3. 使用 `@机器人` 直接对话
---
**技术支持**: support@example.com
**更新日志**: [点击查看](https://example.com/changelog)
"#;
let params = MessageParams::new_markdown(markdown_content);
ctx.api.post_message_with_params(&ctx.token, channel_id, params).await?;
Ok(())
}
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
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
动态 Markdown
rust
async fn send_dynamic_markdown(
ctx: &Context,
channel_id: &str,
user_name: &str,
stats: &UserStats
) -> Result<(), botrs::BotError> {
let markdown_content = format!(r#"
# 用户统计报告
## 📊 {user_name} 的数据概览
### 基础信息
- **用户名**: {user_name}
- **注册时间**: {register_date}
- **最后活跃**: {last_active}
### 活动统计
| 项目 | 数值 | 排名 |
|------|------|------|
| 发送消息 | **{message_count}** 条 | 🥇 #{message_rank} |
| 在线时长 | **{online_hours}** 小时 | 🥈 #{online_rank} |
| 获得点赞 | **{likes_count}** 个 | 🥉 #{likes_rank} |
### 成就徽章
{achievements}
### 本月目标
- [ ] 发送 1000 条消息 ({current_messages}/1000)
- [ ] 在线 100 小时 ({current_hours}/100)
- [x] ~~获得 50 个点赞~~ ✅
> 💡 **提示**: 继续保持活跃,下个月可能获得"活跃之星"称号!
"#,
user_name = user_name,
register_date = stats.register_date,
last_active = stats.last_active,
message_count = stats.message_count,
message_rank = stats.message_rank,
online_hours = stats.online_hours,
online_rank = stats.online_rank,
likes_count = stats.likes_count,
likes_rank = stats.likes_rank,
achievements = stats.achievements.join(" "),
current_messages = stats.current_month_messages,
current_hours = stats.current_month_hours,
);
let params = MessageParams::new_markdown(&markdown_content);
ctx.api.post_message_with_params(&ctx.token, channel_id, params).await?;
Ok(())
}
struct UserStats {
register_date: String,
last_active: String,
message_count: u32,
message_rank: u32,
online_hours: u32,
online_rank: u32,
likes_count: u32,
likes_rank: u32,
achievements: Vec<String>,
current_month_messages: u32,
current_month_hours: u32,
}
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
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
交互式消息
按钮消息
rust
use botrs::{MessageKeyboard, KeyboardButton, KeyboardRow};
async fn send_button_message(
ctx: &Context,
channel_id: &str
) -> Result<(), botrs::BotError> {
let keyboard = MessageKeyboard::new()
.add_row(KeyboardRow::new()
.add_button(KeyboardButton::new("👍 点赞", "like_button"))
.add_button(KeyboardButton::new("👎 踩", "dislike_button"))
.add_button(KeyboardButton::new("❤️ 收藏", "favorite_button"))
)
.add_row(KeyboardRow::new()
.add_button(KeyboardButton::new("📊 查看统计", "stats_button"))
.add_button(KeyboardButton::new("⚙️ 设置", "settings_button"))
)
.add_row(KeyboardRow::new()
.add_button(KeyboardButton::new("🔗 访问官网", "website_button").with_url("https://example.com"))
);
let params = MessageParams::new_text("请选择操作:")
.with_keyboard(keyboard);
ctx.api.post_message_with_params(&ctx.token, channel_id, params).await?;
Ok(())
}
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
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
复杂交互式卡片
rust
async fn send_interactive_card(
ctx: &Context,
channel_id: &str
) -> Result<(), botrs::BotError> {
// 创建嵌入内容
let embed = Embed::new()
.title("📋 任务管理系统")
.description("选择要执行的操作")
.color(0x4a90e2)
.field("待办任务", "5 个", true)
.field("进行中", "3 个", true)
.field("已完成", "12 个", true)
.thumbnail("https://example.com/task-icon.png");
// 创建键盘
let keyboard = MessageKeyboard::new()
.add_row(KeyboardRow::new()
.add_button(KeyboardButton::new("📝 新建任务", "create_task"))
.add_button(KeyboardButton::new("📋 查看任务", "view_tasks"))
)
.add_row(KeyboardRow::new()
.add_button(KeyboardButton::new("✅ 完成任务", "complete_task"))
.add_button(KeyboardButton::new("🗑️ 删除任务", "delete_task"))
)
.add_row(KeyboardRow::new()
.add_button(KeyboardButton::new("📈 统计报告", "task_stats"))
.add_button(KeyboardButton::new("⚙️ 设置提醒", "set_reminder"))
);
let params = MessageParams::new_embed(embed)
.with_keyboard(keyboard);
ctx.api.post_message_with_params(&ctx.token, channel_id, params).await?;
Ok(())
}
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
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
实际应用示例
天气信息卡片
rust
async fn send_weather_card(
ctx: &Context,
channel_id: &str,
city: &str,
weather_data: &WeatherData
) -> Result<(), botrs::BotError> {
let weather_emoji = match weather_data.condition.as_str() {
"sunny" => "☀️",
"cloudy" => "☁️",
"rainy" => "🌧️",
"snowy" => "❄️",
_ => "🌤️",
};
let embed = Embed::new()
.title(&format!("{} {} 天气", weather_emoji, city))
.description(&format!("当前天气: {}", weather_data.description))
.color(match weather_data.condition.as_str() {
"sunny" => 0xffd700,
"cloudy" => 0x808080,
"rainy" => 0x4169e1,
"snowy" => 0xe6e6fa,
_ => 0x87ceeb,
})
.field("🌡️ 温度", &format!("{}°C", weather_data.temperature), true)
.field("💧 湿度", &format!("{}%", weather_data.humidity), true)
.field("💨 风速", &format!("{} km/h", weather_data.wind_speed), true)
.field("👁️ 能见度", &format!("{} km", weather_data.visibility), true)
.field("🌅 日出", &weather_data.sunrise, true)
.field("🌇 日落", &weather_data.sunset, true)
.thumbnail(&weather_data.icon_url)
.footer("数据更新时间", None)
.timestamp(chrono::Utc::now());
let keyboard = MessageKeyboard::new()
.add_row(KeyboardRow::new()
.add_button(KeyboardButton::new("🔄 刷新", "refresh_weather"))
.add_button(KeyboardButton::new("📅 7天预报", "week_forecast"))
.add_button(KeyboardButton::new("🏙️ 切换城市", "change_city"))
);
let params = MessageParams::new_embed(embed)
.with_keyboard(keyboard);
ctx.api.post_message_with_params(&ctx.token, channel_id, params).await?;
Ok(())
}
struct WeatherData {
condition: String,
description: String,
temperature: i32,
humidity: u32,
wind_speed: u32,
visibility: u32,
sunrise: String,
sunset: String,
icon_url: String,
}
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
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
投票系统
rust
async fn send_poll_message(
ctx: &Context,
channel_id: &str,
question: &str,
options: &[String]
) -> Result<(), botrs::BotError> {
let embed = Embed::new()
.title("📊 投票")
.description(question)
.color(0x9b59b6)
.field("参与方式", "点击下方按钮进行投票", false)
.footer("投票将在24小时后截止", None);
let mut keyboard = MessageKeyboard::new();
let mut current_row = KeyboardRow::new();
for (index, option) in options.iter().enumerate() {
let emoji = match index {
0 => "🅰️",
1 => "🅱️",
2 => "🅲️",
3 => "🅳️",
_ => "▫️",
};
current_row = current_row.add_button(
KeyboardButton::new(
&format!("{} {}", emoji, option),
&format!("vote_{}", index)
)
);
// 每行最多2个按钮
if current_row.buttons.len() >= 2 || index == options.len() - 1 {
keyboard = keyboard.add_row(current_row);
current_row = KeyboardRow::new();
}
}
// 添加结果查看按钮
keyboard = keyboard.add_row(KeyboardRow::new()
.add_button(KeyboardButton::new("📈 查看结果", "poll_results"))
);
let params = MessageParams::new_embed(embed)
.with_keyboard(keyboard);
ctx.api.post_message_with_params(&ctx.token, channel_id, params).await?;
Ok(())
}
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
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
完整事件处理器示例
rust
use botrs::{Context, EventHandler, Message, Ready, Interaction};
use tracing::{info, warn};
struct RichMessageHandler;
#[async_trait::async_trait]
impl EventHandler for RichMessageHandler {
async fn ready(&self, _ctx: Context, ready: Ready) {
info!("富文本消息机器人已就绪: {}", ready.user.username);
}
async fn message_create(&self, ctx: Context, message: Message) {
if message.is_from_bot() {
return;
}
let content = match &message.content {
Some(content) => content.trim(),
None => return,
};
match content {
"!embed" => {
if let Err(e) = send_simple_embed(&ctx, &message.channel_id).await {
warn!("发送嵌入消息失败: {}", e);
}
}
"!weather" => {
let weather_data = WeatherData {
condition: "sunny".to_string(),
description: "晴朗".to_string(),
temperature: 25,
humidity: 60,
wind_speed: 10,
visibility: 15,
sunrise: "06:30".to_string(),
sunset: "18:45".to_string(),
icon_url: "https://example.com/sunny.png".to_string(),
};
if let Err(e) = send_weather_card(&ctx, &message.channel_id, "北京", &weather_data).await {
warn!("发送天气卡片失败: {}", e);
}
}
"!poll" => {
let options = vec![
"选项 A".to_string(),
"选项 B".to_string(),
"选项 C".to_string(),
];
if let Err(e) = send_poll_message(
&ctx,
&message.channel_id,
"你最喜欢哪种编程语言?",
&options
).await {
warn!("发送投票消息失败: {}", e);
}
}
"!markdown" => {
if let Err(e) = send_markdown_message(&ctx, &message.channel_id).await {
warn!("发送 Markdown 消息失败: {}", e);
}
}
"!buttons" => {
if let Err(e) = send_button_message(&ctx, &message.channel_id).await {
warn!("发送按钮消息失败: {}", e);
}
}
_ => {}
}
}
async fn interaction_create(&self, ctx: Context, interaction: Interaction) {
// 处理按钮点击等交互事件
if let Some(data) = &interaction.data {
match data.custom_id.as_str() {
"like_button" => {
// 处理点赞
info!("用户点击了点赞按钮");
}
"vote_0" | "vote_1" | "vote_2" | "vote_3" => {
// 处理投票
info!("用户进行了投票: {}", data.custom_id);
}
"refresh_weather" => {
// 刷新天气信息
info!("用户请求刷新天气");
}
_ => {}
}
}
}
}
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
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
最佳实践
设计原则
- 清晰简洁: 信息层次分明,避免过度装饰
- 用户友好: 按钮文字明确,操作逻辑清晰
- 响应及时: 交互操作要有即时反馈
- 适配主题: 颜色搭配符合频道主题
性能考虑
- 图片优化: 使用适当大小的图片,避免过大文件
- 内容长度: 控制嵌入内容的字段数量和长度
- 交互限制: 合理设置按钮数量,避免界面拥挤
- 缓存利用: 对静态内容进行适当缓存
错误处理
rust
async fn safe_send_rich_message<F, Fut>(
operation_name: &str,
operation: F
) -> Result<(), botrs::BotError>
where
F: FnOnce() -> Fut,
Fut: std::future::Future<Output = Result<(), botrs::BotError>>,
{
match operation().await {
Ok(_) => {
info!("{} 发送成功", operation_name);
Ok(())
}
Err(e) => {
warn!("{} 发送失败: {}", operation_name, e);
Err(e)
}
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
富文本消息让机器人能够提供更加丰富和交互性的用户体验。通过合理使用不同的消息类型,您可以创建出功能强大且用户友好的机器人应用程序。