Customizable Twitch chat bot written in rust

# Quick Start 

Run a Simple bot with Built in functionality 

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

```
cargo run -p forcebot_core 
```

# Features

## Built In Chat Commands

- `quiet on` / `quiet off` - Moderators & Broadcasters can quiet the bot

- `enable $module$` / `disable $module$` - Moderators & Broadcasters can enable or disable `Modules` of bot functionality through chat `Commands`


## Custom Modules can be coded to load additional functionality 

Developers an create Modules that add more bot functionality

The main `forcebot_core` Binary crate includes the following Custom `Modules`  
   
- `debug` - outputs to console messages from the channel where it was enabled. Toggle debug with the Commands `debug on` or `debug off`
- `guest_badge` - Temporary badges can be issued to chatters
- `besty` - Tomfoolery
- `pyramid` - for detecting & handling pyramids


## `forcebot_core` Bot Library

- `forcebot_core` library API provides Custom package developers a way to add functionality by adding `Modules` that contain Bot Objects like `Commands` and `Listeners`
- `Listeners` and `Commands` listen for a defined callback trigger condition and run an defined execution callback 
- `Commands` are similar to `Listeners` with refined trigger conditions including using bot `prefix` with the `Command` , triggers based on `Badge` , and more
- Workspace for package developers to independently code their own `Modules`

## Workspaces


Workspace comes with binary crates with working or example bots that use `forcebot_core` library 
- `moderator_reactor` - bot kneels to all moderator messages
- `simple_module_example` - bot has a `test` `Module` with a `test` `Command` . Moderators & Broadcasters can manage the `Module` in chat with `enable` / `disable` `Commands`
- `new_empty_bot` - while empty, has `disable` and `enable` chat `Commands` . This is an example of the bot without any loaded modules
- `simple_command_bot` - bot responds to a `test` `Command`. As the command was not loaded through a `Module`, `disable` & `enable` commands don't work on the `test` command. This could be a Global `Command`  
- `simple_debug_listener` - bot outputs all twitch `ServerMessages` received to terminal
    


# Example Bots

Use the following to build and run built-in bots. No coding required!

## New Empty Bot
Run an empty simple bot that logs into chat and has minimum built in functions 

```
cargo run -p new_empty_bot
```

## Full Featured Forcebot

Run a forcebot with fun catered customizations

```
cargo run -p forcebot_core 
```


## Simple Debug Listener
Run a bot that listens to all messages and output to console

```
cargo run -p simple_debug_listener
```

## Simple Command Bot
Run a bot that uses the `test` chat `Command` . `Commands` are prefixed and must be ran by a chatter with a `vip` badge or above 

```
cargo run -p simple_command_bot
```

## Moderator Reactor
Run a bot that listens for messages with the `moderator` badge, and replies to that mod with an emote

```
cargo run -p moderator_reactor
```

## Module loaded Bot
Run a bot that has a `test` chat `Command`. As the command was loaded through a module, moderators or broadcastors can `enable` or `disable` the module through chat commands

```
cargo run -p simple_module_example
```

# Workspace packages 

Source is a workspace of packages . In particular, `forcebot_core` is the main library crate to use

*TIP* : if you want to start customizing you own bot, create a binary package in the workspace for your bot's binary crate

More info about workspaces - https://doc.rust-lang.org/book/ch14-03-cargo-workspaces.html


## Creating a new package

To create a new package 

1. Create a new package

For example, to create a new binary crate in the workspace

```
cargo new my_new_bot
```

2. In the newly created directory for your package, adjust the `Cargo.toml` to the following 

```
[dependencies]
forcebot_core = {path = "../forcebot_core"}
dotenv = "0.15.0"
lazy_static = "1.5.0"
tokio = { version = "1.33.0", features = ["full"] }
twitch-irc = "5.0.1"
```

3. Copy `main.rs` from the `new_empty_bot` package into your package

4. Optionally, customize your `main()` to load modules before starting the bot

5. Build and run your package
```
cargo run -p my_new_bot
```


# Example Code

## New Bot

Uses Env defined variables to create and run the bot

```rust
use forcebot_core::Bot;

#[tokio::main]
pub async fn main() {

    /* 1. Create the bot using env */
    let bot = Bot::new().await;

    /* 2. Run the bot */
    bot.run().await;

}
```

## Customize by Loading Custom Modules 

A `Module` is a group of bot objects (eg `Command`) that elevated users can manage through built in `disable` and `enable` commands

Custom `Modules` can be loaded into a new bot with minimum coding : just load the modules and run the bot

```rust
use forcebot_core::{custom_mods::{debug, guest_badge, pyramid}, Bot};



#[tokio::main]
pub async fn main() {

    /* Create the bot using env */
    let bot = Bot::new().await;

    /* 1. Load the module into the bot */
    bot.load_module(funbot_objs::create_module()).await;

    /* 2. Load Custom Modules */
    bot.load_module(guest_badge::create_module()).await;
    bot.load_module(pyramid::create_module()).await;
    bot.load_module(debug::create_module()).await;
    
    
    /* 3. Run the bot */
    bot.run().await;

}
```


## Create your own Custom Modules

Create a custom `Module` by :

1. Defining Functions that create the Custom Bot Objects (eg `Command`)

2. Define a function that creates a `Module` with the Custom Bot Objects loaded


