From 2532cc5a3ef0ce14caeeb711b503cb632361d33b Mon Sep 17 00:00:00 2001
From: modulatingforce <modulatingforce@gmail.com>
Date: Sun, 26 Jan 2025 12:41:44 -0500
Subject: [PATCH 1/7] encap bot

---
 readme.md          |  61 ++++++++++++++++++++++++--
 src/botcore.rs     |   1 +
 src/botcore/bot.rs | 104 +++++++++++++++++++++++++++++++++++++++++++++
 src/main.rs        |  48 ++++++++-------------
 4 files changed, 180 insertions(+), 34 deletions(-)
 create mode 100644 src/botcore/bot.rs

diff --git a/readme.md b/readme.md
index 75ccfba..e77154e 100644
--- a/readme.md
+++ b/readme.md
@@ -1,6 +1,61 @@
-Twitch bot written in rust
+Twitch chat bot written in rust
+
+# Quick Start 
+
+Runs the bot's binary crate
+
+1. Generate a twitch access token
+
+    - Get a Bot Chat Token here - https://twitchtokengenerator.com
+    - More Info - https://dev.twitch.tv/docs/authentication
+
+2. Define an `.env` file with the following 
+
+```
+login_name=BOTNAME
+access_token=ACCESS_TOKEN
+bot_channels=BOTNAME
+prefix=`
+bot_admins=ADMIN
+```
+
+3. Build & run
 
-# Compile & Run
 ```
 cargo run
-```
\ No newline at end of file
+```
+
+# Example Code
+
+**Quick Start Main**
+
+Uses Env defined variables to create and run the bot
+
+```rust
+use botcore::bot::Bot;
+
+mod botcore;
+
+#[tokio::main]
+pub async fn main() {
+
+    /* 1. Create the bot using env */
+    let bot = Bot::new();
+
+    /* 2. Run the bot */
+    bot.run().await;
+
+}
+```
+
+# Crate Rust Documentation 
+
+Clean Build Documentation 
+```
+cargo clean && cargo doc
+```
+
+Open Crate Doc
+```
+cargo doc --open
+```
diff --git a/src/botcore.rs b/src/botcore.rs
index e69de29..96f1e66 100644
--- a/src/botcore.rs
+++ b/src/botcore.rs
@@ -0,0 +1 @@
+pub mod bot;
\ No newline at end of file
diff --git a/src/botcore/bot.rs b/src/botcore/bot.rs
new file mode 100644
index 0000000..f01a489
--- /dev/null
+++ b/src/botcore/bot.rs
@@ -0,0 +1,104 @@
+
+
+use tokio::sync::{mpsc::UnboundedReceiver, Mutex};
+use twitch_irc::{login::StaticLoginCredentials, message::ServerMessage, SecureTCPTransport, TwitchIRCClient};
+use dotenv::dotenv;
+use std::env;
+
+/// Twitch chat bot
+pub struct Bot {
+    /// Prefix for commands
+    _prefix: char,
+    /// inbound chat msg stream
+    incoming_msgs: Mutex<UnboundedReceiver<ServerMessage>>,
+    /// outbound chat client msg stream
+    client: TwitchIRCClient<SecureTCPTransport,StaticLoginCredentials>,
+    /// joined channels
+    botchannels: Vec<String>,
+}
+
+
+impl Bot {
+
+    /// Creates a new `Bot` using env variables
+    /// 
+    /// Be sure the following is defined in an `.env` file
+    /// - login_name
+    /// - access_token
+    /// - bot_channels
+    /// - prefix
+    /// - 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();
+        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)
+       
+
+    }
+
+    /// Creates a new `Bot` using bot information
+    /// 
+    /// Bot joined channels will include channels from `.env` and `botchannels` argument
+    pub fn new_from(bot_login_name:String,oauth_token:String,prefix:char,botchannels:Vec<String>) -> Bot {
+
+        dotenv().ok();
+        let bot_login_name = bot_login_name;
+
+        let config = twitch_irc::ClientConfig::new_simple(StaticLoginCredentials::new(
+            bot_login_name.to_owned(),
+            Some(oauth_token.to_owned()),
+        ));
+
+        let (incoming_messages, client) =
+            TwitchIRCClient::<SecureTCPTransport, StaticLoginCredentials>::new(config);
+
+        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());
+        }
+
+        Bot {
+             _prefix : prefix,
+             incoming_msgs : Mutex::new(incoming_messages),
+             client,
+             botchannels : botchannels_all,
+        }
+    }
+
+    /// Runs the bot 
+    pub async fn run(self) {
+
+        for chnl in &self.botchannels {
+            self.client.join(chnl.to_owned()).unwrap();
+        }
+
+        let join_handle = tokio::spawn(async move {
+            let mut in_msgs_lock = self.incoming_msgs.lock().await;
+            while let Some(message) = in_msgs_lock.recv().await {
+                //sprintln!("Received message: {:?}", message);
+                dbg!("Received message: {:?}", message);
+            }
+            drop(in_msgs_lock);
+        });
+        
+        join_handle.await.unwrap();
+    }
+    
+}
\ No newline at end of file
diff --git a/src/main.rs b/src/main.rs
index 6448eeb..93f840e 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -1,40 +1,26 @@
-use dotenv::dotenv;
-use twitch_irc::{login::StaticLoginCredentials, ClientConfig, SecureTCPTransport, TwitchIRCClient};
-use std::env;
+//! 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>
+
+pub use botcore::bot::Bot;
 
 mod botcore;
 
 #[tokio::main]
 pub async fn main() {
-    
-    dotenv().ok();
-    let login_name = env::var("login_name").unwrap().to_owned();
-    let oauth_token = env::var("access_token").unwrap().to_owned();
 
-    let mut botchannels = Vec::new();
+    /* 1. Create the bot using env */
+    let bot = Bot::new();
 
-    for chnl in env::var("bot_channels").unwrap().split(',') {
-            botchannels.push(chnl.to_owned());
-    }
-
-    let config = ClientConfig::new_simple(StaticLoginCredentials::new(
-        login_name.to_owned(),
-        Some(oauth_token.to_owned()),
-    ));
-
-    let (mut incoming_messages, client) =
-        TwitchIRCClient::<SecureTCPTransport, StaticLoginCredentials>::new(config);
-
-    for chnl in botchannels {
-        client.join(chnl.to_owned()).unwrap();
-    }
-
-    let join_handle = tokio::spawn(async move {
-        while let Some(message) = incoming_messages.recv().await {
-            println!("Received message: {:?}", message);
-        }
-    });
-    
-    join_handle.await.unwrap();
+    /* 2. Run the bot */
+    bot.run().await;
 
 }
