diff --git a/readme.md b/readme.md index 3477c61..e44ecf9 100644 --- a/readme.md +++ b/readme.md @@ -25,43 +25,60 @@ bot_admins=ADMIN cargo run ``` -# Binary Crates +# Example Bots -## Simple Empty Bot -Run a simple bot that logs into chat based on env +Use the following commands to build and run built-in bots. No coding required! + +## New Bot +Run an empty simple bot that logs into chat and has minimum built in functions ``` -cargo run --bin simple_bot +cargo run --bin new_bot ``` -## Fun Bot +## WIP Customized Fun Bot + Run a forcebot with fun catered customizations +*ongoing work in progress* + ``` cargo run --bin fun_bot ``` -## Simple Bot with Example Custom Listener -Run a bot with some custom listeners +## Simple Debug Listener +Run a bot that listens to all messages and output to console ``` -cargo run --bin simple_bot_listener +cargo run --bin simple_debug_listener ``` -## Bot with Example Custom Command -Run a bot with some custom listeners +## 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 bot_cmd_example +cargo run --bin 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 +``` + +## 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 +``` # Example Code -## Simple Bot +## New Bot Uses Env defined variables to create and run the bot @@ -81,8 +98,103 @@ pub async fn main() { ``` -## Custom Bot with listener -Bot with a simple listener +## Module with Custom Command + +A `Module` is a group of bot objects (eg `Command`) that elevated users can manage. + +Bot objects are recommended to be loaded through a `Module` + + +```rust +use std::sync::Arc; + +use forcebot_rs_v2::Bot; +use forcebot_rs_v2::asyncfn_box; +use forcebot_rs_v2::Command; +use forcebot_rs_v2::Module; +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 module */ + let mut custom_mod = Module::new("test".to_string(), "".to_string()); + + /* 2. Create a new cmd */ + let mut cmd = Command::new("test".to_string(),"".to_string()); + + 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()) + } + + cmd.set_exec_fn(asyncfn_box(execbody)); + cmd.set_admin_only(false); + cmd.set_min_badge("moderator".to_string()); + + /* 3. Load the cmd into a new module */ + custom_mod.load_command(cmd); + + /* 4. Load the module into the bot */ + bot.load_module(custom_mod); + + /* Run the bot */ + bot.run().await; + +} + +``` + +## Simple Debug Listener +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 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); + Result::Ok("Success".to_string()) + } + + /* 2d. Set and Store the execution body using `async_box()` */ + listener.set_exec_fn(asyncfn_box(execbody)); + + /* 3. Load the listener into the bot */ + bot.load_listener(listener); + + /* 4. Run the bot */ + bot.run().await; + +} + +``` + +## Moderator Reator Example listener listens for a moderator badge and reply in chat @@ -104,12 +216,14 @@ pub async fn main() { /* 1. Create a new blank Listener */ let mut listener = Listener::new(); - /* 2. Set a trigger condition callback */ + /* 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; } } @@ -120,18 +234,16 @@ 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 { - match bot.client.say_in_reply_to(&msg, String::from("test")).await { - Ok(_) => return Result::Ok("Success".to_string()) , - Err(_) => return Result::Err("Not Valid message type".to_string()) - } + 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()) } - /* 4. Set the execution body using `async_box()` */ + /* 4. Set and Store the execution body using `async_box()` */ listener.set_exec_fn(asyncfn_box(execbody)); - /* 5. Load the Listener into the bot */ + /* 5. Load the listener into the bot */ bot.load_listener(listener); /* Run the bot */ @@ -139,9 +251,10 @@ pub async fn main() { } + ``` -## Bot with Custom command +## Simple Test Command ```rust use std::sync::Arc; @@ -159,25 +272,26 @@ pub async fn main() { let mut bot = Bot::new(); /* 1. Create a new blank cmd */ - let mut cmd = Command::new("tester".to_string(),"".to_string()); + let mut cmd = Command::new("test".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 { - match bot.client.say_in_reply_to(&msg, String::from("cmd tested")).await { - Ok(_) => return Result::Ok("Success".to_string()) , - Err(_) => return Result::Err("Not Valid message type".to_string()) - } + 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(asyncfn_box(execbody)); + /* 4. optionally, remove admin only default flag */ cmd.set_admin_only(false); /* 5. optionally, set min badge*/ cmd.set_min_badge("broadcaster".to_string()); - +// /* 6. Load the cmd into the bot */ bot.load_command(cmd); @@ -185,6 +299,7 @@ pub async fn main() { bot.run().await; } + ``` # Crate Rust Documentation diff --git a/src/bin/fun_bot.rs b/src/bin/fun_bot.rs index 7df3f4d..9ed8eb5 100644 --- a/src/bin/fun_bot.rs +++ b/src/bin/fun_bot.rs @@ -1,4 +1,7 @@ -//! Fun forcebot with catered customizations #todo +//! WIP Fun forcebot with catered customizations #todo +//! +//! Custom modules that can be managed in chat through `disable` and `enable` commands +//! - funbot //! //! Be sure the followig is defined in `.env` //! - login_name @@ -11,13 +14,7 @@ //! - Get a Bot Chat Token here - <https://twitchtokengenerator.com> //! - More Info - <https://dev.twitch.tv/docs/authentication> -use std::sync::Arc; - use forcebot_rs_v2::Bot; -use forcebot_rs_v2::asyncfn_box; -use forcebot_rs_v2::Command; -use twitch_irc::message::ServerMessage; - #[tokio::main] pub async fn main() { @@ -25,28 +22,49 @@ pub async fn main() { /* Create the bot using env */ let mut bot = Bot::new(); - /* 1. Create a new blank cmd */ - let mut cmd = Command::new("remind besty".to_string(),"annytfYandere ".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 _n= bot.client.say_in_reply_to( - &msg, "annytfYandere he's mine".to_string()).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)); - - cmd.set_admin_only(false); - cmd.set_min_badge("broadcaster".to_string()); - - /* 4. Load the cmd into the bot */ - bot.load_command(cmd); - - /* Run the bot */ + /* 1. Load the module into the bot */ + bot.load_module(funbot_objs::create_module()); + + /* 2. Run the bot */ bot.run().await; } + + +pub mod funbot_objs { + use std::sync::Arc; + + use forcebot_rs_v2::{asyncfn_box, 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("funbot".to_string(), "".to_string()); + + custom_mod.load_command(create_cmd_test()); + + custom_mod + } + + /// Create a Command Object + fn create_cmd_test() -> Command { + + let mut cmd = Command::new("remind besty".to_string(),"annytfYandere ".to_string()); + + 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, "annytfYandere he's mine".to_string()).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_min_badge("vip".to_string()); + cmd + + } +} \ No newline at end of file diff --git a/src/bin/simple_bot_listener.rs b/src/bin/moderator_reactor.rs similarity index 87% rename from src/bin/simple_bot_listener.rs rename to src/bin/moderator_reactor.rs index 11a62e9..a7bb62c 100644 --- a/src/bin/simple_bot_listener.rs +++ b/src/bin/moderator_reactor.rs @@ -47,10 +47,8 @@ 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 { - match bot.client.say_in_reply_to(&msg, String::from("test")).await { - Ok(_) => return Result::Ok("Success".to_string()) , - Err(_) => return Result::Err("Not Valid message type".to_string()) - } + 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()) } diff --git a/src/bin/simple_bot.rs b/src/bin/new_bot.rs similarity index 100% rename from src/bin/simple_bot.rs rename to src/bin/new_bot.rs diff --git a/src/bin/bot_cmd_example.rs b/src/bin/simple_command_bot.rs similarity index 80% rename from src/bin/bot_cmd_example.rs rename to src/bin/simple_command_bot.rs index 7335461..0e60bc1 100644 --- a/src/bin/bot_cmd_example.rs +++ b/src/bin/simple_command_bot.rs @@ -1,5 +1,7 @@ //! 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` //! - login_name //! - access_token @@ -26,15 +28,13 @@ pub async fn main() { let mut bot = Bot::new(); /* 1. Create a new blank cmd */ - let mut cmd = Command::new("tester".to_string(),"".to_string()); + let mut cmd = Command::new("test".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 { - match bot.client.say_in_reply_to(&msg, String::from("cmd tested")).await { - Ok(_) => return Result::Ok("Success".to_string()) , - Err(_) => return Result::Err("Not Valid message type".to_string()) - } + 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()) } diff --git a/src/bin/simple_debug_listener.rs b/src/bin/simple_debug_listener.rs new file mode 100644 index 0000000..ee129bd --- /dev/null +++ b/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_rs_v2::{asyncfn_box, 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); + Result::Ok("Success".to_string()) + } + + /* 2d. Set and Store the execution body using `async_box()` */ + listener.set_exec_fn(asyncfn_box(execbody)); + + /* 3. Load the listener into the bot */ + bot.load_listener(listener); + + /* 4. Run the bot */ + bot.run().await; + +} diff --git a/src/bin/simple_module.rs b/src/bin/simple_module.rs new file mode 100644 index 0000000..3795f8e --- /dev/null +++ b/src/bin/simple_module.rs @@ -0,0 +1,62 @@ +//! 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 std::sync::Arc; + +use forcebot_rs_v2::Bot; +use forcebot_rs_v2::asyncfn_box; +use forcebot_rs_v2::Command; +use forcebot_rs_v2::Module; +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 module */ + let mut custom_mod = Module::new("test".to_string(), "".to_string()); + + /* 2. Create a new cmd */ + let mut cmd = Command::new("test".to_string(),"".to_string()); + + 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()) + } + + cmd.set_exec_fn(asyncfn_box(execbody)); + cmd.set_admin_only(false); + cmd.set_min_badge("moderator".to_string()); + + /* 3. Load the cmd into a new module */ + custom_mod.load_command(cmd); + + /* 4. Load the module into the bot */ + bot.load_module(custom_mod); + + /* Run the bot */ + bot.run().await; + +} diff --git a/src/botcore.rs b/src/botcore.rs index 269a97a..678d9a9 100644 --- a/src/botcore.rs +++ b/src/botcore.rs @@ -1,2 +1,3 @@ pub mod bot; -pub mod bot_objects; \ No newline at end of file +pub mod bot_objects; +pub mod modules; \ No newline at end of file diff --git a/src/botcore/bot.rs b/src/botcore/bot.rs index 4292dac..89598af 100644 --- a/src/botcore/bot.rs +++ b/src/botcore/bot.rs @@ -5,10 +5,9 @@ use twitch_irc::{login::StaticLoginCredentials, message::ServerMessage, SecureTC use dotenv::dotenv; use std::{env, sync::Arc}; -use crate::Command; - -use super::bot_objects::listener::Listener; +use crate::{Command, Listener, Module}; +use super::bot_objects::built_in_objects; /// Twitch chat bot @@ -28,6 +27,10 @@ pub struct Bot listeners: Vec<Listener>, /// commands commands: Vec<Command>, + /// modules + modules: Vec<Module>, + /// channel module status + channel_module_status: Mutex<Vec<(String,String,String)>> } @@ -49,16 +52,12 @@ impl Bot let prefix = env::var("prefix") .unwrap() .to_owned(); - // .chars() - // .next() - // .expect("ERROR : when defining prefix"); let mut botchannels = Vec::new(); for chnl in env::var("bot_channels").unwrap().split(',') { botchannels.push(chnl.to_owned()); } - Bot::new_from(bot_login_name, oauth_token, prefix, botchannels) @@ -98,7 +97,7 @@ impl Bot } - Bot { + let mut bot = Bot { prefix, incoming_msgs : Mutex::new(incoming_messages), client, @@ -106,7 +105,15 @@ impl Bot listeners : vec![], commands : vec![], admins, + modules: vec![], + channel_module_status: Mutex::new(vec![]), + }; + + for cmd in built_in_objects::create_commands() { + bot.load_command(cmd); } + + bot } /// Runs the bot @@ -115,17 +122,15 @@ impl Bot for chnl in &self.botchannels { self.client.join(chnl.to_owned()).unwrap(); } - let bot = Arc::new(self); let join_handle = tokio::spawn(async move { - let mut in_msgs_lock = bot.incoming_msgs.lock().await; + let a = bot.clone(); + let mut in_msgs_lock = a.incoming_msgs.lock().await; - while let Some(message) = in_msgs_lock.recv().await { - // dbg!("Received message: {:?}", message.clone()); - + while let Some(message) = in_msgs_lock.recv().await { for listener in &(*bot).listeners { let a = listener.clone(); @@ -134,14 +139,47 @@ impl Bot let _ = listener.execute_fn(bot.clone(),message.clone()).await; } } - for cmd in &(*bot).commands { + + if let ServerMessage::Privmsg(msg) = message.clone() { + + for cmd in &(*bot).commands { - let a = cmd.clone(); - if a.command_triggered(bot.clone(),message.clone()) { - - let _ = cmd.execute_fn(bot.clone(),message.clone()).await; + let a = cmd.clone(); + if a.command_triggered(bot.clone(),msg.clone()) { + + let _ = cmd.execute_fn(bot.clone(),message.clone()).await; + } } - } + + + for module in &(*bot).modules { + + 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 listener in module.get_listeners() { + + let a = listener.clone(); + if a.cond_triggered(bot.clone(),message.clone()) { + + 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()) { + + let _ = cmd.execute_fn(bot.clone(),message.clone()).await; + } + } + + } + + } else {} ; + + } drop(in_msgs_lock); }); @@ -169,4 +207,32 @@ impl Bot self.admins.clone() } -} \ No newline at end of file + /// loads a `Module` and its bot objects + pub fn load_module(&mut self,m: Module) { + self.modules.push(m) + } + + 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())); + } + } + + 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())) { + + let index = lock + .iter() + .position(|x| *x == + (channel.clone(),module.clone(),"disabled".to_string())) + .unwrap(); + lock.remove(index); + } + } + + +} + + diff --git a/src/botcore/bot_objects.rs b/src/botcore/bot_objects.rs index 7bb8186..da787d3 100644 --- a/src/botcore/bot_objects.rs +++ b/src/botcore/bot_objects.rs @@ -20,4 +20,90 @@ where T: Future<Output = Result<String,String>> + Send + 'static, { Box::new(move |a,b| Box::pin(f(a,b))) +} + + + +/// collection of functions to create built in objects +pub mod built_in_objects { + + + use std::sync::Arc; + + use twitch_irc::message::ServerMessage; + + use crate::{asyncfn_box, Bot, Command}; + + + /// create a vector of command build in objects + pub fn create_commands() -> Vec<Command> + { + let mut cmds = vec![]; + + cmds.push(create_disable_cmd()); + cmds.push(create_enable_cmd()); + + cmds + + } + + fn create_disable_cmd() -> Command { + /* 1. Create a new blank cmd */ + let mut cmd = Command::new("disable".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; + } + } + 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(false); + + /* 5. optionally, set min badge*/ + cmd.set_min_badge("moderator".to_string()); + cmd + + } + + fn create_enable_cmd() -> Command { + /* 1. Create a new blank cmd */ + let mut cmd = Command::new("enable".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.enable_module(msg.channel_login.clone(), arg.to_string()).await; + } + } + + let _ = bot.client.say_in_reply_to(&msg, String::from("Enabled!")).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(false); + + /* 5. optionally, set min badge*/ + cmd.set_min_badge("moderator".to_string()); + cmd + } + + } \ No newline at end of file diff --git a/src/botcore/bot_objects/command.rs b/src/botcore/bot_objects/command.rs index 59df04b..d904b06 100644 --- a/src/botcore/bot_objects/command.rs +++ b/src/botcore/bot_objects/command.rs @@ -1,6 +1,6 @@ use std::sync::Arc; -use twitch_irc::message::ServerMessage; +use twitch_irc::message::{PrivmsgMessage, ServerMessage}; use crate::{asyncfn_box, botcore::bot::Bot}; @@ -25,7 +25,7 @@ pub struct Command min_badge : String, admin_only : bool, prefix : String, - custom_cond_fn : fn(Arc<Bot>,ServerMessage) -> bool, + custom_cond_fn : fn(Arc<Bot>,PrivmsgMessage) -> bool, } impl Command @@ -49,12 +49,12 @@ impl Command exec_fn : Arc::new(asyncfn_box(execbody)), min_badge : "vip".to_string(), admin_only : true, - custom_cond_fn : |_:Arc<Bot>,_:ServerMessage| true, + custom_cond_fn : |_:Arc<Bot>,_:PrivmsgMessage| true, } } /// set a trigger conditin callback that returns true if the listener shoud trigger - pub fn set_custom_cond_fn(&mut self,cond_fn: fn(Arc<Bot>,ServerMessage) -> bool) { + pub fn set_custom_cond_fn(&mut self,cond_fn: fn(Arc<Bot>,PrivmsgMessage) -> bool) { self.custom_cond_fn = cond_fn; } @@ -70,12 +70,9 @@ 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:ServerMessage) -> bool { + pub fn command_triggered(&self,bot:Arc<Bot>,msg:PrivmsgMessage) -> bool { - - fn cmd_called(cmd:&Command,bot:Arc<Bot>,message:ServerMessage) -> bool { - if let ServerMessage::Privmsg(msg) = message { - // dbg!(msg.clone()); + 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()); @@ -83,44 +80,35 @@ impl Command prefixed_cmd.push_str(&cmd.prefix); } prefixed_cmd.push_str(&cmd.command); - return msg.message_text.starts_with(prefixed_cmd.as_str()) - } else { false } + return message.message_text.starts_with(prefixed_cmd.as_str()) } - fn caller_badge_ok(cmd:&Command,_bot:Arc<Bot>,message:ServerMessage) -> bool { - - if let ServerMessage::Privmsg(msg) = message { - // dbg!(msg.clone()) - // dbg!(cmd.min_badge.clone()); - for badge in msg.badges { - - match cmd.min_badge.as_str() { - "broadcaster" => { - if badge.name == cmd.min_badge { return true } - else { return false } - }, - "moderator" => { - match badge.name.as_str() { - "moderator" | "broadcaster" => return true, - _ => (), - } - }, - "vip" => { - match badge.name.as_str() { - "vip" | "moderator" | "broadcaster" => return true, - _ => (), - } - }, - _ => return false, - } + fn caller_badge_ok(cmd:&Command,_bot:Arc<Bot>,message:PrivmsgMessage) -> bool { + for badge in message.badges { + + match cmd.min_badge.as_str() { + "broadcaster" => { + if badge.name == cmd.min_badge { return true } + else { return false } + }, + "moderator" => { + match badge.name.as_str() { + "moderator" | "broadcaster" => return true, + _ => (), + } + }, + "vip" => { + match badge.name.as_str() { + "vip" | "moderator" | "broadcaster" => return true, + _ => (), + } + }, + _ => return false, } - - return false; - } else { - return false; } + return false; } @@ -129,18 +117,15 @@ impl Command /// /// 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:ServerMessage) -> bool { - - if let ServerMessage::Privmsg(msg) = message { - if (cmd.admin_only && bot.get_admins().contains(&msg.sender.login)) || !cmd.admin_only { - return true; - } else { - return false; - } - } else { false } + 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; + } } - fn custom_cond_ok(cmd:&Command,bot:Arc<Bot>,message:ServerMessage) -> bool { + fn custom_cond_ok(cmd:&Command,bot:Arc<Bot>,message:PrivmsgMessage) -> bool { (cmd.custom_cond_fn)(bot,message) } diff --git a/src/botcore/modules.rs b/src/botcore/modules.rs new file mode 100644 index 0000000..e57ecc2 --- /dev/null +++ b/src/botcore/modules.rs @@ -0,0 +1,51 @@ + + +use crate::{Command, Listener}; + + +/// Bot `Module` that groups a set of `bot_objects` +/// +/// Elevated chatters can disable modules by their name or chat alias +pub struct Module +{ + name: String, + _alias: String, + listeners: Vec<Listener>, + commands: Vec<Command>, +} + +impl Module +{ + /// create a new module + pub fn new(name:String,alias:String) -> Module { + Module { + name, + _alias: alias, + listeners: vec![], + commands: vec![] + } + } + + /// Loads a `Listener` into the module + pub fn load_listener(&mut self,l : Listener) { + self.listeners.push(l); + } + + /// Loads a `Command` into the module + 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() + } + + pub fn get_name(&self) -> String { + self.name.clone() + } + +} \ No newline at end of file diff --git a/src/lib.rs b/src/lib.rs index 9c0379d..f99d4b3 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -2,30 +2,126 @@ //! //! Customize by adding additional bot objects //! -//! # Example Simple Bot -//! ``` +//! ## New Bot +//! +//! Uses Env defined variables to create and run the bot +//! +//! ```rust //! use forcebot_rs_v2::Bot; -//! +//! //! #[tokio::main] -//!pub async fn main() { -//! -//! /* 1. Create the bot using env */ -//! let bot = Bot::new(); -//! -//! /* 2. Run the bot */ -//! bot.run().await; -//! -//!} -//! -//! ``` +//! pub async fn main() { //! -//! # Example Code Add Listener +//! /* 1. Create the bot using env */ +//! let bot = Bot::new(); //! -//! Bot with a simple listener +//! /* 2. Run the bot */ +//! bot.run().await; //! -//! Example listener listens for a moderator badge and reply in chat +//! } //! //! ``` +//! +//! ## Module with Custom Command +//! +//! A `Module` is a group of bot objects (eg `Command`) that elevated users can manage. +//! +//! Bot objects are recommended to be loaded through a `Module` +//! +//! +//! ```rust +//! use std::sync::Arc; +//! +//! use forcebot_rs_v2::Bot; +//! use forcebot_rs_v2::asyncfn_box; +//! use forcebot_rs_v2::Command; +//! use forcebot_rs_v2::Module; +//! 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 module */ +//! let mut custom_mod = Module::new("test".to_string(), "".to_string()); +//! +//! /* 2. Create a new cmd */ +//! let mut cmd = Command::new("test".to_string(),"".to_string()); +//! +//! 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()) +//! } +//! +//! cmd.set_exec_fn(asyncfn_box(execbody)); +//! cmd.set_admin_only(false); +//! cmd.set_min_badge("moderator".to_string()); +//! +//! /* 3. Load the cmd into a new module */ +//! custom_mod.load_command(cmd); +//! +//! /* 4. Load the module into the bot */ +//! bot.load_module(custom_mod); +//! +//! /* Run the bot */ +//! bot.run().await; +//! +//! } +//! +//! ``` +//! +//! ## Simple Debug Listener +//! 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 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); +//! Result::Ok("Success".to_string()) +//! } +//! +//! /* 2d. Set and Store the execution body using `async_box()` */ +//! listener.set_exec_fn(asyncfn_box(execbody)); +//! +//! /* 3. Load the listener into the bot */ +//! bot.load_listener(listener); +//! +//! /* 4. Run the bot */ +//! bot.run().await; +//! +//! } +//! +//! ``` +//! +//! ## Modertor Reactor +//! +//! ``` +//! //! use std::sync::Arc; //! //! use forcebot_rs_v2::Bot; @@ -43,12 +139,15 @@ //! /* 1. Create a new blank Listener */ //! let mut listener = Listener::new(); //! -//! /* 2. Set a trigger condition callback */ +//! /* 2. Set a trigger condition function for listener */ +//! //! listener.set_trigger_cond_fn( //! |_:Arc<Bot>,message:ServerMessage| -//! if let ServerMessage::Privmsg(msg) = message { +//! 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"); //! return true; //! } //! } @@ -56,79 +155,31 @@ //! } 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 { -//! match bot.client.say_in_reply_to(&msg, String::from("test")).await { -//! Ok(_) => return Result::Ok("Success".to_string()) , -//! Err(_) => return Result::Err("Not Valid message type".to_string()) -//! } -//! } -//! Result::Err("Not Valid message type".to_string()) -//! } -//! -//! /* 4. Set the execution body using `async_box()` */ -//! listener.set_exec_fn(asyncfn_box(execbody)); -//! -//! /* 5. Load the Listener into the bot */ -//! bot.load_listener(listener); -//! -//! /* Run the bot */ -//! bot.run().await; -//! -//! } -//! ``` -//! -//! # Example Bot with Custom Command -//! ``` -//! use std::sync::Arc; -//! -//! use forcebot_rs_v2::Bot; -//! use forcebot_rs_v2::asyncfn_box; -//! use forcebot_rs_v2::Command; -//! 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 cmd */ -//! let mut cmd = Command::new("tester".to_string(),"".to_string()); -//! -//! /* 2. Define an async fn callback execution */ +//! /* 3. Define an async fn callback execution */ //! async fn execbody(bot:Arc<Bot>,message:ServerMessage) -> Result<String,String> { //! if let ServerMessage::Privmsg(msg) = message { -//! match bot.client.say_in_reply_to(&msg, String::from("cmd tested")).await { -//! Ok(_) => return Result::Ok("Success".to_string()) , -//! Err(_) => return Result::Err("Not Valid message type".to_string()) -//! } +//! 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()) //! } //! -//! /* 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(false); -//! -//! /* 5. optionally, set min badge*/ -//! cmd.set_min_badge("broadcaster".to_string()); -//! -//! /* 6. Load the cmd into the bot */ -//! bot.load_command(cmd); -//! -//! /* Run the bot */ -//! bot.run().await; +//! /* 4. Set and Store the execution body using `async_box()` */ +//! listener.set_exec_fn(asyncfn_box(execbody)); //! +//! /* 5. Load the listener into the bot */ +//! bot.load_listener(listener); +//! +//! /* Run the bot */ +//! bot.run().await; +//! //! } -//! ``` + + pub mod botcore; 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; \ No newline at end of file +pub use crate::botcore::bot_objects::command::Command; +pub use crate::botcore::modules::Module; \ No newline at end of file