From 2a884f95716cac2dc9c708a9a0a155e132013b2a Mon Sep 17 00:00:00 2001 From: modulatingforce <modulatingforce@gmail.com> Date: Wed, 29 Jan 2025 11:54:25 -0500 Subject: [PATCH 01/14] multi command --- src/bin/fun_bot.rs | 2 +- src/bin/simple_command_bot.rs | 2 +- src/bin/simple_module.rs | 2 +- src/botcore/bot_objects.rs | 51 ++++++++++++++++++++++++++++-- src/botcore/bot_objects/command.rs | 28 ++++++++++++---- 5 files changed, 74 insertions(+), 11 deletions(-) diff --git a/src/bin/fun_bot.rs b/src/bin/fun_bot.rs index de67fda..f7bc67f 100644 --- a/src/bin/fun_bot.rs +++ b/src/bin/fun_bot.rs @@ -49,7 +49,7 @@ pub mod funbot_objs { /// Create a Command Object fn create_cmd_test() -> Command { - let mut cmd = Command::new("remind besty".to_string(),"annytfYandere ".to_string()); + let mut cmd = Command::new(vec!["remind besty".to_string()],"annytfYandere ".to_string()); async fn execbody(bot:Arc<Bot>,message:ServerMessage) -> Result<String,String> { if let ServerMessage::Privmsg(msg) = message { diff --git a/src/bin/simple_command_bot.rs b/src/bin/simple_command_bot.rs index a6ec70e..934ddf7 100644 --- a/src/bin/simple_command_bot.rs +++ b/src/bin/simple_command_bot.rs @@ -29,7 +29,7 @@ pub async fn main() { let mut bot = Bot::new(); /* 1. Create a new blank cmd */ - let mut cmd = Command::new("test".to_string(),"".to_string()); + let mut cmd = Command::new(vec!["test".to_string()],"".to_string()); /* 2. Define an async fn callback execution */ async fn execbody(bot:Arc<Bot>,message:ServerMessage) -> Result<String,String> { diff --git a/src/bin/simple_module.rs b/src/bin/simple_module.rs index 61d66e5..ae483c5 100644 --- a/src/bin/simple_module.rs +++ b/src/bin/simple_module.rs @@ -55,7 +55,7 @@ pub mod custom_mod { pub fn cmd_test() -> Command { /* 1. Create a new cmd */ - let mut cmd = Command::new("test".to_string(),"".to_string()); + let mut cmd = Command::new(vec!["test".to_string()],"".to_string()); /* 2. Define exec callback */ async fn execbody(bot:Arc<Bot>,message:ServerMessage) -> Result<String,String> { diff --git a/src/botcore/bot_objects.rs b/src/botcore/bot_objects.rs index 9fb6985..a563d84 100644 --- a/src/botcore/bot_objects.rs +++ b/src/botcore/bot_objects.rs @@ -59,7 +59,7 @@ pub mod built_in_objects { fn create_disable_cmd() -> Command { /* 1. Create a new blank cmd */ - let mut cmd = Command::new("disable".to_string(),"".to_string()); + let mut cmd = Command::new(vec!["disable".to_string()],"".to_string()); /* 2. Define an async fn callback execution */ async fn execbody(bot:Arc<Bot>,message:ServerMessage) -> Result<String,String> { @@ -88,7 +88,7 @@ pub mod built_in_objects { fn create_enable_cmd() -> Command { /* 1. Create a new blank cmd */ - let mut cmd = Command::new("enable".to_string(),"".to_string()); + let mut cmd = Command::new(vec!["enable".to_string()],"".to_string()); /* 2. Define an async fn callback execution */ async fn execbody(bot:Arc<Bot>,message:ServerMessage) -> Result<String,String> { @@ -116,4 +116,51 @@ pub mod built_in_objects { } + /// adminonly command that grants a temporary role + fn create_iam_role_cmd() -> Command { + /* 1. Create a new blank cmd */ + let mut cmd = Command::new(vec![ + "I am ".to_string(), + "I'm ".to_string() + ],"".to_string()); + + /* 2. Define an async fn callback execution */ + async fn execbody(bot:Arc<Bot>,message:ServerMessage) -> Result<String,String> { + if let ServerMessage::Privmsg(msg) = message { + for (i,arg) in msg.message_text.split(" ").enumerate() { + if i > 1 { + // bot.disable_module(msg.channel_login.clone(), arg.to_string()).await; + // #todo + // if not dont have the badge or have a lower priviledge badge + // and they dont have an active guest badge, ths admin can be + // recognzed wth that badge + if arg == "mod" || arg == "moderator" { + + } + if arg == "vip" { + + } + if arg == "broadcaster" || arg == "strimmer" || arg == "streamer" { + + } + } + } + let _ = bot.client.say_in_reply_to(&msg, String::from("Disabled!")).await ; + } + Result::Err("Not Valid message type".to_string()) + } + + /* 3. Set and Store the execution body using `async_box()` */ + cmd.set_exec_fn(asyncfn_box(execbody)); + + /* 4. optionally, remove admin only default flag */ + cmd.set_admin_only(true); + + // /* 5. optionally, set min badge*/ + // cmd.set_min_badge(Badge::Moderator); + cmd + + } + + } \ No newline at end of file diff --git a/src/botcore/bot_objects/command.rs b/src/botcore/bot_objects/command.rs index 574a22e..9100df5 100644 --- a/src/botcore/bot_objects/command.rs +++ b/src/botcore/bot_objects/command.rs @@ -20,7 +20,7 @@ use super::ExecBody; #[derive(Clone)] pub struct Command { - command : String, + commands : Vec<String>, exec_fn : Arc<ExecBody>, min_badge : Badge, admin_only : bool, @@ -38,13 +38,13 @@ impl Command /// if a blank prefix is given, the bot will look for the bot prefix instead /// /// By default, the new command is admin_only - pub fn new(command:String,prefix:String) -> Command { + pub fn new(commands:Vec<String>,prefix:String) -> Command { async fn execbody(_:Arc<Bot>,_:ServerMessage) -> Result<String,String> { Result::Ok("success".to_string()) } Command { - command , + commands , prefix , exec_fn : Arc::new(asyncfn_box(execbody)), min_badge : Badge::Vip, @@ -79,12 +79,28 @@ impl Command } else { prefixed_cmd.push_str(&cmd.prefix); } - prefixed_cmd.push_str(&cmd.command); - return message.message_text.starts_with(prefixed_cmd.as_str()) + for cmd_nm in &cmd.commands { + prefixed_cmd.push_str(cmd_nm); + if message.message_text.starts_with(prefixed_cmd.as_str()) { + return true; + } + }; + return false; } - fn caller_badge_ok(cmd:&Command,_bot:Arc<Bot>,message:PrivmsgMessage) -> bool { + fn caller_badge_ok(cmd:&Command,bot:Arc<Bot>,message:PrivmsgMessage) -> bool { + + // senders that are admins skip badge check if the command is adminonly + if cmd.admin_only && bot.get_admins().contains(&message.sender.login) { + return true; + } ; + + // adminonly commands will can only be ran by admins + if cmd.admin_only && bot.get_admins().contains(&message.sender.login) { + return false; + } + for badge in message.badges { match cmd.min_badge { -- 2.49.0 From a9da9f41929897435cfef3c0784bcaa6aaae4b27 Mon Sep 17 00:00:00 2001 From: modulatingforce <modulatingforce@gmail.com> Date: Wed, 29 Jan 2025 20:00:20 -0500 Subject: [PATCH 02/14] guest_badge module --- src/bin/fun_bot.rs | 15 +++- src/bin/simple_module.rs | 4 +- src/botcore/bot.rs | 122 +++++++++++++++++++++---- src/botcore/bot_objects.rs | 120 +++++++++++++++++++++---- src/botcore/bot_objects/command.rs | 29 +++++- src/botcore/modules.rs | 23 +++-- src/custom_mods.rs | 1 + src/custom_mods/guest_badge.rs | 140 +++++++++++++++++++++++++++++ src/lib.rs | 5 +- 9 files changed, 413 insertions(+), 46 deletions(-) create mode 100644 src/custom_mods/guest_badge.rs diff --git a/src/bin/fun_bot.rs b/src/bin/fun_bot.rs index f7bc67f..eaf0487 100644 --- a/src/bin/fun_bot.rs +++ b/src/bin/fun_bot.rs @@ -1,7 +1,9 @@ //! WIP Fun forcebot with catered customizations #todo //! //! Custom modules that can be managed in chat through `disable` and `enable` commands -//! - funbot +//! - funbot +//! - guests +//! //! //! Be sure the followig is defined in `.env` //! - login_name @@ -14,7 +16,7 @@ //! - Get a Bot Chat Token here - <https://twitchtokengenerator.com> //! - More Info - <https://dev.twitch.tv/docs/authentication> -use forcebot_rs_v2::Bot; +use forcebot_rs_v2::{custom_mods::guest_badge, Bot}; #[tokio::main] pub async fn main() { @@ -24,8 +26,11 @@ pub async fn main() { /* 1. Load the module into the bot */ bot.load_module(funbot_objs::create_module()); + + /* 2. Load Custom Modules */ + bot.load_module(guest_badge::create_module()); - /* 2. Run the bot */ + /* 3. Run the bot */ bot.run().await; } @@ -39,7 +44,9 @@ pub mod funbot_objs { /// Create a Module with a loaded Command object pub fn create_module() -> Module { - let mut custom_mod = Module::new("funbot".to_string(), "".to_string()); + let mut custom_mod = Module::new( + vec!["funbot".to_string()], + "".to_string()); custom_mod.load_command(create_cmd_test()); diff --git a/src/bin/simple_module.rs b/src/bin/simple_module.rs index ae483c5..002ca22 100644 --- a/src/bin/simple_module.rs +++ b/src/bin/simple_module.rs @@ -44,7 +44,9 @@ pub mod custom_mod { /// Module with a loaded command pub fn new() -> Module { /* 1. Create a new module */ - let mut custom_mod = Module::new("test".to_string(), "".to_string()); + let mut custom_mod = Module::new( + vec!["test".to_string()], + "".to_string()); /* 2. Load the cmd into a new module */ custom_mod.load_command(cmd_test()); diff --git a/src/botcore/bot.rs b/src/botcore/bot.rs index 89598af..ac19db2 100644 --- a/src/botcore/bot.rs +++ b/src/botcore/bot.rs @@ -3,11 +3,11 @@ use tokio::sync::{mpsc::UnboundedReceiver, Mutex}; use twitch_irc::{login::StaticLoginCredentials, message::ServerMessage, SecureTCPTransport, TwitchIRCClient}; use dotenv::dotenv; -use std::{env, sync::Arc}; +use std::{env, sync::Arc, time::{Duration, Instant}}; -use crate::{Command, Listener, Module}; +use crate::{Badge, Command, Listener, Module}; -use super::bot_objects::built_in_objects; +use super::{bot_objects::built_in_objects, modules::{self, Status}}; /// Twitch chat bot @@ -30,7 +30,9 @@ pub struct Bot /// modules modules: Vec<Module>, /// channel module status - channel_module_status: Mutex<Vec<(String,String,String)>> + channel_module_status: Mutex<Vec<(String,String,modules::Status)>>, + /// chatter guest badges - chatter,channel,Badge,start_time,duration + chatter_guest_badges: Mutex<Vec<(String,String,Badge,Instant,Duration)>> } @@ -46,6 +48,8 @@ impl Bot /// - bot_admins pub fn new() -> Bot { + + dotenv().ok(); let bot_login_name = env::var("login_name").unwrap().to_owned(); let oauth_token = env::var("access_token").unwrap().to_owned(); @@ -107,6 +111,7 @@ impl Bot admins, modules: vec![], channel_module_status: Mutex::new(vec![]), + chatter_guest_badges: Mutex::new(vec![]), }; for cmd in built_in_objects::create_commands() { @@ -145,18 +150,28 @@ impl Bot for cmd in &(*bot).commands { let a = cmd.clone(); - if a.command_triggered(bot.clone(),msg.clone()) { + if a.command_triggered(bot.clone(),msg.clone()).await { let _ = cmd.execute_fn(bot.clone(),message.clone()).await; } } - for module in &(*bot).modules { + 'module_loop: for module in &(*bot).modules { + // skip modules that are disable let cms_lock = bot.channel_module_status.lock().await; - if cms_lock.contains(&(msg.channel_login.clone(),module.get_name(),"disabled".to_string())) - { continue; } + + for channel_flags in cms_lock.iter() { + if channel_flags.0 == msg.channel_login.clone() { + + if module.get_names().contains(&channel_flags.1) && channel_flags.2 == Status::Disabled { + continue 'module_loop; + } + } + } + + for listener in module.get_listeners() { @@ -169,7 +184,7 @@ impl Bot for cmd in module.get_commands() { let a = cmd.clone(); - if a.command_triggered(bot.clone(),msg.clone()) { + if a.command_triggered(bot.clone(),msg.clone()).await { let _ = cmd.execute_fn(bot.clone(),message.clone()).await; } @@ -196,6 +211,16 @@ impl Bot pub fn load_command(&mut self,c : Command) { self.commands.push(c); } + + + pub fn get_module(&self,module:String) -> Option<Module> { + for modl in self.modules.clone() { + if modl.get_names().contains(&module) { + return Some(modl); + } + } + None + } @@ -212,26 +237,93 @@ impl Bot self.modules.push(m) } + pub async fn get_channel_module_status(&self,channel:String,module:String) -> Status { + let found_disabled:bool = { + let cms_lock = self.channel_module_status.lock().await; + + let mut found = false; + + for channel_flags in cms_lock.iter() { + if channel_flags.0 == channel { + if channel_flags.1 == module && channel_flags.2 == Status::Disabled { + found = true; + } + } + } + found + }; + if found_disabled { return Status::Disabled;} + else { return Status::Enabled; }; + + } + pub async fn disable_module(&self,channel:String,module:String){ - let mut lock = self.channel_module_status.lock().await; - if !lock.contains(&(channel.clone(),module.clone(),"disabled".to_string())) { - lock.push((channel,module,"disabled".to_string())); - } + + let found_disabled:bool = { + let cms_lock = self.channel_module_status.lock().await; + + let mut found = false; + + for channel_flags in cms_lock.iter() { + if channel_flags.0 == channel { + if channel_flags.1 == module && channel_flags.2 == Status::Disabled { + found = true; + } + } + } + found + }; + + if !found_disabled { + + let mut cms_lock = self.channel_module_status.lock().await; + cms_lock.push((channel,module,Status::Disabled)); + + + } + + } pub async fn enable_module(&self,channel:String,module:String){ let mut lock = self.channel_module_status.lock().await; - if lock.contains(&(channel.clone(),module.clone(),"disabled".to_string())) { + if lock.contains(&(channel.clone(),module.clone(),Status::Disabled)) { let index = lock .iter() .position(|x| *x == - (channel.clone(),module.clone(),"disabled".to_string())) + (channel.clone(),module.clone(),Status::Disabled)) .unwrap(); lock.remove(index); } } + pub async fn get_channel_guest_badges(&self,chatter:String,channel:String) -> Vec<(Badge,Instant,Duration)> { + + let bot = Arc::new(self); + let guest_badges_lock = bot.chatter_guest_badges.lock().await; + + let mut badges = vec![]; + for temp_badge in guest_badges_lock.iter() { + if temp_badge.0 == chatter && temp_badge.1 == channel && + temp_badge.3 + temp_badge.4 > Instant::now() + { + badges.push((temp_badge.2.clone(),temp_badge.3,temp_badge.4)); + } + } + + badges + } + + + pub async fn issue_new_guest_badge(&self,chatter:String,channel:String,badge:Badge,start:Instant,dur:Duration) { + let bot = Arc::new(self); + let mut guest_badges_lock = bot.chatter_guest_badges.lock().await; + + guest_badges_lock.push((chatter,channel,badge,start,dur)); + + } + } diff --git a/src/botcore/bot_objects.rs b/src/botcore/bot_objects.rs index a563d84..065e08a 100644 --- a/src/botcore/bot_objects.rs +++ b/src/botcore/bot_objects.rs @@ -13,7 +13,7 @@ use super::bot::Bot; /// chat badge -#[derive(Clone)] +#[derive(Clone,PartialEq, Eq,Debug)] pub enum Badge { Moderator, Broadcaster, @@ -36,13 +36,13 @@ where /// collection of functions to create built in objects pub mod built_in_objects { - + const TEMP_BADGE_DUR_MIN:u64 = 30; - use std::sync::Arc; + use std::{sync::Arc, time::{Duration, Instant}}; use twitch_irc::message::ServerMessage; - use crate::{asyncfn_box, Badge, Bot, Command}; + use crate::{asyncfn_box, modules::Status, Badge, Bot, Command}; /// create a vector of command build in objects @@ -52,6 +52,7 @@ pub mod built_in_objects { cmds.push(create_disable_cmd()); cmds.push(create_enable_cmd()); + cmds.push(create_iam_role_cmd()); cmds @@ -64,12 +65,19 @@ pub mod built_in_objects { /* 2. Define an async fn callback execution */ async fn execbody(bot:Arc<Bot>,message:ServerMessage) -> Result<String,String> { if let ServerMessage::Privmsg(msg) = message { - for (i,arg) in msg.message_text.split(" ").enumerate() { + let mut action_taken = false; + for (i,arg) in msg.message_text.replace("\u{e0000}","").trim().split(" ").enumerate() { if i > 1 { - bot.disable_module(msg.channel_login.clone(), arg.to_string()).await; + if bot.get_channel_module_status(msg.channel_login.clone(), arg.to_string()).await == Status::Enabled { + action_taken = true; + bot.disable_module(msg.channel_login.clone(), arg.to_string()).await; + } + } } - let _ = bot.client.say_in_reply_to(&msg, String::from("Disabled!")).await ; + if action_taken { + let _ = bot.client.say_in_reply_to(&msg, String::from("Disabled!")).await ; + } } Result::Err("Not Valid message type".to_string()) } @@ -93,13 +101,38 @@ pub mod built_in_objects { /* 2. Define an async fn callback execution */ async fn execbody(bot:Arc<Bot>,message:ServerMessage) -> Result<String,String> { if let ServerMessage::Privmsg(msg) = message { - for (i,arg) in msg.message_text.split(" ").enumerate() { + + let mut bot_message="".to_string(); + let mut re_enabled = false; + + for (i,arg) in msg.message_text.replace("\u{e0000}","").trim().split(" ").enumerate() { if i > 1 { - bot.enable_module(msg.channel_login.clone(), arg.to_string()).await; + + if Status::Disabled == bot.get_channel_module_status(msg.channel_login.clone(), arg.to_string()).await { + bot.enable_module(msg.channel_login.clone(), arg.to_string()).await; + + //bot.get_modules() + if let Some(found_mod) = bot.get_module(arg.to_string()) { + bot_message = bot_message.to_string() + found_mod.get_bot_read_description().as_str(); + } + re_enabled = true; + } + } } + + if re_enabled { + + if bot_message.len() > 250 { + bot_message = bot_message[..250].to_string(); + } + + let _ = bot.client.say_in_reply_to(&msg, + format!("Enabled! {}", bot_message) + ).await ; + + } - let _ = bot.client.say_in_reply_to(&msg, String::from("Enabled!")).await ; } Result::Err("Not Valid message type".to_string()) } @@ -113,7 +146,7 @@ pub mod built_in_objects { /* 5. optionally, set min badge*/ cmd.set_min_badge(Badge::Moderator); cmd - } + } /// adminonly command that grants a temporary role @@ -121,31 +154,88 @@ pub mod built_in_objects { /* 1. Create a new blank cmd */ let mut cmd = Command::new(vec![ "I am ".to_string(), - "I'm ".to_string() + "I'm ".to_string(), + "Im a ".to_string(), ],"".to_string()); /* 2. Define an async fn callback execution */ async fn execbody(bot:Arc<Bot>,message:ServerMessage) -> Result<String,String> { if let ServerMessage::Privmsg(msg) = message { - for (i,arg) in msg.message_text.split(" ").enumerate() { + for (i,arg) in msg.message_text.replace("\u{e0000}","").trim().split(" ").enumerate() { if i > 1 { // bot.disable_module(msg.channel_login.clone(), arg.to_string()).await; // #todo // if not dont have the badge or have a lower priviledge badge // and they dont have an active guest badge, ths admin can be // recognzed wth that badge + if arg == "mod" || arg == "moderator" { + let curr_temp_badges = bot.get_channel_guest_badges(msg.sender.login.clone(), msg.channel_login.clone()).await; + let mut found = false; + for temp_badge in curr_temp_badges { + if temp_badge.0 == Badge::Moderator { + found = true; + } + } + if found { + /* do nothing */ + } else { + bot.issue_new_guest_badge( + msg.sender.login.clone(), + msg.channel_login.clone(), + Badge::Moderator, Instant::now(), Duration::from_secs(60*TEMP_BADGE_DUR_MIN)).await; + + let _ = bot.client.say_in_reply_to(&msg, + format!("Temp {:?} issued for {:?} minutes",Badge::Moderator,TEMP_BADGE_DUR_MIN) + ).await ; + } } if arg == "vip" { + let curr_temp_badges = bot.get_channel_guest_badges(msg.sender.login.clone(), msg.channel_login.clone()).await; + let mut found = false; + for temp_badge in curr_temp_badges { + if temp_badge.0 == Badge::Vip { + found = true; + } + } + if found { + /* do nothing */ + } else { + bot.issue_new_guest_badge( + msg.sender.login.clone(), + msg.channel_login.clone(), + Badge::Vip, Instant::now(), Duration::from_secs(60*TEMP_BADGE_DUR_MIN)).await; + let _ = bot.client.say_in_reply_to(&msg, + format!("Temp {:?} issued for {:?} minutes",Badge::Vip,TEMP_BADGE_DUR_MIN) + ).await ; + } } if arg == "broadcaster" || arg == "strimmer" || arg == "streamer" { - + let curr_temp_badges = bot.get_channel_guest_badges(msg.sender.login.clone(), msg.channel_login.clone()).await; + let mut found = false; + for temp_badge in curr_temp_badges { + if temp_badge.0 == Badge::Broadcaster { + found = true; + } + } + if found { + /* do nothing */ + } else { + bot.issue_new_guest_badge( + msg.sender.login.clone(), + msg.channel_login.clone(), + Badge::Broadcaster, Instant::now(), Duration::from_secs(60*TEMP_BADGE_DUR_MIN)).await; + + let _ = bot.client.say_in_reply_to(&msg, + format!("Temp {:?} issued for {:?} minutes",Badge::Broadcaster,TEMP_BADGE_DUR_MIN) + ).await ; + } } } } - let _ = bot.client.say_in_reply_to(&msg, String::from("Disabled!")).await ; + // let _ = bot.client.say_in_reply_to(&msg, String::from("Disabled!")).await ; } Result::Err("Not Valid message type".to_string()) } diff --git a/src/botcore/bot_objects/command.rs b/src/botcore/bot_objects/command.rs index 9100df5..0cb4249 100644 --- a/src/botcore/bot_objects/command.rs +++ b/src/botcore/bot_objects/command.rs @@ -24,6 +24,8 @@ pub struct Command exec_fn : Arc<ExecBody>, min_badge : Badge, admin_only : bool, + /// admin role overrides channel badgen- default : false + admin_override : bool, prefix : String, custom_cond_fn : fn(Arc<Bot>,PrivmsgMessage) -> bool, } @@ -49,6 +51,7 @@ impl Command exec_fn : Arc::new(asyncfn_box(execbody)), min_badge : Badge::Vip, admin_only : true, + admin_override : false , custom_cond_fn : |_:Arc<Bot>,_:PrivmsgMessage| true, } } @@ -70,7 +73,7 @@ impl Command /// checks if the trigger condition is met /// specifically if the message is a valid command and min badge roles provided /// - pub fn command_triggered(&self,bot:Arc<Bot>,msg:PrivmsgMessage) -> bool { + pub async fn command_triggered(&self,bot:Arc<Bot>,msg:PrivmsgMessage) -> bool { fn cmd_called(cmd:&Command,bot:Arc<Bot>,message:PrivmsgMessage) -> bool { let mut prefixed_cmd = "".to_string(); @@ -89,18 +92,21 @@ impl Command } - fn caller_badge_ok(cmd:&Command,bot:Arc<Bot>,message:PrivmsgMessage) -> bool { + async fn caller_badge_ok(cmd:&Command,bot:Arc<Bot>,message:PrivmsgMessage) -> bool { // senders that are admins skip badge check if the command is adminonly if cmd.admin_only && bot.get_admins().contains(&message.sender.login) { return true; } ; - // adminonly commands will can only be ran by admins + // adminOnly commands will can only be ran by admins if cmd.admin_only && bot.get_admins().contains(&message.sender.login) { return false; } + // admin role overrides badge check if enabled + if cmd.admin_override && bot.get_admins().contains( &message.sender.login) { return true; } + for badge in message.badges { match cmd.min_badge { @@ -123,6 +129,15 @@ impl Command } } + for temp_badge in bot.get_channel_guest_badges(message.sender.login, message.channel_login).await { + match (cmd.min_badge.clone(),temp_badge.0) { + (Badge::Broadcaster,Badge::Broadcaster) => return true, + (Badge::Moderator,Badge::Moderator) | (Badge::Moderator,Badge::Broadcaster) => return true, + (Badge::Vip,Badge::Vip)|(Badge::Vip,Badge::Moderator)|(Badge::Vip,Badge::Broadcaster) => return true, + _ => (), + } + } + return false; } @@ -153,7 +168,7 @@ impl Command // custom_cond_ok(self, bot.clone(), msg.clone())); cmd_called(self, bot.clone(), msg.clone()) && - caller_badge_ok(self, bot.clone(), msg.clone()) && + caller_badge_ok(self, bot.clone(), msg.clone()).await && admin_only_ok(self, bot.clone(), msg.clone()) && custom_cond_ok(self, bot, msg) @@ -175,4 +190,10 @@ impl Command pub fn set_admin_only(&mut self,admin_only:bool) { self.admin_only = admin_only } + + /// sets admin_override . This lets admins bypass + /// badge restrictions + pub fn set_admin_override(&mut self,admin_override:bool) { + self.admin_override = admin_override + } } diff --git a/src/botcore/modules.rs b/src/botcore/modules.rs index e57ecc2..8a342c8 100644 --- a/src/botcore/modules.rs +++ b/src/botcore/modules.rs @@ -2,14 +2,21 @@ use crate::{Command, Listener}; +#[derive(PartialEq, Eq,Debug)] +pub enum Status { + Disabled, + Enabled +} /// Bot `Module` that groups a set of `bot_objects` /// /// Elevated chatters can disable modules by their name or chat alias +#[derive(Clone)] pub struct Module { - name: String, - _alias: String, + name: Vec<String>, + // _alias: String, + bot_read_description : String, listeners: Vec<Listener>, commands: Vec<Command>, } @@ -17,10 +24,11 @@ pub struct Module impl Module { /// create a new module - pub fn new(name:String,alias:String) -> Module { + pub fn new(name:Vec<String>,bot_read_description:String) -> Module { Module { name, - _alias: alias, + // _alias: alias, + bot_read_description, listeners: vec![], commands: vec![] } @@ -44,8 +52,13 @@ impl Module self.commands.clone() } - pub fn get_name(&self) -> String { + pub fn get_names(&self) -> Vec<String> { self.name.clone() } + pub fn get_bot_read_description(&self) -> String { + self.bot_read_description.clone() + } + + } \ No newline at end of file diff --git a/src/custom_mods.rs b/src/custom_mods.rs index e69de29..cd79787 100644 --- a/src/custom_mods.rs +++ b/src/custom_mods.rs @@ -0,0 +1 @@ +pub mod guest_badge; \ No newline at end of file diff --git a/src/custom_mods/guest_badge.rs b/src/custom_mods/guest_badge.rs new file mode 100644 index 0000000..175a0e1 --- /dev/null +++ b/src/custom_mods/guest_badge.rs @@ -0,0 +1,140 @@ +use std::{sync::Arc, time::{Duration, Instant}}; + +use twitch_irc::message::ServerMessage; + +use crate::{asyncfn_box, Badge, Bot, Command, Module}; + +/// guest_badge / guest module +/// +/// Temporary badges can be issued to chatters. The bot then opens functionality +/// to that chatter baded on the recognized role +/// +/// Chatters with real badge roles will be able to share guest +/// badges based on their role +/// +/// +/// +/// +/// +/// +/// + +const VIP_GIVEN_DUR_MIN:u64 = 15; +const MOD_GIVEN_DUR_MIN:u64 = 30; + + + +pub fn create_module() -> Module { + + let mut custom_mod = Module::new( + vec!["guests".to_string()], + "Temp Guest badges can be given by chatters with badges. ".to_string()); + + custom_mod.load_command(create_cmd_mod()); + custom_mod.load_command(create_cmd_vip()); + + custom_mod + +} + +fn create_cmd_vip() -> Command { + + let mut cmd = Command::new(vec!["vip".to_string()],"".to_string()); + + + async fn execbody(bot:Arc<Bot>,message:ServerMessage) -> Result<String,String> { + if let ServerMessage::Privmsg(msg) = message { + + let guest_dur_min = { + let mut result=VIP_GIVEN_DUR_MIN; + for badge in msg.clone().badges { + if badge.name == "vip" { result = VIP_GIVEN_DUR_MIN ; } + if badge.name == "moderator" { result = MOD_GIVEN_DUR_MIN ; } + } + result + }; + + + let mut badges_issued =false; + for (i,arg) in msg.message_text.replace("\u{e0000}","").trim().split(" ").enumerate() { + if i > 1 { + let mut already_vip = false; + + + for guest_badge in bot.get_channel_guest_badges(arg.trim().to_string(), msg.channel_login.clone()).await { + if guest_badge.0 == Badge::Vip { already_vip = true } + } + if !already_vip { + badges_issued= true; + bot.issue_new_guest_badge( + arg.trim().to_string(), + msg.channel_login.clone(), + Badge::Vip, Instant::now(), Duration::from_secs(60*guest_dur_min)).await; + + } + } + } + if badges_issued { + + + let _= bot.client.say_in_reply_to( + &msg.clone(), format!("Guest badges issued for {} min",guest_dur_min)).await; + return Result::Ok("Success".to_string()); + } + + } + Result::Err("Not Valid message type".to_string()) + } + + cmd.set_exec_fn(asyncfn_box(execbody)); + + cmd.set_admin_only(false); + cmd.set_admin_override(true); + cmd.set_min_badge(Badge::Vip); + cmd + +} + +fn create_cmd_mod() -> Command { + + let mut cmd = Command::new(vec!["mod".to_string()],"".to_string()); + + async fn execbody(bot:Arc<Bot>,message:ServerMessage) -> Result<String,String> { + if let ServerMessage::Privmsg(msg) = message { + + + let mut badges_issued =false; + for (i,arg) in msg.message_text.replace("\u{e0000}","").trim().split(" ").enumerate() { + if i > 1 { + + let mut already_mod = false; + for guest_badge in bot.get_channel_guest_badges(arg.trim().to_string(), msg.channel_login.clone()).await { + if guest_badge.0 == Badge::Moderator { already_mod = true } + } + if !already_mod { + badges_issued= true; + bot.issue_new_guest_badge( + arg.trim().to_string(), + msg.channel_login.clone(), + Badge::Moderator, Instant::now(), Duration::from_secs(60*MOD_GIVEN_DUR_MIN)).await; + } + } + } + if badges_issued { + let _= bot.client.say_in_reply_to( + &msg, format!("Guest badges issued for {} min",MOD_GIVEN_DUR_MIN)).await; + return Result::Ok("Success".to_string()); + } + } + Result::Err("Not Valid message type".to_string()) + } + + cmd.set_exec_fn(asyncfn_box(execbody)); + + cmd.set_admin_only(false); + cmd.set_admin_override(true); + cmd.set_min_badge(Badge::Moderator); + cmd + + +} \ No newline at end of file diff --git a/src/lib.rs b/src/lib.rs index 9ecb36f..3bb703a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -137,7 +137,7 @@ //! //! ``` //! -//! ## Modertor Reactor +//! ## Moderator Reactor //! //! ``` //! @@ -197,10 +197,11 @@ pub mod botcore; +pub mod custom_mods; pub use crate::botcore::bot::Bot; pub use crate::botcore::bot_objects::asyncfn_box; pub use crate::botcore::bot_objects::listener::Listener; pub use crate::botcore::bot_objects::command::Command; pub use crate::botcore::modules::Module; pub use crate::botcore::bot_objects::Badge; - +pub use crate::botcore::modules; -- 2.49.0 From 6b09aeed694f519971d04a24f5eb590efa49bad1 Mon Sep 17 00:00:00 2001 From: modulatingforce <modulatingforce@gmail.com> Date: Thu, 30 Jan 2025 10:46:59 -0500 Subject: [PATCH 03/14] custom pyramid --- .gitignore | 3 + Cargo.lock | 7 + Cargo.toml | 2 + readme.md | 17 ++- src/bin/fun_bot.rs | 4 +- src/bin/simple_module.rs | 5 +- src/botcore/bot.rs | 25 ++- src/custom_mods.rs | 3 +- src/custom_mods/guest_badge.rs | 14 +- src/custom_mods/pyramid.rs | 270 +++++++++++++++++++++++++++++++++ src/lib.rs | 11 +- 11 files changed, 337 insertions(+), 24 deletions(-) create mode 100644 src/custom_mods/pyramid.rs diff --git a/.gitignore b/.gitignore index c77774d..8e6827f 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,6 @@ # env .env + +# temp +/tmp \ No newline at end of file diff --git a/Cargo.lock b/Cargo.lock index 11ba1db..3ee755c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -146,6 +146,7 @@ name = "forcebot-rs-v2" version = "0.1.0" dependencies = [ "dotenv", + "lazy_static", "tokio", "twitch-irc", ] @@ -214,6 +215,12 @@ version = "0.31.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" +[[package]] +name = "lazy_static" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" + [[package]] name = "libc" version = "0.2.169" diff --git a/Cargo.toml b/Cargo.toml index 53faa8d..e9d2e5e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,9 +6,11 @@ default-run = "forcebot-rs-v2" [dependencies] dotenv = "0.15.0" +lazy_static = "1.5.0" tokio = { version = "1.33.0", features = ["full"] } twitch-irc = "5.0.1" + # [[bin]] # name = "simple_bot" # path = "src/simple_bot.rs" diff --git a/readme.md b/readme.md index d192a1b..045e712 100644 --- a/readme.md +++ b/readme.md @@ -128,16 +128,19 @@ pub async fn main() { pub mod custom_mod { + use std::sync::Arc; use forcebot_rs_v2::{asyncfn_box, Badge, Bot, Command, Module}; use twitch_irc::message::ServerMessage; - /// Module with a loaded command + /// Module definition with a loaded command pub fn new() -> Module { /* 1. Create a new module */ - let mut custom_mod = Module::new("test".to_string(), "".to_string()); + let mut custom_mod = Module::new( + vec!["test".to_string()], + "".to_string()); /* 2. Load the cmd into a new module */ custom_mod.load_command(cmd_test()); @@ -146,9 +149,10 @@ pub mod custom_mod { } + /// Command definition pub fn cmd_test() -> Command { /* 1. Create a new cmd */ - let mut cmd = Command::new("test".to_string(),"".to_string()); + let mut cmd = Command::new(vec!["test".to_string()],"".to_string()); /* 2. Define exec callback */ async fn execbody(bot:Arc<Bot>,message:ServerMessage) -> Result<String,String> { @@ -165,7 +169,6 @@ pub mod custom_mod { cmd.set_min_badge(Badge::Moderator); cmd - } } ``` @@ -290,7 +293,7 @@ pub async fn main() { let mut bot = Bot::new(); /* 1. Create a new blank cmd */ - let mut cmd = Command::new("test".to_string(),"".to_string()); + let mut cmd = Command::new(vec!["test".to_string()],"".to_string()); /* 2. Define an async fn callback execution */ async fn execbody(bot:Arc<Bot>,message:ServerMessage) -> Result<String,String> { @@ -308,8 +311,8 @@ pub async fn main() { cmd.set_admin_only(false); /* 5. optionally, set min badge*/ - cmd.set_min_badge("broadcaster".to_string()); -// + cmd.set_min_badge(Badge::Moderator); + /* 6. Load the cmd into the bot */ bot.load_command(cmd); diff --git a/src/bin/fun_bot.rs b/src/bin/fun_bot.rs index eaf0487..196ba05 100644 --- a/src/bin/fun_bot.rs +++ b/src/bin/fun_bot.rs @@ -3,6 +3,7 @@ //! Custom modules that can be managed in chat through `disable` and `enable` commands //! - funbot //! - guests +//! - pyramid //! //! //! Be sure the followig is defined in `.env` @@ -16,7 +17,7 @@ //! - Get a Bot Chat Token here - <https://twitchtokengenerator.com> //! - More Info - <https://dev.twitch.tv/docs/authentication> -use forcebot_rs_v2::{custom_mods::guest_badge, Bot}; +use forcebot_rs_v2::{custom_mods::{guest_badge, pyramid}, Bot}; #[tokio::main] pub async fn main() { @@ -29,6 +30,7 @@ pub async fn main() { /* 2. Load Custom Modules */ bot.load_module(guest_badge::create_module()); + bot.load_module(pyramid::create_module()); /* 3. Run the bot */ bot.run().await; diff --git a/src/bin/simple_module.rs b/src/bin/simple_module.rs index 002ca22..1204e3a 100644 --- a/src/bin/simple_module.rs +++ b/src/bin/simple_module.rs @@ -41,7 +41,7 @@ pub mod custom_mod { use twitch_irc::message::ServerMessage; - /// Module with a loaded command + /// Module definition with a loaded command pub fn new() -> Module { /* 1. Create a new module */ let mut custom_mod = Module::new( @@ -55,6 +55,7 @@ pub mod custom_mod { } + /// Command definition pub fn cmd_test() -> Command { /* 1. Create a new cmd */ let mut cmd = Command::new(vec!["test".to_string()],"".to_string()); @@ -74,5 +75,5 @@ pub mod custom_mod { cmd.set_min_badge(Badge::Moderator); cmd - } + } } \ No newline at end of file diff --git a/src/botcore/bot.rs b/src/botcore/bot.rs index ac19db2..b70b09c 100644 --- a/src/botcore/bot.rs +++ b/src/botcore/bot.rs @@ -1,7 +1,7 @@ use tokio::sync::{mpsc::UnboundedReceiver, Mutex}; -use twitch_irc::{login::StaticLoginCredentials, message::ServerMessage, SecureTCPTransport, TwitchIRCClient}; +use twitch_irc::{login::StaticLoginCredentials, message::{PrivmsgMessage, ServerMessage}, SecureTCPTransport, TwitchIRCClient}; use dotenv::dotenv; use std::{env, sync::Arc, time::{Duration, Instant}}; @@ -32,7 +32,10 @@ pub struct Bot /// channel module status channel_module_status: Mutex<Vec<(String,String,modules::Status)>>, /// chatter guest badges - chatter,channel,Badge,start_time,duration - chatter_guest_badges: Mutex<Vec<(String,String,Badge,Instant,Duration)>> + chatter_guest_badges: Mutex<Vec<(String,String,Badge,Instant,Duration)>>, + /// Message cache + message_cache: Mutex<Vec<PrivmsgMessage>>, + // message_cache: Vec<PrivmsgMessage> } @@ -112,6 +115,7 @@ impl Bot modules: vec![], channel_module_status: Mutex::new(vec![]), chatter_guest_badges: Mutex::new(vec![]), + message_cache : Mutex::new(vec![]), }; for cmd in built_in_objects::create_commands() { @@ -135,7 +139,10 @@ impl Bot let a = bot.clone(); let mut in_msgs_lock = a.incoming_msgs.lock().await; - while let Some(message) = in_msgs_lock.recv().await { + while let Some(message) = in_msgs_lock.recv().await { + + + for listener in &(*bot).listeners { let a = listener.clone(); @@ -147,6 +154,15 @@ impl Bot if let ServerMessage::Privmsg(msg) = message.clone() { + // let mut cache_lock = bot.message_cache.lock().await; + let mut cache_lock = bot.message_cache.lock().await; + cache_lock.push(msg.clone()); + // dbg!(cache_lock.clone()); + drop(cache_lock); + + // let a = bot.clone(); + // dbg!(bot.get_message_cache()); + for cmd in &(*bot).commands { let a = cmd.clone(); @@ -324,6 +340,9 @@ impl Bot } + pub fn get_message_cache(&self) -> &Mutex<Vec<PrivmsgMessage>> { + &self.message_cache + } } diff --git a/src/custom_mods.rs b/src/custom_mods.rs index cd79787..099cee1 100644 --- a/src/custom_mods.rs +++ b/src/custom_mods.rs @@ -1 +1,2 @@ -pub mod guest_badge; \ No newline at end of file +pub mod guest_badge; +pub mod pyramid; \ No newline at end of file diff --git a/src/custom_mods/guest_badge.rs b/src/custom_mods/guest_badge.rs index 175a0e1..9e9807c 100644 --- a/src/custom_mods/guest_badge.rs +++ b/src/custom_mods/guest_badge.rs @@ -14,16 +14,18 @@ use crate::{asyncfn_box, Badge, Bot, Command, Module}; /// /// /// -/// -/// -/// -/// const VIP_GIVEN_DUR_MIN:u64 = 15; const MOD_GIVEN_DUR_MIN:u64 = 30; - +/// Use this function when loading modules into the bot +/// +/// For example +/// ```rust +/// bot.load_module(guest_badge::create_module()); +/// ``` +/// pub fn create_module() -> Module { let mut custom_mod = Module::new( @@ -32,7 +34,7 @@ pub fn create_module() -> Module { custom_mod.load_command(create_cmd_mod()); custom_mod.load_command(create_cmd_vip()); - + custom_mod } diff --git a/src/custom_mods/pyramid.rs b/src/custom_mods/pyramid.rs new file mode 100644 index 0000000..5874a07 --- /dev/null +++ b/src/custom_mods/pyramid.rs @@ -0,0 +1,270 @@ +use std::sync::{Arc, Mutex}; + +use twitch_irc::message::{PrivmsgMessage, ServerMessage}; + +use crate::{asyncfn_box, Bot, Listener, Module}; + +/// pyramid module +/// +/// for detecting & handling pyramids +/// +/// +/// +/// +use lazy_static::lazy_static; + + +/// Use this function when loading modules into the bot +/// +/// For example +/// ```rust +/// bot.load_module(pyramid::create_module()); +/// ``` +/// +pub fn create_module() -> Module { + + let mut custom_mod = Module::new( + vec!["pyramid".to_string(), + "pyramids".to_string()], + "o7 I can handle pyramids".to_string()); + custom_mod.load_listener(create_pyramid_detector()); + + custom_mod + +} + +fn create_pyramid_detector() -> Listener { + + /* 1. Create a new blank Listener */ + let mut listener = Listener::new(); + + /* 2. Set a trigger condition function for listener */ + listener.set_trigger_cond_fn( + |_bot:Arc<Bot>,_message:ServerMessage| + true + ); + + /* 3. Define an async fn callback execution */ + async fn execbody(bot:Arc<Bot>,message:ServerMessage) -> Result<String,String> { + if let ServerMessage::Privmsg(msg) = message { + if detect_pyramid_complete_ok(bot.clone(), msg.clone()).await { + let _ = bot.client.say_in_reply_to(&msg, "Clap".to_string()).await ; + return Result::Ok("Success".to_string()) ; + } + } + Result::Err("Not Valid message type".to_string()) + } + + /* 4. Set and Store the execution body using `async_box()` */ + listener.set_exec_fn(asyncfn_box(execbody)); + + listener + +} + + +async fn detect_pyramid_complete_ok(_bot:Arc<Bot>,msg:PrivmsgMessage) -> bool { + + let msgtext = msg.message_text.replace("\u{e0000}","").trim().to_string(); + let msgchannel = msg.channel_login; + + // 1. Check if Pyramid started in chat > and recognize pyramid started + if !is_pyramid_started(msgchannel.clone()) & check_start_pyramid(msgchannel.clone(),msgtext.clone(),) { + set_pyramid_started(msgchannel.clone(),true); + push_to_compare(msgchannel.clone(),get_start_pattern(msgchannel.clone())); + + } + + if is_pyramid_started(msgchannel.clone()) { + push_to_compare(msgchannel.clone(),msgtext.clone()); + } + + // 2a. If Pyramid Not Started, Assume message is a potential start pattern + if !is_pyramid_started(msgchannel.clone()) { + set_start_pattern(msgchannel.clone(),msgtext.clone()); + } + + // 2b. If Pyramid is Started, and the latest message is the pattern, check for + // symmetry to determine pyramid + if is_pyramid_started(msgchannel.clone()) && msgtext.clone() == get_start_pattern(msgchannel.clone()) { + if symmetry_ok(msgchannel.clone()) { + return true; + } else { + return false ; + } + } else { + return false; + } + +} + + +lazy_static!{ + /// Message Compare stack per channel (channel:String,msgstack:Vec<String>) + pub static ref COMPARE_MSG_STACK_PER_CHNL: Mutex<Vec<(String,Mutex<Vec<String>>)>> = Mutex::new(vec![]); + #[derive(Debug)] + /// Pyramid Started per channel (channel:String,started:bool) + pub static ref PYRAMID_STARTED_PER_CHNL: Mutex<Vec<(String,Mutex<bool>)>> = Mutex::new(vec![]); + /// Start patterns per channel (channel:String,pattern:String) + pub static ref START_PATTERNS_PER_CHNL: Mutex<Vec<(String,Mutex<String>)>> = Mutex::new(vec![]); + /// temp message stack checker + pub static ref TEMP_MSG_STACK: Mutex<Vec<String>> = Mutex::new(vec![]); +} + +fn read_top_of_compare(channel:String) -> Option<String> { + + let comp_perchnl = COMPARE_MSG_STACK_PER_CHNL.lock().unwrap(); + + for rec in comp_perchnl.iter() { + if rec.0 == channel { + let msg_stack = rec.1.lock().unwrap(); + + return msg_stack.last().cloned(); + } + } + + None + +} + +fn pop_top_of_compare(channel:String) -> Option<String> { + let comp_perchnl = COMPARE_MSG_STACK_PER_CHNL.lock().unwrap(); + + for rec in comp_perchnl.iter() { + if rec.0 == channel { + let mut msg_stack = rec.1.lock().unwrap(); + + let popped = msg_stack.pop(); + return popped; + } + } + + None +} + +fn set_pyramid_started(channel:String,started:bool) { + let mut start_perchnl = PYRAMID_STARTED_PER_CHNL.lock().unwrap(); + let mut found = false; + for rec in start_perchnl.iter() { + if rec.0 == channel { + found = true; + let mut rec_started = rec.1.lock().unwrap(); + *rec_started = started; + } + } + if !found { + start_perchnl.push((channel,Mutex::new(started))); + } +} + +fn is_pyramid_started(channel:String) -> bool { + let start_perchnl = PYRAMID_STARTED_PER_CHNL.lock().unwrap(); + for rec in start_perchnl.iter() { + if rec.0 == channel { + let rec_started = rec.1.lock().unwrap(); + return *rec_started; + } + } + false +} + +fn set_start_pattern(channel:String,pattern:String) { + let mut start_patterns = START_PATTERNS_PER_CHNL.lock().unwrap(); + + let mut found = false; + for rec in start_patterns.iter() { + + if rec.0 == channel { + found = true; + let mut patternlock = rec.1.lock().unwrap(); + *patternlock = pattern.clone(); + } + + } + if !found { + start_patterns.push((channel.clone(),Mutex::new(pattern.clone()))); + } +} + + +fn get_start_pattern(channel:String) -> String { + let start_patterns = START_PATTERNS_PER_CHNL.lock().unwrap(); + + for rec in start_patterns.iter() { + + if rec.0 == channel { + let patternlock = rec.1.lock().unwrap(); + return patternlock.clone(); + } + } + + return "".to_string(); +} + + +/// pushes message to compare stack +fn push_to_compare(channel:String,message:String) { + let mut comp_perchnl = COMPARE_MSG_STACK_PER_CHNL.lock().unwrap(); + + let mut found = false; + for rec in comp_perchnl.iter() { + if rec.0 == channel { + found = true; + let mut msg_stack = rec.1.lock().unwrap(); + msg_stack.push(message.clone()); + // dbg!("Push message to cmp stack ; result last cmp_pchnl - ",comp_perchnl.last()); + } + } + if !found { + comp_perchnl.push((channel,Mutex::new(vec![message]))); + } + +} + + +/// checks latest and next latest messages for potential start +fn check_start_pyramid(channel:String,msgtext: String) -> bool { + msgtext == format!("{} {}",get_start_pattern(channel.clone()),get_start_pattern(channel.clone())) +} + + +/// pops the compare stack to determine symmetry +fn symmetry_ok(channel:String) -> bool { + let mut temp_stack = TEMP_MSG_STACK.lock().unwrap(); + let mut checking_started = false; + if !(read_top_of_compare(channel.clone()).unwrap_or("".to_string()) == get_start_pattern(channel.clone())) { + return false; + } + loop { + + if !checking_started && read_top_of_compare(channel.clone()).unwrap_or("".to_string()) == get_start_pattern(channel.clone()) { + checking_started = true; + } + if temp_stack.last().is_none() || read_top_of_compare(channel.clone()).unwrap_or("".to_string()).len() > temp_stack.last().unwrap_or(&"".to_string()).len() { + temp_stack.push(pop_top_of_compare(channel.clone()).unwrap_or("".to_string())); + + } else if temp_stack.last().is_some() && read_top_of_compare(channel.clone()).unwrap_or("".to_string()).len() < temp_stack.last().unwrap_or(&"".to_string()).len() { + + temp_stack.pop(); + if temp_stack.last().unwrap_or(&"".to_string()).clone() == read_top_of_compare(channel.clone()).unwrap_or("".to_string()) { + temp_stack.pop(); + + continue; + } else { + + temp_stack.clear(); + return false; + } + + } else { /* dbg!("failed catchall"); */ return false; } + if checking_started && read_top_of_compare(channel.clone()).unwrap() == get_start_pattern(channel.clone()) { + + temp_stack.clear(); + return true; + } + + } + + +} + diff --git a/src/lib.rs b/src/lib.rs index 3bb703a..cf5fdc9 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -59,10 +59,12 @@ //! use twitch_irc::message::ServerMessage; //! //! -//! /// Module with a loaded command +//! /// Module definition with a loaded command //! pub fn new() -> Module { //! /* 1. Create a new module */ -//! let mut custom_mod = Module::new("test".to_string(), "".to_string()); +//! let mut custom_mod = Module::new( +//! vec!["test".to_string()], +//! "".to_string()); //! //! /* 2. Load the cmd into a new module */ //! custom_mod.load_command(cmd_test()); @@ -71,9 +73,10 @@ //! //! } //! +//! /// Command definition //! pub fn cmd_test() -> Command { //! /* 1. Create a new cmd */ -//! let mut cmd = Command::new("test".to_string(),"".to_string()); +//! let mut cmd = Command::new(vec!["test".to_string()],"".to_string()); //! //! /* 2. Define exec callback */ //! async fn execbody(bot:Arc<Bot>,message:ServerMessage) -> Result<String,String> { @@ -83,7 +86,7 @@ //! } //! Result::Err("Not Valid message type".to_string()) //! } -//! +//! z //! /* 3. Set Command flags */ //! cmd.set_exec_fn(asyncfn_box(execbody)); //! cmd.set_admin_only(false); -- 2.49.0 From 57f1a3c914e76f4c2855e859fe0de3519c554d61 Mon Sep 17 00:00:00 2001 From: modulatingforce <modulatingforce@gmail.com> Date: Fri, 31 Jan 2025 13:47:12 -0500 Subject: [PATCH 04/14] msg cache per channel --- src/botcore/bot.rs | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/botcore/bot.rs b/src/botcore/bot.rs index b70b09c..71b5cb9 100644 --- a/src/botcore/bot.rs +++ b/src/botcore/bot.rs @@ -344,6 +344,18 @@ impl Bot &self.message_cache } + /// get message cache newest to oldest for a channel + pub async fn get_message_cache_per_channel(&self,channel:String) -> Vec<PrivmsgMessage> { + let cache = self.message_cache.lock().await; + let mut rslt = vec![]; + for a in cache.iter().rev().filter(|x| { x.channel_login==channel }).into_iter() { + rslt.push(a.clone()); + + } + rslt + } + + } -- 2.49.0 From 739f7aeeef28cb1b6ac0cb65c51057d239e4203a Mon Sep 17 00:00:00 2001 From: modulatingforce <modulatingforce@gmail.com> Date: Fri, 31 Jan 2025 21:56:56 -0500 Subject: [PATCH 05/14] async trigger cond --- readme.md | 26 +++--- src/bin/fun_bot.rs | 4 +- src/bin/moderator_reactor.rs | 4 +- src/bin/simple_command_bot.rs | 4 +- src/bin/simple_debug_listener.rs | 4 +- src/bin/simple_module.rs | 4 +- src/botcore/bot.rs | 2 +- src/botcore/bot_objects.rs | 22 +++-- src/botcore/bot_objects/command.rs | 38 ++++++-- src/botcore/bot_objects/listener.rs | 42 +++++++-- src/custom_mods/guest_badge.rs | 6 +- src/custom_mods/pyramid.rs | 78 ++++++++++++++--- src/lib.rs | 129 ++++++++++++++-------------- 13 files changed, 248 insertions(+), 115 deletions(-) diff --git a/readme.md b/readme.md index 045e712..55aa65f 100644 --- a/readme.md +++ b/readme.md @@ -126,12 +126,10 @@ pub async fn main() { } - pub mod custom_mod { - use std::sync::Arc; - use forcebot_rs_v2::{asyncfn_box, Badge, Bot, Command, Module}; + use forcebot_rs_v2::{execution_async, Badge, Bot, Command, Module}; use twitch_irc::message::ServerMessage; @@ -164,11 +162,12 @@ pub mod custom_mod { } /* 3. Set Command flags */ - cmd.set_exec_fn(asyncfn_box(execbody)); + cmd.set_exec_fn(execution_async(execbody)); cmd.set_admin_only(false); cmd.set_min_badge(Badge::Moderator); cmd + } } ``` @@ -179,7 +178,7 @@ Bot with a simple listener that listens for all messages and prints in output ```rust use std::sync::Arc; -use forcebot_rs_v2::{asyncfn_box, Bot, Listener}; +use forcebot_rs_v2::{execution_async, Bot, Listener}; use twitch_irc::message::ServerMessage; #[tokio::main] @@ -203,7 +202,7 @@ pub async fn main() { } /* 2d. Set and Store the execution body using `async_box()` */ - listener.set_exec_fn(asyncfn_box(execbody)); + listener.set_exec_fn(execution_async(execbody)); /* 3. Load the listener into the bot */ bot.load_listener(listener); @@ -220,10 +219,11 @@ pub async fn main() { Example listener listens for a moderator badge and reply in chat ```rust + use std::sync::Arc; use forcebot_rs_v2::Bot; -use forcebot_rs_v2::asyncfn_box; +use forcebot_rs_v2::execution_async; use forcebot_rs_v2::Listener; use twitch_irc::message::ServerMessage; @@ -238,10 +238,11 @@ pub async fn main() { let mut listener = Listener::new(); /* 2. Set a trigger condition function for listener */ + listener.set_trigger_cond_fn( |_:Arc<Bot>,message:ServerMessage| if let ServerMessage::Privmsg(msg) = message { - + // dbg!(msg.clone()); for badge in msg.badges { if matches!(badge, x if x.name == "moderator") { // dbg!("moderator found"); @@ -262,7 +263,7 @@ pub async fn main() { } /* 4. Set and Store the execution body using `async_box()` */ - listener.set_exec_fn(asyncfn_box(execbody)); + listener.set_exec_fn(execution_async(execbody)); /* 5. Load the listener into the bot */ bot.load_listener(listener); @@ -273,6 +274,7 @@ pub async fn main() { } + ``` ## Simple Test Command @@ -280,8 +282,9 @@ pub async fn main() { ```rust use std::sync::Arc; +use forcebot_rs_v2::Badge; use forcebot_rs_v2::Bot; -use forcebot_rs_v2::asyncfn_box; +use forcebot_rs_v2::execution_async; use forcebot_rs_v2::Command; use twitch_irc::message::ServerMessage; @@ -305,7 +308,7 @@ pub async fn main() { } /* 3. Set and Store the execution body using `async_box()` */ - cmd.set_exec_fn(asyncfn_box(execbody)); + cmd.set_exec_fn(execution_async(execbody)); /* 4. optionally, remove admin only default flag */ cmd.set_admin_only(false); @@ -321,6 +324,7 @@ pub async fn main() { } + ``` # Crate Rust Documentation diff --git a/src/bin/fun_bot.rs b/src/bin/fun_bot.rs index 196ba05..27c67a0 100644 --- a/src/bin/fun_bot.rs +++ b/src/bin/fun_bot.rs @@ -41,7 +41,7 @@ pub async fn main() { pub mod funbot_objs { use std::sync::Arc; - use forcebot_rs_v2::{asyncfn_box, Badge, Bot, Command, Module}; + use forcebot_rs_v2::{execution_async, Badge, Bot, Command, Module}; use twitch_irc::message::ServerMessage; /// Create a Module with a loaded Command object @@ -69,7 +69,7 @@ pub mod funbot_objs { Result::Err("Not Valid message type".to_string()) } - cmd.set_exec_fn(asyncfn_box(execbody)); + cmd.set_exec_fn(execution_async(execbody)); cmd.set_admin_only(false); cmd.set_min_badge(Badge::Vip); diff --git a/src/bin/moderator_reactor.rs b/src/bin/moderator_reactor.rs index a7bb62c..279d204 100644 --- a/src/bin/moderator_reactor.rs +++ b/src/bin/moderator_reactor.rs @@ -14,7 +14,7 @@ use std::sync::Arc; use forcebot_rs_v2::Bot; -use forcebot_rs_v2::asyncfn_box; +use forcebot_rs_v2::execution_async; use forcebot_rs_v2::Listener; use twitch_irc::message::ServerMessage; @@ -54,7 +54,7 @@ pub async fn main() { } /* 4. Set and Store the execution body using `async_box()` */ - listener.set_exec_fn(asyncfn_box(execbody)); + listener.set_exec_fn(execution_async(execbody)); /* 5. Load the listener into the bot */ bot.load_listener(listener); diff --git a/src/bin/simple_command_bot.rs b/src/bin/simple_command_bot.rs index 934ddf7..9c2f2ff 100644 --- a/src/bin/simple_command_bot.rs +++ b/src/bin/simple_command_bot.rs @@ -17,7 +17,7 @@ use std::sync::Arc; use forcebot_rs_v2::Badge; use forcebot_rs_v2::Bot; -use forcebot_rs_v2::asyncfn_box; +use forcebot_rs_v2::execution_async; use forcebot_rs_v2::Command; use twitch_irc::message::ServerMessage; @@ -41,7 +41,7 @@ pub async fn main() { } /* 3. Set and Store the execution body using `async_box()` */ - cmd.set_exec_fn(asyncfn_box(execbody)); + cmd.set_exec_fn(execution_async(execbody)); /* 4. optionally, remove admin only default flag */ cmd.set_admin_only(false); diff --git a/src/bin/simple_debug_listener.rs b/src/bin/simple_debug_listener.rs index ee129bd..791c5d0 100644 --- a/src/bin/simple_debug_listener.rs +++ b/src/bin/simple_debug_listener.rs @@ -12,7 +12,7 @@ use std::sync::Arc; -use forcebot_rs_v2::{asyncfn_box, Bot, Listener}; +use forcebot_rs_v2::{execution_async, Bot, Listener}; use twitch_irc::message::ServerMessage; #[tokio::main] @@ -36,7 +36,7 @@ pub async fn main() { } /* 2d. Set and Store the execution body using `async_box()` */ - listener.set_exec_fn(asyncfn_box(execbody)); + listener.set_exec_fn(execution_async(execbody)); /* 3. Load the listener into the bot */ bot.load_listener(listener); diff --git a/src/bin/simple_module.rs b/src/bin/simple_module.rs index 1204e3a..f9e7bec 100644 --- a/src/bin/simple_module.rs +++ b/src/bin/simple_module.rs @@ -37,7 +37,7 @@ pub async fn main() { pub mod custom_mod { use std::sync::Arc; - use forcebot_rs_v2::{asyncfn_box, Badge, Bot, Command, Module}; + use forcebot_rs_v2::{execution_async, Badge, Bot, Command, Module}; use twitch_irc::message::ServerMessage; @@ -70,7 +70,7 @@ pub mod custom_mod { } /* 3. Set Command flags */ - cmd.set_exec_fn(asyncfn_box(execbody)); + cmd.set_exec_fn(execution_async(execbody)); cmd.set_admin_only(false); cmd.set_min_badge(Badge::Moderator); diff --git a/src/botcore/bot.rs b/src/botcore/bot.rs index 71b5cb9..e769efd 100644 --- a/src/botcore/bot.rs +++ b/src/botcore/bot.rs @@ -192,7 +192,7 @@ impl Bot for listener in module.get_listeners() { let a = listener.clone(); - if a.cond_triggered(bot.clone(),message.clone()) { + if a.cond_triggered(bot.clone(),message.clone()) && a.cond_async_triggered(bot.clone(), message.clone()).await { let _ = listener.execute_fn(bot.clone(),message.clone()).await; } diff --git a/src/botcore/bot_objects.rs b/src/botcore/bot_objects.rs index 065e08a..cca0423 100644 --- a/src/botcore/bot_objects.rs +++ b/src/botcore/bot_objects.rs @@ -25,13 +25,25 @@ pub type ExecBody = Box< dyn Fn(Arc<Bot>,ServerMessage) -> Pin<Box<dyn Future<Output = Result<String,String>> + Send>> + Send + Sync, >; -pub fn asyncfn_box<T>(f: fn(Arc<Bot>,ServerMessage) -> T) -> ExecBody +pub fn execution_async<T>(f: fn(Arc<Bot>,ServerMessage) -> T) -> ExecBody where T: Future<Output = Result<String,String>> + Send + 'static, { Box::new(move |a,b| Box::pin(f(a,b))) } +pub type TrigBody = Box< + dyn Fn(Arc<Bot>,ServerMessage) -> Pin<Box<dyn Future<Output = bool> + Send>> + Send + Sync, +>; + +pub fn condition_async<T>(f: fn(Arc<Bot>,ServerMessage) -> T) -> TrigBody +where + T: Future<Output = bool> + Send + 'static, +{ + Box::new(move |a,b| Box::pin(f(a,b))) +} + + /// collection of functions to create built in objects @@ -42,7 +54,7 @@ pub mod built_in_objects { use twitch_irc::message::ServerMessage; - use crate::{asyncfn_box, modules::Status, Badge, Bot, Command}; + use crate::{execution_async, modules::Status, Badge, Bot, Command}; /// create a vector of command build in objects @@ -83,7 +95,7 @@ pub mod built_in_objects { } /* 3. Set and Store the execution body using `async_box()` */ - cmd.set_exec_fn(asyncfn_box(execbody)); + cmd.set_exec_fn(execution_async(execbody)); /* 4. optionally, remove admin only default flag */ cmd.set_admin_only(false); @@ -138,7 +150,7 @@ pub mod built_in_objects { } /* 3. Set and Store the execution body using `async_box()` */ - cmd.set_exec_fn(asyncfn_box(execbody)); + cmd.set_exec_fn(execution_async(execbody)); /* 4. optionally, remove admin only default flag */ cmd.set_admin_only(false); @@ -241,7 +253,7 @@ pub mod built_in_objects { } /* 3. Set and Store the execution body using `async_box()` */ - cmd.set_exec_fn(asyncfn_box(execbody)); + cmd.set_exec_fn(execution_async(execbody)); /* 4. optionally, remove admin only default flag */ cmd.set_admin_only(true); diff --git a/src/botcore/bot_objects/command.rs b/src/botcore/bot_objects/command.rs index 0cb4249..3314347 100644 --- a/src/botcore/bot_objects/command.rs +++ b/src/botcore/bot_objects/command.rs @@ -2,9 +2,9 @@ use std::sync::Arc; use twitch_irc::message::{PrivmsgMessage, ServerMessage}; -use crate::{asyncfn_box, botcore::bot::Bot, Badge}; +use crate::{botcore::{bot::Bot, bot_objects::condition_async}, execution_async, Badge}; -use super::ExecBody; +use super::{ExecBody, TrigBody}; /// Bot `Command` that stores trigger condition callback and a execution functon /// @@ -16,7 +16,7 @@ use super::ExecBody; /// /// AdminOnly commands can only be ran by admin /// -/// Use `asyncfn_box()` on custom async execution bodies +/// Use `execution_async()` on custom async execution bodies #[derive(Clone)] pub struct Command { @@ -28,6 +28,7 @@ pub struct Command admin_override : bool, prefix : String, custom_cond_fn : fn(Arc<Bot>,PrivmsgMessage) -> bool, + custom_cond_async : Arc<TrigBody>, } impl Command @@ -44,26 +45,51 @@ impl Command async fn execbody(_:Arc<Bot>,_:ServerMessage) -> Result<String,String> { Result::Ok("success".to_string()) } + async fn condition01(_:Arc<Bot>,_:ServerMessage) -> bool { true } Command { commands , prefix , - exec_fn : Arc::new(asyncfn_box(execbody)), + exec_fn : Arc::new(execution_async(execbody)), min_badge : Badge::Vip, admin_only : true, admin_override : false , custom_cond_fn : |_:Arc<Bot>,_:PrivmsgMessage| true, + custom_cond_async : Arc::new(condition_async(condition01)), } } - /// set a trigger conditin callback that returns true if the listener shoud trigger + /// set a trigger conditin callback that returns true if the command should trigger pub fn set_custom_cond_fn(&mut self,cond_fn: fn(Arc<Bot>,PrivmsgMessage) -> bool) { self.custom_cond_fn = cond_fn; } + + /// sets the async trigger condition for listener + /// + /// Same as `set_custom_cond_fn()` , but async define + /// + /// Use`execution_async()` on the async fn when storing + /// + /// Example - + /// ```rust + /// /* 1. Create a new blank Listener */ + /// let mut cmd = Command::new(); + /// + /// /* 2. define an async function */ + /// async fn condition01(_:Arc<Bot>,_:ServerMessage) -> bool { true } + /// + /// /* 3. Set and Store the execution body using `async_box()` */ + /// cmd.set_custom_cond_async(condition_async(condition01)); + /// ``` + /// + pub fn set_custom_cond_async(&mut self,condition:TrigBody ) { + self.custom_cond_async = Arc::new(condition); + } + /// sets the execution body of the listener for when it triggers /// - /// Use`asyncfn_box()` on the async fn when storing + /// Use`execution_async()` on the async fn when storing /// /// pub fn set_exec_fn(&mut self,exec_fn:ExecBody ) { diff --git a/src/botcore/bot_objects/listener.rs b/src/botcore/bot_objects/listener.rs index 2bebb8e..cc170df 100644 --- a/src/botcore/bot_objects/listener.rs +++ b/src/botcore/bot_objects/listener.rs @@ -2,9 +2,9 @@ use std::sync::Arc; use twitch_irc::message::ServerMessage; -use crate::{asyncfn_box, Bot}; +use crate::{botcore::bot_objects::condition_async, execution_async, Bot}; -use super::ExecBody; +use super::{ExecBody, TrigBody}; /// Bot `Listener`` that stores trigger condition callback and a execution functon /// @@ -14,6 +14,8 @@ pub struct Listener { /// trigger condition trigger_cond_fn : fn(Arc<Bot>,ServerMessage) -> bool, + /// trigger condition for async + trigger_cond_async : Arc<TrigBody> , /// execution body exec_fn : Arc<ExecBody>, } @@ -26,10 +28,11 @@ impl Listener pub fn new() -> Listener { async fn execbody(_:Arc<Bot>,_:ServerMessage) -> Result<String,String> {Result::Ok("success".to_string()) } - + async fn condition01(_:Arc<Bot>,_:ServerMessage) -> bool { true } Listener { trigger_cond_fn : |_:Arc<Bot>,_:ServerMessage| false, - exec_fn : Arc::new(asyncfn_box(execbody)) + trigger_cond_async : Arc::new(condition_async(condition01)), + exec_fn : Arc::new(execution_async(execbody)), } } @@ -38,6 +41,29 @@ impl Listener self.trigger_cond_fn = cond_fn; } + /// sets the async trigger condition for listener + /// + /// Same as `set_trigger_cond_fn()` , but async define + /// + /// Use`asyncfn_box()` on the async fn when storing + /// + /// Example - + /// ```rust + /// /* 1. Create a new blank Listener */ + /// let mut listener = Listener::new(); + /// + /// /* 2. define an async function */ + /// async fn condition01(_:Arc<Bot>,_:ServerMessage) -> bool { true } + /// + /// /* 3. Set and Store the execution body using `async_box()` */ + /// listener.set_trigger_cond_async(condition_async(condition01)); + /// ``` + /// + pub fn set_trigger_cond_async(&mut self,condition:TrigBody ) { + self.trigger_cond_async = Arc::new(condition); + } + + /// sets the execution body of the listener for when it triggers /// /// Use`asyncfn_box()` on the async fn when storing @@ -60,7 +86,13 @@ impl Listener /// checks if the trigger condition is met pub fn cond_triggered(&self,bot:Arc<Bot>,msg:ServerMessage) -> bool { - (self.trigger_cond_fn)(bot,msg) + (self.trigger_cond_fn)(bot,msg) + } + + /// executes the listeners executon body + pub async fn cond_async_triggered(&self,bot:Arc<Bot>,msg:ServerMessage) -> bool { + // (self.exec_fn)(bot,msg) + (self.trigger_cond_async)(bot,msg).await } /// executes the listeners executon body diff --git a/src/custom_mods/guest_badge.rs b/src/custom_mods/guest_badge.rs index 9e9807c..ee1eb16 100644 --- a/src/custom_mods/guest_badge.rs +++ b/src/custom_mods/guest_badge.rs @@ -2,7 +2,7 @@ use std::{sync::Arc, time::{Duration, Instant}}; use twitch_irc::message::ServerMessage; -use crate::{asyncfn_box, Badge, Bot, Command, Module}; +use crate::{execution_async, Badge, Bot, Command, Module}; /// guest_badge / guest module /// @@ -88,7 +88,7 @@ fn create_cmd_vip() -> Command { Result::Err("Not Valid message type".to_string()) } - cmd.set_exec_fn(asyncfn_box(execbody)); + cmd.set_exec_fn(execution_async(execbody)); cmd.set_admin_only(false); cmd.set_admin_override(true); @@ -131,7 +131,7 @@ fn create_cmd_mod() -> Command { Result::Err("Not Valid message type".to_string()) } - cmd.set_exec_fn(asyncfn_box(execbody)); + cmd.set_exec_fn(execution_async(execbody)); cmd.set_admin_only(false); cmd.set_admin_override(true); diff --git a/src/custom_mods/pyramid.rs b/src/custom_mods/pyramid.rs index 5874a07..de633bb 100644 --- a/src/custom_mods/pyramid.rs +++ b/src/custom_mods/pyramid.rs @@ -1,14 +1,17 @@ + + use std::sync::{Arc, Mutex}; use twitch_irc::message::{PrivmsgMessage, ServerMessage}; -use crate::{asyncfn_box, Bot, Listener, Module}; +use crate::{condition_async, Badge, Bot, Command, Listener, Module}; /// pyramid module /// /// for detecting & handling pyramids /// -/// +/// - listener - detects pyramid +/// - cmd & listener - interrupts some chatters temporarily /// /// use lazy_static::lazy_static; @@ -38,13 +41,21 @@ fn create_pyramid_detector() -> Listener { /* 1. Create a new blank Listener */ let mut listener = Listener::new(); - /* 2. Set a trigger condition function for listener */ - listener.set_trigger_cond_fn( - |_bot:Arc<Bot>,_message:ServerMessage| - true - ); + /* 2. Define an async trigger condition callback */ + async fn condition01(bot:Arc<Bot>,message:ServerMessage) -> bool { + if let ServerMessage::Privmsg(msg) = message { + if detect_pyramid_complete_ok(bot.clone(), msg.clone()).await { + return true; + } + } + false + } + + /* 3. Set a trigger condition function for listener */ + listener.set_trigger_cond_async(condition_async(condition01)); + - /* 3. Define an async fn callback execution */ + /* 4. Define an async fn callback execution */ async fn execbody(bot:Arc<Bot>,message:ServerMessage) -> Result<String,String> { if let ServerMessage::Privmsg(msg) = message { if detect_pyramid_complete_ok(bot.clone(), msg.clone()).await { @@ -55,8 +66,8 @@ fn create_pyramid_detector() -> Listener { Result::Err("Not Valid message type".to_string()) } - /* 4. Set and Store the execution body using `async_box()` */ - listener.set_exec_fn(asyncfn_box(execbody)); + /* 5. Set and Store the execution body using `async_box()` */ + listener.set_exec_fn(Box::new(move |a,b| Box::pin(execbody(a,b)))); listener @@ -109,6 +120,10 @@ lazy_static!{ pub static ref START_PATTERNS_PER_CHNL: Mutex<Vec<(String,Mutex<String>)>> = Mutex::new(vec![]); /// temp message stack checker pub static ref TEMP_MSG_STACK: Mutex<Vec<String>> = Mutex::new(vec![]); + + /// interruptor targets - (channel:String,chatters:Vec<String>>) + pub static ref INTERRUPT_TRG_PER_CHNL: Mutex<Vec<(String,Mutex<Vec<String>>)>> = Mutex::new(vec![]); + } fn read_top_of_compare(channel:String) -> Option<String> { @@ -268,3 +283,46 @@ fn symmetry_ok(channel:String) -> bool { } + +/// pyramid interruptor #todo +/// +/// pick chatters that will be interrupted if they solo build +/// +/// takes in arguments as chatters +/// +/// chatters are then interrupted for a random duration under 15m +/// +/// if a duration is given, take that duration eg 15m , 25m +/// +/// +fn create_interruptor_cmd() -> Command { + let mut cmd = Command::new(vec![ + "no pyramid".to_string(), + "no pyramids".to_string() + ], + "".to_string()); + + /* 2. Define an async fn callback execution */ + async fn execbody(bot:Arc<Bot>,message:ServerMessage) -> Result<String,String> { + if let ServerMessage::Privmsg(msg) = message { + let _ = bot.client.say_in_reply_to(&msg, String::from("test success")).await; + return Result::Ok("Success".to_string()) ; + } + Result::Err("Not Valid message type".to_string()) + } + + /* 3. Set and Store the execution body using `async_box()` */ + cmd.set_exec_fn(Box::new(move |a,b| Box::pin(execbody(a,b)))); + + /* 4. optionally, remove admin only default flag */ + cmd.set_admin_only(false); + + /* 5. optionally, set min badge*/ + cmd.set_min_badge(Badge::Moderator); + + + cmd +} + + + diff --git a/src/lib.rs b/src/lib.rs index cf5fdc9..7981025 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -36,65 +36,65 @@ //! //! ```rust //! use forcebot_rs_v2::Bot; -//! -//! #[tokio::main] -//! pub async fn main() { -//! -//! /* Create the bot using env */ -//! let mut bot = Bot::new(); -//! -//! /* load the Module */ -//! bot.load_module(custom_mod::new()); -//! -//! /* Run the bot */ -//! bot.run().await; -//! -//! } -//! -//! -//! pub mod custom_mod { -//! use std::sync::Arc; -//! -//! use forcebot_rs_v2::{asyncfn_box, Badge, Bot, Command, Module}; -//! use twitch_irc::message::ServerMessage; -//! -//! -//! /// Module definition with a loaded command -//! pub fn new() -> Module { -//! /* 1. Create a new module */ -//! let mut custom_mod = Module::new( -//! vec!["test".to_string()], -//! "".to_string()); -//! -//! /* 2. Load the cmd into a new module */ -//! custom_mod.load_command(cmd_test()); -//! -//! custom_mod -//! -//! } -//! -//! /// Command definition -//! pub fn cmd_test() -> Command { -//! /* 1. Create a new cmd */ -//! let mut cmd = Command::new(vec!["test".to_string()],"".to_string()); -//! -//! /* 2. Define exec callback */ -//! async fn execbody(bot:Arc<Bot>,message:ServerMessage) -> Result<String,String> { -//! if let ServerMessage::Privmsg(msg) = message { -//! let _= bot.client.say_in_reply_to( -//! &msg, "test return".to_string()).await; -//! } -//! Result::Err("Not Valid message type".to_string()) -//! } -//! z -//! /* 3. Set Command flags */ -//! cmd.set_exec_fn(asyncfn_box(execbody)); -//! cmd.set_admin_only(false); -//! cmd.set_min_badge(Badge::Moderator); -//! -//! cmd -//! } -//! } +//! +//! #[tokio::main] +//! pub async fn main() { +//! +//! /* Create the bot using env */ +//! let mut bot = Bot::new(); +//! +//! /* load the Module */ +//! bot.load_module(custom_mod::new()); +//! +//! /* Run the bot */ +//! bot.run().await; +//! +//! } +//! +//! pub mod custom_mod { +//! use std::sync::Arc; +//! +//! use forcebot_rs_v2::{execution_async, Badge, Bot, Command, Module}; +//! use twitch_irc::message::ServerMessage; +//! +//! +//! /// Module definition with a loaded command +//! pub fn new() -> Module { +//! /* 1. Create a new module */ +//! let mut custom_mod = Module::new( +//! vec!["test".to_string()], +//! "".to_string()); +//! +//! /* 2. Load the cmd into a new module */ +//! custom_mod.load_command(cmd_test()); +//! +//! custom_mod +//! +//! } +//! +//! /// Command definition +//! pub fn cmd_test() -> Command { +//! /* 1. Create a new cmd */ +//! let mut cmd = Command::new(vec!["test".to_string()],"".to_string()); +//! +//! /* 2. Define exec callback */ +//! async fn execbody(bot:Arc<Bot>,message:ServerMessage) -> Result<String,String> { +//! if let ServerMessage::Privmsg(msg) = message { +//! let _= bot.client.say_in_reply_to( +//! &msg, "test return".to_string()).await; +//! } +//! Result::Err("Not Valid message type".to_string()) +//! } +//! +//! /* 3. Set Command flags */ +//! cmd.set_exec_fn(execution_async(execbody)); +//! cmd.set_admin_only(false); +//! cmd.set_min_badge(Badge::Moderator); +//! +//! cmd +//! } +//! } +//! //! //! ``` //! @@ -104,7 +104,7 @@ //! ```rust //! use std::sync::Arc; //! -//! use forcebot_rs_v2::{asyncfn_box, Bot, Listener}; +//! use forcebot_rs_v2::{execution_async, Bot, Listener}; //! use twitch_irc::message::ServerMessage; //! //! #[tokio::main] @@ -128,7 +128,7 @@ //! } //! //! /* 2d. Set and Store the execution body using `async_box()` */ -//! listener.set_exec_fn(asyncfn_box(execbody)); +//! listener.set_exec_fn(execution_async(execbody)); //! //! /* 3. Load the listener into the bot */ //! bot.load_listener(listener); @@ -147,7 +147,7 @@ //! use std::sync::Arc; //! //! use forcebot_rs_v2::Bot; -//! use forcebot_rs_v2::asyncfn_box; +//! use forcebot_rs_v2::execution_async; //! use forcebot_rs_v2::Listener; //! use twitch_irc::message::ServerMessage; //! @@ -187,7 +187,7 @@ //! } //! //! /* 4. Set and Store the execution body using `async_box()` */ -//! listener.set_exec_fn(asyncfn_box(execbody)); +//! listener.set_exec_fn(execution_async(execbody)); //! //! /* 5. Load the listener into the bot */ //! bot.load_listener(listener); @@ -202,7 +202,8 @@ pub mod botcore; pub mod custom_mods; pub use crate::botcore::bot::Bot; -pub use crate::botcore::bot_objects::asyncfn_box; +pub use crate::botcore::bot_objects::execution_async; +pub use crate::botcore::bot_objects::condition_async; pub use crate::botcore::bot_objects::listener::Listener; pub use crate::botcore::bot_objects::command::Command; pub use crate::botcore::modules::Module; -- 2.49.0 From 802fd714c204b0f134d18f9beb856227c471e7c6 Mon Sep 17 00:00:00 2001 From: modulatingforce <modulatingforce@gmail.com> Date: Sat, 1 Feb 2025 03:43:53 -0500 Subject: [PATCH 06/14] module disable by default --- src/botcore/bot.rs | 10 ++++++++-- src/botcore/modules.rs | 15 +++++++++++++-- 2 files changed, 21 insertions(+), 4 deletions(-) diff --git a/src/botcore/bot.rs b/src/botcore/bot.rs index e769efd..1a54cc6 100644 --- a/src/botcore/bot.rs +++ b/src/botcore/bot.rs @@ -249,9 +249,15 @@ impl Bot } /// loads a `Module` and its bot objects - pub fn load_module(&mut self,m: Module) { + pub async fn load_module(&mut self,m: Module) { + if m.get_status_by_default() == Status::Disabled { + for chnl in &self.botchannels { + self.disable_module(chnl.clone(), + m.get_names().first().unwrap().clone()).await + } + } self.modules.push(m) - } + } pub async fn get_channel_module_status(&self,channel:String,module:String) -> Status { let found_disabled:bool = { diff --git a/src/botcore/modules.rs b/src/botcore/modules.rs index 8a342c8..2ed44dc 100644 --- a/src/botcore/modules.rs +++ b/src/botcore/modules.rs @@ -2,7 +2,7 @@ use crate::{Command, Listener}; -#[derive(PartialEq, Eq,Debug)] +#[derive(PartialEq, Eq,Debug,Clone)] pub enum Status { Disabled, Enabled @@ -19,6 +19,8 @@ pub struct Module bot_read_description : String, listeners: Vec<Listener>, commands: Vec<Command>, + // disable module at load for bot channels + default_status_per_channel: Status, } impl Module @@ -30,7 +32,8 @@ impl Module // _alias: alias, bot_read_description, listeners: vec![], - commands: vec![] + commands: vec![], + default_status_per_channel: Status::Disabled, } } @@ -60,5 +63,13 @@ impl Module self.bot_read_description.clone() } + pub fn set_status_by_default(&mut self,status:Status){ + self.default_status_per_channel = status; + } + + pub fn get_status_by_default(&self) -> Status { + self.default_status_per_channel.clone() + } + } \ No newline at end of file -- 2.49.0 From 34842d4c41420c654585d2e7d96dc954b3b62968 Mon Sep 17 00:00:00 2001 From: modulatingforce <modulatingforce@gmail.com> Date: Sat, 1 Feb 2025 07:18:23 -0500 Subject: [PATCH 07/14] exec body and listeners adj --- readme.md | 2 +- src/bin/fun_bot.rs | 6 +-- src/bin/simple_module.rs | 2 +- src/botcore/bot.rs | 10 ++--- src/botcore/bot_objects.rs | 60 +++++++++++++++++++++++++++-- src/botcore/bot_objects/command.rs | 23 +++++------ src/botcore/bot_objects/listener.rs | 21 ++++------ src/custom_mods/pyramid.rs | 4 +- src/lib.rs | 3 +- 9 files changed, 88 insertions(+), 43 deletions(-) diff --git a/readme.md b/readme.md index 55aa65f..769ba75 100644 --- a/readme.md +++ b/readme.md @@ -119,7 +119,7 @@ pub async fn main() { let mut bot = Bot::new(); /* load the Module */ - bot.load_module(custom_mod::new()); + bot.load_module(custom_mod::new()).await; /* Run the bot */ bot.run().await; diff --git a/src/bin/fun_bot.rs b/src/bin/fun_bot.rs index 27c67a0..4f119ce 100644 --- a/src/bin/fun_bot.rs +++ b/src/bin/fun_bot.rs @@ -26,11 +26,11 @@ pub async fn main() { let mut bot = Bot::new(); /* 1. Load the module into the bot */ - bot.load_module(funbot_objs::create_module()); + bot.load_module(funbot_objs::create_module()).await; /* 2. Load Custom Modules */ - bot.load_module(guest_badge::create_module()); - bot.load_module(pyramid::create_module()); + bot.load_module(guest_badge::create_module()).await; + bot.load_module(pyramid::create_module()).await; /* 3. Run the bot */ bot.run().await; diff --git a/src/bin/simple_module.rs b/src/bin/simple_module.rs index f9e7bec..77bd5da 100644 --- a/src/bin/simple_module.rs +++ b/src/bin/simple_module.rs @@ -26,7 +26,7 @@ pub async fn main() { let mut bot = Bot::new(); /* load the Module */ - bot.load_module(custom_mod::new()); + bot.load_module(custom_mod::new()).await; /* Run the bot */ bot.run().await; diff --git a/src/botcore/bot.rs b/src/botcore/bot.rs index 1a54cc6..f1a8798 100644 --- a/src/botcore/bot.rs +++ b/src/botcore/bot.rs @@ -141,13 +141,11 @@ impl Bot while let Some(message) = in_msgs_lock.recv().await { - - + for listener in &(*bot).listeners { let a = listener.clone(); - if a.cond_triggered(bot.clone(),message.clone()) { - + if a.cond_triggered(bot.clone(),message.clone()).await { let _ = listener.execute_fn(bot.clone(),message.clone()).await; } } @@ -160,8 +158,6 @@ impl Bot // dbg!(cache_lock.clone()); drop(cache_lock); - // let a = bot.clone(); - // dbg!(bot.get_message_cache()); for cmd in &(*bot).commands { @@ -192,7 +188,7 @@ impl Bot for listener in module.get_listeners() { let a = listener.clone(); - if a.cond_triggered(bot.clone(),message.clone()) && a.cond_async_triggered(bot.clone(), message.clone()).await { + if a.cond_triggered(bot.clone(),message.clone()).await { let _ = listener.execute_fn(bot.clone(),message.clone()).await; } diff --git a/src/botcore/bot_objects.rs b/src/botcore/bot_objects.rs index cca0423..d5ae5b2 100644 --- a/src/botcore/bot_objects.rs +++ b/src/botcore/bot_objects.rs @@ -7,7 +7,7 @@ use std::future::Future; use std::pin::Pin; use std::sync::Arc; -use twitch_irc::message::ServerMessage; +use twitch_irc::message::{PrivmsgMessage, ServerMessage}; use super::bot::Bot; @@ -25,6 +25,21 @@ pub type ExecBody = Box< dyn Fn(Arc<Bot>,ServerMessage) -> Pin<Box<dyn Future<Output = Result<String,String>> + Send>> + Send + Sync, >; +/// used to store async execution functions. Primarily used for `Command` +/// +/// call this to store execution functions in `Commands` +/// +/// # example +/// ``` +/// /* 2. Define exec callback */ +/// async fn execbody(bot:Arc<Bot>,message:ServerMessage) -> Result<String,String> { +/// /* do smth */ +/// } +/// +/// /* 3. Set Command flags */ +/// cmd.set_exec_fn(execution_async(execbody)); +/// ``` +/// pub fn execution_async<T>(f: fn(Arc<Bot>,ServerMessage) -> T) -> ExecBody where T: Future<Output = Result<String,String>> + Send + 'static, @@ -32,11 +47,24 @@ where Box::new(move |a,b| Box::pin(f(a,b))) } -pub type TrigBody = Box< - dyn Fn(Arc<Bot>,ServerMessage) -> Pin<Box<dyn Future<Output = bool> + Send>> + Send + Sync, +pub type CommandTrigger = Box< + dyn Fn(Arc<Bot>,PrivmsgMessage) -> Pin<Box<dyn Future<Output = bool> + Send>> + Send + Sync, >; -pub fn condition_async<T>(f: fn(Arc<Bot>,ServerMessage) -> T) -> TrigBody +/// used to store async trigger condition callback functions. Primarily used for `Command` +/// +/// # example +/// ``` +/// /* 2. Define condition callback */ +/// async fn condition01(bot:Arc<Bot>,message:ServerMessage) -> bool { +/// /* do smth */ +/// } +/// +/// /* 3. Set Command flags */ +/// cmd.set_custom_cond_async(command_condition_async(condition01)); +/// ``` +/// +pub fn command_condition_async<T>(f: fn(Arc<Bot>,PrivmsgMessage) -> T) -> CommandTrigger where T: Future<Output = bool> + Send + 'static, { @@ -44,6 +72,30 @@ where } +pub type ListenerTrigger = Box< + dyn Fn(Arc<Bot>,ServerMessage) -> Pin<Box<dyn Future<Output = bool> + Send>> + Send + Sync, +>; + +/// used to store async trigger condition callback functions. Primarily used for `Listener` +/// +/// # example +/// ``` +/// /* 2. Define condition callback */ +/// async fn condition01(bot:Arc<Bot>,message:ServerMessage) -> bool { +/// /* do smth */ +/// } +/// +/// /* 3. Set Command flags */ +/// cmd.set_custom_cond_async(listener_condition_async(condition01)); +/// ``` +/// +pub fn listener_condition_async<T>(f: fn(Arc<Bot>,ServerMessage) -> T) -> ListenerTrigger +where + T: Future<Output = bool> + Send + 'static, +{ + Box::new(move |a,b| Box::pin(f(a,b))) +} + /// collection of functions to create built in objects diff --git a/src/botcore/bot_objects/command.rs b/src/botcore/bot_objects/command.rs index 3314347..001ac31 100644 --- a/src/botcore/bot_objects/command.rs +++ b/src/botcore/bot_objects/command.rs @@ -2,9 +2,9 @@ use std::sync::Arc; use twitch_irc::message::{PrivmsgMessage, ServerMessage}; -use crate::{botcore::{bot::Bot, bot_objects::condition_async}, execution_async, Badge}; +use crate::{botcore::bot::Bot, command_condition_async, execution_async, Badge}; -use super::{ExecBody, TrigBody}; +use super::{CommandTrigger, ExecBody}; /// Bot `Command` that stores trigger condition callback and a execution functon /// @@ -23,12 +23,13 @@ pub struct Command commands : Vec<String>, exec_fn : Arc<ExecBody>, min_badge : Badge, + /// only admins can run - default : `true` admin_only : bool, - /// admin role overrides channel badgen- default : false + /// admin role overrides channel badge - default : `false` admin_override : bool, prefix : String, custom_cond_fn : fn(Arc<Bot>,PrivmsgMessage) -> bool, - custom_cond_async : Arc<TrigBody>, + custom_cond_async : Arc<CommandTrigger>, } impl Command @@ -45,7 +46,7 @@ impl Command async fn execbody(_:Arc<Bot>,_:ServerMessage) -> Result<String,String> { Result::Ok("success".to_string()) } - async fn condition01(_:Arc<Bot>,_:ServerMessage) -> bool { true } + async fn condition01(_:Arc<Bot>,_:PrivmsgMessage) -> bool { true } Command { commands , @@ -55,11 +56,11 @@ impl Command admin_only : true, admin_override : false , custom_cond_fn : |_:Arc<Bot>,_:PrivmsgMessage| true, - custom_cond_async : Arc::new(condition_async(condition01)), + custom_cond_async : Arc::new(command_condition_async(condition01)), } } - /// set a trigger conditin callback that returns true if the command should trigger + /// set a trigger condition callback that returns true if the command should trigger pub fn set_custom_cond_fn(&mut self,cond_fn: fn(Arc<Bot>,PrivmsgMessage) -> bool) { self.custom_cond_fn = cond_fn; } @@ -83,7 +84,7 @@ impl Command /// cmd.set_custom_cond_async(condition_async(condition01)); /// ``` /// - pub fn set_custom_cond_async(&mut self,condition:TrigBody ) { + pub fn set_custom_cond_async(&mut self,condition:CommandTrigger ) { self.custom_cond_async = Arc::new(condition); } @@ -181,8 +182,8 @@ impl Command } } - fn custom_cond_ok(cmd:&Command,bot:Arc<Bot>,message:PrivmsgMessage) -> bool { - (cmd.custom_cond_fn)(bot,message) + async fn custom_cond_ok(cmd:&Command,bot:Arc<Bot>,message:PrivmsgMessage) -> bool { + (cmd.custom_cond_fn)(bot.clone(),message.clone()) && (cmd.custom_cond_async)(bot,message).await } // dbg!(msg.clone()); @@ -196,7 +197,7 @@ impl Command cmd_called(self, bot.clone(), msg.clone()) && caller_badge_ok(self, bot.clone(), msg.clone()).await && admin_only_ok(self, bot.clone(), msg.clone()) && - custom_cond_ok(self, bot, msg) + custom_cond_ok(self, bot, msg).await } diff --git a/src/botcore/bot_objects/listener.rs b/src/botcore/bot_objects/listener.rs index cc170df..af96cf7 100644 --- a/src/botcore/bot_objects/listener.rs +++ b/src/botcore/bot_objects/listener.rs @@ -2,9 +2,9 @@ use std::sync::Arc; use twitch_irc::message::ServerMessage; -use crate::{botcore::bot_objects::condition_async, execution_async, Bot}; +use crate::{execution_async, listener_condition_async, Bot}; -use super::{ExecBody, TrigBody}; +use super::{ExecBody, ListenerTrigger}; /// Bot `Listener`` that stores trigger condition callback and a execution functon /// @@ -15,7 +15,7 @@ pub struct Listener /// trigger condition trigger_cond_fn : fn(Arc<Bot>,ServerMessage) -> bool, /// trigger condition for async - trigger_cond_async : Arc<TrigBody> , + trigger_cond_async : Arc<ListenerTrigger> , /// execution body exec_fn : Arc<ExecBody>, } @@ -31,7 +31,7 @@ impl Listener async fn condition01(_:Arc<Bot>,_:ServerMessage) -> bool { true } Listener { trigger_cond_fn : |_:Arc<Bot>,_:ServerMessage| false, - trigger_cond_async : Arc::new(condition_async(condition01)), + trigger_cond_async : Arc::new(listener_condition_async(condition01)), exec_fn : Arc::new(execution_async(execbody)), } } @@ -59,7 +59,7 @@ impl Listener /// listener.set_trigger_cond_async(condition_async(condition01)); /// ``` /// - pub fn set_trigger_cond_async(&mut self,condition:TrigBody ) { + pub fn set_trigger_cond_async(&mut self,condition:ListenerTrigger ) { self.trigger_cond_async = Arc::new(condition); } @@ -85,14 +85,9 @@ impl Listener } /// checks if the trigger condition is met - pub fn cond_triggered(&self,bot:Arc<Bot>,msg:ServerMessage) -> bool { - (self.trigger_cond_fn)(bot,msg) - } - - /// executes the listeners executon body - pub async fn cond_async_triggered(&self,bot:Arc<Bot>,msg:ServerMessage) -> bool { - // (self.exec_fn)(bot,msg) - (self.trigger_cond_async)(bot,msg).await + pub async fn cond_triggered(&self,bot:Arc<Bot>,msg:ServerMessage) -> bool { + let list = Arc::new(self); + (list.trigger_cond_fn)(bot.clone(),msg.clone()) && (list.trigger_cond_async)(bot,msg).await } /// executes the listeners executon body diff --git a/src/custom_mods/pyramid.rs b/src/custom_mods/pyramid.rs index de633bb..fcc9da2 100644 --- a/src/custom_mods/pyramid.rs +++ b/src/custom_mods/pyramid.rs @@ -4,7 +4,7 @@ use std::sync::{Arc, Mutex}; use twitch_irc::message::{PrivmsgMessage, ServerMessage}; -use crate::{condition_async, Badge, Bot, Command, Listener, Module}; +use crate::{listener_condition_async, Badge, Bot, Command, Listener, Module}; /// pyramid module /// @@ -52,7 +52,7 @@ fn create_pyramid_detector() -> Listener { } /* 3. Set a trigger condition function for listener */ - listener.set_trigger_cond_async(condition_async(condition01)); + listener.set_trigger_cond_async(listener_condition_async(condition01)); /* 4. Define an async fn callback execution */ diff --git a/src/lib.rs b/src/lib.rs index 7981025..67e89ac 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -203,7 +203,8 @@ pub mod botcore; pub mod custom_mods; pub use crate::botcore::bot::Bot; pub use crate::botcore::bot_objects::execution_async; -pub use crate::botcore::bot_objects::condition_async; +pub use crate::botcore::bot_objects::command_condition_async; +pub use crate::botcore::bot_objects::listener_condition_async; pub use crate::botcore::bot_objects::listener::Listener; pub use crate::botcore::bot_objects::command::Command; pub use crate::botcore::modules::Module; -- 2.49.0 From 2a6b512ce15bcf616c9ff7a63e3160abd4631214 Mon Sep 17 00:00:00 2001 From: modulatingforce <modulatingforce@gmail.com> Date: Sat, 1 Feb 2025 16:00:57 -0500 Subject: [PATCH 08/14] doc adj for modules --- readme.md | 37 +++++++++++++++++++++++++++++-------- src/lib.rs | 33 +++++++++++++++++++++++++++++---- 2 files changed, 58 insertions(+), 12 deletions(-) diff --git a/readme.md b/readme.md index 769ba75..6af83e2 100644 --- a/readme.md +++ b/readme.md @@ -98,10 +98,34 @@ pub async fn main() { ``` -## Customize with Modules +## Customize by Loading Custom Modules A `Module` is a group of bot objects (eg `Command`) that elevated users can manage through built in `disable` and `enable` commands +Custom `Modules` can be loaded into a new bot with minimum coding : just load the modules and run the bot + +```rust +use forcebot_rs_v2::{custom_mods::{guest_badge, pyramid}, Bot}; + +#[tokio::main] +pub async fn main() { + + /* 1. Create the bot using env */ + let mut bot = Bot::new(); + + /* 2. Load Custom Modules */ + bot.load_module(guest_badge::create_module()).await; + bot.load_module(pyramid::create_module()).await; + + /* 3. Run the bot */ + bot.run().await; + +} +``` + + +## Create your own Custom Modules + Create a custom `Module` by : 1. Defining Functions that create the Custom Bot Objects (eg `Command`) @@ -329,14 +353,11 @@ pub async fn main() { # Crate Rust Documentation -Create Crate documentation +Create `forcebot_rs_v2` Rust Crate documentation -Clean Build Documentation +Documentation - Clean Build & Open in Default Browser ``` -cargo clean && cargo doc +cargo clean && cargo doc --open ``` -Open Crate Doc -``` -cargo doc --open -``` + diff --git a/src/lib.rs b/src/lib.rs index 67e89ac..817caac 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -2,7 +2,7 @@ //! //! Customize by adding additional bot objects //! -//! ## New Bot +//! # New Bot //! //! Uses Env defined variables to create and run the bot //! @@ -23,10 +23,35 @@ //! ``` //! //! -//! ## Customize with Modules +//! # Customize with Modules //! //! A `Module` is a group of bot objects (eg `Command`) that elevated users can manage through built in `disable` and `enable` commands //! +//! +//! Custom `Modules` can be loaded into a new bot with minimum coding : just load the modules and run the bot +//! +//! ```rust +//! use forcebot_rs_v2::{custom_mods::{guest_badge, pyramid}, Bot}; +//! +//! #[tokio::main] +//! pub async fn main() { +//! +//! /* 1. Create the bot using env */ +//! let mut bot = Bot::new(); +//! +//! /* 2. Load Custom Modules */ +//! bot.load_module(guest_badge::create_module()).await; +//! bot.load_module(pyramid::create_module()).await; +//! +//! /* 3. Run the bot */ +//! bot.run().await; +//! +//! } +//! ``` +//! +//! +//! # Create your own Custom Modules +//! //! Create a custom `Module` by : //! //! 1. Defining Functions that create the Custom Bot Objects (eg `Command`) @@ -98,7 +123,7 @@ //! //! ``` //! -//! ## Simple Debug Listener +//! # Simple Debug Listener //! Bot with a simple listener that listens for all messages and prints in output //! //! ```rust @@ -140,7 +165,7 @@ //! //! ``` //! -//! ## Moderator Reactor +//! # Moderator Reactor //! //! ``` //! -- 2.49.0 From 46cff68bc1116235123e9395340d2d34179070ac Mon Sep 17 00:00:00 2001 From: modulatingforce <modulatingforce@gmail.com> Date: Sun, 2 Feb 2025 08:17:38 -0500 Subject: [PATCH 09/14] reorg workspaces --- Cargo.lock | 120 ++++++++++++---- Cargo.toml | 31 +---- forcebot_core/Cargo.toml | 11 ++ {src => forcebot_core/src}/bin/fun_bot.rs | 19 ++- {src => forcebot_core/src}/bin/new_bot.rs | 2 +- .../src}/bin/simple_module.rs | 4 +- {src => forcebot_core/src}/botcore.rs | 0 {src => forcebot_core/src}/botcore/bot.rs | 16 ++- .../src}/botcore/bot_objects.rs | 18 ++- .../src}/botcore/bot_objects/command.rs | 6 +- .../src}/botcore/bot_objects/listener.rs | 41 ++++-- {src => forcebot_core/src}/botcore/modules.rs | 5 +- {src => forcebot_core/src}/custom_mods.rs | 0 .../src}/custom_mods/guest_badge.rs | 0 .../src}/custom_mods/pyramid.rs | 96 ++++++++++--- {src => forcebot_core/src}/lib.rs | 42 +++--- moderator_reactor/Cargo.toml | 11 ++ .../src/main.rs | 8 +- new_empty_bot/Cargo.toml | 11 ++ {src => new_empty_bot/src}/main.rs | 3 +- readme.md | 129 ++++++++++++------ simple_command_bot/Cargo.toml | 11 ++ .../src/main.rs | 10 +- simple_debug_listener/Cargo.toml | 11 ++ .../src/main.rs | 4 +- simple_module_example/Cargo.toml | 11 ++ simple_module_example/src/main.rs | 79 +++++++++++ 27 files changed, 519 insertions(+), 180 deletions(-) create mode 100644 forcebot_core/Cargo.toml rename {src => forcebot_core/src}/bin/fun_bot.rs (82%) rename {src => forcebot_core/src}/bin/new_bot.rs (95%) rename {src => forcebot_core/src}/bin/simple_module.rs (95%) rename {src => forcebot_core/src}/botcore.rs (100%) rename {src => forcebot_core/src}/botcore/bot.rs (96%) rename {src => forcebot_core/src}/botcore/bot_objects.rs (94%) rename {src => forcebot_core/src}/botcore/bot_objects/command.rs (97%) rename {src => forcebot_core/src}/botcore/bot_objects/listener.rs (63%) rename {src => forcebot_core/src}/botcore/modules.rs (91%) rename {src => forcebot_core/src}/custom_mods.rs (100%) rename {src => forcebot_core/src}/custom_mods/guest_badge.rs (100%) rename {src => forcebot_core/src}/custom_mods/pyramid.rs (73%) rename {src => forcebot_core/src}/lib.rs (85%) create mode 100644 moderator_reactor/Cargo.toml rename src/bin/moderator_reactor.rs => moderator_reactor/src/main.rs (91%) create mode 100644 new_empty_bot/Cargo.toml rename {src => new_empty_bot/src}/main.rs (93%) create mode 100644 simple_command_bot/Cargo.toml rename src/bin/simple_command_bot.rs => simple_command_bot/src/main.rs (88%) create mode 100644 simple_debug_listener/Cargo.toml rename src/bin/simple_debug_listener.rs => simple_debug_listener/src/main.rs (90%) create mode 100644 simple_module_example/Cargo.toml create mode 100644 simple_module_example/src/main.rs diff --git a/Cargo.lock b/Cargo.lock index 3ee755c..e770a80 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -19,9 +19,9 @@ checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627" [[package]] name = "async-trait" -version = "0.1.85" +version = "0.1.86" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f934833b4b7233644e5848f235df3f57ed8c80f1528a26c3dfa13d2147fa056" +checksum = "644dd749086bf3771a2fbc5f256fdb982d53f011c7d5d560304eafeecebce79d" dependencies = [ "proc-macro2", "quote", @@ -63,9 +63,9 @@ checksum = "325918d6fe32f23b19878fe4b34794ae41fc19ddbe53b10571a4874d44ffd39b" [[package]] name = "cc" -version = "1.2.10" +version = "1.2.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13208fcbb66eaeffe09b99fffbe1af420f00a7b35aa99ad683dfc1aa76145229" +checksum = "e4730490333d58093109dc02c23174c3f4d490998c3fed3cc8e82d57afedb9cf" dependencies = [ "shlex", ] @@ -142,7 +142,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" [[package]] -name = "forcebot-rs-v2" +name = "forcebot_core" version = "0.1.0" dependencies = [ "dotenv", @@ -200,13 +200,14 @@ dependencies = [ [[package]] name = "getrandom" -version = "0.2.15" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" +checksum = "43a49c392881ce6d5c3b8cb70f98717b7c07aabbdff06687b9030dbfbe2725f8" dependencies = [ "cfg-if", "libc", - "wasi", + "wasi 0.13.3+wasi-0.2.2", + "windows-targets", ] [[package]] @@ -271,15 +272,26 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2886843bf800fba2e3377cff24abf6379b4c4d5c6681eaf9ea5b0d15090450bd" dependencies = [ "libc", - "wasi", + "wasi 0.11.0+wasi-snapshot-preview1", "windows-sys 0.52.0", ] +[[package]] +name = "moderator_reactor" +version = "0.1.0" +dependencies = [ + "dotenv", + "forcebot_core", + "lazy_static", + "tokio", + "twitch-irc", +] + [[package]] name = "native-tls" -version = "0.2.12" +version = "0.2.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8614eb2c83d59d1c8cc974dd3f920198647674a0a035e1af1fa58707e317466" +checksum = "0dab59f8e050d5df8e4dd87d9206fb6f65a483e20ac9fda365ade4fab353196c" dependencies = [ "libc", "log", @@ -292,6 +304,17 @@ dependencies = [ "tempfile", ] +[[package]] +name = "new_empty_bot" +version = "0.1.0" +dependencies = [ + "dotenv", + "forcebot_core", + "lazy_static", + "tokio", + "twitch-irc", +] + [[package]] name = "num-traits" version = "0.2.19" @@ -318,9 +341,9 @@ checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775" [[package]] name = "openssl" -version = "0.10.68" +version = "0.10.69" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6174bc48f102d208783c2c84bf931bb75927a617866870de8a4ea85597f871f5" +checksum = "f5e534d133a060a3c19daec1eb3e98ec6f4685978834f2dbadfe2ec215bab64e" dependencies = [ "bitflags", "cfg-if", @@ -344,9 +367,9 @@ dependencies = [ [[package]] name = "openssl-probe" -version = "0.1.5" +version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" +checksum = "d05e27ee213611ffe7d6348b942e8f942b37114c00cc03cec254295a4a17852e" [[package]] name = "openssl-sys" @@ -436,9 +459,9 @@ checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" [[package]] name = "rustix" -version = "0.38.43" +version = "0.38.44" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a78891ee6bf2340288408954ac787aa063d8e8817e9f53abb37c695c6d834ef6" +checksum = "fdb5bc1ae2baa591800df16c9ca78619bf65c0488b41b96ccec5d11220d8c154" dependencies = [ "bitflags", "errno", @@ -500,6 +523,39 @@ dependencies = [ "libc", ] +[[package]] +name = "simple_command_bot" +version = "0.1.0" +dependencies = [ + "dotenv", + "forcebot_core", + "lazy_static", + "tokio", + "twitch-irc", +] + +[[package]] +name = "simple_debug_listener" +version = "0.1.0" +dependencies = [ + "dotenv", + "forcebot_core", + "lazy_static", + "tokio", + "twitch-irc", +] + +[[package]] +name = "simple_module_example" +version = "0.1.0" +dependencies = [ + "dotenv", + "forcebot_core", + "lazy_static", + "tokio", + "twitch-irc", +] + [[package]] name = "slab" version = "0.4.9" @@ -527,9 +583,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.96" +version = "2.0.98" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d5d0adab1ae378d7f53bdebc67a39f1f151407ef230f0ce2883572f5d8985c80" +checksum = "36147f1a48ae0ec2b5b3bc5b537d267457555a10dc06f3dbc8cb11ba3006d3b1" dependencies = [ "proc-macro2", "quote", @@ -538,9 +594,9 @@ dependencies = [ [[package]] name = "tempfile" -version = "3.15.0" +version = "3.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a8a559c81686f576e8cd0290cd2a24a2a9ad80c98b3478856500fcbd7acd704" +checksum = "38c246215d7d24f48ae091a2902398798e05d978b24315d6efbc00ede9a8bb91" dependencies = [ "cfg-if", "fastrand", @@ -687,9 +743,9 @@ dependencies = [ [[package]] name = "unicode-ident" -version = "1.0.14" +version = "1.0.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "adb9e6ca4f869e1180728b7950e35922a7fc6397f7b641499e8f3ef06e50dc83" +checksum = "a210d160f08b701c8721ba1c726c11662f877ea6b7094007e1ca9a1041945034" [[package]] name = "vcpkg" @@ -703,6 +759,15 @@ version = "0.11.0+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" +[[package]] +name = "wasi" +version = "0.13.3+wasi-0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26816d2e1a4a36a2940b96c5296ce403917633dff8f3440e9b236ed6f6bacad2" +dependencies = [ + "wit-bindgen-rt", +] + [[package]] name = "windows-sys" version = "0.52.0" @@ -784,3 +849,12 @@ name = "windows_x86_64_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" + +[[package]] +name = "wit-bindgen-rt" +version = "0.33.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3268f3d866458b787f390cf61f4bbb563b922d091359f9608842999eaee3943c" +dependencies = [ + "bitflags", +] diff --git a/Cargo.toml b/Cargo.toml index e9d2e5e..7b027e7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,24 +1,7 @@ -[package] -name = "forcebot-rs-v2" -version = "0.1.0" -edition = "2021" -default-run = "forcebot-rs-v2" - -[dependencies] -dotenv = "0.15.0" -lazy_static = "1.5.0" -tokio = { version = "1.33.0", features = ["full"] } -twitch-irc = "5.0.1" - - -# [[bin]] -# name = "simple_bot" -# path = "src/simple_bot.rs" - -# [[bin]] -# name = "simple_bot_listener" -# path = "src/simple_bot_listener.rs" - -# [lib] -# name = "botlib" -# path = "src/lib.rs" \ No newline at end of file +[workspace] +members = [ + "forcebot_core", + "simple_module_example", + "new_empty_bot", + "simple_debug_listener", + "moderator_reactor", "simple_command_bot"] diff --git a/forcebot_core/Cargo.toml b/forcebot_core/Cargo.toml new file mode 100644 index 0000000..082c28b --- /dev/null +++ b/forcebot_core/Cargo.toml @@ -0,0 +1,11 @@ +[package] +name = "forcebot_core" +version = "0.1.0" +edition = "2021" +default-run = "fun_bot" + +[dependencies] +dotenv = "0.15.0" +lazy_static = "1.5.0" +tokio = { version = "1.33.0", features = ["full"] } +twitch-irc = "5.0.1" diff --git a/src/bin/fun_bot.rs b/forcebot_core/src/bin/fun_bot.rs similarity index 82% rename from src/bin/fun_bot.rs rename to forcebot_core/src/bin/fun_bot.rs index 4f119ce..d9d8d45 100644 --- a/src/bin/fun_bot.rs +++ b/forcebot_core/src/bin/fun_bot.rs @@ -1,9 +1,9 @@ //! WIP Fun forcebot with catered customizations #todo //! //! Custom modules that can be managed in chat through `disable` and `enable` commands -//! - funbot -//! - guests -//! - pyramid +//! - `besty` +//! - `guests` +//! - `pyramid` //! //! //! Be sure the followig is defined in `.env` @@ -17,7 +17,10 @@ //! - Get a Bot Chat Token here - <https://twitchtokengenerator.com> //! - More Info - <https://dev.twitch.tv/docs/authentication> -use forcebot_rs_v2::{custom_mods::{guest_badge, pyramid}, Bot}; +// use forcebot_rs_v2::{custom_mods::{guest_badge, pyramid}, Bot}; +use forcebot_core::{custom_mods::{guest_badge, pyramid}, Bot}; + + #[tokio::main] pub async fn main() { @@ -41,16 +44,17 @@ pub async fn main() { pub mod funbot_objs { use std::sync::Arc; - use forcebot_rs_v2::{execution_async, Badge, Bot, Command, Module}; + use forcebot_core::{execution_async, modules::Status, Badge, Bot, Command, Module}; use twitch_irc::message::ServerMessage; /// Create a Module with a loaded Command object pub fn create_module() -> Module { let mut custom_mod = Module::new( - vec!["funbot".to_string()], - "".to_string()); + vec!["besty".to_string()], + "Now Aware of besty xdd666 ".to_string()); custom_mod.load_command(create_cmd_test()); + custom_mod.set_status_by_default(Status::Disabled); custom_mod } @@ -73,6 +77,7 @@ pub mod funbot_objs { cmd.set_admin_only(false); cmd.set_min_badge(Badge::Vip); + cmd } diff --git a/src/bin/new_bot.rs b/forcebot_core/src/bin/new_bot.rs similarity index 95% rename from src/bin/new_bot.rs rename to forcebot_core/src/bin/new_bot.rs index cbe682b..4abdbc7 100644 --- a/src/bin/new_bot.rs +++ b/forcebot_core/src/bin/new_bot.rs @@ -10,7 +10,7 @@ //! - Get a Bot Chat Token here - <https://twitchtokengenerator.com> //! - More Info - <https://dev.twitch.tv/docs/authentication> -use forcebot_rs_v2::Bot; +use forcebot_core::Bot; #[tokio::main] pub async fn main() { diff --git a/src/bin/simple_module.rs b/forcebot_core/src/bin/simple_module.rs similarity index 95% rename from src/bin/simple_module.rs rename to forcebot_core/src/bin/simple_module.rs index 77bd5da..02756d7 100644 --- a/src/bin/simple_module.rs +++ b/forcebot_core/src/bin/simple_module.rs @@ -17,7 +17,7 @@ //! - Get a Bot Chat Token here - <https://twitchtokengenerator.com> //! - More Info - <https://dev.twitch.tv/docs/authentication> -use forcebot_rs_v2::Bot; +use forcebot_core::Bot; #[tokio::main] pub async fn main() { @@ -37,7 +37,7 @@ pub async fn main() { pub mod custom_mod { use std::sync::Arc; - use forcebot_rs_v2::{execution_async, Badge, Bot, Command, Module}; + use forcebot_core::{execution_async, Badge, Bot, Command, Module}; use twitch_irc::message::ServerMessage; diff --git a/src/botcore.rs b/forcebot_core/src/botcore.rs similarity index 100% rename from src/botcore.rs rename to forcebot_core/src/botcore.rs diff --git a/src/botcore/bot.rs b/forcebot_core/src/botcore/bot.rs similarity index 96% rename from src/botcore/bot.rs rename to forcebot_core/src/botcore/bot.rs index f1a8798..96a66b6 100644 --- a/src/botcore/bot.rs +++ b/forcebot_core/src/botcore/bot.rs @@ -5,10 +5,16 @@ use twitch_irc::{login::StaticLoginCredentials, message::{PrivmsgMessage, Server use dotenv::dotenv; use std::{env, sync::Arc, time::{Duration, Instant}}; -use crate::{Badge, Command, Listener, Module}; +// use crate::{Badge, Command, Listener, Module}; +use super::bot_objects::command::Command; -use super::{bot_objects::built_in_objects, modules::{self, Status}}; +use crate::botcore::bot_objects::Badge; +use crate::botcore::bot_objects::listener::Listener; +use super::super::botcore::modules::Module; +// use super:: +use super:: {bot_objects::built_in_objects, modules::{self, Status}}; + /// Twitch chat bot pub struct Bot @@ -73,7 +79,7 @@ impl Bot /// Creates a new `Bot` using bot information /// - /// Bot joined channels will include channels from `.env` and `botchannels` argument + /// Bot will join `botchannels` argument pub fn new_from(bot_login_name:String,oauth_token:String,prefix:String,botchannels:Vec<String>) -> Bot { dotenv().ok(); @@ -90,10 +96,6 @@ impl Bot let mut botchannels_all = Vec::new(); botchannels_all.extend(botchannels); - for chnl in env::var("bot_channels").unwrap().split(',') { - botchannels_all.push(chnl.to_owned()); - } - let mut admins = Vec::new(); diff --git a/src/botcore/bot_objects.rs b/forcebot_core/src/botcore/bot_objects.rs similarity index 94% rename from src/botcore/bot_objects.rs rename to forcebot_core/src/botcore/bot_objects.rs index d5ae5b2..397d7fc 100644 --- a/src/botcore/bot_objects.rs +++ b/forcebot_core/src/botcore/bot_objects.rs @@ -105,8 +105,16 @@ pub mod built_in_objects { use std::{sync::Arc, time::{Duration, Instant}}; use twitch_irc::message::ServerMessage; + + use super::{execution_async,command::Command,Bot,Badge,super::modules::Status}; + + // use super::execution_async; + // use super::command::Command; + // use super::Bot; + // use super::Badge; + // use super::super::modules::Status; + // ::{execution_async, modules::Status, Badge, Bot, Command}; - use crate::{execution_async, modules::Status, Badge, Bot, Command}; /// create a vector of command build in objects @@ -146,14 +154,14 @@ pub mod built_in_objects { Result::Err("Not Valid message type".to_string()) } - /* 3. Set and Store the execution body using `async_box()` */ + /* 3. Set and Store the execution body using `execution_async()` */ cmd.set_exec_fn(execution_async(execbody)); /* 4. optionally, remove admin only default flag */ cmd.set_admin_only(false); /* 5. optionally, set min badge*/ - cmd.set_min_badge(Badge::Moderator); + cmd.set_min_badge(Badge::Moderator /* ::Moderator */); cmd } @@ -201,7 +209,7 @@ pub mod built_in_objects { Result::Err("Not Valid message type".to_string()) } - /* 3. Set and Store the execution body using `async_box()` */ + /* 3. Set and Store the execution body using `execution_async()` */ cmd.set_exec_fn(execution_async(execbody)); /* 4. optionally, remove admin only default flag */ @@ -304,7 +312,7 @@ pub mod built_in_objects { Result::Err("Not Valid message type".to_string()) } - /* 3. Set and Store the execution body using `async_box()` */ + /* 3. Set and Store the execution body using `execution_async()` */ cmd.set_exec_fn(execution_async(execbody)); /* 4. optionally, remove admin only default flag */ diff --git a/src/botcore/bot_objects/command.rs b/forcebot_core/src/botcore/bot_objects/command.rs similarity index 97% rename from src/botcore/bot_objects/command.rs rename to forcebot_core/src/botcore/bot_objects/command.rs index 001ac31..54c4b85 100644 --- a/src/botcore/bot_objects/command.rs +++ b/forcebot_core/src/botcore/bot_objects/command.rs @@ -2,7 +2,9 @@ use std::sync::Arc; use twitch_irc::message::{PrivmsgMessage, ServerMessage}; -use crate::{botcore::bot::Bot, command_condition_async, execution_async, Badge}; +// use crate::{botcore::bot::Bot, command_condition_async, execution_async, Badge}; +use super::{execution_async,Bot,Badge,command_condition_async}; + use super::{CommandTrigger, ExecBody}; @@ -80,7 +82,7 @@ impl Command /// /* 2. define an async function */ /// async fn condition01(_:Arc<Bot>,_:ServerMessage) -> bool { true } /// - /// /* 3. Set and Store the execution body using `async_box()` */ + /// /* 3. Set and Store the execution body using `execution_async()` */ /// cmd.set_custom_cond_async(condition_async(condition01)); /// ``` /// diff --git a/src/botcore/bot_objects/listener.rs b/forcebot_core/src/botcore/bot_objects/listener.rs similarity index 63% rename from src/botcore/bot_objects/listener.rs rename to forcebot_core/src/botcore/bot_objects/listener.rs index af96cf7..84bd468 100644 --- a/src/botcore/bot_objects/listener.rs +++ b/forcebot_core/src/botcore/bot_objects/listener.rs @@ -2,13 +2,22 @@ use std::sync::Arc; use twitch_irc::message::ServerMessage; -use crate::{execution_async, listener_condition_async, Bot}; - +use super::{execution_async,Bot,listener_condition_async}; + use super::{ExecBody, ListenerTrigger}; -/// Bot `Listener`` that stores trigger condition callback and a execution functon +/// Bot `Listener` that stores trigger condition callback and a execution functon /// -/// Use `asyncfn_box()` on custom async execution bodies +/// Use `Listener` functions to define the Trigger Condition & Execution callbacks. +/// When the Trigger callback is `true`, the Execution callback runs in the bot loop +/// +/// Create a new empty `Listener` with `new()` +/// +/// Use the following on the empty listener before loading it into the bot to set the callbacks +/// +/// - `set_trigger_cond_fn()` - to define the Trigger condition callback +/// +/// - `set_exec_fn()` - to define the Execution Callback #[derive(Clone)] pub struct Listener { @@ -23,14 +32,22 @@ pub struct Listener impl Listener { - /// Creates a new empty `Listener` . - /// Call `set_trigger_cond_fn()` and `set_exec_fn()` to trigger & execution function callbacks + /// Creates a new empty `Listener` + /// + /// Use `Listener` functions to define the Trigger Condition & Execution callbacks. + /// When the Trigger callback is `true`, the Execution callback runs in the bot loop + /// + /// Use the following on the empty listener before loading it into the bot to set the callbacks + /// + /// - `set_trigger_cond_fn()` - to define the Trigger condition callback + /// + /// - `set_exec_fn()` - to define the Execution Callback pub fn new() -> Listener { async fn execbody(_:Arc<Bot>,_:ServerMessage) -> Result<String,String> {Result::Ok("success".to_string()) } async fn condition01(_:Arc<Bot>,_:ServerMessage) -> bool { true } Listener { - trigger_cond_fn : |_:Arc<Bot>,_:ServerMessage| false, + trigger_cond_fn : |_:Arc<Bot>,_:ServerMessage| true, trigger_cond_async : Arc::new(listener_condition_async(condition01)), exec_fn : Arc::new(execution_async(execbody)), } @@ -45,7 +62,7 @@ impl Listener /// /// Same as `set_trigger_cond_fn()` , but async define /// - /// Use`asyncfn_box()` on the async fn when storing + /// Use`condition_async()` on the async fn when storing /// /// Example - /// ```rust @@ -55,7 +72,7 @@ impl Listener /// /* 2. define an async function */ /// async fn condition01(_:Arc<Bot>,_:ServerMessage) -> bool { true } /// - /// /* 3. Set and Store the execution body using `async_box()` */ + /// /* 3. Set and Store the execution body using `execution_async()` */ /// listener.set_trigger_cond_async(condition_async(condition01)); /// ``` /// @@ -66,7 +83,7 @@ impl Listener /// sets the execution body of the listener for when it triggers /// - /// Use`asyncfn_box()` on the async fn when storing + /// Use`execution_async()` on the async fn when storing /// /// Example - /// ```rust @@ -76,8 +93,8 @@ impl Listener /// /* 2. define an async function */ /// async fn execbody(_:Arc<Bot>,_:ServerMessage) -> Result<String,String> {Result::Ok("success".to_string()) } /// - /// /* 3. Set and Store the execution body using `async_box()` */ - /// listener.set_exec_fn(asyncfn_box(execbody)); + /// /* 3. Set and Store the execution body using `execution_async()` */ + /// listener.set_exec_fn(execution_async(execbody)); /// ``` /// pub fn set_exec_fn(&mut self,exec_fn:ExecBody ) { diff --git a/src/botcore/modules.rs b/forcebot_core/src/botcore/modules.rs similarity index 91% rename from src/botcore/modules.rs rename to forcebot_core/src/botcore/modules.rs index 2ed44dc..9a30e41 100644 --- a/src/botcore/modules.rs +++ b/forcebot_core/src/botcore/modules.rs @@ -1,6 +1,7 @@ +use super::bot_objects::command::Command; +use super::bot_objects::listener::Listener; -use crate::{Command, Listener}; #[derive(PartialEq, Eq,Debug,Clone)] pub enum Status { @@ -33,7 +34,7 @@ impl Module bot_read_description, listeners: vec![], commands: vec![], - default_status_per_channel: Status::Disabled, + default_status_per_channel: Status::Enabled, } } diff --git a/src/custom_mods.rs b/forcebot_core/src/custom_mods.rs similarity index 100% rename from src/custom_mods.rs rename to forcebot_core/src/custom_mods.rs diff --git a/src/custom_mods/guest_badge.rs b/forcebot_core/src/custom_mods/guest_badge.rs similarity index 100% rename from src/custom_mods/guest_badge.rs rename to forcebot_core/src/custom_mods/guest_badge.rs diff --git a/src/custom_mods/pyramid.rs b/forcebot_core/src/custom_mods/pyramid.rs similarity index 73% rename from src/custom_mods/pyramid.rs rename to forcebot_core/src/custom_mods/pyramid.rs index fcc9da2..9b00db2 100644 --- a/src/custom_mods/pyramid.rs +++ b/forcebot_core/src/custom_mods/pyramid.rs @@ -4,7 +4,14 @@ use std::sync::{Arc, Mutex}; use twitch_irc::message::{PrivmsgMessage, ServerMessage}; -use crate::{listener_condition_async, Badge, Bot, Command, Listener, Module}; +// use crate::{execution_async, listener_condition_async, Badge, Bot, Command, Listener, Module}; +use super::super::botcore::bot_objects::execution_async; +use super::super::botcore::bot_objects::listener_condition_async; +use super::super::botcore::bot_objects::listener::Listener; +use super::super::botcore::modules::Module; +use super::super::botcore::bot::Bot; +use super::super::botcore::bot_objects::command::Command; +use super::super::botcore::bot_objects::Badge; /// pyramid module /// @@ -58,15 +65,15 @@ fn create_pyramid_detector() -> Listener { /* 4. Define an async fn callback execution */ async fn execbody(bot:Arc<Bot>,message:ServerMessage) -> Result<String,String> { if let ServerMessage::Privmsg(msg) = message { - if detect_pyramid_complete_ok(bot.clone(), msg.clone()).await { + // if detect_pyramid_complete_ok(bot.clone(), msg.clone()).await { let _ = bot.client.say_in_reply_to(&msg, "Clap".to_string()).await ; return Result::Ok("Success".to_string()) ; - } + // } } Result::Err("Not Valid message type".to_string()) } - /* 5. Set and Store the execution body using `async_box()` */ + /* 5. Set and Store the execution body using `execution_async()` */ listener.set_exec_fn(Box::new(move |a,b| Box::pin(execbody(a,b)))); listener @@ -78,16 +85,17 @@ async fn detect_pyramid_complete_ok(_bot:Arc<Bot>,msg:PrivmsgMessage) -> bool { let msgtext = msg.message_text.replace("\u{e0000}","").trim().to_string(); let msgchannel = msg.channel_login; + let msgchatter = msg.sender.login; // 1. Check if Pyramid started in chat > and recognize pyramid started if !is_pyramid_started(msgchannel.clone()) & check_start_pyramid(msgchannel.clone(),msgtext.clone(),) { set_pyramid_started(msgchannel.clone(),true); - push_to_compare(msgchannel.clone(),get_start_pattern(msgchannel.clone())); + push_to_compare(msgchannel.clone(),msgchatter.clone(),get_start_pattern(msgchannel.clone())); } if is_pyramid_started(msgchannel.clone()) { - push_to_compare(msgchannel.clone(),msgtext.clone()); + push_to_compare(msgchannel.clone(),msgchatter.clone(),msgtext.clone()); } // 2a. If Pyramid Not Started, Assume message is a potential start pattern @@ -111,8 +119,8 @@ async fn detect_pyramid_complete_ok(_bot:Arc<Bot>,msg:PrivmsgMessage) -> bool { lazy_static!{ - /// Message Compare stack per channel (channel:String,msgstack:Vec<String>) - pub static ref COMPARE_MSG_STACK_PER_CHNL: Mutex<Vec<(String,Mutex<Vec<String>>)>> = Mutex::new(vec![]); + /// Message Compare stack per channel (channel:String,msgstack:Vec<(chatter:String,message:String)>) + pub static ref COMPARE_MSG_STACK_PER_CHNL: Mutex<Vec<(String,Mutex<Vec<(String,String)>>)>> = Mutex::new(vec![]); #[derive(Debug)] /// Pyramid Started per channel (channel:String,started:bool) pub static ref PYRAMID_STARTED_PER_CHNL: Mutex<Vec<(String,Mutex<bool>)>> = Mutex::new(vec![]); @@ -126,7 +134,7 @@ lazy_static!{ } -fn read_top_of_compare(channel:String) -> Option<String> { +fn read_top_of_compare(channel:String) -> Option<(String,String)> { let comp_perchnl = COMPARE_MSG_STACK_PER_CHNL.lock().unwrap(); @@ -142,7 +150,7 @@ fn read_top_of_compare(channel:String) -> Option<String> { } -fn pop_top_of_compare(channel:String) -> Option<String> { +fn pop_top_of_compare(channel:String) -> Option<(String,String)> { let comp_perchnl = COMPARE_MSG_STACK_PER_CHNL.lock().unwrap(); for rec in comp_perchnl.iter() { @@ -218,7 +226,7 @@ fn get_start_pattern(channel:String) -> String { /// pushes message to compare stack -fn push_to_compare(channel:String,message:String) { +fn push_to_compare(channel:String,chatter:String,message:String) { let mut comp_perchnl = COMPARE_MSG_STACK_PER_CHNL.lock().unwrap(); let mut found = false; @@ -226,12 +234,12 @@ fn push_to_compare(channel:String,message:String) { if rec.0 == channel { found = true; let mut msg_stack = rec.1.lock().unwrap(); - msg_stack.push(message.clone()); + msg_stack.push((chatter.clone(),message.clone())); // dbg!("Push message to cmp stack ; result last cmp_pchnl - ",comp_perchnl.last()); } } if !found { - comp_perchnl.push((channel,Mutex::new(vec![message]))); + comp_perchnl.push((channel,Mutex::new(vec![(chatter,message)]))); } } @@ -247,21 +255,21 @@ fn check_start_pyramid(channel:String,msgtext: String) -> bool { fn symmetry_ok(channel:String) -> bool { let mut temp_stack = TEMP_MSG_STACK.lock().unwrap(); let mut checking_started = false; - if !(read_top_of_compare(channel.clone()).unwrap_or("".to_string()) == get_start_pattern(channel.clone())) { + if !(read_top_of_compare(channel.clone()).unwrap_or(("".to_string(),"".to_string())).1 == get_start_pattern(channel.clone())) { return false; } loop { - if !checking_started && read_top_of_compare(channel.clone()).unwrap_or("".to_string()) == get_start_pattern(channel.clone()) { + if !checking_started && read_top_of_compare(channel.clone()).unwrap_or(("".to_string(),"".to_string())).1 == get_start_pattern(channel.clone()) { checking_started = true; } - if temp_stack.last().is_none() || read_top_of_compare(channel.clone()).unwrap_or("".to_string()).len() > temp_stack.last().unwrap_or(&"".to_string()).len() { - temp_stack.push(pop_top_of_compare(channel.clone()).unwrap_or("".to_string())); + if temp_stack.last().is_none() || read_top_of_compare(channel.clone()).unwrap_or(("".to_string(),"".to_string())).1.len() > temp_stack.last().unwrap_or(&"".to_string()).len() { + temp_stack.push(pop_top_of_compare(channel.clone()).unwrap_or(("".to_string(),"".to_string())).1); - } else if temp_stack.last().is_some() && read_top_of_compare(channel.clone()).unwrap_or("".to_string()).len() < temp_stack.last().unwrap_or(&"".to_string()).len() { + } else if temp_stack.last().is_some() && read_top_of_compare(channel.clone()).unwrap_or(("".to_string(),"".to_string())).1.len() < temp_stack.last().unwrap_or(&"".to_string()).len() { temp_stack.pop(); - if temp_stack.last().unwrap_or(&"".to_string()).clone() == read_top_of_compare(channel.clone()).unwrap_or("".to_string()) { + if temp_stack.last().unwrap_or(&"".to_string()).clone() == read_top_of_compare(channel.clone()).unwrap_or(("".to_string(),"".to_string())).1 { temp_stack.pop(); continue; @@ -272,7 +280,7 @@ fn symmetry_ok(channel:String) -> bool { } } else { /* dbg!("failed catchall"); */ return false; } - if checking_started && read_top_of_compare(channel.clone()).unwrap() == get_start_pattern(channel.clone()) { + if checking_started && read_top_of_compare(channel.clone()).unwrap().1 == get_start_pattern(channel.clone()) { temp_stack.clear(); return true; @@ -284,7 +292,9 @@ fn symmetry_ok(channel:String) -> bool { } -/// pyramid interruptor #todo +/// #todo +/// +/// pyramid interruptor /// /// pick chatters that will be interrupted if they solo build /// @@ -311,7 +321,7 @@ fn create_interruptor_cmd() -> Command { Result::Err("Not Valid message type".to_string()) } - /* 3. Set and Store the execution body using `async_box()` */ + /* 3. Set and Store the execution body using `execution_async()` */ cmd.set_exec_fn(Box::new(move |a,b| Box::pin(execbody(a,b)))); /* 4. optionally, remove admin only default flag */ @@ -324,5 +334,47 @@ fn create_interruptor_cmd() -> Command { cmd } +/// #todo +fn create_interruptor_module(channel:String) -> Module { + /* 1. Create a new module */ + let modname = format!("interruptor {}",channel); + let mut custom_mod = Module::new( + vec![modname], + "".to_string()); + /* 2. Load the cmd into a new module */ + custom_mod.load_listener(create_interruptor_listener()); + custom_mod + +} + +/// #todo +fn create_interruptor_listener() -> Listener { + /* 2a. Create a new blank Listener */ + let mut listener = Listener::new(); + + /* 2b. Set a trigger condition function for listener */ + listener.set_trigger_cond_fn( + |_:Arc<Bot>,_:ServerMessage| true + ); + + /* 2c. Define an async fn callback execution */ + async fn execbody(_:Arc<Bot>,message:ServerMessage) -> Result<String,String> { + dbg!(message); + Result::Ok("Success".to_string()) + } + + /* 2d. Set and Store the execution body using `execution_async()` */ + listener.set_exec_fn(execution_async(execbody)); + + listener + +} + +/// #todo +/// +/// Returns Some(chatter) if the pyramid in progress is being built by a solo +fn solo_building(channel:String) -> Option<String> { + None +} diff --git a/src/lib.rs b/forcebot_core/src/lib.rs similarity index 85% rename from src/lib.rs rename to forcebot_core/src/lib.rs index 817caac..8ccc272 100644 --- a/src/lib.rs +++ b/forcebot_core/src/lib.rs @@ -1,4 +1,4 @@ -//! `forcebot-rs-v2` : Twitch chat bot written in rust. +//! `forcebot_core` library for `forcebot-rs-v2` Twitch chat bot //! //! Customize by adding additional bot objects //! @@ -7,7 +7,7 @@ //! Uses Env defined variables to create and run the bot //! //! ```rust -//! use forcebot_rs_v2::Bot; +//! use forcebot_core::Bot; //! //! #[tokio::main] //! pub async fn main() { @@ -27,11 +27,10 @@ //! //! A `Module` is a group of bot objects (eg `Command`) that elevated users can manage through built in `disable` and `enable` commands //! -//! //! Custom `Modules` can be loaded into a new bot with minimum coding : just load the modules and run the bot //! //! ```rust -//! use forcebot_rs_v2::{custom_mods::{guest_badge, pyramid}, Bot}; +//! use forcebot_core::{custom_mods::{guest_badge, pyramid}, Bot}; //! //! #[tokio::main] //! pub async fn main() { @@ -60,7 +59,7 @@ //! //! //! ```rust -//! use forcebot_rs_v2::Bot; +//! use forcebot_core::Bot; //! //! #[tokio::main] //! pub async fn main() { @@ -79,7 +78,7 @@ //! pub mod custom_mod { //! use std::sync::Arc; //! -//! use forcebot_rs_v2::{execution_async, Badge, Bot, Command, Module}; +//! use forcebot_core::{execution_async, Badge, Bot, Command, Module}; //! use twitch_irc::message::ServerMessage; //! //! @@ -129,7 +128,7 @@ //! ```rust //! use std::sync::Arc; //! -//! use forcebot_rs_v2::{execution_async, Bot, Listener}; +//! use forcebot_core::{execution_async, Bot, Listener}; //! use twitch_irc::message::ServerMessage; //! //! #[tokio::main] @@ -152,7 +151,7 @@ //! Result::Ok("Success".to_string()) //! } //! -//! /* 2d. Set and Store the execution body using `async_box()` */ +//! /* 2d. Set and Store the execution body using `execution_async()` */ //! listener.set_exec_fn(execution_async(execbody)); //! //! /* 3. Load the listener into the bot */ @@ -171,9 +170,9 @@ //! //! use std::sync::Arc; //! -//! use forcebot_rs_v2::Bot; -//! use forcebot_rs_v2::execution_async; -//! use forcebot_rs_v2::Listener; +//! use forcebot_core::Bot; +//! use forcebot_core::execution_async; +//! use forcebot_core::Listener; //! use twitch_irc::message::ServerMessage; //! //! @@ -211,7 +210,7 @@ //! Result::Err("Not Valid message type".to_string()) //! } //! -//! /* 4. Set and Store the execution body using `async_box()` */ +//! /* 4. Set and Store the execution body using `execution_async()` */ //! listener.set_exec_fn(execution_async(execbody)); //! //! /* 5. Load the listener into the bot */ @@ -226,12 +225,13 @@ pub mod botcore; pub mod custom_mods; -pub use crate::botcore::bot::Bot; -pub use crate::botcore::bot_objects::execution_async; -pub use crate::botcore::bot_objects::command_condition_async; -pub use crate::botcore::bot_objects::listener_condition_async; -pub use crate::botcore::bot_objects::listener::Listener; -pub use crate::botcore::bot_objects::command::Command; -pub use crate::botcore::modules::Module; -pub use crate::botcore::bot_objects::Badge; -pub use crate::botcore::modules; +pub use botcore::bot::Bot; +pub use botcore::bot_objects::execution_async; +pub use botcore::bot_objects::command_condition_async; +pub use botcore::bot_objects::listener_condition_async; +pub use botcore::bot_objects::listener::Listener; +// pub use crate::botcore::bot_objects::command::Command; +pub use botcore::bot_objects::command::Command; +pub use botcore::modules::Module; +pub use botcore::bot_objects::Badge; +pub use botcore::modules; diff --git a/moderator_reactor/Cargo.toml b/moderator_reactor/Cargo.toml new file mode 100644 index 0000000..f3431e6 --- /dev/null +++ b/moderator_reactor/Cargo.toml @@ -0,0 +1,11 @@ +[package] +name = "moderator_reactor" +version = "0.1.0" +edition = "2021" + +[dependencies] +forcebot_core = {path = "../forcebot_core"} +dotenv = "0.15.0" +lazy_static = "1.5.0" +tokio = { version = "1.33.0", features = ["full"] } +twitch-irc = "5.0.1" diff --git a/src/bin/moderator_reactor.rs b/moderator_reactor/src/main.rs similarity index 91% rename from src/bin/moderator_reactor.rs rename to moderator_reactor/src/main.rs index 279d204..58c0c9f 100644 --- a/src/bin/moderator_reactor.rs +++ b/moderator_reactor/src/main.rs @@ -13,9 +13,9 @@ use std::sync::Arc; -use forcebot_rs_v2::Bot; -use forcebot_rs_v2::execution_async; -use forcebot_rs_v2::Listener; +use forcebot_core::Bot; +use forcebot_core::execution_async; +use forcebot_core::Listener; use twitch_irc::message::ServerMessage; @@ -53,7 +53,7 @@ pub async fn main() { Result::Err("Not Valid message type".to_string()) } - /* 4. Set and Store the execution body using `async_box()` */ + /* 4. Set and Store the execution body using `execution_async()` */ listener.set_exec_fn(execution_async(execbody)); /* 5. Load the listener into the bot */ diff --git a/new_empty_bot/Cargo.toml b/new_empty_bot/Cargo.toml new file mode 100644 index 0000000..96d5d3f --- /dev/null +++ b/new_empty_bot/Cargo.toml @@ -0,0 +1,11 @@ +[package] +name = "new_empty_bot" +version = "0.1.0" +edition = "2021" + +[dependencies] +forcebot_core = {path = "../forcebot_core"} +dotenv = "0.15.0" +lazy_static = "1.5.0" +tokio = { version = "1.33.0", features = ["full"] } +twitch-irc = "5.0.1" diff --git a/src/main.rs b/new_empty_bot/src/main.rs similarity index 93% rename from src/main.rs rename to new_empty_bot/src/main.rs index ebbe171..4abdbc7 100644 --- a/src/main.rs +++ b/new_empty_bot/src/main.rs @@ -10,8 +10,7 @@ //! - Get a Bot Chat Token here - <https://twitchtokengenerator.com> //! - More Info - <https://dev.twitch.tv/docs/authentication> -use forcebot_rs_v2::botcore::bot::Bot; - +use forcebot_core::Bot; #[tokio::main] pub async fn main() { diff --git a/readme.md b/readme.md index 6af83e2..d94791a 100644 --- a/readme.md +++ b/readme.md @@ -1,4 +1,4 @@ -Twitch chat bot written in rust +Customizable Twitch chat bot written in rust # Quick Start @@ -22,28 +22,47 @@ bot_admins=ADMIN 3. Build & run ``` -cargo run +cargo run -p forcebot_core ``` +# Features + +- Quick Start to use full feature set bot +- Moderators & Broadcasters can `disable` or `enable` `Modules` of bot functionality through chat `Commands` +- Full Feature Set `forcebot_core` bot has the following modules loaded + - `guest_badge` - Temporary badges can be issued to chatters + - `besty` - Tomfoolery + - `pyramid` - for detecting & handling pyramids +- `forcebot_core` library API provides Custom package developers a way to add functionality by adding `Modules` that contain Bot Objects like `Commands` and `Listeners` +- `Listeners` and `Commands` listen for a defined callback trigger condition and run an defined execution callback +- `Commands` are similar to `Listeners` with refined trigger conditions including using bot `prefix` with the `Command` , triggers based on `Badge` , and more +- Workspace for package developers to independently code their own `Modules` +- Workspace comes with binary crates with working or example bots that use `forcebot_core` library + - `moderator_reactor` - bot kneels to all moderator messages + - `simple_module_example` - bot has a `test` `Module` with a `test` `Command` .Moderators & Broadcasters can manage the `Module` in chat with `enable` / `disable` `Commands` + - `new_empty_bot` - while empty, has `disable` and `enable` chat `Commands` . This is an example of the bot without any loaded modules + - `simple_command_bot` - bot responds to a `test` `Command`. As the command was not loaded through a `Module`, `disable` & `enable` commands don't work on the `test` command. This could be a Global `Command` + - `simple_debug_listener` - bot outputs all twitch `ServerMessages` received to terminal + + + # Example Bots -Use the following commands to build and run built-in bots. No coding required! +Use the following to build and run built-in bots. No coding required! -## New Bot +## New Empty Bot Run an empty simple bot that logs into chat and has minimum built in functions ``` -cargo run --bin new_bot +cargo run -p new_empty_bot ``` -## WIP Customized Fun Bot +## Full Featured Forcebot Run a forcebot with fun catered customizations -*ongoing work in progress* - ``` -cargo run --bin fun_bot +cargo run -p forcebot_core ``` @@ -51,28 +70,69 @@ cargo run --bin fun_bot Run a bot that listens to all messages and output to console ``` -cargo run --bin simple_debug_listener +cargo run -p simple_debug_listener ``` ## Simple Command Bot Run a bot that uses the `test` chat `Command` . `Commands` are prefixed and must be ran by a chatter with a `vip` badge or above ``` -cargo run --bin simple_command_bot +cargo run -p simple_command_bot ``` ## Moderator Reactor Run a bot that listens for messages with the `moderator` badge, and replies to that mod with an emote ``` -cargo run --bin moderator_reactor +cargo run -p moderator_reactor ``` ## Module loaded Bot Run a bot that has a `test` chat `Command`. As the command was loaded through a module, moderators or broadcastors can `enable` or `disable` the module through chat commands ``` -cargo run --bin simple_module +cargo run -p simple_module_example +``` + +# Workspace packages + +Source is a workspace of packages . In particular, `forcebot_core` is the main library crate to use + +*TIP* : if you want to start customizing you own bot, create a binary package in the workspace for your bot's binary crate + +More info about workspaces - https://doc.rust-lang.org/book/ch14-03-cargo-workspaces.html + + +## Creating a new package + +To create a new package + +1. Create a new package + +For example, to create a new binary crate in the workspace + +``` +cargo new my_new_bot +``` + +2. In the newly created directory for your package, adjust the `Cargo.toml` to the following + +``` +[dependencies] +forcebot_core = {path = "../forcebot_core"} +dotenv = "0.15.0" +lazy_static = "1.5.0" +tokio = { version = "1.33.0", features = ["full"] } +twitch-irc = "5.0.1" +``` + +3. Copy `main.rs` from the `new_empty_bot` package into your package + +4. Optionally, customize your `main()` to load modules before starting the bot + +5. Build and run your package +``` +cargo run -p my_new_bot ``` @@ -83,7 +143,7 @@ cargo run --bin simple_module Uses Env defined variables to create and run the bot ```rust -use forcebot_rs_v2::Bot; +use forcebot_core::Bot; #[tokio::main] pub async fn main() { @@ -95,7 +155,6 @@ pub async fn main() { bot.run().await; } - ``` ## Customize by Loading Custom Modules @@ -105,7 +164,8 @@ A `Module` is a group of bot objects (eg `Command`) that elevated users can mana Custom `Modules` can be loaded into a new bot with minimum coding : just load the modules and run the bot ```rust -use forcebot_rs_v2::{custom_mods::{guest_badge, pyramid}, Bot}; +use forcebot_core::{custom_mods::{guest_badge, pyramid}, Bot}; + #[tokio::main] pub async fn main() { @@ -134,7 +194,7 @@ Create a custom `Module` by : ```rust -use forcebot_rs_v2::Bot; +use forcebot_core::Bot; #[tokio::main] pub async fn main() { @@ -153,7 +213,7 @@ pub async fn main() { pub mod custom_mod { use std::sync::Arc; - use forcebot_rs_v2::{execution_async, Badge, Bot, Command, Module}; + use forcebot_core::{execution_async, Badge, Bot, Command, Module}; use twitch_irc::message::ServerMessage; @@ -193,7 +253,6 @@ pub mod custom_mod { cmd } } - ``` ## Simple Debug Listener @@ -202,7 +261,7 @@ Bot with a simple listener that listens for all messages and prints in output ```rust use std::sync::Arc; -use forcebot_rs_v2::{execution_async, Bot, Listener}; +use forcebot_core::{execution_async, Bot, Listener}; use twitch_irc::message::ServerMessage; #[tokio::main] @@ -225,7 +284,7 @@ pub async fn main() { Result::Ok("Success".to_string()) } - /* 2d. Set and Store the execution body using `async_box()` */ + /* 2d. Set and Store the execution body using `execution_async()` */ listener.set_exec_fn(execution_async(execbody)); /* 3. Load the listener into the bot */ @@ -235,7 +294,6 @@ pub async fn main() { bot.run().await; } - ``` ## Moderator Reactor @@ -243,12 +301,11 @@ pub async fn main() { Example listener listens for a moderator badge and reply in chat ```rust - use std::sync::Arc; -use forcebot_rs_v2::Bot; -use forcebot_rs_v2::execution_async; -use forcebot_rs_v2::Listener; +use forcebot_core::Bot; +use forcebot_core::execution_async; +use forcebot_core::Listener; use twitch_irc::message::ServerMessage; @@ -286,7 +343,7 @@ pub async fn main() { Result::Err("Not Valid message type".to_string()) } - /* 4. Set and Store the execution body using `async_box()` */ + /* 4. Set and Store the execution body using `execution_async()` */ listener.set_exec_fn(execution_async(execbody)); /* 5. Load the listener into the bot */ @@ -296,9 +353,6 @@ pub async fn main() { bot.run().await; } - - - ``` ## Simple Test Command @@ -306,13 +360,12 @@ pub async fn main() { ```rust use std::sync::Arc; -use forcebot_rs_v2::Badge; -use forcebot_rs_v2::Bot; -use forcebot_rs_v2::execution_async; -use forcebot_rs_v2::Command; +use forcebot_core::Badge; +use forcebot_core::Bot; +use forcebot_core::execution_async; +use forcebot_core::Command; use twitch_irc::message::ServerMessage; - #[tokio::main] pub async fn main() { @@ -331,7 +384,7 @@ pub async fn main() { Result::Err("Not Valid message type".to_string()) } - /* 3. Set and Store the execution body using `async_box()` */ + /* 3. Set and Store the execution body using `execution_async()` */ cmd.set_exec_fn(execution_async(execbody)); /* 4. optionally, remove admin only default flag */ @@ -347,11 +400,9 @@ pub async fn main() { bot.run().await; } - - ``` -# Crate Rust Documentation +# Crate Rust API Documentation Create `forcebot_rs_v2` Rust Crate documentation diff --git a/simple_command_bot/Cargo.toml b/simple_command_bot/Cargo.toml new file mode 100644 index 0000000..25f2f0f --- /dev/null +++ b/simple_command_bot/Cargo.toml @@ -0,0 +1,11 @@ +[package] +name = "simple_command_bot" +version = "0.1.0" +edition = "2021" + +[dependencies] +forcebot_core = {path = "../forcebot_core"} +dotenv = "0.15.0" +lazy_static = "1.5.0" +tokio = { version = "1.33.0", features = ["full"] } +twitch-irc = "5.0.1" diff --git a/src/bin/simple_command_bot.rs b/simple_command_bot/src/main.rs similarity index 88% rename from src/bin/simple_command_bot.rs rename to simple_command_bot/src/main.rs index 9c2f2ff..2f29e09 100644 --- a/src/bin/simple_command_bot.rs +++ b/simple_command_bot/src/main.rs @@ -15,10 +15,10 @@ use std::sync::Arc; -use forcebot_rs_v2::Badge; -use forcebot_rs_v2::Bot; -use forcebot_rs_v2::execution_async; -use forcebot_rs_v2::Command; +use forcebot_core::Badge; +use forcebot_core::Bot; +use forcebot_core::execution_async; +use forcebot_core::Command; use twitch_irc::message::ServerMessage; @@ -40,7 +40,7 @@ pub async fn main() { Result::Err("Not Valid message type".to_string()) } - /* 3. Set and Store the execution body using `async_box()` */ + /* 3. Set and Store the execution body using `execution_async()` */ cmd.set_exec_fn(execution_async(execbody)); /* 4. optionally, remove admin only default flag */ diff --git a/simple_debug_listener/Cargo.toml b/simple_debug_listener/Cargo.toml new file mode 100644 index 0000000..fceb717 --- /dev/null +++ b/simple_debug_listener/Cargo.toml @@ -0,0 +1,11 @@ +[package] +name = "simple_debug_listener" +version = "0.1.0" +edition = "2021" + +[dependencies] +forcebot_core = {path = "../forcebot_core"} +dotenv = "0.15.0" +lazy_static = "1.5.0" +tokio = { version = "1.33.0", features = ["full"] } +twitch-irc = "5.0.1" diff --git a/src/bin/simple_debug_listener.rs b/simple_debug_listener/src/main.rs similarity index 90% rename from src/bin/simple_debug_listener.rs rename to simple_debug_listener/src/main.rs index 791c5d0..3ce82fc 100644 --- a/src/bin/simple_debug_listener.rs +++ b/simple_debug_listener/src/main.rs @@ -12,7 +12,7 @@ use std::sync::Arc; -use forcebot_rs_v2::{execution_async, Bot, Listener}; +use forcebot_core::{execution_async, Bot, Listener}; use twitch_irc::message::ServerMessage; #[tokio::main] @@ -35,7 +35,7 @@ pub async fn main() { Result::Ok("Success".to_string()) } - /* 2d. Set and Store the execution body using `async_box()` */ + /* 2d. Set and Store the execution body using `execution_async()` */ listener.set_exec_fn(execution_async(execbody)); /* 3. Load the listener into the bot */ diff --git a/simple_module_example/Cargo.toml b/simple_module_example/Cargo.toml new file mode 100644 index 0000000..b00ba0b --- /dev/null +++ b/simple_module_example/Cargo.toml @@ -0,0 +1,11 @@ +[package] +name = "simple_module_example" +version = "0.1.0" +edition = "2021" + +[dependencies] +forcebot_core = {path = "../forcebot_core"} +dotenv = "0.15.0" +lazy_static = "1.5.0" +tokio = { version = "1.33.0", features = ["full"] } +twitch-irc = "5.0.1" diff --git a/simple_module_example/src/main.rs b/simple_module_example/src/main.rs new file mode 100644 index 0000000..4793ed4 --- /dev/null +++ b/simple_module_example/src/main.rs @@ -0,0 +1,79 @@ +//! Simple Module with a Command +//! +//! Adding objects through packages provides controls , +//! such as moderators, and brodcasters can disable or enable mods +//! +//! Here, moderators or above can enable or disable the `test` +//! module with the command `<prefix> disable test` +//! +//! Be sure the followig is defined in `.env` +//! - login_name +//! - access_token +//! - bot_channels +//! - prefix +//! - bot_admins +//! +//! Bot access tokens be generated here - +//! - Get a Bot Chat Token here - <https://twitchtokengenerator.com> +//! - More Info - <https://dev.twitch.tv/docs/authentication> + +use forcebot_core::Bot; + +#[tokio::main] +pub async fn main() { + + /* Create the bot using env */ + let mut bot = Bot::new(); + + /* load the Module */ + bot.load_module(custom_mod::new()).await; + + /* Run the bot */ + bot.run().await; + +} + + +pub mod custom_mod { + use std::sync::Arc; + + use forcebot_core::{execution_async, Badge, Bot, Command, Module}; + use twitch_irc::message::ServerMessage; + + + /// Module definition with a loaded command + pub fn new() -> Module { + /* 1. Create a new module */ + let mut custom_mod = Module::new( + vec!["test".to_string()], + "".to_string()); + + /* 2. Load the cmd into a new module */ + custom_mod.load_command(cmd_test()); + + custom_mod + + } + + /// Command definition + pub fn cmd_test() -> Command { + /* 1. Create a new cmd */ + let mut cmd = Command::new(vec!["test".to_string()],"".to_string()); + + /* 2. Define exec callback */ + async fn execbody(bot:Arc<Bot>,message:ServerMessage) -> Result<String,String> { + if let ServerMessage::Privmsg(msg) = message { + let _= bot.client.say_in_reply_to( + &msg, "test return".to_string()).await; + } + Result::Err("Not Valid message type".to_string()) + } + + /* 3. Set Command flags */ + cmd.set_exec_fn(execution_async(execbody)); + cmd.set_admin_only(false); + cmd.set_min_badge(Badge::Vip); + + cmd + } +} \ No newline at end of file -- 2.49.0 From 5a02b090c6456ad2e0e946c5b0ccf06be1db3f6f Mon Sep 17 00:00:00 2001 From: modulatingforce <modulatingforce@gmail.com> Date: Sun, 2 Feb 2025 17:06:42 -0500 Subject: [PATCH 10/14] fix - pyramid is recognized finished as expected --- forcebot_core/src/custom_mods/pyramid.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/forcebot_core/src/custom_mods/pyramid.rs b/forcebot_core/src/custom_mods/pyramid.rs index 9b00db2..3a3de01 100644 --- a/forcebot_core/src/custom_mods/pyramid.rs +++ b/forcebot_core/src/custom_mods/pyramid.rs @@ -66,7 +66,11 @@ fn create_pyramid_detector() -> Listener { async fn execbody(bot:Arc<Bot>,message:ServerMessage) -> Result<String,String> { if let ServerMessage::Privmsg(msg) = message { // if detect_pyramid_complete_ok(bot.clone(), msg.clone()).await { + let _ = bot.client.say_in_reply_to(&msg, "Clap".to_string()).await ; + + set_pyramid_started(msg.channel_login,false); + return Result::Ok("Success".to_string()) ; // } } -- 2.49.0 From 1e522940c91ae3d10ce220abe7f2de6c397441e4 Mon Sep 17 00:00:00 2001 From: modulatingforce <modulatingforce@gmail.com> Date: Mon, 3 Feb 2025 21:33:44 -0500 Subject: [PATCH 11/14] debug module and fix issue with modules --- forcebot_core/src/bin/fun_bot.rs | 5 +- forcebot_core/src/bin/new_bot.rs | 2 +- .../src/bin/simple_debug_listener.rs | 47 +++++ forcebot_core/src/bin/simple_module.rs | 2 +- forcebot_core/src/botcore/bot.rs | 199 ++++++++++++------ forcebot_core/src/botcore/bot_objects.rs | 2 +- forcebot_core/src/botcore/modules.rs | 3 +- forcebot_core/src/custom_mods.rs | 3 +- forcebot_core/src/custom_mods/debug.rs | 146 +++++++++++++ forcebot_core/src/custom_mods/guest_badge.rs | 2 +- forcebot_core/src/custom_mods/pyramid.rs | 17 +- forcebot_core/src/lib.rs | 3 +- moderator_reactor/src/main.rs | 5 +- new_empty_bot/src/main.rs | 2 +- readme.md | 5 +- simple_command_bot/src/main.rs | 2 +- simple_debug_listener/src/main.rs | 6 +- simple_module_example/src/main.rs | 2 +- 18 files changed, 360 insertions(+), 93 deletions(-) create mode 100644 forcebot_core/src/bin/simple_debug_listener.rs create mode 100644 forcebot_core/src/custom_mods/debug.rs diff --git a/forcebot_core/src/bin/fun_bot.rs b/forcebot_core/src/bin/fun_bot.rs index d9d8d45..872aff9 100644 --- a/forcebot_core/src/bin/fun_bot.rs +++ b/forcebot_core/src/bin/fun_bot.rs @@ -18,7 +18,7 @@ //! - More Info - <https://dev.twitch.tv/docs/authentication> // use forcebot_rs_v2::{custom_mods::{guest_badge, pyramid}, Bot}; -use forcebot_core::{custom_mods::{guest_badge, pyramid}, Bot}; +use forcebot_core::{custom_mods::{debug, guest_badge, pyramid}, Bot}; @@ -26,7 +26,7 @@ use forcebot_core::{custom_mods::{guest_badge, pyramid}, Bot}; pub async fn main() { /* Create the bot using env */ - let mut bot = Bot::new(); + let bot = Bot::new().await; /* 1. Load the module into the bot */ bot.load_module(funbot_objs::create_module()).await; @@ -34,6 +34,7 @@ pub async fn main() { /* 2. Load Custom Modules */ bot.load_module(guest_badge::create_module()).await; bot.load_module(pyramid::create_module()).await; + bot.load_module(debug::create_module()).await; /* 3. Run the bot */ bot.run().await; diff --git a/forcebot_core/src/bin/new_bot.rs b/forcebot_core/src/bin/new_bot.rs index 4abdbc7..a81462e 100644 --- a/forcebot_core/src/bin/new_bot.rs +++ b/forcebot_core/src/bin/new_bot.rs @@ -16,7 +16,7 @@ use forcebot_core::Bot; pub async fn main() { /* 1. Create the bot using env */ - let bot = Bot::new(); + let bot = Bot::new().await; /* 2. Run the bot */ bot.run().await; diff --git a/forcebot_core/src/bin/simple_debug_listener.rs b/forcebot_core/src/bin/simple_debug_listener.rs new file mode 100644 index 0000000..bac75ed --- /dev/null +++ b/forcebot_core/src/bin/simple_debug_listener.rs @@ -0,0 +1,47 @@ +//! Example simple Binary crate that creates & runs bot based on `.env` +//! Be sure the followig is defined in `.env` +//! - login_name +//! - access_token +//! - bot_channels +//! - prefix +//! - bot_admins +//! +//! Bot access tokens be generated here - +//! - Get a Bot Chat Token here - <https://twitchtokengenerator.com> +//! - More Info - <https://dev.twitch.tv/docs/authentication> + +use std::sync::Arc; + +use forcebot_core::{execution_async, Bot, Listener}; +use twitch_irc::message::ServerMessage; + +#[tokio::main] +pub async fn main() { + + /* 1. Create the bot using env */ + let bot = Bot::new().await; + + /* 2a. Create a new blank Listener */ + let mut listener = Listener::new(); + + /* 2b. Set a trigger condition function for listener */ + listener.set_trigger_cond_fn( + |_:Arc<Bot>,_:ServerMessage| true + ); + + /* 2c. Define an async fn callback execution */ + async fn execbody(_:Arc<Bot>,message:ServerMessage) -> Result<String,String> { + dbg!(message); /* outputs message to debug */ + Result::Ok("Success".to_string()) + } + + /* 2d. Set and Store the execution body using `async_box()` */ + listener.set_exec_fn(execution_async(execbody)); + + /* 3. Load the listener into the bot */ + bot.load_listener(listener).await; + + /* 4. Run the bot */ + bot.run().await; + +} diff --git a/forcebot_core/src/bin/simple_module.rs b/forcebot_core/src/bin/simple_module.rs index 02756d7..45bf06d 100644 --- a/forcebot_core/src/bin/simple_module.rs +++ b/forcebot_core/src/bin/simple_module.rs @@ -23,7 +23,7 @@ use forcebot_core::Bot; pub async fn main() { /* Create the bot using env */ - let mut bot = Bot::new(); + let bot = Bot::new().await; /* load the Module */ bot.load_module(custom_mod::new()).await; diff --git a/forcebot_core/src/botcore/bot.rs b/forcebot_core/src/botcore/bot.rs index 96a66b6..9e3070f 100644 --- a/forcebot_core/src/botcore/bot.rs +++ b/forcebot_core/src/botcore/bot.rs @@ -3,7 +3,7 @@ use tokio::sync::{mpsc::UnboundedReceiver, Mutex}; use twitch_irc::{login::StaticLoginCredentials, message::{PrivmsgMessage, ServerMessage}, SecureTCPTransport, TwitchIRCClient}; use dotenv::dotenv; -use std::{env, sync::Arc, time::{Duration, Instant}}; +use std::{env, sync::{Arc, RwLock}, time::{Duration, Instant}}; // use crate::{Badge, Command, Listener, Module}; use super::bot_objects::command::Command; @@ -30,13 +30,13 @@ pub struct Bot /// admin chatters admins : Vec<String>, /// listeners - listeners: Vec<Listener>, + listeners: Mutex<Vec<Listener>>, /// commands - commands: Vec<Command>, + commands: Mutex<Vec<Command>>, /// modules - modules: Vec<Module>, + modules: RwLock<Vec<Module>>, /// channel module status - channel_module_status: Mutex<Vec<(String,String,modules::Status)>>, + channel_module_status: RwLock<Vec<(String,String,modules::Status)>>, /// chatter guest badges - chatter,channel,Badge,start_time,duration chatter_guest_badges: Mutex<Vec<(String,String,Badge,Instant,Duration)>>, /// Message cache @@ -55,7 +55,7 @@ impl Bot /// - bot_channels /// - prefix /// - bot_admins - pub fn new() -> Bot { + pub async fn new() -> Bot { @@ -72,7 +72,7 @@ impl Bot botchannels.push(chnl.to_owned()); } - Bot::new_from(bot_login_name, oauth_token, prefix, botchannels) + Bot::new_from(bot_login_name, oauth_token, prefix, botchannels).await } @@ -80,7 +80,7 @@ impl Bot /// Creates a new `Bot` using bot information /// /// Bot will join `botchannels` argument - pub fn new_from(bot_login_name:String,oauth_token:String,prefix:String,botchannels:Vec<String>) -> Bot { + pub async fn new_from(bot_login_name:String,oauth_token:String,prefix:String,botchannels:Vec<String>) -> Bot { dotenv().ok(); let bot_login_name = bot_login_name; @@ -106,22 +106,22 @@ impl Bot } - let mut bot = Bot { + let bot = Bot { prefix, incoming_msgs : Mutex::new(incoming_messages), client, botchannels : botchannels_all, - listeners : vec![], - commands : vec![], + listeners : Mutex::new(vec![]), + commands : Mutex::new(vec![]), admins, - modules: vec![], - channel_module_status: Mutex::new(vec![]), + modules: RwLock::new(vec![]), + channel_module_status: RwLock::new(vec![]), chatter_guest_badges: Mutex::new(vec![]), message_cache : Mutex::new(vec![]), }; for cmd in built_in_objects::create_commands() { - bot.load_command(cmd); + bot.load_command(cmd).await; } bot @@ -143,8 +143,8 @@ impl Bot while let Some(message) = in_msgs_lock.recv().await { - - for listener in &(*bot).listeners { + let bot_listener_lock = bot.listeners.lock().await; + for listener in bot_listener_lock.iter() { let a = listener.clone(); if a.cond_triggered(bot.clone(),message.clone()).await { @@ -160,8 +160,8 @@ impl Bot // dbg!(cache_lock.clone()); drop(cache_lock); - - for cmd in &(*bot).commands { + let cmd_lock = bot.commands.lock().await; + for cmd in cmd_lock.iter() { let a = cmd.clone(); if a.command_triggered(bot.clone(),msg.clone()).await { @@ -171,40 +171,61 @@ impl Bot } - 'module_loop: for module in &(*bot).modules { - - // skip modules that are disable - let cms_lock = bot.channel_module_status.lock().await; + fn get_enabled_channel_modules(bot: Arc<Bot>,channel:String) -> Vec<Module> { + let botmodules_lock = bot.modules.read().unwrap(); + let botmodules_cpy = botmodules_lock.clone(); + drop(botmodules_lock); + + let mut enabled_mods = Vec::new(); + + 'module_loop: for module in &*botmodules_cpy { + + // dbg!("try cms read"); + let cms_lock = bot.channel_module_status.read().unwrap(); + // dbg!("cms read lock"); + // dbg!(module.get_names()); for channel_flags in cms_lock.iter() { - if channel_flags.0 == msg.channel_login.clone() { - + if channel_flags.0 == channel { + if module.get_names().contains(&channel_flags.1) && channel_flags.2 == Status::Disabled { continue 'module_loop; + // disabled_mods.push(module); } } } - - - - for listener in module.get_listeners() { - - let a = listener.clone(); - if a.cond_triggered(bot.clone(),message.clone()).await { - - let _ = listener.execute_fn(bot.clone(),message.clone()).await; - } - } - for cmd in module.get_commands() { - - let a = cmd.clone(); - if a.command_triggered(bot.clone(),msg.clone()).await { - - let _ = cmd.execute_fn(bot.clone(),message.clone()).await; - } - } - + enabled_mods.push(module.clone()); } + + enabled_mods + + + } + + + + for module in get_enabled_channel_modules(bot.clone(), msg.clone().channel_login) { + + for listener in module.get_listeners() { + + let a = listener.clone(); + if a.cond_triggered(bot.clone(),message.clone()).await { + + let _ = listener.execute_fn(bot.clone(),message.clone()).await; + } + } + for cmd in module.get_commands() { + + let a = cmd.clone(); + if a.command_triggered(bot.clone(),msg.clone()).await { + + let _ = cmd.execute_fn(bot.clone(),message.clone()).await; + } + } + + } + + } else {} ; @@ -217,20 +238,25 @@ impl Bot } /// Loads a `Listener` into the bot - pub fn load_listener(&mut self,l : Listener) { - self.listeners.push(l); + pub async fn load_listener(&self,l : Listener) { + let a = Arc::new(self); + let mut listlock = a.listeners.lock().await; + listlock.push(l); } /// Loads a `Command` into the bot - pub fn load_command(&mut self,c : Command) { - self.commands.push(c); + pub async fn load_command(&self,c : Command) { + let a = Arc::new(self); + let mut cmdlock = a.commands.lock().await; + cmdlock.push(c); } - pub fn get_module(&self,module:String) -> Option<Module> { - for modl in self.modules.clone() { + pub async fn get_module(&self,module:String) -> Option<Module> { + let modlock = self.modules.read().unwrap(); + for modl in modlock.iter() { if modl.get_names().contains(&module) { - return Some(modl); + return Some(modl.clone()); } } None @@ -247,20 +273,35 @@ impl Bot } /// loads a `Module` and its bot objects - pub async fn load_module(&mut self,m: Module) { + pub async fn load_module(&self,m: Module) { + // dbg!("load module - start",m.get_names().first().unwrap()); + let bot = Arc::new(self); + // let bot_lock = bot.lock().await; + // dbg!("bot arc"); if m.get_status_by_default() == Status::Disabled { - for chnl in &self.botchannels { - self.disable_module(chnl.clone(), + // dbg!("module fund disabled by default"); + // dbg!("inner if"); + for (_index,chnl) in bot.botchannels.iter().enumerate() { + // dbg!("iter - ",index); + bot.disable_module(chnl.clone(), m.get_names().first().unwrap().clone()).await } } - self.modules.push(m) + // dbg!("aftee disable check"); + // dbg!(bot.modules); + let mut botmods = bot.modules.write().unwrap(); + // dbg!(m); + // dbg!("loading module ",m.get_names()); + botmods.push(m); } pub async fn get_channel_module_status(&self,channel:String,module:String) -> Status { + // dbg!("get channel module status"); let found_disabled:bool = { - let cms_lock = self.channel_module_status.lock().await; - + // dbg!("try cms read"); + let cms_lock = self.channel_module_status.read().unwrap(); + // dbg!("cms read lock"); + // dbg!(module.clone()); let mut found = false; for channel_flags in cms_lock.iter() { @@ -272,15 +313,37 @@ impl Bot } found }; + + let module_loaded:bool = { + + let mut loaded_yn = false; + + for loaded_m in self.modules.read().unwrap().iter() { + if loaded_m.get_names().contains(&module) { + loaded_yn = true; + } + } + + loaded_yn + + }; + if found_disabled { return Status::Disabled;} + + else if !module_loaded { return Status::NotLoaded ;} else { return Status::Enabled; }; } pub async fn disable_module(&self,channel:String,module:String){ + // dbg!("disable module called",channel.clone(),module.clone()); let found_disabled:bool = { - let cms_lock = self.channel_module_status.lock().await; + // dbg!("finding disabled mod"); + // dbg!("try cms read"); + let cms_lock = self.channel_module_status.read().unwrap(); + // dbg!("cms read lock"); + // dbg!(module.clone()); let mut found = false; @@ -291,13 +354,20 @@ impl Bot } } } + drop(cms_lock); found }; if !found_disabled { - let mut cms_lock = self.channel_module_status.lock().await; - cms_lock.push((channel,module,Status::Disabled)); + // self.channel_module_status. + // dbg!("try cms write"); /*,self.channel_module_status */ + let mut cms_lock = self.channel_module_status.write().unwrap(); + // cms_lock. + // dbg!("cms write lock"); + cms_lock.push((channel,module.clone(),Status::Disabled)); + // dbg!(module.clone()); + drop(cms_lock); } @@ -306,8 +376,12 @@ impl Bot } pub async fn enable_module(&self,channel:String,module:String){ - let mut lock = self.channel_module_status.lock().await; - if lock.contains(&(channel.clone(),module.clone(),Status::Disabled)) { + // dbg!("enable module called",channel.clone(),module.clone()); + // dbg!("try cms write"); + let mut lock = self.channel_module_status.write().unwrap(); + // dbg!("cms write lock"); + // dbg!(module.clone()); + while lock.contains(&(channel.clone(),module.clone(),Status::Disabled)) { let index = lock .iter() @@ -316,6 +390,7 @@ impl Bot .unwrap(); lock.remove(index); } + drop(lock); } pub async fn get_channel_guest_badges(&self,chatter:String,channel:String) -> Vec<(Badge,Instant,Duration)> { diff --git a/forcebot_core/src/botcore/bot_objects.rs b/forcebot_core/src/botcore/bot_objects.rs index 397d7fc..b0d334d 100644 --- a/forcebot_core/src/botcore/bot_objects.rs +++ b/forcebot_core/src/botcore/bot_objects.rs @@ -184,7 +184,7 @@ pub mod built_in_objects { bot.enable_module(msg.channel_login.clone(), arg.to_string()).await; //bot.get_modules() - if let Some(found_mod) = bot.get_module(arg.to_string()) { + if let Some(found_mod) = bot.get_module(arg.to_string()).await { bot_message = bot_message.to_string() + found_mod.get_bot_read_description().as_str(); } re_enabled = true; diff --git a/forcebot_core/src/botcore/modules.rs b/forcebot_core/src/botcore/modules.rs index 9a30e41..889cf9e 100644 --- a/forcebot_core/src/botcore/modules.rs +++ b/forcebot_core/src/botcore/modules.rs @@ -6,7 +6,8 @@ use super::bot_objects::listener::Listener; #[derive(PartialEq, Eq,Debug,Clone)] pub enum Status { Disabled, - Enabled + Enabled, + NotLoaded, } /// Bot `Module` that groups a set of `bot_objects` diff --git a/forcebot_core/src/custom_mods.rs b/forcebot_core/src/custom_mods.rs index 099cee1..f295b1e 100644 --- a/forcebot_core/src/custom_mods.rs +++ b/forcebot_core/src/custom_mods.rs @@ -1,2 +1,3 @@ pub mod guest_badge; -pub mod pyramid; \ No newline at end of file +pub mod pyramid; +pub mod debug; \ No newline at end of file diff --git a/forcebot_core/src/custom_mods/debug.rs b/forcebot_core/src/custom_mods/debug.rs new file mode 100644 index 0000000..df5b321 --- /dev/null +++ b/forcebot_core/src/custom_mods/debug.rs @@ -0,0 +1,146 @@ + + +use std::sync::{Arc}; + +use crate::{execution_async, modules::Status, Bot, Command, Listener, Module}; +use twitch_irc::message::ServerMessage; +// use lazy_static::lazy_static; + +// lazy_static!{ +// /// Listener per channel (channel:String,Listener) +// pub static ref LISTENER_PER_CHNL: Mutex<Vec<(String,Mutex<Listener>)>> = Mutex::new(vec![]); +// } + +/// debug module +/// +/// Commands to enable debugging messages in chat +/// +/// `debug on` to start +/// +/// `debug off` to stop +/// +/// + + +/// Use this function when loading modules into the bot +/// +/// For example +/// ```rust +/// bot.load_module(debug::create_module()); +/// ``` +/// +pub fn create_module() -> Module { + /* 1. Create a new module */ + let mut custom_mod = Module::new( + vec!["debug".to_string()], + "".to_string()); + + /* 2. Load the cmd into a new module */ + custom_mod.load_command(cmd_debug_on()); + custom_mod.load_command(cmd_debug_off()); + custom_mod + +} + +/// Command definition for debug command +fn cmd_debug_on() -> Command { + /* 1. Create a new cmd */ + let mut cmd = Command::new(vec!["debug on".to_string()],"".to_string()); + + /* 2. Define exec callback */ + async fn execbody(bot:Arc<Bot>,message:ServerMessage) -> Result<String,String> { + if let ServerMessage::Privmsg(msg) = message { + // dbg!("debug cmd on executed"); + + let modulename="debug listener".to_string(); + + if let Status::NotLoaded = bot.get_channel_module_status(msg.channel_login.clone(), modulename.clone()).await { + let module = create_listener_module(modulename.clone()); + bot.load_module(module.clone()).await; + } + let modl = bot.get_module(modulename).await.unwrap(); + bot.enable_module(msg.channel_login.clone(), modl.get_names().first().unwrap().clone()).await; + println!("Debug enabled for channel {}",msg.channel_login); + } + Result::Err("Not Valid message type".to_string()) + } + + /* 3. Set Command flags */ + cmd.set_exec_fn(execution_async(execbody)); + cmd.set_admin_only(true); + // cmd.set_min_badge(Badge::Moderator); + cmd.set_admin_override(true); + + cmd +} + +/// Command definition for debug off command +fn cmd_debug_off() -> Command { + /* 1. Create a new cmd */ + let mut cmd = Command::new(vec!["debug off".to_string()],"".to_string()); + + /* 2. Define exec callback */ + async fn execbody(bot:Arc<Bot>,message:ServerMessage) -> Result<String,String> { + if let ServerMessage::Privmsg(msg) = message { + // dbg!("debug cmd on executed"); + + let modulename="debug listener".to_string(); + + // if let Status::NotLoaded = bot.get_channel_module_status(msg.channel_login.clone(), modulename.clone()).await { + // let module = create_listener_module(modulename.clone()); + // bot.load_module(module.clone()).await; + // } + let modl = bot.get_module(modulename).await.unwrap(); + bot.disable_module(msg.channel_login.clone(), modl.get_names().first().unwrap().clone()).await; + println!("Debug disabled for channel {}",msg.channel_login); + } + Result::Err("Not Valid message type".to_string()) + } + + /* 3. Set Command flags */ + cmd.set_exec_fn(execution_async(execbody)); + cmd.set_admin_only(true); + // cmd.set_min_badge(Badge::Moderator); + cmd.set_admin_override(true); + + cmd +} + +fn create_listener_module(name:String) -> Module { + + let mut custom_mod = Module::new( + vec![name], + "".to_string()); + // dbg!("debug listener module created"); + custom_mod.load_listener(cmd_debug_listener()); + custom_mod.set_status_by_default(Status::Disabled); + + custom_mod + +} + +/// Listener for debug +fn cmd_debug_listener() -> Listener { + // dbg!("Creating debug listener"); + /* 2a. Create a new blank Listener */ + let mut listener = Listener::new(); + + /* 2b. Set a trigger condition function for listener */ + listener.set_trigger_cond_fn( + |_:Arc<Bot>,_:ServerMessage| true + ); + + /* 2c. Define an async fn callback execution */ + async fn execbody(_:Arc<Bot>,message:ServerMessage) -> Result<String,String> { + if let ServerMessage::Privmsg(msg) = message { + dbg!(msg); /* outputs message to debug */ + } + Result::Err("Not Valid message type".to_string()) + } + + /* 2d. Set and Store the execution body using `execution_async()` */ + listener.set_exec_fn(execution_async(execbody)); + + listener +} + diff --git a/forcebot_core/src/custom_mods/guest_badge.rs b/forcebot_core/src/custom_mods/guest_badge.rs index ee1eb16..8577bf9 100644 --- a/forcebot_core/src/custom_mods/guest_badge.rs +++ b/forcebot_core/src/custom_mods/guest_badge.rs @@ -7,7 +7,7 @@ use crate::{execution_async, Badge, Bot, Command, Module}; /// guest_badge / guest module /// /// Temporary badges can be issued to chatters. The bot then opens functionality -/// to that chatter baded on the recognized role +/// to that chatter based on the recognized role /// /// Chatters with real badge roles will be able to share guest /// badges based on their role diff --git a/forcebot_core/src/custom_mods/pyramid.rs b/forcebot_core/src/custom_mods/pyramid.rs index 3a3de01..0ea950b 100644 --- a/forcebot_core/src/custom_mods/pyramid.rs +++ b/forcebot_core/src/custom_mods/pyramid.rs @@ -149,9 +149,7 @@ fn read_top_of_compare(channel:String) -> Option<(String,String)> { return msg_stack.last().cloned(); } } - None - } fn pop_top_of_compare(channel:String) -> Option<(String,String)> { @@ -165,7 +163,6 @@ fn pop_top_of_compare(channel:String) -> Option<(String,String)> { return popped; } } - None } @@ -283,7 +280,7 @@ fn symmetry_ok(channel:String) -> bool { return false; } - } else { /* dbg!("failed catchall"); */ return false; } + } else { return false; } if checking_started && read_top_of_compare(channel.clone()).unwrap().1 == get_start_pattern(channel.clone()) { temp_stack.clear(); @@ -309,7 +306,7 @@ fn symmetry_ok(channel:String) -> bool { /// if a duration is given, take that duration eg 15m , 25m /// /// -fn create_interruptor_cmd() -> Command { +fn _create_interruptor_cmd() -> Command { let mut cmd = Command::new(vec![ "no pyramid".to_string(), "no pyramids".to_string() @@ -339,7 +336,7 @@ fn create_interruptor_cmd() -> Command { } /// #todo -fn create_interruptor_module(channel:String) -> Module { +fn _create_interruptor_module(channel:String) -> Module { /* 1. Create a new module */ let modname = format!("interruptor {}",channel); let mut custom_mod = Module::new( @@ -347,14 +344,14 @@ fn create_interruptor_module(channel:String) -> Module { "".to_string()); /* 2. Load the cmd into a new module */ - custom_mod.load_listener(create_interruptor_listener()); + custom_mod.load_listener(_create_interruptor_listener()); custom_mod } /// #todo -fn create_interruptor_listener() -> Listener { +fn _create_interruptor_listener() -> Listener { /* 2a. Create a new blank Listener */ let mut listener = Listener::new(); @@ -365,7 +362,7 @@ fn create_interruptor_listener() -> Listener { /* 2c. Define an async fn callback execution */ async fn execbody(_:Arc<Bot>,message:ServerMessage) -> Result<String,String> { - dbg!(message); + dbg!(message); /* outputs message to debug */ Result::Ok("Success".to_string()) } @@ -379,6 +376,6 @@ fn create_interruptor_listener() -> Listener { /// #todo /// /// Returns Some(chatter) if the pyramid in progress is being built by a solo -fn solo_building(channel:String) -> Option<String> { +fn _solo_building(_channel:String) -> Option<String> { None } diff --git a/forcebot_core/src/lib.rs b/forcebot_core/src/lib.rs index 8ccc272..f33057e 100644 --- a/forcebot_core/src/lib.rs +++ b/forcebot_core/src/lib.rs @@ -147,7 +147,7 @@ //! //! /* 2c. Define an async fn callback execution */ //! async fn execbody(_:Arc<Bot>,message:ServerMessage) -> Result<String,String> { -//! dbg!(message); +//! dbg!(message); /* outputs message to debug */ //! Result::Ok("Success".to_string()) //! } //! @@ -190,7 +190,6 @@ //! listener.set_trigger_cond_fn( //! |_:Arc<Bot>,message:ServerMessage| //! if let ServerMessage::Privmsg(msg) = message { -//! // dbg!(msg.clone()); //! for badge in msg.badges { //! if matches!(badge, x if x.name == "moderator") { //! // dbg!("moderator found"); diff --git a/moderator_reactor/src/main.rs b/moderator_reactor/src/main.rs index 58c0c9f..528b62e 100644 --- a/moderator_reactor/src/main.rs +++ b/moderator_reactor/src/main.rs @@ -23,7 +23,7 @@ use twitch_irc::message::ServerMessage; pub async fn main() { /* Create the bot using env */ - let mut bot = Bot::new(); + let bot = Bot::new().await; /* 1. Create a new blank Listener */ let mut listener = Listener::new(); @@ -33,7 +33,6 @@ pub async fn main() { listener.set_trigger_cond_fn( |_:Arc<Bot>,message:ServerMessage| if let ServerMessage::Privmsg(msg) = message { - // dbg!(msg.clone()); for badge in msg.badges { if matches!(badge, x if x.name == "moderator") { // dbg!("moderator found"); @@ -57,7 +56,7 @@ pub async fn main() { listener.set_exec_fn(execution_async(execbody)); /* 5. Load the listener into the bot */ - bot.load_listener(listener); + bot.load_listener(listener).await; /* Run the bot */ bot.run().await; diff --git a/new_empty_bot/src/main.rs b/new_empty_bot/src/main.rs index 4abdbc7..a81462e 100644 --- a/new_empty_bot/src/main.rs +++ b/new_empty_bot/src/main.rs @@ -16,7 +16,7 @@ use forcebot_core::Bot; pub async fn main() { /* 1. Create the bot using env */ - let bot = Bot::new(); + let bot = Bot::new().await; /* 2. Run the bot */ bot.run().await; diff --git a/readme.md b/readme.md index d94791a..c4e81d0 100644 --- a/readme.md +++ b/readme.md @@ -30,6 +30,7 @@ cargo run -p forcebot_core - Quick Start to use full feature set bot - Moderators & Broadcasters can `disable` or `enable` `Modules` of bot functionality through chat `Commands` - Full Feature Set `forcebot_core` bot has the following modules loaded + - `debug` - outputs to console messages from the channel where it was enabled. Toggle debug with the Commands `debug on` or `debug off` - `guest_badge` - Temporary badges can be issued to chatters - `besty` - Tomfoolery - `pyramid` - for detecting & handling pyramids @@ -280,7 +281,7 @@ pub async fn main() { /* 2c. Define an async fn callback execution */ async fn execbody(_:Arc<Bot>,message:ServerMessage) -> Result<String,String> { - dbg!(message); + dbg!(message); /* outputs message to debug */ Result::Ok("Success".to_string()) } @@ -323,7 +324,7 @@ pub async fn main() { listener.set_trigger_cond_fn( |_:Arc<Bot>,message:ServerMessage| if let ServerMessage::Privmsg(msg) = message { - // dbg!(msg.clone()); + for badge in msg.badges { if matches!(badge, x if x.name == "moderator") { // dbg!("moderator found"); diff --git a/simple_command_bot/src/main.rs b/simple_command_bot/src/main.rs index 2f29e09..3806e7f 100644 --- a/simple_command_bot/src/main.rs +++ b/simple_command_bot/src/main.rs @@ -26,7 +26,7 @@ use twitch_irc::message::ServerMessage; pub async fn main() { /* Create the bot using env */ - let mut bot = Bot::new(); + let mut bot = Bot::new().await; /* 1. Create a new blank cmd */ let mut cmd = Command::new(vec!["test".to_string()],"".to_string()); diff --git a/simple_debug_listener/src/main.rs b/simple_debug_listener/src/main.rs index 3ce82fc..27ab37e 100644 --- a/simple_debug_listener/src/main.rs +++ b/simple_debug_listener/src/main.rs @@ -19,7 +19,7 @@ use twitch_irc::message::ServerMessage; pub async fn main() { /* 1. Create the bot using env */ - let mut bot = Bot::new(); + let bot = Bot::new().await; /* 2a. Create a new blank Listener */ let mut listener = Listener::new(); @@ -31,7 +31,7 @@ pub async fn main() { /* 2c. Define an async fn callback execution */ async fn execbody(_:Arc<Bot>,message:ServerMessage) -> Result<String,String> { - dbg!(message); + dbg!(message); /* outputs message to debug */ Result::Ok("Success".to_string()) } @@ -39,7 +39,7 @@ pub async fn main() { listener.set_exec_fn(execution_async(execbody)); /* 3. Load the listener into the bot */ - bot.load_listener(listener); + bot.load_listener(listener).await; /* 4. Run the bot */ bot.run().await; diff --git a/simple_module_example/src/main.rs b/simple_module_example/src/main.rs index 4793ed4..3851d34 100644 --- a/simple_module_example/src/main.rs +++ b/simple_module_example/src/main.rs @@ -23,7 +23,7 @@ use forcebot_core::Bot; pub async fn main() { /* Create the bot using env */ - let mut bot = Bot::new(); + let bot = Bot::new().await; /* load the Module */ bot.load_module(custom_mod::new()).await; -- 2.49.0 From 5faf982485a5770fba143fd0bb747b52a7cdca3e Mon Sep 17 00:00:00 2001 From: modulatingforce <modulatingforce@gmail.com> Date: Tue, 4 Feb 2025 00:20:36 -0500 Subject: [PATCH 12/14] quiet module --- forcebot_core/src/bin/fun_bot.rs | 10 ++- forcebot_core/src/botcore/bot.rs | 56 +++++++++---- .../src/botcore/bot_objects/command.rs | 13 ++- .../src/botcore/bot_objects/listener.rs | 38 ++++++++- forcebot_core/src/botcore/modules.rs | 7 +- forcebot_core/src/custom_mods.rs | 3 +- forcebot_core/src/custom_mods/debug.rs | 8 +- forcebot_core/src/custom_mods/quiet.rs | 84 +++++++++++++++++++ simple_command_bot/src/main.rs | 4 +- 9 files changed, 183 insertions(+), 40 deletions(-) create mode 100644 forcebot_core/src/custom_mods/quiet.rs diff --git a/forcebot_core/src/bin/fun_bot.rs b/forcebot_core/src/bin/fun_bot.rs index 872aff9..483177c 100644 --- a/forcebot_core/src/bin/fun_bot.rs +++ b/forcebot_core/src/bin/fun_bot.rs @@ -1,9 +1,10 @@ //! WIP Fun forcebot with catered customizations #todo //! //! Custom modules that can be managed in chat through `disable` and `enable` commands -//! - `besty` +//! - `besty` - uses a custom prefix tp trigger //! - `guests` //! - `pyramid` +//! - `quiet` //! //! //! Be sure the followig is defined in `.env` @@ -18,7 +19,7 @@ //! - More Info - <https://dev.twitch.tv/docs/authentication> // use forcebot_rs_v2::{custom_mods::{guest_badge, pyramid}, Bot}; -use forcebot_core::{custom_mods::{debug, guest_badge, pyramid}, Bot}; +use forcebot_core::{custom_mods::{debug, guest_badge, pyramid, quiet}, Bot}; @@ -35,6 +36,7 @@ pub async fn main() { bot.load_module(guest_badge::create_module()).await; bot.load_module(pyramid::create_module()).await; bot.load_module(debug::create_module()).await; + bot.load_module(quiet::create_module()).await; /* 3. Run the bot */ bot.run().await; @@ -45,7 +47,7 @@ pub async fn main() { pub mod funbot_objs { use std::sync::Arc; - use forcebot_core::{execution_async, modules::Status, Badge, Bot, Command, Module}; + use forcebot_core::{execution_async, Badge, Bot, Command, Module}; use twitch_irc::message::ServerMessage; /// Create a Module with a loaded Command object @@ -55,7 +57,7 @@ pub mod funbot_objs { "Now Aware of besty xdd666 ".to_string()); custom_mod.load_command(create_cmd_test()); - custom_mod.set_status_by_default(Status::Disabled); + // custom_mod.set_status_by_default(Status::Disabled); custom_mod } diff --git a/forcebot_core/src/botcore/bot.rs b/forcebot_core/src/botcore/bot.rs index 9e3070f..e8a4d04 100644 --- a/forcebot_core/src/botcore/bot.rs +++ b/forcebot_core/src/botcore/bot.rs @@ -41,7 +41,9 @@ pub struct Bot chatter_guest_badges: Mutex<Vec<(String,String,Badge,Instant,Duration)>>, /// Message cache message_cache: Mutex<Vec<PrivmsgMessage>>, - // message_cache: Vec<PrivmsgMessage> + /// channel_quiet + channel_quiet_yn: RwLock<Vec<(String,RwLock<bool>)>>, + } @@ -118,6 +120,7 @@ impl Bot channel_module_status: RwLock::new(vec![]), chatter_guest_badges: Mutex::new(vec![]), message_cache : Mutex::new(vec![]), + channel_quiet_yn : RwLock::new(vec![]), }; for cmd in built_in_objects::create_commands() { @@ -183,14 +186,13 @@ impl Bot // dbg!("try cms read"); let cms_lock = bot.channel_module_status.read().unwrap(); - // dbg!("cms read lock"); - // dbg!(module.get_names()); + for channel_flags in cms_lock.iter() { if channel_flags.0 == channel { if module.get_names().contains(&channel_flags.1) && channel_flags.2 == Status::Disabled { continue 'module_loop; - // disabled_mods.push(module); + } } } @@ -202,7 +204,6 @@ impl Bot } - for module in get_enabled_channel_modules(bot.clone(), msg.clone().channel_login) { @@ -224,12 +225,8 @@ impl Bot } } - - - } else {} ; - } drop(in_msgs_lock); }); @@ -360,13 +357,11 @@ impl Bot if !found_disabled { - // self.channel_module_status. - // dbg!("try cms write"); /*,self.channel_module_status */ + let mut cms_lock = self.channel_module_status.write().unwrap(); - // cms_lock. - // dbg!("cms write lock"); + cms_lock.push((channel,module.clone(),Status::Disabled)); - // dbg!(module.clone()); + drop(cms_lock); @@ -434,6 +429,39 @@ impl Bot rslt } + /// Get the quiet status of a channel + pub fn get_channel_quiet(&self,channel:String) -> bool { + for a in self.channel_quiet_yn.read().unwrap().iter() { + if a.0 == channel { + return a.1.read().unwrap().clone(); + } + } + return false; + } + + /// Get the quiet status of a channel + pub fn set_channel_quiet(&self,channel:String,quiet_on:bool) { + let mut found = false; + + let chnlquiet = self.channel_quiet_yn.read().unwrap(); + for rec in chnlquiet.iter() { + if rec.0 == channel { + found = true; + let mut status = rec.1.write().unwrap(); + *status = quiet_on; + drop(status); + } + } + drop(chnlquiet); + + if !found { + // dbg!("set chn quiet > !found channel quiet status"); + let mut chnlquiet = self.channel_quiet_yn.write().unwrap(); + chnlquiet.push((channel,RwLock::new(quiet_on))); + drop(chnlquiet); + } + + } } diff --git a/forcebot_core/src/botcore/bot_objects/command.rs b/forcebot_core/src/botcore/bot_objects/command.rs index 54c4b85..7095ca5 100644 --- a/forcebot_core/src/botcore/bot_objects/command.rs +++ b/forcebot_core/src/botcore/bot_objects/command.rs @@ -188,18 +188,17 @@ impl Command (cmd.custom_cond_fn)(bot.clone(),message.clone()) && (cmd.custom_cond_async)(bot,message).await } - // dbg!(msg.clone()); + fn quiet_off_ok(cmd:&Command,bot:Arc<Bot>,message:PrivmsgMessage) -> bool { + !bot.get_channel_quiet(message.channel_login.clone()) + || bot.get_channel_quiet(message.channel_login.clone()) && cmd.commands.contains(&("quiet off".to_string())) + } - // dbg!(caller_badge_ok(self, bot.clone(), msg.clone())); - // dbg!(cmd_called(self, bot.clone(), msg.clone()) && - // caller_badge_ok(self, bot.clone(), msg.clone()) && - // admin_only_ok(self, bot.clone(), msg.clone()) && - // custom_cond_ok(self, bot.clone(), msg.clone())); cmd_called(self, bot.clone(), msg.clone()) && caller_badge_ok(self, bot.clone(), msg.clone()).await && admin_only_ok(self, bot.clone(), msg.clone()) && - custom_cond_ok(self, bot, msg).await + custom_cond_ok(self, bot.clone(), msg.clone()).await && + quiet_off_ok(self, bot, msg) } diff --git a/forcebot_core/src/botcore/bot_objects/listener.rs b/forcebot_core/src/botcore/bot_objects/listener.rs index 84bd468..2895acb 100644 --- a/forcebot_core/src/botcore/bot_objects/listener.rs +++ b/forcebot_core/src/botcore/bot_objects/listener.rs @@ -1,7 +1,10 @@ use std::sync::Arc; +// use tokio::sync::Mutex; use twitch_irc::message::ServerMessage; +use crate::Module; + use super::{execution_async,Bot,listener_condition_async}; use super::{ExecBody, ListenerTrigger}; @@ -27,6 +30,7 @@ pub struct Listener trigger_cond_async : Arc<ListenerTrigger> , /// execution body exec_fn : Arc<ExecBody>, + parent_module : Arc<Option<Module>>, } impl Listener @@ -50,6 +54,7 @@ impl Listener trigger_cond_fn : |_:Arc<Bot>,_:ServerMessage| true, trigger_cond_async : Arc::new(listener_condition_async(condition01)), exec_fn : Arc::new(execution_async(execbody)), + parent_module : Arc::new(None), } } @@ -103,14 +108,41 @@ impl Listener /// checks if the trigger condition is met pub async fn cond_triggered(&self,bot:Arc<Bot>,msg:ServerMessage) -> bool { - let list = Arc::new(self); - (list.trigger_cond_fn)(bot.clone(),msg.clone()) && (list.trigger_cond_async)(bot,msg).await + + let list = Arc::new(self.clone()); + + async fn defined_conditions_ok(list:Arc<Listener>,bot:Arc<Bot>,msg:ServerMessage) -> bool { + // let list = Arc::new(self); + (list.trigger_cond_fn)(bot.clone(),msg.clone()) && (list.trigger_cond_async)(bot,msg).await + } + + fn quiet_off_ok(list:Arc<Listener>,bot:Arc<Bot>,message:ServerMessage) -> bool { + if let ServerMessage::Privmsg(msg) = message { + + if let Some(parent_mod) = &*list.parent_module { + return !bot.get_channel_quiet(msg.channel_login) || parent_mod.get_names().contains(&"debug".to_string()); + } + + return !bot.get_channel_quiet(msg.channel_login) ; + } + return true; /* quiet is off for non chat msgs */ + } + + defined_conditions_ok(list.clone(), bot.clone(), msg.clone()).await && + quiet_off_ok(list, bot, msg) } /// executes the listeners executon body pub async fn execute_fn(&self,bot:Arc<Bot>,msg:ServerMessage) -> Result<String, String> { - // (self.exec_fn)(bot,msg) + (self.exec_fn)(bot,msg).await } + + /// sets parent module + pub fn set_parent_module(&mut self,module:Module) { + self.parent_module = Arc::new(Some(module)); + } + + } diff --git a/forcebot_core/src/botcore/modules.rs b/forcebot_core/src/botcore/modules.rs index 889cf9e..d3802b5 100644 --- a/forcebot_core/src/botcore/modules.rs +++ b/forcebot_core/src/botcore/modules.rs @@ -1,4 +1,6 @@ +// use std::sync::{Arc, Mutex}; + use super::bot_objects::command::Command; use super::bot_objects::listener::Listener; @@ -40,13 +42,14 @@ impl Module } /// Loads a `Listener` into the module - pub fn load_listener(&mut self,l : Listener) { + pub fn load_listener(&mut self,mut l : Listener) { + l.set_parent_module(self.clone()); self.listeners.push(l); } /// Loads a `Command` into the module pub fn load_command(&mut self,c : Command) { - self.commands.push(c); + self.commands.push(c); } pub fn get_listeners(&self) -> Vec<Listener> { diff --git a/forcebot_core/src/custom_mods.rs b/forcebot_core/src/custom_mods.rs index f295b1e..49848a1 100644 --- a/forcebot_core/src/custom_mods.rs +++ b/forcebot_core/src/custom_mods.rs @@ -1,3 +1,4 @@ pub mod guest_badge; pub mod pyramid; -pub mod debug; \ No newline at end of file +pub mod debug; +pub mod quiet; \ No newline at end of file diff --git a/forcebot_core/src/custom_mods/debug.rs b/forcebot_core/src/custom_mods/debug.rs index df5b321..4bbfc73 100644 --- a/forcebot_core/src/custom_mods/debug.rs +++ b/forcebot_core/src/custom_mods/debug.rs @@ -1,15 +1,9 @@ -use std::sync::{Arc}; +use std::sync::Arc; use crate::{execution_async, modules::Status, Bot, Command, Listener, Module}; use twitch_irc::message::ServerMessage; -// use lazy_static::lazy_static; - -// lazy_static!{ -// /// Listener per channel (channel:String,Listener) -// pub static ref LISTENER_PER_CHNL: Mutex<Vec<(String,Mutex<Listener>)>> = Mutex::new(vec![]); -// } /// debug module /// diff --git a/forcebot_core/src/custom_mods/quiet.rs b/forcebot_core/src/custom_mods/quiet.rs new file mode 100644 index 0000000..a9827f6 --- /dev/null +++ b/forcebot_core/src/custom_mods/quiet.rs @@ -0,0 +1,84 @@ +use std::sync::Arc; + +use twitch_irc::message::ServerMessage; + +use crate::{execution_async, Badge, Bot, Command, Module}; + + +/// quiet the bot in a channel +/// +/// use +/// `quiet on` +/// `quiet off` +/// +/// +/// + +/// Use this function when loading modules into the bot +/// +/// For example +/// ```rust +/// bot.load_module(quiet::create_module()); +/// ``` +/// +pub fn create_module() -> Module { + /* 1. Create a new module */ + let mut custom_mod = Module::new( + vec!["quiet".to_string()], + "".to_string()); + + /* 2. Load the cmd into a new module */ + custom_mod.load_command(cmd_quiet_on()); + custom_mod.load_command(cmd_quiet_off()); + custom_mod + +} + +/// Command definition for quiet command +fn cmd_quiet_on() -> Command { + /* 1. Create a new cmd */ + let mut cmd = Command::new(vec!["quiet on".to_string()],"".to_string()); + + /* 2. Define exec callback */ + async fn execbody(bot:Arc<Bot>,message:ServerMessage) -> Result<String,String> { + if let ServerMessage::Privmsg(msg) = message { + + // dbg!("quiet on called"); + bot.set_channel_quiet(msg.channel_login.clone(), true); + println!("channel {} set quiet true",msg.channel_login); + } + Result::Err("Not Valid message type".to_string()) + } + + /* 3. Set Command flags */ + cmd.set_exec_fn(execution_async(execbody)); + cmd.set_admin_only(false); + cmd.set_min_badge(Badge::Moderator); + cmd.set_admin_override(true); + + cmd +} + +/// Command definition for quiet command +fn cmd_quiet_off() -> Command { + /* 1. Create a new cmd */ + let mut cmd = Command::new(vec!["quiet off".to_string()],"".to_string()); + + /* 2. Define exec callback */ + async fn execbody(bot:Arc<Bot>,message:ServerMessage) -> Result<String,String> { + if let ServerMessage::Privmsg(msg) = message { + + bot.set_channel_quiet(msg.channel_login.clone(), false); + println!("channel {} set quiet false",msg.channel_login); + } + Result::Err("Not Valid message type".to_string()) + } + + /* 3. Set Command flags */ + cmd.set_exec_fn(execution_async(execbody)); + cmd.set_admin_only(false); + cmd.set_min_badge(Badge::Moderator); + cmd.set_admin_override(true); + + cmd +} diff --git a/simple_command_bot/src/main.rs b/simple_command_bot/src/main.rs index 3806e7f..f231f0c 100644 --- a/simple_command_bot/src/main.rs +++ b/simple_command_bot/src/main.rs @@ -26,7 +26,7 @@ use twitch_irc::message::ServerMessage; pub async fn main() { /* Create the bot using env */ - let mut bot = Bot::new().await; + let bot = Bot::new().await; /* 1. Create a new blank cmd */ let mut cmd = Command::new(vec!["test".to_string()],"".to_string()); @@ -50,7 +50,7 @@ pub async fn main() { cmd.set_min_badge(Badge::Moderator); /* 6. Load the cmd into the bot */ - bot.load_command(cmd); + bot.load_command(cmd).await; /* Run the bot */ bot.run().await; -- 2.49.0 From 8eaa56dd0c04da7d16d2adbf32d336068601568a Mon Sep 17 00:00:00 2001 From: modulatingforce <modulatingforce@gmail.com> Date: Thu, 6 Feb 2025 09:35:39 -0500 Subject: [PATCH 13/14] enh pyramid + chat module --- forcebot_core/Cargo.toml | 1 + forcebot_core/src/bin/fun_bot.rs | 9 +- .../src/bin/simple_debug_listener.rs | 47 ------ forcebot_core/src/bin/simple_module.rs | 2 +- forcebot_core/src/botcore.rs | 4 +- forcebot_core/src/botcore/bot.rs | 120 +++++++++----- forcebot_core/src/botcore/bot_objects.rs | 25 ++- .../src/botcore/bot_objects/command.rs | 15 +- .../src/botcore/bot_objects/listener.rs | 25 +-- forcebot_core/src/botcore/built_in_mods.rs | 14 ++ .../built_in_mods}/quiet.rs | 16 +- forcebot_core/src/botcore/chat.rs | 151 ++++++++++++++++++ forcebot_core/src/custom_mods.rs | 2 +- forcebot_core/src/custom_mods/guest_badge.rs | 4 +- forcebot_core/src/custom_mods/pyramid.rs | 101 +++++++++++- moderator_reactor/src/main.rs | 2 +- readme.md | 89 +++++++---- simple_command_bot/src/main.rs | 2 +- simple_module_example/src/main.rs | 2 +- 19 files changed, 455 insertions(+), 176 deletions(-) delete mode 100644 forcebot_core/src/bin/simple_debug_listener.rs create mode 100644 forcebot_core/src/botcore/built_in_mods.rs rename forcebot_core/src/{custom_mods => botcore/built_in_mods}/quiet.rs (79%) create mode 100644 forcebot_core/src/botcore/chat.rs diff --git a/forcebot_core/Cargo.toml b/forcebot_core/Cargo.toml index 082c28b..70ac147 100644 --- a/forcebot_core/Cargo.toml +++ b/forcebot_core/Cargo.toml @@ -5,6 +5,7 @@ edition = "2021" default-run = "fun_bot" [dependencies] +# async-recursion = "1.1.1" /* has issues when used */ dotenv = "0.15.0" lazy_static = "1.5.0" tokio = { version = "1.33.0", features = ["full"] } diff --git a/forcebot_core/src/bin/fun_bot.rs b/forcebot_core/src/bin/fun_bot.rs index 483177c..abbc426 100644 --- a/forcebot_core/src/bin/fun_bot.rs +++ b/forcebot_core/src/bin/fun_bot.rs @@ -18,8 +18,7 @@ //! - Get a Bot Chat Token here - <https://twitchtokengenerator.com> //! - More Info - <https://dev.twitch.tv/docs/authentication> -// use forcebot_rs_v2::{custom_mods::{guest_badge, pyramid}, Bot}; -use forcebot_core::{custom_mods::{debug, guest_badge, pyramid, quiet}, Bot}; +use forcebot_core::{custom_mods::{debug, guest_badge, pyramid}, Bot}; @@ -31,12 +30,10 @@ pub async fn main() { /* 1. Load the module into the bot */ bot.load_module(funbot_objs::create_module()).await; - - /* 2. Load Custom Modules */ bot.load_module(guest_badge::create_module()).await; bot.load_module(pyramid::create_module()).await; bot.load_module(debug::create_module()).await; - bot.load_module(quiet::create_module()).await; + /* 3. Run the bot */ bot.run().await; @@ -69,7 +66,7 @@ pub mod funbot_objs { async fn execbody(bot:Arc<Bot>,message:ServerMessage) -> Result<String,String> { if let ServerMessage::Privmsg(msg) = message { - let _= bot.client.say_in_reply_to( + let _= bot.chat.lock().await.say_in_reply_to( &msg, "annytfYandere he's mine".to_string()).await; return Result::Ok("Success".to_string()); } diff --git a/forcebot_core/src/bin/simple_debug_listener.rs b/forcebot_core/src/bin/simple_debug_listener.rs deleted file mode 100644 index bac75ed..0000000 --- a/forcebot_core/src/bin/simple_debug_listener.rs +++ /dev/null @@ -1,47 +0,0 @@ -//! Example simple Binary crate that creates & runs bot based on `.env` -//! Be sure the followig is defined in `.env` -//! - login_name -//! - access_token -//! - bot_channels -//! - prefix -//! - bot_admins -//! -//! Bot access tokens be generated here - -//! - Get a Bot Chat Token here - <https://twitchtokengenerator.com> -//! - More Info - <https://dev.twitch.tv/docs/authentication> - -use std::sync::Arc; - -use forcebot_core::{execution_async, Bot, Listener}; -use twitch_irc::message::ServerMessage; - -#[tokio::main] -pub async fn main() { - - /* 1. Create the bot using env */ - let bot = Bot::new().await; - - /* 2a. Create a new blank Listener */ - let mut listener = Listener::new(); - - /* 2b. Set a trigger condition function for listener */ - listener.set_trigger_cond_fn( - |_:Arc<Bot>,_:ServerMessage| true - ); - - /* 2c. Define an async fn callback execution */ - async fn execbody(_:Arc<Bot>,message:ServerMessage) -> Result<String,String> { - dbg!(message); /* outputs message to debug */ - Result::Ok("Success".to_string()) - } - - /* 2d. Set and Store the execution body using `async_box()` */ - listener.set_exec_fn(execution_async(execbody)); - - /* 3. Load the listener into the bot */ - bot.load_listener(listener).await; - - /* 4. Run the bot */ - bot.run().await; - -} diff --git a/forcebot_core/src/bin/simple_module.rs b/forcebot_core/src/bin/simple_module.rs index 45bf06d..3e36d90 100644 --- a/forcebot_core/src/bin/simple_module.rs +++ b/forcebot_core/src/bin/simple_module.rs @@ -63,7 +63,7 @@ pub mod custom_mod { /* 2. Define exec callback */ async fn execbody(bot:Arc<Bot>,message:ServerMessage) -> Result<String,String> { if let ServerMessage::Privmsg(msg) = message { - let _= bot.client.say_in_reply_to( + let _= bot.chat.lock().await.say_in_reply_to( &msg, "test return".to_string()).await; } Result::Err("Not Valid message type".to_string()) diff --git a/forcebot_core/src/botcore.rs b/forcebot_core/src/botcore.rs index 678d9a9..99449f0 100644 --- a/forcebot_core/src/botcore.rs +++ b/forcebot_core/src/botcore.rs @@ -1,3 +1,5 @@ pub mod bot; pub mod bot_objects; -pub mod modules; \ No newline at end of file +pub mod modules; +pub mod built_in_mods; +pub mod chat; \ No newline at end of file diff --git a/forcebot_core/src/botcore/bot.rs b/forcebot_core/src/botcore/bot.rs index e8a4d04..cccc61f 100644 --- a/forcebot_core/src/botcore/bot.rs +++ b/forcebot_core/src/botcore/bot.rs @@ -1,14 +1,15 @@ +// use async_recursion::async_recursion; use tokio::sync::{mpsc::UnboundedReceiver, Mutex}; use twitch_irc::{login::StaticLoginCredentials, message::{PrivmsgMessage, ServerMessage}, SecureTCPTransport, TwitchIRCClient}; use dotenv::dotenv; use std::{env, sync::{Arc, RwLock}, time::{Duration, Instant}}; // use crate::{Badge, Command, Listener, Module}; -use super::bot_objects::command::Command; +use super::{bot_objects::command::Command, built_in_mods, chat::Chat}; -use crate::botcore::bot_objects::Badge; +use crate::botcore::{bot_objects::Badge, chat}; use crate::botcore::bot_objects::listener::Listener; use super::super::botcore::modules::Module; // use super:: @@ -25,6 +26,8 @@ pub struct Bot incoming_msgs: Mutex<UnboundedReceiver<ServerMessage>>, /// outbound chat client msg stream pub client: TwitchIRCClient<SecureTCPTransport,StaticLoginCredentials>, + /// *preferred* bot enforced outbound chat client msg stream + pub chat : Mutex<Chat>, /// joined channels botchannels: Vec<String>, /// admin chatters @@ -41,8 +44,8 @@ pub struct Bot chatter_guest_badges: Mutex<Vec<(String,String,Badge,Instant,Duration)>>, /// Message cache message_cache: Mutex<Vec<PrivmsgMessage>>, - /// channel_quiet - channel_quiet_yn: RwLock<Vec<(String,RwLock<bool>)>>, + // /// channel_quiet + // channel_quiet_yn: RwLock<Vec<(String,RwLock<bool>)>>, } @@ -57,7 +60,8 @@ impl Bot /// - bot_channels /// - prefix /// - bot_admins - pub async fn new() -> Bot { + // #[async_recursion] + pub async fn new() -> Arc<Bot> { @@ -82,7 +86,10 @@ impl Bot /// Creates a new `Bot` using bot information /// /// Bot will join `botchannels` argument - pub async fn new_from(bot_login_name:String,oauth_token:String,prefix:String,botchannels:Vec<String>) -> Bot { + pub async fn new_from(bot_login_name:String, + oauth_token:String, + prefix:String, + botchannels:Vec<String>) -> Arc<Bot> { dotenv().ok(); let bot_login_name = bot_login_name; @@ -111,7 +118,8 @@ impl Bot let bot = Bot { prefix, incoming_msgs : Mutex::new(incoming_messages), - client, + client : client.clone(), + chat : Mutex::new(Chat::new(client).await), botchannels : botchannels_all, listeners : Mutex::new(vec![]), commands : Mutex::new(vec![]), @@ -120,31 +128,61 @@ impl Bot channel_module_status: RwLock::new(vec![]), chatter_guest_badges: Mutex::new(vec![]), message_cache : Mutex::new(vec![]), - channel_quiet_yn : RwLock::new(vec![]), + // channel_quiet_yn : RwLock::new(vec![]), }; + async fn load_modules(bot:Bot) -> Bot { + // let mut bot1 = bot; + + // bot1.chat = Some(Chat::new(client, bot1)); + for cmd in built_in_objects::create_commands() { - bot.load_command(cmd).await; + bot.load_command(cmd).await; } + built_in_mods::load_built_in_mods(&bot).await; bot + + } + + let bot = load_modules(bot).await; + + + let bot = Arc::new(bot); + + // let lock = bot.chat.lock().await; + + // *lock = Some(Chat::new(chat, bot.clone())); + + // let cht = Chat::new(chat).await; + + // bot.chat.lock() + + // lock.set_parent_bot(bot.clone()); + + println!("Joined - {:?}",bot.botchannels); + + + bot.clone() } /// Runs the bot - pub async fn run(self) { + pub async fn run(self:Arc<Self>) { for chnl in &self.botchannels { self.client.join(chnl.to_owned()).unwrap(); } - let bot = Arc::new(self); + // let bot = Arc::new(self); + let bot = self; let join_handle = tokio::spawn(async move { let a = bot.clone(); let mut in_msgs_lock = a.incoming_msgs.lock().await; - while let Some(message) = in_msgs_lock.recv().await { + while let Some(message) = in_msgs_lock.recv().await { + // dbg!(message.clone()) ; let bot_listener_lock = bot.listeners.lock().await; for listener in bot_listener_lock.iter() { @@ -429,39 +467,39 @@ impl Bot rslt } - /// Get the quiet status of a channel - pub fn get_channel_quiet(&self,channel:String) -> bool { - for a in self.channel_quiet_yn.read().unwrap().iter() { - if a.0 == channel { - return a.1.read().unwrap().clone(); - } - } - return false; - } + // /// Get the quiet status of a channel + // pub fn get_channel_quiet(&self,channel:String) -> bool { + // for a in self.channel_quiet_yn.read().unwrap().iter() { + // if a.0 == channel { + // return a.1.read().unwrap().clone(); + // } + // } + // return false; + // } - /// Get the quiet status of a channel - pub fn set_channel_quiet(&self,channel:String,quiet_on:bool) { - let mut found = false; + // /// Get the quiet status of a channel + // pub fn set_channel_quiet(&self,channel:String,quiet_on:bool) { + // let mut found = false; - let chnlquiet = self.channel_quiet_yn.read().unwrap(); - for rec in chnlquiet.iter() { - if rec.0 == channel { - found = true; - let mut status = rec.1.write().unwrap(); - *status = quiet_on; - drop(status); - } - } - drop(chnlquiet); + // let chnlquiet = self.channel_quiet_yn.read().unwrap(); + // for rec in chnlquiet.iter() { + // if rec.0 == channel { + // found = true; + // let mut status = rec.1.write().unwrap(); + // *status = quiet_on; + // drop(status); + // } + // } + // drop(chnlquiet); - if !found { - // dbg!("set chn quiet > !found channel quiet status"); - let mut chnlquiet = self.channel_quiet_yn.write().unwrap(); - chnlquiet.push((channel,RwLock::new(quiet_on))); - drop(chnlquiet); - } + // if !found { + // // dbg!("set chn quiet > !found channel quiet status"); + // let mut chnlquiet = self.channel_quiet_yn.write().unwrap(); + // chnlquiet.push((channel,RwLock::new(quiet_on))); + // drop(chnlquiet); + // } - } + // } } diff --git a/forcebot_core/src/botcore/bot_objects.rs b/forcebot_core/src/botcore/bot_objects.rs index b0d334d..dd4e6db 100644 --- a/forcebot_core/src/botcore/bot_objects.rs +++ b/forcebot_core/src/botcore/bot_objects.rs @@ -21,6 +21,7 @@ pub enum Badge { } + pub type ExecBody = Box< dyn Fn(Arc<Bot>,ServerMessage) -> Pin<Box<dyn Future<Output = Result<String,String>> + Send>> + Send + Sync, >; @@ -107,14 +108,6 @@ pub mod built_in_objects { use twitch_irc::message::ServerMessage; use super::{execution_async,command::Command,Bot,Badge,super::modules::Status}; - - // use super::execution_async; - // use super::command::Command; - // use super::Bot; - // use super::Badge; - // use super::super::modules::Status; - // ::{execution_async, modules::Status, Badge, Bot, Command}; - /// create a vector of command build in objects @@ -148,7 +141,7 @@ pub mod built_in_objects { } } if action_taken { - let _ = bot.client.say_in_reply_to(&msg, String::from("Disabled!")).await ; + let _ = bot.chat.lock().await.say_in_reply_to(&msg, String::from("Disabled!")).await ; } } Result::Err("Not Valid message type".to_string()) @@ -199,7 +192,7 @@ pub mod built_in_objects { bot_message = bot_message[..250].to_string(); } - let _ = bot.client.say_in_reply_to(&msg, + let _ = bot.chat.lock().await.say_in_reply_to(&msg, format!("Enabled! {}", bot_message) ).await ; @@ -257,7 +250,7 @@ pub mod built_in_objects { msg.channel_login.clone(), Badge::Moderator, Instant::now(), Duration::from_secs(60*TEMP_BADGE_DUR_MIN)).await; - let _ = bot.client.say_in_reply_to(&msg, + let _ = bot.chat.lock().await.say_in_reply_to(&msg, format!("Temp {:?} issued for {:?} minutes",Badge::Moderator,TEMP_BADGE_DUR_MIN) ).await ; } @@ -279,8 +272,8 @@ pub mod built_in_objects { msg.channel_login.clone(), Badge::Vip, Instant::now(), Duration::from_secs(60*TEMP_BADGE_DUR_MIN)).await; - let _ = bot.client.say_in_reply_to(&msg, - format!("Temp {:?} issued for {:?} minutes",Badge::Vip,TEMP_BADGE_DUR_MIN) + let _ = bot.chat.lock().await.say_in_reply_to(&msg, + format!("Temp {:?} issued for {:?} minutes for the bot admin",Badge::Vip,TEMP_BADGE_DUR_MIN) ).await ; } } @@ -300,14 +293,14 @@ pub mod built_in_objects { msg.channel_login.clone(), Badge::Broadcaster, Instant::now(), Duration::from_secs(60*TEMP_BADGE_DUR_MIN)).await; - let _ = bot.client.say_in_reply_to(&msg, - format!("Temp {:?} issued for {:?} minutes",Badge::Broadcaster,TEMP_BADGE_DUR_MIN) + let _ = bot.chat.lock().await.say_in_reply_to(&msg, + format!("Temp {:?} issued for {:?} minutes for the bot admin",Badge::Broadcaster,TEMP_BADGE_DUR_MIN) ).await ; } } } } - // let _ = bot.client.say_in_reply_to(&msg, String::from("Disabled!")).await ; + // let _ = bot.chat.lock().await.say_in_reply_to(&msg, String::from("Disabled!")).await ; } Result::Err("Not Valid message type".to_string()) } diff --git a/forcebot_core/src/botcore/bot_objects/command.rs b/forcebot_core/src/botcore/bot_objects/command.rs index 7095ca5..b72cc8a 100644 --- a/forcebot_core/src/botcore/bot_objects/command.rs +++ b/forcebot_core/src/botcore/bot_objects/command.rs @@ -2,7 +2,7 @@ use std::sync::Arc; use twitch_irc::message::{PrivmsgMessage, ServerMessage}; -// use crate::{botcore::bot::Bot, command_condition_async, execution_async, Badge}; + use super::{execution_async,Bot,Badge,command_condition_async}; @@ -188,17 +188,18 @@ impl Command (cmd.custom_cond_fn)(bot.clone(),message.clone()) && (cmd.custom_cond_async)(bot,message).await } - fn quiet_off_ok(cmd:&Command,bot:Arc<Bot>,message:PrivmsgMessage) -> bool { - !bot.get_channel_quiet(message.channel_login.clone()) - || bot.get_channel_quiet(message.channel_login.clone()) && cmd.commands.contains(&("quiet off".to_string())) - } + // async fn quiet_off_ok(cmd:&Command,bot:Arc<Bot>,message:PrivmsgMessage) -> bool { + // !bot.chat.lock().await.get_channel_quiet(message.channel_login.clone()) + // || bot.chat.lock().await.get_channel_quiet(message.channel_login.clone()) && cmd.commands.contains(&("quiet off".to_string())) + // } cmd_called(self, bot.clone(), msg.clone()) && caller_badge_ok(self, bot.clone(), msg.clone()).await && admin_only_ok(self, bot.clone(), msg.clone()) && - custom_cond_ok(self, bot.clone(), msg.clone()).await && - quiet_off_ok(self, bot, msg) + custom_cond_ok(self, bot.clone(), msg.clone()).await + // && + // quiet_off_ok(self, bot, msg).await } diff --git a/forcebot_core/src/botcore/bot_objects/listener.rs b/forcebot_core/src/botcore/bot_objects/listener.rs index 2895acb..24f7ce3 100644 --- a/forcebot_core/src/botcore/bot_objects/listener.rs +++ b/forcebot_core/src/botcore/bot_objects/listener.rs @@ -1,6 +1,6 @@ use std::sync::Arc; -// use tokio::sync::Mutex; + use twitch_irc::message::ServerMessage; use crate::Module; @@ -116,20 +116,21 @@ impl Listener (list.trigger_cond_fn)(bot.clone(),msg.clone()) && (list.trigger_cond_async)(bot,msg).await } - fn quiet_off_ok(list:Arc<Listener>,bot:Arc<Bot>,message:ServerMessage) -> bool { - if let ServerMessage::Privmsg(msg) = message { + // async fn quiet_off_ok(list:Arc<Listener>,bot:Arc<Bot>,message:ServerMessage) -> bool { + // if let ServerMessage::Privmsg(msg) = message { - if let Some(parent_mod) = &*list.parent_module { - return !bot.get_channel_quiet(msg.channel_login) || parent_mod.get_names().contains(&"debug".to_string()); - } + // if let Some(parent_mod) = &*list.parent_module { + // return !bot.chat.lock().await.get_channel_quiet(msg.channel_login) || parent_mod.get_names().contains(&"debug".to_string()); + // } - return !bot.get_channel_quiet(msg.channel_login) ; - } - return true; /* quiet is off for non chat msgs */ - } + // return !bot.chat.lock().await.get_channel_quiet(msg.channel_login) ; + // } + // return true; /* quiet is off for non chat msgs */ + // } - defined_conditions_ok(list.clone(), bot.clone(), msg.clone()).await && - quiet_off_ok(list, bot, msg) + defined_conditions_ok(list.clone(), bot.clone(), msg.clone()).await + // && + // quiet_off_ok(list, bot, msg).await } /// executes the listeners executon body diff --git a/forcebot_core/src/botcore/built_in_mods.rs b/forcebot_core/src/botcore/built_in_mods.rs new file mode 100644 index 0000000..53e04a3 --- /dev/null +++ b/forcebot_core/src/botcore/built_in_mods.rs @@ -0,0 +1,14 @@ +// use std::sync::Arc; + +use crate::Bot; + +pub mod quiet; + + +/// used to internally load internal modules +pub async fn load_built_in_mods(bot:&Bot){ + + bot.load_module(quiet::create_module()).await; + +} + diff --git a/forcebot_core/src/custom_mods/quiet.rs b/forcebot_core/src/botcore/built_in_mods/quiet.rs similarity index 79% rename from forcebot_core/src/custom_mods/quiet.rs rename to forcebot_core/src/botcore/built_in_mods/quiet.rs index a9827f6..865c81b 100644 --- a/forcebot_core/src/custom_mods/quiet.rs +++ b/forcebot_core/src/botcore/built_in_mods/quiet.rs @@ -44,8 +44,15 @@ fn cmd_quiet_on() -> Command { if let ServerMessage::Privmsg(msg) = message { // dbg!("quiet on called"); - bot.set_channel_quiet(msg.channel_login.clone(), true); + + let chatlock = bot.chat.lock().await; + let _=chatlock.say_in_reply_to(&msg, "Shush ".to_string()).await; + + chatlock.set_channel_quiet(msg.channel_login.clone(), true); println!("channel {} set quiet true",msg.channel_login); + + + return Result::Ok("Success".to_string()); } Result::Err("Not Valid message type".to_string()) } @@ -68,7 +75,12 @@ fn cmd_quiet_off() -> Command { async fn execbody(bot:Arc<Bot>,message:ServerMessage) -> Result<String,String> { if let ServerMessage::Privmsg(msg) = message { - bot.set_channel_quiet(msg.channel_login.clone(), false); + let chatlock = bot.chat.lock().await; + + chatlock.set_channel_quiet(msg.channel_login.clone(), false); + let _=chatlock.say_in_reply_to(&msg, "GoodGirl I'll be good for u chat rar ".to_string()).await; + + println!("channel {} set quiet false",msg.channel_login); } Result::Err("Not Valid message type".to_string()) diff --git a/forcebot_core/src/botcore/chat.rs b/forcebot_core/src/botcore/chat.rs new file mode 100644 index 0000000..d548a4f --- /dev/null +++ b/forcebot_core/src/botcore/chat.rs @@ -0,0 +1,151 @@ +use std::{fmt::Error, ops::Mul, rc::Rc, sync::{Arc, Mutex, RwLock}}; + +use twitch_irc::{login::StaticLoginCredentials, message::ReplyToMessage, SecureTCPTransport, TwitchIRCClient}; + +use crate::Bot; + +/// Bot API to send messages to send messages to chat +/// +/// Uses TwitchIRCClient say_in_reply_to() but enforces controls like quiet +/// +/// + +pub struct Chat +{ + /// outbound chat client msg stream + pub client: TwitchIRCClient<SecureTCPTransport,StaticLoginCredentials>, + /// channel_quiet + channel_quiet_yn: RwLock<Vec<(String,RwLock<bool>)>>, + +} + +impl Chat { + + + pub async fn new(client:TwitchIRCClient<SecureTCPTransport,StaticLoginCredentials>) + -> Chat { + Chat { + client, + // parent_bot : Mutex::new(Bot::new().await) , + channel_quiet_yn : RwLock::new(vec![]), + } + } + + // pub fn set_parent_bot(&self,parent_bot_in:Arc<Bot>) + // { + // let mut lock = self.parent_bot.lock().unwrap(); + // *lock = parent_bot_in; + // } + + /// helper + fn ok_to_send(&self,channel_login: String) -> bool { + + fn not_quiet_ok(chat:&Chat,channel_login:String) -> bool { + // let lock = chat.parent_bot.lock().unwrap(); + // let a = lock.as_ref(); + // if let Some(bot) = &*lock { + return !chat.get_channel_quiet(channel_login); + // } + // true + } + not_quiet_ok(self, channel_login) + + } + + + /// Get the quiet status of a channel + pub fn get_channel_quiet(&self,channel:String) -> bool { + for a in self.channel_quiet_yn.read().unwrap().iter() { + if a.0 == channel { + return a.1.read().unwrap().clone(); + } + } + return false; + } + + /// Get the quiet status of a channel + pub fn set_channel_quiet(&self,channel:String,quiet_on:bool) { + let mut found = false; + + let chnlquiet = self.channel_quiet_yn.read().unwrap(); + for rec in chnlquiet.iter() { + if rec.0 == channel { + found = true; + let mut status = rec.1.write().unwrap(); + *status = quiet_on; + drop(status); + } + } + drop(chnlquiet); + + if !found { + // dbg!("set chn quiet > !found channel quiet status"); + let mut chnlquiet = self.channel_quiet_yn.write().unwrap(); + chnlquiet.push((channel,RwLock::new(quiet_on))); + drop(chnlquiet); + } + + } + + + + pub async fn say_in_reply_to(&self, + reply_to: &impl ReplyToMessage, + message: String + ) -> Result<(),()> { + // reply_to.channel_login() + if self.ok_to_send(reply_to.channel_login().to_string()) { + match self.client.say_in_reply_to(reply_to, message).await { + Ok(_) => return Ok(()), + Err(_) => return Err(()), + } + } else { + return Err(()); + } + + } + + pub async fn say(&self, + channel_login: String, + message: String, + ) -> Result<(),()> { + if self.ok_to_send(channel_login.to_string()) { +match self.client.say(channel_login, message).await { + Ok(_) => return Ok(()), + Err(_) => return Err(()), + } + } else { + return Err(()); + } + } + + pub async fn me(&self, + channel_login: String, + message: String, + ) -> Result<(),()> { + if self.ok_to_send(channel_login.to_string()) { +match self.client.me(channel_login, message).await { + Ok(_) => return Ok(()), + Err(_) => return Err(()), + } + } else { + return Err(()); + } + + } + + pub async fn me_in_reply_to(&self, + reply_to: &impl ReplyToMessage, + message: String +) -> Result<(),()> { + if self.ok_to_send(reply_to.channel_login().to_string()) { + match self.client.me_in_reply_to(reply_to, message).await { + Ok(_) => return Ok(()), + Err(_) => return Err(()), + } + } else { + return Err(()); + } + + } +} \ No newline at end of file diff --git a/forcebot_core/src/custom_mods.rs b/forcebot_core/src/custom_mods.rs index 49848a1..754b7b9 100644 --- a/forcebot_core/src/custom_mods.rs +++ b/forcebot_core/src/custom_mods.rs @@ -1,4 +1,4 @@ pub mod guest_badge; pub mod pyramid; pub mod debug; -pub mod quiet; \ No newline at end of file +// pub mod quiet; \ No newline at end of file diff --git a/forcebot_core/src/custom_mods/guest_badge.rs b/forcebot_core/src/custom_mods/guest_badge.rs index 8577bf9..39c121d 100644 --- a/forcebot_core/src/custom_mods/guest_badge.rs +++ b/forcebot_core/src/custom_mods/guest_badge.rs @@ -79,7 +79,7 @@ fn create_cmd_vip() -> Command { if badges_issued { - let _= bot.client.say_in_reply_to( + let _= bot.chat.lock().await.say_in_reply_to( &msg.clone(), format!("Guest badges issued for {} min",guest_dur_min)).await; return Result::Ok("Success".to_string()); } @@ -123,7 +123,7 @@ fn create_cmd_mod() -> Command { } } if badges_issued { - let _= bot.client.say_in_reply_to( + let _= bot.chat.lock().await.say_in_reply_to( &msg, format!("Guest badges issued for {} min",MOD_GIVEN_DUR_MIN)).await; return Result::Ok("Success".to_string()); } diff --git a/forcebot_core/src/custom_mods/pyramid.rs b/forcebot_core/src/custom_mods/pyramid.rs index 0ea950b..8b36395 100644 --- a/forcebot_core/src/custom_mods/pyramid.rs +++ b/forcebot_core/src/custom_mods/pyramid.rs @@ -51,7 +51,7 @@ fn create_pyramid_detector() -> Listener { /* 2. Define an async trigger condition callback */ async fn condition01(bot:Arc<Bot>,message:ServerMessage) -> bool { if let ServerMessage::Privmsg(msg) = message { - if detect_pyramid_complete_ok(bot.clone(), msg.clone()).await { + if detect_pyramid_complete_ok(bot.clone(), msg.clone()).await && get_pyramid_size(msg.channel_login) > 3 { return true; } } @@ -65,11 +65,33 @@ fn create_pyramid_detector() -> Listener { /* 4. Define an async fn callback execution */ async fn execbody(bot:Arc<Bot>,message:ServerMessage) -> Result<String,String> { if let ServerMessage::Privmsg(msg) = message { + dbg!("enter pyramid listener execution - after pyramid complete"); // if detect_pyramid_complete_ok(bot.clone(), msg.clone()).await { - let _ = bot.client.say_in_reply_to(&msg, "Clap".to_string()).await ; + // let _ = bot.chat.lock().await. + dbg!("> get start pattern"); + let pattern = get_start_pattern(msg.channel_login.clone()); + let mut outmsg ; - set_pyramid_started(msg.channel_login,false); + /* Prefer emote before pattern in case pattern is command */ + if pattern.len() < 50 { + outmsg = format!("Clap {}",pattern); + } else { + outmsg = "Clap".to_string(); + } + + dbg!(get_pyramid_size(msg.channel_login.clone())); + if get_pyramid_size(msg.channel_login.clone()) < 4 { + outmsg = format!("{} annytfMagniGlass",outmsg); + } + + dbg!("> start pattern :",pattern); + + dbg!("> say_in_reply_to completed :",outmsg.clone()); + let _ = bot.chat.lock().await.say_in_reply_to(&msg, outmsg).await ; + + dbg!("> set pyramid started - false"); + set_pyramid_started(msg.channel_login.clone(),false); return Result::Ok("Success".to_string()) ; // } @@ -84,17 +106,22 @@ fn create_pyramid_detector() -> Listener { } - +/// detect pyramid based on latest message and channel +/// +/// async fn detect_pyramid_complete_ok(_bot:Arc<Bot>,msg:PrivmsgMessage) -> bool { + dbg!("enter detect_pyramid_complete()"); - let msgtext = msg.message_text.replace("\u{e0000}","").trim().to_string(); + let msgtext = msg.message_text.replace("","").replace("\u{e0000}","").trim().to_string(); let msgchannel = msg.channel_login; let msgchatter = msg.sender.login; // 1. Check if Pyramid started in chat > and recognize pyramid started if !is_pyramid_started(msgchannel.clone()) & check_start_pyramid(msgchannel.clone(),msgtext.clone(),) { + dbg!("> set pyramid started - true"); set_pyramid_started(msgchannel.clone(),true); push_to_compare(msgchannel.clone(),msgchatter.clone(),get_start_pattern(msgchannel.clone())); + } @@ -109,15 +136,29 @@ async fn detect_pyramid_complete_ok(_bot:Arc<Bot>,msg:PrivmsgMessage) -> bool { // 2b. If Pyramid is Started, and the latest message is the pattern, check for // symmetry to determine pyramid + if is_pyramid_started(msgchannel.clone()) && msgtext.clone() == get_start_pattern(msgchannel.clone()) { if symmetry_ok(msgchannel.clone()) { return true; } else { + dbg!("> set pyramid started - false"); + set_pyramid_started(msgchannel,false); + return false ; } + } + + // 2c. if Pyramid is strted but latest message does not ontain pattern + if is_pyramid_started(msgchannel.clone()) && !msgtext.clone().contains( get_start_pattern(msgchannel.clone()).as_str()) { + + dbg!("> set pyramid started - false"); + set_pyramid_started(msgchannel,false); + + return false ; } else { return false; - } + + }; } @@ -130,6 +171,8 @@ lazy_static!{ pub static ref PYRAMID_STARTED_PER_CHNL: Mutex<Vec<(String,Mutex<bool>)>> = Mutex::new(vec![]); /// Start patterns per channel (channel:String,pattern:String) pub static ref START_PATTERNS_PER_CHNL: Mutex<Vec<(String,Mutex<String>)>> = Mutex::new(vec![]); + /// Pyramid sze per channel (channel:String,started:bool) + pub static ref PYRAMID_SIZE_PER_CHNL: Mutex<Vec<(String,Mutex<i32>)>> = Mutex::new(vec![]); /// temp message stack checker pub static ref TEMP_MSG_STACK: Mutex<Vec<String>> = Mutex::new(vec![]); @@ -249,6 +292,11 @@ fn push_to_compare(channel:String,chatter:String,message:String) { /// checks latest and next latest messages for potential start fn check_start_pyramid(channel:String,msgtext: String) -> bool { msgtext == format!("{} {}",get_start_pattern(channel.clone()),get_start_pattern(channel.clone())) + // msgtext == format!("{} {} {}", + // get_start_pattern(channel.clone()), + // get_start_pattern(channel.clone()), + // get_start_pattern(channel.clone()) + // ) } @@ -259,13 +307,17 @@ fn symmetry_ok(channel:String) -> bool { if !(read_top_of_compare(channel.clone()).unwrap_or(("".to_string(),"".to_string())).1 == get_start_pattern(channel.clone())) { return false; } + + let mut pyramid_size = 0; loop { if !checking_started && read_top_of_compare(channel.clone()).unwrap_or(("".to_string(),"".to_string())).1 == get_start_pattern(channel.clone()) { checking_started = true; } + if temp_stack.last().is_none() || read_top_of_compare(channel.clone()).unwrap_or(("".to_string(),"".to_string())).1.len() > temp_stack.last().unwrap_or(&"".to_string()).len() { temp_stack.push(pop_top_of_compare(channel.clone()).unwrap_or(("".to_string(),"".to_string())).1); + pyramid_size += 1; } else if temp_stack.last().is_some() && read_top_of_compare(channel.clone()).unwrap_or(("".to_string(),"".to_string())).1.len() < temp_stack.last().unwrap_or(&"".to_string()).len() { @@ -276,13 +328,20 @@ fn symmetry_ok(channel:String) -> bool { continue; } else { + set_pyramid_size(channel.clone(), 0); temp_stack.clear(); return false; } - } else { return false; } + } else { + set_pyramid_size(channel.clone(), 0); + return false; + } + if checking_started && read_top_of_compare(channel.clone()).unwrap().1 == get_start_pattern(channel.clone()) { + /* leave pyramid size set for exection */ + set_pyramid_size(channel.clone(), pyramid_size*2-1); temp_stack.clear(); return true; } @@ -293,6 +352,32 @@ fn symmetry_ok(channel:String) -> bool { } +fn set_pyramid_size(channel:String,size:i32) { + let mut size_perchnl = PYRAMID_SIZE_PER_CHNL.lock().unwrap(); + let mut found = false; + for rec in size_perchnl.iter() { + if rec.0 == channel { + found = true; + let mut rec_started = rec.1.lock().unwrap(); + *rec_started = size; + } + } + if !found { + size_perchnl.push((channel,Mutex::new(size))); + } +} + +fn get_pyramid_size(channel:String) -> i32 { + let size_perchnl = PYRAMID_SIZE_PER_CHNL.lock().unwrap(); + for rec in size_perchnl.iter() { + if rec.0 == channel { + let rec_started = rec.1.lock().unwrap(); + return *rec_started; + } + } + 0 +} + /// #todo /// /// pyramid interruptor @@ -316,7 +401,7 @@ fn _create_interruptor_cmd() -> Command { /* 2. Define an async fn callback execution */ async fn execbody(bot:Arc<Bot>,message:ServerMessage) -> Result<String,String> { if let ServerMessage::Privmsg(msg) = message { - let _ = bot.client.say_in_reply_to(&msg, String::from("test success")).await; + let _ = bot.chat.lock().await.say_in_reply_to(&msg, String::from("test success")).await; return Result::Ok("Success".to_string()) ; } Result::Err("Not Valid message type".to_string()) diff --git a/moderator_reactor/src/main.rs b/moderator_reactor/src/main.rs index 528b62e..5705fbe 100644 --- a/moderator_reactor/src/main.rs +++ b/moderator_reactor/src/main.rs @@ -46,7 +46,7 @@ pub async fn main() { /* 3. Define an async fn callback execution */ async fn execbody(bot:Arc<Bot>,message:ServerMessage) -> Result<String,String> { if let ServerMessage::Privmsg(msg) = message { - let _ = bot.client.say_in_reply_to(&msg, "pepeKneel".to_string()).await ; + let _ = bot.chat.lock().await.say_in_reply_to(&msg, "pepeKneel".to_string()).await ; return Result::Ok("Success".to_string()) ; } Result::Err("Not Valid message type".to_string()) diff --git a/readme.md b/readme.md index c4e81d0..aa9355c 100644 --- a/readme.md +++ b/readme.md @@ -27,23 +27,41 @@ cargo run -p forcebot_core # Features -- Quick Start to use full feature set bot -- Moderators & Broadcasters can `disable` or `enable` `Modules` of bot functionality through chat `Commands` -- Full Feature Set `forcebot_core` bot has the following modules loaded - - `debug` - outputs to console messages from the channel where it was enabled. Toggle debug with the Commands `debug on` or `debug off` - - `guest_badge` - Temporary badges can be issued to chatters - - `besty` - Tomfoolery - - `pyramid` - for detecting & handling pyramids +## Built In Chat Commands + +- `quiet on` / `quiet off` - Moderators & Broadcasters can quiet the bot + +- `enable $module$` / `disable $module$` - Moderators & Broadcasters can enable or disable `Modules` of bot functionality through chat `Commands` + + +## Custom Modules can be coded to load additional functionality + +Developers an create Modules that add more bot functionality + +The main `forcebot_core` Binary crate includes the following Custom `Modules` + +- `debug` - outputs to console messages from the channel where it was enabled. Toggle debug with the Commands `debug on` or `debug off` +- `guest_badge` - Temporary badges can be issued to chatters +- `besty` - Tomfoolery +- `pyramid` - for detecting & handling pyramids + + +## `forcebot_core` Bot Library + - `forcebot_core` library API provides Custom package developers a way to add functionality by adding `Modules` that contain Bot Objects like `Commands` and `Listeners` - `Listeners` and `Commands` listen for a defined callback trigger condition and run an defined execution callback - `Commands` are similar to `Listeners` with refined trigger conditions including using bot `prefix` with the `Command` , triggers based on `Badge` , and more - Workspace for package developers to independently code their own `Modules` -- Workspace comes with binary crates with working or example bots that use `forcebot_core` library - - `moderator_reactor` - bot kneels to all moderator messages - - `simple_module_example` - bot has a `test` `Module` with a `test` `Command` .Moderators & Broadcasters can manage the `Module` in chat with `enable` / `disable` `Commands` - - `new_empty_bot` - while empty, has `disable` and `enable` chat `Commands` . This is an example of the bot without any loaded modules - - `simple_command_bot` - bot responds to a `test` `Command`. As the command was not loaded through a `Module`, `disable` & `enable` commands don't work on the `test` command. This could be a Global `Command` - - `simple_debug_listener` - bot outputs all twitch `ServerMessages` received to terminal + +## Workspaces + + +Workspace comes with binary crates with working or example bots that use `forcebot_core` library +- `moderator_reactor` - bot kneels to all moderator messages +- `simple_module_example` - bot has a `test` `Module` with a `test` `Command` . Moderators & Broadcasters can manage the `Module` in chat with `enable` / `disable` `Commands` +- `new_empty_bot` - while empty, has `disable` and `enable` chat `Commands` . This is an example of the bot without any loaded modules +- `simple_command_bot` - bot responds to a `test` `Command`. As the command was not loaded through a `Module`, `disable` & `enable` commands don't work on the `test` command. This could be a Global `Command` +- `simple_debug_listener` - bot outputs all twitch `ServerMessages` received to terminal @@ -150,7 +168,7 @@ use forcebot_core::Bot; pub async fn main() { /* 1. Create the bot using env */ - let bot = Bot::new(); + let bot = Bot::new().await; /* 2. Run the bot */ bot.run().await; @@ -165,18 +183,24 @@ A `Module` is a group of bot objects (eg `Command`) that elevated users can mana Custom `Modules` can be loaded into a new bot with minimum coding : just load the modules and run the bot ```rust -use forcebot_core::{custom_mods::{guest_badge, pyramid}, Bot}; +use forcebot_core::{custom_mods::{debug, guest_badge, pyramid}, Bot}; + #[tokio::main] pub async fn main() { - /* 1. Create the bot using env */ - let mut bot = Bot::new(); + /* Create the bot using env */ + let bot = Bot::new().await; + + /* 1. Load the module into the bot */ + bot.load_module(funbot_objs::create_module()).await; /* 2. Load Custom Modules */ bot.load_module(guest_badge::create_module()).await; bot.load_module(pyramid::create_module()).await; + bot.load_module(debug::create_module()).await; + /* 3. Run the bot */ bot.run().await; @@ -195,13 +219,14 @@ Create a custom `Module` by : ```rust + use forcebot_core::Bot; #[tokio::main] pub async fn main() { /* Create the bot using env */ - let mut bot = Bot::new(); + let bot = Bot::new().await; /* load the Module */ bot.load_module(custom_mod::new()).await; @@ -211,6 +236,7 @@ pub async fn main() { } + pub mod custom_mod { use std::sync::Arc; @@ -240,7 +266,7 @@ pub mod custom_mod { /* 2. Define exec callback */ async fn execbody(bot:Arc<Bot>,message:ServerMessage) -> Result<String,String> { if let ServerMessage::Privmsg(msg) = message { - let _= bot.client.say_in_reply_to( + let _= bot.chat.lock().await.say_in_reply_to( &msg, "test return".to_string()).await; } Result::Err("Not Valid message type".to_string()) @@ -249,7 +275,7 @@ pub mod custom_mod { /* 3. Set Command flags */ cmd.set_exec_fn(execution_async(execbody)); cmd.set_admin_only(false); - cmd.set_min_badge(Badge::Moderator); + cmd.set_min_badge(Badge::Vip); cmd } @@ -260,6 +286,7 @@ pub mod custom_mod { Bot with a simple listener that listens for all messages and prints in output ```rust + use std::sync::Arc; use forcebot_core::{execution_async, Bot, Listener}; @@ -269,7 +296,7 @@ use twitch_irc::message::ServerMessage; pub async fn main() { /* 1. Create the bot using env */ - let mut bot = Bot::new(); + let bot = Bot::new().await; /* 2a. Create a new blank Listener */ let mut listener = Listener::new(); @@ -289,12 +316,13 @@ pub async fn main() { listener.set_exec_fn(execution_async(execbody)); /* 3. Load the listener into the bot */ - bot.load_listener(listener); + bot.load_listener(listener).await; /* 4. Run the bot */ bot.run().await; } + ``` ## Moderator Reactor @@ -302,6 +330,7 @@ pub async fn main() { Example listener listens for a moderator badge and reply in chat ```rust + use std::sync::Arc; use forcebot_core::Bot; @@ -314,7 +343,7 @@ use twitch_irc::message::ServerMessage; pub async fn main() { /* Create the bot using env */ - let mut bot = Bot::new(); + let bot = Bot::new().await; /* 1. Create a new blank Listener */ let mut listener = Listener::new(); @@ -324,7 +353,6 @@ pub async fn main() { listener.set_trigger_cond_fn( |_:Arc<Bot>,message:ServerMessage| if let ServerMessage::Privmsg(msg) = message { - for badge in msg.badges { if matches!(badge, x if x.name == "moderator") { // dbg!("moderator found"); @@ -338,7 +366,7 @@ pub async fn main() { /* 3. Define an async fn callback execution */ async fn execbody(bot:Arc<Bot>,message:ServerMessage) -> Result<String,String> { if let ServerMessage::Privmsg(msg) = message { - let _ = bot.client.say_in_reply_to(&msg, "pepeKneel".to_string()).await ; + let _ = bot.chat.lock().await.say_in_reply_to(&msg, "pepeKneel".to_string()).await ; return Result::Ok("Success".to_string()) ; } Result::Err("Not Valid message type".to_string()) @@ -348,12 +376,13 @@ pub async fn main() { listener.set_exec_fn(execution_async(execbody)); /* 5. Load the listener into the bot */ - bot.load_listener(listener); + bot.load_listener(listener).await; /* Run the bot */ bot.run().await; } + ``` ## Simple Test Command @@ -367,11 +396,12 @@ use forcebot_core::execution_async; use forcebot_core::Command; use twitch_irc::message::ServerMessage; + #[tokio::main] pub async fn main() { /* Create the bot using env */ - let mut bot = Bot::new(); + let bot = Bot::new().await; /* 1. Create a new blank cmd */ let mut cmd = Command::new(vec!["test".to_string()],"".to_string()); @@ -379,7 +409,7 @@ pub async fn main() { /* 2. Define an async fn callback execution */ async fn execbody(bot:Arc<Bot>,message:ServerMessage) -> Result<String,String> { if let ServerMessage::Privmsg(msg) = message { - let _ = bot.client.say_in_reply_to(&msg, String::from("test success")).await; + let _ = bot.chat.lock().await.say_in_reply_to(&msg, String::from("test success")).await; return Result::Ok("Success".to_string()) ; } Result::Err("Not Valid message type".to_string()) @@ -395,12 +425,13 @@ pub async fn main() { cmd.set_min_badge(Badge::Moderator); /* 6. Load the cmd into the bot */ - bot.load_command(cmd); + bot.load_command(cmd).await; /* Run the bot */ bot.run().await; } + ``` # Crate Rust API Documentation diff --git a/simple_command_bot/src/main.rs b/simple_command_bot/src/main.rs index f231f0c..418e1eb 100644 --- a/simple_command_bot/src/main.rs +++ b/simple_command_bot/src/main.rs @@ -34,7 +34,7 @@ pub async fn main() { /* 2. Define an async fn callback execution */ async fn execbody(bot:Arc<Bot>,message:ServerMessage) -> Result<String,String> { if let ServerMessage::Privmsg(msg) = message { - let _ = bot.client.say_in_reply_to(&msg, String::from("test success")).await; + let _ = bot.chat.lock().await.say_in_reply_to(&msg, String::from("test success")).await; return Result::Ok("Success".to_string()) ; } Result::Err("Not Valid message type".to_string()) diff --git a/simple_module_example/src/main.rs b/simple_module_example/src/main.rs index 3851d34..e588b22 100644 --- a/simple_module_example/src/main.rs +++ b/simple_module_example/src/main.rs @@ -63,7 +63,7 @@ pub mod custom_mod { /* 2. Define exec callback */ async fn execbody(bot:Arc<Bot>,message:ServerMessage) -> Result<String,String> { if let ServerMessage::Privmsg(msg) = message { - let _= bot.client.say_in_reply_to( + let _= bot.chat.lock().await.say_in_reply_to( &msg, "test return".to_string()).await; } Result::Err("Not Valid message type".to_string()) -- 2.49.0 From cd69a35ec1b3e65ee72213a26060bcccd8b73f1c Mon Sep 17 00:00:00 2001 From: modulatingforce <modulatingforce@gmail.com> Date: Thu, 6 Feb 2025 09:41:54 -0500 Subject: [PATCH 14/14] cargo-fmt --- forcebot_core/src/bin/fun_bot.rs | 53 +-- forcebot_core/src/bin/new_bot.rs | 8 +- forcebot_core/src/bin/simple_module.rs | 43 +- forcebot_core/src/botcore.rs | 4 +- forcebot_core/src/botcore/bot.rs | 334 +++++++-------- forcebot_core/src/botcore/bot_objects.rs | 288 ++++++++----- .../src/botcore/bot_objects/command.rs | 218 +++++----- .../src/botcore/bot_objects/listener.rs | 124 +++--- forcebot_core/src/botcore/built_in_mods.rs | 6 +- .../src/botcore/built_in_mods/quiet.rs | 48 +-- forcebot_core/src/botcore/chat.rs | 121 +++--- forcebot_core/src/botcore/modules.rs | 30 +- forcebot_core/src/custom_mods.rs | 4 +- forcebot_core/src/custom_mods/debug.rs | 104 +++-- forcebot_core/src/custom_mods/guest_badge.rs | 165 ++++--- forcebot_core/src/custom_mods/pyramid.rs | 403 +++++++++--------- forcebot_core/src/lib.rs | 114 +++-- moderator_reactor/src/main.rs | 51 +-- new_empty_bot/src/main.rs | 8 +- simple_command_bot/src/main.rs | 30 +- simple_debug_listener/src/main.rs | 16 +- simple_module_example/src/main.rs | 43 +- 22 files changed, 1140 insertions(+), 1075 deletions(-) diff --git a/forcebot_core/src/bin/fun_bot.rs b/forcebot_core/src/bin/fun_bot.rs index abbc426..b506faf 100644 --- a/forcebot_core/src/bin/fun_bot.rs +++ b/forcebot_core/src/bin/fun_bot.rs @@ -1,30 +1,30 @@ //! WIP Fun forcebot with catered customizations #todo -//! +//! //! Custom modules that can be managed in chat through `disable` and `enable` commands //! - `besty` - uses a custom prefix tp trigger //! - `guests` //! - `pyramid` //! - `quiet` -//! -//! -//! Be sure the followig is defined in `.env` +//! +//! +//! Be sure the followig is defined in `.env` //! - login_name //! - access_token //! - bot_channels //! - prefix //! - bot_admins -//! -//! Bot access tokens be generated here - +//! +//! Bot access tokens be generated here - //! - Get a Bot Chat Token here - <https://twitchtokengenerator.com> //! - More Info - <https://dev.twitch.tv/docs/authentication> -use forcebot_core::{custom_mods::{debug, guest_badge, pyramid}, Bot}; - - +use forcebot_core::{ + custom_mods::{debug, guest_badge, pyramid}, + Bot, +}; #[tokio::main] pub async fn main() { - /* Create the bot using env */ let bot = Bot::new().await; @@ -33,14 +33,11 @@ pub async fn main() { bot.load_module(guest_badge::create_module()).await; bot.load_module(pyramid::create_module()).await; bot.load_module(debug::create_module()).await; - - + /* 3. Run the bot */ bot.run().await; - } - pub mod funbot_objs { use std::sync::Arc; @@ -50,8 +47,9 @@ pub mod funbot_objs { /// Create a Module with a loaded Command object pub fn create_module() -> Module { let mut custom_mod = Module::new( - vec!["besty".to_string()], - "Now Aware of besty xdd666 ".to_string()); + vec!["besty".to_string()], + "Now Aware of besty xdd666 ".to_string(), + ); custom_mod.load_command(create_cmd_test()); // custom_mod.set_status_by_default(Status::Disabled); @@ -61,16 +59,22 @@ pub mod funbot_objs { /// Create a Command Object fn create_cmd_test() -> Command { - - let mut cmd = Command::new(vec!["remind besty".to_string()],"annytfYandere ".to_string()); + let mut cmd = Command::new( + vec!["remind besty".to_string()], + "annytfYandere ".to_string(), + ); - async fn execbody(bot:Arc<Bot>,message:ServerMessage) -> Result<String,String> { + async fn execbody(bot: Arc<Bot>, message: ServerMessage) -> Result<String, String> { if let ServerMessage::Privmsg(msg) = message { - let _= bot.chat.lock().await.say_in_reply_to( - &msg, "annytfYandere he's mine".to_string()).await; - return Result::Ok("Success".to_string()); + let _ = bot + .chat + .lock() + .await + .say_in_reply_to(&msg, "annytfYandere he's mine".to_string()) + .await; + return Result::Ok("Success".to_string()); } - Result::Err("Not Valid message type".to_string()) + Result::Err("Not Valid message type".to_string()) } cmd.set_exec_fn(execution_async(execbody)); @@ -79,6 +83,5 @@ pub mod funbot_objs { cmd.set_min_badge(Badge::Vip); cmd - } -} \ No newline at end of file +} diff --git a/forcebot_core/src/bin/new_bot.rs b/forcebot_core/src/bin/new_bot.rs index a81462e..856f6b1 100644 --- a/forcebot_core/src/bin/new_bot.rs +++ b/forcebot_core/src/bin/new_bot.rs @@ -1,12 +1,12 @@ //! Example simple Binary crate that creates & runs bot based on `.env` -//! Be sure the followig is defined in `.env` +//! Be sure the followig is defined in `.env` //! - login_name //! - access_token //! - bot_channels //! - prefix //! - bot_admins -//! -//! Bot access tokens be generated here - +//! +//! Bot access tokens be generated here - //! - Get a Bot Chat Token here - <https://twitchtokengenerator.com> //! - More Info - <https://dev.twitch.tv/docs/authentication> @@ -14,11 +14,9 @@ use forcebot_core::Bot; #[tokio::main] pub async fn main() { - /* 1. Create the bot using env */ let bot = Bot::new().await; /* 2. Run the bot */ bot.run().await; - } diff --git a/forcebot_core/src/bin/simple_module.rs b/forcebot_core/src/bin/simple_module.rs index 3e36d90..477da78 100644 --- a/forcebot_core/src/bin/simple_module.rs +++ b/forcebot_core/src/bin/simple_module.rs @@ -1,19 +1,19 @@ //! Simple Module with a Command -//! -//! Adding objects through packages provides controls , +//! +//! Adding objects through packages provides controls , //! such as moderators, and brodcasters can disable or enable mods -//! -//! Here, moderators or above can enable or disable the `test` +//! +//! Here, moderators or above can enable or disable the `test` //! module with the command `<prefix> disable test` -//! -//! Be sure the followig is defined in `.env` +//! +//! Be sure the followig is defined in `.env` //! - login_name //! - access_token //! - bot_channels //! - prefix //! - bot_admins -//! -//! Bot access tokens be generated here - +//! +//! Bot access tokens be generated here - //! - Get a Bot Chat Token here - <https://twitchtokengenerator.com> //! - More Info - <https://dev.twitch.tv/docs/authentication> @@ -21,7 +21,6 @@ use forcebot_core::Bot; #[tokio::main] pub async fn main() { - /* Create the bot using env */ let bot = Bot::new().await; @@ -30,43 +29,41 @@ pub async fn main() { /* Run the bot */ bot.run().await; - } - pub mod custom_mod { use std::sync::Arc; use forcebot_core::{execution_async, Badge, Bot, Command, Module}; use twitch_irc::message::ServerMessage; - /// Module definition with a loaded command pub fn new() -> Module { /* 1. Create a new module */ - let mut custom_mod = Module::new( - vec!["test".to_string()], - "".to_string()); + let mut custom_mod = Module::new(vec!["test".to_string()], "".to_string()); /* 2. Load the cmd into a new module */ custom_mod.load_command(cmd_test()); custom_mod - } - /// Command definition + /// Command definition pub fn cmd_test() -> Command { /* 1. Create a new cmd */ - let mut cmd = Command::new(vec!["test".to_string()],"".to_string()); + let mut cmd = Command::new(vec!["test".to_string()], "".to_string()); /* 2. Define exec callback */ - async fn execbody(bot:Arc<Bot>,message:ServerMessage) -> Result<String,String> { + async fn execbody(bot: Arc<Bot>, message: ServerMessage) -> Result<String, String> { if let ServerMessage::Privmsg(msg) = message { - let _= bot.chat.lock().await.say_in_reply_to( - &msg, "test return".to_string()).await; + let _ = bot + .chat + .lock() + .await + .say_in_reply_to(&msg, "test return".to_string()) + .await; } - Result::Err("Not Valid message type".to_string()) + Result::Err("Not Valid message type".to_string()) } /* 3. Set Command flags */ @@ -76,4 +73,4 @@ pub mod custom_mod { cmd } -} \ No newline at end of file +} diff --git a/forcebot_core/src/botcore.rs b/forcebot_core/src/botcore.rs index 99449f0..a750843 100644 --- a/forcebot_core/src/botcore.rs +++ b/forcebot_core/src/botcore.rs @@ -1,5 +1,5 @@ pub mod bot; pub mod bot_objects; -pub mod modules; pub mod built_in_mods; -pub mod chat; \ No newline at end of file +pub mod chat; +pub mod modules; diff --git a/forcebot_core/src/botcore/bot.rs b/forcebot_core/src/botcore/bot.rs index cccc61f..f3774a1 100644 --- a/forcebot_core/src/botcore/bot.rs +++ b/forcebot_core/src/botcore/bot.rs @@ -1,37 +1,44 @@ - - // use async_recursion::async_recursion; -use tokio::sync::{mpsc::UnboundedReceiver, Mutex}; -use twitch_irc::{login::StaticLoginCredentials, message::{PrivmsgMessage, ServerMessage}, SecureTCPTransport, TwitchIRCClient}; use dotenv::dotenv; -use std::{env, sync::{Arc, RwLock}, time::{Duration, Instant}}; +use std::{ + env, + sync::{Arc, RwLock}, + time::{Duration, Instant}, +}; +use tokio::sync::{mpsc::UnboundedReceiver, Mutex}; +use twitch_irc::{ + login::StaticLoginCredentials, + message::{PrivmsgMessage, ServerMessage}, + SecureTCPTransport, TwitchIRCClient, +}; // use crate::{Badge, Command, Listener, Module}; use super::{bot_objects::command::Command, built_in_mods, chat::Chat}; -use crate::botcore::{bot_objects::Badge, chat}; -use crate::botcore::bot_objects::listener::Listener; use super::super::botcore::modules::Module; +use crate::botcore::bot_objects::listener::Listener; +use crate::botcore::{bot_objects::Badge, chat}; // use super:: -use super:: {bot_objects::built_in_objects, modules::{self, Status}}; - +use super::{ + bot_objects::built_in_objects, + modules::{self, Status}, +}; /// Twitch chat bot -pub struct Bot -{ +pub struct Bot { /// Prefix for commands prefix: String, /// inbound chat msg stream incoming_msgs: Mutex<UnboundedReceiver<ServerMessage>>, /// outbound chat client msg stream - pub client: TwitchIRCClient<SecureTCPTransport,StaticLoginCredentials>, + pub client: TwitchIRCClient<SecureTCPTransport, StaticLoginCredentials>, /// *preferred* bot enforced outbound chat client msg stream - pub chat : Mutex<Chat>, + pub chat: Mutex<Chat>, /// joined channels botchannels: Vec<String>, /// admin chatters - admins : Vec<String>, + admins: Vec<String>, /// listeners listeners: Mutex<Vec<Listener>>, /// commands @@ -39,21 +46,18 @@ pub struct Bot /// modules modules: RwLock<Vec<Module>>, /// channel module status - channel_module_status: RwLock<Vec<(String,String,modules::Status)>>, + channel_module_status: RwLock<Vec<(String, String, modules::Status)>>, /// chatter guest badges - chatter,channel,Badge,start_time,duration - chatter_guest_badges: Mutex<Vec<(String,String,Badge,Instant,Duration)>>, + chatter_guest_badges: Mutex<Vec<(String, String, Badge, Instant, Duration)>>, /// Message cache message_cache: Mutex<Vec<PrivmsgMessage>>, // /// channel_quiet // channel_quiet_yn: RwLock<Vec<(String,RwLock<bool>)>>, - } - -impl Bot -{ +impl Bot { /// Creates a new `Bot` using env variables - /// + /// /// Be sure the following is defined in an `.env` file /// - login_name /// - access_token @@ -62,35 +66,29 @@ impl Bot /// - bot_admins // #[async_recursion] pub async fn new() -> Arc<Bot> { - - - dotenv().ok(); let bot_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(); + let prefix = env::var("prefix").unwrap().to_owned(); let mut botchannels = Vec::new(); for chnl in env::var("bot_channels").unwrap().split(',') { - botchannels.push(chnl.to_owned()); + botchannels.push(chnl.to_owned()); } Bot::new_from(bot_login_name, oauth_token, prefix, botchannels).await - - } /// Creates a new `Bot` using bot information - /// + /// /// Bot will join `botchannels` argument - pub async fn new_from(bot_login_name:String, - oauth_token:String, - prefix:String, - botchannels:Vec<String>) -> Arc<Bot> { - + pub async fn new_from( + bot_login_name: String, + oauth_token: String, + prefix: String, + botchannels: Vec<String>, + ) -> Arc<Bot> { dotenv().ok(); let bot_login_name = bot_login_name; @@ -105,49 +103,45 @@ impl Bot let mut botchannels_all = Vec::new(); botchannels_all.extend(botchannels); - let mut admins = Vec::new(); if let Ok(value) = env::var("bot_admins") { for admin in value.split(',') { - admins.push(String::from(admin)) + admins.push(String::from(admin)) } } - let bot = Bot { - prefix, - incoming_msgs : Mutex::new(incoming_messages), - client : client.clone(), - chat : Mutex::new(Chat::new(client).await), - botchannels : botchannels_all, - listeners : Mutex::new(vec![]), - commands : Mutex::new(vec![]), - admins, - modules: RwLock::new(vec![]), - channel_module_status: RwLock::new(vec![]), - chatter_guest_badges: Mutex::new(vec![]), - message_cache : Mutex::new(vec![]), + prefix, + incoming_msgs: Mutex::new(incoming_messages), + client: client.clone(), + chat: Mutex::new(Chat::new(client).await), + botchannels: botchannels_all, + listeners: Mutex::new(vec![]), + commands: Mutex::new(vec![]), + admins, + modules: RwLock::new(vec![]), + channel_module_status: RwLock::new(vec![]), + chatter_guest_badges: Mutex::new(vec![]), + message_cache: Mutex::new(vec![]), // channel_quiet_yn : RwLock::new(vec![]), }; - async fn load_modules(bot:Bot) -> Bot { + async fn load_modules(bot: Bot) -> Bot { // let mut bot1 = bot; - // bot1.chat = Some(Chat::new(client, bot1)); + // bot1.chat = Some(Chat::new(client, bot1)); - for cmd in built_in_objects::create_commands() { - bot.load_command(cmd).await; - } - built_in_mods::load_built_in_mods(&bot).await; - - bot + for cmd in built_in_objects::create_commands() { + bot.load_command(cmd).await; + } + built_in_mods::load_built_in_mods(&bot).await; + bot } let bot = load_modules(bot).await; - let bot = Arc::new(bot); // let lock = bot.chat.lock().await; @@ -160,15 +154,13 @@ impl Bot // lock.set_parent_bot(bot.clone()); - println!("Joined - {:?}",bot.botchannels); - + println!("Joined - {:?}", bot.botchannels); bot.clone() } - /// Runs the bot - pub async fn run(self:Arc<Self>) { - + /// Runs the bot + pub async fn run(self: Arc<Self>) { for chnl in &self.botchannels { self.client.join(chnl.to_owned()).unwrap(); } @@ -177,24 +169,21 @@ impl Bot let bot = self; let join_handle = tokio::spawn(async move { - let a = bot.clone(); let mut in_msgs_lock = a.incoming_msgs.lock().await; - - while let Some(message) = in_msgs_lock.recv().await { + + while let Some(message) = in_msgs_lock.recv().await { // dbg!(message.clone()) ; let bot_listener_lock = bot.listeners.lock().await; for listener in bot_listener_lock.iter() { - let a = listener.clone(); - if a.cond_triggered(bot.clone(),message.clone()).await { - let _ = listener.execute_fn(bot.clone(),message.clone()).await; + if a.cond_triggered(bot.clone(), message.clone()).await { + let _ = listener.execute_fn(bot.clone(), message.clone()).await; } } if let ServerMessage::Privmsg(msg) = message.clone() { - // let mut cache_lock = bot.message_cache.lock().await; let mut cache_lock = bot.message_cache.lock().await; cache_lock.push(msg.clone()); @@ -203,91 +192,78 @@ impl Bot let cmd_lock = bot.commands.lock().await; for cmd in cmd_lock.iter() { - let a = cmd.clone(); - if a.command_triggered(bot.clone(),msg.clone()).await { - - let _ = cmd.execute_fn(bot.clone(),message.clone()).await; + if a.command_triggered(bot.clone(), msg.clone()).await { + let _ = cmd.execute_fn(bot.clone(), message.clone()).await; } } + fn get_enabled_channel_modules(bot: Arc<Bot>, channel: String) -> Vec<Module> { + let botmodules_lock = bot.modules.read().unwrap(); + let botmodules_cpy = botmodules_lock.clone(); + drop(botmodules_lock); - fn get_enabled_channel_modules(bot: Arc<Bot>,channel:String) -> Vec<Module> { + let mut enabled_mods = Vec::new(); - let botmodules_lock = bot.modules.read().unwrap(); - let botmodules_cpy = botmodules_lock.clone(); - drop(botmodules_lock); + 'module_loop: for module in &*botmodules_cpy { + // dbg!("try cms read"); + let cms_lock = bot.channel_module_status.read().unwrap(); - let mut enabled_mods = Vec::new(); - - 'module_loop: for module in &*botmodules_cpy { - - // dbg!("try cms read"); - let cms_lock = bot.channel_module_status.read().unwrap(); - - for channel_flags in cms_lock.iter() { - if channel_flags.0 == channel { - - if module.get_names().contains(&channel_flags.1) && channel_flags.2 == Status::Disabled { - continue 'module_loop; - + for channel_flags in cms_lock.iter() { + if channel_flags.0 == channel { + if module.get_names().contains(&channel_flags.1) + && channel_flags.2 == Status::Disabled + { + continue 'module_loop; + } } } + enabled_mods.push(module.clone()); } - enabled_mods.push(module.clone()); + + enabled_mods } - enabled_mods - - - } - - - for module in get_enabled_channel_modules(bot.clone(), msg.clone().channel_login) { - - for listener in module.get_listeners() { - - let a = listener.clone(); - if a.cond_triggered(bot.clone(),message.clone()).await { - - let _ = listener.execute_fn(bot.clone(),message.clone()).await; + for module in + get_enabled_channel_modules(bot.clone(), msg.clone().channel_login) + { + for listener in module.get_listeners() { + let a = listener.clone(); + if a.cond_triggered(bot.clone(), message.clone()).await { + let _ = listener.execute_fn(bot.clone(), message.clone()).await; + } + } + for cmd in module.get_commands() { + let a = cmd.clone(); + if a.command_triggered(bot.clone(), msg.clone()).await { + let _ = cmd.execute_fn(bot.clone(), message.clone()).await; + } } } - for cmd in module.get_commands() { - - let a = cmd.clone(); - if a.command_triggered(bot.clone(),msg.clone()).await { - - let _ = cmd.execute_fn(bot.clone(),message.clone()).await; - } - } - - } - } else {} ; - + } else { + }; } drop(in_msgs_lock); }); - + join_handle.await.unwrap(); } /// Loads a `Listener` into the bot - pub async fn load_listener(&self,l : Listener) { + pub async fn load_listener(&self, l: Listener) { let a = Arc::new(self); let mut listlock = a.listeners.lock().await; listlock.push(l); } /// Loads a `Command` into the bot - pub async fn load_command(&self,c : Command) { + pub async fn load_command(&self, c: Command) { let a = Arc::new(self); let mut cmdlock = a.commands.lock().await; cmdlock.push(c); } - - pub async fn get_module(&self,module:String) -> Option<Module> { + pub async fn get_module(&self, module: String) -> Option<Module> { let modlock = self.modules.read().unwrap(); for modl in modlock.iter() { if modl.get_names().contains(&module) { @@ -296,8 +272,6 @@ impl Bot } None } - - pub fn get_prefix(&self) -> String { self.prefix.clone() @@ -306,9 +280,9 @@ impl Bot pub fn get_admins(&self) -> Vec<String> { self.admins.clone() } - + /// loads a `Module` and its bot objects - pub async fn load_module(&self,m: Module) { + pub async fn load_module(&self, m: Module) { // dbg!("load module - start",m.get_names().first().unwrap()); let bot = Arc::new(self); // let bot_lock = bot.lock().await; @@ -316,10 +290,10 @@ impl Bot if m.get_status_by_default() == Status::Disabled { // dbg!("module fund disabled by default"); // dbg!("inner if"); - for (_index,chnl) in bot.botchannels.iter().enumerate() { + for (_index, chnl) in bot.botchannels.iter().enumerate() { // dbg!("iter - ",index); - bot.disable_module(chnl.clone(), - m.get_names().first().unwrap().clone()).await + bot.disable_module(chnl.clone(), m.get_names().first().unwrap().clone()) + .await } } // dbg!("aftee disable check"); @@ -328,11 +302,11 @@ impl Bot // dbg!(m); // dbg!("loading module ",m.get_names()); botmods.push(m); - } + } - pub async fn get_channel_module_status(&self,channel:String,module:String) -> Status { + pub async fn get_channel_module_status(&self, channel: String, module: String) -> Status { // dbg!("get channel module status"); - let found_disabled:bool = { + let found_disabled: bool = { // dbg!("try cms read"); let cms_lock = self.channel_module_status.read().unwrap(); // dbg!("cms read lock"); @@ -349,31 +323,31 @@ impl Bot found }; - let module_loaded:bool = { - + let module_loaded: bool = { let mut loaded_yn = false; for loaded_m in self.modules.read().unwrap().iter() { - if loaded_m.get_names().contains(&module) { + if loaded_m.get_names().contains(&module) { loaded_yn = true; } } loaded_yn - }; - - if found_disabled { return Status::Disabled;} - - else if !module_loaded { return Status::NotLoaded ;} - else { return Status::Enabled; }; + if found_disabled { + return Status::Disabled; + } else if !module_loaded { + return Status::NotLoaded; + } else { + return Status::Enabled; + }; } - pub async fn disable_module(&self,channel:String,module:String){ + pub async fn disable_module(&self, channel: String, module: String) { // dbg!("disable module called",channel.clone(),module.clone()); - let found_disabled:bool = { + let found_disabled: bool = { // dbg!("finding disabled mod"); // dbg!("try cms read"); let cms_lock = self.channel_module_status.read().unwrap(); @@ -394,62 +368,63 @@ impl Bot }; if !found_disabled { - - let mut cms_lock = self.channel_module_status.write().unwrap(); - - cms_lock.push((channel,module.clone(),Status::Disabled)); - + + cms_lock.push((channel, module.clone(), Status::Disabled)); + drop(cms_lock); - - - } - - + } } - pub async fn enable_module(&self,channel:String,module:String){ + pub async fn enable_module(&self, channel: String, module: String) { // dbg!("enable module called",channel.clone(),module.clone()); // dbg!("try cms write"); let mut lock = self.channel_module_status.write().unwrap(); // dbg!("cms write lock"); // dbg!(module.clone()); - while lock.contains(&(channel.clone(),module.clone(),Status::Disabled)) { - + while lock.contains(&(channel.clone(), module.clone(), Status::Disabled)) { let index = lock .iter() - .position(|x| *x == - (channel.clone(),module.clone(),Status::Disabled)) + .position(|x| *x == (channel.clone(), module.clone(), Status::Disabled)) .unwrap(); lock.remove(index); } drop(lock); } - pub async fn get_channel_guest_badges(&self,chatter:String,channel:String) -> Vec<(Badge,Instant,Duration)> { - + pub async fn get_channel_guest_badges( + &self, + chatter: String, + channel: String, + ) -> Vec<(Badge, Instant, Duration)> { let bot = Arc::new(self); let guest_badges_lock = bot.chatter_guest_badges.lock().await; let mut badges = vec![]; for temp_badge in guest_badges_lock.iter() { - if temp_badge.0 == chatter && temp_badge.1 == channel && - temp_badge.3 + temp_badge.4 > Instant::now() + if temp_badge.0 == chatter + && temp_badge.1 == channel + && temp_badge.3 + temp_badge.4 > Instant::now() { - badges.push((temp_badge.2.clone(),temp_badge.3,temp_badge.4)); + badges.push((temp_badge.2.clone(), temp_badge.3, temp_badge.4)); } } badges } - - pub async fn issue_new_guest_badge(&self,chatter:String,channel:String,badge:Badge,start:Instant,dur:Duration) { + pub async fn issue_new_guest_badge( + &self, + chatter: String, + channel: String, + badge: Badge, + start: Instant, + dur: Duration, + ) { let bot = Arc::new(self); let mut guest_badges_lock = bot.chatter_guest_badges.lock().await; - guest_badges_lock.push((chatter,channel,badge,start,dur)); - + guest_badges_lock.push((chatter, channel, badge, start, dur)); } pub fn get_message_cache(&self) -> &Mutex<Vec<PrivmsgMessage>> { @@ -457,12 +432,16 @@ impl Bot } /// get message cache newest to oldest for a channel - pub async fn get_message_cache_per_channel(&self,channel:String) -> Vec<PrivmsgMessage> { + pub async fn get_message_cache_per_channel(&self, channel: String) -> Vec<PrivmsgMessage> { let cache = self.message_cache.lock().await; let mut rslt = vec![]; - for a in cache.iter().rev().filter(|x| { x.channel_login==channel }).into_iter() { + for a in cache + .iter() + .rev() + .filter(|x| x.channel_login == channel) + .into_iter() + { rslt.push(a.clone()); - } rslt } @@ -476,7 +455,7 @@ impl Bot // } // return false; // } - + // /// Get the quiet status of a channel // pub fn set_channel_quiet(&self,channel:String,quiet_on:bool) { // let mut found = false; @@ -491,16 +470,13 @@ impl Bot // } // } // drop(chnlquiet); - + // if !found { // // dbg!("set chn quiet > !found channel quiet status"); // let mut chnlquiet = self.channel_quiet_yn.write().unwrap(); // chnlquiet.push((channel,RwLock::new(quiet_on))); // drop(chnlquiet); // } - + // } - } - - diff --git a/forcebot_core/src/botcore/bot_objects.rs b/forcebot_core/src/botcore/bot_objects.rs index dd4e6db..77de39c 100644 --- a/forcebot_core/src/botcore/bot_objects.rs +++ b/forcebot_core/src/botcore/bot_objects.rs @@ -1,6 +1,5 @@ -pub mod listener; pub mod command; - +pub mod listener; use std::boxed::Box; use std::future::Future; @@ -11,25 +10,24 @@ use twitch_irc::message::{PrivmsgMessage, ServerMessage}; use super::bot::Bot; - /// chat badge -#[derive(Clone,PartialEq, Eq,Debug)] +#[derive(Clone, PartialEq, Eq, Debug)] pub enum Badge { Moderator, Broadcaster, - Vip + Vip, } - - pub type ExecBody = Box< - dyn Fn(Arc<Bot>,ServerMessage) -> Pin<Box<dyn Future<Output = Result<String,String>> + Send>> + Send + Sync, + dyn Fn(Arc<Bot>, ServerMessage) -> Pin<Box<dyn Future<Output = Result<String, String>> + Send>> + + Send + + Sync, >; /// used to store async execution functions. Primarily used for `Command` -/// +/// /// call this to store execution functions in `Commands` -/// +/// /// # example /// ``` /// /* 2. Define exec callback */ @@ -40,20 +38,20 @@ pub type ExecBody = Box< /// /* 3. Set Command flags */ /// cmd.set_exec_fn(execution_async(execbody)); /// ``` -/// -pub fn execution_async<T>(f: fn(Arc<Bot>,ServerMessage) -> T) -> ExecBody +/// +pub fn execution_async<T>(f: fn(Arc<Bot>, ServerMessage) -> T) -> ExecBody where - T: Future<Output = Result<String,String>> + Send + 'static, + T: Future<Output = Result<String, String>> + Send + 'static, { - Box::new(move |a,b| Box::pin(f(a,b))) + Box::new(move |a, b| Box::pin(f(a, b))) } pub type CommandTrigger = Box< - dyn Fn(Arc<Bot>,PrivmsgMessage) -> Pin<Box<dyn Future<Output = bool> + Send>> + Send + Sync, + dyn Fn(Arc<Bot>, PrivmsgMessage) -> Pin<Box<dyn Future<Output = bool> + Send>> + Send + Sync, >; /// used to store async trigger condition callback functions. Primarily used for `Command` -/// +/// /// # example /// ``` /// /* 2. Define condition callback */ @@ -64,21 +62,20 @@ pub type CommandTrigger = Box< /// /* 3. Set Command flags */ /// cmd.set_custom_cond_async(command_condition_async(condition01)); /// ``` -/// -pub fn command_condition_async<T>(f: fn(Arc<Bot>,PrivmsgMessage) -> T) -> CommandTrigger +/// +pub fn command_condition_async<T>(f: fn(Arc<Bot>, PrivmsgMessage) -> T) -> CommandTrigger where T: Future<Output = bool> + Send + 'static, { - Box::new(move |a,b| Box::pin(f(a,b))) + Box::new(move |a, b| Box::pin(f(a, b))) } - pub type ListenerTrigger = Box< - dyn Fn(Arc<Bot>,ServerMessage) -> Pin<Box<dyn Future<Output = bool> + Send>> + Send + Sync, + dyn Fn(Arc<Bot>, ServerMessage) -> Pin<Box<dyn Future<Output = bool> + Send>> + Send + Sync, >; /// used to store async trigger condition callback functions. Primarily used for `Listener` -/// +/// /// # example /// ``` /// /* 2. Define condition callback */ @@ -89,30 +86,29 @@ pub type ListenerTrigger = Box< /// /* 3. Set Command flags */ /// cmd.set_custom_cond_async(listener_condition_async(condition01)); /// ``` -/// -pub fn listener_condition_async<T>(f: fn(Arc<Bot>,ServerMessage) -> T) -> ListenerTrigger +/// +pub fn listener_condition_async<T>(f: fn(Arc<Bot>, ServerMessage) -> T) -> ListenerTrigger where T: Future<Output = bool> + Send + 'static, { - Box::new(move |a,b| Box::pin(f(a,b))) + Box::new(move |a, b| Box::pin(f(a, b))) } - - /// collection of functions to create built in objects pub mod built_in_objects { - const TEMP_BADGE_DUR_MIN:u64 = 30; + const TEMP_BADGE_DUR_MIN: u64 = 30; - use std::{sync::Arc, time::{Duration, Instant}}; + use std::{ + sync::Arc, + time::{Duration, Instant}, + }; use twitch_irc::message::ServerMessage; - - use super::{execution_async,command::Command,Bot,Badge,super::modules::Status}; + use super::{super::modules::Status, command::Command, execution_async, Badge, Bot}; /// create a vector of command build in objects - pub fn create_commands() -> Vec<Command> - { + pub fn create_commands() -> Vec<Command> { let mut cmds = vec![]; cmds.push(create_disable_cmd()); @@ -120,31 +116,45 @@ pub mod built_in_objects { cmds.push(create_iam_role_cmd()); cmds - } fn create_disable_cmd() -> Command { /* 1. Create a new blank cmd */ - let mut cmd = Command::new(vec!["disable".to_string()],"".to_string()); + let mut cmd = Command::new(vec!["disable".to_string()], "".to_string()); /* 2. Define an async fn callback execution */ - async fn execbody(bot:Arc<Bot>,message:ServerMessage) -> Result<String,String> { + async fn execbody(bot: Arc<Bot>, message: ServerMessage) -> Result<String, String> { if let ServerMessage::Privmsg(msg) = message { let mut action_taken = false; - for (i,arg) in msg.message_text.replace("\u{e0000}","").trim().split(" ").enumerate() { + for (i, arg) in msg + .message_text + .replace("\u{e0000}", "") + .trim() + .split(" ") + .enumerate() + { if i > 1 { - if bot.get_channel_module_status(msg.channel_login.clone(), arg.to_string()).await == Status::Enabled { + if bot + .get_channel_module_status(msg.channel_login.clone(), arg.to_string()) + .await + == Status::Enabled + { action_taken = true; - bot.disable_module(msg.channel_login.clone(), arg.to_string()).await; + bot.disable_module(msg.channel_login.clone(), arg.to_string()) + .await; } - } } if action_taken { - let _ = bot.chat.lock().await.say_in_reply_to(&msg, String::from("Disabled!")).await ; + let _ = bot + .chat + .lock() + .await + .say_in_reply_to(&msg, String::from("Disabled!")) + .await; } } - Result::Err("Not Valid message type".to_string()) + Result::Err("Not Valid message type".to_string()) } /* 3. Set and Store the execution body using `execution_async()` */ @@ -156,50 +166,61 @@ pub mod built_in_objects { /* 5. optionally, set min badge*/ cmd.set_min_badge(Badge::Moderator /* ::Moderator */); cmd - - } + } fn create_enable_cmd() -> Command { /* 1. Create a new blank cmd */ - let mut cmd = Command::new(vec!["enable".to_string()],"".to_string()); + let mut cmd = Command::new(vec!["enable".to_string()], "".to_string()); /* 2. Define an async fn callback execution */ - async fn execbody(bot:Arc<Bot>,message:ServerMessage) -> Result<String,String> { + async fn execbody(bot: Arc<Bot>, message: ServerMessage) -> Result<String, String> { if let ServerMessage::Privmsg(msg) = message { - - let mut bot_message="".to_string(); + let mut bot_message = "".to_string(); let mut re_enabled = false; - for (i,arg) in msg.message_text.replace("\u{e0000}","").trim().split(" ").enumerate() { + for (i, arg) in msg + .message_text + .replace("\u{e0000}", "") + .trim() + .split(" ") + .enumerate() + { if i > 1 { + if Status::Disabled + == bot + .get_channel_module_status( + msg.channel_login.clone(), + arg.to_string(), + ) + .await + { + bot.enable_module(msg.channel_login.clone(), arg.to_string()) + .await; - if Status::Disabled == bot.get_channel_module_status(msg.channel_login.clone(), arg.to_string()).await { - bot.enable_module(msg.channel_login.clone(), arg.to_string()).await; - //bot.get_modules() if let Some(found_mod) = bot.get_module(arg.to_string()).await { - bot_message = bot_message.to_string() + found_mod.get_bot_read_description().as_str(); + bot_message = bot_message.to_string() + + found_mod.get_bot_read_description().as_str(); } re_enabled = true; } - } } if re_enabled { - - if bot_message.len() > 250 { + if bot_message.len() > 250 { bot_message = bot_message[..250].to_string(); } - let _ = bot.chat.lock().await.say_in_reply_to(&msg, - format!("Enabled! {}", bot_message) - ).await ; - + let _ = bot + .chat + .lock() + .await + .say_in_reply_to(&msg, format!("Enabled! {}", bot_message)) + .await; } - - } - Result::Err("Not Valid message type".to_string()) + } + Result::Err("Not Valid message type".to_string()) } /* 3. Set and Store the execution body using `execution_async()` */ @@ -211,98 +232,158 @@ pub mod built_in_objects { /* 5. optionally, set min badge*/ cmd.set_min_badge(Badge::Moderator); cmd - } - + } /// adminonly command that grants a temporary role fn create_iam_role_cmd() -> Command { /* 1. Create a new blank cmd */ - let mut cmd = Command::new(vec![ - "I am ".to_string(), - "I'm ".to_string(), - "Im a ".to_string(), - ],"".to_string()); + let mut cmd = Command::new( + vec!["I am ".to_string(), "I'm ".to_string(), "Im a ".to_string()], + "".to_string(), + ); /* 2. Define an async fn callback execution */ - async fn execbody(bot:Arc<Bot>,message:ServerMessage) -> Result<String,String> { + async fn execbody(bot: Arc<Bot>, message: ServerMessage) -> Result<String, String> { if let ServerMessage::Privmsg(msg) = message { - for (i,arg) in msg.message_text.replace("\u{e0000}","").trim().split(" ").enumerate() { + for (i, arg) in msg + .message_text + .replace("\u{e0000}", "") + .trim() + .split(" ") + .enumerate() + { if i > 1 { // bot.disable_module(msg.channel_login.clone(), arg.to_string()).await; // #todo - // if not dont have the badge or have a lower priviledge badge + // if not dont have the badge or have a lower priviledge badge // and they dont have an active guest badge, ths admin can be // recognzed wth that badge if arg == "mod" || arg == "moderator" { - let curr_temp_badges = bot.get_channel_guest_badges(msg.sender.login.clone(), msg.channel_login.clone()).await; + let curr_temp_badges = bot + .get_channel_guest_badges( + msg.sender.login.clone(), + msg.channel_login.clone(), + ) + .await; let mut found = false; for temp_badge in curr_temp_badges { if temp_badge.0 == Badge::Moderator { found = true; - } + } } if found { /* do nothing */ } else { bot.issue_new_guest_badge( - msg.sender.login.clone(), - msg.channel_login.clone(), - Badge::Moderator, Instant::now(), Duration::from_secs(60*TEMP_BADGE_DUR_MIN)).await; + msg.sender.login.clone(), + msg.channel_login.clone(), + Badge::Moderator, + Instant::now(), + Duration::from_secs(60 * TEMP_BADGE_DUR_MIN), + ) + .await; - let _ = bot.chat.lock().await.say_in_reply_to(&msg, - format!("Temp {:?} issued for {:?} minutes",Badge::Moderator,TEMP_BADGE_DUR_MIN) - ).await ; + let _ = bot + .chat + .lock() + .await + .say_in_reply_to( + &msg, + format!( + "Temp {:?} issued for {:?} minutes", + Badge::Moderator, + TEMP_BADGE_DUR_MIN + ), + ) + .await; } - } if arg == "vip" { - let curr_temp_badges = bot.get_channel_guest_badges(msg.sender.login.clone(), msg.channel_login.clone()).await; + let curr_temp_badges = bot + .get_channel_guest_badges( + msg.sender.login.clone(), + msg.channel_login.clone(), + ) + .await; let mut found = false; for temp_badge in curr_temp_badges { if temp_badge.0 == Badge::Vip { found = true; - } + } } if found { /* do nothing */ } else { bot.issue_new_guest_badge( - msg.sender.login.clone(), - msg.channel_login.clone(), - Badge::Vip, Instant::now(), Duration::from_secs(60*TEMP_BADGE_DUR_MIN)).await; + msg.sender.login.clone(), + msg.channel_login.clone(), + Badge::Vip, + Instant::now(), + Duration::from_secs(60 * TEMP_BADGE_DUR_MIN), + ) + .await; - let _ = bot.chat.lock().await.say_in_reply_to(&msg, - format!("Temp {:?} issued for {:?} minutes for the bot admin",Badge::Vip,TEMP_BADGE_DUR_MIN) - ).await ; + let _ = bot + .chat + .lock() + .await + .say_in_reply_to( + &msg, + format!( + "Temp {:?} issued for {:?} minutes for the bot admin", + Badge::Vip, + TEMP_BADGE_DUR_MIN + ), + ) + .await; } } if arg == "broadcaster" || arg == "strimmer" || arg == "streamer" { - let curr_temp_badges = bot.get_channel_guest_badges(msg.sender.login.clone(), msg.channel_login.clone()).await; + let curr_temp_badges = bot + .get_channel_guest_badges( + msg.sender.login.clone(), + msg.channel_login.clone(), + ) + .await; let mut found = false; for temp_badge in curr_temp_badges { if temp_badge.0 == Badge::Broadcaster { found = true; - } + } } if found { /* do nothing */ } else { bot.issue_new_guest_badge( - msg.sender.login.clone(), - msg.channel_login.clone(), - Badge::Broadcaster, Instant::now(), Duration::from_secs(60*TEMP_BADGE_DUR_MIN)).await; - - let _ = bot.chat.lock().await.say_in_reply_to(&msg, - format!("Temp {:?} issued for {:?} minutes for the bot admin",Badge::Broadcaster,TEMP_BADGE_DUR_MIN) - ).await ; + msg.sender.login.clone(), + msg.channel_login.clone(), + Badge::Broadcaster, + Instant::now(), + Duration::from_secs(60 * TEMP_BADGE_DUR_MIN), + ) + .await; + + let _ = bot + .chat + .lock() + .await + .say_in_reply_to( + &msg, + format!( + "Temp {:?} issued for {:?} minutes for the bot admin", + Badge::Broadcaster, + TEMP_BADGE_DUR_MIN + ), + ) + .await; } } } } // let _ = bot.chat.lock().await.say_in_reply_to(&msg, String::from("Disabled!")).await ; } - Result::Err("Not Valid message type".to_string()) + Result::Err("Not Valid message type".to_string()) } /* 3. Set and Store the execution body using `execution_async()` */ @@ -314,8 +395,5 @@ pub mod built_in_objects { // /* 5. optionally, set min badge*/ // cmd.set_min_badge(Badge::Moderator); cmd - - } - - -} \ No newline at end of file + } +} diff --git a/forcebot_core/src/botcore/bot_objects/command.rs b/forcebot_core/src/botcore/bot_objects/command.rs index b72cc8a..10a1e6b 100644 --- a/forcebot_core/src/botcore/bot_objects/command.rs +++ b/forcebot_core/src/botcore/bot_objects/command.rs @@ -2,131 +2,124 @@ use std::sync::Arc; use twitch_irc::message::{PrivmsgMessage, ServerMessage}; - -use super::{execution_async,Bot,Badge,command_condition_async}; - +use super::{command_condition_async, execution_async, Badge, Bot}; use super::{CommandTrigger, ExecBody}; /// Bot `Command` that stores trigger condition callback and a execution functon -/// +/// /// A prefix character or phrase can be defined for the bot to evaluate a trigger condition -/// +/// /// A command or command phrase defines the phrase after the prefix phrase -/// +/// /// If no min badge role is provided, Broadcaster is defaulted. All commands require at least a vip role -/// -/// AdminOnly commands can only be ran by admin -/// +/// +/// AdminOnly commands can only be ran by admin +/// /// Use `execution_async()` on custom async execution bodies #[derive(Clone)] -pub struct Command -{ - commands : Vec<String>, - exec_fn : Arc<ExecBody>, - min_badge : Badge, +pub struct Command { + commands: Vec<String>, + exec_fn: Arc<ExecBody>, + min_badge: Badge, /// only admins can run - default : `true` - admin_only : bool, + admin_only: bool, /// admin role overrides channel badge - default : `false` - admin_override : bool, - prefix : String, - custom_cond_fn : fn(Arc<Bot>,PrivmsgMessage) -> bool, - custom_cond_async : Arc<CommandTrigger>, + admin_override: bool, + prefix: String, + custom_cond_fn: fn(Arc<Bot>, PrivmsgMessage) -> bool, + custom_cond_async: Arc<CommandTrigger>, } -impl Command -{ - +impl Command { /// Creates a new empty `Command` using command `String` and prefix `String` /// Pass an empty string prefix if the bot should use the bot default /// /// Call `set_trigger_cond_fn()` and `set_exec_fn()` to trigger & execution function callbacks /// if a blank prefix is given, the bot will look for the bot prefix instead - /// + /// /// By default, the new command is admin_only - pub fn new(commands:Vec<String>,prefix:String) -> Command { - - async fn execbody(_:Arc<Bot>,_:ServerMessage) -> Result<String,String> - { Result::Ok("success".to_string()) } - async fn condition01(_:Arc<Bot>,_:PrivmsgMessage) -> bool { true } + pub fn new(commands: Vec<String>, prefix: String) -> Command { + async fn execbody(_: Arc<Bot>, _: ServerMessage) -> Result<String, String> { + Result::Ok("success".to_string()) + } + async fn condition01(_: Arc<Bot>, _: PrivmsgMessage) -> bool { + true + } Command { - commands , - prefix , - exec_fn : Arc::new(execution_async(execbody)), - min_badge : Badge::Vip, - admin_only : true, - admin_override : false , - custom_cond_fn : |_:Arc<Bot>,_:PrivmsgMessage| true, - custom_cond_async : Arc::new(command_condition_async(condition01)), + commands, + prefix, + exec_fn: Arc::new(execution_async(execbody)), + min_badge: Badge::Vip, + admin_only: true, + admin_override: false, + custom_cond_fn: |_: Arc<Bot>, _: PrivmsgMessage| true, + custom_cond_async: Arc::new(command_condition_async(condition01)), } } /// set a trigger condition callback that returns true if the command should trigger - pub fn set_custom_cond_fn(&mut self,cond_fn: fn(Arc<Bot>,PrivmsgMessage) -> bool) { + pub fn set_custom_cond_fn(&mut self, cond_fn: fn(Arc<Bot>, PrivmsgMessage) -> bool) { self.custom_cond_fn = cond_fn; } - /// sets the async trigger condition for listener - /// + /// /// Same as `set_custom_cond_fn()` , but async define - /// + /// /// Use`execution_async()` on the async fn when storing - /// - /// Example - + /// + /// Example - /// ```rust /// /* 1. Create a new blank Listener */ /// let mut cmd = Command::new(); /// /// /* 2. define an async function */ /// async fn condition01(_:Arc<Bot>,_:ServerMessage) -> bool { true } - /// + /// /// /* 3. Set and Store the execution body using `execution_async()` */ /// cmd.set_custom_cond_async(condition_async(condition01)); /// ``` - /// - pub fn set_custom_cond_async(&mut self,condition:CommandTrigger ) { + /// + pub fn set_custom_cond_async(&mut self, condition: CommandTrigger) { self.custom_cond_async = Arc::new(condition); } /// sets the execution body of the listener for when it triggers - /// + /// /// Use`execution_async()` on the async fn when storing - /// - /// - pub fn set_exec_fn(&mut self,exec_fn:ExecBody ) { + /// + /// + pub fn set_exec_fn(&mut self, exec_fn: ExecBody) { self.exec_fn = Arc::new(exec_fn); } /// checks if the trigger condition is met /// specifically if the message is a valid command and min badge roles provided - /// - pub async fn command_triggered(&self,bot:Arc<Bot>,msg:PrivmsgMessage) -> bool { - - fn cmd_called(cmd:&Command,bot:Arc<Bot>,message:PrivmsgMessage) -> bool { - let mut prefixed_cmd = "".to_string(); - if cmd.prefix == "" { - prefixed_cmd.push_str(&bot.get_prefix()); - } else { - prefixed_cmd.push_str(&cmd.prefix); + /// + pub async fn command_triggered(&self, bot: Arc<Bot>, msg: PrivmsgMessage) -> bool { + fn cmd_called(cmd: &Command, bot: Arc<Bot>, message: PrivmsgMessage) -> bool { + let mut prefixed_cmd = "".to_string(); + if cmd.prefix == "" { + prefixed_cmd.push_str(&bot.get_prefix()); + } else { + prefixed_cmd.push_str(&cmd.prefix); + } + for cmd_nm in &cmd.commands { + prefixed_cmd.push_str(cmd_nm); + if message.message_text.starts_with(prefixed_cmd.as_str()) { + return true; } - for cmd_nm in &cmd.commands { - prefixed_cmd.push_str(cmd_nm); - if message.message_text.starts_with(prefixed_cmd.as_str()) { - return true; - } - }; - return false; + } + return false; } - - async fn caller_badge_ok(cmd:&Command,bot:Arc<Bot>,message:PrivmsgMessage) -> bool { - + async fn caller_badge_ok(cmd: &Command, bot: Arc<Bot>, message: PrivmsgMessage) -> bool { // senders that are admins skip badge check if the command is adminonly - if cmd.admin_only && bot.get_admins().contains(&message.sender.login) { + if cmd.admin_only && bot.get_admins().contains(&message.sender.login) { return true; - } ; + }; // adminOnly commands will can only be ran by admins if cmd.admin_only && bot.get_admins().contains(&message.sender.login) { @@ -134,35 +127,41 @@ impl Command } // admin role overrides badge check if enabled - if cmd.admin_override && bot.get_admins().contains( &message.sender.login) { return true; } + if cmd.admin_override && bot.get_admins().contains(&message.sender.login) { + return true; + } for badge in message.badges { - match cmd.min_badge { Badge::Broadcaster => { - if badge.name == "broadcaster" { return true } - else { return false } - }, - Badge::Moderator => { - match badge.name.as_str() { - "moderator" | "broadcaster" => return true, - _ => (), + if badge.name == "broadcaster" { + return true; + } else { + return false; } + } + Badge::Moderator => match badge.name.as_str() { + "moderator" | "broadcaster" => return true, + _ => (), }, - Badge::Vip => { - match badge.name.as_str() { - "vip" | "moderator" | "broadcaster" => return true, - _ => (), - } + Badge::Vip => match badge.name.as_str() { + "vip" | "moderator" | "broadcaster" => return true, + _ => (), }, } } - for temp_badge in bot.get_channel_guest_badges(message.sender.login, message.channel_login).await { - match (cmd.min_badge.clone(),temp_badge.0) { - (Badge::Broadcaster,Badge::Broadcaster) => return true, - (Badge::Moderator,Badge::Moderator) | (Badge::Moderator,Badge::Broadcaster) => return true, - (Badge::Vip,Badge::Vip)|(Badge::Vip,Badge::Moderator)|(Badge::Vip,Badge::Broadcaster) => return true, + for temp_badge in bot + .get_channel_guest_badges(message.sender.login, message.channel_login) + .await + { + match (cmd.min_badge.clone(), temp_badge.0) { + (Badge::Broadcaster, Badge::Broadcaster) => return true, + (Badge::Moderator, Badge::Moderator) + | (Badge::Moderator, Badge::Broadcaster) => return true, + (Badge::Vip, Badge::Vip) + | (Badge::Vip, Badge::Moderator) + | (Badge::Vip, Badge::Broadcaster) => return true, _ => (), } } @@ -170,59 +169,58 @@ impl Command return false; } - - /// determines if the command caller can run the command + /// determines if the command caller can run the command /// based on admin_only flag - /// + /// /// callers who are admins can run admin_only commands /// callers can run non-admin_only commands - fn admin_only_ok(cmd:&Command,bot:Arc<Bot>,message:PrivmsgMessage) -> bool { - if (cmd.admin_only && bot.get_admins().contains(&message.sender.login)) || !cmd.admin_only { + fn admin_only_ok(cmd: &Command, bot: Arc<Bot>, message: PrivmsgMessage) -> bool { + if (cmd.admin_only && bot.get_admins().contains(&message.sender.login)) + || !cmd.admin_only + { return true; } else { return false; } } - async fn custom_cond_ok(cmd:&Command,bot:Arc<Bot>,message:PrivmsgMessage) -> bool { - (cmd.custom_cond_fn)(bot.clone(),message.clone()) && (cmd.custom_cond_async)(bot,message).await + async fn custom_cond_ok(cmd: &Command, bot: Arc<Bot>, message: PrivmsgMessage) -> bool { + (cmd.custom_cond_fn)(bot.clone(), message.clone()) + && (cmd.custom_cond_async)(bot, message).await } // async fn quiet_off_ok(cmd:&Command,bot:Arc<Bot>,message:PrivmsgMessage) -> bool { - // !bot.chat.lock().await.get_channel_quiet(message.channel_login.clone()) + // !bot.chat.lock().await.get_channel_quiet(message.channel_login.clone()) // || bot.chat.lock().await.get_channel_quiet(message.channel_login.clone()) && cmd.commands.contains(&("quiet off".to_string())) // } - - cmd_called(self, bot.clone(), msg.clone()) && - caller_badge_ok(self, bot.clone(), msg.clone()).await && - admin_only_ok(self, bot.clone(), msg.clone()) && - custom_cond_ok(self, bot.clone(), msg.clone()).await + cmd_called(self, bot.clone(), msg.clone()) + && caller_badge_ok(self, bot.clone(), msg.clone()).await + && admin_only_ok(self, bot.clone(), msg.clone()) + && custom_cond_ok(self, bot.clone(), msg.clone()).await // && // quiet_off_ok(self, bot, msg).await - } /// executes the listeners executon body - pub async fn execute_fn(&self,bot:Arc<Bot>,msg:ServerMessage) -> Result<String, String> { - (self.exec_fn)(bot,msg).await + pub async fn execute_fn(&self, bot: Arc<Bot>, msg: ServerMessage) -> Result<String, String> { + (self.exec_fn)(bot, msg).await } - /// sets min_badge to run the cmd // pub fn set_min_badge(&mut self,min_badge:String) { - pub fn set_min_badge(&mut self,min_badge:Badge) { + pub fn set_min_badge(&mut self, min_badge: Badge) { self.min_badge = min_badge } /// sets admin_only - pub fn set_admin_only(&mut self,admin_only:bool) { + pub fn set_admin_only(&mut self, admin_only: bool) { self.admin_only = admin_only } - /// sets admin_override . This lets admins bypass + /// sets admin_override . This lets admins bypass /// badge restrictions - pub fn set_admin_override(&mut self,admin_override:bool) { + pub fn set_admin_override(&mut self, admin_override: bool) { self.admin_override = admin_override } } diff --git a/forcebot_core/src/botcore/bot_objects/listener.rs b/forcebot_core/src/botcore/bot_objects/listener.rs index 24f7ce3..2633b3b 100644 --- a/forcebot_core/src/botcore/bot_objects/listener.rs +++ b/forcebot_core/src/botcore/bot_objects/listener.rs @@ -1,124 +1,126 @@ use std::sync::Arc; - use twitch_irc::message::ServerMessage; use crate::Module; -use super::{execution_async,Bot,listener_condition_async}; - +use super::{execution_async, listener_condition_async, Bot}; + use super::{ExecBody, ListenerTrigger}; /// Bot `Listener` that stores trigger condition callback and a execution functon -/// -/// Use `Listener` functions to define the Trigger Condition & Execution callbacks. -/// When the Trigger callback is `true`, the Execution callback runs in the bot loop -/// +/// +/// Use `Listener` functions to define the Trigger Condition & Execution callbacks. +/// When the Trigger callback is `true`, the Execution callback runs in the bot loop +/// /// Create a new empty `Listener` with `new()` -/// +/// /// Use the following on the empty listener before loading it into the bot to set the callbacks -/// -/// - `set_trigger_cond_fn()` - to define the Trigger condition callback -/// +/// +/// - `set_trigger_cond_fn()` - to define the Trigger condition callback +/// /// - `set_exec_fn()` - to define the Execution Callback #[derive(Clone)] -pub struct Listener -{ +pub struct Listener { /// trigger condition - trigger_cond_fn : fn(Arc<Bot>,ServerMessage) -> bool, + trigger_cond_fn: fn(Arc<Bot>, ServerMessage) -> bool, /// trigger condition for async - trigger_cond_async : Arc<ListenerTrigger> , + trigger_cond_async: Arc<ListenerTrigger>, /// execution body - exec_fn : Arc<ExecBody>, - parent_module : Arc<Option<Module>>, + exec_fn: Arc<ExecBody>, + parent_module: Arc<Option<Module>>, } -impl Listener -{ - - /// Creates a new empty `Listener` - /// - /// Use `Listener` functions to define the Trigger Condition & Execution callbacks. - /// When the Trigger callback is `true`, the Execution callback runs in the bot loop - /// +impl Listener { + /// Creates a new empty `Listener` + /// + /// Use `Listener` functions to define the Trigger Condition & Execution callbacks. + /// When the Trigger callback is `true`, the Execution callback runs in the bot loop + /// /// Use the following on the empty listener before loading it into the bot to set the callbacks - /// - /// - `set_trigger_cond_fn()` - to define the Trigger condition callback - /// + /// + /// - `set_trigger_cond_fn()` - to define the Trigger condition callback + /// /// - `set_exec_fn()` - to define the Execution Callback pub fn new() -> Listener { - - async fn execbody(_:Arc<Bot>,_:ServerMessage) -> Result<String,String> {Result::Ok("success".to_string()) } - async fn condition01(_:Arc<Bot>,_:ServerMessage) -> bool { true } + async fn execbody(_: Arc<Bot>, _: ServerMessage) -> Result<String, String> { + Result::Ok("success".to_string()) + } + async fn condition01(_: Arc<Bot>, _: ServerMessage) -> bool { + true + } Listener { - trigger_cond_fn : |_:Arc<Bot>,_:ServerMessage| true, - trigger_cond_async : Arc::new(listener_condition_async(condition01)), - exec_fn : Arc::new(execution_async(execbody)), - parent_module : Arc::new(None), + trigger_cond_fn: |_: Arc<Bot>, _: ServerMessage| true, + trigger_cond_async: Arc::new(listener_condition_async(condition01)), + exec_fn: Arc::new(execution_async(execbody)), + parent_module: Arc::new(None), } } /// set a trigger conditin callback that returns true if the listener shoud trigger - pub fn set_trigger_cond_fn(&mut self,cond_fn: fn(Arc<Bot>,ServerMessage) -> bool) { + pub fn set_trigger_cond_fn(&mut self, cond_fn: fn(Arc<Bot>, ServerMessage) -> bool) { self.trigger_cond_fn = cond_fn; } /// sets the async trigger condition for listener - /// + /// /// Same as `set_trigger_cond_fn()` , but async define - /// + /// /// Use`condition_async()` on the async fn when storing - /// - /// Example - + /// + /// Example - /// ```rust /// /* 1. Create a new blank Listener */ /// let mut listener = Listener::new(); /// /// /* 2. define an async function */ /// async fn condition01(_:Arc<Bot>,_:ServerMessage) -> bool { true } - /// + /// /// /* 3. Set and Store the execution body using `execution_async()` */ /// listener.set_trigger_cond_async(condition_async(condition01)); /// ``` - /// - pub fn set_trigger_cond_async(&mut self,condition:ListenerTrigger ) { + /// + pub fn set_trigger_cond_async(&mut self, condition: ListenerTrigger) { self.trigger_cond_async = Arc::new(condition); } - /// sets the execution body of the listener for when it triggers - /// + /// /// Use`execution_async()` on the async fn when storing - /// - /// Example - + /// + /// Example - /// ```rust /// /* 1. Create a new blank Listener */ /// let mut listener = Listener::new(); /// /// /* 2. define an async function */ /// async fn execbody(_:Arc<Bot>,_:ServerMessage) -> Result<String,String> {Result::Ok("success".to_string()) } - /// + /// /// /* 3. Set and Store the execution body using `execution_async()` */ /// listener.set_exec_fn(execution_async(execbody)); /// ``` - /// - pub fn set_exec_fn(&mut self,exec_fn:ExecBody ) { + /// + pub fn set_exec_fn(&mut self, exec_fn: ExecBody) { self.exec_fn = Arc::new(exec_fn); } /// checks if the trigger condition is met - pub async fn cond_triggered(&self,bot:Arc<Bot>,msg:ServerMessage) -> bool { - + pub async fn cond_triggered(&self, bot: Arc<Bot>, msg: ServerMessage) -> bool { let list = Arc::new(self.clone()); - async fn defined_conditions_ok(list:Arc<Listener>,bot:Arc<Bot>,msg:ServerMessage) -> bool { + async fn defined_conditions_ok( + list: Arc<Listener>, + bot: Arc<Bot>, + msg: ServerMessage, + ) -> bool { // let list = Arc::new(self); - (list.trigger_cond_fn)(bot.clone(),msg.clone()) && (list.trigger_cond_async)(bot,msg).await + (list.trigger_cond_fn)(bot.clone(), msg.clone()) + && (list.trigger_cond_async)(bot, msg).await } // async fn quiet_off_ok(list:Arc<Listener>,bot:Arc<Bot>,message:ServerMessage) -> bool { // if let ServerMessage::Privmsg(msg) = message { - + // if let Some(parent_mod) = &*list.parent_module { // return !bot.chat.lock().await.get_channel_quiet(msg.channel_login) || parent_mod.get_names().contains(&"debug".to_string()); // } @@ -128,22 +130,18 @@ impl Listener // return true; /* quiet is off for non chat msgs */ // } - defined_conditions_ok(list.clone(), bot.clone(), msg.clone()).await + defined_conditions_ok(list.clone(), bot.clone(), msg.clone()).await // && // quiet_off_ok(list, bot, msg).await } /// executes the listeners executon body - pub async fn execute_fn(&self,bot:Arc<Bot>,msg:ServerMessage) -> Result<String, String> { - - (self.exec_fn)(bot,msg).await + pub async fn execute_fn(&self, bot: Arc<Bot>, msg: ServerMessage) -> Result<String, String> { + (self.exec_fn)(bot, msg).await } /// sets parent module - pub fn set_parent_module(&mut self,module:Module) { + pub fn set_parent_module(&mut self, module: Module) { self.parent_module = Arc::new(Some(module)); } - - } - diff --git a/forcebot_core/src/botcore/built_in_mods.rs b/forcebot_core/src/botcore/built_in_mods.rs index 53e04a3..03b8d28 100644 --- a/forcebot_core/src/botcore/built_in_mods.rs +++ b/forcebot_core/src/botcore/built_in_mods.rs @@ -4,11 +4,7 @@ use crate::Bot; pub mod quiet; - /// used to internally load internal modules -pub async fn load_built_in_mods(bot:&Bot){ - +pub async fn load_built_in_mods(bot: &Bot) { bot.load_module(quiet::create_module()).await; - } - diff --git a/forcebot_core/src/botcore/built_in_mods/quiet.rs b/forcebot_core/src/botcore/built_in_mods/quiet.rs index 865c81b..7a12cc1 100644 --- a/forcebot_core/src/botcore/built_in_mods/quiet.rs +++ b/forcebot_core/src/botcore/built_in_mods/quiet.rs @@ -4,18 +4,17 @@ use twitch_irc::message::ServerMessage; use crate::{execution_async, Badge, Bot, Command, Module}; - /// quiet the bot in a channel /// -/// use +/// use /// `quiet on` /// `quiet off` -/// -/// -/// +/// +/// +/// /// Use this function when loading modules into the bot -/// +/// /// For example /// ```rust /// bot.load_module(quiet::create_module()); @@ -23,38 +22,33 @@ use crate::{execution_async, Badge, Bot, Command, Module}; /// pub fn create_module() -> Module { /* 1. Create a new module */ - let mut custom_mod = Module::new( - vec!["quiet".to_string()], - "".to_string()); + let mut custom_mod = Module::new(vec!["quiet".to_string()], "".to_string()); /* 2. Load the cmd into a new module */ custom_mod.load_command(cmd_quiet_on()); custom_mod.load_command(cmd_quiet_off()); custom_mod - } /// Command definition for quiet command fn cmd_quiet_on() -> Command { /* 1. Create a new cmd */ - let mut cmd = Command::new(vec!["quiet on".to_string()],"".to_string()); + let mut cmd = Command::new(vec!["quiet on".to_string()], "".to_string()); /* 2. Define exec callback */ - async fn execbody(bot:Arc<Bot>,message:ServerMessage) -> Result<String,String> { + async fn execbody(bot: Arc<Bot>, message: ServerMessage) -> Result<String, String> { if let ServerMessage::Privmsg(msg) = message { - // dbg!("quiet on called"); let chatlock = bot.chat.lock().await; - let _=chatlock.say_in_reply_to(&msg, "Shush ".to_string()).await; - + let _ = chatlock.say_in_reply_to(&msg, "Shush ".to_string()).await; + chatlock.set_channel_quiet(msg.channel_login.clone(), true); - println!("channel {} set quiet true",msg.channel_login); - + println!("channel {} set quiet true", msg.channel_login); return Result::Ok("Success".to_string()); } - Result::Err("Not Valid message type".to_string()) + Result::Err("Not Valid message type".to_string()) } /* 3. Set Command flags */ @@ -69,21 +63,21 @@ fn cmd_quiet_on() -> Command { /// Command definition for quiet command fn cmd_quiet_off() -> Command { /* 1. Create a new cmd */ - let mut cmd = Command::new(vec!["quiet off".to_string()],"".to_string()); + let mut cmd = Command::new(vec!["quiet off".to_string()], "".to_string()); /* 2. Define exec callback */ - async fn execbody(bot:Arc<Bot>,message:ServerMessage) -> Result<String,String> { + async fn execbody(bot: Arc<Bot>, message: ServerMessage) -> Result<String, String> { if let ServerMessage::Privmsg(msg) = message { - let chatlock = bot.chat.lock().await; - + chatlock.set_channel_quiet(msg.channel_login.clone(), false); - let _=chatlock.say_in_reply_to(&msg, "GoodGirl I'll be good for u chat rar ".to_string()).await; - - - println!("channel {} set quiet false",msg.channel_login); + let _ = chatlock + .say_in_reply_to(&msg, "GoodGirl I'll be good for u chat rar ".to_string()) + .await; + + println!("channel {} set quiet false", msg.channel_login); } - Result::Err("Not Valid message type".to_string()) + Result::Err("Not Valid message type".to_string()) } /* 3. Set Command flags */ diff --git a/forcebot_core/src/botcore/chat.rs b/forcebot_core/src/botcore/chat.rs index d548a4f..42f1ec6 100644 --- a/forcebot_core/src/botcore/chat.rs +++ b/forcebot_core/src/botcore/chat.rs @@ -1,34 +1,36 @@ -use std::{fmt::Error, ops::Mul, rc::Rc, sync::{Arc, Mutex, RwLock}}; +use std::{ + fmt::Error, + ops::Mul, + rc::Rc, + sync::{Arc, Mutex, RwLock}, +}; -use twitch_irc::{login::StaticLoginCredentials, message::ReplyToMessage, SecureTCPTransport, TwitchIRCClient}; +use twitch_irc::{ + login::StaticLoginCredentials, message::ReplyToMessage, SecureTCPTransport, TwitchIRCClient, +}; use crate::Bot; /// Bot API to send messages to send messages to chat -/// +/// /// Uses TwitchIRCClient say_in_reply_to() but enforces controls like quiet -/// -/// +/// +/// -pub struct Chat -{ +pub struct Chat { /// outbound chat client msg stream - pub client: TwitchIRCClient<SecureTCPTransport,StaticLoginCredentials>, + pub client: TwitchIRCClient<SecureTCPTransport, StaticLoginCredentials>, /// channel_quiet - channel_quiet_yn: RwLock<Vec<(String,RwLock<bool>)>>, - + channel_quiet_yn: RwLock<Vec<(String, RwLock<bool>)>>, } impl Chat { - - - pub async fn new(client:TwitchIRCClient<SecureTCPTransport,StaticLoginCredentials>) - -> Chat { - Chat { + pub async fn new(client: TwitchIRCClient<SecureTCPTransport, StaticLoginCredentials>) -> Chat { + Chat { client, // parent_bot : Mutex::new(Bot::new().await) , - channel_quiet_yn : RwLock::new(vec![]), - } + channel_quiet_yn: RwLock::new(vec![]), + } } // pub fn set_parent_bot(&self,parent_bot_in:Arc<Bot>) @@ -38,23 +40,20 @@ impl Chat { // } /// helper - fn ok_to_send(&self,channel_login: String) -> bool { - - fn not_quiet_ok(chat:&Chat,channel_login:String) -> bool { + fn ok_to_send(&self, channel_login: String) -> bool { + fn not_quiet_ok(chat: &Chat, channel_login: String) -> bool { // let lock = chat.parent_bot.lock().unwrap(); // let a = lock.as_ref(); // if let Some(bot) = &*lock { return !chat.get_channel_quiet(channel_login); // } - // true + // true } not_quiet_ok(self, channel_login) - } - /// Get the quiet status of a channel - pub fn get_channel_quiet(&self,channel:String) -> bool { + pub fn get_channel_quiet(&self, channel: String) -> bool { for a in self.channel_quiet_yn.read().unwrap().iter() { if a.0 == channel { return a.1.read().unwrap().clone(); @@ -62,9 +61,9 @@ impl Chat { } return false; } - + /// Get the quiet status of a channel - pub fn set_channel_quiet(&self,channel:String,quiet_on:bool) { + pub fn set_channel_quiet(&self, channel: String, quiet_on: bool) { let mut found = false; let chnlquiet = self.channel_quiet_yn.read().unwrap(); @@ -77,22 +76,20 @@ impl Chat { } } drop(chnlquiet); - + if !found { // dbg!("set chn quiet > !found channel quiet status"); let mut chnlquiet = self.channel_quiet_yn.write().unwrap(); - chnlquiet.push((channel,RwLock::new(quiet_on))); + chnlquiet.push((channel, RwLock::new(quiet_on))); drop(chnlquiet); } - } - - - pub async fn say_in_reply_to(&self, - reply_to: &impl ReplyToMessage, - message: String - ) -> Result<(),()> { + pub async fn say_in_reply_to( + &self, + reply_to: &impl ReplyToMessage, + message: String, + ) -> Result<(), ()> { // reply_to.channel_login() if self.ok_to_send(reply_to.channel_login().to_string()) { match self.client.say_in_reply_to(reply_to, message).await { @@ -102,50 +99,42 @@ impl Chat { } else { return Err(()); } - } - pub async fn say(&self, - channel_login: String, - message: String, - ) -> Result<(),()> { + pub async fn say(&self, channel_login: String, message: String) -> Result<(), ()> { if self.ok_to_send(channel_login.to_string()) { -match self.client.say(channel_login, message).await { - Ok(_) => return Ok(()), - Err(_) => return Err(()), - } + match self.client.say(channel_login, message).await { + Ok(_) => return Ok(()), + Err(_) => return Err(()), + } } else { return Err(()); } } - pub async fn me(&self, - channel_login: String, - message: String, - ) -> Result<(),()> { + pub async fn me(&self, channel_login: String, message: String) -> Result<(), ()> { if self.ok_to_send(channel_login.to_string()) { -match self.client.me(channel_login, message).await { - Ok(_) => return Ok(()), - Err(_) => return Err(()), - } + match self.client.me(channel_login, message).await { + Ok(_) => return Ok(()), + Err(_) => return Err(()), + } } else { return Err(()); } - } - pub async fn me_in_reply_to(&self, + pub async fn me_in_reply_to( + &self, reply_to: &impl ReplyToMessage, - message: String -) -> Result<(),()> { - if self.ok_to_send(reply_to.channel_login().to_string()) { - match self.client.me_in_reply_to(reply_to, message).await { - Ok(_) => return Ok(()), - Err(_) => return Err(()), + message: String, + ) -> Result<(), ()> { + if self.ok_to_send(reply_to.channel_login().to_string()) { + match self.client.me_in_reply_to(reply_to, message).await { + Ok(_) => return Ok(()), + Err(_) => return Err(()), + } + } else { + return Err(()); + } } - } else { - return Err(()); - } - - } -} \ No newline at end of file +} diff --git a/forcebot_core/src/botcore/modules.rs b/forcebot_core/src/botcore/modules.rs index d3802b5..dd9cc29 100644 --- a/forcebot_core/src/botcore/modules.rs +++ b/forcebot_core/src/botcore/modules.rs @@ -1,11 +1,9 @@ - // use std::sync::{Arc, Mutex}; use super::bot_objects::command::Command; use super::bot_objects::listener::Listener; - -#[derive(PartialEq, Eq,Debug,Clone)] +#[derive(PartialEq, Eq, Debug, Clone)] pub enum Status { Disabled, Enabled, @@ -13,24 +11,22 @@ pub enum Status { } /// Bot `Module` that groups a set of `bot_objects` -/// +/// /// Elevated chatters can disable modules by their name or chat alias #[derive(Clone)] -pub struct Module -{ +pub struct Module { name: Vec<String>, // _alias: String, - bot_read_description : String, + bot_read_description: String, listeners: Vec<Listener>, commands: Vec<Command>, // disable module at load for bot channels default_status_per_channel: Status, } -impl Module -{ +impl Module { /// create a new module - pub fn new(name:Vec<String>,bot_read_description:String) -> Module { + pub fn new(name: Vec<String>, bot_read_description: String) -> Module { Module { name, // _alias: alias, @@ -42,20 +38,20 @@ impl Module } /// Loads a `Listener` into the module - pub fn load_listener(&mut self,mut l : Listener) { + pub fn load_listener(&mut self, mut l: Listener) { l.set_parent_module(self.clone()); self.listeners.push(l); } /// Loads a `Command` into the module - pub fn load_command(&mut self,c : Command) { - self.commands.push(c); + pub fn load_command(&mut self, c: Command) { + self.commands.push(c); } pub fn get_listeners(&self) -> Vec<Listener> { self.listeners.clone() } - + pub fn get_commands(&self) -> Vec<Command> { self.commands.clone() } @@ -68,13 +64,11 @@ impl Module self.bot_read_description.clone() } - pub fn set_status_by_default(&mut self,status:Status){ + pub fn set_status_by_default(&mut self, status: Status) { self.default_status_per_channel = status; } pub fn get_status_by_default(&self) -> Status { self.default_status_per_channel.clone() } - - -} \ No newline at end of file +} diff --git a/forcebot_core/src/custom_mods.rs b/forcebot_core/src/custom_mods.rs index 754b7b9..fdbd664 100644 --- a/forcebot_core/src/custom_mods.rs +++ b/forcebot_core/src/custom_mods.rs @@ -1,4 +1,4 @@ +pub mod debug; pub mod guest_badge; pub mod pyramid; -pub mod debug; -// pub mod quiet; \ No newline at end of file +// pub mod quiet; diff --git a/forcebot_core/src/custom_mods/debug.rs b/forcebot_core/src/custom_mods/debug.rs index 4bbfc73..0457e54 100644 --- a/forcebot_core/src/custom_mods/debug.rs +++ b/forcebot_core/src/custom_mods/debug.rs @@ -1,23 +1,20 @@ - - use std::sync::Arc; use crate::{execution_async, modules::Status, Bot, Command, Listener, Module}; use twitch_irc::message::ServerMessage; /// debug module -/// +/// /// Commands to enable debugging messages in chat -/// +/// /// `debug on` to start -/// +/// /// `debug off` to stop -/// -/// - +/// +/// /// Use this function when loading modules into the bot -/// +/// /// For example /// ```rust /// bot.load_module(debug::create_module()); @@ -25,38 +22,42 @@ use twitch_irc::message::ServerMessage; /// pub fn create_module() -> Module { /* 1. Create a new module */ - let mut custom_mod = Module::new( - vec!["debug".to_string()], - "".to_string()); + let mut custom_mod = Module::new(vec!["debug".to_string()], "".to_string()); /* 2. Load the cmd into a new module */ custom_mod.load_command(cmd_debug_on()); custom_mod.load_command(cmd_debug_off()); custom_mod - } /// Command definition for debug command fn cmd_debug_on() -> Command { /* 1. Create a new cmd */ - let mut cmd = Command::new(vec!["debug on".to_string()],"".to_string()); + let mut cmd = Command::new(vec!["debug on".to_string()], "".to_string()); /* 2. Define exec callback */ - async fn execbody(bot:Arc<Bot>,message:ServerMessage) -> Result<String,String> { + async fn execbody(bot: Arc<Bot>, message: ServerMessage) -> Result<String, String> { if let ServerMessage::Privmsg(msg) = message { // dbg!("debug cmd on executed"); - let modulename="debug listener".to_string(); + let modulename = "debug listener".to_string(); - if let Status::NotLoaded = bot.get_channel_module_status(msg.channel_login.clone(), modulename.clone()).await { + if let Status::NotLoaded = bot + .get_channel_module_status(msg.channel_login.clone(), modulename.clone()) + .await + { let module = create_listener_module(modulename.clone()); bot.load_module(module.clone()).await; } let modl = bot.get_module(modulename).await.unwrap(); - bot.enable_module(msg.channel_login.clone(), modl.get_names().first().unwrap().clone()).await; - println!("Debug enabled for channel {}",msg.channel_login); + bot.enable_module( + msg.channel_login.clone(), + modl.get_names().first().unwrap().clone(), + ) + .await; + println!("Debug enabled for channel {}", msg.channel_login); } - Result::Err("Not Valid message type".to_string()) + Result::Err("Not Valid message type".to_string()) } /* 3. Set Command flags */ @@ -71,24 +72,28 @@ fn cmd_debug_on() -> Command { /// Command definition for debug off command fn cmd_debug_off() -> Command { /* 1. Create a new cmd */ - let mut cmd = Command::new(vec!["debug off".to_string()],"".to_string()); + let mut cmd = Command::new(vec!["debug off".to_string()], "".to_string()); /* 2. Define exec callback */ - async fn execbody(bot:Arc<Bot>,message:ServerMessage) -> Result<String,String> { + async fn execbody(bot: Arc<Bot>, message: ServerMessage) -> Result<String, String> { if let ServerMessage::Privmsg(msg) = message { // dbg!("debug cmd on executed"); - let modulename="debug listener".to_string(); + let modulename = "debug listener".to_string(); // if let Status::NotLoaded = bot.get_channel_module_status(msg.channel_login.clone(), modulename.clone()).await { // let module = create_listener_module(modulename.clone()); // bot.load_module(module.clone()).await; // } let modl = bot.get_module(modulename).await.unwrap(); - bot.disable_module(msg.channel_login.clone(), modl.get_names().first().unwrap().clone()).await; - println!("Debug disabled for channel {}",msg.channel_login); + bot.disable_module( + msg.channel_login.clone(), + modl.get_names().first().unwrap().clone(), + ) + .await; + println!("Debug disabled for channel {}", msg.channel_login); } - Result::Err("Not Valid message type".to_string()) + Result::Err("Not Valid message type".to_string()) } /* 3. Set Command flags */ @@ -100,41 +105,34 @@ fn cmd_debug_off() -> Command { cmd } -fn create_listener_module(name:String) -> Module { - - let mut custom_mod = Module::new( - vec![name], - "".to_string()); - // dbg!("debug listener module created"); +fn create_listener_module(name: String) -> Module { + let mut custom_mod = Module::new(vec![name], "".to_string()); + // dbg!("debug listener module created"); custom_mod.load_listener(cmd_debug_listener()); custom_mod.set_status_by_default(Status::Disabled); custom_mod - } /// Listener for debug fn cmd_debug_listener() -> Listener { - // dbg!("Creating debug listener"); - /* 2a. Create a new blank Listener */ - let mut listener = Listener::new(); + // dbg!("Creating debug listener"); + /* 2a. Create a new blank Listener */ + let mut listener = Listener::new(); - /* 2b. Set a trigger condition function for listener */ - listener.set_trigger_cond_fn( - |_:Arc<Bot>,_:ServerMessage| true - ); - - /* 2c. Define an async fn callback execution */ - async fn execbody(_:Arc<Bot>,message:ServerMessage) -> Result<String,String> { - if let ServerMessage::Privmsg(msg) = message { - dbg!(msg); /* outputs message to debug */ - } - Result::Err("Not Valid message type".to_string()) - } - - /* 2d. Set and Store the execution body using `execution_async()` */ - listener.set_exec_fn(execution_async(execbody)); + /* 2b. Set a trigger condition function for listener */ + listener.set_trigger_cond_fn(|_: Arc<Bot>, _: ServerMessage| true); - listener + /* 2c. Define an async fn callback execution */ + async fn execbody(_: Arc<Bot>, message: ServerMessage) -> Result<String, String> { + if let ServerMessage::Privmsg(msg) = message { + dbg!(msg); /* outputs message to debug */ + } + Result::Err("Not Valid message type".to_string()) + } + + /* 2d. Set and Store the execution body using `execution_async()` */ + listener.set_exec_fn(execution_async(execbody)); + + listener } - diff --git a/forcebot_core/src/custom_mods/guest_badge.rs b/forcebot_core/src/custom_mods/guest_badge.rs index 39c121d..2cf4ae7 100644 --- a/forcebot_core/src/custom_mods/guest_badge.rs +++ b/forcebot_core/src/custom_mods/guest_badge.rs @@ -1,91 +1,109 @@ -use std::{sync::Arc, time::{Duration, Instant}}; +use std::{ + sync::Arc, + time::{Duration, Instant}, +}; use twitch_irc::message::ServerMessage; use crate::{execution_async, Badge, Bot, Command, Module}; /// guest_badge / guest module -/// +/// /// Temporary badges can be issued to chatters. The bot then opens functionality /// to that chatter based on the recognized role /// -/// Chatters with real badge roles will be able to share guest +/// Chatters with real badge roles will be able to share guest /// badges based on their role -/// -/// -/// - -const VIP_GIVEN_DUR_MIN:u64 = 15; -const MOD_GIVEN_DUR_MIN:u64 = 30; +/// +/// +/// +const VIP_GIVEN_DUR_MIN: u64 = 15; +const MOD_GIVEN_DUR_MIN: u64 = 30; /// Use this function when loading modules into the bot -/// +/// /// For example /// ```rust /// bot.load_module(guest_badge::create_module()); /// ``` -/// +/// pub fn create_module() -> Module { - let mut custom_mod = Module::new( - vec!["guests".to_string()], - "Temp Guest badges can be given by chatters with badges. ".to_string()); + vec!["guests".to_string()], + "Temp Guest badges can be given by chatters with badges. ".to_string(), + ); custom_mod.load_command(create_cmd_mod()); custom_mod.load_command(create_cmd_vip()); - - custom_mod + custom_mod } fn create_cmd_vip() -> Command { + let mut cmd = Command::new(vec!["vip".to_string()], "".to_string()); - let mut cmd = Command::new(vec!["vip".to_string()],"".to_string()); - - - async fn execbody(bot:Arc<Bot>,message:ServerMessage) -> Result<String,String> { + async fn execbody(bot: Arc<Bot>, message: ServerMessage) -> Result<String, String> { if let ServerMessage::Privmsg(msg) = message { - let guest_dur_min = { - let mut result=VIP_GIVEN_DUR_MIN; + let mut result = VIP_GIVEN_DUR_MIN; for badge in msg.clone().badges { - if badge.name == "vip" { result = VIP_GIVEN_DUR_MIN ; } - if badge.name == "moderator" { result = MOD_GIVEN_DUR_MIN ; } + if badge.name == "vip" { + result = VIP_GIVEN_DUR_MIN; + } + if badge.name == "moderator" { + result = MOD_GIVEN_DUR_MIN; + } } result }; - - - let mut badges_issued =false; - for (i,arg) in msg.message_text.replace("\u{e0000}","").trim().split(" ").enumerate() { + + let mut badges_issued = false; + for (i, arg) in msg + .message_text + .replace("\u{e0000}", "") + .trim() + .split(" ") + .enumerate() + { if i > 1 { let mut already_vip = false; - - - for guest_badge in bot.get_channel_guest_badges(arg.trim().to_string(), msg.channel_login.clone()).await { - if guest_badge.0 == Badge::Vip { already_vip = true } + + for guest_badge in bot + .get_channel_guest_badges(arg.trim().to_string(), msg.channel_login.clone()) + .await + { + if guest_badge.0 == Badge::Vip { + already_vip = true + } } if !already_vip { - badges_issued= true; + badges_issued = true; bot.issue_new_guest_badge( - arg.trim().to_string(), - msg.channel_login.clone(), - Badge::Vip, Instant::now(), Duration::from_secs(60*guest_dur_min)).await; - + arg.trim().to_string(), + msg.channel_login.clone(), + Badge::Vip, + Instant::now(), + Duration::from_secs(60 * guest_dur_min), + ) + .await; } } } - if badges_issued { - - - let _= bot.chat.lock().await.say_in_reply_to( - &msg.clone(), format!("Guest badges issued for {} min",guest_dur_min)).await; + if badges_issued { + let _ = bot + .chat + .lock() + .await + .say_in_reply_to( + &msg.clone(), + format!("Guest badges issued for {} min", guest_dur_min), + ) + .await; return Result::Ok("Success".to_string()); } - } - Result::Err("Not Valid message type".to_string()) + Result::Err("Not Valid message type".to_string()) } cmd.set_exec_fn(execution_async(execbody)); @@ -94,41 +112,58 @@ fn create_cmd_vip() -> Command { cmd.set_admin_override(true); cmd.set_min_badge(Badge::Vip); cmd - } fn create_cmd_mod() -> Command { + let mut cmd = Command::new(vec!["mod".to_string()], "".to_string()); - let mut cmd = Command::new(vec!["mod".to_string()],"".to_string()); - - async fn execbody(bot:Arc<Bot>,message:ServerMessage) -> Result<String,String> { + async fn execbody(bot: Arc<Bot>, message: ServerMessage) -> Result<String, String> { if let ServerMessage::Privmsg(msg) = message { - - - let mut badges_issued =false; - for (i,arg) in msg.message_text.replace("\u{e0000}","").trim().split(" ").enumerate() { + let mut badges_issued = false; + for (i, arg) in msg + .message_text + .replace("\u{e0000}", "") + .trim() + .split(" ") + .enumerate() + { if i > 1 { - let mut already_mod = false; - for guest_badge in bot.get_channel_guest_badges(arg.trim().to_string(), msg.channel_login.clone()).await { - if guest_badge.0 == Badge::Moderator { already_mod = true } + for guest_badge in bot + .get_channel_guest_badges(arg.trim().to_string(), msg.channel_login.clone()) + .await + { + if guest_badge.0 == Badge::Moderator { + already_mod = true + } } if !already_mod { - badges_issued= true; + badges_issued = true; bot.issue_new_guest_badge( - arg.trim().to_string(), - msg.channel_login.clone(), - Badge::Moderator, Instant::now(), Duration::from_secs(60*MOD_GIVEN_DUR_MIN)).await; + arg.trim().to_string(), + msg.channel_login.clone(), + Badge::Moderator, + Instant::now(), + Duration::from_secs(60 * MOD_GIVEN_DUR_MIN), + ) + .await; } } - } + } if badges_issued { - let _= bot.chat.lock().await.say_in_reply_to( - &msg, format!("Guest badges issued for {} min",MOD_GIVEN_DUR_MIN)).await; + let _ = bot + .chat + .lock() + .await + .say_in_reply_to( + &msg, + format!("Guest badges issued for {} min", MOD_GIVEN_DUR_MIN), + ) + .await; return Result::Ok("Success".to_string()); } } - Result::Err("Not Valid message type".to_string()) + Result::Err("Not Valid message type".to_string()) } cmd.set_exec_fn(execution_async(execbody)); @@ -137,6 +172,4 @@ fn create_cmd_mod() -> Command { cmd.set_admin_override(true); cmd.set_min_badge(Badge::Moderator); cmd - - -} \ No newline at end of file +} diff --git a/forcebot_core/src/custom_mods/pyramid.rs b/forcebot_core/src/custom_mods/pyramid.rs index 8b36395..99deb25 100644 --- a/forcebot_core/src/custom_mods/pyramid.rs +++ b/forcebot_core/src/custom_mods/pyramid.rs @@ -1,169 +1,174 @@ - - use std::sync::{Arc, Mutex}; use twitch_irc::message::{PrivmsgMessage, ServerMessage}; // use crate::{execution_async, listener_condition_async, Badge, Bot, Command, Listener, Module}; -use super::super::botcore::bot_objects::execution_async; -use super::super::botcore::bot_objects::listener_condition_async; -use super::super::botcore::bot_objects::listener::Listener; -use super::super::botcore::modules::Module; use super::super::botcore::bot::Bot; use super::super::botcore::bot_objects::command::Command; +use super::super::botcore::bot_objects::execution_async; +use super::super::botcore::bot_objects::listener::Listener; +use super::super::botcore::bot_objects::listener_condition_async; use super::super::botcore::bot_objects::Badge; +use super::super::botcore::modules::Module; /// pyramid module -/// +/// /// for detecting & handling pyramids -/// +/// /// - listener - detects pyramid /// - cmd & listener - interrupts some chatters temporarily -/// -/// +/// +/// use lazy_static::lazy_static; - /// Use this function when loading modules into the bot -/// +/// /// For example /// ```rust /// bot.load_module(pyramid::create_module()); /// ``` -/// +/// pub fn create_module() -> Module { - let mut custom_mod = Module::new( - vec!["pyramid".to_string(), - "pyramids".to_string()], - "o7 I can handle pyramids".to_string()); + vec!["pyramid".to_string(), "pyramids".to_string()], + "o7 I can handle pyramids".to_string(), + ); custom_mod.load_listener(create_pyramid_detector()); custom_mod - } fn create_pyramid_detector() -> Listener { + /* 1. Create a new blank Listener */ + let mut listener = Listener::new(); - /* 1. Create a new blank Listener */ - let mut listener = Listener::new(); - - /* 2. Define an async trigger condition callback */ - async fn condition01(bot:Arc<Bot>,message:ServerMessage) -> bool { - if let ServerMessage::Privmsg(msg) = message { - if detect_pyramid_complete_ok(bot.clone(), msg.clone()).await && get_pyramid_size(msg.channel_login) > 3 { - return true; - } + /* 2. Define an async trigger condition callback */ + async fn condition01(bot: Arc<Bot>, message: ServerMessage) -> bool { + if let ServerMessage::Privmsg(msg) = message { + if detect_pyramid_complete_ok(bot.clone(), msg.clone()).await + && get_pyramid_size(msg.channel_login) > 3 + { + return true; } - false } + false + } - /* 3. Set a trigger condition function for listener */ - listener.set_trigger_cond_async(listener_condition_async(condition01)); + /* 3. Set a trigger condition function for listener */ + listener.set_trigger_cond_async(listener_condition_async(condition01)); - - /* 4. Define an async fn callback execution */ - async fn execbody(bot:Arc<Bot>,message:ServerMessage) -> Result<String,String> { - if let ServerMessage::Privmsg(msg) = message { - dbg!("enter pyramid listener execution - after pyramid complete"); - // if detect_pyramid_complete_ok(bot.clone(), msg.clone()).await { + /* 4. Define an async fn callback execution */ + async fn execbody(bot: Arc<Bot>, message: ServerMessage) -> Result<String, String> { + if let ServerMessage::Privmsg(msg) = message { + dbg!("enter pyramid listener execution - after pyramid complete"); + // if detect_pyramid_complete_ok(bot.clone(), msg.clone()).await { - // let _ = bot.chat.lock().await. - dbg!("> get start pattern"); - let pattern = get_start_pattern(msg.channel_login.clone()); - let mut outmsg ; + // let _ = bot.chat.lock().await. + dbg!("> get start pattern"); + let pattern = get_start_pattern(msg.channel_login.clone()); + let mut outmsg; - /* Prefer emote before pattern in case pattern is command */ - if pattern.len() < 50 { - outmsg = format!("Clap {}",pattern); - } else { - outmsg = "Clap".to_string(); - } - - dbg!(get_pyramid_size(msg.channel_login.clone())); - if get_pyramid_size(msg.channel_login.clone()) < 4 { - outmsg = format!("{} annytfMagniGlass",outmsg); - } - - dbg!("> start pattern :",pattern); - - dbg!("> say_in_reply_to completed :",outmsg.clone()); - let _ = bot.chat.lock().await.say_in_reply_to(&msg, outmsg).await ; - - dbg!("> set pyramid started - false"); - set_pyramid_started(msg.channel_login.clone(),false); - - return Result::Ok("Success".to_string()) ; - // } + /* Prefer emote before pattern in case pattern is command */ + if pattern.len() < 50 { + outmsg = format!("Clap {}", pattern); + } else { + outmsg = "Clap".to_string(); } - Result::Err("Not Valid message type".to_string()) + + dbg!(get_pyramid_size(msg.channel_login.clone())); + if get_pyramid_size(msg.channel_login.clone()) < 4 { + outmsg = format!("{} annytfMagniGlass", outmsg); + } + + dbg!("> start pattern :", pattern); + + dbg!("> say_in_reply_to completed :", outmsg.clone()); + let _ = bot.chat.lock().await.say_in_reply_to(&msg, outmsg).await; + + dbg!("> set pyramid started - false"); + set_pyramid_started(msg.channel_login.clone(), false); + + return Result::Ok("Success".to_string()); + // } } - - /* 5. Set and Store the execution body using `execution_async()` */ - listener.set_exec_fn(Box::new(move |a,b| Box::pin(execbody(a,b)))); + Result::Err("Not Valid message type".to_string()) + } - listener + /* 5. Set and Store the execution body using `execution_async()` */ + listener.set_exec_fn(Box::new(move |a, b| Box::pin(execbody(a, b)))); + listener } /// detect pyramid based on latest message and channel -/// -/// -async fn detect_pyramid_complete_ok(_bot:Arc<Bot>,msg:PrivmsgMessage) -> bool { +/// +/// +async fn detect_pyramid_complete_ok(_bot: Arc<Bot>, msg: PrivmsgMessage) -> bool { dbg!("enter detect_pyramid_complete()"); - let msgtext = msg.message_text.replace("","").replace("\u{e0000}","").trim().to_string(); + let msgtext = msg + .message_text + .replace("", "") + .replace("\u{e0000}", "") + .trim() + .to_string(); let msgchannel = msg.channel_login; let msgchatter = msg.sender.login; // 1. Check if Pyramid started in chat > and recognize pyramid started - if !is_pyramid_started(msgchannel.clone()) & check_start_pyramid(msgchannel.clone(),msgtext.clone(),) { + if !is_pyramid_started(msgchannel.clone()) + & check_start_pyramid(msgchannel.clone(), msgtext.clone()) + { dbg!("> set pyramid started - true"); - set_pyramid_started(msgchannel.clone(),true); - push_to_compare(msgchannel.clone(),msgchatter.clone(),get_start_pattern(msgchannel.clone())); - - + set_pyramid_started(msgchannel.clone(), true); + push_to_compare( + msgchannel.clone(), + msgchatter.clone(), + get_start_pattern(msgchannel.clone()), + ); } if is_pyramid_started(msgchannel.clone()) { - push_to_compare(msgchannel.clone(),msgchatter.clone(),msgtext.clone()); + push_to_compare(msgchannel.clone(), msgchatter.clone(), msgtext.clone()); } // 2a. If Pyramid Not Started, Assume message is a potential start pattern if !is_pyramid_started(msgchannel.clone()) { - set_start_pattern(msgchannel.clone(),msgtext.clone()); + set_start_pattern(msgchannel.clone(), msgtext.clone()); } // 2b. If Pyramid is Started, and the latest message is the pattern, check for - // symmetry to determine pyramid + // symmetry to determine pyramid - if is_pyramid_started(msgchannel.clone()) && msgtext.clone() == get_start_pattern(msgchannel.clone()) { + if is_pyramid_started(msgchannel.clone()) + && msgtext.clone() == get_start_pattern(msgchannel.clone()) + { if symmetry_ok(msgchannel.clone()) { return true; } else { dbg!("> set pyramid started - false"); - set_pyramid_started(msgchannel,false); + set_pyramid_started(msgchannel, false); - return false ; + return false; } } // 2c. if Pyramid is strted but latest message does not ontain pattern - if is_pyramid_started(msgchannel.clone()) && !msgtext.clone().contains( get_start_pattern(msgchannel.clone()).as_str()) { - + if is_pyramid_started(msgchannel.clone()) + && !msgtext + .clone() + .contains(get_start_pattern(msgchannel.clone()).as_str()) + { dbg!("> set pyramid started - false"); - set_pyramid_started(msgchannel,false); + set_pyramid_started(msgchannel, false); - return false ; + return false; } else { return false; - }; - } - -lazy_static!{ +lazy_static! { /// Message Compare stack per channel (channel:String,msgstack:Vec<(chatter:String,message:String)>) pub static ref COMPARE_MSG_STACK_PER_CHNL: Mutex<Vec<(String,Mutex<Vec<(String,String)>>)>> = Mutex::new(vec![]); #[derive(Debug)] @@ -173,35 +178,34 @@ lazy_static!{ pub static ref START_PATTERNS_PER_CHNL: Mutex<Vec<(String,Mutex<String>)>> = Mutex::new(vec![]); /// Pyramid sze per channel (channel:String,started:bool) pub static ref PYRAMID_SIZE_PER_CHNL: Mutex<Vec<(String,Mutex<i32>)>> = Mutex::new(vec![]); - /// temp message stack checker + /// temp message stack checker pub static ref TEMP_MSG_STACK: Mutex<Vec<String>> = Mutex::new(vec![]); - + /// interruptor targets - (channel:String,chatters:Vec<String>>) pub static ref INTERRUPT_TRG_PER_CHNL: Mutex<Vec<(String,Mutex<Vec<String>>)>> = Mutex::new(vec![]); } -fn read_top_of_compare(channel:String) -> Option<(String,String)> { - +fn read_top_of_compare(channel: String) -> Option<(String, String)> { let comp_perchnl = COMPARE_MSG_STACK_PER_CHNL.lock().unwrap(); for rec in comp_perchnl.iter() { if rec.0 == channel { let msg_stack = rec.1.lock().unwrap(); - + return msg_stack.last().cloned(); } } None } -fn pop_top_of_compare(channel:String) -> Option<(String,String)> { +fn pop_top_of_compare(channel: String) -> Option<(String, String)> { let comp_perchnl = COMPARE_MSG_STACK_PER_CHNL.lock().unwrap(); for rec in comp_perchnl.iter() { if rec.0 == channel { let mut msg_stack = rec.1.lock().unwrap(); - + let popped = msg_stack.pop(); return popped; } @@ -209,7 +213,7 @@ fn pop_top_of_compare(channel:String) -> Option<(String,String)> { None } -fn set_pyramid_started(channel:String,started:bool) { +fn set_pyramid_started(channel: String, started: bool) { let mut start_perchnl = PYRAMID_STARTED_PER_CHNL.lock().unwrap(); let mut found = false; for rec in start_perchnl.iter() { @@ -217,14 +221,14 @@ fn set_pyramid_started(channel:String,started:bool) { found = true; let mut rec_started = rec.1.lock().unwrap(); *rec_started = started; - } + } } if !found { - start_perchnl.push((channel,Mutex::new(started))); + start_perchnl.push((channel, Mutex::new(started))); } } -fn is_pyramid_started(channel:String) -> bool { +fn is_pyramid_started(channel: String) -> bool { let start_perchnl = PYRAMID_STARTED_PER_CHNL.lock().unwrap(); for rec in start_perchnl.iter() { if rec.0 == channel { @@ -235,42 +239,37 @@ fn is_pyramid_started(channel:String) -> bool { false } -fn set_start_pattern(channel:String,pattern:String) { +fn set_start_pattern(channel: String, pattern: String) { let mut start_patterns = START_PATTERNS_PER_CHNL.lock().unwrap(); let mut found = false; for rec in start_patterns.iter() { - if rec.0 == channel { found = true; let mut patternlock = rec.1.lock().unwrap(); *patternlock = pattern.clone(); - } - - } + } + } if !found { - start_patterns.push((channel.clone(),Mutex::new(pattern.clone()))); + start_patterns.push((channel.clone(), Mutex::new(pattern.clone()))); } } - -fn get_start_pattern(channel:String) -> String { +fn get_start_pattern(channel: String) -> String { let start_patterns = START_PATTERNS_PER_CHNL.lock().unwrap(); for rec in start_patterns.iter() { - if rec.0 == channel { let patternlock = rec.1.lock().unwrap(); return patternlock.clone(); - } - } + } + } return "".to_string(); } - /// pushes message to compare stack -fn push_to_compare(channel:String,chatter:String,message:String) { +fn push_to_compare(channel: String, chatter: String, message: String) { let mut comp_perchnl = COMPARE_MSG_STACK_PER_CHNL.lock().unwrap(); let mut found = false; @@ -278,20 +277,23 @@ fn push_to_compare(channel:String,chatter:String,message:String) { if rec.0 == channel { found = true; let mut msg_stack = rec.1.lock().unwrap(); - msg_stack.push((chatter.clone(),message.clone())); + msg_stack.push((chatter.clone(), message.clone())); // dbg!("Push message to cmp stack ; result last cmp_pchnl - ",comp_perchnl.last()); } } if !found { - comp_perchnl.push((channel,Mutex::new(vec![(chatter,message)]))); + comp_perchnl.push((channel, Mutex::new(vec![(chatter, message)]))); } - } - /// checks latest and next latest messages for potential start -fn check_start_pyramid(channel:String,msgtext: String) -> bool { - msgtext == format!("{} {}",get_start_pattern(channel.clone()),get_start_pattern(channel.clone())) +fn check_start_pyramid(channel: String, msgtext: String) -> bool { + msgtext + == format!( + "{} {}", + get_start_pattern(channel.clone()), + get_start_pattern(channel.clone()) + ) // msgtext == format!("{} {} {}", // get_start_pattern(channel.clone()), // get_start_pattern(channel.clone()), @@ -299,60 +301,80 @@ fn check_start_pyramid(channel:String,msgtext: String) -> bool { // ) } - /// pops the compare stack to determine symmetry -fn symmetry_ok(channel:String) -> bool { +fn symmetry_ok(channel: String) -> bool { let mut temp_stack = TEMP_MSG_STACK.lock().unwrap(); let mut checking_started = false; - if !(read_top_of_compare(channel.clone()).unwrap_or(("".to_string(),"".to_string())).1 == get_start_pattern(channel.clone())) { + if !(read_top_of_compare(channel.clone()) + .unwrap_or(("".to_string(), "".to_string())) + .1 + == get_start_pattern(channel.clone())) + { return false; } let mut pyramid_size = 0; loop { - - if !checking_started && read_top_of_compare(channel.clone()).unwrap_or(("".to_string(),"".to_string())).1 == get_start_pattern(channel.clone()) { + if !checking_started + && read_top_of_compare(channel.clone()) + .unwrap_or(("".to_string(), "".to_string())) + .1 + == get_start_pattern(channel.clone()) + { checking_started = true; - } + } - if temp_stack.last().is_none() || read_top_of_compare(channel.clone()).unwrap_or(("".to_string(),"".to_string())).1.len() > temp_stack.last().unwrap_or(&"".to_string()).len() { - temp_stack.push(pop_top_of_compare(channel.clone()).unwrap_or(("".to_string(),"".to_string())).1); + if temp_stack.last().is_none() + || read_top_of_compare(channel.clone()) + .unwrap_or(("".to_string(), "".to_string())) + .1 + .len() + > temp_stack.last().unwrap_or(&"".to_string()).len() + { + temp_stack.push( + pop_top_of_compare(channel.clone()) + .unwrap_or(("".to_string(), "".to_string())) + .1, + ); pyramid_size += 1; - - } else if temp_stack.last().is_some() && read_top_of_compare(channel.clone()).unwrap_or(("".to_string(),"".to_string())).1.len() < temp_stack.last().unwrap_or(&"".to_string()).len() { - + } else if temp_stack.last().is_some() + && read_top_of_compare(channel.clone()) + .unwrap_or(("".to_string(), "".to_string())) + .1 + .len() + < temp_stack.last().unwrap_or(&"".to_string()).len() + { temp_stack.pop(); - if temp_stack.last().unwrap_or(&"".to_string()).clone() == read_top_of_compare(channel.clone()).unwrap_or(("".to_string(),"".to_string())).1 { + if temp_stack.last().unwrap_or(&"".to_string()).clone() + == read_top_of_compare(channel.clone()) + .unwrap_or(("".to_string(), "".to_string())) + .1 + { temp_stack.pop(); - + continue; } else { - set_pyramid_size(channel.clone(), 0); temp_stack.clear(); return false; } - - } else { + } else { set_pyramid_size(channel.clone(), 0); - return false; + return false; } - if checking_started && read_top_of_compare(channel.clone()).unwrap().1 == get_start_pattern(channel.clone()) { - + if checking_started + && read_top_of_compare(channel.clone()).unwrap().1 == get_start_pattern(channel.clone()) + { /* leave pyramid size set for exection */ - set_pyramid_size(channel.clone(), pyramid_size*2-1); + set_pyramid_size(channel.clone(), pyramid_size * 2 - 1); temp_stack.clear(); return true; - } - + } } - - } - -fn set_pyramid_size(channel:String,size:i32) { +fn set_pyramid_size(channel: String, size: i32) { let mut size_perchnl = PYRAMID_SIZE_PER_CHNL.lock().unwrap(); let mut found = false; for rec in size_perchnl.iter() { @@ -360,14 +382,14 @@ fn set_pyramid_size(channel:String,size:i32) { found = true; let mut rec_started = rec.1.lock().unwrap(); *rec_started = size; - } + } } if !found { - size_perchnl.push((channel,Mutex::new(size))); + size_perchnl.push((channel, Mutex::new(size))); } } -fn get_pyramid_size(channel:String) -> i32 { +fn get_pyramid_size(channel: String) -> i32 { let size_perchnl = PYRAMID_SIZE_PER_CHNL.lock().unwrap(); for rec in size_perchnl.iter() { if rec.0 == channel { @@ -379,88 +401,85 @@ fn get_pyramid_size(channel:String) -> i32 { } /// #todo -/// -/// pyramid interruptor -/// +/// +/// pyramid interruptor +/// /// pick chatters that will be interrupted if they solo build -/// +/// /// takes in arguments as chatters -/// +/// /// chatters are then interrupted for a random duration under 15m -/// -/// if a duration is given, take that duration eg 15m , 25m -/// -/// +/// +/// if a duration is given, take that duration eg 15m , 25m +/// +/// fn _create_interruptor_cmd() -> Command { - let mut cmd = Command::new(vec![ - "no pyramid".to_string(), - "no pyramids".to_string() - ], - "".to_string()); + let mut cmd = Command::new( + vec!["no pyramid".to_string(), "no pyramids".to_string()], + "".to_string(), + ); - /* 2. Define an async fn callback execution */ - async fn execbody(bot:Arc<Bot>,message:ServerMessage) -> Result<String,String> { - if let ServerMessage::Privmsg(msg) = message { - let _ = bot.chat.lock().await.say_in_reply_to(&msg, String::from("test success")).await; - return Result::Ok("Success".to_string()) ; - } - Result::Err("Not Valid message type".to_string()) + /* 2. Define an async fn callback execution */ + async fn execbody(bot: Arc<Bot>, message: ServerMessage) -> Result<String, String> { + if let ServerMessage::Privmsg(msg) = message { + let _ = bot + .chat + .lock() + .await + .say_in_reply_to(&msg, String::from("test success")) + .await; + return Result::Ok("Success".to_string()); } + Result::Err("Not Valid message type".to_string()) + } - /* 3. Set and Store the execution body using `execution_async()` */ - cmd.set_exec_fn(Box::new(move |a,b| Box::pin(execbody(a,b)))); + /* 3. Set and Store the execution body using `execution_async()` */ + cmd.set_exec_fn(Box::new(move |a, b| Box::pin(execbody(a, b)))); - /* 4. optionally, remove admin only default flag */ - cmd.set_admin_only(false); - - /* 5. optionally, set min badge*/ - cmd.set_min_badge(Badge::Moderator); + /* 4. optionally, remove admin only default flag */ + cmd.set_admin_only(false); + /* 5. optionally, set min badge*/ + cmd.set_min_badge(Badge::Moderator); cmd } /// #todo -fn _create_interruptor_module(channel:String) -> Module { +fn _create_interruptor_module(channel: String) -> Module { /* 1. Create a new module */ - let modname = format!("interruptor {}",channel); - let mut custom_mod = Module::new( - vec![modname], - "".to_string()); + let modname = format!("interruptor {}", channel); + let mut custom_mod = Module::new(vec![modname], "".to_string()); /* 2. Load the cmd into a new module */ custom_mod.load_listener(_create_interruptor_listener()); custom_mod - } /// #todo -fn _create_interruptor_listener() -> Listener { +fn _create_interruptor_listener() -> Listener { /* 2a. Create a new blank Listener */ let mut listener = Listener::new(); /* 2b. Set a trigger condition function for listener */ - listener.set_trigger_cond_fn( - |_:Arc<Bot>,_:ServerMessage| true - ); + listener.set_trigger_cond_fn(|_: Arc<Bot>, _: ServerMessage| true); /* 2c. Define an async fn callback execution */ - async fn execbody(_:Arc<Bot>,message:ServerMessage) -> Result<String,String> { + async fn execbody(_: Arc<Bot>, message: ServerMessage) -> Result<String, String> { dbg!(message); /* outputs message to debug */ - Result::Ok("Success".to_string()) + Result::Ok("Success".to_string()) } /* 2d. Set and Store the execution body using `execution_async()` */ listener.set_exec_fn(execution_async(execbody)); listener - } /// #todo -/// +/// /// Returns Some(chatter) if the pyramid in progress is being built by a solo -fn _solo_building(_channel:String) -> Option<String> { +fn _solo_building(_channel: String) -> Option<String> { None } diff --git a/forcebot_core/src/lib.rs b/forcebot_core/src/lib.rs index f33057e..6d3f317 100644 --- a/forcebot_core/src/lib.rs +++ b/forcebot_core/src/lib.rs @@ -1,32 +1,32 @@ //! `forcebot_core` library for `forcebot-rs-v2` Twitch chat bot -//! +//! //! Customize by adding additional bot objects -//! +//! //! # New Bot -//! +//! //! Uses Env defined variables to create and run the bot -//! +//! //! ```rust //! use forcebot_core::Bot; -//! +//! //! #[tokio::main] //! pub async fn main() { -//! +//! //! /* 1. Create the bot using env */ //! let bot = Bot::new(); -//! +//! //! /* 2. Run the bot */ //! bot.run().await; -//! +//! //! } -//! +//! //! ``` -//! -//! +//! +//! //! # Customize with Modules -//! +//! //! A `Module` is a group of bot objects (eg `Command`) that elevated users can manage through built in `disable` and `enable` commands -//! +//! //! Custom `Modules` can be loaded into a new bot with minimum coding : just load the modules and run the bot //! //! ```rust @@ -48,16 +48,16 @@ //! } //! ``` //! -//! +//! //! # Create your own Custom Modules -//! +//! //! Create a custom `Module` by : -//! +//! //! 1. Defining Functions that create the Custom Bot Objects (eg `Command`) -//! +//! //! 2. Define a function that creates a `Module` with the Custom Bot Objects loaded -//! -//! +//! +//! //! ```rust //! use forcebot_core::Bot; //! @@ -86,7 +86,7 @@ //! pub fn new() -> Module { //! /* 1. Create a new module */ //! let mut custom_mod = Module::new( -//! vec!["test".to_string()], +//! vec!["test".to_string()], //! "".to_string()); //! //! /* 2. Load the cmd into a new module */ @@ -96,7 +96,7 @@ //! //! } //! -//! /// Command definition +//! /// Command definition //! pub fn cmd_test() -> Command { //! /* 1. Create a new cmd */ //! let mut cmd = Command::new(vec!["test".to_string()],"".to_string()); @@ -107,7 +107,7 @@ //! let _= bot.client.say_in_reply_to( //! &msg, "test return".to_string()).await; //! } -//! Result::Err("Not Valid message type".to_string()) +//! Result::Err("Not Valid message type".to_string()) //! } //! //! /* 3. Set Command flags */ @@ -119,99 +119,99 @@ //! } //! } //! -//! +//! //! ``` -//! +//! //! # Simple Debug Listener //! Bot with a simple listener that listens for all messages and prints in output -//! +//! //! ```rust //! use std::sync::Arc; -//! +//! //! use forcebot_core::{execution_async, Bot, Listener}; //! use twitch_irc::message::ServerMessage; -//! +//! //! #[tokio::main] //! pub async fn main() { -//! +//! //! /* 1. Create the bot using env */ //! let mut bot = Bot::new(); -//! +//! //! /* 2a. Create a new blank Listener */ //! let mut listener = Listener::new(); -//! +//! //! /* 2b. Set a trigger condition function for listener */ //! listener.set_trigger_cond_fn( //! |_:Arc<Bot>,_:ServerMessage| true //! ); -//! +//! //! /* 2c. Define an async fn callback execution */ //! async fn execbody(_:Arc<Bot>,message:ServerMessage) -> Result<String,String> { //! dbg!(message); /* outputs message to debug */ -//! Result::Ok("Success".to_string()) +//! Result::Ok("Success".to_string()) //! } -//! +//! //! /* 2d. Set and Store the execution body using `execution_async()` */ //! listener.set_exec_fn(execution_async(execbody)); -//! +//! //! /* 3. Load the listener into the bot */ //! bot.load_listener(listener); -//! +//! //! /* 4. Run the bot */ //! bot.run().await; -//! +//! //! } -//! +//! //! ``` -//! +//! //! # Moderator Reactor -//! +//! //! ``` -//! +//! //! use std::sync::Arc; -//! +//! //! use forcebot_core::Bot; //! use forcebot_core::execution_async; //! use forcebot_core::Listener; //! use twitch_irc::message::ServerMessage; -//! -//! +//! +//! //! #[tokio::main] //! pub async fn main() { -//! +//! //! /* Create the bot using env */ //! let mut bot = Bot::new(); -//! +//! //! /* 1. Create a new blank Listener */ //! let mut listener = Listener::new(); -//! +//! //! /* 2. Set a trigger condition function for listener */ -//! +//! //! listener.set_trigger_cond_fn( -//! |_:Arc<Bot>,message:ServerMessage| +//! |_:Arc<Bot>,message:ServerMessage| //! if let ServerMessage::Privmsg(msg) = message { //! for badge in msg.badges { //! if matches!(badge, x if x.name == "moderator") { //! // dbg!("moderator found"); //! return true; //! } -//! } +//! } //! false //! } else { false } //! ); -//! +//! //! /* 3. Define an async fn callback execution */ //! async fn execbody(bot:Arc<Bot>,message:ServerMessage) -> Result<String,String> { //! if let ServerMessage::Privmsg(msg) = message { //! let _ = bot.client.say_in_reply_to(&msg, "pepeKneel".to_string()).await ; //! return Result::Ok("Success".to_string()) ; //! } -//! Result::Err("Not Valid message type".to_string()) +//! Result::Err("Not Valid message type".to_string()) //! } -//! +//! //! /* 4. Set and Store the execution body using `execution_async()` */ //! listener.set_exec_fn(execution_async(execbody)); -//! +//! //! /* 5. Load the listener into the bot */ //! bot.load_listener(listener); //! @@ -220,17 +220,15 @@ //! //! } - - pub mod botcore; pub mod custom_mods; pub use botcore::bot::Bot; -pub use botcore::bot_objects::execution_async; pub use botcore::bot_objects::command_condition_async; -pub use botcore::bot_objects::listener_condition_async; +pub use botcore::bot_objects::execution_async; pub use botcore::bot_objects::listener::Listener; +pub use botcore::bot_objects::listener_condition_async; // pub use crate::botcore::bot_objects::command::Command; pub use botcore::bot_objects::command::Command; -pub use botcore::modules::Module; pub use botcore::bot_objects::Badge; pub use botcore::modules; +pub use botcore::modules::Module; diff --git a/moderator_reactor/src/main.rs b/moderator_reactor/src/main.rs index 5705fbe..284dd0a 100644 --- a/moderator_reactor/src/main.rs +++ b/moderator_reactor/src/main.rs @@ -1,27 +1,25 @@ //! Simple bot with a custom listeners that listens for moderators and respond to the moderator -//! -//! Be sure the followig is defined in `.env` +//! +//! Be sure the followig is defined in `.env` //! - login_name //! - access_token //! - bot_channels //! - prefix //! - bot_admins -//! -//! Bot access tokens be generated here - +//! +//! Bot access tokens be generated here - //! - Get a Bot Chat Token here - <https://twitchtokengenerator.com> //! - More Info - <https://dev.twitch.tv/docs/authentication> use std::sync::Arc; -use forcebot_core::Bot; use forcebot_core::execution_async; +use forcebot_core::Bot; use forcebot_core::Listener; use twitch_irc::message::ServerMessage; - #[tokio::main] pub async fn main() { - /* Create the bot using env */ let bot = Bot::new().await; @@ -30,26 +28,32 @@ pub async fn main() { /* 2. Set a trigger condition function for listener */ - listener.set_trigger_cond_fn( - |_:Arc<Bot>,message:ServerMessage| - if let ServerMessage::Privmsg(msg) = message { - for badge in msg.badges { - if matches!(badge, x if x.name == "moderator") { - // dbg!("moderator found"); - return true; - } - } - false - } else { false } - ); + listener.set_trigger_cond_fn(|_: Arc<Bot>, message: ServerMessage| { + if let ServerMessage::Privmsg(msg) = message { + for badge in msg.badges { + if matches!(badge, x if x.name == "moderator") { + // dbg!("moderator found"); + return true; + } + } + false + } else { + false + } + }); /* 3. Define an async fn callback execution */ - async fn execbody(bot:Arc<Bot>,message:ServerMessage) -> Result<String,String> { + async fn execbody(bot: Arc<Bot>, message: ServerMessage) -> Result<String, String> { if let ServerMessage::Privmsg(msg) = message { - let _ = bot.chat.lock().await.say_in_reply_to(&msg, "pepeKneel".to_string()).await ; - return Result::Ok("Success".to_string()) ; + let _ = bot + .chat + .lock() + .await + .say_in_reply_to(&msg, "pepeKneel".to_string()) + .await; + return Result::Ok("Success".to_string()); } - Result::Err("Not Valid message type".to_string()) + Result::Err("Not Valid message type".to_string()) } /* 4. Set and Store the execution body using `execution_async()` */ @@ -60,5 +64,4 @@ pub async fn main() { /* Run the bot */ bot.run().await; - } diff --git a/new_empty_bot/src/main.rs b/new_empty_bot/src/main.rs index a81462e..856f6b1 100644 --- a/new_empty_bot/src/main.rs +++ b/new_empty_bot/src/main.rs @@ -1,12 +1,12 @@ //! Example simple Binary crate that creates & runs bot based on `.env` -//! Be sure the followig is defined in `.env` +//! Be sure the followig is defined in `.env` //! - login_name //! - access_token //! - bot_channels //! - prefix //! - bot_admins -//! -//! Bot access tokens be generated here - +//! +//! Bot access tokens be generated here - //! - Get a Bot Chat Token here - <https://twitchtokengenerator.com> //! - More Info - <https://dev.twitch.tv/docs/authentication> @@ -14,11 +14,9 @@ use forcebot_core::Bot; #[tokio::main] pub async fn main() { - /* 1. Create the bot using env */ let bot = Bot::new().await; /* 2. Run the bot */ bot.run().await; - } diff --git a/simple_command_bot/src/main.rs b/simple_command_bot/src/main.rs index 418e1eb..c49e55c 100644 --- a/simple_command_bot/src/main.rs +++ b/simple_command_bot/src/main.rs @@ -1,43 +1,46 @@ //! Bot with custom example commands that responds to caller if allowed -//! +//! //! Commands that are passed a blank prefix will use the bot prefix -//! -//! Be sure the followig is defined in `.env` +//! +//! Be sure the followig is defined in `.env` //! - login_name //! - access_token //! - bot_channels //! - prefix //! - bot_admins -//! -//! Bot access tokens be generated here - +//! +//! Bot access tokens be generated here - //! - Get a Bot Chat Token here - <https://twitchtokengenerator.com> //! - More Info - <https://dev.twitch.tv/docs/authentication> use std::sync::Arc; +use forcebot_core::execution_async; use forcebot_core::Badge; use forcebot_core::Bot; -use forcebot_core::execution_async; use forcebot_core::Command; use twitch_irc::message::ServerMessage; - #[tokio::main] pub async fn main() { - /* Create the bot using env */ let bot = Bot::new().await; /* 1. Create a new blank cmd */ - let mut cmd = Command::new(vec!["test".to_string()],"".to_string()); + let mut cmd = Command::new(vec!["test".to_string()], "".to_string()); /* 2. Define an async fn callback execution */ - async fn execbody(bot:Arc<Bot>,message:ServerMessage) -> Result<String,String> { + async fn execbody(bot: Arc<Bot>, message: ServerMessage) -> Result<String, String> { if let ServerMessage::Privmsg(msg) = message { - let _ = bot.chat.lock().await.say_in_reply_to(&msg, String::from("test success")).await; - return Result::Ok("Success".to_string()) ; + let _ = bot + .chat + .lock() + .await + .say_in_reply_to(&msg, String::from("test success")) + .await; + return Result::Ok("Success".to_string()); } - Result::Err("Not Valid message type".to_string()) + Result::Err("Not Valid message type".to_string()) } /* 3. Set and Store the execution body using `execution_async()` */ @@ -54,5 +57,4 @@ pub async fn main() { /* Run the bot */ bot.run().await; - } diff --git a/simple_debug_listener/src/main.rs b/simple_debug_listener/src/main.rs index 27ab37e..b6dc626 100644 --- a/simple_debug_listener/src/main.rs +++ b/simple_debug_listener/src/main.rs @@ -1,12 +1,12 @@ //! Example simple Binary crate that creates & runs bot based on `.env` -//! Be sure the followig is defined in `.env` +//! Be sure the followig is defined in `.env` //! - login_name //! - access_token //! - bot_channels //! - prefix //! - bot_admins -//! -//! Bot access tokens be generated here - +//! +//! Bot access tokens be generated here - //! - Get a Bot Chat Token here - <https://twitchtokengenerator.com> //! - More Info - <https://dev.twitch.tv/docs/authentication> @@ -17,7 +17,6 @@ use twitch_irc::message::ServerMessage; #[tokio::main] pub async fn main() { - /* 1. Create the bot using env */ let bot = Bot::new().await; @@ -25,14 +24,12 @@ pub async fn main() { let mut listener = Listener::new(); /* 2b. Set a trigger condition function for listener */ - listener.set_trigger_cond_fn( - |_:Arc<Bot>,_:ServerMessage| true - ); + listener.set_trigger_cond_fn(|_: Arc<Bot>, _: ServerMessage| true); /* 2c. Define an async fn callback execution */ - async fn execbody(_:Arc<Bot>,message:ServerMessage) -> Result<String,String> { + async fn execbody(_: Arc<Bot>, message: ServerMessage) -> Result<String, String> { dbg!(message); /* outputs message to debug */ - Result::Ok("Success".to_string()) + Result::Ok("Success".to_string()) } /* 2d. Set and Store the execution body using `execution_async()` */ @@ -43,5 +40,4 @@ pub async fn main() { /* 4. Run the bot */ bot.run().await; - } diff --git a/simple_module_example/src/main.rs b/simple_module_example/src/main.rs index e588b22..0c07084 100644 --- a/simple_module_example/src/main.rs +++ b/simple_module_example/src/main.rs @@ -1,19 +1,19 @@ //! Simple Module with a Command -//! -//! Adding objects through packages provides controls , +//! +//! Adding objects through packages provides controls , //! such as moderators, and brodcasters can disable or enable mods -//! -//! Here, moderators or above can enable or disable the `test` +//! +//! Here, moderators or above can enable or disable the `test` //! module with the command `<prefix> disable test` -//! -//! Be sure the followig is defined in `.env` +//! +//! Be sure the followig is defined in `.env` //! - login_name //! - access_token //! - bot_channels //! - prefix //! - bot_admins -//! -//! Bot access tokens be generated here - +//! +//! Bot access tokens be generated here - //! - Get a Bot Chat Token here - <https://twitchtokengenerator.com> //! - More Info - <https://dev.twitch.tv/docs/authentication> @@ -21,7 +21,6 @@ use forcebot_core::Bot; #[tokio::main] pub async fn main() { - /* Create the bot using env */ let bot = Bot::new().await; @@ -30,43 +29,41 @@ pub async fn main() { /* Run the bot */ bot.run().await; - } - pub mod custom_mod { use std::sync::Arc; use forcebot_core::{execution_async, Badge, Bot, Command, Module}; use twitch_irc::message::ServerMessage; - /// Module definition with a loaded command pub fn new() -> Module { /* 1. Create a new module */ - let mut custom_mod = Module::new( - vec!["test".to_string()], - "".to_string()); + let mut custom_mod = Module::new(vec!["test".to_string()], "".to_string()); /* 2. Load the cmd into a new module */ custom_mod.load_command(cmd_test()); custom_mod - } - /// Command definition + /// Command definition pub fn cmd_test() -> Command { /* 1. Create a new cmd */ - let mut cmd = Command::new(vec!["test".to_string()],"".to_string()); + let mut cmd = Command::new(vec!["test".to_string()], "".to_string()); /* 2. Define exec callback */ - async fn execbody(bot:Arc<Bot>,message:ServerMessage) -> Result<String,String> { + async fn execbody(bot: Arc<Bot>, message: ServerMessage) -> Result<String, String> { if let ServerMessage::Privmsg(msg) = message { - let _= bot.chat.lock().await.say_in_reply_to( - &msg, "test return".to_string()).await; + let _ = bot + .chat + .lock() + .await + .say_in_reply_to(&msg, "test return".to_string()) + .await; } - Result::Err("Not Valid message type".to_string()) + Result::Err("Not Valid message type".to_string()) } /* 3. Set Command flags */ @@ -76,4 +73,4 @@ pub mod custom_mod { cmd } -} \ No newline at end of file +} -- 2.49.0