-- 
2.49.0


From ef344402abbc58cfd65ae4dd959be01571c92be1 Mon Sep 17 00:00:00 2001
From: modulatingforce <modulatingforce@gmail.com>
Date: Sun, 26 Jan 2025 18:17:36 -0500
Subject: [PATCH 2/7] listener obj

---
 Cargo.toml                          | 15 ++++-
 readme.md                           | 88 +++++++++++++++++++++++++++--
 src/bin/simple_bot.rs               | 24 ++++++++
 src/bin/simple_bot_listener.rs      | 66 ++++++++++++++++++++++
 src/botcore.rs                      |  3 +-
 src/botcore/bot.rs                  | 42 +++++++++++---
 src/botcore/bot_objects.rs          |  1 +
 src/botcore/bot_objects/listener.rs | 83 +++++++++++++++++++++++++++
 src/lib.rs                          | 82 +++++++++++++++++++++++++++
 src/main.rs                         |  3 +-
 10 files changed, 390 insertions(+), 17 deletions(-)
 create mode 100644 src/bin/simple_bot.rs
 create mode 100644 src/bin/simple_bot_listener.rs
 create mode 100644 src/botcore/bot_objects.rs
 create mode 100644 src/botcore/bot_objects/listener.rs
 create mode 100644 src/lib.rs

diff --git a/Cargo.toml b/Cargo.toml
index 436b75c..53faa8d 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -2,8 +2,21 @@
 name = "forcebot-rs-v2"
 version = "0.1.0"
 edition = "2021"
+default-run = "forcebot-rs-v2"
 
 [dependencies]
 dotenv = "0.15.0"
 tokio = { version = "1.33.0", features = ["full"] }
-twitch-irc = "5.0.1"
\ No newline at end of file
+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
diff --git a/readme.md b/readme.md
index e77154e..4af0b0c 100644
--- a/readme.md
+++ b/readme.md
@@ -2,7 +2,7 @@ Twitch chat bot written in rust
 
 # Quick Start 
 
-Runs the bot's binary crate
+Run a Simple bot with Built in functionality 
 
 1. Generate a twitch access token
 
@@ -25,16 +25,32 @@ bot_admins=ADMIN
 cargo run
 ```
 
+# Binary Crates
+
+## Simple Bot
+Run a simple bot that logs into chat based on env
+
+```
+cargo run --bin simple_bot
+```
+
+## Simple Bot with Example Custom Listener
+Run a bot with some custom listeners
+
+```
+cargo run --bin simple_bot_listener
+```
+
+
+
 # Example Code
 
-**Quick Start Main**
+## Simple Bot
 
 Uses Env defined variables to create and run the bot
 
 ```rust
-use botcore::bot::Bot;
-
-mod botcore;
+use forcebot_rs_v2::botcore::bot::Bot;
 
 #[tokio::main]
 pub async fn main() {
@@ -46,10 +62,72 @@ pub async fn main() {
     bot.run().await;
 
 }
+
+```
+
+## Custom Bot with listener
+Bot with a simple listener 
+
+Example listener listens for a moderator badge and reply in chat 
+
+```rust
+use std::sync::Arc;
+
+use forcebot_rs_v2::botcore::{bot::Bot, bot_objects::listener::asyncfn_box};
+use forcebot_rs_v2::botcore::bot_objects::listener::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 callback  */
+    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") {
+                        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 {
+            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;
+
+}
+
 ```
 
 # Crate Rust Documentation 
 
+Create Crate documentation
+
 Clean Build Documentation 
 ```
 cargo clean && cargo doc
diff --git a/src/bin/simple_bot.rs b/src/bin/simple_bot.rs
new file mode 100644
index 0000000..674a5d9
--- /dev/null
+++ b/src/bin/simple_bot.rs
@@ -0,0 +1,24 @@
+//! 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 forcebot_rs_v2::botcore::bot::Bot;
+
+#[tokio::main]
+pub async fn main() {
+
+    /* 1. Create the bot using env */
+    let bot = Bot::new();
+
+    /* 2. Run the bot */
+    bot.run().await;
+
+}
diff --git a/src/bin/simple_bot_listener.rs b/src/bin/simple_bot_listener.rs
new file mode 100644
index 0000000..b2a9141
--- /dev/null
+++ b/src/bin/simple_bot_listener.rs
@@ -0,0 +1,66 @@
+//! Simple bot with a custom listeners that listens for moderators and respond to the moderator
+//! 
+//! 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::botcore::{bot::Bot, bot_objects::listener::asyncfn_box};
+use forcebot_rs_v2::botcore::bot_objects::listener::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| 
+            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;
+                    }
+                } 
+                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 {
+            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 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;
+
+}
diff --git a/src/botcore.rs b/src/botcore.rs
index 96f1e66..269a97a 100644
--- a/src/botcore.rs
+++ b/src/botcore.rs
@@ -1 +1,2 @@
-pub mod bot;
\ No newline at end of file
+pub mod bot;
+pub mod bot_objects;
\ No newline at end of file
diff --git a/src/botcore/bot.rs b/src/botcore/bot.rs
index f01a489..e21de83 100644
--- a/src/botcore/bot.rs
+++ b/src/botcore/bot.rs
@@ -3,23 +3,30 @@
 use tokio::sync::{mpsc::UnboundedReceiver, Mutex};
 use twitch_irc::{login::StaticLoginCredentials, message::ServerMessage, SecureTCPTransport, TwitchIRCClient};
 use dotenv::dotenv;
