// use futures::lock::Mutex; use tokio::sync::mpsc::UnboundedReceiver; use tokio::sync::RwLock; use twitch_irc::login::StaticLoginCredentials; use twitch_irc::message::PrivmsgMessage; use twitch_irc::message::ServerMessage; use twitch_irc::transport::tcp::TCPTransport; use twitch_irc::transport::tcp::TLS; use twitch_irc::ClientConfig; use twitch_irc::SecureTCPTransport; use twitch_irc::TwitchIRCClient; // use std::borrow::Borrow; use dotenv::dotenv; use std::borrow::BorrowMut; use std::boxed; use std::cell::Ref; use std::env; use std::collections::HashMap; use rand::Rng; // Important to use tokios Mutex here since std Mutex doesn't work with async functions use tokio::sync::Mutex; use crate::core::botmodules::BotAction; use crate::core::ratelimiter; use crate::core::ratelimiter::RateLimiter; use crate::core::botmodules; use crate::core::botmodules::ModulesManager; use crate::core::identity::{ChangeResult, IdentityManager, Permissible}; use std::cell::RefCell; use std::rc::Rc; use std::sync::Arc; // use futures::lock::Mutex; use std::pin::Pin; //use std::borrow::Borrow; use core::borrow::Borrow; // pub type BotAR = Arc<RwLock<BotInstance>>; use super::botmodules::bot_actions::actions_util::BotAR; use casual_logger::{Level, Log}; pub mod botlog { /* Module intends to add some layers to logging with the module user only requiring to pass : - String Log message - Option<String> - Code_Module - Option<PrivmsgMessage> - this is used to parse out Chatter & Channel into the logs */ use casual_logger::{Level, Log}; use twitch_irc::message::PrivmsgMessage; // trace, debug, info, notice, warn, error, fatal /* in main : Log::debug("Checking bot actions", Some("main()".to_string()), None); in log : [blalba@timestmp] debug = "Checking bot actions", */ pub fn trace( in_msg: &str, in_module: Option<String>, in_prvmsg: Option<&PrivmsgMessage>, ) -> () { let (chnl, chatter) = match in_prvmsg { Some(prvmsg) => { //Log::trace(&format!("(#{}) {}: {}", prvmsg.channel_login, prvmsg.sender.name, prvmsg.message_text)); ( Some(prvmsg.channel_login.clone()), Some(prvmsg.sender.name.clone()), ) // <-- Clone fine atm while we're just working with Strings } None => (None, None), }; Log::trace_t( in_msg, casual_logger::Table::default() // .str("Channel", &format!("{:?}", chnl)) .str("Chatter", &format!("{:?}", chatter)) .str("Code_Module", &format!("{:?}", in_module)), ); } pub fn debug( in_msg: &str, in_module: Option<String>, in_prvmsg: Option<&PrivmsgMessage>, ) -> () { let (chnl, chatter) = match in_prvmsg { Some(prvmsg) => { //Log::trace(&format!("(#{}) {}: {}", prvmsg.channel_login, prvmsg.sender.name, prvmsg.message_text)); ( Some(prvmsg.channel_login.clone()), Some(prvmsg.sender.name.clone()), ) // <-- Clone fine atm while we're just working with Strings } None => (None, None), }; Log::debug_t( in_msg, casual_logger::Table::default() // .str("Channel", &format!("{:?}", chnl)) .str("Chatter", &format!("{:?}", chatter)) .str("Code_Module", &format!("{:?}", in_module)), ); } pub fn info(in_msg: &str, in_module: Option<String>, in_prvmsg: Option<&PrivmsgMessage>) -> () { let (chnl, chatter) = match in_prvmsg { Some(prvmsg) => { //Log::trace(&format!("(#{}) {}: {}", prvmsg.channel_login, prvmsg.sender.name, prvmsg.message_text)); ( Some(prvmsg.channel_login.clone()), Some(prvmsg.sender.name.clone()), ) // <-- Clone fine atm while we're just working with Strings } None => (None, None), }; Log::info_t( in_msg, casual_logger::Table::default() // .str("Channel", &format!("{:?}", chnl)) .str("Chatter", &format!("{:?}", chatter)) .str("Code_Module", &format!("{:?}", in_module)), ); } pub fn notice( in_msg: &str, in_module: Option<String>, in_prvmsg: Option<&PrivmsgMessage>, ) -> () { let (chnl, chatter) = match in_prvmsg { Some(prvmsg) => { //Log::trace(&format!("(#{}) {}: {}", prvmsg.channel_login, prvmsg.sender.name, prvmsg.message_text)); ( Some(prvmsg.channel_login.clone()), Some(prvmsg.sender.name.clone()), ) // <-- Clone fine atm while we're just working with Strings } None => (None, None), }; Log::notice_t( in_msg, casual_logger::Table::default() // .str("Channel", &format!("{:?}", chnl)) .str("Chatter", &format!("{:?}", chatter)) .str("Code_Module", &format!("{:?}", in_module)), ); } pub fn warn(in_msg: &str, in_module: Option<String>, in_prvmsg: Option<&PrivmsgMessage>) -> () { let (chnl, chatter) = match in_prvmsg { Some(prvmsg) => { //Log::trace(&format!("(#{}) {}: {}", prvmsg.channel_login, prvmsg.sender.name, prvmsg.message_text)); ( Some(prvmsg.channel_login.clone()), Some(prvmsg.sender.name.clone()), ) // <-- Clone fine atm while we're just working with Strings } None => (None, None), }; Log::warn_t( in_msg, casual_logger::Table::default() // .str("Channel", &format!("{:?}", chnl)) .str("Chatter", &format!("{:?}", chatter)) .str("Code_Module", &format!("{:?}", in_module)), ); } pub fn error( in_msg: &str, in_module: Option<String>, in_prvmsg: Option<&PrivmsgMessage>, ) -> () { let (chnl, chatter) = match in_prvmsg { Some(prvmsg) => { //Log::trace(&format!("(#{}) {}: {}", prvmsg.channel_login, prvmsg.sender.name, prvmsg.message_text)); ( Some(prvmsg.channel_login.clone()), Some(prvmsg.sender.name.clone()), ) // <-- Clone fine atm while we're just working with Strings } None => (None, None), }; Log::error_t( in_msg, casual_logger::Table::default() // .str("Channel", &format!("{:?}", chnl)) .str("Chatter", &format!("{:?}", chatter)) .str("Code_Module", &format!("{:?}", in_module)), ); } pub fn fatal<'a>( in_msg: &'a str, in_module: Option<String>, in_prvmsg: Option<&PrivmsgMessage>, ) -> &'a str { let (chnl, chatter) = match in_prvmsg { Some(prvmsg) => { //Log::trace(&format!("(#{}) {}: {}", prvmsg.channel_login, prvmsg.sender.name, prvmsg.message_text)); ( Some(prvmsg.channel_login.clone()), Some(prvmsg.sender.name.clone()), ) // <-- Clone fine atm while we're just working with Strings } None => (None, None), }; Log::fatal_t( in_msg, casual_logger::Table::default() // .str("Channel", &format!("{:?}", chnl)) .str("Chatter", &format!("{:?}", chatter)) .str("Code_Module", &format!("{:?}", in_module)), ); in_msg } } #[derive(Debug, PartialEq, Eq, Hash, Clone)] pub enum ChType { Channel(String), } pub use ChType::Channel; #[derive(Clone)] pub struct Chat { pub ratelimiters: Arc<Mutex<HashMap<ChType, RateLimiter>>>, // used to limit messages sent per channel pub client: TwitchIRCClient<TCPTransport<TLS>, StaticLoginCredentials>, } impl Chat { pub fn init( ratelimiters: HashMap<ChType, RateLimiter>, client: TwitchIRCClient<TCPTransport<TLS>, StaticLoginCredentials>, ) -> Chat { Chat { ratelimiters: Arc::new(Mutex::new(ratelimiters)), client: client, } } pub async fn init_channel(&mut self, chnl: ChType) -> () { let n = RateLimiter::new(); self.ratelimiters.lock().await.insert(chnl, n); } // pub async fn say_in_reply_to(&mut self, msg:& PrivmsgMessage , mut outmsg:String) -> () { pub async fn say_in_reply_to(&self, msg: &PrivmsgMessage, mut outmsg: String) -> () { /* formats message before sending to TwitchIRC - [x] Custom String Formatting (e.g., adding random black spaces) - [x] Ratelimiter Handling - [ ] Checkf if BotActions is Enabled & Caller is Allowed to Run */ let a = Arc::clone(&self.ratelimiters); let mut a = a.lock().await; let contextratelimiter = a // .get_mut() .get_mut(&Channel(String::from(&msg.channel_login))) .expect("ERROR: Issue with Rate limiters"); match contextratelimiter.check_limiter() { ratelimiter::LimiterResp::Allow => { let maxblanks = rand::thread_rng().gen_range(1..=20); for _i in 1..maxblanks { let blankspace: &str = ""; outmsg.push_str(blankspace); } self.client.say_in_reply_to(msg, outmsg).await.unwrap(); // println!("(#{}) > {}", msg.channel_login, "rate limit counter increase"); // Log::trace(&format!("(#{}) > {}", msg.channel_login, "rate limit counter increase")); botlog::trace( &format!( "(#{}) > {}", msg.channel_login, "rate limit counter increase" ), Some("Chat > say_in_reply_to".to_string()), Some(&msg), ); contextratelimiter.increment_counter(); // println!("{:?}",self.ratelimiters); // Log::trace(&format!("{:?}",self.ratelimiters)); botlog::trace( &format!("{:?}", self.ratelimiters), Some("Chat > say_in_reply_to".to_string()), Some(&msg), ); } ratelimiter::LimiterResp::Skip => { (); // do nothing otherwise } } Log::flush(); } async fn say(&self, _: String, _: String) -> () { // more info https://docs.rs/twitch-irc/latest/twitch_irc/client/struct.TwitchIRCClient.html#method.say // self.client.say(msg,outmsg).await.unwrap(); } async fn me(&self, _: String, _: String) -> () { // more info https://docs.rs/twitch-irc/latest/twitch_irc/client/struct.TwitchIRCClient.html#method.say // self.client.me(msg,outmsg).await.unwrap(); } async fn me_in_reply_to(&self, _: String, _: String) -> () { // more info https://docs.rs/twitch-irc/latest/twitch_irc/client/struct.TwitchIRCClient.html#method.say // self.client.me(msg,outmsg).await.unwrap(); } } #[derive(Clone)] pub struct BotManagers { // pub botmodules : ModulesManager, pub identity: Arc<RwLock<IdentityManager>>, pub chat: Chat, } impl BotManagers { pub fn init( ratelimiters: HashMap<ChType, RateLimiter>, client: TwitchIRCClient<TCPTransport<TLS>, StaticLoginCredentials>, ) -> BotManagers { BotManagers { identity: Arc::new(RwLock::new(IdentityManager::init())), chat: Chat::init(ratelimiters, client), } } pub fn rIdentity(self) -> Arc<RwLock<IdentityManager>> { self.identity } } pub struct ArcBox<T: Clone>(pub Arc<Mutex<T>>); impl<T: Clone> ArcBox<T> { pub fn inst(&self) -> &Mutex<T> { &self.0 } } //#[derive(Clone)] // #[derive(Copy)] // <-- Cannot be derived pub struct BotInstance { pub prefix: char, pub bot_channel: ChType, pub incoming_messages: Arc<RwLock<UnboundedReceiver<ServerMessage>>>, pub botmodules: Arc<ModulesManager>, pub twitch_oauth: String, pub bot_channels: Vec<ChType>, pub botmgrs: BotManagers, //modesmgr : ModesManager, // Silent/Quiet , uwu , frisky/horny } impl BotInstance { pub async fn init() -> BotInstance { dotenv().ok(); let login_name = env::var("login_name").unwrap().to_owned(); let oauth_token = env::var("access_token").unwrap().to_owned(); let prefix = env::var("prefix") .unwrap() .to_owned() .chars() .next() .expect("ERROR : when defining prefix"); /* Vector of channels to join */ let mut botchannels = Vec::new(); for chnl in env::var("bot_channels").unwrap().split(',') { // println!("(Env Var # {})",chnl); botchannels.push(Channel(String::from(chnl))); } let config = ClientConfig::new_simple(StaticLoginCredentials::new( login_name.to_owned(), Some(oauth_token.to_owned()), )); let (incoming_messages, client) = TwitchIRCClient::<SecureTCPTransport, StaticLoginCredentials>::new(config); // hashmap for channels and their associated ratelimiters let mut ratelimiters = HashMap::new(); for Channel(chnl) in &botchannels { // For each channel in botchannels client.join(chnl.to_owned()).unwrap(); // ratelimiters are a hashmap of channel and a corresponding rate limiter let n = RateLimiter::new(); ratelimiters.insert(Channel(String::from(chnl)), n); //self.chat.ratelimiters.insert(Channel(String::from(chnl)),n); } // let b = BotInstance { // prefix : prefix, // bot_channel : Channel(login_name) , // incoming_messages : Arc::new(RwLock::new(incoming_messages)), // botmodules : ModulesManager::init().await, // twitch_oauth : oauth_token, // bot_channels : botchannels, // botmgrs : BotManagers::init(ratelimiters,client), // }; BotInstance { prefix: prefix, bot_channel: Channel(login_name), incoming_messages: Arc::new(RwLock::new(incoming_messages)), botmodules: ModulesManager::init().await, twitch_oauth: oauth_token, bot_channels: botchannels, botmgrs: BotManagers::init(ratelimiters, client), } // b } pub async fn runner(self) -> () { let bot = Arc::new(RwLock::new(self)); let join_handle = tokio::spawn(async move { let a = bot.read().await; let mut a = a.incoming_messages.write().await; while let Some(message) = a.recv().await { match message { ServerMessage::Notice(msg) => { match &msg.channel_login { Some(chnl) => { // println!("NOTICE : (#{}) {}", chnl, msg.message_text) // Log::notice(&format!("NOTICE : (#{}) {}", chnl, msg.message_text)); botlog::notice( &format!("NOTICE : (#{}) {}", chnl, msg.message_text), Some("BotInstance > runner()".to_string()), None, ); } None => { // println!("NOTICE : {}", msg.message_text); // Log::notice(&format!("NOTICE : {}", msg.message_text)); botlog::notice( &format!("NOTICE : {}", msg.message_text), Some("BotInstance > runner()".to_string()), None, ); } } } ServerMessage::Privmsg(msg) => { // println!("(#{}) {}: {}", msg.channel_login, msg.sender.name, msg.message_text); // Log::trace(&format!("(#{}) {}: {}", msg.channel_login, msg.sender.name, msg.message_text)); botlog::debug( &format!( "Twitch Chat > {} @ #{}: {}", msg.channel_login, msg.sender.name, msg.message_text ), Some("BotInstance > runner()".to_string()), Some(&msg), ); // println!("Privmsg section"); // Log::debug(&format!("Privmsg section")); botlog::trace( &format!("Privmsg section"), Some("BotInstance > runner()".to_string()), Some(&msg), ); BotInstance::listener_main_prvmsg(Arc::clone(&bot), &msg).await; } ServerMessage::Whisper(msg) => { // println!("(w) {}: {}", msg.sender.name, msg.message_text); // Log::trace(&format!("(w) {}: {}", msg.sender.name, msg.message_text)); botlog::trace( &format!("(w) {}: {}", msg.sender.name, msg.message_text), Some("BotInstance > runner()".to_string()), None, ); } ServerMessage::Join(msg) => { // println!("JOINED: {}", msg.channel_login); // Log::notice(&format!("JOINED: {}", msg.channel_login)); botlog::notice( &format!("JOINED: {}", msg.channel_login), Some("BotInstance > runner()".to_string()), None, ); } ServerMessage::Part(msg) => { // println!("PARTED: {}", msg.channel_login); // Log::notice(&format!("PARTED: {}", msg.channel_login)); botlog::notice( &format!("PARTED: {}", msg.channel_login), Some("BotInstance > runner()".to_string()), None, ); } _ => {} }; Log::flush(); } }); join_handle.await.unwrap(); } pub fn get_botmodules(self) -> Arc<ModulesManager> { self.botmodules } pub async fn get_botmgrs(self) -> BotManagers { let a = self.botmgrs; a } pub fn get_identity(&self) -> Arc<RwLock<IdentityManager>> { Arc::clone(&self.botmgrs.identity) } pub fn get_prefix(&self) -> char { (*self).prefix } // ----------------- // PRIVATE FUNCTIONS pub async fn listener_main_prvmsg(bot: BotAR, msg: &PrivmsgMessage) -> () { // println!(">> Inner listenermain_prvmsg()"); // Log::trace(">> Inner listenermain_prvmsg()"); botlog::trace( ">> Inner listenermain_prvmsg()", Some("BotInstance > listener_main_prvmsg()".to_string()), Some(&msg), ); // let a = a; // println!("(#{}) {}: {}", msg.channel_login, msg.sender.name, msg.message_text); // // [ ] 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) let botlock = bot.read().await; let hacts = Arc::clone(&botlock.botmodules.botactions); // let hacts = hacts.read().await; let a = hacts.read().await; // println!("hacts size : {}",(*a).len()); // Log::debug(&format!("hacts size : {}",(*a).len())); botlog::trace( &format!("hacts size : {}", (*a).len()), Some("BotInstance > listener_main_prvmsg()".to_string()), Some(&msg), ); // println!(">> Inner listenermain_prvmsg() >> before for loop of bot actions"); // Log::trace(">> Inner listenermain_prvmsg() >> before for loop of bot actions"); botlog::trace( ">> Inner listenermain_prvmsg() >> before for loop of bot actions", Some("BotInstance > listener_main_prvmsg()".to_string()), Some(&msg), ); for (_m, acts) in &*hacts.read().await { // println!(">> Inner listenermain_prvmsg() >> checking bot actions"); // Log::trace(">> Inner listenermain_prvmsg() >> checking bot actions"); botlog::trace( ">> Inner listenermain_prvmsg() >> checking bot actions", Some("BotInstance > listener_main_prvmsg()".to_string()), Some(&msg), ); // let bot = bot; for a in acts { // println!(">> Inner listenermain_prvmsg() >> checking bot actions >> 2"); // Log::trace(">> Inner listenermain_prvmsg() >> checking bot actions >> 2"); botlog::trace( ">> Inner listenermain_prvmsg() >> checking bot actions >> 2", Some("BotInstance > listener_main_prvmsg()".to_string()), Some(&msg), ); let _act = match a { crate::core::botmodules::BotAction::C(c) => { /* BotCommand handling - - [x] Checks if the input message is a prefix with command name or alias - [ ] Validate User can run based on identityModule(From_Bot)::can_user_run( _usr:String, _channelname:ChType, _chat_badge:ChatBadge, _cmdreqroles:Vec<UserRole>) */ // for v in msg.message_text.split(" ") { // println!("args : {v}"); // } // println!("Reviewing internal commands"); // Log::trace("Reviewing internal commands"); botlog::trace( "Reviewing internal commands", Some("BotInstance > listener_main_prvmsg()".to_string()), Some(&msg), ); // let inpt = msg.message_text.split("\n").next().expect("ERROR during BotCommand"); let inpt = msg .message_text .split(" ") .next() .expect("ERROR during BotCommand"); // [x] Check if a bot command based on ... // [x] prefix + command let mut confirmed_bot_command = false; let instr = bot.read().await.get_prefix(); if inpt == String::from(instr) + c.command.as_str() { confirmed_bot_command = true; } // [x] prefix + alias for alias in &c.alias { let instr = bot.read().await.get_prefix(); if inpt == String::from(instr) + alias.as_str() { confirmed_bot_command = true; } } if confirmed_bot_command { // println!("Confirmed bot command"); // Log::debug("Confirmed bot command"); botlog::debug( "Confirmed bot command", Some("BotInstance > listener_main_prvmsg()".to_string()), Some(&msg), ); // println!("Going for botlock"); // Log::trace("Going for botlock"); botlog::trace( "Going for botlock", Some("BotInstance > listener_main_prvmsg()".to_string()), Some(&msg), ); let botlock = bot.read().await; // println!("Going for identity"); // Log::trace("Going for identity"); botlog::trace( "Going for identity", Some("BotInstance > listener_main_prvmsg()".to_string()), Some(&msg), ); let id = botlock.get_identity(); let eval = { let mut id = id.write().await; // println!("Unlocking identity"); // Log::trace("Unlocking identity"); botlog::trace( "Unpacking identity", Some("BotInstance > listener_main_prvmsg()".to_string()), Some(&msg), ); let (a, b) = id.can_user_run_PRVMSG(&msg, c.required_roles.clone()).await; // // [-] #todo : need ot add functionality around here to do an o7 when a mod has been promoted => Preferring to do this outside the mutex // if let ChangeResult::Success(b) = b { // // let b = b.to_lowercase(); // // let b = b.contains(&"Auto Promoted Mod".to_lowercase()); // if b.to_lowercase().contains(&"Auto Promoted Mod".to_lowercase()) { // let chat = // } // } (a, b) }; // println!("Checking if permissible"); Log::trace("Checking if permissible"); botlog::trace( "Checking if permissible", Some("BotInstance > listener_main_prvmsg()".to_string()), Some(&msg), ); let (eval, rolechange) = eval; if let ChangeResult::Success(b) = rolechange { if b.to_lowercase() .contains(&"Auto Promoted Mod".to_lowercase()) { botlog::notice( "Assigning Mod UserRole to Mod", Some("botinstance > listener_main_prvmsg()".to_string()), Some(&msg), ); // println!("Read() lock Bot"); // Log::trace("Read() lock Bot"); botlog::trace( "Read() lock Bot", Some("BotInstance > listener_main_prvmsg()".to_string()), Some(&msg), ); 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; } } match eval { Permissible::Allow => { // println!("Executed as permissible"); // Log::debug("Executed as permissible"); botlog::debug( "Executed as permissible", Some("BotInstance > listener_main_prvmsg()".to_string()), Some(&msg), ); let a = Arc::clone(&bot); c.execute(a, msg.clone()).await; // println!("exit out of execution"); // Log::trace("exit out of execution"); botlog::trace( "exit out of execution", Some("BotInstance > listener_main_prvmsg()".to_string()), Some(&msg), ); } Permissible::Block => { // println!("User Not allowed to run command"); // Log::info("User Not allowed to run command"); botlog::info( "User Not allowed to run command", Some("BotInstance > listener_main_prvmsg()".to_string()), Some(&msg), ); } // _ => (), }; } } crate::core::botmodules::BotAction::L(l) => { let a = Arc::clone(&bot); l.execute(a, msg.clone()).await; } _ => (), }; } } // // [ ] There should be a BotCommand Listener to check for prefixes ran // println!("End of Separate Listener Main prvmsg"); // Log::trace("End of Separate Listener Main prvmsg"); botlog::trace( "End of Separate Listener Main prvmsg", Some("BotInstance > listener_main_prvmsg()".to_string()), Some(&msg), ); // self // bot Log::flush(); } } // ====================================== // ====================================== // ====================================== // ====================================== // UNIT TEST MODULES #[cfg(test)] mod tests { fn always() { assert_eq!(1, 1); } }