/* ModulesManager is used to manage Modules and BotActions associated with those modules pub struct ModulesManager { statusdb: HashMap>, botactions: HashMap>, } - statusdb: HashMap> - Defines Modules and their ModStatusType (e.g., Enabled at an Instance level, Disabled at a Channel Level) - botactions: HashMap> - Defines Modules and their BotActions (e.g., BotCommand , Listener, Routine) Example { ModulesManager { statusdb: {BotModule("experiments 004"): [Enabled(Instance)]}, botactions: {BotModule("experiments 004"): [C(BotCommand { module: BotModule("experiments 004"), command: "DUPCMD4", alias: ["DUPALIAS4A", "DUPALIAS4B"], help: "DUPCMD4 tester" })]} } } */ use core::panic; use std::collections::HashMap; // use std::error::Error; use std::sync::Arc; // use futures::stream::iter; use twitch_irc::message::PrivmsgMessage; // use casual_logger::Log; use tokio::sync::RwLock; use async_trait::async_trait; use self::bot_actions::actions_util::BotAR; use crate::core::bot_actions::actions_util; use crate::core::botinstance::{BotInstance, ChType,ChangeResult}; use crate::core::botlog; use crate::core::identity::{self, Permissible}; use crate::core::bot_actions; pub use ChType::Channel; pub use ModType::BotModule; use super::identity::ChatBadge; // use super::identity::ChangeResult; pub async fn init(mgr: Arc) { const OF_CMD_CHANNEL:ChType = Channel(String::new()); // 1. Define the BotAction let botc1 = BotCommand { module: BotModule(String::from("core")), command: String::from("enable"), // command call name alias: vec![ String::from("e"), String::from("en")], // String of alternative names exec_body: actions_util::asyncbox(cmd_enable), help: String::from("Test Command tester"), required_roles: vec![ identity::UserRole::BotAdmin, identity::UserRole::Mod(OF_CMD_CHANNEL), identity::UserRole::SupMod(OF_CMD_CHANNEL), identity::UserRole::Broadcaster, ], }; // 2. Add the BotAction to ModulesManager botc1.add_core_to_modmgr(Arc::clone(&mgr)).await; async fn cmd_enable(bot: BotAR, msg: PrivmsgMessage) { /* There should be additional validation checks - BotAdmins can only run instance level (-i) enables - If BotAdmins need to enable/disable at Channel level, they must Promote themselves to be a Mod at least - Other Special Roles (Mod,SupMod,Broadcaster) can run without issues to enable the module at Channel Level */ /* enable -i // enables at Instance enable // enables at Channel */ /* 1. Parse out Message Arguments exec_enable() 2. Get Special Roles of CmdSender 3. If CmdSender is BotAdmin but not (Mod,SupMod,Broadcaster) 3a. , and is not -i (to instance) , return a Failure recommending BotAdmin promote themselves first 3b. , and is -i (to instance) , return a Success 4. If CmdSender not a BotAdmin but is (Mod,SupMod,Broadcaster) 4a. , and is not -i (to instance) , return a Success 4b. , and is -i (to instance) , return a Failure they are not allowed 5. If CmdSender is (Mod,SupMod,Broadcaster) and a BotAdmin 5a. , and is not -i (to instance) , return a Success 5b. , and is -i (to instance) , return a Success */ // [ ] Unwraps arguments from message let (arg1, arg2) = { let mut argv = msg.message_text.split(' '); argv.next(); // Skip the command name let arg1 = argv.next(); let arg2 = argv.next(); (arg1, arg2) }; /* -- Related function to call later exec_enable( &self, requestor: String, requestor_badge: Option, trg_module: ModType, // channel: Option, trg_level: StatusLvl, bot: BotAR, ) -> ChangeResult */ // [ ] requestor: String, let requester = msg.clone().sender.name; // [ ] requestor_badge: Option, let mut requestor_badge_mut: Option = None; for b in &msg.badges { if b.name == "moderator" { requestor_badge_mut = Some(ChatBadge::Mod); } else if b.name == "broadcaster" { requestor_badge_mut = Some(ChatBadge::Broadcaster); } } let requestor_badge = requestor_badge_mut; // [ ] trg_module: ModType, // - [ ] Need to validate an actual ModType - otherwise, fail or exit the cmd let trg_module = if (arg1 == Some("-i")) || (arg1 == Some("-f")) { arg2 } else { arg1 }; if let Some(trg_module_str) = trg_module { let botlock = bot.read().await; let modmgr = Arc::clone(&botlock.botmodules); let modlist = modmgr.moduleslist().await; let rslt = modlist.get(&ModType::BotModule(trg_module_str.to_string())); if let None = rslt { let outmsg = "uuh module doesn't exist"; botlog::debug( outmsg, Some("botmodules.rs > cmd_enable()".to_string()), Some(&msg), ); botlock .botmgrs .chat .say_in_reply_to(&msg, outmsg.to_string()) .await; return; } } // [ ] trg_level: StatusLvl, let currchnl = msg.channel_login.to_lowercase(); let trg_level = if arg1 == Some("-i") { StatusLvl::Instance } else if arg1 == Some("-f") { StatusLvl::Instance } else { StatusLvl::Ch(ChType::Channel(currchnl)) } ; } // 1. Define the BotAction let botc1 = BotCommand { module: BotModule(String::from("core")), command: String::from("disable"), // command call name alias: vec![ String::from("d")], // String of alternative names exec_body: actions_util::asyncbox(cmd_disable), help: String::from("Test Command tester"), required_roles: vec![ identity::UserRole::BotAdmin, identity::UserRole::Mod(OF_CMD_CHANNEL), identity::UserRole::SupMod(OF_CMD_CHANNEL), identity::UserRole::Broadcaster, ], }; // 2. Add the BotAction to ModulesManager botc1.add_core_to_modmgr(Arc::clone(&mgr)).await; async fn cmd_disable(bot: BotAR, msg: PrivmsgMessage) { /* There should be additional validation checks - BotAdmins can only run instance level (-i) disables and (-f) force disable - If BotAdmins need to enable/disable at Channel level, they must Promote themselves to be a Mod at least - Other Special Roles (Mod,SupMod,Broadcaster) can run without issues to disable the module at Channel Level */ /* disable -i // disables at Instance disable // disables at Channel disable -f // force disables (instance and enabled are removed) */ /* 1. If CmdSender is BotAdmin but not (Mod,SupMod,Broadcaster) 1. can_user_run for cmdreqRoles including BotAdmin & not can_user_run for cmdreqRoles (Mod,SupMod,Broadcaster) 1a. , and has no special flags (-i / -f) , return a Failure recommending BotAdmin promote themselves first 1b. , and is -i (to instance) , return a Success 1c. , and is -f (forced) , return a Success 2. If CmdSender not a BotAdmin but is (Mod,SupMod,Broadcaster) 2. not can_user_run for cmdreqRoles including BotAdmin & can_user_run for cmdreqRoles (Mod,SupMod,Broadcaster) 2a. , and has no special flags (-i / -f) , return a Success 2b. , and is -i (to instance) , return a Failure they are not allowed 2c. , and is -f (forced) , return a Failure they are not allowed 3. If CmdSender is (Mod,SupMod,Broadcaster) and a BotAdmin 3. can_user_run for cmdreqRoles (Mod,SupMod,Broadcaster) & can_user_run for cmdreqRoles including BotAdmin 3a. , and has no special flags (-i / -f) , return a Success 3b. , and is -i (to instance) , return a Success 3c. , and is -f (forced) , return a Success */ } } #[derive(Debug, PartialEq, Eq, Hash, Clone)] pub enum ModType { BotModule(String), } #[derive(Debug, PartialEq, Eq, Hash, Clone)] pub enum ModGroup { Core, Custom, } #[derive(Debug, PartialEq, Eq, Hash, Clone)] pub enum StatusLvl { Instance, Ch(ChType), } #[derive(Debug, PartialEq, Eq, Hash, Clone)] pub enum StatusType { Enabled(StatusLvl), Disabled(StatusLvl), } pub enum BotAction { C(BotCommand), L(Listener), R(Routine), } impl BotAction { pub async fn execute(&self, m: BotAR, n: PrivmsgMessage) { match self { BotAction::L(a) => a.execute(m, n).await, BotAction::C(a) => a.execute(m, n).await, _ => (), } } } #[async_trait] pub trait BotActionTrait { async fn add_to_bot(self, bot: BotInstance); async fn add_to_modmgr(self, modmgr: Arc); async fn add_core_to_bot(self, bot: BotInstance); async fn add_core_to_modmgr(self, modmgr: Arc); } pub struct BotCommand { pub module: ModType, pub command: String, // command call name pub alias: Vec, // String of alternative names pub exec_body: bot_actions::actions_util::ExecBody, pub help: String, pub required_roles: Vec, } impl BotCommand { pub async fn execute(&self, m: BotAR, n: PrivmsgMessage) { (*self.exec_body)(m, n).await; } } #[async_trait] impl BotActionTrait for BotCommand { async fn add_to_bot(self, bot: BotInstance) { self.add_to_modmgr(bot.botmodules).await; } async fn add_to_modmgr(self, modmgr: Arc) { modmgr .add_botaction(self.module.clone(), BotAction::C(self)) .await } async fn add_core_to_bot(self, bot: BotInstance) { self.add_core_to_modmgr(bot.botmodules).await; } async fn add_core_to_modmgr(self, modmgr: Arc) { modmgr .add_core_act(self.module.clone(), BotAction::C(self)) .await } } pub struct Listener { pub module: ModType, pub name: String, pub exec_body: bot_actions::actions_util::ExecBody, pub help: String, } impl Listener { pub async fn execute(&self, m: BotAR, n: PrivmsgMessage) { (self.exec_body)(m, n).await; } } #[async_trait] impl BotActionTrait for Listener { async fn add_to_bot(self, bot: BotInstance) { botlog::trace( "Adding action to bot", Some("BotModules > BotActionTrait > add_to_bot()".to_string()), None, ); self.add_to_modmgr(bot.botmodules).await; } async fn add_to_modmgr(self, modmgr: Arc) { botlog::trace( "Adding action to module manager", Some("BotModules > BotActionTrait > add_to_bot()".to_string()), None, ); modmgr .add_botaction(self.module.clone(), BotAction::L(self)) .await; } async fn add_core_to_bot(self, bot: BotInstance) { self.add_core_to_modmgr(bot.botmodules).await; } async fn add_core_to_modmgr(self, modmgr: Arc) { modmgr .add_core_act(self.module.clone(), BotAction::L(self)) .await } } #[derive(Debug)] pub struct Routine {} pub struct ModulesManager { // statusdb: Arc>>>, // statusdb: Arc>>>, statusdb: Arc)>>>, pub botactions: Arc>>>, } /* statusdb <-- shows Enabled/Disabled per Status level botactions HashMap< ModType, <-- e.g., BotModule(String::from("experiments001")) Vec> BotCommand, Listener */ impl ModulesManager { pub async fn init() -> Arc { let mgr = ModulesManager { statusdb: Arc::new(RwLock::new(HashMap::new())), botactions: Arc::new(RwLock::new(HashMap::new())), }; // :: [x] initialize core modules botlog::trace( "ModulesManager > init() > Adding modules", Some("ModulesManager > init()".to_string()), None, ); let mgrarc = Arc::new(mgr); // 1. load core modules crate::core::identity::init(Arc::clone(&mgrarc)).await; // 2. load custom modules crate::custom::init(Arc::clone(&mgrarc)).await; botlog::trace( ">> Modules Manager : End of Init", Some("ModulesManager > init()".to_string()), None, ); mgrarc } pub async fn moduleslist(&self) -> HashMap { // let db = Arc::clone(&self.statusdb); let db = self.statusdb.clone(); let dblock = db.read().await; let mut outmap = HashMap::new(); for (k,v) in &(*dblock) { let (mgrp,_) = v; let mtype = k; outmap.insert((*mtype).clone(), (*mgrp).clone()); } outmap } pub async fn modstatus(&self, in_module: ModType, in_chnl: ChType) -> StatusType { // Example usage : botmanager.modstatus( // BotModule("GambaCore"), // Channel("modulatingforce") // ) // - The ModStatusType checks in the context of the given channel , // but also validates based on wheher the module is disabled at a bot instance // level as well let dbt = self.statusdb.read().await; // let a = dbt.entry(in_module.clone()).; let (mgrp,statusvector) = dbt.get(&in_module).unwrap(); match mgrp { ModGroup::Core => { StatusType::Enabled(StatusLvl::Instance) // This forces core to be validated as Enabled, even if undesired scenario of missing StatusLvl::Instance or empty vectors }, ModGroup::Custom => { // // remove all instance level pattern for the module // while let Some(index) = statusvector // .iter() // .position(|x| (*x == StatusType::Enabled(StatusLvl::Instance)) || (*x == StatusType::Disabled(StatusLvl::Instance))) { // statusvector.remove(index); // } // statusvector.push(StatusType::Disabled(StatusLvl::Instance)); // ( // StatusType::Disabled(StatusLvl::Instance), // ChangeResult::Success("Set Disabled at Instance".to_string()) // ) /* [x] 1. If Disabled at Instance Level , [x] a. And Enabled at a Channel Level > return Enabled(Channel) [x] b. And Disabled at a Channel Level > return Disabled(Channel) [x] c. And Not Defined at Channel Level > return Disabled(Instance) [x] 2. If Enabled at Instance Level , [x] a. And Enabled at a Channel Level > return Enabled(Channel) [x] b. And Disabled at a Channel Level > return Disabled(Channel) [x] c. And Not Defined at Channel Level > return Enabled(Instance) */ if statusvector.contains(&StatusType::Disabled(StatusLvl::Instance)) { // [x] 1. If Disabled at Instance Level , if statusvector.contains(&StatusType::Enabled(StatusLvl::Ch(in_chnl.clone()))) { // [x] a. And Enabled at a Channel Level > return Enabled(Channel) StatusType::Enabled(StatusLvl::Ch(in_chnl.clone())) } else if statusvector.contains(&StatusType::Disabled(StatusLvl::Ch(in_chnl.clone()))) { // [x] b. And Disabled at a Channel Level > return Disabled(Channel) StatusType::Disabled(StatusLvl::Ch(in_chnl.clone())) } else { // [x] c. And Not Defined at Channel Level > return Disabled(Instance) StatusType::Disabled(StatusLvl::Instance) } } else if statusvector.contains(&StatusType::Enabled(StatusLvl::Instance)) { // [x] 2. If Enabled at Instance Level , if statusvector.contains(&StatusType::Enabled(StatusLvl::Ch(in_chnl.clone()))) { // [x] a. And Enabled at a Channel Level > return Enabled(Channel) StatusType::Enabled(StatusLvl::Ch(in_chnl.clone())) } else if statusvector.contains(&StatusType::Disabled(StatusLvl::Ch(in_chnl.clone()))) { // [x] b. And Disabled at a Channel Level > return Disabled(Channel) StatusType::Disabled(StatusLvl::Ch(in_chnl.clone())) } else { // [x] c. And Not Defined at Channel Level > return Enabled(Instance) StatusType::Enabled(StatusLvl::Instance) } } else { // ? In some unexpected scenario (e.g., not define at instance level), assume Disabled at Instance level and set as this way self.set_instance_disabled(in_module).await; StatusType::Disabled(StatusLvl::Instance) } }, } //StatusType::Enabled(StatusLvl::Instance) } // pub fn togglestatus(&self, _: ModType, _: ChType) -> StatusType { // // enables or disables based on current status // StatusType::Enabled(StatusLvl::Instance) // } // pub fn setstatus(&self, _: ModType, _: StatusType) -> Result<&str, Box> { // // sets the status based given ModSatusType // // e.g., b.setstatus(BodModule("GambaCore"), Enabled(Channel("modulatingforce"))).expect("ERROR") // Ok("") // } /* exec_enable(self,requestor,requestor_badge,trg_module,Channel) -> ChangeResult */ pub async fn exec_enable( &self, requestor: String, requestor_badge: Option, trg_module: ModType, // channel: Option, trg_level: StatusLvl, bot: BotAR, ) -> ChangeResult { /* 1. If CmdSender is BotAdmin but not (Mod,SupMod,Broadcaster) 1. can_user_run for cmdreqRoles including BotAdmin & not can_user_run for cmdreqRoles (Mod,SupMod,Broadcaster) 1a. , and is not -i (to instance) , return a Failure recommending BotAdmin promote themselves first 1b. , and is -i (to instance) , return a Success 2. If CmdSender not a BotAdmin but is (Mod,SupMod,Broadcaster) 2. not can_user_run for cmdreqRoles including BotAdmin & can_user_run for cmdreqRoles (Mod,SupMod,Broadcaster) 2a. , and is not -i (to instance) , return a Success 2b. , and is -i (to instance) , return a Failure they are not allowed 3. If CmdSender is (Mod,SupMod,Broadcaster) and a BotAdmin 3. can_user_run for cmdreqRoles (Mod,SupMod,Broadcaster) & can_user_run for cmdreqRoles including BotAdmin 3a. , and is not -i (to instance) , return a Success 3b. , and is -i (to instance) , return a Success */ /* [ ] 1. If CmdSender is BotAdmin but not (Mod,SupMod,Broadcaster) 1. can_user_run for cmdreqRoles including BotAdmin & not can_user_run for cmdreqRoles (Mod,SupMod,Broadcaster) 1a. , and is -i (to instance) , return a Success 1b. , and is not -i (to instance) , return a Failure recommending BotAdmin promote themselves first */ let botlock = bot.read().await; let id = botlock.get_identity(); let mut idlock = id.write().await; // if trg_level = StatusLvl::Instance , the temp_chnl = the broadcaster's or the chatter's let arb_chnl = match trg_level.clone() { StatusLvl::Instance => ChType::Channel(requestor.to_lowercase()), StatusLvl::Ch(a) => a, }; const OF_CMD_CHANNEL:ChType = Channel(String::new()); let (admin_level_access,_) = idlock.can_user_run(requestor.clone(), arb_chnl.clone(), requestor_badge.clone(), vec![ identity::UserRole::BotAdmin, ]).await; let (chnl_elevated_access,_) = idlock.can_user_run(requestor, arb_chnl, requestor_badge.clone(), vec![ identity::UserRole::Mod(OF_CMD_CHANNEL), identity::UserRole::SupMod(OF_CMD_CHANNEL), identity::UserRole::Broadcaster, ]).await; if let Permissible::Allow = admin_level_access { if let Permissible::Block = chnl_elevated_access { match trg_level { StatusLvl::Instance => { self.set_instance_enabled(trg_module.clone()).await; ChangeResult::Success("Enabled at Instance Level".to_string()) }, StatusLvl::Ch(_) => { ChangeResult::Failed("Promote yourself Temporarily First".to_string()) }, }; } } /* [ ] 2. If CmdSender not a BotAdmin but is (Mod,SupMod,Broadcaster) 2. not can_user_run for cmdreqRoles including BotAdmin & can_user_run for cmdreqRoles (Mod,SupMod,Broadcaster) 2a. , and is -i (to instance) , return a Failure they are not allowed 2b. , and is not -i (to instance) , return a Success */ if let Permissible::Block = admin_level_access { if let Permissible::Allow = chnl_elevated_access { match trg_level.clone() { StatusLvl::Instance => { ChangeResult::Failed("You're not allowed".to_string()) }, StatusLvl::Ch(in_chnl) => { self.set_ch_enabled(trg_module.clone(), in_chnl).await; ChangeResult::Success("Enabled at Channel Level".to_string()) }, }; } } /* [ ] 3. If CmdSender is (Mod,SupMod,Broadcaster) and a BotAdmin 3. can_user_run for cmdreqRoles (Mod,SupMod,Broadcaster) & can_user_run for cmdreqRoles including BotAdmin 3a. , and is not -i (to instance) , return a Success 3b. , and is -i (to instance) , return a Success */ if let Permissible::Allow = admin_level_access { if let Permissible::Allow = chnl_elevated_access { match trg_level { StatusLvl::Instance => { self.set_instance_enabled(trg_module.clone()).await; ChangeResult::Success("Enabled at Instance Level".to_string()) }, StatusLvl::Ch(in_chnl) => { self.set_ch_enabled(trg_module.clone(), in_chnl).await; ChangeResult::Success("Enabled at Channel Level".to_string()) }, }; } } // ======================= // ======================= // ======================= // /* // 2. Get Special Roles of CmdSender // 3. If CmdSender is BotAdmin but not (Mod,SupMod,Broadcaster) // 3a. , and is not -i (to instance) , return a Failure recommending BotAdmin promote themselves first // 3b. , and is -i (to instance) , return a Success // 4. If CmdSender not a BotAdmin but is (Mod,SupMod,Broadcaster) // 4a. , and is not -i (to instance) , return a Success // 4b. , and is -i (to instance) , return a Failure they are not allowed // 5. If CmdSender is (Mod,SupMod,Broadcaster) and a BotAdmin // 5a. , and is not -i (to instance) , return a Success // 5b. , and is -i (to instance) , return a Success // */ // // [ ] 2. Get Special Roles of CmdSender // let botlock = bot.read().await; // let id = botlock.get_identity(); // let idlock = id.read().await; // let trgchnl = { // match trg_level { // StatusLvl::Instance => None, // StatusLvl::Ch(a) => Some(a), // } // }; // let requestor_roles = idlock // .getspecialuserroles( // requestor.to_lowercase(), // trgchnl, // ) // .await; // /* // [ ] 3. If CmdSender is BotAdmin but not (Mod,SupMod,Broadcaster) // 3a. , and is not -i (to instance) , return a Failure recommending BotAdmin promote themselves first // 3b. , and is -i (to instance) , return a Success // */ // if requestor_roles.contains(&identity::UserRole::BotAdmin) // && !requestor_roles.contains(&identity::UserRole::Broadcaster) // && !requestor_roles.contains(&identity::UserRole::Mod(trgchnl)) // { // } ChangeResult::Failed("ERROR : Not implemented yet".to_string()) } pub async fn exec_disable( &self, requestor: String, requestor_badge: Option, trg_module: ModType, // channel: Option, trg_level: StatusLvl, force: bool, bot: BotAR, ) -> ChangeResult { /* 1. If CmdSender is BotAdmin but not (Mod,SupMod,Broadcaster) 1. can_user_run for cmdreqRoles including BotAdmin & not can_user_run for cmdreqRoles (Mod,SupMod,Broadcaster) 1a. , and has no special flags (-i / -f) , return a Failure recommending BotAdmin promote themselves first 1b. , and is -i (to instance) , return a Success 1c. , and is -f (forced) , return a Success 2. If CmdSender not a BotAdmin but is (Mod,SupMod,Broadcaster) 2. not can_user_run for cmdreqRoles including BotAdmin & can_user_run for cmdreqRoles (Mod,SupMod,Broadcaster) 2a. , and has no special flags (-i / -f) , return a Success 2b. , and is -i (to instance) , return a Failure they are not allowed 2c. , and is -f (forced) , return a Failure they are not allowed 3. If CmdSender is (Mod,SupMod,Broadcaster) and a BotAdmin 3. can_user_run for cmdreqRoles (Mod,SupMod,Broadcaster) & can_user_run for cmdreqRoles including BotAdmin 3a. , and has no special flags (-i / -f) , return a Success 3b. , and is -i (to instance) , return a Success 3c. , and is -f (forced) , return a Success */ let botlock = bot.read().await; let id = botlock.get_identity(); let mut idlock = id.write().await; // if trg_level = StatusLvl::Instance , the temp_chnl = the broadcaster's or the chatter's let arb_chnl = match trg_level.clone() { StatusLvl::Instance => ChType::Channel(requestor.to_lowercase()), StatusLvl::Ch(a) => a, }; const OF_CMD_CHANNEL:ChType = Channel(String::new()); let (admin_level_access,_) = idlock.can_user_run(requestor.clone(), arb_chnl.clone(), requestor_badge.clone(), vec![ identity::UserRole::BotAdmin, ]).await; let (chnl_elevated_access,_) = idlock.can_user_run(requestor, arb_chnl, requestor_badge.clone(), vec![ identity::UserRole::Mod(OF_CMD_CHANNEL), identity::UserRole::SupMod(OF_CMD_CHANNEL), identity::UserRole::Broadcaster, ]).await; /* [ ] 1. If CmdSender is BotAdmin but not (Mod,SupMod,Broadcaster) 1. can_user_run for cmdreqRoles including BotAdmin & not can_user_run for cmdreqRoles (Mod,SupMod,Broadcaster) 1a. , and is -f (forced) , return a Success 1b. , and is -i (to instance) , return a Success 1c. , and has no special flags (-i / -f) , return a Failure recommending BotAdmin promote themselves first */ if let Permissible::Allow = admin_level_access { if let Permissible::Block = chnl_elevated_access { if force { self.force_disable(trg_module.clone()).await; return ChangeResult::Success("Forced Disable".to_string()); } else { match trg_level { StatusLvl::Instance => { self.set_instance_disabled(trg_module.clone()).await; ChangeResult::Success("Disabled at Instance Level".to_string()) }, StatusLvl::Ch(_) => { ChangeResult::Failed("Promote yourself Temporarily First".to_string()) }, }; } } } /* [ ] 2. If CmdSender not a BotAdmin but is (Mod,SupMod,Broadcaster) 2. not can_user_run for cmdreqRoles including BotAdmin & can_user_run for cmdreqRoles (Mod,SupMod,Broadcaster) 2a. , and is -f (forced) , return a Failure they are not allowed 2b. , and is -i (to instance) , return a Failure they are not allowed 2c. , and has no special flags (-i / -f) , return a Success */ if let Permissible::Block = admin_level_access { if let Permissible::Allow = chnl_elevated_access { if force { return ChangeResult::Failed("You're not allowed".to_string()); } else { match trg_level.clone() { StatusLvl::Instance => { ChangeResult::Failed("You're not allowed".to_string()) }, StatusLvl::Ch(in_chnl) => { self.set_ch_disabled(trg_module.clone(), in_chnl).await; ChangeResult::Success("Disabled at Channel Level".to_string()) }, }; } } } /* [ ] 3. If CmdSender is (Mod,SupMod,Broadcaster) and a BotAdmin 3. can_user_run for cmdreqRoles (Mod,SupMod,Broadcaster) & can_user_run for cmdreqRoles including BotAdmin 3a. , and is -f (forced) , return a Success 3b. , and is -i (to instance) , return a Success 3c. , and has no special flags (-i / -f) , return a Success */ if let Permissible::Allow = admin_level_access { if let Permissible::Allow = chnl_elevated_access { if force { self.force_disable(trg_module.clone()).await; return ChangeResult::Success("Forced Disable".to_string()); } else { match trg_level { StatusLvl::Instance => { self.set_instance_disabled(trg_module.clone()).await; ChangeResult::Success("Disabled at Instance Level".to_string()) }, StatusLvl::Ch(in_chnl) => { self.set_ch_disabled(trg_module.clone(), in_chnl).await; ChangeResult::Success("Disabled at Channel Level".to_string()) }, }; } } } ChangeResult::Failed("ERROR : Not implemented yet".to_string()) } pub async fn set_instance_disabled(&self, in_module: ModType) -> (StatusType,ChangeResult) { // at Instance level // - If core module, do nothing // self.satusdb. let mut dbt = self.statusdb.write().await; // let a = dbt.entry(in_module.clone()).; let (mgrp,statusvector) = dbt.get_mut(&in_module).unwrap(); match mgrp { ModGroup::Core => { ( StatusType::Enabled(StatusLvl::Instance), ChangeResult::Failed("Core Modules cannot be disabled".to_string()) ) }, ModGroup::Custom => { // remove all instance level pattern for the module while let Some(index) = statusvector .iter() .position(|x| (*x == StatusType::Enabled(StatusLvl::Instance)) || (*x == StatusType::Disabled(StatusLvl::Instance))) { statusvector.remove(index); } statusvector.push(StatusType::Disabled(StatusLvl::Instance)); ( StatusType::Disabled(StatusLvl::Instance), ChangeResult::Success("Set Disabled at Instance".to_string()) ) }, } // (StatusType::Disabled(StatusLvl::Instance),ChangeResult::NoChange("Nothing needed".to_string())) } pub async fn force_disable(&self, in_module: ModType) -> (StatusType,ChangeResult) { // Disables the module at Instance level, and removes all Enabled at Channel level // - Bot Moderators MUST Re-enable if they were enabled before // - If core module, do nothing let mut dbt = self.statusdb.write().await; // let a = dbt.entry(in_module.clone()).; let (mgrp,statusvector) = dbt.get_mut(&in_module).unwrap(); match mgrp { ModGroup::Core => { ( StatusType::Enabled(StatusLvl::Instance), ChangeResult::Failed("Core Modules cannot be disabled".to_string()) ) }, ModGroup::Custom => { // remove all instance level pattern & Enabled Channel patterns for the module // Disabled at Channel level might be fine? That way if it gets Enabled at instance level, channel level disables are uninterrupted while let Some(index) = statusvector .iter() .position(|x| if (*x == StatusType::Enabled(StatusLvl::Instance)) || (*x == StatusType::Disabled(StatusLvl::Instance)) { true } else if let StatusType::Enabled(StatusLvl::Ch(_)) = (*x).clone() { true } else {false} ) { statusvector.remove(index); } statusvector.push(StatusType::Disabled(StatusLvl::Instance)); ( StatusType::Disabled(StatusLvl::Instance), ChangeResult::Success("Forced Disabled".to_string()) ) }, } // (StatusType::Disabled(StatusLvl::Instance),ChangeResult::NoChange("Nothing needed".to_string())) } pub async fn set_instance_enabled(&self, in_module: ModType) -> (StatusType,ChangeResult) { // at Instance level // - If core module, do nothing let mut dbt = self.statusdb.write().await; // let a = dbt.entry(in_module.clone()).; let (mgrp,statusvector) = dbt.get_mut(&in_module).unwrap(); match mgrp { ModGroup::Core => { ( StatusType::Enabled(StatusLvl::Instance), ChangeResult::NoChange("Core Modules are always Enabled".to_string()) ) }, ModGroup::Custom => { // remove all instance level pattern for the module while let Some(index) = statusvector .iter() .position(|x| (*x == StatusType::Enabled(StatusLvl::Instance)) || (*x == StatusType::Disabled(StatusLvl::Instance))) { statusvector.remove(index); } statusvector.push(StatusType::Enabled(StatusLvl::Instance)); ( StatusType::Enabled(StatusLvl::Instance), ChangeResult::Success("Set Enabled at Instance".to_string()) ) }, } // (StatusType::Enabled(StatusLvl::Instance),ChangeResult::NoChange("Nothing needed".to_string())) } pub async fn set_ch_disabled(&self, in_module: ModType , in_chnl: ChType) -> (StatusType,ChangeResult) { // at Instance level // - If core module, do nothing let mut dbt = self.statusdb.write().await; // let a = dbt.entry(in_module.clone()).; let (mgrp,statusvector) = dbt.get_mut(&in_module).unwrap(); match mgrp { ModGroup::Core => { ( StatusType::Enabled(StatusLvl::Instance), ChangeResult::Failed("Core Modules cannot be disabled".to_string()) ) }, ModGroup::Custom => { // remove all channel level pattern for the module while let Some(index) = statusvector .iter() .position(|x| (*x == StatusType::Enabled(StatusLvl::Ch(in_chnl.clone()))) || (*x == StatusType::Disabled(StatusLvl::Ch(in_chnl.clone())))) { statusvector.remove(index); } statusvector.push(StatusType::Disabled(StatusLvl::Ch(in_chnl.clone()))); ( StatusType::Disabled(StatusLvl::Ch(in_chnl.clone())), ChangeResult::Success("Set Disabled at Channel Level".to_string()) ) }, } // (StatusType::Disabled(StatusLvl::Instance),ChangeResult::NoChange("Nothing needed".to_string())) } pub async fn set_ch_enabled(&self, in_module: ModType , in_chnl: ChType) -> (StatusType,ChangeResult) { // at Instance level // - If core module, do nothing let mut dbt = self.statusdb.write().await; // let a = dbt.entry(in_module.clone()).; let (mgrp,statusvector) = dbt.get_mut(&in_module).unwrap(); match mgrp { ModGroup::Core => { ( StatusType::Enabled(StatusLvl::Instance), ChangeResult::NoChange("Core Modules are always Enabled".to_string()) ) }, ModGroup::Custom => { // remove all channel level pattern for the module while let Some(index) = statusvector .iter() .position(|x| (*x == StatusType::Enabled(StatusLvl::Ch(in_chnl.clone()))) || (*x == StatusType::Disabled(StatusLvl::Ch(in_chnl.clone())))) { statusvector.remove(index); } statusvector.push(StatusType::Enabled(StatusLvl::Ch(in_chnl.clone()))); ( StatusType::Enabled(StatusLvl::Ch(in_chnl.clone())), ChangeResult::Success("Set Enabled at Channel Level".to_string()) ) }, } // (StatusType::Enabled(StatusLvl::Instance),ChangeResult::NoChange("Nothing needed".to_string())) } pub async fn add_botaction(&self, in_module: ModType, in_action: BotAction) { self.int_add_botaction(in_module,ModGroup::Custom,in_action).await; } pub async fn add_core_act(&self, in_module: ModType, in_action: BotAction) { self.int_add_botaction(in_module,ModGroup::Core,in_action).await; } pub async fn affirm_in_statusdb(&self,in_module:ModType,in_modgroup: ModGroup) { let mut dbt = self.statusdb.write().await; let (_,statusvector) = dbt.entry(in_module.clone()).or_insert((in_modgroup.clone(),Vec::new())); if !statusvector.contains(&StatusType::Enabled(StatusLvl::Instance)) && !statusvector.contains(&StatusType::Disabled(StatusLvl::Instance)) { match in_modgroup { ModGroup::Core => statusvector.push(StatusType::Enabled(StatusLvl::Instance)) , // Pushes the Module as Enabled at Instance Level ModGroup::Custom => statusvector.push(StatusType::Disabled(StatusLvl::Instance)), } } } async fn int_add_botaction(&self, in_module: ModType, in_modgroup: ModGroup, in_action: BotAction) { botlog::trace( "Add botaction called", Some("ModulesManager > init()".to_string()), None, ); /* adds a BotAction to the Modules Manager - This will require a BotModule passed as well This will including the logic of a valid add If it fails to add, either a PANIC or some default coded business rules that handles the botaction add For example, this Should PANIC (ideally Panic?) if it does not successfully add a bot module -- Being unable to indicates a Programming/Developer code logic issue : They cannot add botactions that already exists (?) -- In particular to BotCommands, which must have Unique command call names and aliases that to not conflict with any other already BotCommand added name or alias Other types might be fine? For example, if 2 modules have their own listeners but each have the name "targetchatter" , both would be called separately, even if they both have the same or different logic */ // [x] Before Adding, validate the following : // - If BotAction to Add is a BotCommand , In Module Manager DB (botactions), // Check All Other BotAction Command Names & Aliases to ensure they don't conflict async fn find_conflict_module(mgr: &ModulesManager, act: &BotAction) -> Option { if let BotAction::C(incmd) = act { let actdb = mgr.botactions.read().await; for (module, moduleactions) in &(*actdb) { for modact in moduleactions.iter() { if let BotAction::C(dbcmd) = &modact { // At this point, there is an command incmd and looked up dbcmd // [x] check if given botcommand c.command:String conflicts with any in botactions if incmd.command.to_lowercase() == dbcmd.command.to_lowercase() { // Returning State - with the identified module return Some(module.clone()); // works } for a in &dbcmd.alias { if incmd.command.to_lowercase() == a.to_lowercase() { // Returning State - with the identified module return Some(module.clone()); // works } } // [x] Then do the same check except for each c.alias for inalias in &incmd.alias { if inalias.to_lowercase() == dbcmd.command.to_lowercase() { // Returning State - with the identified module return Some(module.clone()); // works } for a in &dbcmd.alias { if inalias.to_lowercase() == a.to_lowercase() { // Returning State - with the identified module return Some(module.clone()); // works } } } } } } } // for all other scenarios (e.g., Listener, Routine), find no conflicts None } if let Some(c) = find_conflict_module(self, &in_action).await { panic!( "ERROR: Could not add module; there was a conflict with existing module {:?}", c ) } // let mut dbt = self.statusdb.write().await; // // // let statusvector = dbt.entry(in_module.clone()).or_insert((in_modgroup.clone(),Vec::new())); // match in_modgroup { // ModGroup::Core => statusvector.1.push(StatusType::Enabled(StatusLvl::Instance)) , // Pushes the Module as Enabled at Instance Level // ModGroup::Custom => statusvector.1.push(StatusType::Disabled(StatusLvl::Instance)), // } // statusvector.push(ModStatusType::Enabled(StatusLvl::Instance)); // Pushes the Module as Enabled at Instance Level self.affirm_in_statusdb(in_module.clone(),in_modgroup).await; let mut a = self.botactions.write().await; let modactions = a.entry(in_module.clone()).or_insert(Vec::new()); modactions.push(in_action); botlog::trace( format!( "Modules Manager> add_botaction called - botactions size : {}", modactions.len() ) .as_str(), Some("ModulesManager > init()".to_string()), None, ); } fn _statuscleanup(&self, _: Option) { // internal cleans up statusdb . For example : // - remove redudancies . If we see several Enabled("m"), only keep 1x // - Clarify Conflict. If we see Enabled("m") and Disabled("m") , we remove Enabled("m") and keep Disabled("m") // the IDEAL is that this is ran before every read/update operation to ensure quality // Option can pass Some(Channel("m")) (as an example) so statuscleanup only works on the given channel // Passing None to chnl may be a heavy operation, as this will review and look at the whole table } } // ===================== // ===================== // ===================== // ===================== // ===================== #[cfg(test)] mod core_modulesmanager { use casual_logger::Log; use casual_logger::Extension; use super::*; #[test] fn WIP_case_insensitive_test() { Log::set_file_ext(Extension::Log); assert_eq!( BotModule("test".to_string()), BotModule("Test".to_lowercase()) ); } /* Possible Tests [x] Test 1 - Custom ModGroup Workflow 1. affirm_in_statusdb(Experiments01,Custom) 2. modstatus(Experiments01,TestChannel01) & modstatus(Experiments01,TestChannel02) 3. set_instance_enabled(Experiments01) 4. modstatus(Experiments01,TestChannel01) & modstatus(Experiments01,TestChannel02) 5. set_ch_disabled(Experiments01,TestChannel01) 6. modstatus(Experiments01,TestChannel01) & modstatus(Experiments01,TestChannel02) 7. set_ch_enabled(Experiments01,TestChannel01) & set_ch_disabled(Experiments01,TestChannel02) 8. modstatus(Experiments01,TestChannel01) & modstatus(Experiments01,TestChannel02) 9. set_instance_disabled(Experiments01) 10. modstatus(Experiments01,TestChannel01) & modstatus(Experiments01,TestChannel02) 11. force_disable(Experiments01) 12. modstatus(Experiments01,TestChannel01) & modstatus(Experiments01,TestChannel02) [x] Test 2 - Core ModGroup Workflow 1. affirm_in_statusdb(CoreModule01,Core) 2. modstatus(CoreModule01,TestChannel01) & modstatus(CoreModule01,TestChannel02) 3. set_instance_enabled(CoreModule01) 4. modstatus(CoreModule01,TestChannel01) & modstatus(CoreModule01,TestChannel02) 5. set_ch_disabled(CoreModule01,TestChannel01) 6. modstatus(CoreModule01,TestChannel01) & modstatus(CoreModule01,TestChannel02) 7. set_ch_enabled(CoreModule01,TestChannel01) & set_ch_disabled(CoreModule01,TestChannel02) 8. modstatus(CoreModule01,TestChannel01) & modstatus(CoreModule01,TestChannel02) 9. set_instance_disabled(CoreModule01) 10. modstatus(CoreModule01,TestChannel01) & modstatus(CoreModule01,TestChannel02) 11. force_disable(CoreModule01) 12. modstatus(CoreModule01,TestChannel01) & modstatus(CoreModule01,TestChannel02) */ async fn complex_workflow( in_module: ModType , in_modgroup : ModGroup , in_chnl1 : ChType, in_chnl2 : ChType) { let mgr = ModulesManager::init().await; /* 1. affirm_in_statusdb(Experiments01,Custom) 2. modstatus(Experiments01,TestChannel01) & modstatus(Experiments01,TestChannel02) */ mgr.affirm_in_statusdb(in_module.clone(), in_modgroup.clone()).await; match in_modgroup { ModGroup::Custom => { assert_eq!(mgr.modstatus(in_module.clone(), in_chnl1.clone()).await, StatusType::Disabled(StatusLvl::Instance)); assert_eq!(mgr.modstatus(in_module.clone(), in_chnl2.clone()).await, StatusType::Disabled(StatusLvl::Instance)); }, ModGroup::Core => { assert_eq!(mgr.modstatus(in_module.clone(), in_chnl1.clone()).await, StatusType::Enabled(StatusLvl::Instance)); assert_eq!(mgr.modstatus(in_module.clone(), in_chnl2.clone()).await, StatusType::Enabled(StatusLvl::Instance)); }, } /* 3. set_instance_enabled(Experiments01) 4. modstatus(Experiments01,TestChannel01) & modstatus(Experiments01,TestChannel02) */ mgr.set_instance_enabled(in_module.clone()).await; match in_modgroup { ModGroup::Custom => { assert_eq!(mgr.modstatus(in_module.clone(), in_chnl1.clone()).await, StatusType::Enabled(StatusLvl::Instance)); assert_eq!(mgr.modstatus(in_module.clone(), in_chnl2.clone()).await, StatusType::Enabled(StatusLvl::Instance)); }, ModGroup::Core => { assert_eq!(mgr.modstatus(in_module.clone(), in_chnl1.clone()).await, StatusType::Enabled(StatusLvl::Instance)); assert_eq!(mgr.modstatus(in_module.clone(), in_chnl2.clone()).await, StatusType::Enabled(StatusLvl::Instance)); }, } /* 5. set_ch_disabled(Experiments01,TestChannel01) 6. modstatus(Experiments01,TestChannel01) & modstatus(Experiments01,TestChannel02) */ mgr.set_ch_disabled(in_module.clone(),in_chnl1.clone()).await; //StatusType::Disabled(StatusLvl::Ch(in_chnl1.clone())) match in_modgroup { ModGroup::Custom => { assert_eq!(mgr.modstatus(in_module.clone(), in_chnl1.clone()).await, StatusType::Disabled(StatusLvl::Ch(in_chnl1.clone()))); assert_eq!(mgr.modstatus(in_module.clone(), in_chnl2.clone()).await, StatusType::Enabled(StatusLvl::Instance)); }, ModGroup::Core => { assert_eq!(mgr.modstatus(in_module.clone(), in_chnl1.clone()).await, StatusType::Enabled(StatusLvl::Instance)); assert_eq!(mgr.modstatus(in_module.clone(), in_chnl2.clone()).await, StatusType::Enabled(StatusLvl::Instance)); }, } /* 7. set_ch_enabled(Experiments01,TestChannel01) & set_ch_disabled(Experiments01,TestChannel02) 8. modstatus(Experiments01,TestChannel01) & modstatus(Experiments01,TestChannel02) */ mgr.set_ch_enabled(in_module.clone(),in_chnl1.clone()).await; //StatusType::Disabled(StatusLvl::Ch(in_chnl1.clone())) match in_modgroup { ModGroup::Custom => { assert_eq!(mgr.modstatus(in_module.clone(), in_chnl1.clone()).await, StatusType::Enabled(StatusLvl::Ch(in_chnl1.clone()))); assert_eq!(mgr.modstatus(in_module.clone(), in_chnl2.clone()).await, StatusType::Enabled(StatusLvl::Instance)); }, ModGroup::Core => { assert_eq!(mgr.modstatus(in_module.clone(), in_chnl1.clone()).await, StatusType::Enabled(StatusLvl::Instance)); assert_eq!(mgr.modstatus(in_module.clone(), in_chnl2.clone()).await, StatusType::Enabled(StatusLvl::Instance)); }, } /* 9. set_instance_disabled(Experiments01) 10. modstatus(Experiments01,TestChannel01) & modstatus(Experiments01,TestChannel02) */ mgr.set_instance_disabled(in_module.clone()).await; // StatusType::Disabled(StatusLvl::Ch(in_chnl1.clone())) match in_modgroup { ModGroup::Custom => { assert_eq!(mgr.modstatus(in_module.clone(), in_chnl1.clone()).await, StatusType::Enabled(StatusLvl::Ch(in_chnl1.clone()))); assert_eq!(mgr.modstatus(in_module.clone(), in_chnl2.clone()).await, StatusType::Disabled(StatusLvl::Instance)); }, ModGroup::Core => { assert_eq!(mgr.modstatus(in_module.clone(), in_chnl1.clone()).await, StatusType::Enabled(StatusLvl::Instance)); assert_eq!(mgr.modstatus(in_module.clone(), in_chnl2.clone()).await, StatusType::Enabled(StatusLvl::Instance)); }, } /* 11. force_disable(Experiments01) 12. modstatus(Experiments01,TestChannel01) & modstatus(Experiments01,TestChannel02) */ mgr.force_disable(in_module.clone()).await; match in_modgroup { ModGroup::Custom => { assert_eq!(mgr.modstatus(in_module.clone(), in_chnl1.clone()).await, StatusType::Disabled(StatusLvl::Instance)); assert_eq!(mgr.modstatus(in_module.clone(), in_chnl2.clone()).await, StatusType::Disabled(StatusLvl::Instance)); }, ModGroup::Core => { assert_eq!(mgr.modstatus(in_module.clone(), in_chnl1.clone()).await, StatusType::Enabled(StatusLvl::Instance)); assert_eq!(mgr.modstatus(in_module.clone(), in_chnl2.clone()).await, StatusType::Enabled(StatusLvl::Instance)); }, } } #[tokio::test] async fn custom_modgroup_workflow() { Log::set_file_ext(Extension::Log); /* [x] Test 1 - Custom ModGroup Workflow 1. affirm_in_statusdb(Experiments01,Custom) 2. modstatus(Experiments01,TestChannel01) & modstatus(Experiments01,TestChannel02) 3. set_instance_enabled(Experiments01) 4. modstatus(Experiments01,TestChannel01) & modstatus(Experiments01,TestChannel02) 5. set_ch_disabled(Experiments01,TestChannel01) 6. modstatus(Experiments01,TestChannel01) & modstatus(Experiments01,TestChannel02) 7. set_ch_enabled(Experiments01,TestChannel01) & set_ch_disabled(Experiments01,TestChannel02) 8. modstatus(Experiments01,TestChannel01) & modstatus(Experiments01,TestChannel02) 9. set_instance_disabled(Experiments01) 10. modstatus(Experiments01,TestChannel01) & modstatus(Experiments01,TestChannel02) 11. force_disable(Experiments01) 12. modstatus(Experiments01,TestChannel01) & modstatus(Experiments01,TestChannel02) */ let in_module = BotModule("Experiments01".to_string()); let in_modgroup = ModGroup::Custom; let (in_chnl1,in_chnl2) = (ChType::Channel("TestChannel01".to_string()),ChType::Channel("TestChannel02".to_string())); complex_workflow(in_module, in_modgroup, in_chnl1, in_chnl2).await; } #[tokio::test] async fn core_modgroup_workflow() { Log::set_file_ext(Extension::Log); let in_module = BotModule("CoreModule01".to_string()); let in_modgroup = ModGroup::Core; let (in_chnl1,in_chnl2) = (ChType::Channel("TestChannel01".to_string()),ChType::Channel("TestChannel02".to_string())); complex_workflow(in_module, in_modgroup, in_chnl1, in_chnl2).await; } }