-use std::env;
+use std::{env, sync::Arc};
+
+use super::bot_objects::listener::Listener;
+
+
 
 /// Twitch chat bot
-pub struct Bot {
+pub struct Bot 
+{
     /// Prefix for commands
     _prefix: char,
     /// inbound chat msg stream
     incoming_msgs: Mutex<UnboundedReceiver<ServerMessage>>,
     /// outbound chat client msg stream
-    client: TwitchIRCClient<SecureTCPTransport,StaticLoginCredentials>,
+    pub client: TwitchIRCClient<SecureTCPTransport,StaticLoginCredentials>,
     /// joined channels
     botchannels: Vec<String>,
+    /// listeners
+    listeners: Vec<Listener>,
 }
 
 
-impl Bot {
-
+impl Bot
+{
     /// Creates a new `Bot` using env variables
     /// 
     /// Be sure the following is defined in an `.env` file
@@ -79,6 +86,7 @@ impl Bot {
              incoming_msgs : Mutex::new(incoming_messages),
              client,
              botchannels : botchannels_all,
+             listeners : vec![],
         }
     }
 
@@ -88,17 +96,35 @@ 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 = self.incoming_msgs.lock().await;
+            
+            let mut in_msgs_lock = bot.incoming_msgs.lock().await;
+            
             while let Some(message) = in_msgs_lock.recv().await {
-                //sprintln!("Received message: {:?}", message);
-                dbg!("Received message: {:?}", message);
+                // dbg!("Received message: {:?}", message.clone());
+                
+                for listener in &(*bot).listeners {
+                    
+                    let a = listener.clone();
+                    if a.cond_triggered(bot.clone(),message.clone()) {
+                        
+                        let _ = listener.execute_fn(bot.clone(),message.clone()).await;
+                    }
+                }
             }
             drop(in_msgs_lock);
         });
         
         join_handle.await.unwrap();
     }
+
+    /// Loads a `Listener` into the bot
+    pub fn load_listener(&mut self,l : Listener) {
+        self.listeners.push(l);
+    }
     
 }
\ No newline at end of file
diff --git a/src/botcore/bot_objects.rs b/src/botcore/bot_objects.rs
new file mode 100644
index 0000000..baefb0b
--- /dev/null
+++ b/src/botcore/bot_objects.rs
@@ -0,0 +1 @@
+pub mod listener;
\ No newline at end of file
diff --git a/src/botcore/bot_objects/listener.rs b/src/botcore/bot_objects/listener.rs
new file mode 100644
index 0000000..0f953c4
--- /dev/null
+++ b/src/botcore/bot_objects/listener.rs
@@ -0,0 +1,83 @@
+use std::sync::Arc;
+
+use twitch_irc::message::ServerMessage;
+
+use crate::botcore::bot::Bot;
+
+/// Bot `Listener`` that stores trigger condition callback and a execution functon
+/// 
+/// Use `asyncfn_box()` on custom async execution bodies
+#[derive(Clone)]
+pub struct Listener
+{
+    trigger_cond_fn : fn(Arc<Bot>,ServerMessage) -> bool,
+    exec_fn : Arc<ExecBody>,
+}
+
+impl Listener
+{
+
+    /// Creates a new empty `Listener` . 
+    /// Call `set_trigger_cond_fn()` and `set_exec_fn()` to trigger & execution function callbacks
+    pub fn new() -> Listener {
+
+        async fn execbody(_:Arc<Bot>,_:ServerMessage) -> Result<String,String> {Result::Ok("success".to_string()) }
+
+        Listener {
+            trigger_cond_fn : |_:Arc<Bot>,_:ServerMessage| false,
+            exec_fn : Arc::new(asyncfn_box(execbody))
+        }
+    }
+
+    /// 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) {
+        self.trigger_cond_fn = cond_fn;
+    }
+
+    /// sets the execution body of the listener for when it triggers
+    /// 
+    /// 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 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));
+    /// ```
+    /// 
+    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 fn cond_triggered(&self,bot:Arc<Bot>,msg:ServerMessage) -> bool {
+        (self.trigger_cond_fn)(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
+    }
+}
+
+
+use std::boxed::Box;
+use std::future::Future;
+use std::pin::Pin;
+
+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
+where
+    T: Future<Output = Result<String,String>> + Send + 'static,
+{
+    Box::new(move |a,b| Box::pin(f(a,b)))
+}
\ No newline at end of file
diff --git a/src/lib.rs b/src/lib.rs
new file mode 100644
index 0000000..ce1adca
--- /dev/null
+++ b/src/lib.rs
@@ -0,0 +1,82 @@
+//! `forcebot-rs-v2` : Twitch chat bot written in rust
+//! Customize by adding additional bot objects
+//! 
+//! # Example Simple Bot
+//! ```
+//! use forcebot_rs_v2::botcore::bot::Bot;
+//!
+//! #[tokio::main]
+//!pub async fn main() {
+//!
+//!    /* 1. Create the bot using env */
+//!    let bot = Bot::new();
+//!
+//!    /* 2. Run the bot */
+//!    bot.run().await;
+//!
+//!}
+//!
+//! ```
+//! 
+//! # Example Code Add Listener
+//! 
+//! Bot with a simple listener 
+//! 
+//! Example listener listens for a moderator badge and reply in chat 
+//! 
+//! ```
+//! use std::sync::Arc;
+//! 
+//! use forcebot_rs_v2::botcore::{bot::Bot, bot_objects::listener::asyncfn_box};
+//! use forcebot_rs_v2::botcore::bot_objects::listener::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 callback  */
+//!     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") {
+//!                         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 {
+//!            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;
+//! 
+//! }
+//! ```
+//! 
+//! 
+
+pub mod botcore;
\ No newline at end of file
diff --git a/src/main.rs b/src/main.rs
index 93f840e..ebbe171 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -10,9 +10,8 @@
 //! - Get a Bot Chat Token here - <https://twitchtokengenerator.com>
 //! - More Info - <https://dev.twitch.tv/docs/authentication>
 
-pub use botcore::bot::Bot;
+use forcebot_rs_v2::botcore::bot::Bot;
 
