use botrs::{
Client, EventHandler, Intents, Token,
models::message::{
C2CMessageParams, DirectMessageParams, Embed, EmbedField, GroupMessageParams,
MarkdownPayload, MessageParams,
},
};
use tracing::{info, warn};
mod common;
use common::{Config, init_logging};
use std::env;
struct NewApiDemoHandler;
#[async_trait::async_trait]
impl EventHandler for NewApiDemoHandler {
async fn message_create(&self, ctx: botrs::Context, message: botrs::Message) {
let content = match &message.content {
Some(content) => content.trim(),
None => return,
};
if !content.starts_with("/demo") {
return;
}
let channel_id = match &message.channel_id {
Some(id) => id,
None => {
warn!("Received message without channel_id");
return;
}
};
let parts: Vec<&str> = content.split_whitespace().collect();
if parts.len() < 2 {
self.send_help_message(&ctx, channel_id).await;
return;
}
match parts[1] {
"text" => self.demo_text_message(&ctx, channel_id).await,
"embed" => self.demo_embed_message(&ctx, channel_id).await,
"reply" => self.demo_reply_message(&ctx, channel_id, &message.id).await,
"markdown" => self.demo_markdown_message(&ctx, channel_id).await,
"file" => self.demo_file_message(&ctx, channel_id).await,
_ => self.send_help_message(&ctx, channel_id).await,
}
}
async fn group_message_create(&self, ctx: botrs::Context, message: botrs::GroupMessage) {
let content = match &message.content {
Some(content) => content.trim(),
None => return,
};
if content == "/demo group" {
if let Some(group_openid) = &message.group_openid {
self.demo_group_message(&ctx, group_openid).await;
}
}
}
async fn c2c_message_create(&self, ctx: botrs::Context, message: botrs::C2CMessage) {
let content = match &message.content {
Some(content) => content.trim(),
None => return,
};
if content == "/demo c2c" {
self.demo_c2c_message(&ctx, &message).await;
}
}
async fn direct_message_create(&self, ctx: botrs::Context, message: botrs::DirectMessage) {
let content = match &message.content {
Some(content) => content.trim(),
None => return,
};
if content == "/demo dm" {
self.demo_direct_message(&ctx, &message.guild_id).await;
}
}
}
impl NewApiDemoHandler {
async fn send_help_message(&self, ctx: &botrs::Context, channel_id: &str) {
let help_text = r#"**New Message API Demo Commands:**
• `/demo text` - Send a simple text message
• `/demo embed` - Send a message with embed
• `/demo reply` - Reply to your message
• `/demo markdown` - Send a markdown message
• `/demo file` - Send a message with file attachment
**For other message types:**
• `/demo group` - In group chats
• `/demo c2c` - In C2C chats
• `/demo dm` - In direct messages"#;
let params = MessageParams::new_text(help_text);
match ctx
.api
.post_message_with_params(&ctx.token, channel_id, params)
.await
{
Ok(_) => info!("Sent help message"),
Err(e) => warn!("Failed to send help message: {}", e),
}
}
async fn demo_text_message(&self, ctx: &botrs::Context, channel_id: &str) {
let params = MessageParams::new_text("🚀 This is a simple text message using the new API!");
match ctx
.api
.post_message_with_params(&ctx.token, channel_id, params)
.await
{
Ok(_) => info!("Sent text message using new API"),
Err(e) => warn!("Failed to send text message: {}", e),
}
}
async fn demo_embed_message(&self, ctx: &botrs::Context, channel_id: &str) {
let embed = Embed {
title: Some("New Message API Demo".to_string()),
description: Some("This embed was sent using the new MessageParams API!".to_string()),
color: Some(0x00ff00), fields: Some(vec![
EmbedField {
name: Some("Feature".to_string()),
value: Some("Cleaner API".to_string()),
inline: Some(true),
},
EmbedField {
name: Some("Benefit".to_string()),
value: Some("Less None parameters".to_string()),
inline: Some(true),
},
]),
..Default::default()
};
let params = MessageParams {
content: Some("Check out this embed! 📊".to_string()),
embed: Some(embed),
..Default::default()
};
match ctx
.api
.post_message_with_params(&ctx.token, channel_id, params)
.await
{
Ok(_) => info!("Sent embed message using new API"),
Err(e) => warn!("Failed to send embed message: {}", e),
}
}
async fn demo_reply_message(
&self,
ctx: &botrs::Context,
channel_id: &str,
message_id: &Option<String>,
) {
if let Some(msg_id) = message_id {
let params =
MessageParams::new_text("This is a reply using the new API! 💬").with_reply(msg_id);
match ctx
.api
.post_message_with_params(&ctx.token, channel_id, params)
.await
{
Ok(_) => info!("Sent reply message using new API"),
Err(e) => warn!("Failed to send reply message: {}", e),
}
}
}
async fn demo_markdown_message(&self, ctx: &botrs::Context, channel_id: &str) {
let markdown = MarkdownPayload {
content: Some(
"# Markdown Message\n\nThis message uses **markdown** formatting with the new API!"
.to_string(),
),
..Default::default()
};
let params = MessageParams {
markdown: Some(markdown),
..Default::default()
};
match ctx
.api
.post_message_with_params(&ctx.token, channel_id, params)
.await
{
Ok(_) => info!("Sent markdown message using new API"),
Err(e) => warn!("Failed to send markdown message: {}", e),
}
}
async fn demo_file_message(&self, ctx: &botrs::Context, channel_id: &str) {
let png_data = vec![
0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A, 0x00, 0x00, 0x00, 0x0D, 0x49, 0x48,
0x44, 0x52, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x08, 0x02, 0x00, 0x00,
0x00, 0x90, 0x77, 0x53, 0xDE, 0x00, 0x00, 0x00, 0x0C, 0x49, 0x44, 0x41, 0x54, 0x08,
0xD7, 0x63, 0xF8, 0x0F, 0x00, 0x00, 0x01, 0x00, 0x01, 0x5C, 0xCD, 0x90, 0x0C, 0x00,
0x00, 0x00, 0x00, 0x49, 0x45, 0x4E, 0x44, 0xAE, 0x42, 0x60, 0x82,
];
let params = MessageParams::new_text("Here's a file sent with the new API! 📎")
.with_file_image(&png_data);
match ctx
.api
.post_message_with_params(&ctx.token, channel_id, params)
.await
{
Ok(_) => info!("Sent file message using new API"),
Err(e) => warn!("Failed to send file message: {}", e),
}
}
async fn demo_group_message(&self, ctx: &botrs::Context, group_openid: &str) {
let params = GroupMessageParams::new_text("Hello from the new Group Message API! 👥");
match ctx
.api
.post_group_message_with_params(&ctx.token, group_openid, params)
.await
{
Ok(_) => info!("Sent group message using new API"),
Err(e) => warn!("Failed to send group message: {}", e),
}
}
async fn demo_c2c_message(&self, ctx: &botrs::Context, message: &botrs::C2CMessage) {
if let Some(user_openid) = message.author.as_ref().and_then(|a| a.user_openid.as_ref()) {
let params = C2CMessageParams::new_text("Hello from the new C2C Message API! 💬");
match ctx
.api
.post_c2c_message_with_params(&ctx.token, user_openid, params)
.await
{
Ok(_) => info!("Sent C2C message using new API"),
Err(e) => warn!("Failed to send C2C message: {}", e),
}
}
}
async fn demo_direct_message(&self, ctx: &botrs::Context, guild_id: &Option<String>) {
if let Some(guild_id) = guild_id {
let params = DirectMessageParams::new_text("Hello from the new Direct Message API! 📧");
match ctx
.api
.post_dms_with_params(&ctx.token, guild_id, params)
.await
{
Ok(_) => info!("Sent direct message using new API"),
Err(e) => warn!("Failed to send direct message: {}", e),
}
}
}
}
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
init_logging();
info!("Starting AT reply markdown demo...");
let config = Config::load_with_fallback(
Some("examples/config.toml"),
env::args().nth(1), env::args().nth(2), )?;
info!("Configuration loaded successfully");
let token = Token::new(config.bot.app_id, config.bot.secret);
if let Err(e) = token.validate() {
panic!("Invalid token: {e}");
}
info!("Token validated successfully");
let intents = Intents::default().with_public_guild_messages();
let mut client = Client::new(token, intents, NewApiDemoHandler, true)?;
info!("🤖 New Message API Demo Bot is starting...");
info!("💡 Try sending '/demo text' in a channel to see the new API in action!");
info!("📚 Use '/demo' to see all available commands");
client.start().await?;
Ok(())
}