listener obj

This commit is contained in:
modulatingforce 2025-01-26 18:17:36 -05:00
parent 2532cc5a3e
commit ef344402ab
10 changed files with 390 additions and 17 deletions

View file

@ -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"
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"

View file

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

24
src/bin/simple_bot.rs Normal file
View file

@ -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;
}

View file

@ -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;
}

View file

@ -1 +1,2 @@
pub mod bot;
pub mod bot;
pub mod bot_objects;

View file

@ -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);
}
}

View file

@ -0,0 +1 @@
pub mod listener;

View file

@ -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)))
}

82
src/lib.rs Normal file
View file

@ -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;

View file

@ -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() {