-mod botcore;
 
 #[tokio::main]
 pub async fn main() {
-- 
2.49.0


From db930679033daa8c17c038dfdebfb441129a20e4 Mon Sep 17 00:00:00 2001
From: modulatingforce <modulatingforce@gmail.com>
Date: Tue, 28 Jan 2025 09:04:35 -0500
Subject: [PATCH 3/7] command obj

---
 readme.md                           |  58 +++++++++-
 src/bin/bot_cmd_example.rs          |  51 +++++++++
 src/bin/simple_bot.rs               |   2 +-
 src/bin/simple_bot_listener.rs      |   5 +-
 src/botcore/bot.rs                  |  56 ++++++++--
 src/botcore/bot_objects.rs          |  24 ++++-
 src/botcore/bot_objects/command.rs  | 157 ++++++++++++++++++++++++++++
 src/botcore/bot_objects/listener.rs |  21 +---
 src/lib.rs                          |  56 +++++++++-
 9 files changed, 395 insertions(+), 35 deletions(-)
 create mode 100644 src/bin/bot_cmd_example.rs
 create mode 100644 src/botcore/bot_objects/command.rs

diff --git a/readme.md b/readme.md
index 4af0b0c..6671b73 100644
--- a/readme.md
+++ b/readme.md
@@ -41,6 +41,14 @@ Run a bot with some custom listeners
 cargo run --bin simple_bot_listener
 ```
 
+## Bot with Example Custom Command
+Run a bot with some custom listeners
+
+```
+cargo run --bin bot_cmd_example
+```
+
+
 
 
 # Example Code
@@ -50,7 +58,7 @@ cargo run --bin simple_bot_listener
 Uses Env defined variables to create and run the bot
 
 ```rust
-use forcebot_rs_v2::botcore::bot::Bot;
+use forcebot_rs_v2::Bot;
 
 #[tokio::main]
 pub async fn main() {
@@ -73,8 +81,9 @@ Example listener listens for a moderator badge and reply in chat
 ```rust
 use std::sync::Arc;
 
-use forcebot_rs_v2::botcore::{bot::Bot, bot_objects::listener::asyncfn_box};
-use forcebot_rs_v2::botcore::bot_objects::listener::Listener;
+use forcebot_rs_v2::Bot;
+use forcebot_rs_v2::asyncfn_box;
+use forcebot_rs_v2::Listener;
 use twitch_irc::message::ServerMessage;
 
 
@@ -124,6 +133,49 @@ pub async fn main() {
 
 ```
 
+## Bot with Custom command
+
+```rust
+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 */
+    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()) 
+            }
+        }
+        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. Load the cmd into the bot */
+    bot.load_command(cmd);
+
+    /* Run the bot */
+    bot.run().await;
+
+}
+```
+
 # Crate Rust Documentation 
 
 Create Crate documentation
diff --git a/src/bin/bot_cmd_example.rs b/src/bin/bot_cmd_example.rs
new file mode 100644
index 0000000..fa3d6a6
--- /dev/null
+++ b/src/bin/bot_cmd_example.rs
@@ -0,0 +1,51 @@
+//! Bot with custom example commands that responds to caller if allowed
+//! 
+//! 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 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 */
+    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()) 
+            }
+        }
+        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. Load the cmd into the bot */
+    bot.load_command(cmd);
+
+    /* Run the bot */
+    bot.run().await;
+
+}
diff --git a/src/bin/simple_bot.rs b/src/bin/simple_bot.rs
index 674a5d9..cbe682b 100644
--- a/src/bin/simple_bot.rs
+++ b/src/bin/simple_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::botcore::bot::Bot;
+use forcebot_rs_v2::Bot;
 
 #[tokio::main]
 pub async fn main() {
diff --git a/src/bin/simple_bot_listener.rs b/src/bin/simple_bot_listener.rs
index b2a9141..11a62e9 100644
--- a/src/bin/simple_bot_listener.rs
+++ b/src/bin/simple_bot_listener.rs
@@ -13,8 +13,9 @@
 
 use std::sync::Arc;
 
-use forcebot_rs_v2::botcore::{bot::Bot, bot_objects::listener::asyncfn_box};
-use forcebot_rs_v2::botcore::bot_objects::listener::Listener;
+use forcebot_rs_v2::Bot;
+use forcebot_rs_v2::asyncfn_box;
+use forcebot_rs_v2::Listener;
 use twitch_irc::message::ServerMessage;
 
 
diff --git a/src/botcore/bot.rs b/src/botcore/bot.rs
index e21de83..4292dac 100644
--- a/src/botcore/bot.rs
+++ b/src/botcore/bot.rs
@@ -5,6 +5,8 @@ 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;
 
 
@@ -13,15 +15,19 @@ use super::bot_objects::listener::Listener;
 pub struct Bot 
 {
     /// Prefix for commands
-    _prefix: char,
+    prefix: String,
     /// inbound chat msg stream
     incoming_msgs: Mutex<UnboundedReceiver<ServerMessage>>,
     /// outbound chat client msg stream
     pub client: TwitchIRCClient<SecureTCPTransport,StaticLoginCredentials>,
     /// joined channels
     botchannels: Vec<String>,
+    /// admin chatters
+    admins : Vec<String>,
     /// listeners
     listeners: Vec<Listener>,
+    /// commands
+    commands: Vec<Command>,
 }
 
 
@@ -42,16 +48,17 @@ impl Bot
         let oauth_token = env::var("access_token").unwrap().to_owned();
         let prefix = env::var("prefix")
             .unwrap()
-            .to_owned()
-            .chars()
-            .next()
-            .expect("ERROR : when defining prefix");
+            .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)
        
@@ -61,7 +68,7 @@ impl Bot
     /// Creates a new `Bot` using bot information
     /// 
     /// Bot joined channels will include channels from `.env` and `botchannels` argument
-    pub fn new_from(bot_login_name:String,oauth_token:String,prefix:char,botchannels:Vec<String>) -> Bot {
+    pub 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;
@@ -81,12 +88,24 @@ impl Bot
             botchannels_all.push(chnl.to_owned());
         }
 
