diff --git a/.cargo/config.toml b/.cargo/config.toml index 2bcdad5..46ba45d 100644 --- a/.cargo/config.toml +++ b/.cargo/config.toml @@ -1,4 +1,4 @@ [env] # Based on https://doc.rust-lang.org/cargo/reference/config.html -OtherBots = "Supibot,buttsbot,PotatBotat,StreamElements" +OtherBots = "Supibot,buttsbot,PotatBotat,StreamElements,yuumeibot" diff --git a/Cargo.lock b/Cargo.lock index 18195c8..da35376 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -41,6 +41,17 @@ dependencies = [ "libc", ] +[[package]] +name = "async-recursion" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30c5ef0ede93efbf733c1a727f3b6b5a1060bbedd5600183e66f6e4be4af0ec5" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "async-trait" version = "0.1.77" @@ -122,9 +133,9 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "chrono" -version = "0.4.34" +version = "0.4.35" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5bc015644b92d5890fab7489e49d21f879d5c990186827d42ec511919404f38b" +checksum = "8eaf5903dcbc0a39312feb77df2ff4c76387d591b9fc7b04a238dcf8bb62639a" dependencies = [ "android-tzdata", "iana-time-zone", @@ -194,8 +205,10 @@ checksum = "25cbce373ec4653f1a01a31e8a5e5ec0c622dc27ff9c4e6606eefef5cbbed4a5" name = "forcebot_rs" version = "0.1.0" dependencies = [ + "async-recursion", "async-trait", "casual_logger", + "chrono", "dotenv", "futures", "rand", diff --git a/Cargo.toml b/Cargo.toml index 87f8cfa..f3f7724 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,7 +12,10 @@ twitch-irc = "5.0.1" rand = { version = "0.8.5", features = [] } futures = "0.3" async-trait = "0.1.77" +async-recursion = "1.1.0" casual_logger = "0.6.5" +chrono = "0.4.35" + [lib] name = "bot_lib" diff --git a/src/core/bot_actions.rs b/src/core/bot_actions.rs index 2e6b456..8941654 100644 --- a/src/core/bot_actions.rs +++ b/src/core/bot_actions.rs @@ -1,27 +1,83 @@ + +use twitch_irc::message::PrivmsgMessage; +use std::sync::Arc; +use tokio::sync::RwLock; + +use crate::core::botinstance::BotInstance; + +use super::{botmodules::{BotAction, BotModule}, identity::ChatBadge}; + + +pub type BotAR = Arc>; +pub type ActAR = Arc>; + +#[derive(Clone)] +pub struct ExecBodyParams { + pub bot : BotAR, + pub msg : PrivmsgMessage, + pub parent_act : ActAR , +} + + +impl ExecBodyParams { + + pub async fn get_parent_module(&self) -> Option { + + let parent_act = Arc::clone(&self.parent_act); + let parent_act_lock = parent_act.read().await; + let act = &(*parent_act_lock); + match act { + BotAction::C(c) => { + let temp = c.module.clone(); + Some(temp) + }, + BotAction::L(l) => { + let temp = l.module.clone(); + Some(temp) + }, + _ => None + } + } + + pub fn get_sender(&self) -> String { + self.msg.sender.name.clone() + } + + pub fn get_sender_chatbadge(&self) -> Option { + + let mut requestor_badge_mut: Option = None; + + for b in &self.msg.badges { + if b.name == "moderator" { + requestor_badge_mut = Some(ChatBadge::Mod); + } else if b.name == "broadcaster" { + requestor_badge_mut = Some(ChatBadge::Broadcaster); + } + } + requestor_badge_mut + } + + +} + + + pub mod actions_util { + use super::*; + use std::boxed::Box; use std::future::Future; use std::pin::Pin; - use std::sync::Arc; - - use tokio::sync::{Mutex, RwLock}; - - use twitch_irc::message::PrivmsgMessage; - - use crate::core::botinstance::BotInstance; - - pub type BotAM = Arc>; - pub type BotAR = Arc>; pub type ExecBody = Box< - dyn Fn(BotAR, PrivmsgMessage) -> Pin + Send>> + Send + Sync, + dyn Fn(ExecBodyParams) -> Pin + Send>> + Send + Sync, >; - pub fn asyncbox(f: fn(BotAR, PrivmsgMessage) -> T) -> ExecBody + pub fn asyncbox(f: fn(ExecBodyParams) -> T) -> ExecBody where T: Future + Send + 'static, { - Box::new(move |a, b| Box::pin(f(a, b))) + Box::new(move |a| Box::pin(f(a))) } } diff --git a/src/core/botinstance.rs b/src/core/botinstance.rs index 1aa0e49..07b481d 100644 --- a/src/core/botinstance.rs +++ b/src/core/botinstance.rs @@ -17,9 +17,9 @@ use casual_logger::Log; use crate::core::ratelimiter::RateLimiter; -use crate::core::bot_actions::actions_util::BotAR; +use crate::core::bot_actions::BotAR; use crate::core::botmodules::ModulesManager; -use crate::core::identity::{IdentityManager, Permissible,self}; +use crate::core::identity::{IdentityManager, Permissible}; use crate::core::botlog; use crate::core::chat::Chat; @@ -33,14 +33,11 @@ pub enum ChangeResult { } - #[derive(Debug, PartialEq, Eq, Hash, Clone)] -pub enum ChType { - Channel(String), -} -pub use ChType::Channel; +pub struct Channel(pub String); +use super::bot_actions::ExecBodyParams; use super::botmodules::StatusType; #[derive(Clone)] @@ -51,7 +48,7 @@ pub struct BotManagers { impl BotManagers { pub fn init( - ratelimiters: HashMap, + ratelimiters: HashMap, client: TwitchIRCClient, StaticLoginCredentials>, ) -> BotManagers { BotManagers { @@ -75,11 +72,11 @@ impl ArcBox { pub struct BotInstance { pub prefix: char, - pub bot_channel: ChType, + pub bot_channel: Channel, pub incoming_messages: Arc>>, pub botmodules: Arc, pub twitch_oauth: String, - pub bot_channels: Vec, + pub bot_channels: Vec, pub botmgrs: BotManagers, //modesmgr : ModesManager, // [FUTURE] Silent/Quiet , uwu , frisky/horny } @@ -157,6 +154,18 @@ impl BotInstance { while let Some(message) = msglock.recv().await { + + botlog::trace( + format!( + "[TRACE][ServerMessage] > {:?}", + message + ) + .as_str(), + Some("BotInstance > runner()".to_string()), + None, + ); + + match message { ServerMessage::Notice(msg) => { botlog::notice( @@ -165,8 +174,10 @@ impl BotInstance { Some("BotInstance > runner()".to_string()), None, ); + Log::flush(); } ServerMessage::Privmsg(msg) => { + botlog::debug( format!( "[Twitch Chat > {}] > {}: {}", @@ -177,6 +188,18 @@ impl BotInstance { Some(&msg), ); + + botlog::trace( + format!( + "[TRACE][Twitch Chat > {}] > {}: {:?}", + msg.channel_login, msg.sender.name, msg + ) + .as_str(), + Some("BotInstance > runner()".to_string()), + Some(&msg), + ); + Log::flush(); + BotInstance::listener_main_prvmsg(Arc::clone(&bot), &msg).await; } ServerMessage::Whisper(msg) => { @@ -185,6 +208,7 @@ impl BotInstance { Some("BotInstance > runner()".to_string()), None, ); + Log::flush(); } ServerMessage::Join(msg) => { botlog::notice( @@ -192,6 +216,7 @@ impl BotInstance { Some("BotInstance > runner()".to_string()), None, ); + Log::flush(); } ServerMessage::Part(msg) => { botlog::notice( @@ -199,6 +224,7 @@ impl BotInstance { Some("BotInstance > runner()".to_string()), None, ); + Log::flush(); } _ => {} }; @@ -229,6 +255,42 @@ impl BotInstance { // // [ ] #todo Need to run through all Listener Bodies for Enabled Modules for the context of the message (e.g., ModStatus is Enabled in the context for the channel) + + /* + [ ] What we should do instead is : + 1. Check if the message is related to a Reply (so we know how many arguments we should skip) + 2. If a reply, skip the first argument + */ + + let mut msgiter= msg + .message_text + .split(' '); + + let arg1 = msgiter.next(); + let arg2 = msgiter.next(); + + let reply = if let Some(Some(replyid)) = msg.source.tags.0.get("reply-thread-parent-msg-id") { + Some(replyid) + } else { None } + ; + + + let inpt = match reply { + None => { // Regular message, use the first arg as the command + match arg1 { + None => return, // return if no argument found + Some(a) => a, + } + }, + Some(_) => { + match arg2 { // A reply message, use the 2nd arg as the command + None => return, // return if no argument found + Some(a) => a, + } + }, + }; + + let botlock = bot.read().await; let actsdb = Arc::clone(&botlock.botmodules.botactions); let actsdblock = actsdb.read().await; @@ -239,9 +301,15 @@ impl BotInstance { Some(msg), ); + for acts in (*actsdblock).values() { + + for a in acts { - match a { + + let act_clone = Arc::clone(a); + + match &(*act_clone.read().await) { crate::core::botmodules::BotAction::C(c) => { /* BotCommand handling - @@ -259,11 +327,7 @@ impl BotInstance { Some(msg), ); - let inpt = msg - .message_text - .split(' ') - .next() - .expect("ERROR during BotCommand"); + // [x] Check if a bot command based on ... // [x] prefix + command @@ -299,7 +363,7 @@ impl BotInstance { let modmgr = Arc::clone(&botlock.botmodules); let modstatus = modmgr.modstatus( c.module.clone(), - ChType::Channel(msg.channel_login.to_string())).await; + Channel(msg.channel_login.to_string())).await; if let StatusType::Disabled(a) = modstatus { @@ -314,33 +378,51 @@ impl BotInstance { ); - const OF_CMD_CHANNEL:ChType = Channel(String::new()); + let botclone = Arc::clone(&bot); + let botlock = botclone.read().await; + let id = botlock.get_identity(); + let id = Arc::clone(&id); + let idlock = id.read().await; // <-- [ ] 03.24 - seems to work + let user_roles = idlock.getspecialuserroles( + msg.sender.name.clone(), + Some(Channel(msg.channel_login.clone())) + ).await; - 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 + + botlog::trace( + &format!("For Disabled Command Evaluating User Roles {:?}", user_roles), + Some("BotInstance > listener_main_prvmsg()".to_string()), + Some(msg), + ); + + // Only respond to those with th ebelow User Roles + + let outstr = + format!("sadg Module is disabled : {:?}",a); + + + let params = ExecBodyParams { + bot : Arc::clone(&bot), + msg : (*msg).clone(), + parent_act : Arc::clone(&act_clone), }; - 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; - } + // When sending a BotMsgTypeNotif, send_botmsg does Roles related validation as required + + botlock.botmgrs.chat.send_botmsg(super::chat::BotMsgType::Notif( + outstr + ), + params, + ).await; return; }; + botlog::trace( + "ACQUIRING WRITE LOCK : ID", + Some("BotInstance > listener_main_prvmsg()".to_string()), + Some(msg), + ); let eval = { @@ -374,7 +456,51 @@ impl BotInstance { let botlock = bot.read().await; let outstr = "o7 a Mod. I kneel to serve! pepeKneel ".to_string(); - botlock.botmgrs.chat.say_in_reply_to(msg, outstr).await; + + + let params = ExecBodyParams { + bot : Arc::clone(&bot), + msg : (*msg).clone(), + parent_act : Arc::clone(&act_clone), + + }; + + botlock.botmgrs.chat.send_botmsg(super::chat::BotMsgType::Notif( + outstr.to_string() + ), + params.clone(), + ).await; + + } + + if innerstr + .to_lowercase() + .contains(&"Auto Promoted VIP".to_lowercase()) + { + botlog::notice( + "Assigning VIP UserRole to VIP", + Some("botinstance > listener_main_prvmsg()".to_string()), + Some(msg), + ); + + let botlock = bot.read().await; + let outstr = + "❤️ a VIP - love ya!".to_string(); + + + let params = ExecBodyParams { + bot : Arc::clone(&bot), + msg : (*msg).clone(), + parent_act : Arc::clone(&act_clone), + + }; + + botlock.botmgrs.chat.send_botmsg(super::chat::BotMsgType::Notif( + outstr.to_string() + ), + params.clone(), + ).await; + } } @@ -387,12 +513,15 @@ impl BotInstance { ); let a = Arc::clone(&bot); - c.execute(a, msg.clone()).await; + c.execute(ExecBodyParams { + bot : a, + msg : msg.clone() , + parent_act : Arc::clone(&act_clone), + }).await; botlog::trace( "exit out of execution", Some("BotInstance > listener_main_prvmsg()".to_string()), - // Some(&msg), Some(msg), ); } @@ -411,13 +540,12 @@ impl BotInstance { crate::core::botmodules::BotAction::L(l) => { 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; + Channel(msg.channel_login.to_string())).await; if let StatusType::Disabled(a) = modstatus { @@ -433,7 +561,11 @@ impl BotInstance { } else { let a = Arc::clone(&bot); - l.execute(a, msg.clone()).await; + l.execute(ExecBodyParams { + bot : a, + msg : msg.clone() , + parent_act : Arc::clone(&act_clone), + } ).await; } } diff --git a/src/core/botmodules.rs b/src/core/botmodules.rs index cef3b2f..c104fdd 100644 --- a/src/core/botmodules.rs +++ b/src/core/botmodules.rs @@ -3,12 +3,12 @@ ModulesManager is used to manage Modules and BotActions associated with those modules pub struct ModulesManager { - statusdb: HashMap>, - botactions: HashMap>, + 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) +- 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 { @@ -19,28 +19,28 @@ Example */ + +const OF_CMD_CHANNEL:Channel = Channel(String::new()); + + use core::panic; use std::collections::HashMap; 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::bot_actions::actions_util; -use crate::core::botinstance::{BotInstance, ChType,ChangeResult}; +use crate::core::bot_actions::ExecBodyParams; +use crate::core::botinstance::{BotInstance, Channel,ChangeResult}; use crate::core::botlog; use crate::core::identity::{self, Permissible,IdentityManager}; use crate::core::bot_actions; -pub use ChType::Channel; -pub use ModType::BotModule; use std::hash::{Hash, Hasher}; @@ -49,8 +49,6 @@ 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")), @@ -71,7 +69,8 @@ pub async fn init(mgr: Arc) { // 2. Add the BotAction to ModulesManager botc1.add_core_to_modmgr(Arc::clone(&mgr)).await; - async fn cmd_enable(bot: BotAR, msg: PrivmsgMessage) { + // async fn cmd_enable(bot: BotAR, msg: PrivmsgMessage) { + async fn cmd_enable(params : ExecBodyParams) { /* There should be additional validation checks - BotAdmins can only run instance level (-i) enables @@ -104,12 +103,11 @@ pub async fn init(mgr: Arc) { */ - // [x] Unwraps arguments from message let (arg1, arg2) = { - let mut argv = msg.message_text.split(' '); + let mut argv = params.msg.message_text.split(' '); argv.next(); // Skip the command name @@ -126,8 +124,8 @@ pub async fn init(mgr: Arc) { &self, requestor: String, requestor_badge: Option, - trg_module: ModType, - // channel: Option, + trg_module: BotModule, + // channel: Option, trg_level: StatusLvl, bot: BotAR, ) -> ChangeResult @@ -135,26 +133,28 @@ pub async fn init(mgr: Arc) { // [x] requestor: String, - let requestor = msg.clone().sender.name; + let requestor = params.msg.clone().sender.name; // [x] requestor_badge: Option, let mut requestor_badge_mut: Option = None; - for b in &msg.badges { + for b in ¶ms.msg.badges { if b.name == "moderator" { requestor_badge_mut = Some(ChatBadge::Mod); } else if b.name == "broadcaster" { requestor_badge_mut = Some(ChatBadge::Broadcaster); + } else if b.name == "vip" { + requestor_badge_mut = Some(ChatBadge::VIP); } } let requestor_badge = requestor_badge_mut; - // [x] trg_module: ModType, - // - [x] Need to validate an actual ModType - otherwise, fail or exit the cmd + // [x] trg_module: BotModule, + // - [x] Need to validate an actual BotModule - otherwise, fail or exit the cmd let trg_module = if (arg1 == Some("-i")) || (arg1 == Some("-f")) { arg2 } else { arg1 }; @@ -162,21 +162,28 @@ pub async fn init(mgr: Arc) { // if let None = trg_module { if trg_module.is_none() { - let botlock = bot.read().await; + // let botlock = params.bot.read().await; let outmsg = "uuh You need to pass a module"; botlog::debug( outmsg, Some("botmodules.rs > cmd_enable()".to_string()), - Some(&msg), + Some(¶ms.msg), ); - - botlock - .botmgrs - .chat - .say_in_reply_to(&msg, outmsg.to_string()) - .await; + + // We should call a notification around here + + let bot = params.clone().bot; + + let botclone = Arc::clone(&bot); + let botlock = botclone.read().await; + + botlock.botmgrs.chat.send_botmsg(super::chat::BotMsgType::Notif( + outmsg.to_string() + ), + params.clone(), + ).await; return; @@ -185,29 +192,29 @@ pub async fn init(mgr: Arc) { // [x] trg_level: StatusLvl, - let currchnl = msg.channel_login.to_lowercase(); + let currchnl = params.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)) } + else { StatusLvl::Ch(Channel(currchnl)) } ; - let botlock = bot.read().await; + let botlock = params.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()), + BotModule(trg_module.unwrap().to_string()), trg_level, id).await; + + // We should call a notification around here + let outmsg = match rslt.clone() { ChangeResult::Failed(a) => format!("Stare Failed : {}",a), @@ -215,12 +222,13 @@ pub async fn init(mgr: Arc) { ChangeResult::Success(a) => format!("YAAY Success : {}",a), }; - botlock - .botmgrs - .chat - .say_in_reply_to(&msg, outmsg.to_string()) - .await; + botlock.botmgrs.chat.send_botmsg(super::chat::BotMsgType::Notif( + outmsg.to_string() + ), + params.clone(), + ).await; + } @@ -245,7 +253,8 @@ pub async fn init(mgr: Arc) { // 2. Add the BotAction to ModulesManager botc1.add_core_to_modmgr(Arc::clone(&mgr)).await; - async fn cmd_disable(bot: BotAR, msg: PrivmsgMessage) { + // async fn cmd_disable(bot: BotAR, msg: PrivmsgMessage) { + async fn cmd_disable(params : ExecBodyParams) { /* There should be additional validation checks - BotAdmins can only run instance level (-i) disables and (-f) force disable @@ -281,11 +290,11 @@ pub async fn init(mgr: Arc) { */ - // [x] Unwraps arguments from message + // [x] Unwraps arguments from message let (arg1, arg2) = { - let mut argv = msg.message_text.split(' '); + let mut argv = params.msg.message_text.split(' '); argv.next(); // Skip the command name @@ -302,8 +311,8 @@ pub async fn init(mgr: Arc) { &self, requestor: String, requestor_badge: Option, - trg_module: ModType, - // channel: Option, + trg_module: BotModule, + // channel: Option, trg_level: StatusLvl, force: bool, // bot: BotAR, @@ -313,47 +322,50 @@ pub async fn init(mgr: Arc) { // [x] requestor: String, - let requestor = msg.clone().sender.name; + let requestor = params.msg.clone().sender.name; // [x] requestor_badge: Option, let mut requestor_badge_mut: Option = None; - for b in &msg.badges { + for b in ¶ms.msg.badges { if b.name == "moderator" { requestor_badge_mut = Some(ChatBadge::Mod); } else if b.name == "broadcaster" { requestor_badge_mut = Some(ChatBadge::Broadcaster); + } else if b.name == "vip" { + requestor_badge_mut = Some(ChatBadge::VIP); } } let requestor_badge = requestor_badge_mut; - // [x] trg_module: ModType, - // - [x] Need to validate an actual ModType - otherwise, fail or exit the cmd + // [x] trg_module: BotModule, + // - [x] Need to validate an actual BotModule - 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 botlock = params.bot.read().await; let outmsg = "uuh You need to pass a module"; botlog::debug( outmsg, Some("botmodules.rs > cmd_disable()".to_string()), - Some(&msg), + Some(¶ms.msg), ); - - botlock - .botmgrs - .chat - .say_in_reply_to(&msg, outmsg.to_string()) - .await; + + // We should call a notification around here + + botlock.botmgrs.chat.send_botmsg(super::chat::BotMsgType::Notif( + outmsg.to_string() + ), + params.clone(), + ).await; return; @@ -363,27 +375,26 @@ pub async fn init(mgr: Arc) { // [x] trg_level: StatusLvl, - let currchnl = msg.channel_login.to_lowercase(); + let currchnl = params.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)) } + else { StatusLvl::Ch(Channel(currchnl)) } ; - let botlock = bot.read().await; + let botlock = params.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()), + BotModule(trg_module.unwrap().to_string()), trg_level, force, id).await; @@ -395,13 +406,13 @@ pub async fn init(mgr: Arc) { ChangeResult::Success(a) => format!("YAAY Success : {}",a), }; - botlock - .botmgrs - .chat - .say_in_reply_to(&msg, outmsg.to_string()) - .await; - + // We should call a notification around here + botlock.botmgrs.chat.send_botmsg(super::chat::BotMsgType::Notif( + outmsg.to_string() + ), + params.clone(), + ).await; } @@ -411,23 +422,19 @@ pub async fn init(mgr: Arc) { #[derive(Debug, Clone)] -pub enum ModType { - BotModule(String), -} +pub struct BotModule(pub String); -impl PartialEq for ModType { +impl PartialEq for BotModule { 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 Eq for BotModule {} -impl Hash for ModType{ +impl Hash for BotModule{ fn hash(&self, state: &mut H) { - // self.id.hash(state); - // self.phone.hash(state); let BotModule(name) = self.clone(); name.to_lowercase().hash(state); } @@ -443,7 +450,7 @@ pub enum ModGroup { #[derive(Debug, PartialEq, Eq, Hash, Clone)] pub enum StatusLvl { Instance, - Ch(ChType), + Ch(Channel), } #[derive(Debug, PartialEq, Eq, Hash, Clone)] @@ -459,10 +466,10 @@ pub enum BotAction { } impl BotAction { - pub async fn execute(&self, m: BotAR, n: PrivmsgMessage) { + pub async fn execute(&self, params : ExecBodyParams) { match self { - BotAction::L(a) => a.execute(m, n).await, - BotAction::C(a) => a.execute(m, n).await, + BotAction::L(a) => a.execute(params).await, + BotAction::C(a) => a.execute(params).await, _ => (), } } @@ -477,7 +484,7 @@ pub trait BotActionTrait { } pub struct BotCommand { - pub module: ModType, + pub module: BotModule, pub command: String, // command call name pub alias: Vec, // String of alternative names pub exec_body: bot_actions::actions_util::ExecBody, @@ -486,8 +493,8 @@ pub struct BotCommand { } impl BotCommand { - pub async fn execute(&self, m: BotAR, n: PrivmsgMessage) { - (*self.exec_body)(m, n).await; + pub async fn execute(&self, params : ExecBodyParams) { + (*self.exec_body)(params).await; } } @@ -515,15 +522,15 @@ impl BotActionTrait for BotCommand { } pub struct Listener { - pub module: ModType, + pub module: BotModule, 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; + pub async fn execute(&self, params : ExecBodyParams) { + (self.exec_body)(params).await; } } @@ -565,22 +572,23 @@ impl BotActionTrait for Listener { pub struct Routine {} type StatusdbEntry = (ModGroup, Vec); +type ModuleActions = Vec>>; pub struct ModulesManager { - statusdb: Arc>>, - pub botactions: Arc>>>, + statusdb: Arc>>, + pub botactions: Arc>>, } /* statusdb <-- shows Enabled/Disabled per Status level botactions HashMap< - ModType, <-- e.g., BotModule(String::from("experiments001")) + BotModule, <-- e.g., BotModule(String::from("experiments001")) Vec> BotCommand, Listener */ @@ -618,7 +626,7 @@ impl ModulesManager { } - pub async fn moduleslist(&self) -> HashMap + pub async fn moduleslist(&self) -> HashMap { // let db = Arc::clone(&self.statusdb); @@ -635,7 +643,7 @@ impl ModulesManager { outmap } - pub async fn modstatus(&self, in_module: ModType, in_chnl: ChType) -> StatusType { + pub async fn modstatus(&self, in_module: BotModule, in_chnl: Channel) -> StatusType { // Example usage : botmanager.modstatus( // BotModule("GambaCore"), // Channel("modulatingforce") @@ -646,7 +654,6 @@ impl ModulesManager { let dbt = self.statusdb.read().await; - // let a = dbt.entry(in_module.clone()).; let (mgrp,statusvector) = dbt.get(&in_module).unwrap(); match mgrp { @@ -714,7 +721,7 @@ impl ModulesManager { &self, requestor: String, requestor_badge: Option, - trg_module: ModType, + trg_module: BotModule, trg_level: StatusLvl, id: Arc>, ) -> ChangeResult @@ -758,17 +765,23 @@ impl ModulesManager { return ChangeResult::Failed("Module doesn't exist".to_string()); } + botlog::trace( + "ACQUIRING WRITE LOCK : ID", + Some("ModulesManager > Exec_enable".to_string()), + None, + ); + 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::Instance => Channel(requestor.to_lowercase()), StatusLvl::Ch(a) => a, }; - const OF_CMD_CHANNEL:ChType = Channel(String::new()); + const OF_CMD_CHANNEL:Channel = Channel(String::new()); let (admin_level_access,_) = idlock.can_user_run(requestor.clone(), arb_chnl.clone(), requestor_badge.clone(), vec![ @@ -903,7 +916,7 @@ impl ModulesManager { &self, requestor: String, requestor_badge: Option, - trg_module: ModType, + trg_module: BotModule, trg_level: StatusLvl, force: bool, id: Arc>, @@ -940,17 +953,24 @@ impl ModulesManager { return ChangeResult::Failed("Module doesn't exist".to_string()); } + botlog::trace( + "ACQUIRING WRITE LOCK : ID", + Some("ModulesManager > Exec_disable".to_string()), + None, + ); + + 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::Instance => Channel(requestor.to_lowercase()), StatusLvl::Ch(a) => a, }; - const OF_CMD_CHANNEL:ChType = Channel(String::new()); + const OF_CMD_CHANNEL:Channel = Channel(String::new()); let (admin_level_access,_) = idlock.can_user_run(requestor.clone(), arb_chnl.clone(), requestor_badge.clone(), vec![ @@ -1080,7 +1100,7 @@ impl ModulesManager { ChangeResult::Failed("ERROR : Not implemented yet".to_string()) } - pub async fn set_instance_disabled(&self, in_module: ModType) -> (StatusType,ChangeResult) { + pub async fn set_instance_disabled(&self, in_module: BotModule) -> (StatusType,ChangeResult) { // at Instance level // - If core module, do nothing @@ -1112,10 +1132,9 @@ impl ModulesManager { }, } - // (StatusType::Disabled(StatusLvl::Instance),ChangeResult::NoChange("Nothing needed".to_string())) } - pub async fn force_disable(&self, in_module: ModType) -> (StatusType,ChangeResult) { + pub async fn force_disable(&self, in_module: BotModule) -> (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 @@ -1156,10 +1175,9 @@ impl ModulesManager { }, } - // (StatusType::Disabled(StatusLvl::Instance),ChangeResult::NoChange("Nothing needed".to_string())) } - pub async fn set_instance_enabled(&self, in_module: ModType) -> (StatusType,ChangeResult) { + pub async fn set_instance_enabled(&self, in_module: BotModule) -> (StatusType,ChangeResult) { // at Instance level // - If core module, do nothing @@ -1191,10 +1209,9 @@ impl ModulesManager { }, } - // (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) { + pub async fn set_ch_disabled(&self, in_module: BotModule , in_chnl: Channel) -> (StatusType,ChangeResult) { // at Instance level // - If core module, do nothing @@ -1229,10 +1246,9 @@ impl ModulesManager { }, } - // (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) { + pub async fn set_ch_enabled(&self, in_module: BotModule , in_chnl: Channel) -> (StatusType,ChangeResult) { // at Instance level // - If core module, do nothing @@ -1267,21 +1283,20 @@ impl ModulesManager { } - // (StatusType::Enabled(StatusLvl::Instance),ChangeResult::NoChange("Nothing needed".to_string())) } - pub async fn add_botaction(&self, in_module: ModType, in_action: BotAction) { + pub async fn add_botaction(&self, in_module: BotModule, 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) { + pub async fn add_core_act(&self, in_module: BotModule, 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) { + pub async fn affirm_in_statusdb(&self,in_module:BotModule,in_modgroup: ModGroup) { let mut dbt = self.statusdb.write().await; @@ -1297,7 +1312,7 @@ impl ModulesManager { } - async fn int_add_botaction(&self, in_module: ModType, in_modgroup: ModGroup, in_action: BotAction) { + async fn int_add_botaction(&self, in_module: BotModule, in_modgroup: ModGroup, in_action: BotAction) { botlog::trace( "Add botaction called", Some("ModulesManager > init()".to_string()), @@ -1320,13 +1335,21 @@ impl ModulesManager { // - 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 { + 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 { + + + // for modact in moduleactions.iter() { + for modact_prelock in moduleactions.iter() { + + let modact = modact_prelock.read().await; + + // if let BotAction::C(dbcmd) = &modact { + 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 @@ -1382,7 +1405,7 @@ impl ModulesManager { let mut a = self.botactions.write().await; let modactions = a.entry(in_module.clone()).or_insert(Vec::new()); - modactions.push(in_action); + modactions.push(Arc::new(RwLock::new(in_action))); botlog::trace( format!( @@ -1395,12 +1418,12 @@ impl ModulesManager { ); } - fn _statuscleanup(&self, _: Option) { + 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 + // 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 } } @@ -1467,10 +1490,10 @@ mod core_modulesmanager { */ async fn complex_workflow( - in_module: ModType , + in_module: BotModule , in_modgroup : ModGroup , - in_chnl1 : ChType, - in_chnl2 : ChType) + in_chnl1 : Channel, + in_chnl2 : Channel) { @@ -1643,7 +1666,7 @@ mod core_modulesmanager { 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())); + (Channel("TestChannel01".to_string()),Channel("TestChannel02".to_string())); complex_workflow(in_module, in_modgroup, in_chnl1, in_chnl2).await; @@ -1659,7 +1682,7 @@ mod core_modulesmanager { 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())); + (Channel("TestChannel01".to_string()),Channel("TestChannel02".to_string())); complex_workflow(in_module, in_modgroup, in_chnl1, in_chnl2).await; @@ -1701,7 +1724,7 @@ mod core_modulesmanager { async fn inner_enable_disable_complex( requestor:String, - channel:ChType, + channel:Channel, idmgr:IdentityManager, modsmgr:Arc) { @@ -1724,7 +1747,7 @@ mod core_modulesmanager { 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()); + const OF_CMD_CHANNEL:Channel = Channel(String::new()); let (admin_level_access,_) = idlock.can_user_run(requestor.clone(), channel.clone(), requestor_badge.clone(), vec![ @@ -1773,7 +1796,7 @@ mod core_modulesmanager { // [-] requestor_badge: Option, - // [x] trg_module: ModType, + // [x] trg_module: BotModule, let trg_module = in_module; // [x] trg_level: StatusLvl, @@ -2020,7 +2043,7 @@ mod core_modulesmanager { assert!(rslt.contains(&identity::UserRole::BotAdmin)); - let channel = ChType::Channel("somechannel".to_string()); + let channel = Channel("somechannel".to_string()); inner_enable_disable_complex(requestor, channel, idmgr, modsmgr).await; @@ -2058,7 +2081,7 @@ mod core_modulesmanager { let requestor = "mod_user".to_string(); // let botadmin_badge = &None; - let channel = ChType::Channel("somechannel".to_string()); + let channel = Channel("somechannel".to_string()); idmgr.affirm_chatter_in_db(requestor.clone()).await; @@ -2109,7 +2132,7 @@ mod core_modulesmanager { let requestor = "regular_user".to_string(); - let channel = ChType::Channel("somechannel".to_string()); + let channel = Channel("somechannel".to_string()); idmgr.affirm_chatter_in_db(requestor.clone()).await; @@ -2142,7 +2165,7 @@ mod core_modulesmanager { let requestor = "regular_user".to_string(); - let channel = ChType::Channel("somechannel".to_string()); + let channel = Channel("somechannel".to_string()); idmgr.affirm_chatter_in_db(requestor.clone()).await; diff --git a/src/core/chat.rs b/src/core/chat.rs index fe49896..bb938cc 100644 --- a/src/core/chat.rs +++ b/src/core/chat.rs @@ -15,21 +15,36 @@ use rand::Rng; use crate::core::ratelimiter; use crate::core::ratelimiter::RateLimiter; -use crate::core::botinstance::ChType; +use crate::core::botinstance::Channel; use crate::core::botlog; -pub use ChType::Channel; + use tokio::time::{sleep, Duration}; +use super::bot_actions::ExecBodyParams; +use super::identity; + + +use async_recursion::async_recursion; + #[derive(Clone)] pub struct Chat { - pub ratelimiters: Arc>>, // used to limit messages sent per channel + pub ratelimiters: Arc>>, // used to limit messages sent per channel pub client: TwitchIRCClient, StaticLoginCredentials>, } + +#[derive(Clone,Debug)] +pub enum BotMsgType<'a> { + SayInReplyTo(&'a PrivmsgMessage,String), + Say(String,String), + Notif(String), // For Bot Sent Notifications +} + + impl Chat { pub fn init( - ratelimiters: HashMap, + ratelimiters: HashMap, client: TwitchIRCClient, StaticLoginCredentials>, ) -> Chat { Chat { @@ -38,12 +53,16 @@ impl Chat { } } - pub async fn init_channel(&mut self, chnl: ChType) { + pub async fn init_channel(&mut self, chnl: Channel) { let n = RateLimiter::new(); self.ratelimiters.lock().await.insert(chnl, n); } - pub async fn say_in_reply_to(&self, msg: &PrivmsgMessage, mut outmsg: String) { + #[async_recursion] + pub async fn send_botmsg(&self, msginput: BotMsgType<'async_recursion>, params : ExecBodyParams) { + + + /* formats message before sending to TwitchIRC @@ -53,12 +72,256 @@ impl Chat { */ + + botlog::trace( + format!("send_bot_msg params : {:?}",msginput).as_str(), + Some("chat.rs > send_botmsg ".to_string()), + Some(¶ms.msg), + ); + Log::flush(); + + let (channel_login,mut outmsg) = match msginput.clone() { + BotMsgType::SayInReplyTo(msg, outmsg) => { + (msg.channel_login.clone(),outmsg) + }, + BotMsgType::Say(a,b ) => { + (a.clone(),b.clone()) + }, + BotMsgType::Notif(outmsg) => { + (params.msg.channel_login.clone(),outmsg) + } + }; + + + + + botlog::trace( + "BEFORE parent_module call", + Some("chat.rs > send_botmsg ".to_string()), + Some(¶ms.msg), + ); + + let parent_module = params.get_parent_module().await; + + let params_clone = params.clone(); + let botclone = Arc::clone(¶ms_clone.bot); + let botlock = botclone.read().await; + let modmgr = Arc::clone(&botlock.botmodules); + let modstatus = (*modmgr).modstatus( + parent_module.clone().expect("ERROR - Expected a module"), + Channel(channel_login.clone()) + ).await; + + if !params.bot.read().await.bot_channels.contains(&Channel(channel_login.clone())) { + botlog::warn( + &format!("A message attempted to send for a Non-Joined Channel : {}",channel_login.clone()), + Some("Chat > send_botmsg".to_string()), + None, + ); + + if let BotMsgType::SayInReplyTo(_prvmsg,_outmsg) = msginput { + + self.send_botmsg(BotMsgType::Notif( + "uuh Bot can't send to a channel it isn't joined".to_string(), + ), + params).await; + + } + + return ; + } + + /* + [x] !! => 03.24 - Somewhere around here, we should be validating module for target channel + */ + + + /* + - Use ModulesManager.modstatus + + modstatus(&self, in_module: BotModule, in_chnl: Channel) -> StatusType + + */ + + botlog::trace( + format!("BEFORE modstatus check : modstatus = {:?}",modstatus).as_str(), + Some("chat.rs > send_botmsg ".to_string()), + Some(¶ms.msg), + ); + + + + if let super::botmodules::StatusType::Disabled(lvl) = modstatus { + // Note : At this point, chat was called in a channel where the parent module IS enabled + // - this type of validation is done outside of Chat() + // This though takes into account scenarios where we are targetting a different channel + + + botlog::trace( + "BEFORE msginput check", + Some("chat.rs > send_botmsg ".to_string()), + Some(¶ms.msg), + ); + + Log::flush(); + + match msginput { + BotMsgType::Notif(_) => (), // Do nothing with Notif > We'll validate the user later to handle + BotMsgType::SayInReplyTo(_, _) | BotMsgType::Say(_,_) => { + + botlog::trace( + "BEFORE potential Async recursion", + Some("chat.rs > send_botmsg ".to_string()), + Some(¶ms.clone().msg), + ); + + Log::flush(); + + + self.send_botmsg(BotMsgType::Notif( + format!("uuh {:?} is disabled on {} : {:?}", + parent_module.clone().unwrap(), + channel_login.clone(), + lvl + ), + ), params.clone() + ).await; + + + botlog::trace( + "AFTER potential Async recursion", + Some("chat.rs > send_botmsg ".to_string()), + Some(¶ms.msg), + ); + + + Log::flush(); + + return + }, + + } + + } + + + /* + + [x] !! => 03.24 - Would be nice if around here , validate the user has at least some special roles in target channel + - NOTE : If these need to be refined, they can be by the custom module developer at the parent calling function of say() + - This just prevents Chat from being triggered in a channel where the sending chatter does not have any special roles + + */ + + + + /* + + Use + pub async fn getspecialuserroles( + &self, + chattername: String, + channel: Option, + ) -> Vec { + + */ + + // let params_clone = params.clone(); + + let botclone = Arc::clone(¶ms.bot); + let botlock = botclone.read().await; + let id = botlock.get_identity(); + let id = Arc::clone(&id); + let idlock = id.read().await; // <-- [x] 03.24 - seems to work + let user_roles = idlock.getspecialuserroles( + params.get_sender(), + Some(Channel(channel_login.clone())) + ).await; + + botlog::trace( + format!("BEFORE user roles check check : userroles = {:?}",user_roles).as_str(), + Some("chat.rs > send_botmsg ".to_string()), + Some(¶ms.msg), + ); + + Log::flush(); + + // [x] If user has any of the following target roles, they will be allowed - otherwise, they will not be allowed to send + // - Otherwise if not (checked here) , this will not run + // - NOTE : For now, I've removed BotAdmin just for curiosity - BotAdmins can always elevate themselves if they want + // - Will be adding VIP to this as this should include Channel_Level Roles + + if !(user_roles.contains(&identity::UserRole::Mod(Channel(channel_login.clone()))) + || user_roles.contains(&identity::UserRole::SupMod(Channel(channel_login.clone()))) + || user_roles.contains(&identity::UserRole::Broadcaster) + || user_roles.contains(&identity::UserRole::VIP(Channel(channel_login.clone()))) + ) + { + + + match msginput { + BotMsgType::Notif(_) => { + // If Sender is Not a BotAdmin, don't do anything about the notification and return + if !user_roles.contains(&identity::UserRole::BotAdmin) { + return; + } + }, + BotMsgType::SayInReplyTo(_,_ ) | BotMsgType::Say(_,_) => { + // If the BotMsg a Say/SayInReplyTo (from Developer or Chatter) , and the Sender does not have Specific Roles in the Source Channel Sent + + self.send_botmsg(BotMsgType::Notif( + format!("uuh You do not have the right roles to send to {}", + channel_login.clone(), + ), + ), params.clone() + ).await; + + return; + + }, + }; + + + + } + + + /* + At this stage from the above Validations : + msginput would be : + a. BotMsgType::SayInReplyTo | BotMsgType::Say that is + - Towards a Destination Channel that the Sender has Elevated User Roles to Send to + b. BotMsgType::Notif that is + - Going to be sent to the Source Channel (rather than the original say/sayinreplyto was towards) + - A Sender that has Elevated User Roles in Source Channel will see a message ; otherwise, they will not + */ + + /* + + Use the following + + pub async fn can_user_run( + &mut self, + usr: String, + channelname: Channel, + chat_badge: Option, + cmdreqroles: Vec, // ) -> Result> { + ) -> (Permissible, ChangeResult) { + + */ + let rl = Arc::clone(&self.ratelimiters); let mut rllock = rl.lock().await; + botlog::debug( + &format!("Ratelimiter being checked for channel : {}",channel_login.clone()), + Some("Chat > send_botmsg".to_string()), + None, + ); + let contextratelimiter = rllock // .get_mut() - .get_mut(&Channel(String::from(&msg.channel_login))) + .get_mut(&Channel(channel_login.to_lowercase().clone())) .expect("ERROR: Issue with Rate limiters"); // Continue to check the limiter and sleep if required if the minimum is not reached @@ -75,20 +338,41 @@ impl Chat { outmsg.push_str(blankspace); } - self.client.say_in_reply_to(msg, outmsg).await.unwrap(); - + match msginput.clone() { + BotMsgType::SayInReplyTo(msg, _) => { + self.client.say_in_reply_to(msg, outmsg).await.unwrap(); + }, + BotMsgType::Say(a, _) => { + self.client.say(a, outmsg).await.unwrap(); + } + BotMsgType::Notif(outmsg) => { + self.client.say_in_reply_to(¶ms.msg, outmsg).await.unwrap(); + } + } + contextratelimiter.increment_counter(); let logstr = format!( "(#{}) > {} ; contextratelimiter : {:?}", - msg.channel_login, "rate limit counter increase", contextratelimiter + channel_login.clone(), "rate limit counter increase", contextratelimiter ); - botlog::trace( - logstr.as_str(), - Some("Chat > say_in_reply_to".to_string()), - Some(msg), - ); + if let BotMsgType::SayInReplyTo(msg,_ ) = msginput { + botlog::trace( + logstr.as_str(), + Some("Chat > send_botmsg".to_string()), + Some(msg), + ); + } else { + botlog::trace( + logstr.as_str(), + Some("Chat > send_botmsg".to_string()), + None, + ); + } + + + } ratelimiter::LimiterResp::Skip => { // (); // do nothing otherwise @@ -98,13 +382,76 @@ impl Chat { } } - Log::flush(); + + Log::flush(); } - async fn _say(&self, _: String, _: String) { + + + // pub async fn say_in_reply_to(&self, msg: &PrivmsgMessage, outmsg: String) { + // #[async_recursion] + pub async fn say_in_reply_to(&self, msg: &PrivmsgMessage, outmsg: String , params : ExecBodyParams) { + + // let params_clone = params.clone(); + + // let botclone = Arc::clone(¶ms_clone.bot); + // let botlock = botclone.read().await; + // let id = botlock.get_identity(); + // let id = Arc::clone(&id); + + // // botlog::trace( + // // "ACQUIRING WRITE LOCK : ID", + // // Some("Chat > send_botmsg".to_string()), + // // Some(¶ms.msg), + // // ); + // // Log::flush(); + + // botlog::trace( + // "ACQUIRING READ LOCK : ID", + // Some("Chat > send_botmsg".to_string()), + // Some(¶ms.msg), + // ); + // Log::flush(); + + + // // let idlock = id.write().await; // <-- [ ] 03.24 - This is definitely locking it + // let idlock = id.read().await; // <-- [ ] 03.24 - seems to work + // let a = idlock.getspecialuserroles(params.get_sender(), Some(Channel(msg.channel_login.clone()))).await; + // botlog::trace( + // format!("GETSPECIALUSERROLES RESULT : {:?}",a).as_str(), + // Some("Chat > send_botmsg".to_string()), + // Some(¶ms.msg), + // ); + // Log::flush(); + + + + // // botlog::trace( + // // "ACQUIRED WRITE LOCK : ID", + // // Some("Chat > send_botmsg".to_string()), + // // Some(¶ms.msg), + // // ); + // // Log::flush(); + + + + // botlog::trace( + // "ACQUIRED READ LOCK : ID", + // Some("Chat > send_botmsg".to_string()), + // Some(¶ms.msg), + // ); + // Log::flush(); + + + self.send_botmsg(BotMsgType::SayInReplyTo(msg, outmsg) , params).await; + + } + + // pub async fn say(&self, channel_login: String, message: String) { + pub async fn say(&self, channel_login: String, message: String , params : ExecBodyParams) { // more info https://docs.rs/twitch-irc/latest/twitch_irc/client/struct.TwitchIRCClient.html#method.say - // self.client.say(msg,outmsg).await.unwrap(); + self.send_botmsg(BotMsgType::Say(channel_login.to_lowercase(), message), params).await; } async fn _me(&self, _: String, _: String) { diff --git a/src/core/identity.rs b/src/core/identity.rs index 0a27b60..dc2da72 100644 --- a/src/core/identity.rs +++ b/src/core/identity.rs @@ -1,3 +1,8 @@ + + +const OF_CMD_CHANNEL:Channel = Channel(String::new()); + + use std::collections::HashMap; use std::sync::Arc; @@ -7,8 +12,9 @@ use twitch_irc::message::PrivmsgMessage; use casual_logger::Log; -use crate::core::bot_actions::actions_util::{self, BotAR}; -use crate::core::botinstance::{ChType,ChangeResult}; +use crate::core::bot_actions::actions_util; +use crate::core::bot_actions::ExecBodyParams; +use crate::core::botinstance::{Channel,ChangeResult}; use crate::core::botlog; use crate::core::botmodules::{BotActionTrait, BotCommand, BotModule, ModulesManager}; @@ -60,171 +66,15 @@ pub async fn init(mgr: Arc) { exec_body: actions_util::asyncbox(cmd_promote), help: String::from("promote"), required_roles: vec![ - UserRole::Mod(ChType::Channel(String::new())), - UserRole::SupMod(ChType::Channel(String::new())), + UserRole::Mod(OF_CMD_CHANNEL), + UserRole::SupMod(OF_CMD_CHANNEL), UserRole::Broadcaster, UserRole::BotAdmin, ], }; - // 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( - "Called cmd promote", - Some("identity.rs > cmd_prommote()".to_string()), - Some(&msg), - ); - - // -- If the BotCommand.command was called (e.g., promote) & required roles were validated OUTSIDE of this call - // , this is the current function body to execute - - /* - - `promote` / `demote` - - [ ] `SupMod` & `Broadcaster` & `BotAdmin` can run - - [ ] `UserRole`s that can run, can - - [ ] run `promote` on a regular `Chatter` to make them a `Mod` - - [ ] run `demote` on a `Mod` to make them a `Chatter` - - [ ] Only `BotAdmin` can : - - [ ] target themselves to `promote` / `demote` , in the case that they want to make themselves either a `Mod` or `SupMod` for the channel temporarily - - [ ] `promote admin ` to assign them `BotAdmin` role - - `[ ] Broadcaster` & `BotAdmin` can `demote` a `SupMod` to make them a `Mod` or `promote` the other way - */ - - /* - Usage : - - promote - - demote - - promote -admin - - */ - - // println!("{}",msg.message_text); - botlog::trace( - format!("Twich Message > {}", msg.message_text).as_str(), - Some("identity.rs > cmd_promote()".to_string()), - None, - ); - - let sendername = msg.clone().sender.name; - - let mut argv = msg.message_text.split(' '); - - argv.next(); // Skip the command name - - let arg1 = argv.next(); - - let arg2 = argv.next(); - - let mut sender_badge: Option = None; - - for b in &msg.badges { - if b.name == "moderator" { - sender_badge = Some(ChatBadge::Mod); - } else if b.name == "broadcaster" { - sender_badge = Some(ChatBadge::Broadcaster); - } - } - - let targetchnl = msg.channel_login.to_lowercase(); - - /* - - [x] 1. Get trgusr (regardless of -admin flag) - [x] 2. promote trguser - [x] 3. Output resulting change - - */ - - // [x] 1. Get trgusr (regardless of -admin flag) - - let targetusr = if arg1 == Some("-admin") { arg2 } else { arg1 }; - - // [x] 2. promote trguser - - // [x] Get a required lock first - - let botlock = bot.read().await; - let id = botlock.get_identity(); - let idlock = id.read().await; - - let rslt = match targetusr { - Some(targetusr) => { - botlog::debug( - "running promote()", - Some("identity.rs > cmd_promote()".to_string()), - None, - ); - Log::flush(); - - let target_bot_admin_role = if arg1 == Some("-admin") { - Some(UserRole::BotAdmin) - } else { - None - }; - - idlock - .promote( - sendername.clone(), - &sender_badge, - targetusr.to_string(), - Some(ChType::Channel(targetchnl.clone())), - target_bot_admin_role, - ) - .await - } - - None => { - botlog::debug( - // &format!("No Targer User argument"), - "No Targer User argument", - Some("identity.rs > cmd_demote()".to_string()), - None, - ); - Log::flush(); - - ChangeResult::NoChange("No Targer User".to_string()) - } - }; - - // [x] 3. Output resulting change - - let outmsg = match rslt { - ChangeResult::Success(a) => { - format!("o7 Successfully promoted : {a}") - } - ChangeResult::Failed(a) => { - format!("PoroSad failed to promote : {a}") - } - ChangeResult::NoChange(a) => { - format!("uuh No Promotion Change : {a}") - } - }; - - botlog::debug( - outmsg.as_str(), - Some("identity.rs > cmd_prommote()".to_string()), - Some(&msg), - ); - - botlock - .botmgrs - .chat - .say_in_reply_to(&msg, outmsg.to_string()) - .await; - - botlog::trace( - // &format!("End of cmd_promote()"), - "End of cmd_promote()", - Some("identity.rs > cmd_prommote()".to_string()), - None, - ); - } - + let tempb = BotCommand { module: BotModule(String::from("identity")), command: String::from("demote"), // command call name @@ -232,191 +82,15 @@ pub async fn init(mgr: Arc) { exec_body: actions_util::asyncbox(cmd_demote), help: String::from("demote"), required_roles: vec![ - UserRole::Mod(ChType::Channel(String::new())), - UserRole::SupMod(ChType::Channel(String::new())), + UserRole::Mod(OF_CMD_CHANNEL), + UserRole::SupMod(OF_CMD_CHANNEL), UserRole::Broadcaster, UserRole::BotAdmin, ], }; - // 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( - "Called cmd demote", - Some("identity.rs > cmd_demote()".to_string()), - Some(&msg), - ); - Log::flush(); - - // -- If the BotCommand.command was called (e.g., demote) & required roles were validated OUTSIDE of this call - // , this is the current function body to execute - - /* - - `promote` / `demote` - - [ ] `SupMod` & `Broadcaster` & `BotAdmin` can run - - [ ] `UserRole`s that can run, can - - [ ] run `promote` on a regular `Chatter` to make them a `Mod` - - [ ] run `demote` on a `Mod` to make them a `Chatter` - - [ ] Only `BotAdmin` can : - - [ ] target themselves to `promote` / `demote` , in the case that they want to make themselves either a `Mod` or `SupMod` for the channel temporarily - - [ ] `promote admin ` to assign them `BotAdmin` role - - `[ ] Broadcaster` & `BotAdmin` can `demote` a `SupMod` to make them a `Mod` or `promote` the other way - */ - - /* - Usage : - - promote - - demote - - promote -admin - - */ - - // [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) - }; - - // --- - - /* - - [x] 1. Parse out the following - - Sender (e.g., Name & Badge) - - Target User (arg1) - - Target Channel (current channel) - - Msg or Msg.Message_Text (for later) - - - [x] 2. Run Demote() - - within demote(), getspecialuserroles() is called on both the sender and the target - - getspecialuserroles() only sends current db , while canuserrun() may change db depending on the most current state of the sender - - getspecialuserroles also borrows the sender's badge to evaluate - - - [x] 3. Take ChangeResult and output response - - */ - - /* - - - [x] 1. Parse out the following - - Sender (e.g., Name & Badge) - - Target User (arg1) - - Target Channel (current channel) - - (no need) Msg or Msg.Message_Text (for later) - - */ - - let sendername = msg.clone().sender.name; - - let mut sender_badge_mut: Option = None; - - for b in &msg.badges { - if b.name == "moderator" { - sender_badge_mut = Some(ChatBadge::Mod); - } else if b.name == "broadcaster" { - sender_badge_mut = Some(ChatBadge::Broadcaster); - } - } - - let sender_badge = sender_badge_mut; - - let targetusr = arg1; - - let targetchnl = msg.channel_login.to_lowercase(); - - /* - - - [x] 2. Run Demote() - - within demote(), getspecialuserroles() is called on both the sender and the target - - getspecialuserroles() only sends current db , while canuserrun() may change db depending on the most current state of the sender - - getspecialuserroles also borrows the sender's badge to evaluate - - - */ - - // [x] Get a required lock first - - let botlock = bot.read().await; - let id = botlock.get_identity(); - let idlock = id.read().await; - - let rslt = match targetusr { - Some(targetusr) => { - botlog::debug( - // &format!("running demote()"), - "running demote()", - Some("identity.rs > cmd_demote()".to_string()), - None, - ); - Log::flush(); - - idlock - .demote( - sendername.clone(), - &sender_badge, - targetusr.to_string(), - Some(ChType::Channel(targetchnl.clone())), - ) - .await - } - - None => { - botlog::debug( - // &format!("No Targer User argument"), - "No Targer User argument", - Some("identity.rs > cmd_demote()".to_string()), - None, - ); - Log::flush(); - - ChangeResult::NoChange("No Targer User".to_string()) - } - }; - - /* - - - [x] 3. Take ChangeResult and output response - - */ - - let outmsg = match rslt { - ChangeResult::Success(a) => { - format!("o7 Successfully demoted : {a}") - } - ChangeResult::Failed(a) => { - format!("PoroSad failed to demote : {a}") - } - ChangeResult::NoChange(a) => { - format!("uuh No Demotion Change : {a}") - } - }; - - botlog::debug( - outmsg.as_str(), - Some("identity.rs > cmd_demote()".to_string()), - Some(&msg), - ); - - botlock - .botmgrs - .chat - .say_in_reply_to(&msg, outmsg.to_string()) - .await; - } - + let tempcomm = BotCommand { module: BotModule(String::from("identity")), command: String::from("getroles"), // command call name @@ -424,148 +98,15 @@ pub async fn init(mgr: Arc) { exec_body: actions_util::asyncbox(getroles), help: String::from("getroles"), required_roles: vec![ - UserRole::Mod(ChType::Channel(String::new())), - UserRole::SupMod(ChType::Channel(String::new())), + UserRole::Mod(OF_CMD_CHANNEL), + UserRole::SupMod(OF_CMD_CHANNEL), UserRole::Broadcaster, UserRole::BotAdmin, ], }; - // 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( - "Called cmd getroles", - Some("identity.rs > cmd_getroles()".to_string()), - Some(&msg), - ); - - /* - Usage - - getroles - - If channel is provided, provide roles for that channel specifically - - */ - - let mut argv = msg.message_text.split(' '); - - argv.next(); // Skip the command name - - let arg1 = argv.next(); - - let targetuser = match arg1 { - None => return, // exit if no arguments - Some(arg) => arg, - }; - - let arg2 = argv.next(); - - let targetchnl = arg2; - - let botlock = bot.read().await; - - let id = botlock.get_identity(); - - let idlock = id.read().await; - - let sproles = match targetchnl { - None => { - // [ ] If targetchnl is not provided, default to pulling the current channel - idlock - .getspecialuserroles( - String::from(targetuser), - Some(ChType::Channel(msg.channel_login.to_lowercase())), - ) - .await - } - Some(targetchnl) => { - // [x] gets special roles for caller - let callersproles = idlock - .getspecialuserroles( - msg.sender.name.to_lowercase(), - Some(ChType::Channel(targetchnl.to_lowercase().to_string())), - ) - .await; - - if callersproles.contains(&UserRole::Mod(ChType::Channel( - targetchnl.to_lowercase().to_string(), - ))) || callersproles.contains(&UserRole::SupMod(ChType::Channel( - targetchnl.to_lowercase().to_string(), - ))) || callersproles.contains(&UserRole::Broadcaster) - { - idlock - .getspecialuserroles( - String::from(targetuser), - Some(ChType::Channel(targetchnl.to_lowercase())), - ) - .await - } else { - // Otherwise, don't get the target channel, return the current channel instead - idlock - .getspecialuserroles( - String::from(targetuser), - Some(ChType::Channel(msg.channel_login.to_lowercase())), - ) - .await - } - } - }; - - botlog::debug( - &format!("User roles of Target Chatter >> {:?}", sproles), - Some("identity.rs > init > getroles()".to_string()), - Some(&msg), - ); - - botlog::trace( - // &format!("Evaluating special roles"), - "Evaluating special roles", - Some("identity.rs > init > getroles()".to_string()), - Some(&msg), - ); - - let outmsg = if ((targetuser.to_lowercase() == msg.channel_login.to_lowercase()) - && arg2.is_none()) - || (arg2.is_some() && arg2.unwrap() == targetuser.to_lowercase()) - { - // First evaluates if they're broadcaster - - let mut outmsg = "FeelsWowMan they're the broadcaster. ".to_string(); - - if sproles.contains(&UserRole::Mod(ChType::Channel( - msg.channel_login.to_lowercase(), - ))) || sproles.contains(&UserRole::SupMod(ChType::Channel( - msg.channel_login.to_lowercase(), - ))) || sproles.contains(&UserRole::BotAdmin) - { - outmsg += format!("Target chatter's user roles are : {:?}", sproles).as_str(); - } - outmsg - } else if sproles.contains(&UserRole::Mod(ChType::Channel( - msg.channel_login.to_lowercase(), - ))) || sproles.contains(&UserRole::SupMod(ChType::Channel( - msg.channel_login.to_lowercase(), - ))) || sproles.contains(&UserRole::BotAdmin) - { - format!("Target chatter's user roles are : {:?}", sproles) - } else { - "Target chatter has no special roles LULE ".to_string() - }; - - botlog::debug( - format!("Chat Say Reply message : {}", outmsg).as_str(), - Some("identity.rs > init > getroles()".to_string()), - Some(&msg), - ); - - botlock.botmgrs.chat.say_in_reply_to(&msg, outmsg).await; - - // [ ] NOTE : After the above, I should receive only the roles in the context of the current channel I received this ideally and maybe BotAdmin ; not outside - } - + botlog::trace( "End of Init MOdule add", Some("identity.rs > init ".to_string()), @@ -575,11 +116,568 @@ pub async fn init(mgr: Arc) { Log::flush(); } + + +async fn cmd_promote(params : ExecBodyParams) { + botlog::trace( + "Called cmd promote", + Some("identity.rs > cmd_prommote()".to_string()), + Some(¶ms.msg), + ); + + // -- If the BotCommand.command was called (e.g., promote) & required roles were validated OUTSIDE of this call + // , this is the current function body to execute + + /* + - `promote` / `demote` + - [ ] `SupMod` & `Broadcaster` & `BotAdmin` can run + - [ ] `UserRole`s that can run, can + - [ ] run `promote` on a regular `Chatter` to make them a `Mod` + - [ ] run `demote` on a `Mod` to make them a `Chatter` + - [ ] Only `BotAdmin` can : + - [ ] target themselves to `promote` / `demote` , in the case that they want to make themselves either a `Mod` or `SupMod` for the channel temporarily + - [ ] `promote admin ` to assign them `BotAdmin` role + - `[ ] Broadcaster` & `BotAdmin` can `demote` a `SupMod` to make them a `Mod` or `promote` the other way + */ + + /* + Usage : + + promote + promote -m + promote -mod + + demote + + promote -v + promote -vip + + promote -admin + + */ + + // println!("{}",params.msg.message_text); + botlog::trace( + format!("Twich Message > {}", params.msg.message_text).as_str(), + Some("identity.rs > cmd_promote()".to_string()), + None, + ); + + let sendername = params.msg.clone().sender.name; + + let mut argv = params.msg.message_text.split(' '); + + argv.next(); // Skip the command name + + let arg1 = argv.next(); + + let arg2 = argv.next(); + + let mut sender_badge: Option = None; + + for b in ¶ms.msg.badges { + if b.name == "moderator" { + sender_badge = Some(ChatBadge::Mod); + } else if b.name == "broadcaster" { + sender_badge = Some(ChatBadge::Broadcaster); + } else if b.name == "vip" { + sender_badge = Some(ChatBadge::VIP); + } + }; + + let targetchnl = params.msg.channel_login.to_lowercase(); + + /* + + [x] 1. Get trgusr (regardless of -admin flag) + [x] 2. promote trguser + [x] 3. Output resulting change + + */ + + + + let botlock = params.bot.read().await; + let id = botlock.get_identity(); + let idlock = id.read().await; + + + // [x] 1. Get trgusr (regardless of -admin flag) + + // let targetusr = if arg1 == Some("-admin") { arg2 } else { arg1 }; + let targetusr = if + arg1 == Some("-admin") + || arg1 == Some("-v") + || arg1 == Some("-vip") + || arg1 == Some("-m") + || arg1 == Some("-mod") + { arg2 } + else if let Some(a) = arg1 { + if a.starts_with('-') { + botlock.botmgrs.chat.send_botmsg( + super::chat::BotMsgType::Notif( + "Invalid Argument Flag".to_string() + ), + params.clone(), + ).await; + return + } else { arg1 } + } + else { arg1 }; + + // [x] 2. promote trguser + + // [x] Get a required lock first + let rslt = match targetusr { + Some(targetusr) => { + botlog::debug( + "running promote()", + Some("identity.rs > cmd_promote()".to_string()), + None, + ); + + Log::flush(); + + + // // Believe this is just using this as a Constaint depending on input + let target_role = + if arg1 == Some("-admin") { + Some(UserRole::BotAdmin) + } else if arg1 == Some("-vip") || arg1 == Some("-v") { + Some(UserRole::VIP(Channel(targetchnl.clone()))) + } else { + None // [x] Internal promote() logic automatically considers trg_role targetting -mod or -m + }; + + idlock + .promote( + sendername.clone(), + &sender_badge, + targetusr.to_string(), + Some(Channel(targetchnl.clone())), + target_role, + ) + .await + } + + None => { + botlog::debug( + "No Targer User argument", + Some("identity.rs > cmd_demote()".to_string()), + None, + ); + Log::flush(); + + ChangeResult::NoChange("No Targer User".to_string()) + } + }; + + // [x] 3. Output resulting change + + let outmsg = match rslt { + ChangeResult::Success(a) => { + format!("o7 Successfully promoted : {a}") + } + ChangeResult::Failed(a) => { + format!("PoroSad failed to promote : {a}") + } + ChangeResult::NoChange(a) => { + format!("uuh No Promotion Change : {a}") + } + }; + + botlog::debug( + outmsg.as_str(), + Some("identity.rs > cmd_prommote()".to_string()), + Some(¶ms.msg), + ); + + // We should call a notification around here + + botlock.botmgrs.chat.send_botmsg(super::chat::BotMsgType::Notif( + outmsg.to_string() + ), + params.clone(), + ).await; + + + botlog::trace( + "End of cmd_promote()", + Some("identity.rs > cmd_prommote()".to_string()), + None, + ); +} + + + +async fn cmd_demote(params : ExecBodyParams) { + botlog::debug( + "Called cmd demote", + Some("identity.rs > cmd_demote()".to_string()), + Some(¶ms.msg), + ); + Log::flush(); + + // -- If the BotCommand.command was called (e.g., demote) & required roles were validated OUTSIDE of this call + // , this is the current function body to execute + + /* + - `promote` / `demote` + - [ ] `SupMod` & `Broadcaster` & `BotAdmin` can run + - [ ] `UserRole`s that can run, can + - [ ] run `promote` on a regular `Chatter` to make them a `Mod` + - [ ] run `demote` on a `Mod` to make them a `Chatter` + - [ ] Only `BotAdmin` can : + - [ ] target themselves to `promote` / `demote` , in the case that they want to make themselves either a `Mod` or `SupMod` for the channel temporarily + - [ ] `promote admin ` to assign them `BotAdmin` role + - `[ ] Broadcaster` & `BotAdmin` can `demote` a `SupMod` to make them a `Mod` or `promote` the other way + */ + + /* + Usage : + + promote + + demote + + demote -m + demote -mod + + demote -v + demote -vip + + // promote -admin + + + + */ + + // [x] Unwraps arguments from message + + let (arg1, arg2) = { + let mut argv = params.msg.message_text.split(' '); + + argv.next(); // Skip the command name + + let arg1 = argv.next(); + + let arg2 = argv.next(); + + (arg1, arg2) + }; + + // --- + + /* + - [x] 1. Parse out the following + - Sender (e.g., Name & Badge) + - Target User (arg1) + - Target Channel (current channel) + - Msg or Msg.Message_Text (for later) + + - [x] 2. Run Demote() + - within demote(), getspecialuserroles() is called on both the sender and the target + - getspecialuserroles() only sends current db , while canuserrun() may change db depending on the most current state of the sender + - getspecialuserroles also borrows the sender's badge to evaluate + + - [x] 3. Take ChangeResult and output response + + */ + + /* + + - [x] 1. Parse out the following + - Sender (e.g., Name & Badge) + - Target User (arg1) + - Target Channel (current channel) + - (no need) Msg or Msg.Message_Text (for later) + + */ + + + + // [x] Get a required lock first + + let botlock = params.bot.read().await; + let id = botlock.get_identity(); + let idlock = id.read().await; + + + let sendername = params.msg.clone().sender.name; + + let mut sender_badge_mut: Option = None; + + for b in ¶ms.msg.badges { + if b.name == "moderator" { + sender_badge_mut = Some(ChatBadge::Mod); + } else if b.name == "broadcaster" { + sender_badge_mut = Some(ChatBadge::Broadcaster); + } else if b.name == "vip" { + sender_badge_mut = Some(ChatBadge::VIP); + } + }; + + let sender_badge = sender_badge_mut; + + + let targetchnl = params.msg.channel_login.to_lowercase(); + + + // let targetusr = arg1; + let targetusr = if + arg1 == Some("-v") + || arg1 == Some("-vip") + || arg1 == Some("-m") + || arg1 == Some("-mod") + { arg2 } + else if let Some(a) = arg1 { + if a.starts_with('-') { + botlock.botmgrs.chat.send_botmsg( + super::chat::BotMsgType::Notif( + "Invalid Argument Flag".to_string() + ), + params.clone(), + ).await; + return + } else { arg1 } + } + else { arg1 }; + + // Note : At the moment, no handling of -admin + let target_role = + if arg1 == Some("-vip") || arg1 == Some("-v") { + Some(UserRole::VIP(Channel(targetchnl.clone()))) + } else { + None // [x] Internal promote() logic automatically considers trg_role targetting -mod or -m + }; + + + + /* + + - [x] 2. Run Demote() + - within demote(), getspecialuserroles() is called on both the sender and the target + - getspecialuserroles() only sends current db , while canuserrun() may change db depending on the most current state of the sender + - getspecialuserroles also borrows the sender's badge to evaluate + + + */ + + let rslt = match targetusr { + Some(targetusr) => { + botlog::debug( + "running demote()", + Some("identity.rs > cmd_demote()".to_string()), + None, + ); + Log::flush(); + + idlock + .demote( + sendername.clone(), + &sender_badge, + targetusr.to_string(), + Some(Channel(targetchnl.clone())), + target_role, + ) + .await + } + + None => { + botlog::debug( + "No Targer User argument", + Some("identity.rs > cmd_demote()".to_string()), + None, + ); + Log::flush(); + + ChangeResult::NoChange("No Targer User".to_string()) + } + }; + + /* + + - [x] 3. Take ChangeResult and output response + + */ + + let outmsg = match rslt { + ChangeResult::Success(a) => { + format!("o7 Successfully demoted : {a}") + } + ChangeResult::Failed(a) => { + format!("PoroSad failed to demote : {a}") + } + ChangeResult::NoChange(a) => { + format!("uuh No Demotion Change : {a}") + } + }; + + botlog::debug( + outmsg.as_str(), + Some("identity.rs > cmd_demote()".to_string()), + Some(¶ms.msg), + ); + + botlock.botmgrs.chat.send_botmsg(super::chat::BotMsgType::Notif( + outmsg.to_string() + ), + params.clone(), + ).await; + + +} + + + +async fn getroles(params : ExecBodyParams) { + botlog::debug( + "Called cmd getroles", + Some("identity.rs > cmd_getroles()".to_string()), + Some(¶ms.msg), + ); + + /* + Usage + + getroles + - If channel is provided, provide roles for that channel specifically + + */ + + + let mut argv = params.msg.message_text.split(' '); + + argv.next(); // Skip the command name + + let arg1 = argv.next(); + + let targetuser = match arg1 { + None => return, // exit if no arguments + Some(arg) => arg, + }; + + let arg2 = argv.next(); + + let targetchnl = arg2; + + let botlock = params.bot.read().await; + + let id = botlock.get_identity(); + + let idlock = id.read().await; + + let sproles = match targetchnl { + None => { + // [ ] If targetchnl is not provided, default to pulling the current channel + idlock + .getspecialuserroles( + String::from(targetuser), + Some(Channel(params.msg.channel_login.to_lowercase())), + ) + .await + } + Some(targetchnl) => { + // [x] gets special roles for caller + let callersproles = idlock + .getspecialuserroles( + params.msg.sender.name.to_lowercase(), + Some(Channel(targetchnl.to_lowercase().to_string())), + ) + .await; + + // Below appears to be validating if getroles() should run based on caller's specific roles + // - No Need to add VIP here + if callersproles.contains(&UserRole::Mod(Channel( + targetchnl.to_lowercase().to_string(), + ))) || callersproles.contains(&UserRole::SupMod(Channel( + targetchnl.to_lowercase().to_string(), + ))) || callersproles.contains(&UserRole::Broadcaster) + { + idlock + .getspecialuserroles( + String::from(targetuser), + Some(Channel(targetchnl.to_lowercase())), + ) + .await + } else { + // Otherwise, don't get the target channel, return the current channel instead + idlock + .getspecialuserroles( + String::from(targetuser), + Some(Channel(params.msg.channel_login.to_lowercase())), + ) + .await + } + } + }; + + botlog::debug( + &format!("User roles of Target Chatter >> {:?}", sproles), + Some("identity.rs > init > getroles()".to_string()), + Some(¶ms.msg), + ); + + botlog::trace( + "Evaluating special roles", + Some("identity.rs > init > getroles()".to_string()), + Some(¶ms.msg), + ); + + let outmsg = if ((targetuser.to_lowercase() == params.msg.channel_login.to_lowercase()) + && arg2.is_none()) + || (arg2.is_some() && arg2.unwrap() == targetuser.to_lowercase()) + { + // First evaluates if they're broadcaster + + let mut outmsg = "FeelsWowMan they're the broadcaster. ".to_string(); + + // Below appears to be validating if getroles() should run based on caller's specific roles + // - No Need to add VIP here + if sproles.contains(&UserRole::Mod(Channel( + params.msg.channel_login.to_lowercase(), + ))) || sproles.contains(&UserRole::SupMod(Channel( + params.msg.channel_login.to_lowercase(), + ))) || sproles.contains(&UserRole::BotAdmin) + { + outmsg += format!("Target chatter's user roles are : {:?}", sproles).as_str(); + } + outmsg + } else if sproles.contains(&UserRole::Mod(Channel( + params.msg.channel_login.to_lowercase(), + ))) || sproles.contains(&UserRole::SupMod(Channel( + params.msg.channel_login.to_lowercase(), + ))) || sproles.contains(&UserRole::BotAdmin) + { + format!("Target chatter's user roles are : {:?}", sproles) + } else { + "Target chatter has no special roles LULE ".to_string() + }; + + botlog::debug( + format!("Chat Say Reply message : {}", outmsg).as_str(), + Some("identity.rs > init > getroles()".to_string()), + Some(¶ms.msg), + ); + + botlock.botmgrs.chat.send_botmsg(super::chat::BotMsgType::Notif( + outmsg.to_string() + ), + params.clone(), + ).await; + + + // [ ] NOTE : After the above, I should receive only the roles in the context of the current channel I received this ideally and maybe BotAdmin ; not outside +} + + + #[derive(Debug, PartialEq, Eq, Clone)] pub enum UserRole { Chatter, - Mod(ChType), // String specifies Channel - SupMod(ChType), // String specifies Channel + Mod(Channel), + SupMod(Channel), + VIP(Channel), Broadcaster, BotAdmin, } @@ -606,15 +704,9 @@ pub struct IdentityManager { pub enum ChatBadge { Broadcaster, Mod, + VIP, } -// #[derive(Debug, PartialEq, Eq)] -// pub enum ChangeResult { -// Success(String), -// Failed(String), -// NoChange(String), -// } - impl IdentityManager { pub fn init() -> IdentityManager { let mut a = HashMap::new(); @@ -682,7 +774,6 @@ impl IdentityManager { botlog::trace( "Checking within PRVMSG", Some("identity.rs > can_user_run_PRVMSG()".to_string()), - // Some(&msg), Some(msg), ); @@ -693,12 +784,14 @@ impl IdentityManager { sender_badge = Some(ChatBadge::Mod); } else if b.name == "broadcaster" { sender_badge = Some(ChatBadge::Broadcaster); + } else if b.name == "vip" { + sender_badge = Some(ChatBadge::VIP); } - } + }; self.can_user_run( msg.sender.name.to_owned(), - ChType::Channel(msg.channel_login.to_owned()), + Channel(msg.channel_login.to_owned()), sender_badge, cmdreqroles, ) @@ -709,11 +802,10 @@ impl IdentityManager { pub async fn can_user_run( &mut self, usr: String, - channelname: ChType, + channelname: Channel, chat_badge: Option, cmdreqroles: Vec, // ) -> Result> { ) -> (Permissible, ChangeResult) { - // println!{"Checking within can_user_run()"}; botlog::debug( &format!( "Checking within can_user_run() : @@ -760,8 +852,6 @@ impl IdentityManager { // [x] If cmdreqroles is empty vector , automatically assume Ok(Permissible::Allow) - // let idar = Arc::new(RwLock::new(self)); - let usr = usr.to_lowercase(); @@ -782,25 +872,25 @@ impl IdentityManager { ); } - // if cmdreqroles.len() == 0 { if cmdreqroles.is_empty() { - // return Ok(Permissible::Allow) return ( Permissible::Allow, ChangeResult::NoChange("Command has no required cmdreqroles".to_string()), ); } - let mut modrolechange = ChangeResult::NoChange("".to_string()); + let mut rolechange = ChangeResult::NoChange("".to_string()); match chat_badge { - // [x] If chatBadge::Broadcaster ... - // [x] and cmdreqroles includes UserRole::Broadcaster , Ok(Permissible::Allow) - // [x] and cmdreqroles includes UserRole::Mod("") OR UserRole::SupMod("") , Ok(Permissible::Allow) + + // If ChatBadge::Broadcaster is observed, + // Check if cmdreqroles contains Channel Level Roles . Broadcaster should have Permissible::Allow for any of these + Some(ChatBadge::Broadcaster) => { if cmdreqroles.contains(&UserRole::Broadcaster) - || cmdreqroles.contains(&UserRole::Mod(ChType::Channel(String::new()))) - || cmdreqroles.contains(&UserRole::SupMod(ChType::Channel(String::new()))) + || cmdreqroles.contains(&UserRole::Mod(OF_CMD_CHANNEL)) + || cmdreqroles.contains(&UserRole::SupMod(OF_CMD_CHANNEL)) + || cmdreqroles.contains(&UserRole::VIP(OF_CMD_CHANNEL)) { // return Ok(Permissible::Allow) return ( @@ -854,7 +944,48 @@ impl IdentityManager { usrroles_lock.push(UserRole::Mod(channelname.clone())); - modrolechange = ChangeResult::Success("Auto Promoted Mod".to_string()); + rolechange = ChangeResult::Success("Auto Promoted Mod".to_string()); + } + } + } + Some(ChatBadge::VIP) => { + botlog::info( + "VIP Chatbadge detected", + Some("identity.rs > can_user_run()".to_string()), + None, + ); + + let rolesdb = Arc::clone(&self.special_roles_users); + + self.affirm_chatter_in_db(usr.clone()).await; + + let rolesdb_lock = rolesdb.write().await; + + match (*rolesdb_lock).get(&usr.to_lowercase()) { + Some(usrroles) + if usrroles + .read() + .await + .contains(&UserRole::VIP(channelname.clone())) => + { + // Do nothing when theh have a VIP badge and have VIP badge for the channel + botlog::trace( + "Already a VIP in roles", + Some("identity.rs > can_user_run()".to_string()), + None, + ); + } + + _ => { + // In the event they have a VIP badge , are running a bot command, but don't have a channel mod role yet... + + let mut rolesdb_lock_mut = rolesdb_lock; + let usrroles = rolesdb_lock_mut.get_mut(&usr.to_lowercase()).unwrap(); + let mut usrroles_lock = usrroles.write().await; + + usrroles_lock.push(UserRole::VIP(channelname.clone())); + + rolechange = ChangeResult::Success("Auto Promoted VIP".to_string()); } } } @@ -869,7 +1000,7 @@ impl IdentityManager { None, ); - if cmdreqroles.contains(&UserRole::Mod(ChType::Channel(String::new()))) { + if cmdreqroles.contains(&UserRole::Mod(OF_CMD_CHANNEL)) { botlog::trace( "Command requires Mod Role", Some("identity.rs > can_user_run()".to_string()), @@ -898,14 +1029,14 @@ impl IdentityManager { Some("identity.rs > can_user_run()".to_string()), None, ); - return (Permissible::Allow, modrolechange); + return (Permissible::Allow, rolechange); } } } // [x] If cmdreqroles includes UserRole::SupMod("") , checks if chatter has UserRole::SupMod(channelname::ChType) to determine if Ok(Permissible::Allow) - if cmdreqroles.contains(&UserRole::SupMod(ChType::Channel(String::new()))) { + if cmdreqroles.contains(&UserRole::SupMod(OF_CMD_CHANNEL)) { if let Some(a) = self .special_roles_users .read() @@ -916,7 +1047,7 @@ impl IdentityManager { .await .contains(&UserRole::SupMod(channelname.clone())) { - return (Permissible::Allow, modrolechange); + return (Permissible::Allow, rolechange); } } } @@ -963,11 +1094,47 @@ impl IdentityManager { ); if a.read().await.contains(&UserRole::BotAdmin) { - return (Permissible::Allow, modrolechange); + return (Permissible::Allow, rolechange); } } } + // [x] If cmdreqroles includes UserRole::VIP and chatter has UserRole::VIP , Ok(Permissible::Allow) + + if cmdreqroles.contains(&UserRole::VIP(OF_CMD_CHANNEL)) { + + botlog::trace( + "Command requires VIP Role", + Some("identity.rs > can_user_run()".to_string()), + None, + ); + + if let Some(a) = self + .special_roles_users + .read() + .await + .get(&usr.to_lowercase()) + { + botlog::trace( + "Special roles found for user", + Some("identity.rs > can_user_run()".to_string()), + None, + ); + + if a.read().await.contains(&UserRole::VIP(channelname.clone())) + { + botlog::trace( + "> Special Role Identified : VIP ", + Some("identity.rs > can_user_run()".to_string()), + None, + ); + return (Permissible::Allow, rolechange); + } + } + + } + + ( Permissible::Block, ChangeResult::NoChange("Not any permissiable condition".to_string()), @@ -979,7 +1146,7 @@ impl IdentityManager { authorizer: String, authorizer_badge: &Option, trgchatter: String, - channel: Option, + channel: Option, trg_role: Option, ) -> ChangeResult { botlog::trace( @@ -992,20 +1159,27 @@ impl IdentityManager { Log::flush(); /* + + + // [x] => 03.25 - Q. Would there need to be extra handling here for VIP? + + [x] 1. Check if Authorizer Mod Badge then Auto Promote to Mod if not Mod [x] 2. Get Authorizer & Target Chatter Roles with a Given Channel [x] 3. If the authorizer & Target Chatters are the same, and the Authorizer is not a Admin, return no change [x] 4a. If Authorizer is BotAdmin & trg_role is Some(BotAdmin) , set Target as BotAdmin and return - [x] 4b. If target is Broadcaster, return NoChange - [ ] 4c. If Authorizer is a SupMod,Broadcaster,BotAdmin , can Promote Target Chatter > Mod + [x] 4b. If Authorizer is a Mod,SupMod,Broadcaster & trg_role is Some(VIP(channel)), can Promote a Target Chatter > VIP + [x] 4c. If target is Broadcaster, return NoChange + [x] 4d. If Authorizer is a SupMod,Broadcaster,BotAdmin , can Promote Target Chatter > Mod - NOTE : We do not validate trg_role here - app logic requires you to promote 1 to Mod and 1 more to SupMod - [ ] 4d. If Authorizer is a Broadcaster,BotAdmin , can Promote a Target Mod > SupMod + [x] 4e. If Authorizer is a Broadcaster,BotAdmin , can Promote a Target Mod > SupMod - NOTE : We do not validate trg_role here - app logic requires you to promote 1 to Mod and 1 more to SupMod + */ - // [x] 1. Check if Authorizer Mod Badge then Auto Promote to Mod if not Mod + // [x] 1. Check if Authorizer Mod or VIP Badge then Auto Promote to matching UserRole if not already assigned let trgchatter = trgchatter.to_lowercase(); @@ -1027,10 +1201,23 @@ impl IdentityManager { .await; } + + // [x] - May want to Auto VIP Authorizer here + Some(ChatBadge::VIP) + if (!authusrroles.contains(&UserRole::VIP(channel.clone()))) => + { + authusrroles.push(UserRole::VIP(channel.clone())); + + self.affirm_chatter_in_db(authorizer.clone()).await; + self.add_role(authorizer.clone(), UserRole::VIP(channel.clone())) + .await; + } + _ => (), } } + // [x] 2. Get Authorizer & Target Chatter Roles let trgusrroles = self @@ -1048,7 +1235,6 @@ impl IdentityManager { (authusrroles, trgusrroles) }; - // [x] 3. If the authorizer & Target Chatters are the same, and the Authorizer is not a Admin, return no change if trgchatter == authorizer && !authusrroles.contains(&UserRole::BotAdmin) { return ChangeResult::NoChange("Can't target yourself".to_string()); @@ -1067,20 +1253,59 @@ impl IdentityManager { } } - // [x] 4b. If target is Broadcaster, return NoChange + botlog::debug( + format!("VIP Evaluation : Channel = {:?} ; trg_role = {:?} ", + channel.clone(),trg_role + ).as_str(), + Some("identity.rs > promote()".to_string()), + None, + ); + + // [x] 4b. If Authorizer is a Mod,SupMod,Broadcaster & trg_role is Some(VIP(channel)), can Promote a Target Chatter > VIP + if let Some(trg_chnl) = channel.clone() { + if trg_role == Some(UserRole::VIP(trg_chnl.clone())) + && ( authusrroles.contains(&UserRole::Mod(trg_chnl.clone())) + || authusrroles.contains(&UserRole::SupMod(trg_chnl.clone())) + || authusrroles.contains(&UserRole::Broadcaster) + ) + { + if trgusrroles.contains(&UserRole::VIP(trg_chnl.clone())) { + return ChangeResult::NoChange("Already has the role".to_string()); + } + else { + self.affirm_chatter_in_db(trgchatter.clone()).await; + + self.add_role(trgchatter.clone(), UserRole::VIP(trg_chnl.clone())).await; + + return ChangeResult::Success("Promotion Successful".to_string()); + } + } else if trg_role == Some(UserRole::VIP(trg_chnl.clone())) + { + return ChangeResult::Failed(String::from("You're not permitted to do that")); + } + } + + + + + + // [x] 4c. If target is Broadcaster, return NoChange if trgusrroles.contains(&UserRole::Broadcaster) { return ChangeResult::NoChange("Can't target broadcaster".to_string()); } + /* - [ ] 4c. If Authorizer is a SupMod,Broadcaster,BotAdmin , can Promote Target Chatter > Mod + [x] 4d. If Authorizer is a SupMod,Broadcaster,BotAdmin , can Promote Target Chatter > Mod - NOTE : We do not validate trg_role here - app logic requires you to promote 1 to Mod and 1 more to SupMod - [ ] 4d. If Authorizer is a Broadcaster,BotAdmin , can Promote a Target Mod > SupMod + [x] 4e. If Authorizer is a Broadcaster,BotAdmin , can Promote a Target Mod > SupMod - NOTE : We do not validate trg_role here - app logic requires you to promote 1 to Mod and 1 more to SupMod */ if let Some(trg_chnl) = channel.clone() { + + // 1. Checks first if Target User's Roles do not Include Broadcaster,Mod,SupMod for the Channel if !trgusrroles.contains(&UserRole::Broadcaster) && !trgusrroles.contains(&UserRole::Mod(trg_chnl.clone())) && !trgusrroles.contains(&UserRole::SupMod(trg_chnl.clone())) @@ -1089,6 +1314,8 @@ impl IdentityManager { // target's Next Role would be Mod // Authorizer must be SupMod,Broadcaster,BotAdmin // > Promote target to Mod + + // 2. If Authorizer has Elevated Admin Roles for the Channel (SupMod,Broadcaster,BotAdmin) > set target to MOD if authusrroles.contains(&UserRole::SupMod(trg_chnl.clone())) || authusrroles.contains(&UserRole::Broadcaster) || authusrroles.contains(&UserRole::BotAdmin) @@ -1150,6 +1377,8 @@ impl IdentityManager { } }; + + botlog::warn( "Runtime reached undeveloped code", Some("identity.rs > promote()".to_string()), @@ -1163,7 +1392,8 @@ impl IdentityManager { authorizer: String, authorizer_badge: &Option, trgchatter: String, - channel: Option, + channel: Option, + trg_role: Option, ) -> ChangeResult { botlog::trace(&format!("IN VARS for demote() : Authorizer : {:?} ; Target Chatter : {} ; Target Channel : {:?}", authorizer,trgchatter,channel), Some("identity.rs > demote()".to_string()), None); @@ -1177,7 +1407,7 @@ impl IdentityManager { Use the roles of the above to determine whether the authorizer can demote the target user or not */ - // [x] 1. If Authorizer's Badge is Mod, ensuring Sender is in DB as Mod(Channel) + // [x] 1. If Authorizer's Badge is Mod/VIP, ensuring Sender is in DB as Mod(Channel) let trgchatter = trgchatter.to_lowercase(); @@ -1200,6 +1430,17 @@ impl IdentityManager { self.add_role(authorizer.clone(), UserRole::Mod(channel.clone())) .await; } + // [x] - May want to Auto VIP Authorizer here + Some(ChatBadge::VIP) + if (!authusrroles.contains(&UserRole::VIP(channel.clone()))) => + { + authusrroles.push(UserRole::VIP(channel.clone())); + + self.affirm_chatter_in_db(authorizer.clone()).await; + self.add_role(authorizer.clone(), UserRole::VIP(channel.clone())) + .await; + } + _ => (), } } @@ -1215,7 +1456,30 @@ impl IdentityManager { return ChangeResult::NoChange("Can't target yourself".to_string()); } - // [x] 4a. Authorizers who are BotAdmin, Broadcaster or Supermod can demote a Mod + // [x] 4. If Authorizer is a Mod,SupMod,Broadcaster & trg_role is Some(VIP(channel)), can Promote a Target Chatter > VIP + + if ( authusrroles.contains(&UserRole::Mod(channel.clone())) + || authusrroles.contains(&UserRole::SupMod(channel.clone())) + || authusrroles.contains(&UserRole::Broadcaster) + ) + && trg_role == Some(UserRole::VIP(channel.clone())) { + if !trgusrroles.contains(&UserRole::VIP(channel.clone())) { + return ChangeResult::NoChange("Already does not have VIP role".to_string()); + } + else { + // self.affirm_chatter_in_db(trgchatter.clone()).await; + + self.remove_role(trgchatter.clone(), UserRole::VIP(channel.clone())).await; + + return ChangeResult::Success("Demotion Successful".to_string()); + } + } + + + // [x] 5. - Mod/SupMod Logic + + + // [x] 5a. Authorizers who are BotAdmin, Broadcaster or Supermod can demote a Mod if (authusrroles.contains(&UserRole::BotAdmin) || authusrroles.contains(&UserRole::Broadcaster) @@ -1226,7 +1490,7 @@ impl IdentityManager { .await; return ChangeResult::Success("Demoted successfully".to_string()); } - // [x] 4b. Authorizers who are BotAdmin, Broadcaster can demote a SupMod + // [x] 5b. Authorizers who are BotAdmin, Broadcaster can demote a SupMod else if (authusrroles.contains(&UserRole::BotAdmin) || authusrroles.contains(&UserRole::Broadcaster)) && trgusrroles.contains(&UserRole::SupMod(channel.clone())) @@ -1237,7 +1501,7 @@ impl IdentityManager { .await; return ChangeResult::Success("Demoted successfully".to_string()); } - // [x] 4c. When Target chatter isnt a Mod or SupMod to demote + // [x] 5c. When Target chatter isnt a Mod or SupMod to demote else if !trgusrroles.contains(&UserRole::Mod(channel.clone())) && !trgusrroles.contains(&UserRole::SupMod(channel.clone())) { @@ -1245,7 +1509,7 @@ impl IdentityManager { "Target chatter does not have a role that can be demoted".to_string(), ); } - // [x] 4d. When they're only a Mod + // [x] 5d. When they're only a Mod else if authusrroles.contains(&UserRole::Mod(channel.clone())) { return ChangeResult::Failed("You're not permitted to do that".to_string()); } @@ -1260,8 +1524,13 @@ impl IdentityManager { pub async fn getspecialuserroles( &self, chattername: String, - channel: Option, + channel: Option, ) -> Vec { + /* + NOTE : Any NEW or CHANGES to UserRole type should have additional handling here + Specifically for Channel Elevated Roles + */ + /* Note : Ideally this be called for a given chatter name ? */ @@ -1283,22 +1552,20 @@ impl IdentityManager { // Checks if broadcaster let channel_out = match channel { - Some(channel_tmp) => { - match channel_tmp { - ChType::Channel(channel_tmp) => { - // In this block, Some input channel is given - // We're comparing the channel name with chattername to determine if they're a broadcaster - if chattername == channel_tmp.to_lowercase() { - evalsproles.push(UserRole::Broadcaster); - } - - Some(ChType::Channel(channel_tmp)) - } // _ => () + Some(chnl) => { + // In this block, Some input channel is given + // We're comparing the channel name with chattername to determine if they're a broadcaster + // if chattername == chnl.0 + if chattername == chnl.0.to_lowercase() + { + evalsproles.push(UserRole::Broadcaster); } - } + Some(chnl) + }, None => None, }; + let rolesdb = Arc::clone(&self.special_roles_users); let rolesdb_lock = rolesdb.read().await; @@ -1334,6 +1601,9 @@ impl IdentityManager { if a.read().await.contains(&UserRole::SupMod(channel.clone())) { evalsproles.push(UserRole::SupMod(channel.clone())); } + if a.read().await.contains(&UserRole::VIP(channel.clone())) { + evalsproles.push(UserRole::VIP(channel.clone())); + } // else {}; } None => { @@ -1379,8 +1649,8 @@ mod core_identity { fn user_role_identity() { Log::set_file_ext(Extension::Log); assert_eq!( - UserRole::SupMod(ChType::Channel("strong".to_string())), - UserRole::SupMod(ChType::Channel("Strong".to_lowercase())) + UserRole::SupMod(Channel("strong".to_string())), + UserRole::SupMod(Channel("Strong".to_lowercase())) ); } @@ -1395,7 +1665,8 @@ mod core_identity { let (usr, channelname, chat_badge, cmdreqroles) = ( bot, - ChType::Channel("twitchchanneltest".to_string()), + // Channel::construct("twitchchanneltest".to_string()), + Channel("twitchchanneltest".to_string()), None, vec![] ); @@ -1420,7 +1691,8 @@ mod core_identity { let test_id_mgr = IdentityManager::init(); // [x] Mod Attempts to Promote User - let channel = Some(ChType::Channel("twitchchanneltest".to_string())); + // let channel = Some(Channel::construct("twitchchanneltest".to_string())); + let channel = Some(Channel("twitchchanneltest".to_string())); let trgchatter = "regularChatter".to_string(); let authorizer_badge = &Some(ChatBadge::Mod); let authorizer = "chatMod".to_string(); @@ -1450,7 +1722,7 @@ mod core_identity { let test_id_mgr = IdentityManager::init(); // [x] Broadcaster Promotes Chatter to SupMod - let channel = Some(ChType::Channel("broadcasterer".to_string())); + let channel = Some(Channel("broadcasterer".to_string())); let trgchatter = "regularChatter".to_string(); let authorizer_badge = &Some(ChatBadge::Broadcaster); let authorizer = "broadcasterer".to_string(); @@ -1475,7 +1747,7 @@ mod core_identity { .getspecialuserroles(trgchatter.clone(), channel.clone()) .await; - assert!(rslt.contains(&UserRole::Mod(ChType::Channel("broadcasterer".to_string())))); + assert!(rslt.contains(&UserRole::Mod(Channel("broadcasterer".to_string())))); let rslt = test_id_mgr .promote( @@ -1496,7 +1768,7 @@ mod core_identity { .getspecialuserroles(trgchatter.clone(), channel.clone()) .await; - assert!(rslt.contains(&UserRole::SupMod(ChType::Channel( + assert!(rslt.contains(&UserRole::SupMod(Channel( "broadcasterer".to_string() )))); @@ -1529,8 +1801,7 @@ mod core_identity { let broadcaster = "broadcasterer".to_string(); let broadcaster_badge = &Some(ChatBadge::Broadcaster); - // let channel = Some(ChType::Channel(broadcaster.clone())); - let channel = ChType::Channel(broadcaster.clone()); + let channel = Channel(broadcaster.clone()); let supchatter = "superModerator".to_string(); let trg_role = None; @@ -1572,10 +1843,9 @@ mod core_identity { // [x] SupMod Attempts to Promote Chatter to SupMod - // let broadcaster = "broadcasterer".to_string(); let authorizer = supchatter; let authorizer_badge = &Some(ChatBadge::Broadcaster); - let channel = Some(ChType::Channel(broadcaster.clone())); + let channel = Some(Channel(broadcaster.clone())); let trgchatter = "regularChatter".to_string(); let trg_role = None; @@ -1643,10 +1913,9 @@ mod core_identity { // [x] SupMod Attempts to Promote Chatter to SupMod - // let broadcaster = "broadcasterer".to_string(); let authorizer = botadmin; let authorizer_badge = botadmin_badge; - let channel = Some(ChType::Channel("somechannel".to_string())); + let channel = Some(Channel("somechannel".to_string())); let trgchatter = "regularChatter".to_string(); let trg_role = None; @@ -1721,7 +1990,7 @@ mod core_identity { let supmod = "supmoder".to_string(); - let channel = Some(ChType::Channel("somechannel".to_string())); + let channel = Some(Channel("somechannel".to_string())); test_id_mgr.affirm_chatter_in_db(supmod.clone()).await; test_id_mgr @@ -1754,6 +2023,7 @@ mod core_identity { let authorizer = regmod.clone(); let authorizer_badge = &None; let trgchatter = supmod.clone(); + let trg_role = None; let rslt = test_id_mgr .demote( @@ -1761,6 +2031,7 @@ mod core_identity { authorizer_badge, trgchatter.clone(), channel.clone(), + trg_role.clone(), ) .await; @@ -1781,6 +2052,7 @@ mod core_identity { authorizer_badge, trgchatter.clone(), channel.clone(), + trg_role.clone(), ) .await; @@ -1795,6 +2067,7 @@ mod core_identity { authorizer_badge, trgchatter.clone(), channel.clone(), + trg_role.clone(), ) .await; @@ -1805,4 +2078,213 @@ mod core_identity { ) ); } + + + + #[tokio::test] + async fn vip_workflow_01() { + Log::set_file_ext(Extension::Log); + //Log::set_level(Level::Trace); + + // Channel Elevated User Promotes/Demotes VIP + + let test_id_mgr = IdentityManager::init(); + + + + // [x] 1. Requester has a Mod Role + + let channel = Some(Channel("somechannel".to_string())); + let authorizer_badge = &Some(ChatBadge::Mod); + let authorizer = "chatMod".to_string(); + let trgchatter = "regularChatter".to_string(); + let trg_role = Some(UserRole::VIP(channel.clone().unwrap())); + + let authorizer = authorizer.to_lowercase(); + let trgchatter = trgchatter.to_lowercase(); + + test_id_mgr.affirm_chatter_in_db(authorizer.clone()).await; + test_id_mgr.affirm_chatter_in_db(trgchatter.clone()).await; + + test_id_mgr + .add_role(authorizer.clone(), UserRole::Mod(channel.clone().unwrap())) + .await; + + let rslt = test_id_mgr + .getspecialuserroles(authorizer.clone(), channel.clone()) + .await; + + assert_eq!(rslt,vec![UserRole::Mod(channel.clone().unwrap())]); + + // [x] 2. assert getspecialuserroles for Target Chatter + + let rslt = test_id_mgr + .getspecialuserroles(trgchatter.clone(), channel.clone()) + .await; + + assert_eq!(rslt,vec![]); + + // [x] 3. Requester Promotes a Target Chatter to VIP + + let rslt = test_id_mgr + .promote( + authorizer.clone(), + authorizer_badge, + trgchatter.clone(), + channel.clone(), + trg_role.clone(), + ) + .await; + + assert_eq!( + rslt, + ChangeResult::Success("Promotion Successful".to_string()) + ); + + + // [x] 4. assert getspecialuserroles for Target Chatter + + let rslt = test_id_mgr + .getspecialuserroles(trgchatter.clone(), channel.clone()) + .await; + + assert!(rslt.contains(&UserRole::VIP(channel.clone().unwrap()))); + + // [x] 5. Requester Promotes a Target Chatter to VIP + + let rslt = test_id_mgr + .promote( + authorizer.clone(), + authorizer_badge, + trgchatter.clone(), + channel.clone(), + trg_role.clone(), + ) + .await; + + assert_eq!( + rslt, + ChangeResult::NoChange("Already has the role".to_string()) + ); + + + // [x] 6. assert getspecialuserroles for Target Chatter + + let rslt = test_id_mgr + .getspecialuserroles(trgchatter.clone(), channel.clone()) + .await; + + assert!(rslt.contains(&UserRole::VIP(channel.clone().unwrap()))); + + + // [x] 7. Requester Demotes a Target Chatter from VIP + + let rslt = test_id_mgr + .demote( + authorizer.clone(), + authorizer_badge, + trgchatter.clone(), + channel.clone(), + trg_role.clone(), + ) + .await; + + assert_eq!( + rslt, + ChangeResult::Success("Demotion Successful".to_string()) + ); + + // [x] 8. assert getspecialuserroles for Target Chatter + + let rslt = test_id_mgr + .getspecialuserroles(trgchatter.clone(), channel.clone()) + .await; + + // assert!(rslt.contains(&UserRole::VIP(channel.clone().unwrap()))); + assert_eq!(rslt,vec![]); + + + + // [x] 9. Requester Demotes a Target Chatter from VIP + + let rslt = test_id_mgr + .demote( + authorizer.clone(), + authorizer_badge, + trgchatter.clone(), + channel.clone(), + trg_role.clone(), + ) + .await; + + assert_eq!( + rslt, + ChangeResult::NoChange("Already does not have VIP role".to_string()) + ); + + // [x] 10. assert getspecialuserroles for Target Chatter + + let rslt = test_id_mgr + .getspecialuserroles(trgchatter.clone(), channel.clone()) + .await; + + assert_eq!(rslt,vec![]); + + + + + + } + + + #[tokio::test] + async fn auto_vip_workflow() { + Log::set_file_ext(Extension::Log); + + let mut test_id_mgr = IdentityManager::init(); + + + + // let channel = Some(Channel("somechannel".to_string())); + let channel = Channel("somechannel".to_string()); + let authorizer_badge = Some(ChatBadge::VIP); + let authorizer = "chatMod".to_string(); + + let authorizer = authorizer.to_lowercase(); + + + // [x] 1. assert getspecialuserroles for Target Chatter + + let rslt = test_id_mgr + .getspecialuserroles(authorizer.clone(), Some(channel.clone())) + .await; + + assert_eq!(rslt,vec![]); + + // [x] 2. Run canuserrun() for the Requester . (This is ran after BotCommands are ran) + + let rslt = test_id_mgr + .can_user_run( + authorizer.clone(), + channel.clone(), + authorizer_badge, + vec![ + UserRole::VIP(OF_CMD_CHANNEL), + ] + ).await; + + assert_eq!(rslt, + (Permissible::Allow,ChangeResult::Success("Auto Promoted VIP".to_string()))); + + // [x] 3. assert getspecialuserroles for Target Chatter + + let rslt = test_id_mgr + .getspecialuserroles(authorizer.clone(), Some(channel.clone())) + .await; + + assert_eq!(rslt,vec![UserRole::VIP(channel)]); + + + + } } diff --git a/src/custom.rs b/src/custom.rs index fc802e6..6107797 100644 --- a/src/custom.rs +++ b/src/custom.rs @@ -1,5 +1,5 @@ /* - `modules` will : + `custom` will : - be a starting refrence point for the bot instance to pull module definitions for */ @@ -11,7 +11,8 @@ pub use crate::core::botmodules::ModulesManager; // [ ] Load submodules -mod experiments; +// mod experiments; +mod experimental; // [ ] init() function that accepts bot instance - this is passed to init() on submodules @@ -19,5 +20,6 @@ pub async fn init(mgr: Arc) { // Modules initializer loads modules into the bot // this is achieved by calling submodules that also have fn init() defined - experiments::init(mgr).await + // experiments::init(mgr).await + experimental::init(mgr).await; } diff --git a/src/custom/experimental.rs b/src/custom/experimental.rs new file mode 100644 index 0000000..409abd1 --- /dev/null +++ b/src/custom/experimental.rs @@ -0,0 +1,24 @@ +/* + `experimental` will : + - be for mostly experimental +*/ + +use std::sync::Arc; + +// pub use crate::core::botinstance::BotInstance; +pub use crate::core::botmodules::ModulesManager; + +// [ ] Load submodules + +mod experiment001; +mod experiment002; + +// [ ] init() function that accepts bot instance - this is passed to init() on submodules + +pub async fn init(mgr: Arc) { + // Modules initializer loads modules into the bot + // this is achieved by calling submodules that also have fn init() defined + + experiment001::init(Arc::clone(&mgr)).await; + experiment002::init(Arc::clone(&mgr)).await; +} diff --git a/src/custom/experiments.rs b/src/custom/experimental/experiment001.rs similarity index 72% rename from src/custom/experiments.rs rename to src/custom/experimental/experiment001.rs index 529e551..dd3cba4 100644 --- a/src/custom/experiments.rs +++ b/src/custom/experimental/experiment001.rs @@ -10,17 +10,18 @@ */ + +const OF_CMD_CHANNEL:Channel = Channel(String::new()); + + use rand::Rng; use std::sync::Arc; -use twitch_irc::message::PrivmsgMessage; - -// use crate::core::botinstance::ChType::Channel; -use crate::core::botinstance::ChType; -use ChType::Channel; +use crate::core::bot_actions::ExecBodyParams; +use crate::core::botinstance::Channel; use crate::core::botlog; -use crate::core::bot_actions::actions_util::{self, BotAR}; +use crate::core::bot_actions::actions_util; use crate::core::botmodules::{BotActionTrait, BotCommand, BotModule, Listener, ModulesManager}; use crate::core::identity::UserRole::*; @@ -29,8 +30,6 @@ use tokio::time::{sleep, Duration}; 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("experiments001")), @@ -60,18 +59,6 @@ pub async fn init(mgr: Arc) { // 2. Add the BotAction to ModulesManager list1.add_to_modmgr(Arc::clone(&mgr)).await; - - // // 1. Define the BotAction - // let list1 = Listener { - // module: BotModule(String::from("experiments001")), - // name: String::from("babygirl Listener"), - // exec_body: actions_util::asyncbox(babygirl), - // help: String::from(""), - // }; - - // // 2. Add the BotAction to ModulesManager - // list1.add_to_modmgr(Arc::clone(&mgr)).await; - // 1. Define the BotAction let botc1 = BotCommand { module: BotModule(String::from("experiments001")), @@ -110,73 +97,84 @@ pub async fn init(mgr: Arc) { } -async fn good_girl(bot: BotAR, msg: PrivmsgMessage) { +async fn good_girl(params : ExecBodyParams) { + // [ ] Uses gen_ratio() to output bool based on a ratio probability . // - For example gen_ratio(2,3) is 2 out of 3 or 0.67% (numerator,denomitator) // - More Info : https://rust-random.github.io/rand/rand/trait.Rng.html#method.gen_ratio - if msg.sender.name.to_lowercase() == "ModulatingForce".to_lowercase() - || msg.sender.name.to_lowercase() == "mzNToRi".to_lowercase() - // if msg.sender.name.to_lowercase() == "mzNToRi".to_lowercase() + if params.msg.sender.name.to_lowercase() == "ModulatingForce".to_lowercase() + || params.msg.sender.name.to_lowercase() == "mzNToRi".to_lowercase() { botlog::debug( "Good Girl Detected > Pausechamp", Some("experiments > goodgirl()".to_string()), - Some(&msg), + Some(¶ms.msg), ); - let rollwin = rand::thread_rng().gen_ratio(1, 1); + let rollwin = rand::thread_rng().gen_ratio(1, 10); if rollwin { botlog::debug( "Oh that's a good girl!", Some("experiments > goodgirl()".to_string()), - Some(&msg), + Some(¶ms.msg), ); - let bot = Arc::clone(&bot); + let bot = Arc::clone(¶ms.bot); + let botlock = bot.read().await; // uses chat.say_in_reply_to() for the bot controls for messages botlock - .botmgrs - .chat - .say_in_reply_to(&msg, String::from("GoodGirl xdd ")) - .await; + .botmgrs + .chat + .say_in_reply_to( + ¶ms.msg, + String::from("GoodGirl xdd "), + params.clone() + ).await; + + } } } -async fn testy(mut _chat: BotAR, msg: PrivmsgMessage) { +async fn testy(params : ExecBodyParams) { println!("testy triggered!"); // NOTE : This test function intends to print (e.g., to stdout) at fn call botlog::debug( "testy triggered!", Some("experiments > testy()".to_string()), - Some(&msg), + Some(¶ms.msg), ); } -async fn babygirl(bot: BotAR, msg: PrivmsgMessage) { +async fn babygirl(params : ExecBodyParams) { + + println!("babygirl triggered!"); // NOTE : This test function intends to print (e.g., to stdout) at fn call botlog::debug( "babygirl triggered!", Some("experiments > babygirl()".to_string()), - Some(&msg), + Some(¶ms.msg), ); - let bot = Arc::clone(&bot); + let bot = Arc::clone(¶ms.bot); let botlock = bot.read().await; - // uses chat.say_in_reply_to() for the bot controls for messages + botlock .botmgrs .chat - .say_in_reply_to(&msg, String::from("16:13 notohh: cafdk")) - .await; + .say_in_reply_to( + ¶ms.msg, + String::from("16:13 notohh: cafdk"), + params.clone() + ).await; sleep(Duration::from_secs_f64(0.5)).await; @@ -184,8 +182,11 @@ async fn babygirl(bot: BotAR, msg: PrivmsgMessage) { botlock .botmgrs .chat - .say_in_reply_to(&msg, String::from("16:13 notohh: have fun eating princess")) - .await; + .say_in_reply_to( + ¶ms.msg, + String::from("16:13 notohh: have fun eating princess"), + params.clone() + ).await; sleep(Duration::from_secs_f64(2.0)).await; @@ -193,21 +194,23 @@ async fn babygirl(bot: BotAR, msg: PrivmsgMessage) { botlock .botmgrs .chat - .say_in_reply_to(&msg, String::from("16:13 notohh: baby girl")) - .await; + .say_in_reply_to( + ¶ms.msg, + String::from("16:13 notohh: baby girl"), + params.clone() + ).await; + } - - -async fn routinelike(_bot: BotAR, msg: PrivmsgMessage) { +async fn routinelike(params : ExecBodyParams) { println!("routinelike triggered!"); // NOTE : This test function intends to print (e.g., to stdout) at fn call botlog::debug( "routinelike triggered!", Some("experiments > routinelike()".to_string()), - Some(&msg), + Some(¶ms.msg), ); // spawn an async block that runs independently from others diff --git a/src/custom/experimental/experiment002.rs b/src/custom/experimental/experiment002.rs new file mode 100644 index 0000000..a4ecb25 --- /dev/null +++ b/src/custom/experimental/experiment002.rs @@ -0,0 +1,194 @@ +/* + Custom Modules - + + Usage : + [ ] within the file's init(), define BotActions & Load them into the ModulesManager + [ ] Define Execution Bodies for these BotActions + [ ] Afterwards, add the following to parent modules.rs file + - mod ; + - within init(), ::init(mgr).await + +*/ + + +const OF_CMD_CHANNEL:Channel = Channel(String::new()); + + +use std::sync::Arc; + +use chrono::{TimeZone,Local}; + + +use crate::core::bot_actions::ExecBodyParams; +use crate::core::botinstance::Channel; +use crate::core::botlog; + +use casual_logger::Log; + +use crate::core::bot_actions::actions_util; +use crate::core::botmodules::{BotActionTrait, BotCommand, BotModule, ModulesManager}; + +use crate::core::identity::UserRole::*; + +pub async fn init(mgr: Arc) { + + + // 1. Define the BotAction + let botc1 = BotCommand { + module: BotModule(String::from("experiments002")), + command: String::from("say"), // command call name + alias: vec![ + "s".to_string(), + ], // String of alternative names + exec_body: actions_util::asyncbox(sayout), + help: String::from("Test Command tester"), + required_roles: vec![ + BotAdmin, + // Mod(OF_CMD_CHANNEL), + VIP(OF_CMD_CHANNEL), + ], + }; + + // 2. Add the BotAction to ModulesManager + botc1.add_to_modmgr(Arc::clone(&mgr)).await; + + // If enabling by defauling at instance level , uncomment the following + // mgr.set_instance_enabled(BotModule(String::from("experiments002"))).await; + + +} + + +async fn sayout(params : ExecBodyParams) { + + + /* + usage : + + */ + + + + let reply_parent = if let Some(Some(reply)) = params.msg.source.tags.0.get("reply-parent-msg-body") { + Some(reply) + } else { None } + ; + + + let reply_parent_ts = if let Some(Some(replyts)) = params.msg.source.tags.0.get("tmi-sent-ts") { + + let a: i64 = replyts.parse().unwrap(); + let b = Local.timestamp_millis_opt(a).unwrap(); + Some(b.format("%m-%d %H:%M")) + } else { None } + ; + + // [x] Unwraps arguments from message + + + let argrslt = + if let Some((_,str1)) = params.msg.message_text.split_once(' ') { + if reply_parent.is_none() { + if let Some((channelstr,msgstr)) = str1.split_once(' ') { + Some((channelstr,msgstr)) + } + else { None } + } else if let Some((_,str2)) = str1.split_once(' ') { + if let Some((channelstr,msgstr)) = str2.split_once(' ') { + Some((channelstr,msgstr)) + } + else { None } + } else { None } + } + else { None }; + + + + + match argrslt { + Some((trgchnl,outmsg)) => { + + let bot = Arc::clone(¶ms.bot); + + let botlock = bot.read().await; + + // [x] Validate first if trgchnl exists + + botlog::trace( + &format!("[TRACE] Evaluated status of {} : {:?}", + trgchnl.to_string().clone(),botlock.botmgrs.chat.client.get_channel_status(trgchnl.to_string().clone()).await), + Some("Chat > send_botmsg".to_string()), + None, + ); + + /* + 1. If a Reply , + [ ] Get Parent Content message - reply_parent + [ ] Get Parent Chatter - reply_parent_usr + [ ] Get Parent Channel - msg.channel_login + -> Share this first then + [ ] Get Reply Message (that triggered bot command) - msgstr + [ ] Get Reply Sender - msg.sender.name + [ ] Get Target Channel - trgchnl + + 2. If not a reply + [ ] Get Reply Message (that triggered bot command) - msgstr + [ ] Get Reply Sender - msg.sender.name + [ ] Get Target Channel - trgchnl + */ + + // reply_parent_ts + + let newoutmsg = if let Some(srcmsg) = reply_parent { + + format!("{} {} @ {} : {}", + reply_parent_ts.unwrap(), + params.msg.sender.name, + params.msg.channel_login, + srcmsg) + } else { + format!("in {} - {} : {}", + params.msg.channel_login, + params.msg.sender.name, + outmsg) + }; + + botlock + .botmgrs + .chat + .say( + trgchnl.to_string(), + newoutmsg.to_string(), + params.clone(), + ).await; + + + }, + None => { + botlog::debug( + "sayout had issues trying to parse arguments", + Some("experiment002 > sayout".to_string()), + Some(¶ms.msg), + ); + + let bot = Arc::clone(¶ms.bot); + + let botlock = bot.read().await; + + // uses chat.say_in_reply_to() for the bot controls for messages + botlock + .botmgrs + .chat + .say_in_reply_to( + ¶ms.msg, + String::from("Invalid arguments"), + params.clone() + ).await; + + + }, + + } + + Log::flush(); +} \ No newline at end of file diff --git a/src/main.rs b/src/main.rs index 40b5598..6bc6c0f 100644 --- a/src/main.rs +++ b/src/main.rs @@ -33,7 +33,11 @@ pub async fn main() { for acts in (*actsdb_lock).values() { for act in acts { - let outstr = match act { + + let act_prelock = act; + let act = act_prelock.read().await; + + let outstr = match &(*act) { botmodules::BotAction::C(b) => { format!("bot actions > Command : {}", b.command) }