The world's most clever kitty cat
1use std::{
2 boxed::Box,
3 sync::{Arc, atomic::Ordering},
4};
5
6use log::warn;
7use twilight_model::{
8 channel::message::{AllowedMentions, MessageFlags, MessageType},
9 gateway::payload::incoming::MessageCreate,
10 id::{
11 Id,
12 marker::{ChannelMarker, MessageMarker},
13 },
14};
15
16use crate::{BotContext, prelude::*, status::update_status};
17
18async fn learn_message(msg: &str, ctx: Arc<BotContext>) -> Result {
19 let mut brain = ctx.brain_handle.write().await;
20 let learned_new_word = brain.ingest(msg);
21 ctx.pending_save.store(true, Ordering::Relaxed);
22
23 if learned_new_word {
24 update_status(&brain, &ctx.shard_sender).context("Failed to update status")?;
25 }
26
27 Ok(())
28}
29
30async fn reply_message(
31 msg: &str,
32 msg_id: Id<MessageMarker>,
33 channel_id: Id<ChannelMarker>,
34 is_self: bool,
35 ctx: &Arc<BotContext>,
36) -> Result {
37 let (typ_tx, typ_rx) = tokio::sync::oneshot::channel();
38 let (done_tx, done_rx) = tokio::sync::oneshot::channel();
39
40 let ctx_typ = ctx.clone();
41 let typ_id = channel_id;
42 tokio::spawn(async move {
43 if typ_rx.await.ok().is_some_and(|start| start)
44 && let Err(why) = ctx_typ.http.create_typing_trigger(typ_id).await
45 {
46 warn!("Failed to set typing indicator:\n{why:?}");
47 }
48 done_tx.send(()).ok();
49 });
50
51 let brain = ctx.brain_handle.read().await;
52 if let Some(reply_text) = brain.respond(msg, is_self, false, Some(typ_tx)) {
53 drop(brain);
54 done_rx.await.ok();
55 let allowed_mentions = AllowedMentions::default();
56 let my_msg = ctx
57 .http
58 .create_message(channel_id)
59 .content(&reply_text)
60 .allowed_mentions(Some(&allowed_mentions));
61
62 let my_msg = if !is_self {
63 my_msg.reply(msg_id).fail_if_not_exists(false)
64 } else {
65 my_msg
66 };
67
68 my_msg.await.context("Failed to send message")?;
69 }
70
71 Ok(())
72}
73
74pub async fn handle_discord_message(msg: Box<MessageCreate>, ctx: Arc<BotContext>) -> Result {
75 let is_self = msg.author.id == ctx.self_id;
76 let is_normal_message = matches!(msg.kind, MessageType::Regular | MessageType::Reply);
77 let is_ephemeral = msg
78 .flags
79 .is_some_and(|flags| flags.contains(MessageFlags::EPHEMERAL));
80 let is_dm = msg.guild_id.is_none();
81
82 // Should we consider this message at all?
83 if !is_normal_message || is_ephemeral || is_dm {
84 return Ok(());
85 }
86
87 // Should Reply to Message?
88 if ctx.reply_channels.contains(&msg.channel_id) {
89 reply_message(&msg.content, msg.id, msg.channel_id, is_self, &ctx)
90 .await
91 .context("Bingus failed to reply to a message")?;
92 }
93
94 // Should we learn from this message? (We don't want to learn from ourselves)
95 if !is_self {
96 learn_message(&msg.content, ctx)
97 .await
98 .context("Bingus failed to learn from a message")?;
99 }
100
101 Ok(())
102}