diff --git a/src/core/bot_actions.rs b/src/core/bot_actions.rs index 8941654..130be94 100644 --- a/src/core/bot_actions.rs +++ b/src/core/bot_actions.rs @@ -5,40 +5,83 @@ use tokio::sync::RwLock; use crate::core::botinstance::BotInstance; -use super::{botmodules::{BotAction, BotModule}, identity::ChatBadge}; +use super::{botinstance::Channel, botmodules::{BotAction, BotModule, Routine}, identity::ChatBadge}; pub type BotAR = Arc>; pub type ActAR = Arc>; +pub type RoutineAR = Arc>; #[derive(Clone)] pub struct ExecBodyParams { pub bot : BotAR, pub msg : PrivmsgMessage, - pub parent_act : ActAR , + pub parent_act : Option , + pub curr_act : ActAR , } impl ExecBodyParams { - pub async fn get_parent_module(&self) -> Option { + // pub async fn get_parent_module(&self) -> Option { + // pub async fn get_parent_module(&self) -> BotModule { + pub async fn get_module(&self) -> BotModule { - let parent_act = Arc::clone(&self.parent_act); - let parent_act_lock = parent_act.read().await; + let curr_act = Arc::clone(&self.curr_act); + let parent_act_lock = curr_act.read().await; let act = &(*parent_act_lock); match act { BotAction::C(c) => { - let temp = c.module.clone(); - Some(temp) + // let temp = c.module.clone(); + // Some(temp) + c.module.clone() }, BotAction::L(l) => { - let temp = l.module.clone(); - Some(temp) + // let temp = l.module.clone(); + // Some(temp) + l.module.clone() }, - _ => None + BotAction::R(r) => { + // let temp = r.module.clone(); + // Some(temp) + r.read().await.module.clone() + } + // _ => None } } + pub async fn get_channel(&self) -> Option { + + // THIS IS INCORRECT - BELOW MAY BE PULLING THE PARENT BOTACTION + // NOT THE CURRENT BOT ACTION + + + let curr_act = Arc::clone(&self.curr_act); + let parent_act_lock = curr_act.read().await; + let act = &(*parent_act_lock); + match act { + BotAction::C(_) => { + // let temp = c.module.clone(); + // Some(temp) + Some(Channel(self.msg.channel_login.clone())) + }, + BotAction::L(_) => { + // let temp = l.module.clone(); + // Some(temp) + // l.module.clone() + Some(Channel(self.msg.channel_login.clone())) + }, + BotAction::R(r) => { + // let temp = r.module.clone(); + // Some(temp) + Some(r.read().await.channel.clone()) + } + // _ => None + } + } + + + pub fn get_sender(&self) -> String { self.msg.sender.name.clone() } diff --git a/src/core/botinstance.rs b/src/core/botinstance.rs index 07b481d..86ac8bb 100644 --- a/src/core/botinstance.rs +++ b/src/core/botinstance.rs @@ -404,7 +404,8 @@ impl BotInstance { let params = ExecBodyParams { bot : Arc::clone(&bot), msg : (*msg).clone(), - parent_act : Arc::clone(&act_clone), + parent_act : None, + curr_act : Arc::clone(&act_clone), }; // When sending a BotMsgTypeNotif, send_botmsg does Roles related validation as required @@ -461,7 +462,8 @@ impl BotInstance { let params = ExecBodyParams { bot : Arc::clone(&bot), msg : (*msg).clone(), - parent_act : Arc::clone(&act_clone), + parent_act : None, + curr_act : Arc::clone(&act_clone), }; @@ -491,7 +493,8 @@ impl BotInstance { let params = ExecBodyParams { bot : Arc::clone(&bot), msg : (*msg).clone(), - parent_act : Arc::clone(&act_clone), + parent_act : None, + curr_act : Arc::clone(&act_clone), }; @@ -516,7 +519,8 @@ impl BotInstance { c.execute(ExecBodyParams { bot : a, msg : msg.clone() , - parent_act : Arc::clone(&act_clone), + parent_act : None, + curr_act : Arc::clone(&act_clone), }).await; botlog::trace( @@ -564,7 +568,8 @@ impl BotInstance { l.execute(ExecBodyParams { bot : a, msg : msg.clone() , - parent_act : Arc::clone(&act_clone), + parent_act : None, + curr_act : Arc::clone(&act_clone), } ).await; } diff --git a/src/core/botmodules.rs b/src/core/botmodules.rs index c104fdd..f4dfb18 100644 --- a/src/core/botmodules.rs +++ b/src/core/botmodules.rs @@ -25,14 +25,25 @@ const OF_CMD_CHANNEL:Channel = Channel(String::new()); use core::panic; +use std::borrow::Borrow; +use std::borrow::BorrowMut; use std::collections::HashMap; +use std::ops::DerefMut; use std::sync::Arc; use casual_logger::Log; +use chrono::DateTime; +// use chrono::Duration; +use chrono::Local; +use chrono::OutOfRangeError; use tokio::sync::RwLock; use async_trait::async_trait; +use tokio::task::JoinHandle; + +use tokio::time::Instant; +use tokio::time::{sleep, Duration}; use crate::core::bot_actions::actions_util; use crate::core::bot_actions::ExecBodyParams; @@ -44,6 +55,8 @@ use crate::core::bot_actions; use std::hash::{Hash, Hasher}; +use super::bot_actions::ActAR; +use super::bot_actions::RoutineAR; use super::identity::ChatBadge; @@ -69,7 +82,6 @@ pub async fn init(mgr: Arc) { // 2. Add the BotAction to ModulesManager botc1.add_core_to_modmgr(Arc::clone(&mgr)).await; - // async fn cmd_enable(bot: BotAR, msg: PrivmsgMessage) { async fn cmd_enable(params : ExecBodyParams) { /* There should be additional validation checks @@ -253,7 +265,6 @@ pub async fn init(mgr: Arc) { // 2. Add the BotAction to ModulesManager botc1.add_core_to_modmgr(Arc::clone(&mgr)).await; - // async fn cmd_disable(bot: BotAR, msg: PrivmsgMessage) { async fn cmd_disable(params : ExecBodyParams) { /* There should be additional validation checks @@ -462,7 +473,7 @@ pub enum StatusType { pub enum BotAction { C(BotCommand), L(Listener), - R(Routine), + R(Arc>), } impl BotAction { @@ -568,8 +579,895 @@ impl BotActionTrait for Listener { } } -#[derive(Debug)] -pub struct Routine {} +// #[derive(Debug, PartialEq, Eq, Hash, Clone)] +#[derive(Debug, PartialEq, Eq, Hash)] +pub enum RoutineAttr { + DelayedStart, + ScheduledStart(DateTime), // Scheduled Date (if any) after which, if not started, may trigger + LoopDuration(Duration), // How long to wait between iterations + LoopInfinitely, + RunOnce, + MaxTimeThreshold(DateTime), // DateTime after which, it will abort/cancel or stop + MaxIterations(i64), +} +/* + a Routine can be given different combinations of the above, but business logic may validate + these at Routine construction + + For example, a Routine could have the following characteristics + - DelayedStart (so it skips the first iteration) + - ScheduledStart(DateTime) - With a Start Date-Time for the first iteration to trigger + - LoopDuration(Duration) - How long to wait between iterations + + The above without any other thresholds would loop infinitely with the above characteristics + + Another example, + - LoopDuration(Duration) - How long to wait between iterations + - MaxTimeThreshold(DateTime) + - MaxIterations(i64) + + The above has thresholds , so if either are reached, it would abort/cancel or stop . Since there is no + ScheduledStart, the routine would have to be started manually elsewhere + + Another example , + - (no RoutineAttr) + + The above would only run once, and only when the Start() is called + +*/ + +// For some key statuses and in particular Stopping to Gracefully stop +pub enum RoutineSignal { + Stopping, // Gracefully Stopping + Stopped, // When cancelling or aborting, this also is represented by Stopped + Started, // After Routine Started + NotStarted, +} + +// #[derive(Debug)] +pub struct Routine { + pub name : String , + pub module : BotModule , // from() can determine this if passed parents_params + pub channel : Channel , // Requiring some channel context + exec_body: bot_actions::actions_util::ExecBody, + pub parent_params : ExecBodyParams , + pub join_handle : Option>>> , + start_time : Option> , + pub complete_iterations : i64 , + pub remaining_iterations : Option , + routine_attr : Vec , + pub internal_signal : RoutineSignal , + pub self_routine_ar : Option , + pub self_act_ar : Option , +} + + +impl Routine { + + + // // pub fn set + // // pub async fn refresh_self_ref(self) { + // pub async fn refresh_routine_internal(routine_ar : RoutineAR) + // { + + // /* + // Execute after the Routine is constructed + // - If not, a start() will also call this + + // */ + + // // 1. Update the self reference to itself + + // let mut mut_lock = routine_ar.write().await; + // mut_lock.self_routine_ar = Some(routine_ar.clone()); + + // // 2. Update the current self_act_ar + // mut_lock.self_act_ar = Some(Arc::new(RwLock::new(BotAction::R(routine_ar.clone())))); + + + + + + // } + + + pub async fn validate_attr(routine_attr : &Vec) + -> Result + // [ ] => 03.27 - REVIEW FOR COMPLETION + { + + /* + + GENERAL LOGIC : + [x] 1. Define RoutineAttr in a broad level that are known to be implented or are work in progress + [x] 2. Built in Logic will check these vectors, and return if Not Implemented + [x] 3. If Implemented , then there are additional internal validation based on combination done later + + */ + + + // [x] 1. Define RoutineAttr in a broad level that are known to be implented or are work in progress + + // adjust the below for those that are work in progress or that are implemented + // - This will allow other functions to validate that it is implemented + + // // WORK IN PROGRESS VECTOR - Vec<$RoutineAttr> + + // let wip_attr:Vec = vec![ + // RoutineAttr::DelayedStart, + // RoutineAttr::ScheduledStart(chrono::offset::Local::now()), + // RoutineAttr::LoopDuration(Duration::from_secs(1)), + // RoutineAttr::LoopInfinitely, // Note : There's no added implementation for this + // RoutineAttr::RunOnce, + // RoutineAttr::MaxTimeThreshold(chrono::offset::Local::now()), + // RoutineAttr::MaxIterations(1), + + // ]; + + // let implemented_attr:Vec = vec![ + // ]; + + + // [x] 2. Built in Logic will check these vectors, and return if Not Implemented + + // let mut unimplemented = routine_attr.iter() + // .filter(|x| { + // let inx = x; + // wip_attr.iter().filter(|y| matches!(y,i if i == inx)).next().is_none() + // && implemented_attr.iter().filter(|y| matches!(y,i if i == inx)).next().is_none() + // } + // ); + + let mut attribute_supported = false; + + for given_routine in routine_attr { + + // if !matches!(given_routine,RoutineAttr::DelayedStart) + // && !matches!(given_routine,RoutineAttr::ScheduledStart(_)) + // && !matches!(given_routine,RoutineAttr::LoopDuration(_)) + // && !matches!(given_routine,RoutineAttr::LoopInfinitely) + // && !matches!(given_routine,RoutineAttr::RunOnce) + // && !matches!(given_routine,RoutineAttr::MaxTimeThreshold(_)) + // && !matches!(given_routine,RoutineAttr::MaxIterations(_)) + if matches!(given_routine,RoutineAttr::DelayedStart) + || matches!(given_routine,RoutineAttr::ScheduledStart(_)) + || matches!(given_routine,RoutineAttr::LoopDuration(_)) + || matches!(given_routine,RoutineAttr::LoopInfinitely) + || matches!(given_routine,RoutineAttr::RunOnce) + || matches!(given_routine,RoutineAttr::MaxTimeThreshold(_)) + || matches!(given_routine,RoutineAttr::MaxIterations(_)) + + { + + attribute_supported = true; + + } + + }; + + + if !attribute_supported { + botlog::trace( + "[ERROR][Routine Feature NOT IMPLEMENTED]", + Some("Routine > Validate_attr()".to_string()), + None, + ); + + Log::flush(); + + botlog::trace( + format!( + "[ERROR][Routine Feature NOT IMPLEMENTED] > Problem Routine - {:?}" + ,routine_attr).as_str(), + Some("Routine > Validate_attr()".to_string()), + None, + ); + + Log::flush(); + + return Err("NOT IMPLEMENTED".to_string()); + } + + + + + + + // if unimplemented.next().is_some() { + + // botlog::trace( + // "[ERROR][Routine Feature NOT IMPLEMENTED]", + // Some("Routine > Validate_attr()".to_string()), + // None, + // ); + + // Log::flush(); + + // return Err("NOT IMPLEMENTED".to_string()); + // } + + + + + + // [x] 3. If Implemented , then there are additional internal validation based on combination done later to ERR + + // Below ensures a routine_attr containing LoopInfinitely does not contain conflicit attributes + if routine_attr.contains(&RoutineAttr::LoopInfinitely) && + ( routine_attr.contains(&RoutineAttr::RunOnce) + || routine_attr.iter().filter(|y| matches!(y,&&RoutineAttr::MaxIterations(_))).next().is_some() + || routine_attr.iter().filter(|y| matches!(y,&&RoutineAttr::MaxTimeThreshold(_))).next().is_some() + ) + { + return Err("Conflicting Routine Attributes".to_string()) + } + + // [x] if there is no RunOnce, there must be a LoopDuration + if !routine_attr.contains(&RoutineAttr::RunOnce) + && routine_attr.iter() + .filter(|x| matches!(x,&&RoutineAttr::LoopDuration(_)) ) + .next().is_none() + { + return Err("LoopDuration is required if not RunOnce".to_string()); + } + + + // [x] Err if DelayedStart but no LoopDuration + if routine_attr.contains(&RoutineAttr::DelayedStart) && + routine_attr.iter() + .filter(|x| matches!(x,&&RoutineAttr::LoopDuration(_)) ) + .next().is_none() + { + return Err("DelayedStart must include a LoopDuration".to_string()) + } + + + + + // [x] 4. If all other failure checks above works, ensure one more time that the atstribute is implemented + // - If not, routine NOT IMPLEMENTED error + + // if routine_attr.iter() + // .filter(|x| { + // let inx = x; + // wip_attr.iter().filter(|y| matches!(y,i if i == inx)).next().is_none() + // || implemented_attr.iter().filter(|y| matches!(y,i if i == inx)).next().is_none() + // }) + // .next() + // .is_none() + // { + + botlog::trace( + "[OK][Implemented & Validated]", + Some("Routine > Validate_attr()".to_string()), + None, + ); + + Log::flush(); + + Ok("Implemented & Validated".to_string()) + + + + } + + pub async fn validate_self_attr(self) + -> Result + // [x] => 03.27 - COMPLETED + { + Routine::validate_attr(&self.routine_attr).await + } + + // Constructor + pub async fn from( + name : String , + module : BotModule , + channel : Channel, + routine_attr : Vec , + exec_body : bot_actions::actions_util::ExecBody , + parent_params : ExecBodyParams + ) -> Result< + Arc>, + String + > + // [x] => 03.27 - COMPLETED + { + + Routine::validate_attr(&routine_attr).await?; + + let routine_ar = Arc::new(RwLock::new(Routine { + name , + module , + channel , + exec_body , + parent_params , + join_handle : None , + start_time : None , + complete_iterations : 0 , + remaining_iterations : None , + routine_attr : routine_attr , + internal_signal : RoutineSignal::NotStarted , + self_routine_ar : None , + self_act_ar : None , + })); + + let mut mut_lock = routine_ar.write().await; + mut_lock.self_routine_ar = Some(routine_ar.clone()); + + // 2. Update the current self_act_ar + mut_lock.self_act_ar = Some(Arc::new(RwLock::new(BotAction::R(routine_ar.clone())))); + + Ok(routine_ar.clone()) + + // return Ok(Arc::new(RwLock::new(Routine { + // name , + // module , + // channel , + // exec_body , + // parent_params , + // join_handle : None , + // start_time : None , + // complete_iterations : 0 , + // remaining_iterations : None , + // routine_attr : routine_attr , + // internal_signal : RoutineSignal::NotStarted , + // self_routine_ar : None , + // self_act_ar : None , + // }))) ; + + + } + + pub async fn start( + trg_routine_ar : Arc> + // ) -> Result + ) -> Result>,String> + // [ ] => 03.27 - REVIEW FOR COMPLETION + { + + + // // [x] Prep by updating it's own self reference + // Routine::refresh_routine_internal(trg_routine_ar.clone()).await; + + // [x] Asyncio Spawn likely around here + // [x] & Assigns self.join_handle + + + /* + UMBRELLA ROUTINE LOGIC + 1. Create a loop scenario based on routine_attr such as RunOnce + 2. Run the loop depending on how the Routine is setup + + + START LOGIC : + 1. Ideally validate the routineattr that they're not a problem scenario (However, this should have been validated At Setup) + a. Extra helper validation function though would help in case the attributes were changes between setup + 2. Use these attributes only in Critical areas of the loop logic to determine changes in Loop logic + + */ + + + // Use the following to stop the function from going any further if not implemented + + Routine::validate_attr(&trg_routine_ar.read().await.routine_attr).await?; + // if !trg_routine_ar.read().await.routine_attr.contains(&RoutineAttr::RunOnce) { + + + // botlog::trace( + // format!( + // "[ERROR][Routine Feature NOT IMPLEMENTED] {} in {}", + // trg_routine_ar.read().await.name, + // trg_routine_ar.read().await.channel.0 + // ) + // .as_str(), + // Some(format!( + // "Routine > start() > (In Tokio Spawn) > {:?}", + // trg_routine_ar.read().await.module + // )), + // Some(&trg_routine_ar.read().await.parent_params.msg), + // ); + + // Log::flush(); + + // return Err("NOT IMPLEMENTED".to_string()) + + // } + + + let trg_routine_arout = Arc::clone(&trg_routine_ar); + + + botlog::trace( + "innerhelper() started", + Some(format!( + "Routine > start() > (In Tokio Spawn)", + )), + Some(&trg_routine_ar.read().await.parent_params.msg), + ); + + Log::flush(); + + // Spawn the task + let join_handle = tokio::spawn(async move { + + botlog::trace( + ">> Within Spawn", + Some(format!( + "Routine > start() > (In Tokio Spawn)", + )), + Some(&trg_routine_ar.read().await.parent_params.msg), + ); + + Log::flush(); + + // [x] If Scheduled Start or Delayed Start, Handle that first + + + fn duration_to_datetime(future_dt: DateTime) -> Result + { + (future_dt - chrono::offset::Local::now()).to_std() + } + + + + let delayduration = { + + + let lock = trg_routine_ar.read().await; + + let mut related_attrs = lock + .routine_attr.iter() + .filter(|x| matches!(x,&&RoutineAttr::DelayedStart) || matches!(x,&&RoutineAttr::ScheduledStart(_)) ); + + // match related_attrs.next() { + + // } + + async fn duration_from_attr(attr: &RoutineAttr,trg_routine_ar : RoutineAR) -> Option { + // let duration_from_attr = async { + let lock = trg_routine_ar.read().await; + + match attr { + RoutineAttr::ScheduledStart(dt) => { + if let Ok(dur) = duration_to_datetime(*dt) { + Some(dur) + } else { None } + }, + RoutineAttr::DelayedStart => { + let mut loopdur_attr_iter = lock + .routine_attr.iter() + .filter(|x| matches!(x,&&RoutineAttr::LoopDuration(_)) ); + + if let Some(loopdur_attr) = loopdur_attr_iter.next() { + if let RoutineAttr::LoopDuration(dur) = loopdur_attr { + Some(*dur) + } else { None } + } else { None } + // None + }, + _ => { None } // Handle no other combination + } + } + + // The following is done twice just in case ScheduledStart and DelayedStart are defined + let delayduration01 = if let Some(attr) = related_attrs.next() { + duration_from_attr(attr, trg_routine_ar.clone()).await + } else { None }; + + let delayduration02 = if let Some(attr) = related_attrs.next() { + duration_from_attr(attr, trg_routine_ar.clone()).await + } else { None }; + + // if there is a 2nd related duration, pick the minimum, otherwise, pick the results of delayduration01 + if delayduration02.is_some() { + Some(Duration::min(delayduration01.unwrap(),delayduration02.unwrap())) + } else { delayduration01 } + + }; + + + botlog::trace( + format!( + "[TRACE][Routine Processing] {} in {} > Delay Duration - {:?} ", + trg_routine_ar.read().await.name, + trg_routine_ar.read().await.channel.0 , + delayduration , + ) + .as_str(), + Some(format!( + "Routine > start() > (In Tokio Spawn) > {:?}", + trg_routine_ar.read().await.module + )), + Some(&trg_routine_ar.read().await.parent_params.msg), + ); + + if let Some(dur) = delayduration { + sleep(dur).await; + } + + + { // [x] Loop Initialization - Prior to Loop that calls Custom Routine Execution Body + let mut a = trg_routine_ar.write().await; + a.start_time = Some(chrono::offset::Local::now()); + + if let Some(&RoutineAttr::MaxIterations(iternum)) = + a.routine_attr.iter() + .filter(|x| matches!(x,RoutineAttr::MaxIterations(_))) + .next() + { + a.remaining_iterations = Some(iternum); + } + } + + loop { // [x] Routine loop + + + // [x] execution body + // trg_routine_ar.read().await.loopbody().await; + { + trg_routine_ar.write().await.loopbody().await; + } + + + { // [x] End of Loop iteration + let mut a = trg_routine_ar.write().await; + + // [x] Check if Gracefully Stopping Signal was sent + if matches!(a.internal_signal,RoutineSignal::Stopping) { + a.internal_signal = RoutineSignal::Stopped; + break ; + } + + // [x] Check and adjust iterations + a.complete_iterations += 1; + if let Some(i) = a.remaining_iterations { + if i > 0 { a.remaining_iterations = Some(i-1) ; } + else { break ; } // if remaining iterations is 0, exit + } + + } + + // [x] End of Loop Validation + // These generally may include routine_attr related checks to , for example, break out of the loop + + if trg_routine_ar.read().await.routine_attr.contains(&RoutineAttr::RunOnce) { + if trg_routine_ar.read().await.complete_iterations > 0 { break; } + } + + // return if max time has passed + if let Some(&RoutineAttr::MaxTimeThreshold(dt)) = trg_routine_ar.read().await.routine_attr.iter() + .filter(|x| matches!(x,&&RoutineAttr::MaxTimeThreshold(_)) ) + .next() { + if chrono::offset::Local::now() > dt { break; } + } + + // [x] Checks for Loop duration to sleep + if let Some(&RoutineAttr::LoopDuration(dur)) = trg_routine_ar.read().await.routine_attr.iter() + .filter(|x| matches!(x,&&RoutineAttr::LoopDuration(_)) ) + .next() + { + sleep(dur).await; + }; + + + } + + + botlog::trace( + format!( + "[TRACE][Routine Completed] {} in {}", + trg_routine_ar.read().await.name, + trg_routine_ar.read().await.channel.0 + ) + .as_str(), + Some(format!( + "Routine > start() > (In Tokio Spawn) > {:?}", + trg_routine_ar.read().await.module + )), + Some(&trg_routine_ar.read().await.parent_params.msg), + ); + + botlog::trace( + format!( + "[TRACE][Routine Completed][Routine Header Test] {} in {} > Completed Iterations : {}", + trg_routine_ar.read().await.name, + trg_routine_ar.read().await.channel.0 , + trg_routine_ar.read().await.complete_iterations, + ) + .as_str(), + Some(format!( + "Routine > start() > (In Tokio Spawn) > {:?}", + trg_routine_ar.read().await.module + )), + Some(&trg_routine_ar.read().await.parent_params.msg), + ); + + Log::flush(); + trg_routine_ar + }); + + + { // Recommendation to ensure a clean update is to use one write() lock that was awaited + // - We can isolate the write lock by ensuring it's in it's own block + + let mut lock = trg_routine_arout.write().await; + lock.join_handle = Some(Arc::new(RwLock::new(join_handle))); + lock.internal_signal = RoutineSignal::Started; + + } + trg_routine_arout.write().await.internal_signal = RoutineSignal::Started; + + return Ok(trg_routine_arout); + + } + + async fn loopbody(&mut self) + // [x] => 03.27 - COMPLETED + { + botlog::trace( + "loopbody() started", + Some(format!( + "Routine > start() > (During Tokio Spawn) > Execution body", + )), + None, + ); + + Log::flush(); + + let self_ar = Arc::new(RwLock::new(self)); + + { + let mut mutlock = self_ar.write().await; + + mutlock.parent_params.parent_act = Some(mutlock.parent_params.curr_act.clone()); + mutlock.parent_params.curr_act = mutlock.self_act_ar.to_owned().unwrap(); + } + + (self_ar.read().await.exec_body)( + self_ar.read().await.parent_params.clone() + ).await; + + // (self.exec_body)( + // self.parent_params.clone() + // ).await; + } + + pub async fn stop(&mut self) -> Result + // [ ] => 03.27 - REVIEW FOR COMPLETION + { + + + let self_rw = Arc::new(RwLock::new(self)); + + { + let mut self_lock = self_rw.write().await; + self_lock.internal_signal = RoutineSignal::Stopping; + } + + + let self_lock = self_rw.read().await; + + botlog::trace( + format!( + "[ROUTINE][Sent Gracefully Stop Signal] {} in {}", + self_lock.name,self_lock.channel.0 + ) + .as_str(), + Some(format!( + "Routine > stop() > {:?}", + self_lock.module + )), + Some(&self_lock.parent_params.msg), + ); + + Log::flush(); + + Ok("Sent Gracefully Stop Signal".to_string()) + + // botlog::trace( + // format!( + // "[ERROR][Routine NOT IMPLEMENTED] {} in {}", + // self_lock.name,self_lock.channel.0 + // ) + // .as_str(), + // Some(format!( + // "Routine > start() > (In Tokio Spawn) > {:?}", + // self_lock.module + // )), + // Some(&self_lock.parent_params.msg), + // ); + + // Log::flush(); + + // Err("NOT IMPLEMENTED".to_string()) + } + + pub async fn cancel(&mut self) -> Result + // [ ] => 03.27 - REVIEW FOR COMPLETION + { + + // [ ] Likely calls abort() + // Related : + // https://docs.rs/tokio/latest/tokio/task/struct.JoinHandle.html#method.abort + + + let self_rw = Arc::new(RwLock::new(self)); + let self_lock = self_rw.read().await; + + + match &self_lock.join_handle { + None => return Err("No Join Handle on the Routine to Cancel".to_string()), + Some(a) => { + a.read().await.abort(); + { + let mut lock_mut = self_rw.write().await; + lock_mut.internal_signal = RoutineSignal::Stopped; + + } + } + } + + + botlog::trace( + format!( + "[ROUTINE][Cancelled Routine] {} in {}", + self_lock.name,self_lock.channel.0 + ) + .as_str(), + Some(format!( + "Routine > cancel() > {:?}", + self_lock.module + )), + Some(&self_lock.parent_params.msg), + ); + + Log::flush(); + Ok("Cancelled Successfully".to_string()) + + + // botlog::trace( + // format!( + // "[ERROR][Routine NOT IMPLEMENTED] {} in {}", + // self_lock.name,self_lock.channel.0 + // ) + // .as_str(), + // Some(format!( + // "Routine > start() > (In Tokio Spawn) > {:?}", + // self_lock.module + // )), + // Some(&self_lock.parent_params.msg), + // ); + + // Log::flush(); + // Err("NOT IMPLEMENTED".to_string()) + } + + pub async fn restart( + // &mut self, + self, + force : bool + ) -> Result + // [ ] => 03.27 - REVIEW FOR COMPLETION + { + // force flag aborts the routine immediately (like cancel()) + + + let self_rw = Arc::new(RwLock::new(self)); + + if force + { + let mut self_lock = self_rw.write().await; + self_lock.cancel().await?; + } else { + let mut self_lock = self_rw.write().await; + self_lock.stop().await?; + } + + Routine::start(self_rw.clone()).await?; + + let self_lock = self_rw.read().await; + + botlog::trace( + format!( + "[ROUTINE][Restarted Routine] {} in {}", + self_lock.name,self_lock.channel.0 + ) + .as_str(), + Some(format!( + "Routine > restart() > {:?}", + self_lock.module + )), + Some(&self_lock.parent_params.msg), + ); + + Log::flush(); + Ok("Restarted successfully".to_string()) + } + + pub async fn change_channel( + &mut self, + channel : Channel + ) -> Result + // [ ] => 03.28 - REVIEW FOR COMPLETION + { + // [x] Think Ideally it should try to + // change the target channel of the + // internal process too if possible? + + self.channel = channel; + + + let self_rw = Arc::new(RwLock::new(self)); + + let self_lock = self_rw.read().await; + + botlog::trace( + format!( + "[ROUTINE][Change Channel] {} in {}", + self_lock.name,self_lock.channel.0 + ) + .as_str(), + Some(format!( + "Routine > restart() > {:?}", + self_lock.module + )), + Some(&self_lock.parent_params.msg), + ); + + Log::flush(); + + // Err("NOT IMPLEMENTED".to_string()) + Ok("Changed Successfully".to_string()) + } + + + pub async fn set_routine_attributes( + &mut self, + routine_attr : Vec + ) -> Result + // [ ] => 03.27 - WIP - NOT IMPLEMENTED + { + // This is way to custom set attributes first + // They will Be Validated before being applied + // IDEALLY the routine also be restarted afterwards externally + + Routine::validate_attr(&routine_attr).await?; + + + let self_rw = Arc::new(RwLock::new(self)); + { + let mut self_lock = self_rw.write().await; + self_lock.routine_attr = routine_attr; + } + + // let self_rw = Arc::new(RwLock::new(self)); + + let self_lock = self_rw.read().await; + + botlog::trace( + format!( + "[ROUTINE][Set Routine Attributes] {} in {}", + self_lock.name,self_lock.channel.0 + ) + .as_str(), + Some(format!( + "Routine > restart() > {:?}", + self_lock.module + )), + Some(&self_lock.parent_params.msg), + ); + + Log::flush(); + + + Ok("Changed Successfully".to_string()) + } + + +} + + + type StatusdbEntry = (ModGroup, Vec); type ModuleActions = Vec>>; diff --git a/src/core/chat.rs b/src/core/chat.rs index bb938cc..a83bd8c 100644 --- a/src/core/chat.rs +++ b/src/core/chat.rs @@ -101,14 +101,15 @@ impl Chat { Some(¶ms.msg), ); - let parent_module = params.get_parent_module().await; + let parent_module = params.get_module().await; let params_clone = params.clone(); let botclone = Arc::clone(¶ms_clone.bot); let botlock = botclone.read().await; let modmgr = Arc::clone(&botlock.botmodules); let modstatus = (*modmgr).modstatus( - parent_module.clone().expect("ERROR - Expected a module"), + // parent_module.clone().expect("ERROR - Expected a module"), + parent_module.clone(), Channel(channel_login.clone()) ).await; @@ -180,7 +181,8 @@ impl Chat { self.send_botmsg(BotMsgType::Notif( format!("uuh {:?} is disabled on {} : {:?}", - parent_module.clone().unwrap(), + // parent_module.clone().unwrap(), + parent_module.clone(), channel_login.clone(), lvl ), diff --git a/src/core/identity.rs b/src/core/identity.rs index dc2da72..e1500eb 100644 --- a/src/core/identity.rs +++ b/src/core/identity.rs @@ -553,7 +553,16 @@ async fn getroles(params : ExecBodyParams) { let arg1 = argv.next(); let targetuser = match arg1 { - None => return, // exit if no arguments + None => { + + botlog::debug( + "Exittingcmd getroles - Invalid arguments ", + Some("identity.rs > init > getroles()".to_string()), + Some(¶ms.msg), + ); + return + + }, // exit if no arguments Some(arg) => arg, }; diff --git a/src/custom/experimental.rs b/src/custom/experimental.rs index 409abd1..7da2ecb 100644 --- a/src/custom/experimental.rs +++ b/src/custom/experimental.rs @@ -12,6 +12,7 @@ pub use crate::core::botmodules::ModulesManager; mod experiment001; mod experiment002; +mod experiment003; // [ ] init() function that accepts bot instance - this is passed to init() on submodules @@ -21,4 +22,5 @@ pub async fn init(mgr: Arc) { experiment001::init(Arc::clone(&mgr)).await; experiment002::init(Arc::clone(&mgr)).await; + experiment003::init(Arc::clone(&mgr)).await; } diff --git a/src/custom/experimental/experiment003.rs b/src/custom/experimental/experiment003.rs new file mode 100644 index 0000000..41c1d65 --- /dev/null +++ b/src/custom/experimental/experiment003.rs @@ -0,0 +1,666 @@ +/* + Custom Modules - + + Usage : + [ ] within the file's init(), define BotActions & Load them into the ModulesManager + [ ] Define Execution Bodies for these BotActions + [ ] Afterwards, add the following to parent modules.rs file + - mod ; + - within init(), ::init(mgr).await + +*/ + + +const OF_CMD_CHANNEL:Channel = Channel(String::new()); + + +use casual_logger::Log; +use rand::Rng; +use rand::thread_rng; +use rand::rngs::StdRng; +use rand::seq::SliceRandom; +use tokio::sync::RwLock; +use std::borrow::Borrow; +use std::borrow::BorrowMut; +use std::sync::Arc; + +use crate::core::bot_actions::ExecBodyParams; +use crate::core::botinstance::Channel; +use crate::core::botlog; + +use crate::core::bot_actions::actions_util; +use crate::core::botmodules::{BotAction, BotActionTrait, BotCommand, BotModule, Listener, ModulesManager, Routine, RoutineAttr}; + +use crate::core::identity::UserRole::*; + +use tokio::time::{sleep, Duration}; + +pub async fn init(mgr: Arc) { + + // 1. Define the BotAction + let botc1 = BotCommand { + module: BotModule(String::from("experiments003")), + command: String::from("test3"), // command call name + alias: vec![], // String of alternative names + exec_body: actions_util::asyncbox(test3_body), + help: String::from("Test Command tester"), + required_roles: vec![ + BotAdmin, + Mod(OF_CMD_CHANNEL), + ], + }; + + // 2. Add the BotAction to ModulesManager + botc1.add_to_modmgr(Arc::clone(&mgr)).await; + + + // 1. Define the BotAction + let botc1 = BotCommand { + module: BotModule(String::from("experiments003")), + command: String::from("countdown"), // command call name + alias: vec![], // String of alternative names + exec_body: actions_util::asyncbox(countdown_chnl_v1), + help: String::from("Test Command tester"), + required_roles: vec![ + BotAdmin, + Mod(OF_CMD_CHANNEL), + ], + }; + + // 2. Add the BotAction to ModulesManager + botc1.add_to_modmgr(Arc::clone(&mgr)).await; + + + +} + + +async fn countdown_chnl_v1(params : ExecBodyParams) { + + botlog::debug( + "[CHILDFN] countdown_chnl() triggered!", + Some("Experiments003 > countdown_chnl()".to_string()), + Some(¶ms.msg), + ); + + + /* + create a fun countdown BotCommand that allows an Elevated Chatter to + -a add channels to target a routine message + -start to start the routine with an input String, that sends a number + of messages to the targeted channels with a countdown, until it + reaches 0 when it sends a cute or funny message + + NOTE : At the moment, I don't have customizable persistence, so I would just use + counters from the Routine itself + */ + + /* + Because of some bot core features are not available, v1.0 of this could be : + [x] 1. Create a Routine & start a routine + [x] 2. Have the routine go through each joined channel randomly and countdown + [x] 3. At the end, say "0, I love you uwu~" in the last chosen channel + */ + + /* + Usage => 03.28 - skipping arguments as functinoality isn't enhanced + + -a => 03.28 - Not sure if this is possible at the moment? + + -start + + -stop => 03.28 - Not sure if this is possible at the moment? + + */ + + /* + [ ] Functional Use Case + + 1. -a adds targetted channels + + */ + + // [-] Unwraps arguments from message + + // let (arg1, arg2) = { + + // let mut argv = params.msg.message_text.split(' '); + + // argv.next(); // Skip the command name + + // let arg1 = argv.next(); + + // let arg2 = argv.next(); + + // (arg1, arg2) + // }; + + + + // [ ] 1. Create a Routine & start a routine + + // let parentmodule = params.get_parent_module().await; + let module = params.get_module().await; + let channel = params.get_channel().await; + let routine_attr = vec![ + // RoutineAttr::RunOnce + RoutineAttr::MaxIterations(5), + RoutineAttr::LoopDuration(Duration::from_secs(1)) + ]; + // let exec_body = actions_util::asyncbox(rtestbody); + let exec_body = actions_util::asyncbox(innertester); // <-- 03.27 - when below is uncommented, this is throwing an issue + + async fn innertester(params : ExecBodyParams) { + + { + let curract_guard = params.curr_act.read().await; + + + // let logmsg_botact = match *params.curr_act.read().await { + + let logmsg_botact = match *curract_guard { + BotAction::C(_) => "command", + BotAction::R(_) => "routine", + BotAction::L(_) => "listener", + } ; + + + botlog::trace( + format!("Params > Curr_act type : {:?}", logmsg_botact).as_str(), + Some("Experiments003 > countdown_chnl()".to_string()), + Some(¶ms.msg), + ); + Log::flush(); + } + + { + let bot = Arc::clone(¶ms.bot); + let botlock = bot.read().await; + + let curract_guard = params.curr_act.write().await; + + // let routine_lock = arr.write().await; + + if let BotAction::R(arr) = &*curract_guard { + // if let BotAction::R(arr) = &*params.curr_act.read().await { + + botlog::trace( + "Before loading remaining iterations", + Some("Experiments003 > countdown_chnl()".to_string()), + None, + ); + Log::flush(); + + + // let iterleft = arr.read().await.remaining_iterations.unwrap_or(0); + + // // let iterleft = if arr.read().await.remaining_iterations.is_none() { 0i64 } + // // else { arr.read().await.remaining_iterations.unwrap() }; + // let iterleft = match arr.read().await.remaining_iterations { + // None => 0, + // Some(a) => a, + // }; + + // let routine_lock = arr.read().await; + // if let Some(a) = routine_lock.remaining_iterations.clone() { + // println!("Remaining iterations > {}",a) + // } + + { + let routine_lock = arr.write().await; + let a = routine_lock.remaining_iterations; + println!("remaining iterations : {:?}", a); + } + + botlog::trace( + "after loading remaining iterations", + Some("Experiments003 > countdown_chnl()".to_string()), + None, + ); + Log::flush(); + + // [ ] get joined channels + let joinedchannels = botlock.bot_channels.clone(); + + + fn pick_a_channel(chnlvec : Vec) -> Channel { + + + botlog::trace( + "In Pick_a_Channel()", + Some("Experiments003 > countdown_chnl()".to_string()), + None, + ); + Log::flush(); + + // More Information : https://docs.rs/rand/0.7.2/rand/seq/trait.SliceRandom.html#tymethod.choose + + let mut rng = thread_rng(); + + // let joinedchannels = botlock.bot_channels.clone(); + (*chnlvec.choose(&mut rng).unwrap()).clone() + } + + + let chosen_channel = pick_a_channel(joinedchannels); + + botlog::trace( + format!("Picked a channel: {:?}", chosen_channel).as_str(), + Some("Experiments003 > countdown_chnl()".to_string()), + Some(¶ms.msg), + ); + Log::flush(); + + // let outmsg = if iterleft == 1 { + // format!("{} I love you uwu~",iterleft) + // } else { format!("{}",iterleft) }; + + // botlock.botmgrs.chat + // .say( + // // joinedchannels.choose(&mut rng).unwrap().0.clone(), + // chosen_channel.0.clone(), + // outmsg, + // params.clone() + + + // ).await; + + } + } + + + } + + + + // [ ] setup the routine + if let Ok(newr) = Routine::from( + "Routine Test".to_string(), + module, + channel.unwrap(), + routine_attr, + exec_body, + params.clone() + ).await { + let newr_ar = newr.clone(); + // [ ] start the routine + if let Ok(_) = Routine::start(newr_ar.clone()).await { + + + botlog::debug( + "Successfully started", + Some("experiment003 > countdown_chnl()".to_string()), + Some(¶ms.msg), + ); + + + Log::flush(); + + let bot = Arc::clone(¶ms.bot); + + let botlock = bot.read().await; + + // uses chat.say_in_reply_to() for the bot controls for messages + botlock + .botmgrs + .chat + .say_in_reply_to( + ¶ms.msg, + "Started Routine!".to_string(), + params.clone() + ).await; + + // let jhandle = newr.clone().read().await.join_handle.clone().unwrap(); + // let a = jhandle.write().await; + // a. + // sleep(Duration::from_secs(300)).await; + + } + + + } + + + // botlock + // .botmgrs + // .chat + // .say_in_reply_to( + // ¶ms.msg, + // format!("{:?}",), + // params.clone() + // ).await; + + + +} + + + + +async fn test3_body(params : ExecBodyParams) { + // println!("testy triggered!"); // NOTE : This test function intends to print (e.g., to stdout) at fn call + botlog::debug( + "testy triggered!", + Some("Experiments003 > test3 command body".to_string()), + Some(¶ms.msg), + ); + + /* + Test Routine Start() by : + 1. In this single exec body , create a Routine + 2. Create a Routine Execution Body + 3. Pass the Execution Body & Routine Attributes to create the Routine + 4. Start the Routine + 5. For RunOnce , we should see it only trigger once, and then complete in the logs + + */ + + // [x] Get the module from params + + // let parentmodule = params.get_parent_module().await; + let module = params.get_module().await; + let channel = params.get_channel().await; + let routine_attr = vec![ + RoutineAttr::RunOnce + ]; + // let exec_body = actions_util::asyncbox(rtestbody); + let exec_body = actions_util::asyncbox(rtestbody); // <-- 03.27 - when below is uncommented, this is throwing an issue + + + // let parent_params = params.clone(); + // let params_clone = params.clone(); + + + async fn rtestbody(params : ExecBodyParams) { + + + let guard = params.curr_act.read().await; + { + let logmsg_botact = match *guard { + BotAction::C(_) => "command", + BotAction::R(_) => "routine", + BotAction::L(_) => "listener", + } ; + + botlog::trace( + format!("Params > Curr_act type : {:?}", logmsg_botact).as_str(), + Some("Experiments003 > test3 command body".to_string()), + Some(¶ms.msg), + ); + Log::flush(); + } + + + { + let logmsg_botact = match *guard { + BotAction::C(_) => "command 2", + BotAction::R(_) => "routine 2", + BotAction::L(_) => "listener 2", + } ; + + botlog::trace( + format!("Params > Curr_act type : {:?}", logmsg_botact).as_str(), + Some("Experiments003 > test3 command body".to_string()), + Some(¶ms.msg), + ); + Log::flush(); + } + + + { + println!("Critical code area start"); // <= 03.29 - This is printed + if let BotAction::R(c) = &*guard { + println!("{:?}",c.read().await.channel); + } + println!("Critical code area end"); // <= 03.29 - ISSUE This is NOT printed + + // if let BotAction::R(arr) = &*params.curr_act.read().await { + // for curriter in 0..5 { + // println!("tester - Routine - Completed Iterations : {}", + // arr.read().await.complete_iterations); + // println!("tester - Custom Loop - Completed Iterations : {}", + // curriter); + // sleep(Duration::from_secs_f64(0.5)).await; + // } + // } + } + + } + + + + + botlog::debug( + format!("RTESTBODY : module - {:?} ; channel - {:?}", + module,channel + ).as_str(), + Some("experiment003 > test3_body".to_string()), + Some(¶ms.msg), + ); + + + + let a = Routine::from( + "Routine Test".to_string(), + module, + channel.unwrap(), + routine_attr, + exec_body, + params.clone() + ).await; + + + + if let Ok(newr) = a { + + // NOTE : The below is a "Playing aound" feature + // In the below, we're unnecessarily adjusting the ExecBodyParams of the parent + + // To get the reference to the Routine or the BotAction there are now refernces to itself + + // [ ] before execute , be sure to adjust curr_act + + // let mut params_mut = params; + let newr_ar = newr.clone(); + + // params_mut.curr_act = Arc::new(RwLock::new( + // BotAction::R(newr_ar.clone()) + // )); + + + // { + // newr_ar.write().await.parent_params = params_mut.clone(); + // } + + let rslt = Routine::start(newr_ar.clone()).await; + + // let rslt = newr_ar.read().await.start().await; + + let rsltstr = match rslt { + Ok(_) => "successful".to_string(), + Err(a) => a, + }; + + + botlog::debug( + format!("TEST3_BODY RESULT : {:?}", + rsltstr + ).as_str(), + Some("experiment003 > test3_body".to_string()), + Some(¶ms.msg), + ); + + Log::flush(); + + let bot = Arc::clone(¶ms.bot); + + let botlock = bot.read().await; + + // uses chat.say_in_reply_to() for the bot controls for messages + botlock + .botmgrs + .chat + .say_in_reply_to( + ¶ms.msg, + format!("Routine Result : {:?}",rsltstr), + params.clone() + ).await; + + // [x] Will not be handling JoinHandles here . If immediate abort() handling is required, below is an example that works + /* + + + let a = newr.clone().read().await.join_handle.clone(); + match a { + Some(b) => { + b.read().await.borrow().abort(); // [x] <-- This aborts if wanting to abort immediately + //() + }, + None => (), + } + + */ + + + } + + + Log::flush(); + + +} + + + + + +async fn good_girl(params : ExecBodyParams) { + + // [ ] Uses gen_ratio() to output bool based on a ratio probability . + // - For example gen_ratio(2,3) is 2 out of 3 or 0.67% (numerator,denomitator) + // - More Info : https://rust-random.github.io/rand/rand/trait.Rng.html#method.gen_ratio + + if params.msg.sender.name.to_lowercase() == "ModulatingForce".to_lowercase() + || params.msg.sender.name.to_lowercase() == "mzNToRi".to_lowercase() + { + botlog::debug( + "Good Girl Detected > Pausechamp", + Some("experiments > goodgirl()".to_string()), + Some(¶ms.msg), + ); + + let rollwin = rand::thread_rng().gen_ratio(1, 10); + + if rollwin { + botlog::debug( + "Oh that's a good girl!", + Some("experiments > goodgirl()".to_string()), + Some(¶ms.msg), + ); + + let bot = Arc::clone(¶ms.bot); + + + let botlock = bot.read().await; + + // uses chat.say_in_reply_to() for the bot controls for messages + botlock + .botmgrs + .chat + .say_in_reply_to( + ¶ms.msg, + String::from("GoodGirl xdd "), + params.clone() + ).await; + + + } + } +} + +async fn testy(params : ExecBodyParams) { + println!("testy triggered!"); // NOTE : This test function intends to print (e.g., to stdout) at fn call + botlog::debug( + "testy triggered!", + Some("experiments > testy()".to_string()), + Some(¶ms.msg), + ); +} + + +async fn babygirl(params : ExecBodyParams) { + + + println!("babygirl triggered!"); // NOTE : This test function intends to print (e.g., to stdout) at fn call + botlog::debug( + "babygirl triggered!", + Some("experiments > babygirl()".to_string()), + Some(¶ms.msg), + ); + + + let bot = Arc::clone(¶ms.bot); + + let botlock = bot.read().await; + + + botlock + .botmgrs + .chat + .say_in_reply_to( + ¶ms.msg, + String::from("16:13 notohh: cafdk"), + params.clone() + ).await; + + + sleep(Duration::from_secs_f64(0.5)).await; + + botlock + .botmgrs + .chat + .say_in_reply_to( + ¶ms.msg, + String::from("16:13 notohh: have fun eating princess"), + params.clone() + ).await; + + + sleep(Duration::from_secs_f64(2.0)).await; + + botlock + .botmgrs + .chat + .say_in_reply_to( + ¶ms.msg, + String::from("16:13 notohh: baby girl"), + params.clone() + ).await; + + + +} + + +async fn routinelike(params : ExecBodyParams) { + println!("routinelike triggered!"); // NOTE : This test function intends to print (e.g., to stdout) at fn call + botlog::debug( + "routinelike triggered!", + Some("experiments > routinelike()".to_string()), + Some(¶ms.msg), + ); + + // spawn an async block that runs independently from others + + tokio::spawn( async { + for _ in 0..5 { + println!(">> Innterroutine triggered!"); + sleep(Duration::from_secs_f64(5.0)).await; + } + } + ); + + // lines are executed after in conjunction to the spawn + +} +