command obj
This commit is contained in:
parent
ef344402ab
commit
db93067903
9 changed files with 395 additions and 35 deletions
58
readme.md
58
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
|
||||
|
|
51
src/bin/bot_cmd_example.rs
Normal file
51
src/bin/bot_cmd_example.rs
Normal file
|
@ -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;
|
||||
|
||||
}
|
|
@ -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() {
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
|
||||
}
|
|
@ -1 +1,23 @@
|
|||
pub mod listener;
|
||||
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)))
|
||||
}
|
157
src/botcore/bot_objects/command.rs
Normal file
157
src/botcore/bot_objects/command.rs
Normal file
|
@ -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
|
||||
}
|
||||
}
|
|
@ -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)))
|
||||
}
|
56
src/lib.rs
56
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;
|
||||
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;
|
Loading…
Add table
Add a link
Reference in a new issue