```rust

use forcebot_core::Bot;

#[tokio::main]
pub async fn main() {

    /* Create the bot using env */
    let bot = Bot::new().await;

    /* load the Module */
    bot.load_module(custom_mod::new()).await;

    /* Run the bot */
    bot.run().await;

}


pub mod custom_mod {
    use std::sync::Arc;

    use forcebot_core::{execution_async, Badge, Bot, Command, Module};
    use twitch_irc::message::ServerMessage;


    /// Module definition with a loaded command
    pub fn new() -> Module {
        /* 1. Create a new module */
        let mut custom_mod = Module::new(
            vec!["test".to_string()], 
            "".to_string());

        /* 2. Load the cmd into a new module */
        custom_mod.load_command(cmd_test());

        custom_mod

    }

    /// Command definition 
    pub fn cmd_test() -> Command {
        /* 1. Create a new cmd */
        let mut cmd = Command::new(vec!["test".to_string()],"".to_string());

        /* 2. Define exec callback  */
        async fn execbody(bot:Arc<Bot>,message:ServerMessage) -> Result<String,String> {
            if let ServerMessage::Privmsg(msg) = message {
                let _= bot.chat.lock().await.say_in_reply_to(
                    &msg, "test return".to_string()).await;
            }
            Result::Err("Not Valid message type".to_string()) 
        }

        /* 3. Set Command flags */
        cmd.set_exec_fn(execution_async(execbody));
        cmd.set_admin_only(false);
        cmd.set_min_badge(Badge::Vip);

        cmd
    }
}
```

## Simple Debug Listener
Bot with a simple listener that listens for all messages and prints in output

```rust

use std::sync::Arc;

use forcebot_core::{execution_async, Bot, Listener};
use twitch_irc::message::ServerMessage;

#[tokio::main]
pub async fn main() {

    /* 1. Create the bot using env */
    let bot = Bot::new().await;

    /* 2a. Create a new blank Listener */
    let mut listener = Listener::new();

    /* 2b. Set a trigger condition function for listener */
    listener.set_trigger_cond_fn(
        |_:Arc<Bot>,_:ServerMessage| true
    );

    /* 2c. Define an async fn callback execution */
    async fn execbody(_:Arc<Bot>,message:ServerMessage) -> Result<String,String> {
        dbg!(message); /* outputs message to debug */
        Result::Ok("Success".to_string()) 
    }

    /* 2d. Set and Store the execution body using `execution_async()`  */
    listener.set_exec_fn(execution_async(execbody));

    /* 3. Load the listener into the bot */
    bot.load_listener(listener).await;

    /* 4. Run the bot */
    bot.run().await;

}

```

## Moderator Reactor

Example listener listens for a moderator badge and reply in chat 

```rust

use std::sync::Arc;

use forcebot_core::Bot;
use forcebot_core::execution_async;
use forcebot_core::Listener;
use twitch_irc::message::ServerMessage;


#[tokio::main]
pub async fn main() {

    /* Create the bot using env */
    let bot = Bot::new().await;

    /* 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 {
                for badge in msg.badges {
                    if matches!(badge, x if x.name == "moderator") {
                        // dbg!("moderator found");
                        return true;
                    }
                } 
                false
            } else { false }
    );

    /* 3. Define an async fn callback execution */
    async fn execbody(bot:Arc<Bot>,message:ServerMessage) -> Result<String,String> {
        if let ServerMessage::Privmsg(msg) = message {
            let _ = bot.chat.lock().await.say_in_reply_to(&msg, "pepeKneel".to_string()).await ;
            return Result::Ok("Success".to_string()) ;
        }
        Result::Err("Not Valid message type".to_string()) 
    }

    /* 4. Set and Store the execution body using `execution_async()`  */
    listener.set_exec_fn(execution_async(execbody));

    /* 5. Load the listener into the bot */
    bot.load_listener(listener).await;

    /* Run the bot */
    bot.run().await;

}

```

## Simple Test Command

```rust
use std::sync::Arc;

use forcebot_core::Badge;
use forcebot_core::Bot;
use forcebot_core::execution_async;
use forcebot_core::Command;
use twitch_irc::message::ServerMessage;


#[tokio::main]
pub async fn main() {

    /* Create the bot using env */
    let bot = Bot::new().await;

    /* 1. Create a new blank cmd */
    let mut cmd = Command::new(vec!["test".to_string()],"".to_string());

    /* 2. Define an async fn callback execution */
    async fn execbody(bot:Arc<Bot>,message:ServerMessage) -> Result<String,String> {
        if let ServerMessage::Privmsg(msg) = message {
            let _ = bot.chat.lock().await.say_in_reply_to(&msg, String::from("test success")).await;
            return Result::Ok("Success".to_string()) ;
        }
        Result::Err("Not Valid message type".to_string()) 
    }

    /* 3. Set and Store the execution body using `execution_async()`  */
    cmd.set_exec_fn(execution_async(execbody));

    /* 4. optionally, remove admin only default flag */
    cmd.set_admin_only(false);

    /* 5. optionally, set min badge*/
    cmd.set_min_badge(Badge::Moderator);

    /* 6. Load the cmd into the bot */
    bot.load_command(cmd).await;

    /* Run the bot */
    bot.run().await;

}

```

# Crate Rust API Documentation 

Create `forcebot_rs_v2` Rust Crate documentation

Documentation - Clean Build & Open in Default Browser  
```
cargo clean && cargo doc --open
```