From 2559ef4452ccbedc0c3d8e0c687809d7753fa732 Mon Sep 17 00:00:00 2001 From: Daniel Flanagan Date: Fri, 12 Apr 2024 11:59:46 -0500 Subject: [PATCH] Responding to chat pings --- src/chatbot.rs | 390 ++++++++++++++++++++++++++++--------------------- 1 file changed, 226 insertions(+), 164 deletions(-) diff --git a/src/chatbot.rs b/src/chatbot.rs index 40637a7..bf2bbe7 100644 --- a/src/chatbot.rs +++ b/src/chatbot.rs @@ -6,9 +6,229 @@ use std::{any::Any, future}; use discord::{ - model::{Event, Message}, - Discord, + model::{ChannelId, Event, Message, ReadyEvent}, + Discord as DDiscord, }; +use tokio::sync::Mutex; + +const COMMAND_PREFIX: &str = "!"; + +struct Discord { + discord: DDiscord, + connection: Option>>, +} + +impl Discord { + pub fn try_new(conf: Arc) -> Result { + let discord = DDiscord::from_bot_token( + conf.discord + .as_ref() + .unwrap() + .bot_token + .clone() + .expose_secret(), + )?; + Ok(Self { + discord, + connection: None, + }) + } + + pub async fn connect(&mut self) -> Result { + if self.connection.is_some() { + return Err(anyhow::anyhow!("already connected")); + } + let (connection, ready_ev) = self.discord.connect()?; + self.connection = Some(Arc::new(Mutex::new(connection))); + Ok(ready_ev) + } + + pub async fn handle_commands(self: Arc) -> Result<()> { + if let Some(conn) = self.connection.clone() { + loop { + // TODO: I'm suspicious this will just always be locked here and + // I'm doing something stupid. + let mut conn = conn.lock().await; + while let Ok(event) = conn.recv_event() { + let rself = self.clone(); + tokio::spawn(async move { + match rself.handle_event(event).await { + Ok(_) => {} + Err(err) => { + error!("Failed to handle Discord event: {err}") + } + } + }); + } + } + } + + Ok(()) + } + + pub async fn handle_event(&self, event: Event) -> Result<()> { + match event { + Event::Ready(_) => return Ok(self.ignore_event(event)), + Event::Resumed { trace: _ } => return Ok(self.ignore_event(event)), + Event::UserUpdate(_) => return Ok(self.ignore_event(event)), + Event::UserNoteUpdate(_, _) => return Ok(self.ignore_event(event)), + Event::UserSettingsUpdate { + detect_platform_accounts: _, + developer_mode: _, + enable_tts_command: _, + inline_attachment_media: _, + inline_embed_media: _, + locale: _, + message_display_compact: _, + render_embeds: _, + server_positions: _, + show_current_game: _, + status: _, + theme: _, + convert_emoticons: _, + friend_source_flags: _, + } => return Ok(self.ignore_event(event)), + Event::UserServerSettingsUpdate(_) => return Ok(self.ignore_event(event)), + Event::VoiceStateUpdate(_, _) => return Ok(self.ignore_event(event)), + Event::VoiceServerUpdate { + server_id: _, + channel_id: _, + endpoint: _, + token: _, + } => return Ok(self.ignore_event(event)), + Event::CallCreate(_) => return Ok(self.ignore_event(event)), + Event::CallUpdate { + channel_id: _, + message_id: _, + region: _, + ringing: _, + } => return Ok(self.ignore_event(event)), + Event::CallDelete(_) => return Ok(self.ignore_event(event)), + Event::ChannelRecipientAdd(_, _) => return Ok(self.ignore_event(event)), + Event::ChannelRecipientRemove(_, _) => return Ok(self.ignore_event(event)), + Event::TypingStart { + channel_id: _, + user_id: _, + timestamp: _, + } => return Ok(self.ignore_event(event)), + Event::PresenceUpdate { + presence: _, + server_id: _, + roles: _, + } => return Ok(self.ignore_event(event)), + Event::PresencesReplace(_) => return Ok(self.ignore_event(event)), + Event::RelationshipAdd(_) => return Ok(self.ignore_event(event)), + Event::RelationshipRemove(_, _) => return Ok(self.ignore_event(event)), + Event::MessageCreate(msg) => return Ok(self.handle_message(msg)?), + Event::MessageUpdate { + id: _, + channel_id: _, + kind: _, + content: _, + nonce: _, + tts: _, + pinned: _, + timestamp: _, + edited_timestamp: _, + author: _, + mention_everyone: _, + mentions: _, + mention_roles: _, + attachments: _, + embeds: _, + } => return Ok(self.ignore_event(event)), + Event::MessageAck { + channel_id: _, + message_id: _, + } => return Ok(self.ignore_event(event)), + Event::MessageDelete { + channel_id: _, + message_id: _, + } => return Ok(self.ignore_event(event)), + Event::MessageDeleteBulk { + channel_id: _, + ids: _, + } => return Ok(self.ignore_event(event)), + Event::ServerCreate(_) => return Ok(self.ignore_event(event)), + Event::ServerUpdate(_) => return Ok(self.ignore_event(event)), + Event::ServerDelete(_) => return Ok(self.ignore_event(event)), + Event::ServerMemberAdd(_, _) => return Ok(self.ignore_event(event)), + Event::ServerMemberUpdate { + server_id: _, + roles: _, + user: _, + nick: _, + } => return Ok(self.ignore_event(event)), + Event::ServerMemberRemove(_, _) => return Ok(self.ignore_event(event)), + Event::ServerMembersChunk(_, _) => return Ok(self.ignore_event(event)), + Event::ServerSync { + server_id: _, + large: _, + members: _, + presences: _, + } => return Ok(self.ignore_event(event)), + Event::ServerRoleCreate(_, _) => return Ok(self.ignore_event(event)), + Event::ServerRoleUpdate(_, _) => return Ok(self.ignore_event(event)), + Event::ServerRoleDelete(_, _) => return Ok(self.ignore_event(event)), + Event::ServerBanAdd(_, _) => return Ok(self.ignore_event(event)), + Event::ServerBanRemove(_, _) => return Ok(self.ignore_event(event)), + Event::ServerIntegrationsUpdate(_) => return Ok(self.ignore_event(event)), + Event::ServerEmojisUpdate(_, _) => return Ok(self.ignore_event(event)), + Event::ChannelCreate(_) => return Ok(self.ignore_event(event)), + Event::ChannelUpdate(_) => return Ok(self.ignore_event(event)), + Event::ChannelDelete(_) => return Ok(self.ignore_event(event)), + Event::ChannelPinsAck { + channel_id: _, + timestamp: _, + } => return Ok(self.ignore_event(event)), + Event::ChannelPinsUpdate { + channel_id: _, + last_pin_timestamp: _, + } => return Ok(self.ignore_event(event)), + Event::ReactionAdd(_) => return Ok(self.ignore_event(event)), + Event::ReactionRemove(_) => return Ok(self.ignore_event(event)), + Event::Unknown(_, _) => return Ok(self.ignore_event(event)), + _ => return Ok(self.ignore_event(event)), + } + } + + pub fn ignore_event(&self, event: Event) -> () { + let event_type_id = event.type_id(); + info!("Ignoring Discord event of type: {event_type_id:?}") + } + + pub fn handle_message(&self, msg: Message) -> Result<()> { + info!( + "Recieved Discord message in channel {} with content: {}", + msg.channel_id, msg.content + ); + + let text = msg.content.clone(); + let trimmed_text = text.trim(); + if trimmed_text.starts_with(COMMAND_PREFIX) { + self.handle_text_command(trimmed_text, msg)? + } + + Ok(()) + } + + fn send_message(&self, channel: ChannelId, text: &str) -> Result { + self.discord + .send_message(channel, text, "", false) + .map_err(|e| e.into()) + } + + fn handle_text_command(&self, trimmed_text: &str, msg: Message) -> Result<()> { + let trimmed_text = trimmed_text.trim_start_matches(COMMAND_PREFIX); + match trimmed_text { + s if s.starts_with("ping") => { + let _ = self.send_message(msg.channel_id, "`pong`")?; + } + _ => {} + } + Ok(()) + } +} use crate::prelude::*; @@ -19,167 +239,9 @@ pub async fn start(conf: Arc) -> Result<()> { future::pending::<()>().await; } - let discord = Discord::from_bot_token( - conf.discord - .as_ref() - .unwrap() - .bot_token - .clone() - .expose_secret(), - )?; - - let (mut conn, ready_ev) = discord.connect()?; + let mut discord = Discord::try_new(conf.clone())?; + let ready_ev = discord.connect().await; info!("Discord connection ready: {ready_ev:?}"); - while let Ok(event) = conn.recv_event() { - tokio::spawn(async move { - match handle_event(event).await { - Ok(_) => {} - Err(err) => { - error!("Failed to handle Discord event: {err}") - } - } - }); - } - - Ok(()) -} - -pub async fn handle_event(event: Event) -> Result<()> { - match event { - Event::Ready(_) => return Ok(ignore_event(event)), - Event::Resumed { trace: _ } => return Ok(ignore_event(event)), - Event::UserUpdate(_) => return Ok(ignore_event(event)), - Event::UserNoteUpdate(_, _) => return Ok(ignore_event(event)), - Event::UserSettingsUpdate { - detect_platform_accounts: _, - developer_mode: _, - enable_tts_command: _, - inline_attachment_media: _, - inline_embed_media: _, - locale: _, - message_display_compact: _, - render_embeds: _, - server_positions: _, - show_current_game: _, - status: _, - theme: _, - convert_emoticons: _, - friend_source_flags: _, - } => return Ok(ignore_event(event)), - Event::UserServerSettingsUpdate(_) => return Ok(ignore_event(event)), - Event::VoiceStateUpdate(_, _) => return Ok(ignore_event(event)), - Event::VoiceServerUpdate { - server_id: _, - channel_id: _, - endpoint: _, - token: _, - } => return Ok(ignore_event(event)), - Event::CallCreate(_) => return Ok(ignore_event(event)), - Event::CallUpdate { - channel_id: _, - message_id: _, - region: _, - ringing: _, - } => return Ok(ignore_event(event)), - Event::CallDelete(_) => return Ok(ignore_event(event)), - Event::ChannelRecipientAdd(_, _) => return Ok(ignore_event(event)), - Event::ChannelRecipientRemove(_, _) => return Ok(ignore_event(event)), - Event::TypingStart { - channel_id: _, - user_id: _, - timestamp: _, - } => return Ok(ignore_event(event)), - Event::PresenceUpdate { - presence: _, - server_id: _, - roles: _, - } => return Ok(ignore_event(event)), - Event::PresencesReplace(_) => return Ok(ignore_event(event)), - Event::RelationshipAdd(_) => return Ok(ignore_event(event)), - Event::RelationshipRemove(_, _) => return Ok(ignore_event(event)), - Event::MessageCreate(msg) => return Ok(handle_message(msg)), - Event::MessageUpdate { - id: _, - channel_id: _, - kind: _, - content: _, - nonce: _, - tts: _, - pinned: _, - timestamp: _, - edited_timestamp: _, - author: _, - mention_everyone: _, - mentions: _, - mention_roles: _, - attachments: _, - embeds: _, - } => return Ok(ignore_event(event)), - Event::MessageAck { - channel_id: _, - message_id: _, - } => return Ok(ignore_event(event)), - Event::MessageDelete { - channel_id: _, - message_id: _, - } => return Ok(ignore_event(event)), - Event::MessageDeleteBulk { - channel_id: _, - ids: _, - } => return Ok(ignore_event(event)), - Event::ServerCreate(_) => return Ok(ignore_event(event)), - Event::ServerUpdate(_) => return Ok(ignore_event(event)), - Event::ServerDelete(_) => return Ok(ignore_event(event)), - Event::ServerMemberAdd(_, _) => return Ok(ignore_event(event)), - Event::ServerMemberUpdate { - server_id: _, - roles: _, - user: _, - nick: _, - } => return Ok(ignore_event(event)), - Event::ServerMemberRemove(_, _) => return Ok(ignore_event(event)), - Event::ServerMembersChunk(_, _) => return Ok(ignore_event(event)), - Event::ServerSync { - server_id: _, - large: _, - members: _, - presences: _, - } => return Ok(ignore_event(event)), - Event::ServerRoleCreate(_, _) => return Ok(ignore_event(event)), - Event::ServerRoleUpdate(_, _) => return Ok(ignore_event(event)), - Event::ServerRoleDelete(_, _) => return Ok(ignore_event(event)), - Event::ServerBanAdd(_, _) => return Ok(ignore_event(event)), - Event::ServerBanRemove(_, _) => return Ok(ignore_event(event)), - Event::ServerIntegrationsUpdate(_) => return Ok(ignore_event(event)), - Event::ServerEmojisUpdate(_, _) => return Ok(ignore_event(event)), - Event::ChannelCreate(_) => return Ok(ignore_event(event)), - Event::ChannelUpdate(_) => return Ok(ignore_event(event)), - Event::ChannelDelete(_) => return Ok(ignore_event(event)), - Event::ChannelPinsAck { - channel_id: _, - timestamp: _, - } => return Ok(ignore_event(event)), - Event::ChannelPinsUpdate { - channel_id: _, - last_pin_timestamp: _, - } => return Ok(ignore_event(event)), - Event::ReactionAdd(_) => return Ok(ignore_event(event)), - Event::ReactionRemove(_) => return Ok(ignore_event(event)), - Event::Unknown(_, _) => return Ok(ignore_event(event)), - _ => return Ok(ignore_event(event)), - } -} - -pub fn ignore_event(event: Event) -> () { - let event_type_id = event.type_id(); - info!("Ignoring Discord event of type: {event_type_id:?}") -} - -pub fn handle_message(msg: Message) -> () { - info!( - "Recieved Discord message in channel {} with content: {}", - msg.channel_id, msg.content - ); - - return (); + let adiscord = Arc::new(discord); + adiscord.handle_commands().await }