+
+        let mut admins = Vec::new();
+
+        if let Ok(value) = env::var("bot_admins") {
+            for admin in value.split(',') {
+                admins.push(String::from(admin))        
+            }
+        }
+
+
         Bot {
-             _prefix : prefix,
+             prefix,
              incoming_msgs : Mutex::new(incoming_messages),
              client,
              botchannels : botchannels_all,
              listeners : vec![],
+             commands : vec![],
+             admins,
         }
     }
 
@@ -115,6 +134,14 @@ impl Bot
                         let _ = listener.execute_fn(bot.clone(),message.clone()).await;
                     }
                 }
+                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;
+                    }
+                }
             }
             drop(in_msgs_lock);
         });
@@ -126,5 +153,20 @@ impl Bot
     pub fn load_listener(&mut self,l : Listener) {
         self.listeners.push(l);
     }
+
+    /// Loads a `Command` into the bot
+    pub fn load_command(&mut self,c : Command) {
+        self.commands.push(c);
+    }
+    
+
+
+    pub fn get_prefix(&self) -> String {
+        self.prefix.clone()
+    }
+
+    pub fn get_admins(&self) -> Vec<String> {
+        self.admins.clone()
+    }
     
 }
\ No newline at end of file
diff --git a/src/botcore/bot_objects.rs b/src/botcore/bot_objects.rs
index baefb0b..7bb8186 100644
--- a/src/botcore/bot_objects.rs
+++ b/src/botcore/bot_objects.rs
@@ -1 +1,23 @@
-pub mod listener;
\ No newline at end of file
+pub mod listener;
+pub mod command;
+
+
+use std::boxed::Box;
+use std::future::Future;
+use std::pin::Pin;
+use std::sync::Arc;
+
+use twitch_irc::message::ServerMessage;
+
+use super::bot::Bot;
+
+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
+where
+    T: Future<Output = Result<String,String>> + Send + 'static,
+{
+    Box::new(move |a,b| Box::pin(f(a,b)))
+}
\ No newline at end of file
diff --git a/src/botcore/bot_objects/command.rs b/src/botcore/bot_objects/command.rs
new file mode 100644
index 0000000..2239991
--- /dev/null
+++ b/src/botcore/bot_objects/command.rs
@@ -0,0 +1,157 @@
+use std::sync::Arc;
+
+use twitch_irc::message::ServerMessage;
+
+use crate::{asyncfn_box, botcore::bot::Bot};
+
+use super::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 
+/// 
+/// Use `asyncfn_box()` on custom async execution bodies
+#[derive(Clone)]
+pub struct Command
+{
+    command : String,
+    exec_fn : Arc<ExecBody>,
+    min_badge : String,
+    admin_only : bool,
+    prefix : String,
+    custom_cond_fn : fn(Arc<Bot>,ServerMessage) -> bool,
+}
+
+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(command:String,prefix:String) -> Command {
+
+        async fn execbody(_:Arc<Bot>,_:ServerMessage) -> Result<String,String> 
+        { Result::Ok("success".to_string()) }
+
+        Command {
+            command ,
+            prefix ,
+            exec_fn : Arc::new(asyncfn_box(execbody)),
+            min_badge : "vip".to_string(),
+            admin_only : true,
+            custom_cond_fn : |_:Arc<Bot>,_:ServerMessage| 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) {
+        self.custom_cond_fn = cond_fn;
+    }
+
+    /// sets the execution body of the listener for when it triggers
+    /// 
+    /// Use`asyncfn_box()` on the async fn when storing
+    /// 
+    /// 
+    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 fn command_triggered(&self,bot:Arc<Bot>,msg:ServerMessage) -> bool {
+
+    
+        fn cmd_called(cmd:&Command,bot:Arc<Bot>,message:ServerMessage) -> bool {
+            if let ServerMessage::Privmsg(msg) = message {
+                // dbg!(msg.clone());
+                let mut prefixed_cmd = "".to_string();
+                if cmd.prefix == "" {
+                    prefixed_cmd.push_str(&bot.get_prefix());
+                } else { 
+                    prefixed_cmd.push_str(&cmd.prefix); 
+                }
+                prefixed_cmd.push_str(&cmd.command);
+                return msg.message_text.starts_with(prefixed_cmd.as_str())
+            } else { false }
+        }
+
+        
+        fn caller_badge_ok(cmd:&Command,_bot:Arc<Bot>,message:ServerMessage) -> bool {
+
+            if let ServerMessage::Privmsg(msg) = message {
+                // dbg!(msg.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,
+                    }
+                }
+
+                return false;
+            } else {
+                return false;
+            }
+
+        }
+
+        
+        /// 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:ServerMessage) -> bool {
+
+            if let ServerMessage::Privmsg(msg) = message {
+                if cmd.admin_only && bot.get_admins().contains(&msg.sender.login) {
+                    return true;
+                } else {
+                    return false;
+                }
+            } else { false }
+        }
+
+        fn custom_cond_ok(cmd:&Command,bot:Arc<Bot>,message:ServerMessage) -> bool {
+            (cmd.custom_cond_fn)(bot,message)
+        }
+
+        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, 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).await
+    }
+}
diff --git a/src/botcore/bot_objects/listener.rs b/src/botcore/bot_objects/listener.rs
index 0f953c4..2bebb8e 100644
--- a/src/botcore/bot_objects/listener.rs
+++ b/src/botcore/bot_objects/listener.rs
@@ -2,7 +2,9 @@ use std::sync::Arc;
 
 use twitch_irc::message::ServerMessage;
 
-use crate::botcore::bot::Bot;
+use crate::{asyncfn_box, Bot};
+
+use super::ExecBody;
 
 /// Bot `Listener`` that stores trigger condition callback and a execution functon
 /// 
@@ -10,7 +12,9 @@ use crate::botcore::bot::Bot;
 #[derive(Clone)]
 pub struct Listener
 {
+    /// trigger condition
     trigger_cond_fn : fn(Arc<Bot>,ServerMessage) -> bool,
+    /// execution body
     exec_fn : Arc<ExecBody>,
 }
 
@@ -66,18 +70,3 @@ impl Listener
     }
 }
 
