use anyhow::Result;
use runbot::prelude::{send_message::SendMessageAsyncResponse, *};
use std::{sync::Arc, time::Duration};
#[tokio::main]
async fn main() {
tracing_subscriber::fmt()
.with_max_level(tracing::Level::DEBUG)
.init();
let bot_ctx = BotContextBuilder::new()
.url("ws://localhost:3001")
.add_processor(DEMO_MESSAGE_PROCESSOR_FN)
.add_processor(DEMO_NOTICE_PROCESSOR_FN)
.add_processor(DEMO_AUTO_APPROVE_FN)
.add_processor(DEMO_COMMAND_BAN)
.add_processor(EXAMPLE_MOD)
.add_processor(DEMO_MESSAGE_PROCESSOR_FN_MORE)
.build()
.unwrap();
loop_client(bot_ctx).await.unwrap();
}
#[processor]
pub async fn demo_message_processor_fn(
bot_ctx: Arc<BotContext>,
message: &Message,
) -> Result<bool> {
if message.raw_message.eq("hello") {
if let MessageSubType::Friend = message.sub_type {
let async_response = bot_ctx
.send_private_message(message.user_id, "world".to_string())
.await?;
let bot_ctx = bot_ctx.clone();
tokio::spawn(async move {
let msg_id = async_response.wait_response().await.unwrap().message_id;
tokio::time::sleep(Duration::from_secs(10)).await;
bot_ctx.delete_msg(msg_id).await.unwrap();
});
return Ok(true);
}
}
Ok(false)
}
#[processor]
pub async fn demo_notice_processor_fn(bot_ctx: Arc<BotContext>, notice: &Notice) -> Result<bool> {
match notice {
Notice::FriendRecall(friend_recall) => {
bot_ctx
.send_private_message(
friend_recall.user_id,
format!("{} 撤回了一条消息", friend_recall.user_id),
)
.await?;
return Ok(true);
}
_ => {}
}
Ok(false)
}
#[processor]
pub async fn demo_auto_approve_fn(bot_ctx: Arc<BotContext>, request: &Request) -> Result<bool> {
match request {
Request::Friend(friend_request) => {
bot_ctx
.set_friend_add_request(friend_request.flag.as_str(), true, None)
.await?;
return Ok(true);
}
_ => {}
}
Ok(false)
}
#[processor(command = "[-|/|~]ban {time:n}[unit:s|m|h]? {user:n}+")]
pub async fn demo_command_ban(
bot_ctx: Arc<BotContext>,
message: &Message,
time: i64,
unit: Option<String>,
user: Vec<i64>,
) -> Result<bool> {
let unit = match unit {
Some(unit) => match unit.as_str() {
"s" => 1,
"m" => 60,
"h" => 3600,
_ => unreachable!(),
},
None => 1,
};
let time = time * unit;
let msg = format!("禁用用户 {:?} {time}秒", user);
match message.message_type {
MessageType::Group => {
bot_ctx.send_group_message(message.group_id, msg).await?;
}
MessageType::Private => {
bot_ctx.send_private_message(message.user_id, msg).await?;
}
_ => {}
}
Ok(true)
}
#[cfg(test)]
mod tests {
use super::*;
#[tokio::test]
async fn test_demo_command_ban() {
let bot_ctx = BotContextBuilder::new().build().unwrap();
let message = Message {
message_type: MessageType::Group,
message: vec![MessageData::Text(MessageText {
text: "-ban 10 1234567890".to_string(),
})],
..Default::default()
};
let result = DemoCommandBan
.process_message(bot_ctx, &message)
.await
.unwrap();
assert!(result);
}
}
#[module(
name = "ExampleMod",
help = "help()",
processors = "mod_process_a+mod_process_b_instance()"
)]
impl Module for ExampleMod {}
fn help() -> &'static str {
"我是帮助"
}
#[processor]
async fn mod_process_a(_bot_ctx: Arc<BotContext>, _messgae: &Message) -> Result<bool> {
Ok(false)
}
#[processor]
async fn mod_process_b(_bot_ctx: Arc<BotContext>, _messgae: &Message) -> Result<bool> {
Ok(false)
}
fn mod_process_b_instance() -> Processor {
MOD_PROCESS_B.into()
}
#[processor]
pub async fn demo_message_processor_fn_more(
bot_ctx: Arc<BotContext>,
message: &Message,
) -> Result<bool> {
if message.raw_message.eq("create dir") {
if let MessageType::Group = message.message_type {
let data = bot_ctx
.create_group_file_folder(message.group_id, "new folder")
.await?
.wait(Duration::from_secs(10))
.await?;
tracing::error!("create dir data: {:?}", data);
return Ok(true);
}
}
if let Some(MessageData::Json(json)) = message.message.first() {
if let Ok(json) = serde_json::from_str::<serde_json::Value>(json.data.as_str()) {
if let Some(meta) = json.get("meta") {
if let Some(detail_1) = meta.get("detail_1") {
if let Some(qqdocurl) = detail_1.get("qqdocurl") {
if let Some(qqdocurl) = qqdocurl.as_str() {
if qqdocurl.contains("/b23.tv/") {
message
.reply(
bot_ctx,
format!("你发送了一个b23.tv的链接 : {qqdocurl}"),
)
.await?;
return Ok(true);
}
}
}
}
}
}
}
if message.raw_message.eq("list files") {
if let MessageType::Group = message.message_type {
let data = bot_ctx.get_group_root_files(message.group_id).await?;
let mut reply = String::new();
for folder in data.folders {
reply.push_str(&format!(
"文件夹: {} id: {}\n",
folder.folder_name, folder.folder_id
));
}
for file in data.files {
reply.push_str(&format!("文件: {} id: {}\n", file.file_name, file.file_id));
}
if reply.is_empty() {
reply.push_str("没有文件");
}
message.reply(bot_ctx, reply).await?;
return Ok(true);
}
}
if message.raw_message.eq("set sign") {
if let MessageType::Group = message.message_type {
bot_ctx.set_group_sign(message.group_id).await?;
return Ok(true);
}
}
if message.raw_message.eq("get file url") {
if let MessageType::Group = message.message_type {
let files = bot_ctx.get_group_root_files(message.group_id).await?.files;
if files.is_empty() {
message.reply(bot_ctx, "没有文件").await?;
return Ok(true);
}
let data = bot_ctx
.get_group_file_url(message.group_id, files[0].file_id.as_str())
.await?;
message
.reply(bot_ctx, format!("文件链接: {}", data.url))
.await?;
return Ok(true);
}
}
Ok(false)
}
#[async_trait::async_trait]
pub trait Reply {
async fn reply(
&self,
bot_ctx: Arc<BotContext>,
chain: impl SendMessage,
) -> Result<SendMessageAsyncResponse>;
}
#[async_trait::async_trait]
impl Reply for Message {
async fn reply(
&self,
bot_ctx: Arc<BotContext>,
chain: impl SendMessage,
) -> Result<SendMessageAsyncResponse> {
match self.message_type {
MessageType::Private => Ok(bot_ctx.send_private_message(self.user_id, chain).await?),
MessageType::Group => {
let mut data = vec![];
let mut chain = chain.chain();
data.push(MessageData::Reply(MessageReply {
id: self.message_id,
}));
data.push(MessageData::At(MessageAt {
qq: self.sender.user_id.to_string(),
name: self.sender.nickname.clone(),
}));
data.append(&mut chain);
Ok(bot_ctx.send_group_message(self.group_id, data).await?)
}
_ => {
return Err(anyhow::anyhow!("不支持的消息类型"));
}
}
}
}