diff --git a/src/core/botinstance.rs b/src/core/botinstance.rs index 144e8ee..1aa0e49 100644 --- a/src/core/botinstance.rs +++ b/src/core/botinstance.rs @@ -19,12 +19,21 @@ use crate::core::ratelimiter::RateLimiter; use crate::core::bot_actions::actions_util::BotAR; use crate::core::botmodules::ModulesManager; -use crate::core::identity::{ChangeResult, IdentityManager, Permissible}; +use crate::core::identity::{IdentityManager, Permissible,self}; use crate::core::botlog; use crate::core::chat::Chat; +#[derive(Debug, PartialEq, Eq, Clone)] +pub enum ChangeResult { + Success(String), + Failed(String), + NoChange(String), +} + + + #[derive(Debug, PartialEq, Eq, Hash, Clone)] pub enum ChType { Channel(String), @@ -32,6 +41,8 @@ pub enum ChType { pub use ChType::Channel; +use super::botmodules::StatusType; + #[derive(Clone)] pub struct BotManagers { pub identity: Arc>, @@ -235,7 +246,7 @@ impl BotInstance { /* BotCommand handling - - [x] Checks if the input message is a prefix with command name or alias - - [ ] Validate User can run based on identityModule(From_Bot)::can_user_run( + - [x] Validate User can run based on identityModule(From_Bot)::can_user_run( _usr:String, _channelname:ChType, _chat_badge:ChatBadge, @@ -284,6 +295,54 @@ impl BotInstance { let botlock = bot.read().await; let id = botlock.get_identity(); + // [x] Check first if the Module for that Given Command is Enabled or Disabled on the given Channel + let modmgr = Arc::clone(&botlock.botmodules); + let modstatus = modmgr.modstatus( + c.module.clone(), + ChType::Channel(msg.channel_login.to_string())).await; + + + if let StatusType::Disabled(a) = modstatus { + + // [x] Should only respond if a BotAdmin , Mod , SupMod , BroadCaster + // - Specifically it should respond only to those who may be able to enable the module + + botlog::trace( + &format!("Identified cmd is associated with Disabled Module : StatusLvl = {:?}", a), + Some("BotInstance > listener_main_prvmsg()".to_string()), + Some(msg), + ); + + + const OF_CMD_CHANNEL:ChType = Channel(String::new()); + + let elevated_access = { + let mut idlock = id.write().await; + let (permissability, _) = idlock + .can_user_run_prvmsg(msg, + vec![ + identity::UserRole::BotAdmin, + identity::UserRole::Mod(OF_CMD_CHANNEL), + identity::UserRole::SupMod(OF_CMD_CHANNEL), + identity::UserRole::Broadcaster, + ]) + .await; + + permissability + }; + + if let Permissible::Allow = elevated_access { + let botlock = bot.read().await; + let outstr = + format!("sadg Module is disabled : {:?}",a); + botlock.botmgrs.chat.say_in_reply_to(msg, outstr).await; + } + + return; + }; + + + let eval = { let mut idlock = id.write().await; let (permissability, chngrslt) = idlock @@ -322,7 +381,7 @@ impl BotInstance { match eval { Permissible::Allow => { botlog::debug( - "Executed as permissible", + "Executing as permissible", Some("BotInstance > listener_main_prvmsg()".to_string()), Some(msg), ); @@ -350,8 +409,33 @@ impl BotInstance { } crate::core::botmodules::BotAction::L(l) => { - let a = Arc::clone(&bot); - l.execute(a, msg.clone()).await; + + let botlock = bot.read().await; + // let id = botlock.get_identity(); + + // [x] Check first if the Module for that Given Command is Enabled or Disabled on the given Channel + let modmgr = Arc::clone(&botlock.botmodules); + let modstatus = modmgr.modstatus( + l.module.clone(), + ChType::Channel(msg.channel_login.to_string())).await; + + + if let StatusType::Disabled(a) = modstatus { + + // [x] Should only respond if a BotAdmin , Mod , SupMod , BroadCaster + // - Specifically it should respond only to those who may be able to enable the module + + botlog::trace( + &format!("Identified listener is associated with Disabled Module : StatusLvl = {:?}", a), + Some("BotInstance > listener_main_prvmsg()".to_string()), + Some(msg), + ); + + } else { + let a = Arc::clone(&bot); + l.execute(a, msg.clone()).await; + } + } _ => (), diff --git a/src/core/botmodules.rs b/src/core/botmodules.rs index 0bfe4fa..cef3b2f 100644 --- a/src/core/botmodules.rs +++ b/src/core/botmodules.rs @@ -22,37 +22,432 @@ Example use core::panic; use std::collections::HashMap; -use std::error::Error; use std::sync::Arc; 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::botinstance::{BotInstance, ChType}; +use crate::core::bot_actions::actions_util; +use crate::core::botinstance::{BotInstance, ChType,ChangeResult}; use crate::core::botlog; -use crate::core::identity; +use crate::core::identity::{self, Permissible,IdentityManager}; use crate::core::bot_actions; pub use ChType::Channel; pub use ModType::BotModule; -#[derive(Debug, PartialEq, Eq, Hash, Clone)] +use std::hash::{Hash, Hasher}; + +use super::identity::ChatBadge; + + +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 + + */ + + + + // [x] 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 + */ + + + // [x] requestor: String, + let requestor = msg.clone().sender.name; + + + // [x] 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; + + + // [x] trg_module: ModType, + // - [x] 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 no trg_module was passed + // if let None = trg_module { + if trg_module.is_none() { + + let botlock = bot.read().await; + + let outmsg = "uuh You need to pass a module"; + + 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; + + } + + + // [x] trg_level: StatusLvl, + + let currchnl = msg.channel_login.to_lowercase(); + + let trg_level = + if arg1 == Some("-i") || arg1 == Some("-f") { StatusLvl::Instance } + // else if arg1 == Some("-f") { StatusLvl::Instance } + else { StatusLvl::Ch(ChType::Channel(currchnl)) } + ; + + + + let botlock = bot.read().await; + let modmgr = Arc::clone(&botlock.botmodules); + let id = botlock.get_identity(); + + + // modmgr.exec_enable(requestor, requestor_badge, trg_module, trg_level, id) + let rslt = modmgr.exec_enable( + requestor, + requestor_badge, + ModType::BotModule(trg_module.unwrap().to_string()), + trg_level, + id).await; + + + let outmsg = match rslt.clone() { + ChangeResult::Failed(a) => format!("Stare Failed : {}",a), + ChangeResult::NoChange(a) => format!("Hmm No Change : {}",a), + ChangeResult::Success(a) => format!("YAAY Success : {}",a), + }; + + botlock + .botmgrs + .chat + .say_in_reply_to(&msg, outmsg.to_string()) + .await; + + + } + + + + // 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 + */ + + + // [x] 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_disable( + &self, + requestor: String, + requestor_badge: Option, + trg_module: ModType, + // channel: Option, + trg_level: StatusLvl, + force: bool, + // bot: BotAR, + id: Arc>, + ) -> ChangeResult + */ + + + // [x] requestor: String, + let requestor = msg.clone().sender.name; + + + // [x] 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; + + // [x] trg_module: ModType, + // - [x] 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 no trg_module was passed + // if let None = trg_module { + if trg_module.is_none() { + + let botlock = bot.read().await; + + let outmsg = "uuh You need to pass a module"; + + botlog::debug( + outmsg, + Some("botmodules.rs > cmd_disable()".to_string()), + Some(&msg), + ); + + botlock + .botmgrs + .chat + .say_in_reply_to(&msg, outmsg.to_string()) + .await; + + return; + + } + + + + // [x] trg_level: StatusLvl, + + let currchnl = msg.channel_login.to_lowercase(); + + let trg_level = + if arg1 == Some("-i") || arg1 == Some("-f") { StatusLvl::Instance } + // else if arg1 == Some("-f") { StatusLvl::Instance } + else { StatusLvl::Ch(ChType::Channel(currchnl)) } + ; + + + + let botlock = bot.read().await; + let modmgr = Arc::clone(&botlock.botmodules); + let id = botlock.get_identity(); + + let force = arg1 == Some("-f"); + + // modmgr.exec_enable(requestor, requestor_badge, trg_module, trg_level, id) + let rslt = modmgr.exec_disable( + requestor, + requestor_badge, + ModType::BotModule(trg_module.unwrap().to_string()), + trg_level, + force, + id).await; + + + let outmsg = match rslt.clone() { + ChangeResult::Failed(a) => format!("Stare Failed : {}",a), + ChangeResult::NoChange(a) => format!("Hmm No Change : {}",a), + ChangeResult::Success(a) => format!("YAAY Success : {}",a), + }; + + botlock + .botmgrs + .chat + .say_in_reply_to(&msg, outmsg.to_string()) + .await; + + + + } + + +} + + + +#[derive(Debug, Clone)] pub enum ModType { BotModule(String), } -#[derive(Debug)] -pub enum StatusLvl { - Instance, - _Ch(ChType), +impl PartialEq for ModType { + fn eq(&self, other: &Self) -> bool { + let BotModule(name1) = self.clone(); + let BotModule(name2) = other.clone(); + name1.to_lowercase() == name2.to_lowercase() + } +} +impl Eq for ModType {} + +impl Hash for ModType{ + fn hash(&self, state: &mut H) { + // self.id.hash(state); + // self.phone.hash(state); + let BotModule(name) = self.clone(); + name.to_lowercase().hash(state); + } } -#[derive(Debug)] -pub enum ModStatusType { + +#[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), } @@ -77,6 +472,8 @@ impl BotAction { 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 { @@ -105,6 +502,16 @@ impl BotActionTrait for BotCommand { .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 { @@ -142,13 +549,25 @@ impl BotActionTrait for Listener { .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 {} +type StatusdbEntry = (ModGroup, Vec); + pub struct ModulesManager { - statusdb: Arc>>>, + statusdb: Arc>>, pub botactions: Arc>>>, } @@ -184,6 +603,7 @@ impl ModulesManager { // 1. load core modules crate::core::identity::init(Arc::clone(&mgrarc)).await; + crate::core::botmodules::init(Arc::clone(&mgrarc)).await; // 2. load custom modules crate::custom::init(Arc::clone(&mgrarc)).await; @@ -197,7 +617,25 @@ impl ModulesManager { mgrarc } - pub fn modstatus(&self, _: ModType, _: ChType) -> ModStatusType { + + 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") @@ -205,21 +643,661 @@ impl ModulesManager { // - 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 - ModStatusType::Enabled(StatusLvl::Instance) + + 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 => { + + /* + + [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) -> ModStatusType { - // enables or disables based on current status - ModStatusType::Enabled(StatusLvl::Instance) + pub async fn exec_enable( + &self, + requestor: String, + requestor_badge: Option, + trg_module: ModType, + trg_level: StatusLvl, + id: Arc>, + ) -> 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 + */ + + + /* + [x] 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 + + + */ + + + // [x] Validate in trg_module first + + let modlist = self.moduleslist().await; + let rslt = modlist.get(&trg_module); + + if rslt.is_none() { + return ChangeResult::Failed("Module doesn't exist".to_string()); + } + + + 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 { + + botlog::debug( + &format!("?? REACHED INNER TIER : + admin_level_access : {:?} ; chnl_elevated_access : {:?}", + admin_level_access , chnl_elevated_access), + Some("botmodules.rs > exec_enable()".to_string()), + None, + ); + match trg_level { + StatusLvl::Instance => { + self.set_instance_enabled(trg_module.clone()).await; + return ChangeResult::Success("Enabled at Instance Level".to_string()); + }, + StatusLvl::Ch(_) => { + return ChangeResult::Failed("Promote yourself Temporarily First".to_string()); + }, + }; + + } + } + + + /* + [x] 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 => { + return ChangeResult::Failed("You're not allowed".to_string()); + }, + StatusLvl::Ch(in_chnl) => { + self.set_ch_enabled(trg_module.clone(), in_chnl).await; + return ChangeResult::Success("Enabled at Channel Level".to_string()); + }, + }; + } + } + + + /* + + [x] 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; + return ChangeResult::Success("Enabled at Instance Level".to_string()); + }, + StatusLvl::Ch(in_chnl) => { + self.set_ch_enabled(trg_module.clone(), in_chnl).await; + return ChangeResult::Success("Enabled at Channel Level".to_string()); + }, + }; + } + } + + + // Respond in case of General Chatter + // The below should NOT be required , as current internal logic would prevent + // a BotCommand to be ran by a Chatter if it requires any special roles and + // that chatter does not have htose roles + // However, below is added to satisfy unit tests + + + if let Permissible::Block = admin_level_access { + if let Permissible::Block = chnl_elevated_access { + match trg_level { + StatusLvl::Instance => { + return ChangeResult::Failed("You're not allowed".to_string()); + }, + StatusLvl::Ch(_) => { + return ChangeResult::Failed("You're not allowed".to_string()); + }, + }; + } + } + + + + // ======================= + // ======================= + // ======================= + + + + botlog::debug( + &format!("FAILURE involves : + admin_level_access : {:?} ; chnl_elevated_access : {:?}", + admin_level_access , chnl_elevated_access), + Some("botmodules.rs > exec_enable()".to_string()), + None, + ); + + + Log::flush(); + + ChangeResult::Failed("ERROR : Not implemented yet".to_string()) } - pub fn setstatus(&self, _: ModType, _: ModStatusType) -> Result<&str, Box> { - // sets the status based given ModSatusType - // e.g., b.setstatus(BodModule("GambaCore"), Enabled(Channel("modulatingforce"))).expect("ERROR") - Ok("") + + pub async fn exec_disable( + &self, + requestor: String, + requestor_badge: Option, + trg_module: ModType, + trg_level: StatusLvl, + force: bool, + id: Arc>, + ) -> 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 + */ + + // [x] Validate in trg_module first + + let modlist = self.moduleslist().await; + let rslt = modlist.get(&trg_module); + + if rslt.is_none() { + return ChangeResult::Failed("Module doesn't exist".to_string()); + } + + + 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; + + + /* + + [x] 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; + return ChangeResult::Success("Disabled at Instance Level".to_string()); + }, + StatusLvl::Ch(_) => { + return ChangeResult::Failed("Promote yourself Temporarily First".to_string()); + }, + }; + } + } + } + + + /* + [x] 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 => { + return ChangeResult::Failed("You're not allowed".to_string()); + }, + StatusLvl::Ch(in_chnl) => { + self.set_ch_disabled(trg_module.clone(), in_chnl).await; + return ChangeResult::Success("Disabled at Channel Level".to_string()); + }, + }; + } + } + } + + + /* + [x] 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; + return ChangeResult::Success("Disabled at Instance Level".to_string()); + }, + StatusLvl::Ch(in_chnl) => { + self.set_ch_disabled(trg_module.clone(), in_chnl).await; + return ChangeResult::Success("Disabled at Channel Level".to_string()); + }, + }; + } + } + } + + + + // Respond in case of General Chatter + // The below should NOT be required , as current internal logic would prevent + // a BotCommand to be ran by a Chatter if it requires any special roles and + // that chatter does not have htose roles + // However, below is added to satisfy unit tests + + + if let Permissible::Block = admin_level_access { + if let Permissible::Block = chnl_elevated_access { + match trg_level { + StatusLvl::Instance => { + return ChangeResult::Failed("You're not allowed".to_string()); + }, + StatusLvl::Ch(_) => { + return ChangeResult::Failed("You're not allowed".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 + + let mut dbt = self.statusdb.write().await; + + 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 (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 { + matches!((*x).clone(), StatusType::Enabled(StatusLvl::Ch(_))) + } + ) + { + 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 (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 (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 (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()), @@ -299,10 +1377,7 @@ impl ModulesManager { ) } - let mut dbt = self.statusdb.write().await; - let statusvector = dbt.entry(in_module.clone()).or_insert(Vec::new()); - - 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()); @@ -329,3 +1404,798 @@ impl ModulesManager { // 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 case_insensitive_test() { + Log::set_file_ext(Extension::Log); + assert_eq!( + BotModule("TEST".to_string()), + BotModule("test".to_string()) + ); + } + + + /* + 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; + + + } + + + + + /* + 1. Create new ModulesManager & Identity Manager + 2. modmgr.affirm_in_statusdb(Experiments01,Custom) + + 3. affirm when BotAdmin attempts to exec_enable on the following + a. Channel Level , where they are not a Mod + b. Channel Level , when they are a Mod + c. Instance Level + 4. affirm when BotAdmin attempts to exec_disable on the following + a. Channel Level , where they are not a Mod + b. Channel Level , when they are a Mod + c. Instance Level + d. force disable + + + 1. Create new ModulesManager & Identity Manager + 2. modmgr.affirm_in_statusdb(Experiments01,Custom) + + 3. affirm when Mod attempts to exec_enable on the following + a. Channel Level , where they are not a Mod + b. Channel Level , when they are a Mod + c. Instance Level + 4. affirm when Mod attempts to exec_disable on the following + a. Channel Level , where they are not a Mod + b. Channel Level , when they are a Mod + c. Instance Level + d. force disable + + */ + + async fn inner_enable_disable_complex( + requestor:String, + channel:ChType, + idmgr:IdentityManager, + modsmgr:Arc) + { + + /* + Parent Tests would involve : + - Testing with a BotAdmin User + - Testing with a Mod User + - Testing with a Regular Chatter + */ + + enum TestScenarios { + BotadminUser, + ModUser, + RegularChatter, + // ModuleDoesNotExist, // preferring instead to handle in it's own smaller test + } + + let mut idlock = idmgr.clone(); + + let requestor_badge = None; // If they are a Mod on the Given Channel already, that can be evaluated without the current badge + + const OF_CMD_CHANNEL:ChType = Channel(String::new()); + + let (admin_level_access,_) = idlock.can_user_run(requestor.clone(), channel.clone(), requestor_badge.clone(), + vec![ + identity::UserRole::BotAdmin, + ]).await; + + + let (chnl_elevated_access,_) = idlock.can_user_run(requestor.clone(), channel.clone(), requestor_badge.clone(), + vec![ + identity::UserRole::Mod(OF_CMD_CHANNEL), + identity::UserRole::SupMod(OF_CMD_CHANNEL), + identity::UserRole::Broadcaster, + ]).await; + + + let current_test_scenario = + match admin_level_access { + Permissible::Allow => { + match chnl_elevated_access { + Permissible::Allow => { TestScenarios::BotadminUser }, + Permissible::Block => { TestScenarios::BotadminUser } + } + }, + Permissible::Block => { + match chnl_elevated_access { + Permissible::Allow => { TestScenarios::ModUser }, + Permissible::Block => { TestScenarios::RegularChatter } + } + } + }; + + + + // [x] 2. modmgr.affirm_in_statusdb(Experiments01,Custom) + + let in_module = BotModule("Experiments01".to_string()); + let in_modgroup = ModGroup::Custom; + + modsmgr.affirm_in_statusdb(in_module.clone(), in_modgroup.clone()).await; + + /* + [x] 3. affirm when BotAdmin attempts to exec_enable on the following + a. Channel Level , where they are not a Mod + */ + + + // [-] requestor_badge: Option, + + // [x] trg_module: ModType, + let trg_module = in_module; + + // [x] trg_level: StatusLvl, + + let trg_level = StatusLvl::Ch(channel.clone()); // setting to Channel Level + + + // [x] id: Arc>, + let id = Arc::new(RwLock::new(idmgr.clone())); + + + let rslt = modsmgr.exec_enable(requestor.clone(), + None, + trg_module.clone(), + trg_level.clone(), + id.clone()).await; + + match current_test_scenario { + TestScenarios::BotadminUser => + assert_eq!(rslt,ChangeResult::Failed("Promote yourself Temporarily First".to_string())), + TestScenarios::ModUser => + assert_eq!(rslt,ChangeResult::Success("Enabled at Channel Level".to_string())), + TestScenarios::RegularChatter => + assert_eq!(rslt,ChangeResult::Failed("You're not allowed".to_string())), + } + + + /* + [x] 3. affirm when BotAdmin attempts to exec_enable on the following + b. Channel Level , when they are a Mod + */ + + // [x] requestor_badge: Option, + + let requestor_badge = match current_test_scenario { + TestScenarios::BotadminUser => + Some(ChatBadge::Mod), // setting badge to Mod -- for the Problem Scenario . They are both BotAdmin & Mod + TestScenarios::ModUser => + Some(ChatBadge::Mod), // setting badge to Mod + TestScenarios::RegularChatter => + None, // setting badge to None + } ; + + + let rslt = modsmgr.exec_enable(requestor.clone(), + requestor_badge, + trg_module.clone(), + trg_level.clone(), + id.clone()).await; + + match current_test_scenario { + TestScenarios::BotadminUser => + assert_eq!(rslt,ChangeResult::Success("Enabled at Channel Level".to_string())), + TestScenarios::ModUser => + assert_eq!(rslt,ChangeResult::Success("Enabled at Channel Level".to_string())), + TestScenarios::RegularChatter => + assert_eq!(rslt,ChangeResult::Failed("You're not allowed".to_string())), + } + + /* + [x] 3. affirm when BotAdmin attempts to exec_enable on the following + c. Instance Level + */ + + let trg_level = StatusLvl::Instance; // setting to Instance level + + let requestor_badge = match current_test_scenario { + TestScenarios::BotadminUser => + None, + TestScenarios::ModUser => + Some(ChatBadge::Mod), + TestScenarios::RegularChatter => + None, // setting badge to None + }; + + let rslt = modsmgr.exec_enable(requestor.clone(), + requestor_badge, // passing based on scenario + trg_module.clone(), + trg_level.clone(), + id.clone()).await; + + + match current_test_scenario { + TestScenarios::BotadminUser => + assert_eq!(rslt,ChangeResult::Success("Enabled at Instance Level".to_string())), + TestScenarios::ModUser => + assert_eq!(rslt,ChangeResult::Failed("You're not allowed".to_string())), + TestScenarios::RegularChatter => + assert_eq!(rslt,ChangeResult::Failed("You're not allowed".to_string())), + } + + /* + [x] 4. affirm when BotAdmin attempts to exec_disable on the following + a. Channel Level , where they are not a Mod + */ + + let trg_level = StatusLvl::Ch(channel.clone()); // setting to Channel Level + + let rslt: ChangeResult = modsmgr.exec_disable(requestor.clone(), + None, // Does not have a ChatBadge like Mod + trg_module.clone(), + trg_level.clone(), + false, + id.clone()).await; + + match current_test_scenario { + TestScenarios::BotadminUser => + assert_eq!(rslt,ChangeResult::Success("Disabled at Channel Level".to_string())), + TestScenarios::ModUser => + assert_eq!(rslt,ChangeResult::Success("Disabled at Channel Level".to_string())), + TestScenarios::RegularChatter => + assert_eq!(rslt,ChangeResult::Failed("You're not allowed".to_string())), + } + + + /* + [x] 4. affirm when BotAdmin attempts to exec_disable on the following + b. Channel Level , when they are a Mod + */ + + + let trg_level = StatusLvl::Ch(channel.clone()); // setting to Channel Level + + let requestor_badge = match current_test_scenario { + TestScenarios::BotadminUser => + None, + TestScenarios::ModUser => + Some(ChatBadge::Mod), + TestScenarios::RegularChatter => + None, // setting badge to None + }; + + let rslt: ChangeResult = modsmgr.exec_disable(requestor.clone(), + requestor_badge, + trg_module.clone(), + trg_level.clone(), + false, + id.clone()).await; + + + match current_test_scenario { + TestScenarios::BotadminUser => + assert_eq!(rslt,ChangeResult::Success("Disabled at Channel Level".to_string())), + TestScenarios::ModUser => + assert_eq!(rslt,ChangeResult::Success("Disabled at Channel Level".to_string())), + TestScenarios::RegularChatter => + assert_eq!(rslt,ChangeResult::Failed("You're not allowed".to_string())), + } + + + /* + [x] 4. affirm when BotAdmin attempts to exec_disable on the following + c. Instance Level + */ + + let trg_level = StatusLvl::Instance; // setting to Instance level + + + let rslt: ChangeResult = modsmgr.exec_disable(requestor.clone(), + None, // Does not have a ChatBadge like Mod + trg_module.clone(), + trg_level.clone(), + false, + id.clone()).await; + + match current_test_scenario { + TestScenarios::BotadminUser => + assert_eq!(rslt,ChangeResult::Success("Disabled at Instance Level".to_string())), + TestScenarios::ModUser => + assert_eq!(rslt,ChangeResult::Failed("You're not allowed".to_string())), + TestScenarios::RegularChatter => + assert_eq!(rslt,ChangeResult::Failed("You're not allowed".to_string())), + } + + /* + [ ] 4. affirm when BotAdmin attempts to exec_disable on the following + d. force disable + */ + + let trg_level = StatusLvl::Instance; // setting to Instance level + + let rslt: ChangeResult = modsmgr.exec_disable(requestor.clone(), + None, // Does not have a ChatBadge like Mod + trg_module.clone(), + trg_level.clone(), + true, // force flag - true + id.clone()).await; + + match current_test_scenario { + TestScenarios::BotadminUser => + assert_eq!(rslt,ChangeResult::Success("Forced Disable".to_string())), + TestScenarios::ModUser => + assert_eq!(rslt,ChangeResult::Failed("You're not allowed".to_string())), + TestScenarios::RegularChatter => + assert_eq!(rslt,ChangeResult::Failed("You're not allowed".to_string())), + } + + + } + + + #[tokio::test] + async fn enable_disable_bot_admin_workflow() { + Log::set_file_ext(Extension::Log); + /* + + 1. Create new ModulesManager & Identity Manager + 2. modmgr.affirm_in_statusdb(Experiments01,Custom) + + 3. affirm when BotAdmin attempts to exec_enable on the following + a. Channel Level , where they are not a Mod + b. Channel Level , when they are a Mod + c. Instance Level + 4. affirm when BotAdmin attempts to exec_disable on the following + a. Channel Level , where they are not a Mod + b. Channel Level , when they are a Mod + c. Instance Level + d. force disable + + */ + + + // [x] 1. Create new ModulesManager & Identity Manager + let idmgr = IdentityManager::init(); + let modsmgr = ModulesManager::init().await; + + /* + [x] 3. affirm when BotAdmin attempts to exec_enable on the following + a. Channel Level , where they are not a Mod + */ + + // [x] Create BotAdmin first + + let requestor = "botadministrator".to_string(); + + idmgr.affirm_chatter_in_db(requestor.clone()).await; + idmgr + .add_role(requestor.clone(), identity::UserRole::BotAdmin) + .await; + + let rslt = idmgr + .getspecialuserroles(requestor.clone(), None) + .await; + + assert!(rslt.contains(&identity::UserRole::BotAdmin)); + + let channel = ChType::Channel("somechannel".to_string()); + + + inner_enable_disable_complex(requestor, channel, idmgr, modsmgr).await; + + + + } + + + + #[tokio::test] + async fn enable_disable_mod_workflow() { + Log::set_file_ext(Extension::Log); + + /* + 1. Create new ModulesManager & Identity Manager + 2. modmgr.affirm_in_statusdb(Experiments01,Custom) + + 3. affirm when Mod attempts to exec_enable on the following + a. Channel Level , where they are not a Mod + b. Channel Level , when they are a Mod + c. Instance Level + 4. affirm when Mod attempts to exec_disable on the following + a. Channel Level , where they are not a Mod + b. Channel Level , when they are a Mod + c. Instance Level + d. force disable + */ + + + // [x] 1. Create new ModulesManager & Identity Manager + let idmgr = IdentityManager::init(); + let modsmgr = ModulesManager::init().await; + + + let requestor = "mod_user".to_string(); + // let botadmin_badge = &None; + let channel = ChType::Channel("somechannel".to_string()); + + + idmgr.affirm_chatter_in_db(requestor.clone()).await; + idmgr + .add_role(requestor.clone(), identity::UserRole::Mod(channel.clone())) + .await; + + let rslt = idmgr + .getspecialuserroles( + requestor.clone(), + Some(channel.clone()) // None if BotAdmin ; Otherwise, pass Some(Channel) + ) + .await; + + assert!(rslt.contains(&identity::UserRole::Mod(channel.clone()))); + + + inner_enable_disable_complex(requestor, channel, idmgr, modsmgr).await; + + + + } + + #[tokio::test] + async fn enable_disable_chatter_workflow() { + Log::set_file_ext(Extension::Log); + + + /* + 1. Create new ModulesManager & Identity Manager + 2. modmgr.affirm_in_statusdb(Experiments01,Custom) + + 3. affirm when Mod attempts to exec_enable on the following + a. Channel Level , where they are not a Mod + b. Channel Level , when they are a Mod + c. Instance Level + 4. affirm when Mod attempts to exec_disable on the following + a. Channel Level , where they are not a Mod + b. Channel Level , when they are a Mod + c. Instance Level + d. force disable + */ + + + // [x] 1. Create new ModulesManager & Identity Manager + let idmgr = IdentityManager::init(); + let modsmgr = ModulesManager::init().await; + + + let requestor = "regular_user".to_string(); + let channel = ChType::Channel("somechannel".to_string()); + + + idmgr.affirm_chatter_in_db(requestor.clone()).await; + + let rslt = idmgr + .getspecialuserroles( + requestor.clone(), + Some(channel.clone()) // None if BotAdmin ; Otherwise, pass Some(Channel) + ) + .await; + + assert!(!rslt.contains(&identity::UserRole::Mod(channel.clone())) || + !rslt.contains(&identity::UserRole::BotAdmin)); + + + inner_enable_disable_complex(requestor, channel, idmgr, modsmgr).await; + + } + + + #[tokio::test] + async fn enable_disable_modulenotexist_workflow() { + Log::set_file_ext(Extension::Log); + + + // [x] 1. Create new ModulesManager & Identity Manager + let idmgr = IdentityManager::init(); + let modsmgr = ModulesManager::init().await; + + + let requestor = "regular_user".to_string(); + + let channel = ChType::Channel("somechannel".to_string()); + + + idmgr.affirm_chatter_in_db(requestor.clone()).await; + + + let rslt = idmgr + .getspecialuserroles( + requestor.clone(), + Some(channel.clone()) // None if BotAdmin ; Otherwise, pass Some(Channel) + ) + .await; + + assert!(!rslt.contains(&identity::UserRole::Mod(channel.clone())) || + !rslt.contains(&identity::UserRole::BotAdmin)); + + // After above, regular chatter is created + + // [x] 2. modmgr.affirm_in_statusdb(Existing_Module,Custom) + + let in_module = BotModule("Existing_Module".to_string()); + let in_modgroup = ModGroup::Custom; + + modsmgr.affirm_in_statusdb(in_module.clone(), in_modgroup.clone()).await; + + + let trg_level = StatusLvl::Ch(channel.clone()); // setting to Channel Level + + + // [x] Test with Non Existing module > exec + + let trg_module = BotModule("Non_Existent_Module".to_string()); + + let rslt = modsmgr.exec_enable(requestor.clone(), + None, + trg_module.clone(), + trg_level.clone(), + Arc::new(RwLock::new(idmgr.clone()))).await; + + assert_eq!(rslt,ChangeResult::Failed("Module doesn't exist".to_string())); + + // [x] Test with Non Existing module > disable + + let trg_module = BotModule("Non_Existent_Module".to_string()); + + let rslt = modsmgr.exec_disable(requestor.clone(), + None, + trg_module.clone(), + trg_level.clone(), + false, + Arc::new(RwLock::new(idmgr))).await; + + assert_eq!(rslt,ChangeResult::Failed("Module doesn't exist".to_string())); + + } + +} diff --git a/src/core/identity.rs b/src/core/identity.rs index f5e710b..0a27b60 100644 --- a/src/core/identity.rs +++ b/src/core/identity.rs @@ -8,7 +8,7 @@ use twitch_irc::message::PrivmsgMessage; use casual_logger::Log; use crate::core::bot_actions::actions_util::{self, BotAR}; -use crate::core::botinstance::ChType; +use crate::core::botinstance::{ChType,ChangeResult}; use crate::core::botlog; use crate::core::botmodules::{BotActionTrait, BotCommand, BotModule, ModulesManager}; @@ -67,7 +67,8 @@ pub async fn init(mgr: Arc) { ], }; - tempb.add_to_modmgr(Arc::clone(&mgr)).await; + // tempb.add_to_modmgr(Arc::clone(&mgr)).await; + tempb.add_core_to_modmgr(Arc::clone(&mgr)).await; async fn cmd_promote(bot: BotAR, msg: PrivmsgMessage) { botlog::trace( @@ -238,7 +239,9 @@ pub async fn init(mgr: Arc) { ], }; - tempb.add_to_modmgr(Arc::clone(&mgr)).await; + // tempb.add_to_modmgr(Arc::clone(&mgr)).await; + // add_core_to_modmgr + tempb.add_core_to_modmgr(Arc::clone(&mgr)).await; async fn cmd_demote(bot: BotAR, msg: PrivmsgMessage) { botlog::debug( @@ -428,7 +431,9 @@ pub async fn init(mgr: Arc) { ], }; - tempcomm.add_to_modmgr(Arc::clone(&mgr)).await; + // tempcomm.add_to_modmgr(Arc::clone(&mgr)).await; + // add_core_to_modmgr + tempcomm.add_core_to_modmgr(Arc::clone(&mgr)).await; async fn getroles(bot: BotAR, msg: PrivmsgMessage) { botlog::debug( @@ -597,18 +602,18 @@ pub struct IdentityManager { > */ -#[derive(Debug)] +#[derive(Debug, Clone)] pub enum ChatBadge { Broadcaster, Mod, } -#[derive(Debug, PartialEq, Eq)] -pub enum ChangeResult { - Success(String), - Failed(String), - NoChange(String), -} +// #[derive(Debug, PartialEq, Eq)] +// pub enum ChangeResult { +// Success(String), +// Failed(String), +// NoChange(String), +// } impl IdentityManager { pub fn init() -> IdentityManager { @@ -625,7 +630,8 @@ impl IdentityManager { } } - async fn add_role(&self, trgchatter: String, trg_role: UserRole) { + // => 03.22 - Force - Made public because botmodules unit tests + pub async fn add_role(&self, trgchatter: String, trg_role: UserRole) { let mut srulock = self.special_roles_users.write().await; let mut usrrolelock = srulock .get_mut(&trgchatter) @@ -647,7 +653,8 @@ impl IdentityManager { } } - async fn affirm_chatter_in_db(&self, trgchatter: String) { + // => 03.22 - Force - Made public because botmodules unit tests + pub async fn affirm_chatter_in_db(&self, trgchatter: String) { let mut srulock = self.special_roles_users.write().await; srulock .entry(trgchatter.clone()) @@ -689,59 +696,6 @@ impl IdentityManager { } } - // if &msg.badges.contains(Badge{}) { - - // } - - // if let Some(sender_badge) = sender_badge { - // match sender_badge { - // Some(sender_badge) => { - // return &self.can_user_run(msg.sender.name.to_owned(), - // ChType::Channel(msg.channel_login.to_owned()), - // sender_badge, - // cmdreqroles - // return self.can_user_run(msg.sender.name.to_owned(), - // let a = Arc::new(Mutex::new(self)); - // let mut a = a.lock().await; - // let a = **a; - // let a = a.can_user_run(msg.sender.name.to_owned(), - // ChType::Channel(msg.channel_login.to_owned()), - // sender_badge, - // cmdreqroles - // ) ; - // let a = *self; - // let a = Arc::new(Mutex::new(a)); - // let a = a.lock().await.can_user_run(msg.sender.name.to_owned(), - // ChType::Channel(msg.channel_login.to_owned()), - // sender_badge, - // cmdreqroles - // ) ; - // return a; - // return self.can_user_run(msg.sender.name.to_owned(), - // ChType::Channel(msg.channel_login.to_owned()), - // sender_badge, - // cmdreqroles - // ).await - - // * NOTE : We're preferring to pass the ChangeResult up , where we have access to Chat via BotInstance - // that have more strained chatting rules - // let evalpermissible = self.can_user_run(msg.sender.name.to_owned(), - // ChType::Channel(msg.channel_login.to_owned()), - // sender_badge, - // cmdreqroles - // ).await ; - // evalpermissible - // // } - // None => { - - // } - // here , sender_badge is likely None - // This could be a regular chatter, BotAdmin,SupserMod - - // [ ] Call can_user_run() - // (self,Permissible::Block) - // (Permissible::Block,ChangeResult::NoChange("".to_string())) - self.can_user_run( msg.sender.name.to_owned(), ChType::Channel(msg.channel_login.to_owned()), @@ -749,6 +703,7 @@ impl IdentityManager { cmdreqroles, ) .await + } pub async fn can_user_run( diff --git a/src/custom/experiments.rs b/src/custom/experiments.rs index de2f424..529e551 100644 --- a/src/custom/experiments.rs +++ b/src/custom/experiments.rs @@ -125,7 +125,7 @@ async fn good_girl(bot: BotAR, msg: PrivmsgMessage) { Some(&msg), ); - let rollwin = rand::thread_rng().gen_ratio(1, 8); + let rollwin = rand::thread_rng().gen_ratio(1, 1); if rollwin { botlog::debug(