summary refs log tree commit diff
diff options
context:
space:
mode:
authorAshelyn Rose <git@ashen.earth>2025-03-01 19:14:24 -0700
committerAshelyn Rose <git@ashen.earth>2025-03-01 19:14:24 -0700
commitc3063561e247ddeba1ca337af23f53832e45001e (patch)
treed698e781da3a91ea249aba4248bd705a72554b15
parentdc69de9e3535e3ca49f6b74c54d2c32d218c2d81 (diff)
Reproxy
-rw-r--r--src/system/mod.rs5
-rw-r--r--src/system/plugin.rs26
-rw-r--r--src/system/plugin/autoproxy.rs146
-rw-r--r--src/system/plugin/prefixes.rs2
-rw-r--r--src/system/plugin/reproxy.rs62
-rw-r--r--src/system/types.rs3
6 files changed, 157 insertions, 87 deletions
diff --git a/src/system/mod.rs b/src/system/mod.rs
index c33c481..595e69c 100644
--- a/src/system/mod.rs
+++ b/src/system/mod.rs
@@ -84,13 +84,14 @@ impl Manager {
                     }
 
                     // Handle as message
-                    let mut message_response = Response::Noop { delete_source: false };
+                    let mut message_response = Response::DefaultNoop;
                     for plugin in &all_plugins {
                         plugin.handle_message(&logger, &system, &message, &mut message_response).await;
                     }
 
                     match message_response.clone() {
-                        Response::Noop { delete_source } => {
+                        Response::DefaultNoop => (),
+                        Response::ExplicitNoop { delete_source, member: _ } => {
                             if delete_source {
                                 let client = system.members.iter().find(|m| m.discord_token == seen_by).map(|m| m.client.clone())
                                     .expect("No such client");
diff --git a/src/system/plugin.rs b/src/system/plugin.rs
index 8e6066f..043b4d0 100644
--- a/src/system/plugin.rs
+++ b/src/system/plugin.rs
@@ -1,21 +1,19 @@
-mod autoproxy;
-mod edit;
-// mod ghost;
-mod prefixes;
-// mod reproxy;
-
-use std::{collections::HashMap, sync::Arc};
-
 use async_trait::async_trait;
-
-use edit::Edit;
+use std::{collections::HashMap, sync::Arc};
 use twilight_model::{channel::Message, id::{marker::ChannelMarker, Id}};
 
-use super::log::Logger;
 use crate::system::types::{System, Response};
+use super::log::Logger;
+
+mod autoproxy;
+mod edit;
+mod prefixes;
+mod reproxy;
 
-pub use prefixes::ProxyPrefixes;
-pub use autoproxy::Autoproxy;
+use edit::Edit;
+use prefixes::ProxyPrefixes;
+use autoproxy::Autoproxy;
+use reproxy::Reproxy;
 
 #[derive(Copy, Clone, Debug)]
 pub enum PluginCommand {
@@ -37,6 +35,8 @@ pub trait SeancePlugin<'system> {
 pub fn get_plugins<'system>() -> (Vec<Arc<Box<dyn SeancePlugin<'system>>>>, HashMap<&'static str, (PluginCommand, Arc<Box<dyn SeancePlugin<'system>>>)>) {
     let all_plugins : Vec<Arc<Box<dyn SeancePlugin<'system>>>> = vec![
         Arc::new(Box::new(ProxyPrefixes::new())),
+        // Before autoproxy because it should only recognize explicitly prefixed messages
+        Arc::new(Box::new(Reproxy::new())), 
         Arc::new(Box::new(Autoproxy::new())),
         Arc::new(Box::new(Edit::new())),
     ];
diff --git a/src/system/plugin/autoproxy.rs b/src/system/plugin/autoproxy.rs
index d9beaf0..03e20b8 100644
--- a/src/system/plugin/autoproxy.rs
+++ b/src/system/plugin/autoproxy.rs
@@ -28,6 +28,44 @@ impl Autoproxy {
     }
 }
 
+impl Autoproxy {
+    // Sets the latch and sets a timeout to unlatch it if
+    // state is identical when the timeout goes off
+    async fn set_latch(&self, member: &Member, latch_time: &Timestamp) {
+        let starting_state = {self.current_state.lock().await.clone()};
+
+        let is_latch = match starting_state {
+            InnerState::LatchActive { current_member: _, last_message: _ }
+            | InnerState::LatchInactive => true,
+            _ => false,
+        };
+
+        if !is_latch {
+            return
+        }
+
+        {*self.current_state.lock().await = InnerState::LatchActive {
+           current_member: member.clone(),
+           last_message: latch_time.clone(),
+        }};
+
+        let state_arc = self.current_state.clone();
+        let sent_member = member.clone();
+        let sent_timestamp = latch_time.clone();
+
+        tokio::spawn(async move {
+            sleep(Duration::from_secs(15 * 60)).await;
+            let current_state = {state_arc.lock().await.clone()};
+
+            if let InnerState::LatchActive { current_member, last_message } = current_state {
+                if sent_member.discord_token == current_member.discord_token && sent_timestamp.as_micros() == last_message.as_micros() {
+                    {*state_arc.lock().await = InnerState::LatchInactive};
+                }
+            }
+        });
+    }
+}
+
 #[async_trait]
 impl<'system> SeancePlugin<'system> for Autoproxy {
     fn get_commands(&self) -> Vec<PluginCommand> {
@@ -76,31 +114,44 @@ impl<'system> SeancePlugin<'system> for Autoproxy {
                 logger.log_line(None, "Deleting source message".to_string()).await;
             }
 
-            *response = Response::Noop { delete_source: message.content == "\\\\" };
+            *response = Response::ExplicitNoop { delete_source: message.content == "\\\\", member: None };
             return
         }
 
-        if let Response::Noop { delete_source: _ } = response {
-            let current_state = {self.current_state.lock().await.clone()};
-            match current_state {
-                InnerState::Off => return,
-                InnerState::LatchInactive => return,
-                InnerState::Member { current_member } => {
-                    logger.log_line(Some(current_member.discord_token.clone()), "Proxying via member mode autoproxy".to_string()).await;
-                    *response = Response::Proxy {
-                        member: current_member.clone(),
-                        content: message.content.clone(),
-                    }
-                },
-                InnerState::LatchActive { current_member, last_message: _ } => {
-                    logger.log_line(Some(current_member.discord_token.clone()), "Proxying via autoproxy latch".to_string()).await;
-                    *response = Response::Proxy {
-                        member: current_member.clone(),
-                        content: message.content.clone(),
-                    }
-                },
-            }
+        match response {
+            Response::DefaultNoop => {
+                match starting_state {
+                    InnerState::Member { current_member } => {
+                        logger.log_line(Some(current_member.discord_token.clone()), "Proxying via member mode autoproxy".to_string()).await;
+                        *response = Response::Proxy {
+                            member: current_member.clone(),
+                            content: message.content.clone(),
+                        }
+                    },
+                    InnerState::LatchActive { current_member, last_message: _ } => {
+                        logger.log_line(Some(current_member.discord_token.clone()), "Proxying via autoproxy latch".to_string()).await;
+                        *response = Response::Proxy {
+                            member: current_member.clone(),
+                            content: message.content.clone(),
+                        }
+                    },
+                    _ => (),
+                }
+            },
 
+            // This gets set by reproxy, so update our autoproxy for it
+            Response::ExplicitNoop { delete_source: _, member } => {
+                if let Some(member) = member {
+                    match starting_state {
+                        InnerState::LatchInactive 
+                        | InnerState::LatchActive { current_member: _, last_message: _ }=> {
+                            self.set_latch(&member, &message.timestamp).await;
+                        },
+                        _ => (),
+                    }
+                }
+            },
+            _ => ()
         }
     }
 
@@ -109,57 +160,12 @@ impl<'system> SeancePlugin<'system> for Autoproxy {
             Response::Proxy { member, content: _ } => {
                 let current_state = {self.current_state.lock().await.clone()};
                 match current_state {
-                    InnerState::Off => return,
-                    InnerState::Member { current_member: _ } => return,
-                    InnerState::LatchInactive => {
-                        logger.log_line(Some(member.discord_token.clone()), "Setting autoproxy latch".to_string()).await;
-                        {*self.current_state.lock().await = InnerState::LatchActive {
-                           current_member: member.clone(),
-                           last_message: message.timestamp,
-                        }};
-
-                        let state_arc = self.current_state.clone();
-                        let sent_member = member.clone();
-                        let sent_timestamp = message.timestamp.clone();
-
-                        tokio::spawn(async move {
-                            sleep(Duration::from_secs(15 * 60)).await;
-                            let current_state = {state_arc.lock().await.clone()};
-
-                            if let InnerState::LatchActive { current_member, last_message } = current_state {
-                                if sent_member.discord_token == current_member.discord_token && sent_timestamp.as_micros() == last_message.as_micros() {
-                                    {*state_arc.lock().await = InnerState::LatchInactive};
-                                }
-                            }
-                        });
-                    },
-                    InnerState::LatchActive { current_member: _, last_message: _ } => {
+                    InnerState::LatchInactive
+                    |InnerState::LatchActive { current_member: _, last_message: _ }=> {
                         logger.log_line(Some(member.discord_token.clone()), "Setting autoproxy latch".to_string()).await;
-                        {*self.current_state.lock().await = InnerState::LatchActive {
-                            current_member: member.clone(),
-                            last_message: message.timestamp,
-                        }};
-
-                        let state_arc = self.current_state.clone();
-                        let sent_member = member.clone();
-                        let sent_timestamp = message.timestamp.clone();
-                        let logger = logger.clone();
-
-                        tokio::spawn(async move {
-                            sleep(Duration::from_secs(15 * 60)).await;
-                            let current_state = {state_arc.lock().await.clone()};
-                            logger.log_line(Some(sent_member.discord_token.clone()), "Latch timeout".to_string()).await;
-
-                            if let InnerState::LatchActive { current_member, last_message } = current_state {
-                                if sent_member.discord_token == current_member.discord_token && sent_timestamp.as_micros() == last_message.as_micros() {
-                                    logger.log_line(Some(sent_member.discord_token.clone()), "Latch expired".to_string()).await;
-                                    {*state_arc.lock().await = InnerState::LatchInactive};
-                                } else {
-                                    logger.log_line(Some(sent_member.discord_token.clone()), "Timeout no longer valid".to_string()).await;
-                                }
-                            }
-                        });
+                        self.set_latch(&member, &message.timestamp).await;
                     },
+                    _ => return,
                 }
             },
             _ => return,
diff --git a/src/system/plugin/prefixes.rs b/src/system/plugin/prefixes.rs
index 55a48d3..c613d16 100644
--- a/src/system/plugin/prefixes.rs
+++ b/src/system/plugin/prefixes.rs
@@ -24,7 +24,7 @@ impl<'system> SeancePlugin<'system> for ProxyPrefixes {
     }
 
     async fn handle_message<'message>(&self, logger: &'system Logger, system: &'system System, message: &'message Message, response: &'message mut Response) {
-        if let Response::Noop { delete_source: _ } = response {
+        if let Response::DefaultNoop = response {
             for member in &system.members {
                 match member.message_pattern.captures(message.content.as_str()) {
                     None => continue,
diff --git a/src/system/plugin/reproxy.rs b/src/system/plugin/reproxy.rs
new file mode 100644
index 0000000..145bed8
--- /dev/null
+++ b/src/system/plugin/reproxy.rs
@@ -0,0 +1,62 @@
+use async_trait::async_trait;
+use twilight_model::id::{marker::ChannelMarker, Id};
+use twilight_model::channel::Message;
+use crate::system::util;
+use crate::system::{log::Logger, types::{Response, System}};
+
+use super::{PluginCommand, SeancePlugin};
+
+pub struct Reproxy;
+
+impl Reproxy {
+    pub fn new() -> Self {
+        Self
+    }
+}
+
+#[async_trait]
+impl<'system> SeancePlugin<'system> for Reproxy {
+    fn get_commands(&self) -> Vec<PluginCommand> {
+        vec![]
+    }
+
+    async fn handle_command<'message>(&self, _logger: &'system Logger, _system: &'system System, _message: &'message Message, _command: PluginCommand, _args: Vec<&'message str>) {
+        unreachable!("Reproxy has no commands");
+    }
+
+    async fn handle_message<'message>(&self, logger: &'system Logger, system: &'system System, message: &'message Message, response: &'message mut Response) {
+        if let Response::Proxy { member, content } = response {
+            if content.as_str().trim() == "*" {
+                let most_recent_message = system.get_most_recent_message(message.channel_id).await;
+                if let Some((reproxy_target, update_most_recent)) = match message.kind {
+                    twilight_model::channel::message::MessageType::Regular => most_recent_message.map(|(message, _)| (message, true)),
+                    twilight_model::channel::message::MessageType::Reply => async {Some((
+                        message.referenced_message.as_ref()?.as_ref().clone(),
+                        message.referenced_message.as_ref()?.id == most_recent_message?.0.id
+                    ))}.await,
+                    _ => todo!(),
+                } {
+                    if let Ok(new_message) = util::duplicate_message(&member.client, &reproxy_target, reproxy_target.content.as_str()).await {
+                        let _ = {member.client.lock().await.delete_message(reproxy_target.channel_id, reproxy_target.id).await};
+
+                        if update_most_recent {
+                            system.cache_most_recent_message(new_message.channel_id, new_message, member.clone()).await;
+                        }
+
+                        *response = Response::ExplicitNoop { delete_source: true, member: Some(member.clone()) };
+                    } else {
+                        logger.log_err(None, format!("Error reproxying message")).await;
+                        *response = Response::ExplicitNoop { delete_source: false, member: None };
+                    }
+                } else {
+                    logger.log_err(None, format!("Could not find reproxy target")).await;
+                    *response = Response::ExplicitNoop { delete_source: false, member: None };
+                }
+            }
+        }
+    }
+
+    async fn post_response<'message>(&self, _logger: &'system Logger, _system: &'system System, _message: &'message Message, _channel: Id<ChannelMarker>, _response: &'message Response) {
+        // noop
+    }
+}
diff --git a/src/system/types.rs b/src/system/types.rs
index 4143bfd..aaef165 100644
--- a/src/system/types.rs
+++ b/src/system/types.rs
@@ -26,8 +26,9 @@ pub struct System {
 
 #[derive(Clone)]
 pub enum Response {
+    DefaultNoop,
+    ExplicitNoop { delete_source: bool, member: Option<Member> },
     Proxy {member: Member, content: String},
-    Noop {delete_source: bool},
 }
 
 pub enum SystemThreadCommand {