-
-use std::boxed::Box;
-use std::future::Future;
-use std::pin::Pin;
-
-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
-where
-    T: Future<Output = Result<String,String>> + Send + 'static,
-{
-    Box::new(move |a,b| Box::pin(f(a,b)))
-}
\ No newline at end of file
diff --git a/src/lib.rs b/src/lib.rs
index ce1adca..4b1a70d 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -1,9 +1,10 @@
-//! `forcebot-rs-v2` : Twitch chat bot written in rust
+//! `forcebot-rs-v2` : Twitch chat bot written in rust.
+//! 
 //! Customize by adding additional bot objects
 //! 
 //! # Example Simple Bot
 //! ```
-//! use forcebot_rs_v2::botcore::bot::Bot;
+//! use forcebot_rs_v2::Bot;
 //!
 //! #[tokio::main]
 //!pub async fn main() {
@@ -27,8 +28,9 @@
 //! ```
 //! use std::sync::Arc;
 //! 
-//! use forcebot_rs_v2::botcore::{bot::Bot, bot_objects::listener::asyncfn_box};
-//! use forcebot_rs_v2::botcore::bot_objects::listener::Listener;
+//! use forcebot_rs_v2::Bot;
+//! use forcebot_rs_v2::asyncfn_box;
+//! use forcebot_rs_v2::Listener;
 //! use twitch_irc::message::ServerMessage;
 //! 
 //! 
@@ -77,6 +79,50 @@
 //! }
 //! ```
 //! 
+//! # 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 */
+//!     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()) 
+//!             }
+//!         }
+//!         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. Load the cmd into the bot */
+//!     bot.load_command(cmd);
+//! 
+//!     /* Run the bot */
+//!     bot.run().await;
+//! 
+//! }
+//! ```
 
-pub mod botcore;
\ No newline at end of file
+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
-- 
2.49.0


From 3e4584c39f4b8eca99c6d960f0e2781abe087c05 Mon Sep 17 00:00:00 2001
From: modulatingforce <modulatingforce@gmail.com>
Date: Tue, 28 Jan 2025 11:43:57 -0500
Subject: [PATCH 4/7] fun_bot init

---
 readme.md          | 10 +++++++++-
 src/bin/fun_bot.rs | 49 ++++++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 58 insertions(+), 1 deletion(-)
 create mode 100644 src/bin/fun_bot.rs

diff --git a/readme.md b/readme.md
index 6671b73..bbd2096 100644
--- a/readme.md
+++ b/readme.md
@@ -27,13 +27,21 @@ cargo run
 
 # Binary Crates
 
-## Simple Bot
+## Simple Empty Bot
 Run a simple bot that logs into chat based on env
 
 ```
 cargo run --bin simple_bot
 ```
 
+## Fun Bot
+Run a forcebot with fun catered customizations
+
+```
+cargo run --bin fun_bot
+```
+
+
 ## Simple Bot with Example Custom Listener
 Run a bot with some custom listeners
 
diff --git a/src/bin/fun_bot.rs b/src/bin/fun_bot.rs
new file mode 100644
index 0000000..8469bee
--- /dev/null
+++ b/src/bin/fun_bot.rs
@@ -0,0 +1,49 @@
+//! Fun forcebot with catered customizations #todo
+//! 
+//! 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 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("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));
+
+    /* 4. Load the cmd into the bot */
+    bot.load_command(cmd);
+
+    /* Run the bot */
+    bot.run().await;
+
+}
-- 
2.49.0


From b2244a81eb4cbac16cbf6f384b85fd2c871a5253 Mon Sep 17 00:00:00 2001
From: modulatingforce <modulatingforce@gmail.com>
Date: Tue, 28 Jan 2025 12:47:21 -0500
Subject: [PATCH 5/7] cmd set admin only & min badge

---
 readme.md                          |  9 ++++++---
 src/bin/bot_cmd_example.rs         |  8 +++++++-
 src/bin/fun_bot.rs                 |  3 +++
 src/botcore/bot_objects/command.rs | 22 +++++++++++++++++++++-
 src/lib.rs                         |  8 +++++++-
 5 files changed, 44 insertions(+), 6 deletions(-)

diff --git a/readme.md b/readme.md
index bbd2096..3477c61 100644
--- a/readme.md
+++ b/readme.md
@@ -172,10 +172,13 @@ pub async fn main() {
         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);
 
