diff options
author | ashelyn ghost <git@ashen.earth> | 2024-07-09 00:44:27 -0600 |
---|---|---|
committer | Ashelyn Rose <git@ashen.earth> | 2024-07-09 00:44:27 -0600 |
commit | 0b90860d730b2fbd7ebe2b9c39084edd006f515d (patch) | |
tree | 53b1294f0104056485bd45a3395a27768ff215ef | |
parent | d22f2b74d8931a32da0ed970bedcb4e49f4934f7 (diff) |
autoproxy
-rw-r--r-- | src/config.rs | 16 | ||||
-rw-r--r-- | src/system.rs | 86 |
2 files changed, 86 insertions, 16 deletions
diff --git a/src/config.rs b/src/config.rs index 26d3666..794e477 100644 --- a/src/config.rs +++ b/src/config.rs @@ -19,8 +19,11 @@ pub enum PresenceMode { } #[derive(Deserialize, Clone)] +#[serde(tag = "mode", rename_all = "lowercase")] pub enum AutoproxyConfig { - Member(String), + Member { + name: MemberName + }, Latch { scope: AutoproxyLatchScope, timeout_seconds: u32, @@ -29,6 +32,7 @@ pub enum AutoproxyConfig { } #[derive(Deserialize, Clone)] +#[serde(rename_all = "lowercase")] pub enum AutoproxyLatchScope { Global, Server @@ -55,9 +59,11 @@ fn default_forward_pings() -> bool { false } +pub type MemberName = String; + #[derive(Deserialize, Clone)] pub struct Member { - pub name: String, + pub name: MemberName, #[serde(with = "serde_regex")] pub message_pattern: Regex, pub discord_token: String, @@ -78,13 +84,13 @@ impl Config { config.systems.iter().for_each(|config_system| { let (system_name, system) = config_system; if let Some(autoproxy) = &system.autoproxy { - if let AutoproxyConfig::Member(autoproxy_member) = autoproxy { + if let AutoproxyConfig::Member { name } = autoproxy { let member_matches = system.members.iter().all(|member| { - member.name == *autoproxy_member + member.name == *name }); if !member_matches { - panic!("System {} autoproxy member {} does not match a known member name", system_name, autoproxy_member); + panic!("System {} autoproxy member {} does not match a known member name", system_name, name); } } } diff --git a/src/system.rs b/src/system.rs index 332e901..3fe4195 100644 --- a/src/system.rs +++ b/src/system.rs @@ -5,13 +5,14 @@ use twilight_http::Client; use twilight_model::id::{Id, marker::{ChannelMarker, MessageMarker, UserMarker}}; use twilight_model::util::Timestamp; -use crate::listener::{Listener, ClientEvent}; +use crate::{config::{AutoproxyConfig, Member, MemberName}, listener::{Listener, ClientEvent}}; pub struct System { pub name: String, pub config: crate::config::System, pub message_dedup_cache: lru::LruCache<Id<MessageMarker>, Timestamp>, - pub clients: HashMap<String, Client>, + pub clients: HashMap<MemberName, Client>, + pub latch_state: Option<(Member, Timestamp)>, } impl System { @@ -21,6 +22,7 @@ impl System { config: system_config, message_dedup_cache: lru::LruCache::new(NonZeroUsize::new(100).unwrap()), clients: HashMap::new(), + latch_state: None, } } @@ -49,7 +51,7 @@ impl System { Some(event) => match event { ClientEvent::Message { event_time, message_id, channel_id, content, author: _ } => { if self.is_new_message(message_id, event_time) { - self.handle_message(message_id, channel_id, content).await; + self.handle_message(message_id, channel_id, content, event_time).await; } }, ClientEvent::Error(_err) => { @@ -76,19 +78,81 @@ impl System { } } - async fn handle_message(&mut self, message_id: Id<MessageMarker>, channel_id: Id<ChannelMarker>, content: String) { + async fn handle_message(&mut self, message_id: Id<MessageMarker>, channel_id: Id<ChannelMarker>, content: String, timestamp: Timestamp) { // Check for command + // TODO: Commands + // TODO: Escaping + + // TODO: Non-latching prefixes maybe? // Check for prefix - for member in self.config.members.iter() { - if let Some(captures) = member.message_pattern.captures(content.as_str()) { - let client = self.clients.get(&member.name).expect("No client for member"); - let content = captures.name("content").expect("No capture group").as_str(); + let match_prefix = self.config.members.iter().find_map(|member| Some((member, member.matches_proxy_prefix(&content)?))); + if let Some((member, matched_content)) = match_prefix { + self.proxy_message(message_id, channel_id, member, matched_content).await; + self.update_autoproxy_state_after_message(member.clone(), timestamp); + return + } - if let Ok(_) = client.create_message(channel_id) - .content(content).expect("Cannot set content").await { - client.delete_message(channel_id, message_id).await.expect("Could not delete message"); + + // Check for autoproxy + if let Some(autoproxy_config) = &self.config.autoproxy { + match autoproxy_config { + AutoproxyConfig::Member {name} => { + let member = self.config.members.iter().find(|member| member.name == *name).expect("Invalid autoproxy member name"); + self.proxy_message(message_id, channel_id, member, 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 { + let time_since_last = timestamp.as_secs() - last_timestamp.as_secs(); + if time_since_last <= (*timeout_seconds).into() { + self.proxy_message(message_id, channel_id, &member, content.as_str()).await; + self.latch_state = Some((member.clone(), timestamp)); + } } + }, + } + } + } + + async fn proxy_message(&self, message_id: Id<MessageMarker>, channel_id: Id<ChannelMarker>, member: &Member, content: &str) { + let client = self.clients.get(&member.name).expect("No client for member"); + + if let Ok(_) = client.create_message(channel_id) + .content(content).expect("Cannot set content").await { + client.delete_message(channel_id, message_id).await.expect("Could not delete message"); + } + } + + fn update_autoproxy_state_after_message(&mut self, member: Member, timestamp: Timestamp) { + match &self.config.autoproxy { + None => (), + Some(AutoproxyConfig::Member { name }) => (), + Some(AutoproxyConfig::Latch { scope, timeout_seconds, presence_indicator }) => { + self.latch_state = Some((member.clone(), timestamp)); + } + } + } +} + + + +impl crate::config::Member { + pub fn matches_proxy_prefix<'a>(&self, content: &'a String) -> Option<&'a str> { + match self.message_pattern.captures(content.as_str()) { + None => None, + Some(captures) => { + let full_match = captures.get(0).unwrap(); + + if full_match.len() != content.len() { + return None + } + + match captures.name("content") { + None => None, + Some(matched_content) => Some(matched_content.as_str()), + } } } } |