From 4fa4907c3da23249ddec2bcb50e48f708152059e Mon Sep 17 00:00:00 2001 From: Ashelyn Rose Date: Sat, 5 Oct 2024 13:11:46 -0600 Subject: Rudimentary message parsing and upgraded time dep for 1.80.0 --- src/system/bot/client.rs | 18 +++++-- src/system/bot/mod.rs | 5 ++ src/system/message_parser.rs | 126 +++++++++++++++++++++++++++++++++++++++++++ src/system/mod.rs | 114 +++++++++++---------------------------- 4 files changed, 176 insertions(+), 87 deletions(-) create mode 100644 src/system/message_parser.rs (limited to 'src/system') diff --git a/src/system/bot/client.rs b/src/system/bot/client.rs index c55759a..006ce8f 100644 --- a/src/system/bot/client.rs +++ b/src/system/bot/client.rs @@ -52,6 +52,8 @@ impl Client { let delete_result = client.delete_message(channel_id, message_id).await; let member_id = self.bot_conf.read().await.member_id; + drop(client); + match delete_result { Err(err) => { match &err.kind() { @@ -60,11 +62,7 @@ impl Client { // Code for "Missing Permissions": https://discord.com/developers/docs/topics/opcodes-and-status-codes#json-json-error-codes if err.code == 50013 { println!("ERROR: Client {} doesn't have permissions to delete message", member_id); - let _ = client.create_reaction( - channel_id, - message_id, - &RequestReactionType::Unicode { name: "🔐" } - ).await; + let _ = self.react_message(channel_id, message_id, &RequestReactionType::Unicode { name: "🔐" }).await; } }, _ => (), @@ -78,6 +76,16 @@ impl Client { } } + pub async fn react_message(&self, channel_id: ChannelId, message_id: MessageId, react: &'_ RequestReactionType<'_>) -> Result<(), TwiError> { + let _ = self.client.lock().await.create_reaction( + channel_id, + message_id, + react + ).await; + + return Ok(()) + } + pub async fn duplicate_message(&self, message: &TwiMessage, content: &str) -> Result { let client = self.client.lock().await; diff --git a/src/system/bot/mod.rs b/src/system/bot/mod.rs index 6bf8d78..3c4585f 100644 --- a/src/system/bot/mod.rs +++ b/src/system/bot/mod.rs @@ -5,6 +5,7 @@ use std::sync::Arc; use tokio::sync::mpsc::Sender; use tokio::sync::RwLock; use twilight_http::error::Error as TwiError; +use twilight_http::request::channel::reaction::RequestReactionType; pub use super::types::*; pub use client::MessageDuplicateError; @@ -73,6 +74,10 @@ impl Bot { self.client.delete_message(channel_id, message_id).await } + pub async fn react_message(&self, channel_id: ChannelId, message_id: MessageId, react: &'_ RequestReactionType<'_>) -> Result<(), TwiError> { + self.client.react_message(channel_id, message_id, react).await + } + pub async fn duplicate_message(&self, message_id: &TwiMessage, content: &str) -> Result { self.client.duplicate_message(message_id, content).await } diff --git a/src/system/message_parser.rs b/src/system/message_parser.rs new file mode 100644 index 0000000..b404064 --- /dev/null +++ b/src/system/message_parser.rs @@ -0,0 +1,126 @@ +use std::sync::LazyLock; +use regex::Regex; + +use crate::config::{self, System}; + +use super::{FullMessage, MemberId, MessageId, Timestamp}; + +pub enum ParsedMessage { + Command(Command), + ProxiedMessage { + member_id: MemberId, + message_content: String, + latch: bool, + }, + UnproxiedMessage, + LatchClear(MemberId), + + // TODO: Figure out how to represent emotes + EmoteAdd(MemberId, MessageId, ()), + EmoteRemove(MemberId, MessageId, ()), +} + +pub enum Command { + Edit(MessageId, String), + Reproxy(MessageId, MemberId), + Nick(MemberId, String), + ReloadSystemConfig, + ExitSéance, + UnknownCommand, +} + +pub struct MessageParser {} + +static CORRECTION_REGEX: LazyLock = LazyLock::new(|| { + Regex::new(r"^\*\B+$").unwrap() +}); + +impl MessageParser { + pub fn parse(message: &FullMessage, secondary_message: Option<&FullMessage>, system_config: &System, latch_state: Option<(MemberId, Timestamp)>) -> ParsedMessage { + if message.content == r"\\" { + return ParsedMessage::LatchClear(if let Some((member_id, _)) = latch_state { + member_id + } else { + 0 + }) + } + + if message.content.starts_with(r"\") { + return ParsedMessage::UnproxiedMessage + } + + if message.content.starts_with(r"!") { + return ParsedMessage::Command( + MessageParser::parse_command(message, secondary_message, system_config, latch_state) + ); + } + + if CORRECTION_REGEX.is_match(message.content.as_str()) { + if let Some(parse) = MessageParser::check_correction(message, secondary_message) { + return parse + } + } + + if let Some(parse) = MessageParser::check_member_patterns(message, system_config) { + return parse + } + + if let Some(parse) = MessageParser::check_autoproxy(message, latch_state) { + return parse + } + + // If nothing else + ParsedMessage::UnproxiedMessage + } + + fn parse_command(message: &FullMessage, secondary_message: Option<&FullMessage>, system_config: &System, latch_state: Option<(MemberId, Timestamp)>) -> Command { + + // If unable to parse + Command::UnknownCommand + } + + fn check_correction(message: &FullMessage, secondary_message: Option<&FullMessage>) -> Option { + None + } + + fn check_member_patterns(message: &FullMessage, system_config: &System) -> Option { + let matches_prefix = system_config.members.iter().enumerate().find_map(|(member_id, member)| + Some((member_id, member.matches_proxy_prefix(&message)?)) + ); + + if let Some((member_id, matched_content)) = matches_prefix { + Some(ParsedMessage::ProxiedMessage { + member_id, + message_content: matched_content.to_string(), + latch: true, + }) + } else { + None + } + } + + fn check_autoproxy(message: &FullMessage, latch_state: Option<(MemberId, Timestamp)>) -> Option { + if let Some((member_id, _)) = latch_state { + Some(ParsedMessage::ProxiedMessage { + member_id, + message_content: message.content.clone(), + latch: true, + }) + } else { + None + } + } +} + +impl crate::config::Member { + pub fn matches_proxy_prefix<'a>(&self, message: &'a FullMessage) -> Option<&'a str> { + match self.message_pattern.captures(message.content.as_str()) { + None => None, + Some(captures) => match captures.name("content") { + None => None, + Some(matched_content) => Some(matched_content.as_str()), + }, + } + } +} + diff --git a/src/system/mod.rs b/src/system/mod.rs index 4cb99ea..3c6064b 100644 --- a/src/system/mod.rs +++ b/src/system/mod.rs @@ -4,7 +4,8 @@ use tokio::{ sync::{mpsc::{channel, Sender}, RwLock}, time::sleep, }; -use twilight_model::id::{marker::UserMarker, Id}; +use twilight_http::request::channel::reaction::RequestReactionType; +use twilight_model::{channel::message::ReactionType, id::{marker::UserMarker, Id}}; use twilight_model::util::Timestamp; use crate::config::{AutoproxyConfig, AutoproxyLatchScope, Member}; @@ -12,10 +13,15 @@ use crate::config::{AutoproxyConfig, AutoproxyLatchScope, Member}; mod aggregator; mod bot; mod types; +mod message_parser; + +use message_parser::MessageParser; use aggregator::MessageAggregator; use bot::Bot; pub use types::*; +use self::message_parser::Command; + pub struct Manager { pub name: String, @@ -155,81 +161,37 @@ impl Manager { } async fn handle_message(&mut self, message: TwiMessage, timestamp: Timestamp) { - // TODO: Commands - if message.content.eq("!panic") { - self.bots.iter_mut().next().unwrap().1.shutdown().await; - } + let parsed_message = MessageParser::parse(&message, None, &self.config, self.latch_state); - // Escape sequence - if message.content.starts_with(r"\") { - if message.content == r"\\" { - let bot = if let Some((current_member, _)) = self.latch_state.clone() { - self.bots - .get(¤t_member) - .expect(format!("No client for member {}", current_member).as_str()) - } else { - self.bots.iter().next().expect("No clients!").1 - }; + match parsed_message { + message_parser::ParsedMessage::UnproxiedMessage => (), - // We don't really care about the outcome here, we don't proxy afterwards - let _ = bot.delete_message(message.channel_id, message.id).await; - self.latch_state = None - } else if message.content.starts_with(r"\\") { + message_parser::ParsedMessage::LatchClear(member_id) => { + let _ = self.bots.get(&member_id).unwrap().delete_message(message.channel_id, message.id).await; self.latch_state = None; - } - - return; - } - - // TODO: Non-latching prefixes maybe? - - // Check for prefix - let match_prefix = - self.config - .members - .iter() - .enumerate() - .find_map(|(member_id, member)| { - Some((member_id, member.matches_proxy_prefix(&message)?)) - }); - if let Some((member_id, matched_content)) = match_prefix { - if let Ok(_) = self.proxy_message(&message, member_id, matched_content).await { - self.latch_state = Some((member_id, timestamp)); - self.update_autoproxy_state_after_message(member_id, timestamp); - self.update_status_of_system().await; - } - return - } + }, - // Check for autoproxy - if let Some(autoproxy_config) = &self.config.autoproxy { - match autoproxy_config { - AutoproxyConfig::Member { name } => { - let (member_id, _member) = self - .find_member_by_name(&name) - .expect("Invalid autoproxy member name"); - self.proxy_message(&message, member_id, message.content.as_str()) - .await; - } - // TODO: Do something with the latch scope - // TODO: Do something with presence setting - AutoproxyConfig::Latch { - scope, - timeout_seconds, - presence_indicator, - } => { - if let Some((member, last_timestamp)) = self.latch_state.clone() { - let time_since_last = timestamp.as_secs() - last_timestamp.as_secs(); - if time_since_last <= (*timeout_seconds).into() { - if let Ok(_) = self.proxy_message(&message, member, message.content.as_str()).await { - self.latch_state = Some((member, timestamp)); - self.update_autoproxy_state_after_message(member, timestamp); - self.update_status_of_system().await; - } - } + message_parser::ParsedMessage::ProxiedMessage { member_id, message_content, latch } => { + if let Ok(_) = self.proxy_message(&message, member_id, message_content.as_str()).await { + if latch { + self.update_autoproxy_state_after_message(member_id, timestamp); + self.update_status_of_system().await; } } - } + }, + + message_parser::ParsedMessage::Command(Command::UnknownCommand) => { + let member_id = if let Some((member_id, _)) = self.latch_state { + member_id + } else { + 0 + }; + + let _ = self.bots.get(&member_id).unwrap().react_message(message.channel_id, message.id, &RequestReactionType::Unicode { name: "⁉️" }).await; + }, + message_parser::ParsedMessage::Command(_) => todo!(), + message_parser::ParsedMessage::EmoteAdd(_, _, _) => todo!(), + message_parser::ParsedMessage::EmoteRemove(_, _, _) => todo!(), } } @@ -340,15 +302,3 @@ impl Manager { } } -impl crate::config::Member { - pub fn matches_proxy_prefix<'a>(&self, message: &'a TwiMessage) -> Option<&'a str> { - match self.message_pattern.captures(message.content.as_str()) { - None => None, - Some(captures) => match captures.name("content") { - None => None, - Some(matched_content) => Some(matched_content.as_str()), - }, - } - } -} - -- cgit 1.4.1