1use crate::config::BabyriteConfig;
7use crate::expand::{EXPANDERS, ExpandContext, ExpandedContent};
8use serenity::all::{ActivityData, Context, EventHandler, Message, Ready};
9use serenity::futures::future::join_all;
10use serenity_builder::model::message::{SerenityMessage, SerenityMessageMentionType};
11use tracing::Instrument;
12
13pub struct BabyriteEventHandler;
15
16#[serenity::async_trait]
17impl EventHandler for BabyriteEventHandler {
18 async fn ready(&self, ctx: Context, bot: Ready) {
19 let version = format!("v{}", env!("CARGO_PKG_VERSION"));
20 ctx.set_activity(ActivityData::custom(format!("Running {}", version)).into());
21 tracing::info!("Running {}, {} is connected!", version, bot.user.name);
22 }
23
24 async fn message(&self, ctx: Context, request: Message) {
25 if request.author.bot {
26 return;
27 }
28
29 let Some(request_guild_id) = request.guild_id else {
30 return;
31 };
32
33 let span = tracing::info_span!(
38 "message",
39 message_id = %request.id,
40 guild_id = %request_guild_id,
41 channel_id = %request.channel_id,
42 author = %request.author.name,
43 );
44
45 async {
46 let text = &request.content;
47 let config = BabyriteConfig::get();
48
49 let mut unknown_command = None;
57 if config.features.commands {
58 let bot_id = ctx.cache.current_user().id;
59 match crate::command::parse(text, bot_id) {
60 Some(crate::command::Command::Unknown(word)) => unknown_command = Some(word),
61 Some(command) => {
62 dispatch_command(&ctx, &request, command).await;
63 return;
64 }
65 None => {}
66 }
67 }
68
69 let cx = ExpandContext {
72 ctx: &ctx,
73 message: &request,
74 guild_id: request_guild_id,
75 };
76 let results: Vec<ExpandedContent> = join_all(
77 EXPANDERS
78 .iter()
79 .filter(|expander| expander.enabled(config))
80 .map(|expander| expander.expand_all(&cx)),
81 )
82 .await
83 .into_iter()
84 .flatten()
85 .collect();
86
87 if results.is_empty() {
88 if let Some(word) = unknown_command {
89 dispatch_command(&ctx, &request, crate::command::Command::Unknown(word)).await;
90 } else {
91 tracing::debug!("no expandable content found");
92 }
93 return;
94 }
95
96 send_expanded_contents(&ctx, &request, results).await;
97 }
98 .instrument(span)
99 .await;
100 }
101}
102
103async fn dispatch_command(ctx: &Context, request: &Message, command: crate::command::Command) {
105 tracing::debug!(?command, "handling mention command");
106 crate::command::execute(ctx, request, command).await;
107}
108
109async fn send_expanded_contents(ctx: &Context, request: &Message, results: Vec<ExpandedContent>) {
111 let mut embeds = Vec::new();
112 let mut code_blocks = Vec::new();
113
114 for result in results {
115 match result {
116 ExpandedContent::Embed(embed) => embeds.push(*embed),
117 ExpandedContent::CodeBlock {
118 language,
119 code,
120 metadata,
121 } => {
122 code_blocks.push(format!("{metadata}\n```{language}\n{code}\n```"));
123 }
124 }
125 }
126
127 let embed_count = embeds.len();
128 let code_block_count = code_blocks.len();
129
130 if !embeds.is_empty() {
132 let message_builder = SerenityMessage::builder()
133 .embeds(embeds)
134 .mention_type(SerenityMessageMentionType::Reply(Box::new(request.clone())))
135 .build();
136
137 let converted_message = match message_builder.convert() {
138 Ok(m) => m,
139 Err(e) => {
140 tracing::error!(error = ?e, "failed to convert embed message");
141 return;
142 }
143 };
144
145 if let Err(e) = request
146 .channel_id
147 .send_message(&ctx.http, converted_message)
148 .await
149 {
150 tracing::error!(error = ?e, "failed to send preview");
151 return;
152 }
153 }
154
155 for block in code_blocks {
157 if let Err(e) = request.channel_id.say(&ctx.http, &block).await {
158 tracing::error!(error = ?e, "failed to send code block");
159 }
160 }
161
162 tracing::info!(
163 embeds = embed_count,
164 code_blocks = code_block_count,
165 "preview sent"
166 );
167}