-    /* 4. Load the cmd into the bot */
+    /* 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 */
diff --git a/src/bin/bot_cmd_example.rs b/src/bin/bot_cmd_example.rs
index fa3d6a6..7335461 100644
--- a/src/bin/bot_cmd_example.rs
+++ b/src/bin/bot_cmd_example.rs
@@ -42,7 +42,13 @@ pub async fn main() {
     /* 3. Set and Store the execution body using `async_box()`  */
     cmd.set_exec_fn(asyncfn_box(execbody));
 
-    /* 4. Load the cmd into the bot */
+    /* 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 */
diff --git a/src/bin/fun_bot.rs b/src/bin/fun_bot.rs
index 8469bee..7df3f4d 100644
--- a/src/bin/fun_bot.rs
+++ b/src/bin/fun_bot.rs
@@ -40,6 +40,9 @@ pub async fn main() {
     /* 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);
 
diff --git a/src/botcore/bot_objects/command.rs b/src/botcore/bot_objects/command.rs
index 2239991..59df04b 100644
--- a/src/botcore/bot_objects/command.rs
+++ b/src/botcore/bot_objects/command.rs
@@ -92,6 +92,7 @@ impl Command
 
             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() {
@@ -131,7 +132,7 @@ impl Command
         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) {
+                if (cmd.admin_only && bot.get_admins().contains(&msg.sender.login)) || !cmd.admin_only {
                     return true;
                 } else {
                     return false;
@@ -143,6 +144,14 @@ impl Command
             (cmd.custom_cond_fn)(bot,message)
         }
 
+        // dbg!(msg.clone());
+
+        // 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()) &&
         admin_only_ok(self, bot.clone(), msg.clone()) &&
@@ -154,4 +163,15 @@ impl Command
     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) {
+        self.min_badge = min_badge
+    }
+
+    /// sets admin_only
+    pub fn set_admin_only(&mut self,admin_only:bool) {
+        self.admin_only = admin_only
+    }
 }
diff --git a/src/lib.rs b/src/lib.rs
index 4b1a70d..9c0379d 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -112,7 +112,13 @@
 //!     /* 3. Set and Store the execution body using `async_box()`  */
 //!     cmd.set_exec_fn(asyncfn_box(execbody));
 //! 
-//!     /* 4. Load the cmd into the bot */
+//!     /* 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 */
-- 
2.49.0


From 92b6cd3f941197885319e68d61e536cbe7101228 Mon Sep 17 00:00:00 2001
From: modulatingforce <modulatingforce@gmail.com>
Date: Wed, 29 Jan 2025 05:29:22 -0500
Subject: [PATCH 6/7] modules functionality

---
 readme.md                                     | 171 +++++++++++---
 src/bin/fun_bot.rs                            |  76 ++++---
 ...e_bot_listener.rs => moderator_reactor.rs} |   6 +-
 src/bin/{simple_bot.rs => new_bot.rs}         |   0
 ...t_cmd_example.rs => simple_command_bot.rs} |  10 +-
 src/bin/simple_debug_listener.rs              |  47 ++++
 src/bin/simple_module.rs                      |  62 +++++
 src/botcore.rs                                |   3 +-
 src/botcore/bot.rs                            | 106 +++++++--
 src/botcore/bot_objects.rs                    |  86 +++++++
 src/botcore/bot_objects/command.rs            |  87 +++----
 src/botcore/modules.rs                        |  51 +++++
 src/lib.rs                                    | 215 +++++++++++-------
 13 files changed, 700 insertions(+), 220 deletions(-)
 rename src/bin/{simple_bot_listener.rs => moderator_reactor.rs} (87%)
 rename src/bin/{simple_bot.rs => new_bot.rs} (100%)
 rename src/bin/{bot_cmd_example.rs => simple_command_bot.rs} (80%)
 create mode 100644 src/bin/simple_debug_listener.rs
 create mode 100644 src/bin/simple_module.rs
 create mode 100644 src/botcore/modules.rs

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
-- 
2.49.0


From 33dcf4c4e7b6ad1d974ca0b2f43e997fffd5e409 Mon Sep 17 00:00:00 2001
From: modulatingforce <modulatingforce@gmail.com>
Date: Wed, 29 Jan 2025 07:52:41 -0500
Subject: [PATCH 7/7] Badges & Doc adj

---
 readme.md                          | 86 +++++++++++++++++------------
 src/bin/fun_bot.rs                 |  4 +-
 src/bin/simple_command_bot.rs      |  3 +-
 src/bin/simple_module.rs           | 74 +++++++++++++++----------
 src/botcore/bot_objects.rs         | 16 +++++-
 src/botcore/bot_objects/command.rs | 20 +++----
 src/lib.rs                         | 89 ++++++++++++++++++------------
 7 files changed, 178 insertions(+), 114 deletions(-)

