use async_trait::async_trait; use std::sync::Arc; use tokio::sync::Mutex; use twilight_model::{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::Word("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::ExplicitNoop { delete_source: message.content == "\\\\", member: None }; return } 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; }, _ => (), } } }, _ => () } } 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::LatchInactive |InnerState::LatchActive { current_member: _, last_message: _ }=> { logger.log_line(Some(member.discord_token.clone()), "Setting autoproxy latch".to_string()).await; self.set_latch(&member, &message.timestamp).await; }, _ => return, } }, _ => return, } } } 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}; } } }); } }