Skip to main content

babyrite/
event.rs

1//! Event handling module for Discord events.
2//!
3//! This module implements the serenity [`EventHandler`] trait to handle
4//! Discord gateway events such as ready and message events.
5
6use crate::config::BabyriteConfig;
7use crate::expand::ExpandedContent;
8use crate::expand::discord::MessageLinkIDs;
9use crate::expand::github::GitHubPermalink;
10use serenity::all::{ActivityData, Context, EventHandler, Message, Ready};
11use serenity::prelude::TypeMapKey;
12use serenity_builder::model::message::{SerenityMessage, SerenityMessageMentionType};
13
14/// TypeMap key for the shared reqwest HTTP client.
15pub struct HttpClient;
16
17impl TypeMapKey for HttpClient {
18    type Value = reqwest::Client;
19}
20
21/// Event handler for Babyrite bot.
22pub struct BabyriteEventHandler;
23
24#[serenity::async_trait]
25impl EventHandler for BabyriteEventHandler {
26    async fn ready(&self, ctx: Context, bot: Ready) {
27        let version = format!("v{}", env!("CARGO_PKG_VERSION"));
28        ctx.set_activity(ActivityData::custom(format!("Running {}", version)).into());
29        tracing::info!("Running {}, {} is connected!", version, bot.user.name);
30    }
31
32    async fn message(&self, ctx: Context, request: Message) {
33        if request.author.bot {
34            return;
35        }
36
37        let request_guild_id = match request.guild_id {
38            Some(id) => id,
39            None => return,
40        };
41
42        let text = &request.content;
43        let config = BabyriteConfig::get();
44        let mut results = Vec::new();
45
46        // Discord link expansion
47        for ids in MessageLinkIDs::parse_all(text) {
48            if ids.guild_id != request_guild_id {
49                continue;
50            }
51
52            tracing::info!(
53                "Begin generating the preview. (Requester: {})",
54                &request.author.name
55            );
56
57            match ids.fetch(&ctx).await {
58                Ok(content) => results.push(content),
59                Err(e) => tracing::error!("{}", e),
60            }
61        }
62
63        // GitHub Permalink expansion (can be disabled via config)
64        if config.features.github_permalink {
65            let permalinks = GitHubPermalink::parse_all(text);
66            if !permalinks.is_empty() {
67                let data = ctx.data.read().await;
68                if let Some(http_client) = data.get::<HttpClient>() {
69                    for permalink in permalinks {
70                        tracing::info!(
71                            "Begin expanding GitHub permalink. (Requester: {})",
72                            &request.author.name
73                        );
74
75                        match permalink.fetch(http_client).await {
76                            Ok(content) => results.push(content),
77                            Err(e) => tracing::error!("{}", e),
78                        }
79                    }
80                } else {
81                    tracing::error!("HTTP client not found in TypeMap");
82                }
83            }
84        }
85
86        if results.is_empty() {
87            return;
88        }
89
90        send_expanded_contents(&ctx, &request, results).await;
91    }
92}
93
94/// Sends expanded contents as a reply to the original message.
95async fn send_expanded_contents(ctx: &Context, request: &Message, results: Vec<ExpandedContent>) {
96    let mut embeds = Vec::new();
97    let mut code_blocks = Vec::new();
98
99    for result in results {
100        match result {
101            ExpandedContent::Embed(embed) => embeds.push(*embed),
102            ExpandedContent::CodeBlock {
103                language,
104                code,
105                metadata,
106            } => {
107                code_blocks.push(format!("{metadata}\n```{language}\n{code}\n```"));
108            }
109        }
110    }
111
112    // Send embeds if any
113    if !embeds.is_empty() {
114        let message_builder = SerenityMessage::builder()
115            .embeds(embeds)
116            .mention_type(SerenityMessageMentionType::Reply(Box::new(request.clone())))
117            .build();
118
119        let converted_message = match message_builder.convert() {
120            Ok(m) => m,
121            Err(e) => {
122                tracing::error!(?e);
123                return;
124            }
125        };
126
127        if let Err(e) = request
128            .channel_id
129            .send_message(&ctx.http, converted_message)
130            .await
131        {
132            tracing::error!("Failed to send preview: {:?}", e);
133            return;
134        }
135    }
136
137    // Send code blocks as plain messages
138    for block in code_blocks {
139        if let Err(e) = request.channel_id.say(&ctx.http, &block).await {
140            tracing::error!("Failed to send code block: {:?}", e);
141        }
142    }
143
144    tracing::info!("Preview sent successfully.");
145}