use std::collections::HashMap; use std::sync::Arc; use tokio::sync::RwLock; use twitch_irc::message::PrivmsgMessage; use casual_logger::Log; use crate::core::bot_actions::actions_util::{self, BotAR}; use crate::core::botinstance::{Channel,ChangeResult}; use crate::core::botlog; use crate::core::botmodules::{BotActionTrait, BotCommand, BotModule, ModulesManager}; use dotenv::dotenv; use std::env; fn adminvector() -> Vec { vec![String::from("ModulatingForce")] //vec![] } pub fn otherbots_vector() -> Vec { // vec![String::from("ModulatingForce")] // //vec![] dotenv().ok(); let mut other_bots = Vec::new(); if let Ok(value) = env::var("OtherBots") { for bot in value.split(',') { other_bots.push(String::from(bot).to_lowercase()) } } botlog::debug( &format!( "OtherBots : {:?}",other_bots, ), Some("identity.rs > otherbots_vector()".to_string()), None, ); other_bots } pub async fn init(mgr: Arc) { botlog::trace( "Went into Identity Module init", Some("identity.rs > init()".to_string()), None, ); let tempb = BotCommand { module: BotModule::construct(String::from("identity")), command: String::from("promote"), // command call name alias: vec![], // String of alternative names exec_body: actions_util::asyncbox(cmd_promote), help: String::from("promote"), required_roles: vec![ UserRole::Mod(Channel::construct(String::new())), UserRole::SupMod(Channel::construct(String::new())), 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(Channel::construct(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::construct(String::from("identity")), command: String::from("demote"), // command call name alias: vec![], // String of alternative names exec_body: actions_util::asyncbox(cmd_demote), help: String::from("demote"), required_roles: vec![ UserRole::Mod(Channel::construct(String::new())), UserRole::SupMod(Channel::construct(String::new())), 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(Channel::construct(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::construct(String::from("identity")), command: String::from("getroles"), // command call name alias: vec![], // String of alternative names exec_body: actions_util::asyncbox(getroles), help: String::from("getroles"), required_roles: vec![ UserRole::Mod(Channel::construct(String::new())), UserRole::SupMod(Channel::construct(String::new())), 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(Channel::construct(msg.channel_login.to_lowercase())), ) .await } Some(targetchnl) => { // [x] gets special roles for caller let callersproles = idlock .getspecialuserroles( msg.sender.name.to_lowercase(), Some(Channel::construct(targetchnl.to_lowercase().to_string())), ) .await; if callersproles.contains(&UserRole::Mod(Channel::construct( targetchnl.to_lowercase().to_string(), ))) || callersproles.contains(&UserRole::SupMod(Channel::construct( targetchnl.to_lowercase().to_string(), ))) || callersproles.contains(&UserRole::Broadcaster) { idlock .getspecialuserroles( String::from(targetuser), Some(Channel::construct(targetchnl.to_lowercase())), ) .await } else { // Otherwise, don't get the target channel, return the current channel instead idlock .getspecialuserroles( String::from(targetuser), Some(Channel::construct(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(Channel::construct( msg.channel_login.to_lowercase(), ))) || sproles.contains(&UserRole::SupMod(Channel::construct( 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::construct( msg.channel_login.to_lowercase(), ))) || sproles.contains(&UserRole::SupMod(Channel::construct( 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()), None, ); Log::flush(); } #[derive(Debug, PartialEq, Eq, Clone)] pub enum UserRole { Chatter, Mod(Channel), // String specifies Channel SupMod(Channel), // String specifies Channel Broadcaster, BotAdmin, } #[derive(Debug, PartialEq, Eq)] pub enum Permissible { Allow, Block, } type UserRolesDB = HashMap>>>; #[derive(Clone)] pub struct IdentityManager { special_roles_users: Arc>, } /* HashMap< String, <-- Chatter / Username Vec -- <-- Vectors are basically arrays > */ #[derive(Debug, Clone)] pub enum ChatBadge { Broadcaster, Mod, } // #[derive(Debug, PartialEq, Eq)] // pub enum ChangeResult { // Success(String), // Failed(String), // NoChange(String), // } impl IdentityManager { pub fn init() -> IdentityManager { let mut a = HashMap::new(); for admn in adminvector() { a.insert( admn.to_lowercase(), Arc::new(RwLock::new(vec![UserRole::BotAdmin])), ); } IdentityManager { special_roles_users: Arc::new(RwLock::new(a)), } } // => 03.22 - Force - Made public because botmodules unit tests pub async fn add_role(&self, trgchatter: String, trg_role: UserRole) { let mut srulock = self.special_roles_users.write().await; let mut usrrolelock = srulock .get_mut(&trgchatter) .expect("Error retrieving roles") .write() .await; usrrolelock.push(trg_role); } async fn remove_role(&self, trgchatter: String, trg_role: UserRole) { let mut srulock = self.special_roles_users.write().await; let mut usrrolelock = srulock .get_mut(&trgchatter) .expect("Error retrieving roles") .write() .await; if let Some(indx) = usrrolelock.iter().position(|value| *value == trg_role) { usrrolelock.swap_remove(indx); } } // => 03.22 - Force - Made public because botmodules unit tests pub async fn affirm_chatter_in_db(&self, trgchatter: String) { let mut srulock = self.special_roles_users.write().await; srulock .entry(trgchatter.clone()) .or_insert(Arc::new(RwLock::new(vec![]))); botlog::trace( &format!( "Ensuring User in Roles {:?}", srulock.entry(trgchatter.clone()) ), Some("IdentityManager > affirm_chatter_in_db()".to_string()), None, ); Log::flush(); } // [ ] Maybe I should create a can_user_run version that simply takes PrvMsg, but then calls can_user_run directly pub async fn can_user_run_prvmsg( &mut self, msg: &PrivmsgMessage, cmdreqroles: Vec, ) -> (Permissible, ChangeResult) { // [ ] Check what Badges in PrivmsgMessage botlog::trace( "Checking within PRVMSG", Some("identity.rs > can_user_run_PRVMSG()".to_string()), // Some(&msg), Some(msg), ); 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); } } self.can_user_run( msg.sender.name.to_owned(), Channel::construct(msg.channel_login.to_owned()), sender_badge, cmdreqroles, ) .await } pub async fn can_user_run( &mut self, usr: String, channelname: Channel, chat_badge: Option, cmdreqroles: Vec, // ) -> Result> { ) -> (Permissible, ChangeResult) { // println!{"Checking within can_user_run()"}; botlog::debug( &format!( "Checking within can_user_run() : usr : {} ; channel : {:?} ; badge : {:?} ; cmdreqroles : {:?}", usr, channelname, chat_badge, cmdreqroles ), Some("identity.rs > can_user_run()".to_string()), None, ); /* canUserRun - Input : usr:String, channelname:ChType, chat_badge:ChatBadge, cmdreqroles:Vec Output : Result> Some Possible outcomes : Ok(Permissible::Allow) , Ok(Permissible::Block) Description For a Given Chatter (with any special ChatBadge) who ran the Command at a Given Channel , check if that user can run the command based on the given cmdreqroles required by the command Inputs and business logic determine if the user can run the command based on the command's required roles */ // Requirements /* [x] If cmdreqroles is empty vector , automatically assume Ok(Permissible::Allow) [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) [x] If chatBadge::Mod ... [x] Check if they have either UserRole::Mod(channelname::ChType) or UserRole::SupMod(channelname::ChType) [x] If not, assign them UserRole::Mod(channelname::ChType) [x] If cmdreqroles includes UserRole::Mod("") , checks if chatter has UserRole::Mod(channelname::ChType) or UserRole::SupMod(channelname::ChType) to determine if Ok(Permissible::Allow) [x] If cmdreqroles includes UserRole::SupMod("") , checks if chatter has UserRole::SupMod(channelname::ChType) to determine if Ok(Permissible::Allow) [x] If cmdreqroles includes UserRole::BotAdmin and chatter has UserRole::BotAdmin , Ok(Permissible::Allow) [x] Otherwise, Ok(Permissible::Block) */ // [x] If cmdreqroles is empty vector , automatically assume Ok(Permissible::Allow) // let idar = Arc::new(RwLock::new(self)); let usr = usr.to_lowercase(); let bot_vector = otherbots_vector() ; // result of pulling from Cargo.toml botlog::trace( &format!( "Checking user is part of known bots: bot_vector.contains(&usr) : {:?}",bot_vector.contains(&usr) ), Some("identity.rs > can_user_run()".to_string()), None, ); if bot_vector.contains(&usr) { return ( Permissible::Block, ChangeResult::NoChange("Other Bots Cannot Run Commands".to_string()), ); } // 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()); 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) Some(ChatBadge::Broadcaster) => { if cmdreqroles.contains(&UserRole::Broadcaster) || cmdreqroles.contains(&UserRole::Mod(Channel::construct(String::new()))) || cmdreqroles.contains(&UserRole::SupMod(Channel::construct(String::new()))) { // return Ok(Permissible::Allow) return ( Permissible::Allow, ChangeResult::NoChange("Broadcaster Role".to_string()), ); } } // [x] If chatBadge::Mod ... // [x] Check if they have either UserRole::Mod(channelname::ChType) or UserRole::SupMod(channelname::ChType) // [x] If not, assign them UserRole::Mod(channelname::ChType) Some(ChatBadge::Mod) => { botlog::info( "Mod 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::Mod(channelname.clone())) || usrroles .read() .await .contains(&UserRole::SupMod(channelname.clone())) => { // Do nothing when theh have a mod badge and have either a supmod or mod badge for the channel botlog::trace( "Already a mod in roles", Some("identity.rs > can_user_run()".to_string()), None, ); } _ => { // In the event they have a mod 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::Mod(channelname.clone())); modrolechange = ChangeResult::Success("Auto Promoted Mod".to_string()); } } } _ => (), // Don't handle other roles here } // [x] If cmdreqroles includes UserRole::Mod("") , checks if chatter has UserRole::Mod(channelname::ChType) or UserRole::SupMod(channelname::ChType) to determine if Ok(Permissible::Allow) botlog::trace( &format!("cmd required roles : {:?}", cmdreqroles), Some("identity.rs > can_user_run()".to_string()), None, ); if cmdreqroles.contains(&UserRole::Mod(Channel::construct(String::new()))) { botlog::trace( "Command requires Mod 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::Mod(channelname.clone())) || a.read() .await .contains(&UserRole::SupMod(channelname.clone())) { botlog::trace( "> Special Role Identified : Mod ", Some("identity.rs > can_user_run()".to_string()), None, ); return (Permissible::Allow, modrolechange); } } } // [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(Channel::construct(String::new()))) { if let Some(a) = self .special_roles_users .read() .await .get(&usr.to_lowercase()) { if a.read() .await .contains(&UserRole::SupMod(channelname.clone())) { return (Permissible::Allow, modrolechange); } } } // [x] If cmdreqroles includes UserRole::BotAdmin and chatter has UserRole::BotAdmin , Ok(Permissible::Allow) botlog::trace( &format!( "Eval cmdreqroles with botadmin : {}", cmdreqroles.contains(&UserRole::BotAdmin) ), Some("identity.rs > can_user_run()".to_string()), None, ); if cmdreqroles.contains(&UserRole::BotAdmin) { botlog::trace( format!( "special roles get : {:?}", self.special_roles_users .read() .await .get(&usr.to_lowercase()) ) .as_str(), 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( format!( "special roles contains BotAdmin: {}", a.read().await.contains(&UserRole::BotAdmin) ) .as_str(), Some("identity.rs > can_user_run()".to_string()), None, ); if a.read().await.contains(&UserRole::BotAdmin) { return (Permissible::Allow, modrolechange); } } } ( Permissible::Block, ChangeResult::NoChange("Not any permissiable condition".to_string()), ) } pub async fn promote( &self, authorizer: String, authorizer_badge: &Option, trgchatter: String, channel: Option, trg_role: Option, ) -> ChangeResult { botlog::trace( &format!( "IN VARS for promote() : auth : {} ; authbadge : {:?} ; trg : {} ; Channel {:?} ; {:?}", authorizer,authorizer_badge,trgchatter,channel,trg_role), Some("identity.rs > promote()".to_string()), None, ); Log::flush(); /* [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 - 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 - 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 let trgchatter = trgchatter.to_lowercase(); let (authusrroles, trgusrroles) = if let Some(channel) = channel.clone() { let mut authusrroles = self .getspecialuserroles(authorizer.to_lowercase().clone(), Some(channel.clone())) .await; { match *authorizer_badge { Some(ChatBadge::Mod) if (!authusrroles.contains(&UserRole::Mod(channel.clone())) && !authusrroles.contains(&UserRole::SupMod(channel.clone()))) => { authusrroles.push(UserRole::Mod(channel.clone())); self.affirm_chatter_in_db(authorizer.clone()).await; self.add_role(authorizer.clone(), UserRole::Mod(channel.clone())) .await; } _ => (), } } // [x] 2. Get Authorizer & Target Chatter Roles let trgusrroles = self .getspecialuserroles(trgchatter.to_lowercase().clone(), Some(channel.clone())) .await; (authusrroles, trgusrroles) } else { let authusrroles = self .getspecialuserroles(authorizer.to_lowercase().clone(), None) .await; let trgusrroles = self .getspecialuserroles(trgchatter.to_lowercase().clone(), None) .await; (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()); } // [x] 4a. If Authorizer is BotAdmin & trg_role is Some(BotAdmin) , set Target as BotAdmin and return if authusrroles.contains(&UserRole::BotAdmin) && trg_role == Some(UserRole::BotAdmin) { if trgusrroles.contains(&UserRole::BotAdmin) { return ChangeResult::NoChange("Already has the role".to_string()); } else { self.affirm_chatter_in_db(trgchatter.clone()).await; self.add_role(trgchatter.clone(), UserRole::BotAdmin).await; return ChangeResult::Success("Promotion Successful".to_string()); } } // [x] 4b. 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 - 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 - 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() { if !trgusrroles.contains(&UserRole::Broadcaster) && !trgusrroles.contains(&UserRole::Mod(trg_chnl.clone())) && !trgusrroles.contains(&UserRole::SupMod(trg_chnl.clone())) { // target user is neither Mod nor SupMod && not broadcaster // target's Next Role would be Mod // Authorizer must be SupMod,Broadcaster,BotAdmin // > Promote target to Mod if authusrroles.contains(&UserRole::SupMod(trg_chnl.clone())) || authusrroles.contains(&UserRole::Broadcaster) || authusrroles.contains(&UserRole::BotAdmin) { self.affirm_chatter_in_db(trgchatter.clone()).await; self.add_role(trgchatter.clone(), UserRole::Mod(trg_chnl.clone())) .await; return ChangeResult::Success(String::from("Promotion Successful")); } else { // Other else conditions would be mostly spcecial responses like ChangeResult::NoChange or ChangeResult::Fail // related to authusrroles return ChangeResult::Failed(String::from("You're not permitted to do that")); } } else if !trgusrroles.contains(&UserRole::Broadcaster) && trgusrroles.contains(&UserRole::Mod(trg_chnl.clone())) && !trgusrroles.contains(&UserRole::SupMod(trg_chnl.clone())) { // target user is a Mod && not broadcaster // target's Next Role would be SupMod // [ ] #todo Check first if target have SupMod - Optional but could be done to cleanup first // Authorizer must be Broadcaster,BotAdmin // > Promote target to SupMod if authusrroles.contains(&UserRole::Broadcaster) || authusrroles.contains(&UserRole::BotAdmin) { self.affirm_chatter_in_db(trgchatter.clone()).await; self.add_role(trgchatter.clone(), UserRole::SupMod(trg_chnl.clone())) .await; self.remove_role(trgchatter, UserRole::Mod(trg_chnl.clone())) .await; return ChangeResult::Success(String::from("Promotion Successful")); } else { return ChangeResult::Failed(String::from("You're not permitted to do that")); } } else if !trgusrroles.contains(&UserRole::Broadcaster) && trgusrroles.contains(&UserRole::SupMod(trg_chnl.clone())) { // target user is a SuMod && not broadcaster // No Change return ChangeResult::Failed(String::from("Already highest available role")); } else { // since handling for channel is already done, will be handling other trguserroles situations here // At the moment, without any new roles, this should not be reached botlog::warn( "Code Warning : add handing for other trgusrroles", Some("identity.rs > promote()".to_string()), None, ); return ChangeResult::Failed(String::from("Code Warning")); } }; botlog::warn( "Runtime reached undeveloped code", Some("identity.rs > promote()".to_string()), None, ); ChangeResult::Failed(String::from("ERROR")) } pub async fn demote( &self, authorizer: String, authorizer_badge: &Option, trgchatter: String, channel: Option, ) -> ChangeResult { botlog::trace(&format!("IN VARS for demote() : Authorizer : {:?} ; Target Chatter : {} ; Target Channel : {:?}", authorizer,trgchatter,channel), Some("identity.rs > demote()".to_string()), None); Log::flush(); /* Check authorizer roles (if any) for the target channel Check Targer User's roles (if any) for the target channel Target Channel may be NONE in the case of Non-Channel related roles (FUTURE ENH) 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) let trgchatter = trgchatter.to_lowercase(); if let Some(channel) = channel { let mut authusrroles = self .getspecialuserroles(authorizer.to_lowercase().clone(), Some(channel.clone())) .await; // let authusrroles = authusrroles; { // let authusrroles_mut = &mut authusrroles; // [x] Add Mod(channel) to authusrroles // [x] #TODO also add to DB if possible? match *authorizer_badge { Some(ChatBadge::Mod) if (!authusrroles.contains(&UserRole::Mod(channel.clone())) && !authusrroles.contains(&UserRole::SupMod(channel.clone()))) => { authusrroles.push(UserRole::Mod(channel.clone())); self.add_role(authorizer.clone(), UserRole::Mod(channel.clone())) .await; } _ => (), } } // [x] 2. Targer User's Vec let trgusrroles = self .getspecialuserroles(trgchatter.to_lowercase().clone(), Some(channel.clone())) .await; // [x] 3. Return if Authorizer & Target are same chatter and Authorizer is not a BotAdmin if trgchatter == authorizer && !authusrroles.contains(&UserRole::BotAdmin) { return ChangeResult::NoChange("Can't target yourself".to_string()); } // [x] 4a. Authorizers who are BotAdmin, Broadcaster or Supermod can demote a Mod if (authusrroles.contains(&UserRole::BotAdmin) || authusrroles.contains(&UserRole::Broadcaster) || authusrroles.contains(&UserRole::SupMod(channel.clone()))) && trgusrroles.contains(&UserRole::Mod(channel.clone())) { self.remove_role(trgchatter.clone(), UserRole::Mod(channel.clone())) .await; return ChangeResult::Success("Demoted successfully".to_string()); } // [x] 4b. 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())) { self.add_role(trgchatter.clone(), UserRole::Mod(channel.clone())) .await; self.remove_role(trgchatter.clone(), UserRole::SupMod(channel.clone())) .await; return ChangeResult::Success("Demoted successfully".to_string()); } // [x] 4c. When Target chatter isnt a Mod or SupMod to demote else if !trgusrroles.contains(&UserRole::Mod(channel.clone())) && !trgusrroles.contains(&UserRole::SupMod(channel.clone())) { return ChangeResult::Failed( "Target chatter does not have a role that can be demoted".to_string(), ); } // [x] 4d. 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()); } } botlog::warn("Potential Unhandled Demotion Condition : Consider explicitely adding in for better handling", Some("identity.rs > demote()".to_string()), None); Log::flush(); ChangeResult::Failed(String::from("Did not meet criteria to demote succesfully")) } pub async fn getspecialuserroles( &self, chattername: String, channel: Option, ) -> Vec { /* Note : Ideally this be called for a given chatter name ? */ // [ ] !!! TODO: I don't think below is evaluating by given channel botlog::debug( &format!( "IN VARS > chattername {} ; channel {:?}", chattername, channel ), Some("IdentityManager > getspecialuserroles()".to_string()), None, ); // resulting vector let mut evalsproles = vec![]; let chattername = chattername.to_lowercase(); // Checks if broadcaster let channel_out = match channel { 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 { evalsproles.push(UserRole::Broadcaster); } Some(chnl) }, None => None, }; let rolesdb = Arc::clone(&self.special_roles_users); let rolesdb_lock = rolesdb.read().await; let vecroles = &(*rolesdb_lock).get(&chattername); match vecroles { Some(a) => { // [ ] This needs to take the user roles for the user, then yield only the one for the channel if channel is explicitely provided // Some(Arc::clone(a)) match channel_out { Some(channel) => { botlog::debug( &format!("INTERNAL > All Roles found {:?}", &a), Some("IdentityManager > getspecialuserroles()".to_string()), None, ); botlog::debug( &format!( "INTERNAL > eval special roles contains botadmin : {:?}", a.read().await.contains(&UserRole::BotAdmin) ), Some("IdentityManager > getspecialuserroles()".to_string()), None, ); if a.read().await.contains(&UserRole::BotAdmin) { evalsproles.push(UserRole::BotAdmin); } if a.read().await.contains(&UserRole::Mod(channel.clone())) { evalsproles.push(UserRole::Mod(channel.clone())); } if a.read().await.contains(&UserRole::SupMod(channel.clone())) { evalsproles.push(UserRole::SupMod(channel.clone())); } // else {}; } None => { if a.read().await.contains(&UserRole::BotAdmin) { evalsproles.push(UserRole::BotAdmin); } } } } None => { // here, the user has no special listed roles. Note though Broadcaster is not stored in special roles // Do nothing in this case // There may be an issue if the chattername does not exist at the moment in special_roles_users // In this case, evalsproles would only contain Broadcaster flags if any } } botlog::debug( &format!("OUT > evalsproles {:?}", &evalsproles), Some("IdentityManager > getspecialuserroles()".to_string()), None, ); // return evalsproles; evalsproles } } // ===================== // ===================== // ===================== // ===================== // ===================== #[cfg(test)] mod core_identity { use casual_logger::Extension; use super::*; #[test] fn user_role_identity() { Log::set_file_ext(Extension::Log); assert_eq!( UserRole::SupMod(Channel::construct("strong".to_string())), UserRole::SupMod(Channel::construct("Strong".to_lowercase())) ); } #[tokio::test] async fn otherbots_checks() { Log::set_file_ext(Extension::Log); let mut test_id_mgr = IdentityManager::init(); for bot in otherbots_vector() { let (usr, channelname, chat_badge, cmdreqroles) = ( bot, Channel::construct("twitchchanneltest".to_string()), None, vec![] ); let rslt = test_id_mgr.can_user_run(usr, channelname, chat_badge, cmdreqroles).await; assert_eq!( (Permissible::Block, ChangeResult::NoChange("Other Bots Cannot Run Commands".to_string())), rslt ); } } #[tokio::test] async fn promote_workflow_01() { Log::set_file_ext(Extension::Log); // Log::set_level(Level::Trace); let test_id_mgr = IdentityManager::init(); // [x] Mod Attempts to Promote User let channel = Some(Channel::construct("twitchchanneltest".to_string())); let trgchatter = "regularChatter".to_string(); let authorizer_badge = &Some(ChatBadge::Mod); let authorizer = "chatMod".to_string(); let trg_role = None; let rslt = test_id_mgr .promote( authorizer, authorizer_badge, trgchatter.clone(), channel.clone(), trg_role, ) .await; assert_eq!( rslt, ChangeResult::Failed(String::from("You're not permitted to do that")) ); } #[tokio::test] async fn promote_workflow_02() { Log::set_file_ext(Extension::Log); // Log::set_level(Level::Trace); let test_id_mgr = IdentityManager::init(); // [x] Broadcaster Promotes Chatter to SupMod let channel = Some(Channel::construct("broadcasterer".to_string())); let trgchatter = "regularChatter".to_string(); let authorizer_badge = &Some(ChatBadge::Broadcaster); let authorizer = "broadcasterer".to_string(); let trg_role = None; 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()) ); let rslt = test_id_mgr .getspecialuserroles(trgchatter.clone(), channel.clone()) .await; assert!(rslt.contains(&UserRole::Mod(Channel::construct("broadcasterer".to_string())))); 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()) ); let rslt = test_id_mgr .getspecialuserroles(trgchatter.clone(), channel.clone()) .await; assert!(rslt.contains(&UserRole::SupMod(Channel::construct( "broadcasterer".to_string() )))); let rslt = test_id_mgr .promote( authorizer.clone(), authorizer_badge, trgchatter.clone(), channel.clone(), trg_role.clone(), ) .await; assert_eq!( rslt, ChangeResult::Failed(String::from("Already highest available role")) ); } #[tokio::test] async fn promote_workflow_03() { Log::set_file_ext(Extension::Log); // Log::set_level(Level::Trace); let test_id_mgr = IdentityManager::init(); // [x] SupMod Promotes Chatter to SupMod // [x] Broadcaster first promotes a SupMod let broadcaster = "broadcasterer".to_string(); let broadcaster_badge = &Some(ChatBadge::Broadcaster); // let channel = Some(ChType::Channel(broadcaster.clone())); let channel = Channel::construct(broadcaster.clone()); let supchatter = "superModerator".to_string(); let trg_role = None; let rslt = test_id_mgr .promote( broadcaster.clone(), broadcaster_badge, supchatter.clone(), Some(channel.clone()), trg_role.clone(), ) .await; assert_eq!( rslt, ChangeResult::Success("Promotion Successful".to_string()) ); let rslt = test_id_mgr .promote( broadcaster.clone(), broadcaster_badge, supchatter.clone(), Some(channel.clone()), trg_role.clone(), ) .await; assert_eq!( rslt, ChangeResult::Success("Promotion Successful".to_string()) ); let rslt = test_id_mgr .getspecialuserroles(supchatter.clone(), Some(channel.clone())) .await; assert!(rslt.contains(&UserRole::SupMod(channel))); // [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(Channel::construct(broadcaster.clone())); let trgchatter = "regularChatter".to_string(); let trg_role = None; 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()) ); let rslt = test_id_mgr .getspecialuserroles(trgchatter.clone(), channel.clone()) .await; assert!(rslt.contains(&UserRole::Mod(channel.clone().unwrap()))); let rslt = test_id_mgr .promote( authorizer.clone(), authorizer_badge, trgchatter.clone(), channel.clone(), trg_role.clone(), ) .await; assert_eq!( rslt, ChangeResult::Failed("You're not permitted to do that".to_string()) ); } #[tokio::test] async fn promote_workflow_04() { Log::set_file_ext(Extension::Log); // Log::set_level(Level::Trace); let test_id_mgr = IdentityManager::init(); // [x] BotAdmin Promotes Chatter to SupMod // [x] Create BotAdmin first let botadmin = "botadministrator".to_string(); let botadmin_badge = &None; test_id_mgr.affirm_chatter_in_db(botadmin.clone()).await; test_id_mgr .add_role(botadmin.clone(), UserRole::BotAdmin) .await; let rslt = test_id_mgr .getspecialuserroles(botadmin.clone(), None) .await; assert!(rslt.contains(&UserRole::BotAdmin)); // [x] SupMod Attempts to Promote Chatter to SupMod // let broadcaster = "broadcasterer".to_string(); let authorizer = botadmin; let authorizer_badge = botadmin_badge; let channel = Some(Channel::construct("somechannel".to_string())); let trgchatter = "regularChatter".to_string(); let trg_role = None; 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()) ); let rslt = test_id_mgr .getspecialuserroles(trgchatter.clone(), channel.clone()) .await; assert!(rslt.contains(&UserRole::Mod(channel.clone().unwrap()))); 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()) ); let rslt = test_id_mgr .getspecialuserroles(trgchatter.clone(), channel.clone()) .await; assert!(rslt.contains(&UserRole::SupMod(channel.clone().unwrap()))); let rslt = test_id_mgr .promote( authorizer.clone(), authorizer_badge, trgchatter.clone(), channel.clone(), trg_role.clone(), ) .await; assert_eq!( rslt, ChangeResult::Failed(String::from("Already highest available role")) ); } #[tokio::test] async fn demote_workflow_01() { Log::set_file_ext(Extension::Log); // Log::set_level(Level::Trace); // [x] SupMod demotes a mod // [x] create a SupMod first let test_id_mgr = IdentityManager::init(); let supmod = "supmoder".to_string(); let channel = Some(Channel::construct("somechannel".to_string())); test_id_mgr.affirm_chatter_in_db(supmod.clone()).await; test_id_mgr .add_role(supmod.clone(), UserRole::SupMod(channel.clone().unwrap())) .await; let rslt = test_id_mgr .getspecialuserroles(supmod.clone(), channel.clone()) .await; assert!(rslt.contains(&UserRole::SupMod(channel.clone().unwrap()))); // [x] Create regular mod let regmod = "moder".to_string(); test_id_mgr.affirm_chatter_in_db(regmod.clone()).await; test_id_mgr .add_role(regmod.clone(), UserRole::Mod(channel.clone().unwrap())) .await; let rslt = test_id_mgr .getspecialuserroles(regmod.clone(), channel.clone()) .await; assert!(rslt.contains(&UserRole::Mod(channel.clone().unwrap()))); // [x] Regular mod attempts to demote a supmod let authorizer = regmod.clone(); let authorizer_badge = &None; let trgchatter = supmod.clone(); let rslt = test_id_mgr .demote( authorizer.clone(), authorizer_badge, trgchatter.clone(), channel.clone(), ) .await; assert_eq!( rslt, ChangeResult::Failed("You're not permitted to do that".to_string()) ); // [x] SupMod demotes regular mod let authorizer = supmod; let authorizer_badge = &None; let trgchatter = regmod; let rslt = test_id_mgr .demote( authorizer.clone(), authorizer_badge, trgchatter.clone(), channel.clone(), ) .await; assert_eq!( rslt, ChangeResult::Success("Demoted successfully".to_string()) ); let rslt = test_id_mgr .demote( authorizer.clone(), authorizer_badge, trgchatter.clone(), channel.clone(), ) .await; assert_eq!( rslt, ChangeResult::Failed( "Target chatter does not have a role that can be demoted".to_string() ) ); } }