use async_trait::async_trait; use std::sync::Arc; use tokio::sync::Mutex; use twilight_model::{channel::{Channel, Message}, id::{marker::ChannelMarker, Id}, util::Timestamp}; use crate::system::{plugin::PluginCommand, types::{Member, Response, System}}; use super::SeancePlugin; use tokio::time::sleep; use std::time::Duration; use super::Logger; pub struct Autoproxy { current_state: Arc>, } #[derive(Clone)] enum InnerState { Off, LatchActive { current_member: Member, last_message: Timestamp }, LatchInactive, Member { current_member: Member }, } impl Autoproxy { pub fn new() -> Self { Self { current_state: Arc::new(Mutex::new(InnerState::LatchInactive)) } } } #[async_trait] impl<'system> SeancePlugin<'system> for Autoproxy { fn get_commands(&self) -> Vec { vec![PluginCommand::Long("auto")] } async fn handle_command<'message>(&self, logger: &'system Logger, system: &'system System, message: &'message Message, _command: PluginCommand, args: Vec<&'message str>) { let mut args = args.iter().map(|r| *r); let first_word = args.next(); match first_word { Some("off") => *self.current_state.lock().await = InnerState::Off, Some("latch") => *self.current_state.lock().await = InnerState::LatchInactive, Some("member") => { let mention = args.next(); if let Some(member) = system.resolve_mention(mention).await { logger.log_line(Some(member.discord_token.clone()), "Setting member autoproxy".to_string()).await; {*self.current_state.lock().await = InnerState::Member { current_member: member.clone() }}; } else { logger.log_err(None, format!("Unknown user: {}", mention.unwrap_or("[empty string]"))).await; } }, Some(other) => { logger.log_err(None, format!("Unknown autoproxy mode: {other}")).await; }, None => { logger.log_err(None, "Must specify autoproxy mode".to_string()).await; } }; } async fn handle_message<'message>(&self, logger: &'system Logger, system: &'system System, message: &'message Message, response: &'message mut Response) { let starting_state = {self.current_state.lock().await.clone()}; if message.content.starts_with("\\") { logger.log_line(None, "Skipping proxy".to_string()).await; if message.content.starts_with("\\\\") { if let InnerState::LatchActive {current_member: _, last_message: _} = starting_state { logger.log_line(None, "Unlatching".to_string()).await; {*self.current_state.lock().await = InnerState::LatchInactive}; logger.log_line(None, "Done".to_string()).await; } } if message.content == "\\\\" { logger.log_line(None, "Deleting source message".to_string()).await; } *response = Response::Noop { delete_source: message.content == "\\\\" }; 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(), } }, } } } async fn post_response<'message>(&self, logger: &'system Logger, system: &'system System, message: &'message Message, channel: Id, response: &'message Response) { match response { 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: _ } => { 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; } } }); }, } }, _ => return, } } }