diff --git a/readme.md b/readme.md
index e44ecf9..d192a1b 100644
--- a/readme.md
+++ b/readme.md
@@ -98,22 +98,19 @@ pub async fn main() {
 
 ```
 
-## Module with Custom Command
+## Customize with Modules
 
-A `Module` is a group of bot objects (eg `Command`) that elevated users can manage.
+A `Module` is a group of bot objects (eg `Command`) that elevated users can manage through built in `disable` and `enable` commands
 
-Bot objects are recommended to be loaded through a `Module` 
+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 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() {
@@ -121,35 +118,56 @@ 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);
+    /* 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 with a loaded command
+    pub fn new() -> Module {
+        /* 1. Create a new module */
+        let mut custom_mod = Module::new("test".to_string(), "".to_string());
+
+        /* 2. Load the cmd into a new module */
+        custom_mod.load_command(cmd_test());
+
+        custom_mod
+
+    }
+
+    pub fn cmd_test() -> Command {
+        /* 1. Create a new cmd */
+        let mut cmd = Command::new("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(asyncfn_box(execbody));
+        cmd.set_admin_only(false);
+        cmd.set_min_badge(Badge::Moderator);
+
+        cmd
+        }
+}
+
 ```
 
 ## Simple Debug Listener
@@ -194,7 +212,7 @@ pub async fn main() {
 
 ```
 
-## Moderator Reator
+## Moderator Reactor
 
 Example listener listens for a moderator badge and reply in chat 
 
diff --git a/src/bin/fun_bot.rs b/src/bin/fun_bot.rs
index 9ed8eb5..de67fda 100644
--- a/src/bin/fun_bot.rs
+++ b/src/bin/fun_bot.rs
@@ -34,7 +34,7 @@ pub async fn main() {
 pub mod funbot_objs {
     use std::sync::Arc;
 
-    use forcebot_rs_v2::{asyncfn_box, Bot, Command, Module};
+    use forcebot_rs_v2::{asyncfn_box, Badge, Bot, Command, Module};
     use twitch_irc::message::ServerMessage;
 
     /// Create a Module with a loaded Command object
@@ -63,7 +63,7 @@ pub mod funbot_objs {
         cmd.set_exec_fn(asyncfn_box(execbody));
 
         cmd.set_admin_only(false);
-        cmd.set_min_badge("vip".to_string());
+        cmd.set_min_badge(Badge::Vip);
         cmd
 
     }
diff --git a/src/bin/simple_command_bot.rs b/src/bin/simple_command_bot.rs
index 0e60bc1..a6ec70e 100644
--- a/src/bin/simple_command_bot.rs
+++ b/src/bin/simple_command_bot.rs
@@ -15,6 +15,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::Command;
@@ -46,7 +47,7 @@ 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/simple_module.rs b/src/bin/simple_module.rs
index 3795f8e..61d66e5 100644
--- a/src/bin/simple_module.rs
+++ b/src/bin/simple_module.rs
@@ -17,14 +17,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 forcebot_rs_v2::Module;
-use twitch_irc::message::ServerMessage;
-
 
 #[tokio::main]
 pub async fn main() {
@@ -32,31 +25,52 @@ 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);
+    /* 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 with a loaded command
+    pub fn new() -> Module {
+        /* 1. Create a new module */
+        let mut custom_mod = Module::new("test".to_string(), "".to_string());
+
+        /* 2. Load the cmd into a new module */
+        custom_mod.load_command(cmd_test());
+
+        custom_mod
+
+    }
+
+    pub fn cmd_test() -> Command {
+        /* 1. Create a new cmd */
+        let mut cmd = Command::new("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(asyncfn_box(execbody));
+        cmd.set_admin_only(false);
+        cmd.set_min_badge(Badge::Moderator);
+
+        cmd
+        }
+}
\ No newline at end of file
diff --git a/src/botcore/bot_objects.rs b/src/botcore/bot_objects.rs
index da787d3..9fb6985 100644
--- a/src/botcore/bot_objects.rs
+++ b/src/botcore/bot_objects.rs
@@ -11,6 +11,16 @@ use twitch_irc::message::ServerMessage;
 
 use super::bot::Bot;
 
+
+/// chat badge
+#[derive(Clone)]
+pub enum Badge {
+    Moderator,
+    Broadcaster,
+    Vip
+}
+
+
 pub type ExecBody = Box<
     dyn Fn(Arc<Bot>,ServerMessage) -> Pin<Box<dyn Future<Output = Result<String,String>> + Send>> + Send + Sync,
 >;
@@ -32,7 +42,7 @@ pub mod built_in_objects {
 
     use twitch_irc::message::ServerMessage;
 
-    use crate::{asyncfn_box, Bot, Command};
+    use crate::{asyncfn_box, Badge, Bot, Command};
 
 
     /// create a vector of command build in objects
@@ -71,7 +81,7 @@ pub mod built_in_objects {
         cmd.set_admin_only(false);
 
         /* 5. optionally, set min badge*/
-        cmd.set_min_badge("moderator".to_string());
+        cmd.set_min_badge(Badge::Moderator);
         cmd
         
     } 
@@ -101,7 +111,7 @@ pub mod built_in_objects {
         cmd.set_admin_only(false);
 
         /* 5. optionally, set min badge*/
-        cmd.set_min_badge("moderator".to_string());
+        cmd.set_min_badge(Badge::Moderator);
         cmd
     }
 
diff --git a/src/botcore/bot_objects/command.rs b/src/botcore/bot_objects/command.rs
index d904b06..574a22e 100644
--- a/src/botcore/bot_objects/command.rs
+++ b/src/botcore/bot_objects/command.rs
@@ -2,7 +2,7 @@ use std::sync::Arc;
 
 use twitch_irc::message::{PrivmsgMessage, ServerMessage};
 
-use crate::{asyncfn_box, botcore::bot::Bot};
+use crate::{asyncfn_box, botcore::bot::Bot, Badge};
 
 use super::ExecBody;
 
@@ -22,7 +22,7 @@ pub struct Command
 {
     command : String,
     exec_fn : Arc<ExecBody>,
-    min_badge : String,
+    min_badge : Badge,
     admin_only : bool,
     prefix : String,
     custom_cond_fn : fn(Arc<Bot>,PrivmsgMessage) -> bool,
@@ -47,7 +47,7 @@ impl Command
             command ,
             prefix ,
             exec_fn : Arc::new(asyncfn_box(execbody)),
-            min_badge : "vip".to_string(),
+            min_badge : Badge::Vip,
             admin_only : true,
             custom_cond_fn : |_:Arc<Bot>,_:PrivmsgMessage| true,
         }
@@ -87,24 +87,23 @@ impl Command
         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 }
+                match cmd.min_badge {
+                    Badge::Broadcaster => {
+                        if badge.name == "broadcaster" { return true }
                         else { return false } 
                     },
-                    "moderator" => {
+                    Badge::Moderator => {
                         match badge.name.as_str() {
                             "moderator" | "broadcaster" => return true,
                             _ => (),
                         }
                     },
-                    "vip" => {
+                    Badge::Vip => {
                         match badge.name.as_str() {
                             "vip" | "moderator" | "broadcaster" => return true,
                             _ => (),
                         }
                     },
-                    _ => return false,
                 }
             }
 
@@ -151,7 +150,8 @@ impl Command
 
 
     /// 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:String) {
+    pub fn set_min_badge(&mut self,min_badge:Badge) {
         self.min_badge = min_badge
     }
 
diff --git a/src/lib.rs b/src/lib.rs
index f99d4b3..9ecb36f 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -22,22 +22,20 @@
 //! 
 //! ```
 //! 
-//! ## Module with Custom Command
 //! 
-//! A `Module` is a group of bot objects (eg `Command`) that elevated users can manage.
+//! ## Customize with Modules
 //! 
-//! Bot objects are recommended to be loaded through a `Module` 
+//! A `Module` is a group of bot objects (eg `Command`) that elevated users can manage through built in `disable` and `enable` commands
+//! 
+//! 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 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() {
@@ -45,35 +43,56 @@
 //!     /* 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);
+//!     /* 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 with a loaded command
+//!     pub fn new() -> Module {
+//!         /* 1. Create a new module */
+//!         let mut custom_mod = Module::new("test".to_string(), "".to_string());
+//! 
+//!         /* 2. Load the cmd into a new module */
+//!         custom_mod.load_command(cmd_test());
+//! 
+//!         custom_mod
+//! 
+//!     }
+//! 
+//!     pub fn cmd_test() -> Command {
+//!         /* 1. Create a new cmd */
+//!         let mut cmd = Command::new("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(asyncfn_box(execbody));
+//!         cmd.set_admin_only(false);
+//!         cmd.set_min_badge(Badge::Moderator);
+//! 
+//!         cmd
+//!         }
+//! }
+//! 
 //! ```
 //! 
 //! ## Simple Debug Listener
@@ -182,4 +201,6 @@ 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;
\ No newline at end of file
+pub use crate::botcore::modules::Module;
+pub use crate::botcore::bot_objects::Badge;
+
-- 
2.49.0