WIP: Enhancements through Funbot #4
42 changed files with 3267 additions and 1174 deletions
.gitignoreCargo.lockCargo.toml
forcebot_core
Cargo.toml
src
bin
botcore.rsbotcore
custom_mods.rscustom_mods
lib.rsmoderator_reactor
new_empty_bot
readme.mdsimple_command_bot
simple_debug_listener
simple_module_example
src
3
.gitignore
vendored
3
.gitignore
vendored
|
@ -3,3 +3,6 @@
|
||||||
|
|
||||||
# env
|
# env
|
||||||
.env
|
.env
|
||||||
|
|
||||||
|
# temp
|
||||||
|
/tmp
|
127
Cargo.lock
generated
127
Cargo.lock
generated
|
@ -19,9 +19,9 @@ checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "async-trait"
|
name = "async-trait"
|
||||||
version = "0.1.85"
|
version = "0.1.86"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "3f934833b4b7233644e5848f235df3f57ed8c80f1528a26c3dfa13d2147fa056"
|
checksum = "644dd749086bf3771a2fbc5f256fdb982d53f011c7d5d560304eafeecebce79d"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
|
@ -63,9 +63,9 @@ checksum = "325918d6fe32f23b19878fe4b34794ae41fc19ddbe53b10571a4874d44ffd39b"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "cc"
|
name = "cc"
|
||||||
version = "1.2.10"
|
version = "1.2.11"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "13208fcbb66eaeffe09b99fffbe1af420f00a7b35aa99ad683dfc1aa76145229"
|
checksum = "e4730490333d58093109dc02c23174c3f4d490998c3fed3cc8e82d57afedb9cf"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"shlex",
|
"shlex",
|
||||||
]
|
]
|
||||||
|
@ -142,10 +142,11 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be"
|
checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "forcebot-rs-v2"
|
name = "forcebot_core"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"dotenv",
|
"dotenv",
|
||||||
|
"lazy_static",
|
||||||
"tokio",
|
"tokio",
|
||||||
"twitch-irc",
|
"twitch-irc",
|
||||||
]
|
]
|
||||||
|
@ -199,13 +200,14 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "getrandom"
|
name = "getrandom"
|
||||||
version = "0.2.15"
|
version = "0.3.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7"
|
checksum = "43a49c392881ce6d5c3b8cb70f98717b7c07aabbdff06687b9030dbfbe2725f8"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"cfg-if",
|
"cfg-if",
|
||||||
"libc",
|
"libc",
|
||||||
"wasi",
|
"wasi 0.13.3+wasi-0.2.2",
|
||||||
|
"windows-targets",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -214,6 +216,12 @@ version = "0.31.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f"
|
checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "lazy_static"
|
||||||
|
version = "1.5.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "libc"
|
name = "libc"
|
||||||
version = "0.2.169"
|
version = "0.2.169"
|
||||||
|
@ -264,15 +272,26 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "2886843bf800fba2e3377cff24abf6379b4c4d5c6681eaf9ea5b0d15090450bd"
|
checksum = "2886843bf800fba2e3377cff24abf6379b4c4d5c6681eaf9ea5b0d15090450bd"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"libc",
|
"libc",
|
||||||
"wasi",
|
"wasi 0.11.0+wasi-snapshot-preview1",
|
||||||
"windows-sys 0.52.0",
|
"windows-sys 0.52.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "moderator_reactor"
|
||||||
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"dotenv",
|
||||||
|
"forcebot_core",
|
||||||
|
"lazy_static",
|
||||||
|
"tokio",
|
||||||
|
"twitch-irc",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "native-tls"
|
name = "native-tls"
|
||||||
version = "0.2.12"
|
version = "0.2.13"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "a8614eb2c83d59d1c8cc974dd3f920198647674a0a035e1af1fa58707e317466"
|
checksum = "0dab59f8e050d5df8e4dd87d9206fb6f65a483e20ac9fda365ade4fab353196c"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"libc",
|
"libc",
|
||||||
"log",
|
"log",
|
||||||
|
@ -285,6 +304,17 @@ dependencies = [
|
||||||
"tempfile",
|
"tempfile",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "new_empty_bot"
|
||||||
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"dotenv",
|
||||||
|
"forcebot_core",
|
||||||
|
"lazy_static",
|
||||||
|
"tokio",
|
||||||
|
"twitch-irc",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "num-traits"
|
name = "num-traits"
|
||||||
version = "0.2.19"
|
version = "0.2.19"
|
||||||
|
@ -311,9 +341,9 @@ checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "openssl"
|
name = "openssl"
|
||||||
version = "0.10.68"
|
version = "0.10.69"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "6174bc48f102d208783c2c84bf931bb75927a617866870de8a4ea85597f871f5"
|
checksum = "f5e534d133a060a3c19daec1eb3e98ec6f4685978834f2dbadfe2ec215bab64e"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags",
|
"bitflags",
|
||||||
"cfg-if",
|
"cfg-if",
|
||||||
|
@ -337,9 +367,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "openssl-probe"
|
name = "openssl-probe"
|
||||||
version = "0.1.5"
|
version = "0.1.6"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf"
|
checksum = "d05e27ee213611ffe7d6348b942e8f942b37114c00cc03cec254295a4a17852e"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "openssl-sys"
|
name = "openssl-sys"
|
||||||
|
@ -429,9 +459,9 @@ checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rustix"
|
name = "rustix"
|
||||||
version = "0.38.43"
|
version = "0.38.44"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "a78891ee6bf2340288408954ac787aa063d8e8817e9f53abb37c695c6d834ef6"
|
checksum = "fdb5bc1ae2baa591800df16c9ca78619bf65c0488b41b96ccec5d11220d8c154"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags",
|
"bitflags",
|
||||||
"errno",
|
"errno",
|
||||||
|
@ -493,6 +523,39 @@ dependencies = [
|
||||||
"libc",
|
"libc",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "simple_command_bot"
|
||||||
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"dotenv",
|
||||||
|
"forcebot_core",
|
||||||
|
"lazy_static",
|
||||||
|
"tokio",
|
||||||
|
"twitch-irc",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "simple_debug_listener"
|
||||||
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"dotenv",
|
||||||
|
"forcebot_core",
|
||||||
|
"lazy_static",
|
||||||
|
"tokio",
|
||||||
|
"twitch-irc",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "simple_module_example"
|
||||||
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"dotenv",
|
||||||
|
"forcebot_core",
|
||||||
|
"lazy_static",
|
||||||
|
"tokio",
|
||||||
|
"twitch-irc",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "slab"
|
name = "slab"
|
||||||
version = "0.4.9"
|
version = "0.4.9"
|
||||||
|
@ -520,9 +583,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "syn"
|
name = "syn"
|
||||||
version = "2.0.96"
|
version = "2.0.98"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "d5d0adab1ae378d7f53bdebc67a39f1f151407ef230f0ce2883572f5d8985c80"
|
checksum = "36147f1a48ae0ec2b5b3bc5b537d267457555a10dc06f3dbc8cb11ba3006d3b1"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
|
@ -531,9 +594,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tempfile"
|
name = "tempfile"
|
||||||
version = "3.15.0"
|
version = "3.16.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "9a8a559c81686f576e8cd0290cd2a24a2a9ad80c98b3478856500fcbd7acd704"
|
checksum = "38c246215d7d24f48ae091a2902398798e05d978b24315d6efbc00ede9a8bb91"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"cfg-if",
|
"cfg-if",
|
||||||
"fastrand",
|
"fastrand",
|
||||||
|
@ -680,9 +743,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "unicode-ident"
|
name = "unicode-ident"
|
||||||
version = "1.0.14"
|
version = "1.0.16"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "adb9e6ca4f869e1180728b7950e35922a7fc6397f7b641499e8f3ef06e50dc83"
|
checksum = "a210d160f08b701c8721ba1c726c11662f877ea6b7094007e1ca9a1041945034"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "vcpkg"
|
name = "vcpkg"
|
||||||
|
@ -696,6 +759,15 @@ version = "0.11.0+wasi-snapshot-preview1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
|
checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "wasi"
|
||||||
|
version = "0.13.3+wasi-0.2.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "26816d2e1a4a36a2940b96c5296ce403917633dff8f3440e9b236ed6f6bacad2"
|
||||||
|
dependencies = [
|
||||||
|
"wit-bindgen-rt",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "windows-sys"
|
name = "windows-sys"
|
||||||
version = "0.52.0"
|
version = "0.52.0"
|
||||||
|
@ -777,3 +849,12 @@ name = "windows_x86_64_msvc"
|
||||||
version = "0.52.6"
|
version = "0.52.6"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
|
checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "wit-bindgen-rt"
|
||||||
|
version = "0.33.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "3268f3d866458b787f390cf61f4bbb563b922d091359f9608842999eaee3943c"
|
||||||
|
dependencies = [
|
||||||
|
"bitflags",
|
||||||
|
]
|
||||||
|
|
29
Cargo.toml
29
Cargo.toml
|
@ -1,22 +1,7 @@
|
||||||
[package]
|
[workspace]
|
||||||
name = "forcebot-rs-v2"
|
members = [
|
||||||
version = "0.1.0"
|
"forcebot_core",
|
||||||
edition = "2021"
|
"simple_module_example",
|
||||||
default-run = "forcebot-rs-v2"
|
"new_empty_bot",
|
||||||
|
"simple_debug_listener",
|
||||||
[dependencies]
|
"moderator_reactor", "simple_command_bot"]
|
||||||
dotenv = "0.15.0"
|
|
||||||
tokio = { version = "1.33.0", features = ["full"] }
|
|
||||||
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"
|
|
||||||
|
|
12
forcebot_core/Cargo.toml
Normal file
12
forcebot_core/Cargo.toml
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
[package]
|
||||||
|
name = "forcebot_core"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2021"
|
||||||
|
default-run = "fun_bot"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
# async-recursion = "1.1.1" /* has issues when used */
|
||||||
|
dotenv = "0.15.0"
|
||||||
|
lazy_static = "1.5.0"
|
||||||
|
tokio = { version = "1.33.0", features = ["full"] }
|
||||||
|
twitch-irc = "5.0.1"
|
87
forcebot_core/src/bin/fun_bot.rs
Normal file
87
forcebot_core/src/bin/fun_bot.rs
Normal file
|
@ -0,0 +1,87 @@
|
||||||
|
//! WIP Fun forcebot with catered customizations #todo
|
||||||
|
//!
|
||||||
|
//! Custom modules that can be managed in chat through `disable` and `enable` commands
|
||||||
|
//! - `besty` - uses a custom prefix tp trigger
|
||||||
|
//! - `guests`
|
||||||
|
//! - `pyramid`
|
||||||
|
//! - `quiet`
|
||||||
|
//!
|
||||||
|
//!
|
||||||
|
//! 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_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;
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub mod funbot_objs {
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
use forcebot_core::{execution_async, Badge, 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(
|
||||||
|
vec!["besty".to_string()],
|
||||||
|
"Now Aware of besty xdd666 ".to_string(),
|
||||||
|
);
|
||||||
|
|
||||||
|
custom_mod.load_command(create_cmd_test());
|
||||||
|
// custom_mod.set_status_by_default(Status::Disabled);
|
||||||
|
|
||||||
|
custom_mod
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Create a Command Object
|
||||||
|
fn create_cmd_test() -> Command {
|
||||||
|
let mut cmd = Command::new(
|
||||||
|
vec!["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
|
||||||
|
.chat
|
||||||
|
.lock()
|
||||||
|
.await
|
||||||
|
.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(execution_async(execbody));
|
||||||
|
|
||||||
|
cmd.set_admin_only(false);
|
||||||
|
cmd.set_min_badge(Badge::Vip);
|
||||||
|
|
||||||
|
cmd
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,24 +1,22 @@
|
||||||
//! Example simple Binary crate that creates & runs bot based on `.env`
|
//! Example simple Binary crate that creates & runs bot based on `.env`
|
||||||
//! Be sure the followig is defined in `.env`
|
//! Be sure the followig is defined in `.env`
|
||||||
//! - login_name
|
//! - login_name
|
||||||
//! - access_token
|
//! - access_token
|
||||||
//! - bot_channels
|
//! - bot_channels
|
||||||
//! - prefix
|
//! - prefix
|
||||||
//! - bot_admins
|
//! - bot_admins
|
||||||
//!
|
//!
|
||||||
//! Bot access tokens be generated here -
|
//! Bot access tokens be generated here -
|
||||||
//! - Get a Bot Chat Token here - <https://twitchtokengenerator.com>
|
//! - Get a Bot Chat Token here - <https://twitchtokengenerator.com>
|
||||||
//! - More Info - <https://dev.twitch.tv/docs/authentication>
|
//! - More Info - <https://dev.twitch.tv/docs/authentication>
|
||||||
|
|
||||||
use forcebot_rs_v2::Bot;
|
use forcebot_core::Bot;
|
||||||
|
|
||||||
#[tokio::main]
|
#[tokio::main]
|
||||||
pub async fn main() {
|
pub async fn main() {
|
||||||
|
|
||||||
/* 1. Create the bot using env */
|
/* 1. Create the bot using env */
|
||||||
let bot = Bot::new();
|
let bot = Bot::new().await;
|
||||||
|
|
||||||
/* 2. Run the bot */
|
/* 2. Run the bot */
|
||||||
bot.run().await;
|
bot.run().await;
|
||||||
|
|
||||||
}
|
}
|
|
@ -1,76 +1,76 @@
|
||||||
//! Simple Module with a Command
|
//! Simple Module with a Command
|
||||||
//!
|
//!
|
||||||
//! Adding objects through packages provides controls ,
|
//! Adding objects through packages provides controls ,
|
||||||
//! such as moderators, and brodcasters can disable or enable mods
|
//! such as moderators, and brodcasters can disable or enable mods
|
||||||
//!
|
//!
|
||||||
//! Here, moderators or above can enable or disable the `test`
|
//! Here, moderators or above can enable or disable the `test`
|
||||||
//! module with the command `<prefix> disable test`
|
//! module with the command `<prefix> disable test`
|
||||||
//!
|
//!
|
||||||
//! Be sure the followig is defined in `.env`
|
//! Be sure the followig is defined in `.env`
|
||||||
//! - login_name
|
//! - login_name
|
||||||
//! - access_token
|
//! - access_token
|
||||||
//! - bot_channels
|
//! - bot_channels
|
||||||
//! - prefix
|
//! - prefix
|
||||||
//! - bot_admins
|
//! - bot_admins
|
||||||
//!
|
//!
|
||||||
//! Bot access tokens be generated here -
|
//! Bot access tokens be generated here -
|
||||||
//! - Get a Bot Chat Token here - <https://twitchtokengenerator.com>
|
//! - Get a Bot Chat Token here - <https://twitchtokengenerator.com>
|
||||||
//! - More Info - <https://dev.twitch.tv/docs/authentication>
|
//! - More Info - <https://dev.twitch.tv/docs/authentication>
|
||||||
|
|
||||||
use forcebot_rs_v2::Bot;
|
use forcebot_core::Bot;
|
||||||
|
|
||||||
#[tokio::main]
|
#[tokio::main]
|
||||||
pub async fn main() {
|
pub async fn main() {
|
||||||
|
|
||||||
/* Create the bot using env */
|
/* Create the bot using env */
|
||||||
let mut bot = Bot::new();
|
let bot = Bot::new().await;
|
||||||
|
|
||||||
/* load the Module */
|
/* load the Module */
|
||||||
bot.load_module(custom_mod::new());
|
bot.load_module(custom_mod::new()).await;
|
||||||
|
|
||||||
/* Run the bot */
|
/* Run the bot */
|
||||||
bot.run().await;
|
bot.run().await;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
pub mod custom_mod {
|
pub mod custom_mod {
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
use forcebot_rs_v2::{asyncfn_box, Badge, Bot, Command, Module};
|
use forcebot_core::{execution_async, Badge, Bot, Command, Module};
|
||||||
use twitch_irc::message::ServerMessage;
|
use twitch_irc::message::ServerMessage;
|
||||||
|
|
||||||
|
/// Module definition with a loaded command
|
||||||
/// Module with a loaded command
|
|
||||||
pub fn new() -> Module {
|
pub fn new() -> Module {
|
||||||
/* 1. Create a new module */
|
/* 1. Create a new module */
|
||||||
let mut custom_mod = Module::new("test".to_string(), "".to_string());
|
let mut custom_mod = Module::new(vec!["test".to_string()], "".to_string());
|
||||||
|
|
||||||
/* 2. Load the cmd into a new module */
|
/* 2. Load the cmd into a new module */
|
||||||
custom_mod.load_command(cmd_test());
|
custom_mod.load_command(cmd_test());
|
||||||
|
|
||||||
custom_mod
|
custom_mod
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Command definition
|
||||||
pub fn cmd_test() -> Command {
|
pub fn cmd_test() -> Command {
|
||||||
/* 1. Create a new cmd */
|
/* 1. Create a new cmd */
|
||||||
let mut cmd = Command::new("test".to_string(),"".to_string());
|
let mut cmd = Command::new(vec!["test".to_string()], "".to_string());
|
||||||
|
|
||||||
/* 2. Define exec callback */
|
/* 2. Define exec callback */
|
||||||
async fn execbody(bot:Arc<Bot>,message:ServerMessage) -> Result<String,String> {
|
async fn execbody(bot: Arc<Bot>, message: ServerMessage) -> Result<String, String> {
|
||||||
if let ServerMessage::Privmsg(msg) = message {
|
if let ServerMessage::Privmsg(msg) = message {
|
||||||
let _= bot.client.say_in_reply_to(
|
let _ = bot
|
||||||
&msg, "test return".to_string()).await;
|
.chat
|
||||||
|
.lock()
|
||||||
|
.await
|
||||||
|
.say_in_reply_to(&msg, "test return".to_string())
|
||||||
|
.await;
|
||||||
}
|
}
|
||||||
Result::Err("Not Valid message type".to_string())
|
Result::Err("Not Valid message type".to_string())
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 3. Set Command flags */
|
/* 3. Set Command flags */
|
||||||
cmd.set_exec_fn(asyncfn_box(execbody));
|
cmd.set_exec_fn(execution_async(execbody));
|
||||||
cmd.set_admin_only(false);
|
cmd.set_admin_only(false);
|
||||||
cmd.set_min_badge(Badge::Moderator);
|
cmd.set_min_badge(Badge::Moderator);
|
||||||
|
|
||||||
cmd
|
cmd
|
||||||
}
|
}
|
||||||
}
|
}
|
5
forcebot_core/src/botcore.rs
Normal file
5
forcebot_core/src/botcore.rs
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
pub mod bot;
|
||||||
|
pub mod bot_objects;
|
||||||
|
pub mod built_in_mods;
|
||||||
|
pub mod chat;
|
||||||
|
pub mod modules;
|
482
forcebot_core/src/botcore/bot.rs
Normal file
482
forcebot_core/src/botcore/bot.rs
Normal file
|
@ -0,0 +1,482 @@
|
||||||
|
// use async_recursion::async_recursion;
|
||||||
|
use dotenv::dotenv;
|
||||||
|
use std::{
|
||||||
|
env,
|
||||||
|
sync::{Arc, RwLock},
|
||||||
|
time::{Duration, Instant},
|
||||||
|
};
|
||||||
|
use tokio::sync::{mpsc::UnboundedReceiver, Mutex};
|
||||||
|
use twitch_irc::{
|
||||||
|
login::StaticLoginCredentials,
|
||||||
|
message::{PrivmsgMessage, ServerMessage},
|
||||||
|
SecureTCPTransport, TwitchIRCClient,
|
||||||
|
};
|
||||||
|
|
||||||
|
// use crate::{Badge, Command, Listener, Module};
|
||||||
|
use super::{bot_objects::command::Command, built_in_mods, chat::Chat};
|
||||||
|
|
||||||
|
use super::super::botcore::modules::Module;
|
||||||
|
use crate::botcore::bot_objects::listener::Listener;
|
||||||
|
use crate::botcore::{bot_objects::Badge, chat};
|
||||||
|
// use super::
|
||||||
|
|
||||||
|
use super::{
|
||||||
|
bot_objects::built_in_objects,
|
||||||
|
modules::{self, Status},
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Twitch chat bot
|
||||||
|
pub struct Bot {
|
||||||
|
/// Prefix for commands
|
||||||
|
prefix: String,
|
||||||
|
/// inbound chat msg stream
|
||||||
|
incoming_msgs: Mutex<UnboundedReceiver<ServerMessage>>,
|
||||||
|
/// outbound chat client msg stream
|
||||||
|
pub client: TwitchIRCClient<SecureTCPTransport, StaticLoginCredentials>,
|
||||||
|
/// *preferred* bot enforced outbound chat client msg stream
|
||||||
|
pub chat: Mutex<Chat>,
|
||||||
|
/// joined channels
|
||||||
|
botchannels: Vec<String>,
|
||||||
|
/// admin chatters
|
||||||
|
admins: Vec<String>,
|
||||||
|
/// listeners
|
||||||
|
listeners: Mutex<Vec<Listener>>,
|
||||||
|
/// commands
|
||||||
|
commands: Mutex<Vec<Command>>,
|
||||||
|
/// modules
|
||||||
|
modules: RwLock<Vec<Module>>,
|
||||||
|
/// channel module status
|
||||||
|
channel_module_status: RwLock<Vec<(String, String, modules::Status)>>,
|
||||||
|
/// chatter guest badges - chatter,channel,Badge,start_time,duration
|
||||||
|
chatter_guest_badges: Mutex<Vec<(String, String, Badge, Instant, Duration)>>,
|
||||||
|
/// Message cache
|
||||||
|
message_cache: Mutex<Vec<PrivmsgMessage>>,
|
||||||
|
// /// channel_quiet
|
||||||
|
// channel_quiet_yn: RwLock<Vec<(String,RwLock<bool>)>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
// #[async_recursion]
|
||||||
|
pub async fn new() -> Arc<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();
|
||||||
|
|
||||||
|
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).await
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Creates a new `Bot` using bot information
|
||||||
|
///
|
||||||
|
/// Bot will join `botchannels` argument
|
||||||
|
pub async fn new_from(
|
||||||
|
bot_login_name: String,
|
||||||
|
oauth_token: String,
|
||||||
|
prefix: String,
|
||||||
|
botchannels: Vec<String>,
|
||||||
|
) -> Arc<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);
|
||||||
|
|
||||||
|
let mut admins = Vec::new();
|
||||||
|
|
||||||
|
if let Ok(value) = env::var("bot_admins") {
|
||||||
|
for admin in value.split(',') {
|
||||||
|
admins.push(String::from(admin))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let bot = Bot {
|
||||||
|
prefix,
|
||||||
|
incoming_msgs: Mutex::new(incoming_messages),
|
||||||
|
client: client.clone(),
|
||||||
|
chat: Mutex::new(Chat::new(client).await),
|
||||||
|
botchannels: botchannels_all,
|
||||||
|
listeners: Mutex::new(vec![]),
|
||||||
|
commands: Mutex::new(vec![]),
|
||||||
|
admins,
|
||||||
|
modules: RwLock::new(vec![]),
|
||||||
|
channel_module_status: RwLock::new(vec![]),
|
||||||
|
chatter_guest_badges: Mutex::new(vec![]),
|
||||||
|
message_cache: Mutex::new(vec![]),
|
||||||
|
// channel_quiet_yn : RwLock::new(vec![]),
|
||||||
|
};
|
||||||
|
|
||||||
|
async fn load_modules(bot: Bot) -> Bot {
|
||||||
|
// let mut bot1 = bot;
|
||||||
|
|
||||||
|
// bot1.chat = Some(Chat::new(client, bot1));
|
||||||
|
|
||||||
|
for cmd in built_in_objects::create_commands() {
|
||||||
|
bot.load_command(cmd).await;
|
||||||
|
}
|
||||||
|
built_in_mods::load_built_in_mods(&bot).await;
|
||||||
|
|
||||||
|
bot
|
||||||
|
}
|
||||||
|
|
||||||
|
let bot = load_modules(bot).await;
|
||||||
|
|
||||||
|
let bot = Arc::new(bot);
|
||||||
|
|
||||||
|
// let lock = bot.chat.lock().await;
|
||||||
|
|
||||||
|
// *lock = Some(Chat::new(chat, bot.clone()));
|
||||||
|
|
||||||
|
// let cht = Chat::new(chat).await;
|
||||||
|
|
||||||
|
// bot.chat.lock()
|
||||||
|
|
||||||
|
// lock.set_parent_bot(bot.clone());
|
||||||
|
|
||||||
|
println!("Joined - {:?}", bot.botchannels);
|
||||||
|
|
||||||
|
bot.clone()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Runs the bot
|
||||||
|
pub async fn run(self: Arc<Self>) {
|
||||||
|
for chnl in &self.botchannels {
|
||||||
|
self.client.join(chnl.to_owned()).unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
// let bot = Arc::new(self);
|
||||||
|
let bot = self;
|
||||||
|
|
||||||
|
let join_handle = tokio::spawn(async move {
|
||||||
|
let a = bot.clone();
|
||||||
|
let mut in_msgs_lock = a.incoming_msgs.lock().await;
|
||||||
|
|
||||||
|
while let Some(message) = in_msgs_lock.recv().await {
|
||||||
|
// dbg!(message.clone()) ;
|
||||||
|
|
||||||
|
let bot_listener_lock = bot.listeners.lock().await;
|
||||||
|
for listener in bot_listener_lock.iter() {
|
||||||
|
let a = listener.clone();
|
||||||
|
if a.cond_triggered(bot.clone(), message.clone()).await {
|
||||||
|
let _ = listener.execute_fn(bot.clone(), message.clone()).await;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if let ServerMessage::Privmsg(msg) = message.clone() {
|
||||||
|
// let mut cache_lock = bot.message_cache.lock().await;
|
||||||
|
let mut cache_lock = bot.message_cache.lock().await;
|
||||||
|
cache_lock.push(msg.clone());
|
||||||
|
// dbg!(cache_lock.clone());
|
||||||
|
drop(cache_lock);
|
||||||
|
|
||||||
|
let cmd_lock = bot.commands.lock().await;
|
||||||
|
for cmd in cmd_lock.iter() {
|
||||||
|
let a = cmd.clone();
|
||||||
|
if a.command_triggered(bot.clone(), msg.clone()).await {
|
||||||
|
let _ = cmd.execute_fn(bot.clone(), message.clone()).await;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_enabled_channel_modules(bot: Arc<Bot>, channel: String) -> Vec<Module> {
|
||||||
|
let botmodules_lock = bot.modules.read().unwrap();
|
||||||
|
let botmodules_cpy = botmodules_lock.clone();
|
||||||
|
drop(botmodules_lock);
|
||||||
|
|
||||||
|
let mut enabled_mods = Vec::new();
|
||||||
|
|
||||||
|
'module_loop: for module in &*botmodules_cpy {
|
||||||
|
// dbg!("try cms read");
|
||||||
|
let cms_lock = bot.channel_module_status.read().unwrap();
|
||||||
|
|
||||||
|
for channel_flags in cms_lock.iter() {
|
||||||
|
if channel_flags.0 == channel {
|
||||||
|
if module.get_names().contains(&channel_flags.1)
|
||||||
|
&& channel_flags.2 == Status::Disabled
|
||||||
|
{
|
||||||
|
continue 'module_loop;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
enabled_mods.push(module.clone());
|
||||||
|
}
|
||||||
|
|
||||||
|
enabled_mods
|
||||||
|
}
|
||||||
|
|
||||||
|
for module in
|
||||||
|
get_enabled_channel_modules(bot.clone(), msg.clone().channel_login)
|
||||||
|
{
|
||||||
|
for listener in module.get_listeners() {
|
||||||
|
let a = listener.clone();
|
||||||
|
if a.cond_triggered(bot.clone(), message.clone()).await {
|
||||||
|
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()).await {
|
||||||
|
let _ = cmd.execute_fn(bot.clone(), message.clone()).await;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
};
|
||||||
|
}
|
||||||
|
drop(in_msgs_lock);
|
||||||
|
});
|
||||||
|
|
||||||
|
join_handle.await.unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Loads a `Listener` into the bot
|
||||||
|
pub async fn load_listener(&self, l: Listener) {
|
||||||
|
let a = Arc::new(self);
|
||||||
|
let mut listlock = a.listeners.lock().await;
|
||||||
|
listlock.push(l);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Loads a `Command` into the bot
|
||||||
|
pub async fn load_command(&self, c: Command) {
|
||||||
|
let a = Arc::new(self);
|
||||||
|
let mut cmdlock = a.commands.lock().await;
|
||||||
|
cmdlock.push(c);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn get_module(&self, module: String) -> Option<Module> {
|
||||||
|
let modlock = self.modules.read().unwrap();
|
||||||
|
for modl in modlock.iter() {
|
||||||
|
if modl.get_names().contains(&module) {
|
||||||
|
return Some(modl.clone());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_prefix(&self) -> String {
|
||||||
|
self.prefix.clone()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_admins(&self) -> Vec<String> {
|
||||||
|
self.admins.clone()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// loads a `Module` and its bot objects
|
||||||
|
pub async fn load_module(&self, m: Module) {
|
||||||
|
// dbg!("load module - start",m.get_names().first().unwrap());
|
||||||
|
let bot = Arc::new(self);
|
||||||
|
// let bot_lock = bot.lock().await;
|
||||||
|
// dbg!("bot arc");
|
||||||
|
if m.get_status_by_default() == Status::Disabled {
|
||||||
|
// dbg!("module fund disabled by default");
|
||||||
|
// dbg!("inner if");
|
||||||
|
for (_index, chnl) in bot.botchannels.iter().enumerate() {
|
||||||
|
// dbg!("iter - ",index);
|
||||||
|
bot.disable_module(chnl.clone(), m.get_names().first().unwrap().clone())
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// dbg!("aftee disable check");
|
||||||
|
// dbg!(bot.modules);
|
||||||
|
let mut botmods = bot.modules.write().unwrap();
|
||||||
|
// dbg!(m);
|
||||||
|
// dbg!("loading module ",m.get_names());
|
||||||
|
botmods.push(m);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn get_channel_module_status(&self, channel: String, module: String) -> Status {
|
||||||
|
// dbg!("get channel module status");
|
||||||
|
let found_disabled: bool = {
|
||||||
|
// dbg!("try cms read");
|
||||||
|
let cms_lock = self.channel_module_status.read().unwrap();
|
||||||
|
// dbg!("cms read lock");
|
||||||
|
// dbg!(module.clone());
|
||||||
|
let mut found = false;
|
||||||
|
|
||||||
|
for channel_flags in cms_lock.iter() {
|
||||||
|
if channel_flags.0 == channel {
|
||||||
|
if channel_flags.1 == module && channel_flags.2 == Status::Disabled {
|
||||||
|
found = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
found
|
||||||
|
};
|
||||||
|
|
||||||
|
let module_loaded: bool = {
|
||||||
|
let mut loaded_yn = false;
|
||||||
|
|
||||||
|
for loaded_m in self.modules.read().unwrap().iter() {
|
||||||
|
if loaded_m.get_names().contains(&module) {
|
||||||
|
loaded_yn = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
loaded_yn
|
||||||
|
};
|
||||||
|
|
||||||
|
if found_disabled {
|
||||||
|
return Status::Disabled;
|
||||||
|
} else if !module_loaded {
|
||||||
|
return Status::NotLoaded;
|
||||||
|
} else {
|
||||||
|
return Status::Enabled;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn disable_module(&self, channel: String, module: String) {
|
||||||
|
// dbg!("disable module called",channel.clone(),module.clone());
|
||||||
|
|
||||||
|
let found_disabled: bool = {
|
||||||
|
// dbg!("finding disabled mod");
|
||||||
|
// dbg!("try cms read");
|
||||||
|
let cms_lock = self.channel_module_status.read().unwrap();
|
||||||
|
// dbg!("cms read lock");
|
||||||
|
// dbg!(module.clone());
|
||||||
|
|
||||||
|
let mut found = false;
|
||||||
|
|
||||||
|
for channel_flags in cms_lock.iter() {
|
||||||
|
if channel_flags.0 == channel {
|
||||||
|
if channel_flags.1 == module && channel_flags.2 == Status::Disabled {
|
||||||
|
found = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
drop(cms_lock);
|
||||||
|
found
|
||||||
|
};
|
||||||
|
|
||||||
|
if !found_disabled {
|
||||||
|
let mut cms_lock = self.channel_module_status.write().unwrap();
|
||||||
|
|
||||||
|
cms_lock.push((channel, module.clone(), Status::Disabled));
|
||||||
|
|
||||||
|
drop(cms_lock);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn enable_module(&self, channel: String, module: String) {
|
||||||
|
// dbg!("enable module called",channel.clone(),module.clone());
|
||||||
|
// dbg!("try cms write");
|
||||||
|
let mut lock = self.channel_module_status.write().unwrap();
|
||||||
|
// dbg!("cms write lock");
|
||||||
|
// dbg!(module.clone());
|
||||||
|
while lock.contains(&(channel.clone(), module.clone(), Status::Disabled)) {
|
||||||
|
let index = lock
|
||||||
|
.iter()
|
||||||
|
.position(|x| *x == (channel.clone(), module.clone(), Status::Disabled))
|
||||||
|
.unwrap();
|
||||||
|
lock.remove(index);
|
||||||
|
}
|
||||||
|
drop(lock);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn get_channel_guest_badges(
|
||||||
|
&self,
|
||||||
|
chatter: String,
|
||||||
|
channel: String,
|
||||||
|
) -> Vec<(Badge, Instant, Duration)> {
|
||||||
|
let bot = Arc::new(self);
|
||||||
|
let guest_badges_lock = bot.chatter_guest_badges.lock().await;
|
||||||
|
|
||||||
|
let mut badges = vec![];
|
||||||
|
for temp_badge in guest_badges_lock.iter() {
|
||||||
|
if temp_badge.0 == chatter
|
||||||
|
&& temp_badge.1 == channel
|
||||||
|
&& temp_badge.3 + temp_badge.4 > Instant::now()
|
||||||
|
{
|
||||||
|
badges.push((temp_badge.2.clone(), temp_badge.3, temp_badge.4));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
badges
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn issue_new_guest_badge(
|
||||||
|
&self,
|
||||||
|
chatter: String,
|
||||||
|
channel: String,
|
||||||
|
badge: Badge,
|
||||||
|
start: Instant,
|
||||||
|
dur: Duration,
|
||||||
|
) {
|
||||||
|
let bot = Arc::new(self);
|
||||||
|
let mut guest_badges_lock = bot.chatter_guest_badges.lock().await;
|
||||||
|
|
||||||
|
guest_badges_lock.push((chatter, channel, badge, start, dur));
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_message_cache(&self) -> &Mutex<Vec<PrivmsgMessage>> {
|
||||||
|
&self.message_cache
|
||||||
|
}
|
||||||
|
|
||||||
|
/// get message cache newest to oldest for a channel
|
||||||
|
pub async fn get_message_cache_per_channel(&self, channel: String) -> Vec<PrivmsgMessage> {
|
||||||
|
let cache = self.message_cache.lock().await;
|
||||||
|
let mut rslt = vec![];
|
||||||
|
for a in cache
|
||||||
|
.iter()
|
||||||
|
.rev()
|
||||||
|
.filter(|x| x.channel_login == channel)
|
||||||
|
.into_iter()
|
||||||
|
{
|
||||||
|
rslt.push(a.clone());
|
||||||
|
}
|
||||||
|
rslt
|
||||||
|
}
|
||||||
|
|
||||||
|
// /// Get the quiet status of a channel
|
||||||
|
// pub fn get_channel_quiet(&self,channel:String) -> bool {
|
||||||
|
// for a in self.channel_quiet_yn.read().unwrap().iter() {
|
||||||
|
// if a.0 == channel {
|
||||||
|
// return a.1.read().unwrap().clone();
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// return false;
|
||||||
|
// }
|
||||||
|
|
||||||
|
// /// Get the quiet status of a channel
|
||||||
|
// pub fn set_channel_quiet(&self,channel:String,quiet_on:bool) {
|
||||||
|
// let mut found = false;
|
||||||
|
|
||||||
|
// let chnlquiet = self.channel_quiet_yn.read().unwrap();
|
||||||
|
// for rec in chnlquiet.iter() {
|
||||||
|
// if rec.0 == channel {
|
||||||
|
// found = true;
|
||||||
|
// let mut status = rec.1.write().unwrap();
|
||||||
|
// *status = quiet_on;
|
||||||
|
// drop(status);
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// drop(chnlquiet);
|
||||||
|
|
||||||
|
// if !found {
|
||||||
|
// // dbg!("set chn quiet > !found channel quiet status");
|
||||||
|
// let mut chnlquiet = self.channel_quiet_yn.write().unwrap();
|
||||||
|
// chnlquiet.push((channel,RwLock::new(quiet_on)));
|
||||||
|
// drop(chnlquiet);
|
||||||
|
// }
|
||||||
|
|
||||||
|
// }
|
||||||
|
}
|
399
forcebot_core/src/botcore/bot_objects.rs
Normal file
399
forcebot_core/src/botcore/bot_objects.rs
Normal file
|
@ -0,0 +1,399 @@
|
||||||
|
pub mod command;
|
||||||
|
pub mod listener;
|
||||||
|
|
||||||
|
use std::boxed::Box;
|
||||||
|
use std::future::Future;
|
||||||
|
use std::pin::Pin;
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
use twitch_irc::message::{PrivmsgMessage, ServerMessage};
|
||||||
|
|
||||||
|
use super::bot::Bot;
|
||||||
|
|
||||||
|
/// chat badge
|
||||||
|
#[derive(Clone, PartialEq, Eq, Debug)]
|
||||||
|
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,
|
||||||
|
>;
|
||||||
|
|
||||||
|
/// used to store async execution functions. Primarily used for `Command`
|
||||||
|
///
|
||||||
|
/// call this to store execution functions in `Commands`
|
||||||
|
///
|
||||||
|
/// # example
|
||||||
|
/// ```
|
||||||
|
/// /* 2. Define exec callback */
|
||||||
|
/// async fn execbody(bot:Arc<Bot>,message:ServerMessage) -> Result<String,String> {
|
||||||
|
/// /* do smth */
|
||||||
|
/// }
|
||||||
|
///
|
||||||
|
/// /* 3. Set Command flags */
|
||||||
|
/// cmd.set_exec_fn(execution_async(execbody));
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
pub fn execution_async<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)))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub type CommandTrigger = Box<
|
||||||
|
dyn Fn(Arc<Bot>, PrivmsgMessage) -> Pin<Box<dyn Future<Output = bool> + Send>> + Send + Sync,
|
||||||
|
>;
|
||||||
|
|
||||||
|
/// used to store async trigger condition callback functions. Primarily used for `Command`
|
||||||
|
///
|
||||||
|
/// # example
|
||||||
|
/// ```
|
||||||
|
/// /* 2. Define condition callback */
|
||||||
|
/// async fn condition01(bot:Arc<Bot>,message:ServerMessage) -> bool {
|
||||||
|
/// /* do smth */
|
||||||
|
/// }
|
||||||
|
///
|
||||||
|
/// /* 3. Set Command flags */
|
||||||
|
/// cmd.set_custom_cond_async(command_condition_async(condition01));
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
pub fn command_condition_async<T>(f: fn(Arc<Bot>, PrivmsgMessage) -> T) -> CommandTrigger
|
||||||
|
where
|
||||||
|
T: Future<Output = bool> + Send + 'static,
|
||||||
|
{
|
||||||
|
Box::new(move |a, b| Box::pin(f(a, b)))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub type ListenerTrigger = Box<
|
||||||
|
dyn Fn(Arc<Bot>, ServerMessage) -> Pin<Box<dyn Future<Output = bool> + Send>> + Send + Sync,
|
||||||
|
>;
|
||||||
|
|
||||||
|
/// used to store async trigger condition callback functions. Primarily used for `Listener`
|
||||||
|
///
|
||||||
|
/// # example
|
||||||
|
/// ```
|
||||||
|
/// /* 2. Define condition callback */
|
||||||
|
/// async fn condition01(bot:Arc<Bot>,message:ServerMessage) -> bool {
|
||||||
|
/// /* do smth */
|
||||||
|
/// }
|
||||||
|
///
|
||||||
|
/// /* 3. Set Command flags */
|
||||||
|
/// cmd.set_custom_cond_async(listener_condition_async(condition01));
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
pub fn listener_condition_async<T>(f: fn(Arc<Bot>, ServerMessage) -> T) -> ListenerTrigger
|
||||||
|
where
|
||||||
|
T: Future<Output = bool> + 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 {
|
||||||
|
const TEMP_BADGE_DUR_MIN: u64 = 30;
|
||||||
|
|
||||||
|
use std::{
|
||||||
|
sync::Arc,
|
||||||
|
time::{Duration, Instant},
|
||||||
|
};
|
||||||
|
|
||||||
|
use twitch_irc::message::ServerMessage;
|
||||||
|
|
||||||
|
use super::{super::modules::Status, command::Command, execution_async, Badge, Bot};
|
||||||
|
|
||||||
|
/// 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.push(create_iam_role_cmd());
|
||||||
|
|
||||||
|
cmds
|
||||||
|
}
|
||||||
|
|
||||||
|
fn create_disable_cmd() -> Command {
|
||||||
|
/* 1. Create a new blank cmd */
|
||||||
|
let mut cmd = Command::new(vec!["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 {
|
||||||
|
let mut action_taken = false;
|
||||||
|
for (i, arg) in msg
|
||||||
|
.message_text
|
||||||
|
.replace("\u{e0000}", "")
|
||||||
|
.trim()
|
||||||
|
.split(" ")
|
||||||
|
.enumerate()
|
||||||
|
{
|
||||||
|
if i > 1 {
|
||||||
|
if bot
|
||||||
|
.get_channel_module_status(msg.channel_login.clone(), arg.to_string())
|
||||||
|
.await
|
||||||
|
== Status::Enabled
|
||||||
|
{
|
||||||
|
action_taken = true;
|
||||||
|
bot.disable_module(msg.channel_login.clone(), arg.to_string())
|
||||||
|
.await;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if action_taken {
|
||||||
|
let _ = bot
|
||||||
|
.chat
|
||||||
|
.lock()
|
||||||
|
.await
|
||||||
|
.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 `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 /* ::Moderator */);
|
||||||
|
cmd
|
||||||
|
}
|
||||||
|
|
||||||
|
fn create_enable_cmd() -> Command {
|
||||||
|
/* 1. Create a new blank cmd */
|
||||||
|
let mut cmd = Command::new(vec!["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 {
|
||||||
|
let mut bot_message = "".to_string();
|
||||||
|
let mut re_enabled = false;
|
||||||
|
|
||||||
|
for (i, arg) in msg
|
||||||
|
.message_text
|
||||||
|
.replace("\u{e0000}", "")
|
||||||
|
.trim()
|
||||||
|
.split(" ")
|
||||||
|
.enumerate()
|
||||||
|
{
|
||||||
|
if i > 1 {
|
||||||
|
if Status::Disabled
|
||||||
|
== bot
|
||||||
|
.get_channel_module_status(
|
||||||
|
msg.channel_login.clone(),
|
||||||
|
arg.to_string(),
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
{
|
||||||
|
bot.enable_module(msg.channel_login.clone(), arg.to_string())
|
||||||
|
.await;
|
||||||
|
|
||||||
|
//bot.get_modules()
|
||||||
|
if let Some(found_mod) = bot.get_module(arg.to_string()).await {
|
||||||
|
bot_message = bot_message.to_string()
|
||||||
|
+ found_mod.get_bot_read_description().as_str();
|
||||||
|
}
|
||||||
|
re_enabled = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if re_enabled {
|
||||||
|
if bot_message.len() > 250 {
|
||||||
|
bot_message = bot_message[..250].to_string();
|
||||||
|
}
|
||||||
|
|
||||||
|
let _ = bot
|
||||||
|
.chat
|
||||||
|
.lock()
|
||||||
|
.await
|
||||||
|
.say_in_reply_to(&msg, format!("Enabled! {}", bot_message))
|
||||||
|
.await;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
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);
|
||||||
|
cmd
|
||||||
|
}
|
||||||
|
|
||||||
|
/// adminonly command that grants a temporary role
|
||||||
|
fn create_iam_role_cmd() -> Command {
|
||||||
|
/* 1. Create a new blank cmd */
|
||||||
|
let mut cmd = Command::new(
|
||||||
|
vec!["I am ".to_string(), "I'm ".to_string(), "Im a ".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
|
||||||
|
.replace("\u{e0000}", "")
|
||||||
|
.trim()
|
||||||
|
.split(" ")
|
||||||
|
.enumerate()
|
||||||
|
{
|
||||||
|
if i > 1 {
|
||||||
|
// bot.disable_module(msg.channel_login.clone(), arg.to_string()).await;
|
||||||
|
// #todo
|
||||||
|
// if not dont have the badge or have a lower priviledge badge
|
||||||
|
// and they dont have an active guest badge, ths admin can be
|
||||||
|
// recognzed wth that badge
|
||||||
|
|
||||||
|
if arg == "mod" || arg == "moderator" {
|
||||||
|
let curr_temp_badges = bot
|
||||||
|
.get_channel_guest_badges(
|
||||||
|
msg.sender.login.clone(),
|
||||||
|
msg.channel_login.clone(),
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
let mut found = false;
|
||||||
|
for temp_badge in curr_temp_badges {
|
||||||
|
if temp_badge.0 == Badge::Moderator {
|
||||||
|
found = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if found {
|
||||||
|
/* do nothing */
|
||||||
|
} else {
|
||||||
|
bot.issue_new_guest_badge(
|
||||||
|
msg.sender.login.clone(),
|
||||||
|
msg.channel_login.clone(),
|
||||||
|
Badge::Moderator,
|
||||||
|
Instant::now(),
|
||||||
|
Duration::from_secs(60 * TEMP_BADGE_DUR_MIN),
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
let _ = bot
|
||||||
|
.chat
|
||||||
|
.lock()
|
||||||
|
.await
|
||||||
|
.say_in_reply_to(
|
||||||
|
&msg,
|
||||||
|
format!(
|
||||||
|
"Temp {:?} issued for {:?} minutes",
|
||||||
|
Badge::Moderator,
|
||||||
|
TEMP_BADGE_DUR_MIN
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if arg == "vip" {
|
||||||
|
let curr_temp_badges = bot
|
||||||
|
.get_channel_guest_badges(
|
||||||
|
msg.sender.login.clone(),
|
||||||
|
msg.channel_login.clone(),
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
let mut found = false;
|
||||||
|
for temp_badge in curr_temp_badges {
|
||||||
|
if temp_badge.0 == Badge::Vip {
|
||||||
|
found = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if found {
|
||||||
|
/* do nothing */
|
||||||
|
} else {
|
||||||
|
bot.issue_new_guest_badge(
|
||||||
|
msg.sender.login.clone(),
|
||||||
|
msg.channel_login.clone(),
|
||||||
|
Badge::Vip,
|
||||||
|
Instant::now(),
|
||||||
|
Duration::from_secs(60 * TEMP_BADGE_DUR_MIN),
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
let _ = bot
|
||||||
|
.chat
|
||||||
|
.lock()
|
||||||
|
.await
|
||||||
|
.say_in_reply_to(
|
||||||
|
&msg,
|
||||||
|
format!(
|
||||||
|
"Temp {:?} issued for {:?} minutes for the bot admin",
|
||||||
|
Badge::Vip,
|
||||||
|
TEMP_BADGE_DUR_MIN
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if arg == "broadcaster" || arg == "strimmer" || arg == "streamer" {
|
||||||
|
let curr_temp_badges = bot
|
||||||
|
.get_channel_guest_badges(
|
||||||
|
msg.sender.login.clone(),
|
||||||
|
msg.channel_login.clone(),
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
let mut found = false;
|
||||||
|
for temp_badge in curr_temp_badges {
|
||||||
|
if temp_badge.0 == Badge::Broadcaster {
|
||||||
|
found = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if found {
|
||||||
|
/* do nothing */
|
||||||
|
} else {
|
||||||
|
bot.issue_new_guest_badge(
|
||||||
|
msg.sender.login.clone(),
|
||||||
|
msg.channel_login.clone(),
|
||||||
|
Badge::Broadcaster,
|
||||||
|
Instant::now(),
|
||||||
|
Duration::from_secs(60 * TEMP_BADGE_DUR_MIN),
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
let _ = bot
|
||||||
|
.chat
|
||||||
|
.lock()
|
||||||
|
.await
|
||||||
|
.say_in_reply_to(
|
||||||
|
&msg,
|
||||||
|
format!(
|
||||||
|
"Temp {:?} issued for {:?} minutes for the bot admin",
|
||||||
|
Badge::Broadcaster,
|
||||||
|
TEMP_BADGE_DUR_MIN
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// let _ = bot.chat.lock().await.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 `execution_async()` */
|
||||||
|
cmd.set_exec_fn(execution_async(execbody));
|
||||||
|
|
||||||
|
/* 4. optionally, remove admin only default flag */
|
||||||
|
cmd.set_admin_only(true);
|
||||||
|
|
||||||
|
// /* 5. optionally, set min badge*/
|
||||||
|
// cmd.set_min_badge(Badge::Moderator);
|
||||||
|
cmd
|
||||||
|
}
|
||||||
|
}
|
226
forcebot_core/src/botcore/bot_objects/command.rs
Normal file
226
forcebot_core/src/botcore/bot_objects/command.rs
Normal file
|
@ -0,0 +1,226 @@
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
use twitch_irc::message::{PrivmsgMessage, ServerMessage};
|
||||||
|
|
||||||
|
use super::{command_condition_async, execution_async, Badge, Bot};
|
||||||
|
|
||||||
|
use super::{CommandTrigger, 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 `execution_async()` on custom async execution bodies
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct Command {
|
||||||
|
commands: Vec<String>,
|
||||||
|
exec_fn: Arc<ExecBody>,
|
||||||
|
min_badge: Badge,
|
||||||
|
/// only admins can run - default : `true`
|
||||||
|
admin_only: bool,
|
||||||
|
/// admin role overrides channel badge - default : `false`
|
||||||
|
admin_override: bool,
|
||||||
|
prefix: String,
|
||||||
|
custom_cond_fn: fn(Arc<Bot>, PrivmsgMessage) -> bool,
|
||||||
|
custom_cond_async: Arc<CommandTrigger>,
|
||||||
|
}
|
||||||
|
|
||||||
|
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(commands: Vec<String>, prefix: String) -> Command {
|
||||||
|
async fn execbody(_: Arc<Bot>, _: ServerMessage) -> Result<String, String> {
|
||||||
|
Result::Ok("success".to_string())
|
||||||
|
}
|
||||||
|
async fn condition01(_: Arc<Bot>, _: PrivmsgMessage) -> bool {
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
|
Command {
|
||||||
|
commands,
|
||||||
|
prefix,
|
||||||
|
exec_fn: Arc::new(execution_async(execbody)),
|
||||||
|
min_badge: Badge::Vip,
|
||||||
|
admin_only: true,
|
||||||
|
admin_override: false,
|
||||||
|
custom_cond_fn: |_: Arc<Bot>, _: PrivmsgMessage| true,
|
||||||
|
custom_cond_async: Arc::new(command_condition_async(condition01)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// set a trigger condition callback that returns true if the command should trigger
|
||||||
|
pub fn set_custom_cond_fn(&mut self, cond_fn: fn(Arc<Bot>, PrivmsgMessage) -> bool) {
|
||||||
|
self.custom_cond_fn = cond_fn;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// sets the async trigger condition for listener
|
||||||
|
///
|
||||||
|
/// Same as `set_custom_cond_fn()` , but async define
|
||||||
|
///
|
||||||
|
/// Use`execution_async()` on the async fn when storing
|
||||||
|
///
|
||||||
|
/// Example -
|
||||||
|
/// ```rust
|
||||||
|
/// /* 1. Create a new blank Listener */
|
||||||
|
/// let mut cmd = Command::new();
|
||||||
|
///
|
||||||
|
/// /* 2. define an async function */
|
||||||
|
/// async fn condition01(_:Arc<Bot>,_:ServerMessage) -> bool { true }
|
||||||
|
///
|
||||||
|
/// /* 3. Set and Store the execution body using `execution_async()` */
|
||||||
|
/// cmd.set_custom_cond_async(condition_async(condition01));
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
pub fn set_custom_cond_async(&mut self, condition: CommandTrigger) {
|
||||||
|
self.custom_cond_async = Arc::new(condition);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// sets the execution body of the listener for when it triggers
|
||||||
|
///
|
||||||
|
/// Use`execution_async()` 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 async fn command_triggered(&self, bot: Arc<Bot>, msg: PrivmsgMessage) -> bool {
|
||||||
|
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());
|
||||||
|
} else {
|
||||||
|
prefixed_cmd.push_str(&cmd.prefix);
|
||||||
|
}
|
||||||
|
for cmd_nm in &cmd.commands {
|
||||||
|
prefixed_cmd.push_str(cmd_nm);
|
||||||
|
if message.message_text.starts_with(prefixed_cmd.as_str()) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn caller_badge_ok(cmd: &Command, bot: Arc<Bot>, message: PrivmsgMessage) -> bool {
|
||||||
|
// senders that are admins skip badge check if the command is adminonly
|
||||||
|
if cmd.admin_only && bot.get_admins().contains(&message.sender.login) {
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
|
||||||
|
// adminOnly commands will can only be ran by admins
|
||||||
|
if cmd.admin_only && bot.get_admins().contains(&message.sender.login) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// admin role overrides badge check if enabled
|
||||||
|
if cmd.admin_override && bot.get_admins().contains(&message.sender.login) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
for badge in message.badges {
|
||||||
|
match cmd.min_badge {
|
||||||
|
Badge::Broadcaster => {
|
||||||
|
if badge.name == "broadcaster" {
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Badge::Moderator => match badge.name.as_str() {
|
||||||
|
"moderator" | "broadcaster" => return true,
|
||||||
|
_ => (),
|
||||||
|
},
|
||||||
|
Badge::Vip => match badge.name.as_str() {
|
||||||
|
"vip" | "moderator" | "broadcaster" => return true,
|
||||||
|
_ => (),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for temp_badge in bot
|
||||||
|
.get_channel_guest_badges(message.sender.login, message.channel_login)
|
||||||
|
.await
|
||||||
|
{
|
||||||
|
match (cmd.min_badge.clone(), temp_badge.0) {
|
||||||
|
(Badge::Broadcaster, Badge::Broadcaster) => return true,
|
||||||
|
(Badge::Moderator, Badge::Moderator)
|
||||||
|
| (Badge::Moderator, Badge::Broadcaster) => return true,
|
||||||
|
(Badge::Vip, Badge::Vip)
|
||||||
|
| (Badge::Vip, Badge::Moderator)
|
||||||
|
| (Badge::Vip, Badge::Broadcaster) => return true,
|
||||||
|
_ => (),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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: PrivmsgMessage) -> bool {
|
||||||
|
if (cmd.admin_only && bot.get_admins().contains(&message.sender.login))
|
||||||
|
|| !cmd.admin_only
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn custom_cond_ok(cmd: &Command, bot: Arc<Bot>, message: PrivmsgMessage) -> bool {
|
||||||
|
(cmd.custom_cond_fn)(bot.clone(), message.clone())
|
||||||
|
&& (cmd.custom_cond_async)(bot, message).await
|
||||||
|
}
|
||||||
|
|
||||||
|
// async fn quiet_off_ok(cmd:&Command,bot:Arc<Bot>,message:PrivmsgMessage) -> bool {
|
||||||
|
// !bot.chat.lock().await.get_channel_quiet(message.channel_login.clone())
|
||||||
|
// || bot.chat.lock().await.get_channel_quiet(message.channel_login.clone()) && cmd.commands.contains(&("quiet off".to_string()))
|
||||||
|
// }
|
||||||
|
|
||||||
|
cmd_called(self, bot.clone(), msg.clone())
|
||||||
|
&& caller_badge_ok(self, bot.clone(), msg.clone()).await
|
||||||
|
&& admin_only_ok(self, bot.clone(), msg.clone())
|
||||||
|
&& custom_cond_ok(self, bot.clone(), msg.clone()).await
|
||||||
|
// &&
|
||||||
|
// quiet_off_ok(self, bot, msg).await
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 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
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 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: Badge) {
|
||||||
|
self.min_badge = min_badge
|
||||||
|
}
|
||||||
|
|
||||||
|
/// sets admin_only
|
||||||
|
pub fn set_admin_only(&mut self, admin_only: bool) {
|
||||||
|
self.admin_only = admin_only
|
||||||
|
}
|
||||||
|
|
||||||
|
/// sets admin_override . This lets admins bypass
|
||||||
|
/// badge restrictions
|
||||||
|
pub fn set_admin_override(&mut self, admin_override: bool) {
|
||||||
|
self.admin_override = admin_override
|
||||||
|
}
|
||||||
|
}
|
147
forcebot_core/src/botcore/bot_objects/listener.rs
Normal file
147
forcebot_core/src/botcore/bot_objects/listener.rs
Normal file
|
@ -0,0 +1,147 @@
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
use twitch_irc::message::ServerMessage;
|
||||||
|
|
||||||
|
use crate::Module;
|
||||||
|
|
||||||
|
use super::{execution_async, listener_condition_async, Bot};
|
||||||
|
|
||||||
|
use super::{ExecBody, ListenerTrigger};
|
||||||
|
|
||||||
|
/// Bot `Listener` that stores trigger condition callback and a execution functon
|
||||||
|
///
|
||||||
|
/// Use `Listener` functions to define the Trigger Condition & Execution callbacks.
|
||||||
|
/// When the Trigger callback is `true`, the Execution callback runs in the bot loop
|
||||||
|
///
|
||||||
|
/// Create a new empty `Listener` with `new()`
|
||||||
|
///
|
||||||
|
/// Use the following on the empty listener before loading it into the bot to set the callbacks
|
||||||
|
///
|
||||||
|
/// - `set_trigger_cond_fn()` - to define the Trigger condition callback
|
||||||
|
///
|
||||||
|
/// - `set_exec_fn()` - to define the Execution Callback
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct Listener {
|
||||||
|
/// trigger condition
|
||||||
|
trigger_cond_fn: fn(Arc<Bot>, ServerMessage) -> bool,
|
||||||
|
/// trigger condition for async
|
||||||
|
trigger_cond_async: Arc<ListenerTrigger>,
|
||||||
|
/// execution body
|
||||||
|
exec_fn: Arc<ExecBody>,
|
||||||
|
parent_module: Arc<Option<Module>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Listener {
|
||||||
|
/// Creates a new empty `Listener`
|
||||||
|
///
|
||||||
|
/// Use `Listener` functions to define the Trigger Condition & Execution callbacks.
|
||||||
|
/// When the Trigger callback is `true`, the Execution callback runs in the bot loop
|
||||||
|
///
|
||||||
|
/// Use the following on the empty listener before loading it into the bot to set the callbacks
|
||||||
|
///
|
||||||
|
/// - `set_trigger_cond_fn()` - to define the Trigger condition callback
|
||||||
|
///
|
||||||
|
/// - `set_exec_fn()` - to define the Execution Callback
|
||||||
|
pub fn new() -> Listener {
|
||||||
|
async fn execbody(_: Arc<Bot>, _: ServerMessage) -> Result<String, String> {
|
||||||
|
Result::Ok("success".to_string())
|
||||||
|
}
|
||||||
|
async fn condition01(_: Arc<Bot>, _: ServerMessage) -> bool {
|
||||||
|
true
|
||||||
|
}
|
||||||
|
Listener {
|
||||||
|
trigger_cond_fn: |_: Arc<Bot>, _: ServerMessage| true,
|
||||||
|
trigger_cond_async: Arc::new(listener_condition_async(condition01)),
|
||||||
|
exec_fn: Arc::new(execution_async(execbody)),
|
||||||
|
parent_module: Arc::new(None),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 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 async trigger condition for listener
|
||||||
|
///
|
||||||
|
/// Same as `set_trigger_cond_fn()` , but async define
|
||||||
|
///
|
||||||
|
/// Use`condition_async()` 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 condition01(_:Arc<Bot>,_:ServerMessage) -> bool { true }
|
||||||
|
///
|
||||||
|
/// /* 3. Set and Store the execution body using `execution_async()` */
|
||||||
|
/// listener.set_trigger_cond_async(condition_async(condition01));
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
pub fn set_trigger_cond_async(&mut self, condition: ListenerTrigger) {
|
||||||
|
self.trigger_cond_async = Arc::new(condition);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// sets the execution body of the listener for when it triggers
|
||||||
|
///
|
||||||
|
/// Use`execution_async()` 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 `execution_async()` */
|
||||||
|
/// listener.set_exec_fn(execution_async(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 async fn cond_triggered(&self, bot: Arc<Bot>, msg: ServerMessage) -> bool {
|
||||||
|
let list = Arc::new(self.clone());
|
||||||
|
|
||||||
|
async fn defined_conditions_ok(
|
||||||
|
list: Arc<Listener>,
|
||||||
|
bot: Arc<Bot>,
|
||||||
|
msg: ServerMessage,
|
||||||
|
) -> bool {
|
||||||
|
// let list = Arc::new(self);
|
||||||
|
(list.trigger_cond_fn)(bot.clone(), msg.clone())
|
||||||
|
&& (list.trigger_cond_async)(bot, msg).await
|
||||||
|
}
|
||||||
|
|
||||||
|
// async fn quiet_off_ok(list:Arc<Listener>,bot:Arc<Bot>,message:ServerMessage) -> bool {
|
||||||
|
// if let ServerMessage::Privmsg(msg) = message {
|
||||||
|
|
||||||
|
// if let Some(parent_mod) = &*list.parent_module {
|
||||||
|
// return !bot.chat.lock().await.get_channel_quiet(msg.channel_login) || parent_mod.get_names().contains(&"debug".to_string());
|
||||||
|
// }
|
||||||
|
|
||||||
|
// return !bot.chat.lock().await.get_channel_quiet(msg.channel_login) ;
|
||||||
|
// }
|
||||||
|
// return true; /* quiet is off for non chat msgs */
|
||||||
|
// }
|
||||||
|
|
||||||
|
defined_conditions_ok(list.clone(), bot.clone(), msg.clone()).await
|
||||||
|
// &&
|
||||||
|
// quiet_off_ok(list, bot, msg).await
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 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
|
||||||
|
}
|
||||||
|
|
||||||
|
/// sets parent module
|
||||||
|
pub fn set_parent_module(&mut self, module: Module) {
|
||||||
|
self.parent_module = Arc::new(Some(module));
|
||||||
|
}
|
||||||
|
}
|
10
forcebot_core/src/botcore/built_in_mods.rs
Normal file
10
forcebot_core/src/botcore/built_in_mods.rs
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
// use std::sync::Arc;
|
||||||
|
|
||||||
|
use crate::Bot;
|
||||||
|
|
||||||
|
pub mod quiet;
|
||||||
|
|
||||||
|
/// used to internally load internal modules
|
||||||
|
pub async fn load_built_in_mods(bot: &Bot) {
|
||||||
|
bot.load_module(quiet::create_module()).await;
|
||||||
|
}
|
90
forcebot_core/src/botcore/built_in_mods/quiet.rs
Normal file
90
forcebot_core/src/botcore/built_in_mods/quiet.rs
Normal file
|
@ -0,0 +1,90 @@
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
use twitch_irc::message::ServerMessage;
|
||||||
|
|
||||||
|
use crate::{execution_async, Badge, Bot, Command, Module};
|
||||||
|
|
||||||
|
/// quiet the bot in a channel
|
||||||
|
///
|
||||||
|
/// use
|
||||||
|
/// `quiet on`
|
||||||
|
/// `quiet off`
|
||||||
|
///
|
||||||
|
///
|
||||||
|
///
|
||||||
|
|
||||||
|
/// Use this function when loading modules into the bot
|
||||||
|
///
|
||||||
|
/// For example
|
||||||
|
/// ```rust
|
||||||
|
/// bot.load_module(quiet::create_module());
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
pub fn create_module() -> Module {
|
||||||
|
/* 1. Create a new module */
|
||||||
|
let mut custom_mod = Module::new(vec!["quiet".to_string()], "".to_string());
|
||||||
|
|
||||||
|
/* 2. Load the cmd into a new module */
|
||||||
|
custom_mod.load_command(cmd_quiet_on());
|
||||||
|
custom_mod.load_command(cmd_quiet_off());
|
||||||
|
custom_mod
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Command definition for quiet command
|
||||||
|
fn cmd_quiet_on() -> Command {
|
||||||
|
/* 1. Create a new cmd */
|
||||||
|
let mut cmd = Command::new(vec!["quiet on".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 {
|
||||||
|
// dbg!("quiet on called");
|
||||||
|
|
||||||
|
let chatlock = bot.chat.lock().await;
|
||||||
|
let _ = chatlock.say_in_reply_to(&msg, "Shush ".to_string()).await;
|
||||||
|
|
||||||
|
chatlock.set_channel_quiet(msg.channel_login.clone(), true);
|
||||||
|
println!("channel {} set quiet true", msg.channel_login);
|
||||||
|
|
||||||
|
return Result::Ok("Success".to_string());
|
||||||
|
}
|
||||||
|
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::Moderator);
|
||||||
|
cmd.set_admin_override(true);
|
||||||
|
|
||||||
|
cmd
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Command definition for quiet command
|
||||||
|
fn cmd_quiet_off() -> Command {
|
||||||
|
/* 1. Create a new cmd */
|
||||||
|
let mut cmd = Command::new(vec!["quiet off".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 chatlock = bot.chat.lock().await;
|
||||||
|
|
||||||
|
chatlock.set_channel_quiet(msg.channel_login.clone(), false);
|
||||||
|
let _ = chatlock
|
||||||
|
.say_in_reply_to(&msg, "GoodGirl I'll be good for u chat rar ".to_string())
|
||||||
|
.await;
|
||||||
|
|
||||||
|
println!("channel {} set quiet false", msg.channel_login);
|
||||||
|
}
|
||||||
|
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::Moderator);
|
||||||
|
cmd.set_admin_override(true);
|
||||||
|
|
||||||
|
cmd
|
||||||
|
}
|
140
forcebot_core/src/botcore/chat.rs
Normal file
140
forcebot_core/src/botcore/chat.rs
Normal file
|
@ -0,0 +1,140 @@
|
||||||
|
use std::{
|
||||||
|
fmt::Error,
|
||||||
|
ops::Mul,
|
||||||
|
rc::Rc,
|
||||||
|
sync::{Arc, Mutex, RwLock},
|
||||||
|
};
|
||||||
|
|
||||||
|
use twitch_irc::{
|
||||||
|
login::StaticLoginCredentials, message::ReplyToMessage, SecureTCPTransport, TwitchIRCClient,
|
||||||
|
};
|
||||||
|
|
||||||
|
use crate::Bot;
|
||||||
|
|
||||||
|
/// Bot API to send messages to send messages to chat
|
||||||
|
///
|
||||||
|
/// Uses TwitchIRCClient say_in_reply_to() but enforces controls like quiet
|
||||||
|
///
|
||||||
|
///
|
||||||
|
|
||||||
|
pub struct Chat {
|
||||||
|
/// outbound chat client msg stream
|
||||||
|
pub client: TwitchIRCClient<SecureTCPTransport, StaticLoginCredentials>,
|
||||||
|
/// channel_quiet
|
||||||
|
channel_quiet_yn: RwLock<Vec<(String, RwLock<bool>)>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Chat {
|
||||||
|
pub async fn new(client: TwitchIRCClient<SecureTCPTransport, StaticLoginCredentials>) -> Chat {
|
||||||
|
Chat {
|
||||||
|
client,
|
||||||
|
// parent_bot : Mutex::new(Bot::new().await) ,
|
||||||
|
channel_quiet_yn: RwLock::new(vec![]),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// pub fn set_parent_bot(&self,parent_bot_in:Arc<Bot>)
|
||||||
|
// {
|
||||||
|
// let mut lock = self.parent_bot.lock().unwrap();
|
||||||
|
// *lock = parent_bot_in;
|
||||||
|
// }
|
||||||
|
|
||||||
|
/// helper
|
||||||
|
fn ok_to_send(&self, channel_login: String) -> bool {
|
||||||
|
fn not_quiet_ok(chat: &Chat, channel_login: String) -> bool {
|
||||||
|
// let lock = chat.parent_bot.lock().unwrap();
|
||||||
|
// let a = lock.as_ref();
|
||||||
|
// if let Some(bot) = &*lock {
|
||||||
|
return !chat.get_channel_quiet(channel_login);
|
||||||
|
// }
|
||||||
|
// true
|
||||||
|
}
|
||||||
|
not_quiet_ok(self, channel_login)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get the quiet status of a channel
|
||||||
|
pub fn get_channel_quiet(&self, channel: String) -> bool {
|
||||||
|
for a in self.channel_quiet_yn.read().unwrap().iter() {
|
||||||
|
if a.0 == channel {
|
||||||
|
return a.1.read().unwrap().clone();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get the quiet status of a channel
|
||||||
|
pub fn set_channel_quiet(&self, channel: String, quiet_on: bool) {
|
||||||
|
let mut found = false;
|
||||||
|
|
||||||
|
let chnlquiet = self.channel_quiet_yn.read().unwrap();
|
||||||
|
for rec in chnlquiet.iter() {
|
||||||
|
if rec.0 == channel {
|
||||||
|
found = true;
|
||||||
|
let mut status = rec.1.write().unwrap();
|
||||||
|
*status = quiet_on;
|
||||||
|
drop(status);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
drop(chnlquiet);
|
||||||
|
|
||||||
|
if !found {
|
||||||
|
// dbg!("set chn quiet > !found channel quiet status");
|
||||||
|
let mut chnlquiet = self.channel_quiet_yn.write().unwrap();
|
||||||
|
chnlquiet.push((channel, RwLock::new(quiet_on)));
|
||||||
|
drop(chnlquiet);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn say_in_reply_to(
|
||||||
|
&self,
|
||||||
|
reply_to: &impl ReplyToMessage,
|
||||||
|
message: String,
|
||||||
|
) -> Result<(), ()> {
|
||||||
|
// reply_to.channel_login()
|
||||||
|
if self.ok_to_send(reply_to.channel_login().to_string()) {
|
||||||
|
match self.client.say_in_reply_to(reply_to, message).await {
|
||||||
|
Ok(_) => return Ok(()),
|
||||||
|
Err(_) => return Err(()),
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return Err(());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn say(&self, channel_login: String, message: String) -> Result<(), ()> {
|
||||||
|
if self.ok_to_send(channel_login.to_string()) {
|
||||||
|
match self.client.say(channel_login, message).await {
|
||||||
|
Ok(_) => return Ok(()),
|
||||||
|
Err(_) => return Err(()),
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return Err(());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn me(&self, channel_login: String, message: String) -> Result<(), ()> {
|
||||||
|
if self.ok_to_send(channel_login.to_string()) {
|
||||||
|
match self.client.me(channel_login, message).await {
|
||||||
|
Ok(_) => return Ok(()),
|
||||||
|
Err(_) => return Err(()),
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return Err(());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn me_in_reply_to(
|
||||||
|
&self,
|
||||||
|
reply_to: &impl ReplyToMessage,
|
||||||
|
message: String,
|
||||||
|
) -> Result<(), ()> {
|
||||||
|
if self.ok_to_send(reply_to.channel_login().to_string()) {
|
||||||
|
match self.client.me_in_reply_to(reply_to, message).await {
|
||||||
|
Ok(_) => return Ok(()),
|
||||||
|
Err(_) => return Err(()),
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return Err(());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
74
forcebot_core/src/botcore/modules.rs
Normal file
74
forcebot_core/src/botcore/modules.rs
Normal file
|
@ -0,0 +1,74 @@
|
||||||
|
// use std::sync::{Arc, Mutex};
|
||||||
|
|
||||||
|
use super::bot_objects::command::Command;
|
||||||
|
use super::bot_objects::listener::Listener;
|
||||||
|
|
||||||
|
#[derive(PartialEq, Eq, Debug, Clone)]
|
||||||
|
pub enum Status {
|
||||||
|
Disabled,
|
||||||
|
Enabled,
|
||||||
|
NotLoaded,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Bot `Module` that groups a set of `bot_objects`
|
||||||
|
///
|
||||||
|
/// Elevated chatters can disable modules by their name or chat alias
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct Module {
|
||||||
|
name: Vec<String>,
|
||||||
|
// _alias: String,
|
||||||
|
bot_read_description: String,
|
||||||
|
listeners: Vec<Listener>,
|
||||||
|
commands: Vec<Command>,
|
||||||
|
// disable module at load for bot channels
|
||||||
|
default_status_per_channel: Status,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Module {
|
||||||
|
/// create a new module
|
||||||
|
pub fn new(name: Vec<String>, bot_read_description: String) -> Module {
|
||||||
|
Module {
|
||||||
|
name,
|
||||||
|
// _alias: alias,
|
||||||
|
bot_read_description,
|
||||||
|
listeners: vec![],
|
||||||
|
commands: vec![],
|
||||||
|
default_status_per_channel: Status::Enabled,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Loads a `Listener` into the module
|
||||||
|
pub fn load_listener(&mut self, mut l: Listener) {
|
||||||
|
l.set_parent_module(self.clone());
|
||||||
|
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_names(&self) -> Vec<String> {
|
||||||
|
self.name.clone()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_bot_read_description(&self) -> String {
|
||||||
|
self.bot_read_description.clone()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_status_by_default(&mut self, status: Status) {
|
||||||
|
self.default_status_per_channel = status;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_status_by_default(&self) -> Status {
|
||||||
|
self.default_status_per_channel.clone()
|
||||||
|
}
|
||||||
|
}
|
4
forcebot_core/src/custom_mods.rs
Normal file
4
forcebot_core/src/custom_mods.rs
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
pub mod debug;
|
||||||
|
pub mod guest_badge;
|
||||||
|
pub mod pyramid;
|
||||||
|
// pub mod quiet;
|
138
forcebot_core/src/custom_mods/debug.rs
Normal file
138
forcebot_core/src/custom_mods/debug.rs
Normal file
|
@ -0,0 +1,138 @@
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
use crate::{execution_async, modules::Status, Bot, Command, Listener, Module};
|
||||||
|
use twitch_irc::message::ServerMessage;
|
||||||
|
|
||||||
|
/// debug module
|
||||||
|
///
|
||||||
|
/// Commands to enable debugging messages in chat
|
||||||
|
///
|
||||||
|
/// `debug on` to start
|
||||||
|
///
|
||||||
|
/// `debug off` to stop
|
||||||
|
///
|
||||||
|
///
|
||||||
|
|
||||||
|
/// Use this function when loading modules into the bot
|
||||||
|
///
|
||||||
|
/// For example
|
||||||
|
/// ```rust
|
||||||
|
/// bot.load_module(debug::create_module());
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
pub fn create_module() -> Module {
|
||||||
|
/* 1. Create a new module */
|
||||||
|
let mut custom_mod = Module::new(vec!["debug".to_string()], "".to_string());
|
||||||
|
|
||||||
|
/* 2. Load the cmd into a new module */
|
||||||
|
custom_mod.load_command(cmd_debug_on());
|
||||||
|
custom_mod.load_command(cmd_debug_off());
|
||||||
|
custom_mod
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Command definition for debug command
|
||||||
|
fn cmd_debug_on() -> Command {
|
||||||
|
/* 1. Create a new cmd */
|
||||||
|
let mut cmd = Command::new(vec!["debug on".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 {
|
||||||
|
// dbg!("debug cmd on executed");
|
||||||
|
|
||||||
|
let modulename = "debug listener".to_string();
|
||||||
|
|
||||||
|
if let Status::NotLoaded = bot
|
||||||
|
.get_channel_module_status(msg.channel_login.clone(), modulename.clone())
|
||||||
|
.await
|
||||||
|
{
|
||||||
|
let module = create_listener_module(modulename.clone());
|
||||||
|
bot.load_module(module.clone()).await;
|
||||||
|
}
|
||||||
|
let modl = bot.get_module(modulename).await.unwrap();
|
||||||
|
bot.enable_module(
|
||||||
|
msg.channel_login.clone(),
|
||||||
|
modl.get_names().first().unwrap().clone(),
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
println!("Debug enabled for channel {}", msg.channel_login);
|
||||||
|
}
|
||||||
|
Result::Err("Not Valid message type".to_string())
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 3. Set Command flags */
|
||||||
|
cmd.set_exec_fn(execution_async(execbody));
|
||||||
|
cmd.set_admin_only(true);
|
||||||
|
// cmd.set_min_badge(Badge::Moderator);
|
||||||
|
cmd.set_admin_override(true);
|
||||||
|
|
||||||
|
cmd
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Command definition for debug off command
|
||||||
|
fn cmd_debug_off() -> Command {
|
||||||
|
/* 1. Create a new cmd */
|
||||||
|
let mut cmd = Command::new(vec!["debug off".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 {
|
||||||
|
// dbg!("debug cmd on executed");
|
||||||
|
|
||||||
|
let modulename = "debug listener".to_string();
|
||||||
|
|
||||||
|
// if let Status::NotLoaded = bot.get_channel_module_status(msg.channel_login.clone(), modulename.clone()).await {
|
||||||
|
// let module = create_listener_module(modulename.clone());
|
||||||
|
// bot.load_module(module.clone()).await;
|
||||||
|
// }
|
||||||
|
let modl = bot.get_module(modulename).await.unwrap();
|
||||||
|
bot.disable_module(
|
||||||
|
msg.channel_login.clone(),
|
||||||
|
modl.get_names().first().unwrap().clone(),
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
println!("Debug disabled for channel {}", msg.channel_login);
|
||||||
|
}
|
||||||
|
Result::Err("Not Valid message type".to_string())
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 3. Set Command flags */
|
||||||
|
cmd.set_exec_fn(execution_async(execbody));
|
||||||
|
cmd.set_admin_only(true);
|
||||||
|
// cmd.set_min_badge(Badge::Moderator);
|
||||||
|
cmd.set_admin_override(true);
|
||||||
|
|
||||||
|
cmd
|
||||||
|
}
|
||||||
|
|
||||||
|
fn create_listener_module(name: String) -> Module {
|
||||||
|
let mut custom_mod = Module::new(vec![name], "".to_string());
|
||||||
|
// dbg!("debug listener module created");
|
||||||
|
custom_mod.load_listener(cmd_debug_listener());
|
||||||
|
custom_mod.set_status_by_default(Status::Disabled);
|
||||||
|
|
||||||
|
custom_mod
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Listener for debug
|
||||||
|
fn cmd_debug_listener() -> Listener {
|
||||||
|
// dbg!("Creating debug listener");
|
||||||
|
/* 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> {
|
||||||
|
if let ServerMessage::Privmsg(msg) = message {
|
||||||
|
dbg!(msg); /* outputs message to debug */
|
||||||
|
}
|
||||||
|
Result::Err("Not Valid message type".to_string())
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 2d. Set and Store the execution body using `execution_async()` */
|
||||||
|
listener.set_exec_fn(execution_async(execbody));
|
||||||
|
|
||||||
|
listener
|
||||||
|
}
|
175
forcebot_core/src/custom_mods/guest_badge.rs
Normal file
175
forcebot_core/src/custom_mods/guest_badge.rs
Normal file
|
@ -0,0 +1,175 @@
|
||||||
|
use std::{
|
||||||
|
sync::Arc,
|
||||||
|
time::{Duration, Instant},
|
||||||
|
};
|
||||||
|
|
||||||
|
use twitch_irc::message::ServerMessage;
|
||||||
|
|
||||||
|
use crate::{execution_async, Badge, Bot, Command, Module};
|
||||||
|
|
||||||
|
/// guest_badge / guest module
|
||||||
|
///
|
||||||
|
/// Temporary badges can be issued to chatters. The bot then opens functionality
|
||||||
|
/// to that chatter based on the recognized role
|
||||||
|
///
|
||||||
|
/// Chatters with real badge roles will be able to share guest
|
||||||
|
/// badges based on their role
|
||||||
|
///
|
||||||
|
///
|
||||||
|
///
|
||||||
|
|
||||||
|
const VIP_GIVEN_DUR_MIN: u64 = 15;
|
||||||
|
const MOD_GIVEN_DUR_MIN: u64 = 30;
|
||||||
|
|
||||||
|
/// Use this function when loading modules into the bot
|
||||||
|
///
|
||||||
|
/// For example
|
||||||
|
/// ```rust
|
||||||
|
/// bot.load_module(guest_badge::create_module());
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
pub fn create_module() -> Module {
|
||||||
|
let mut custom_mod = Module::new(
|
||||||
|
vec!["guests".to_string()],
|
||||||
|
"Temp Guest badges can be given by chatters with badges. ".to_string(),
|
||||||
|
);
|
||||||
|
|
||||||
|
custom_mod.load_command(create_cmd_mod());
|
||||||
|
custom_mod.load_command(create_cmd_vip());
|
||||||
|
|
||||||
|
custom_mod
|
||||||
|
}
|
||||||
|
|
||||||
|
fn create_cmd_vip() -> Command {
|
||||||
|
let mut cmd = Command::new(vec!["vip".to_string()], "".to_string());
|
||||||
|
|
||||||
|
async fn execbody(bot: Arc<Bot>, message: ServerMessage) -> Result<String, String> {
|
||||||
|
if let ServerMessage::Privmsg(msg) = message {
|
||||||
|
let guest_dur_min = {
|
||||||
|
let mut result = VIP_GIVEN_DUR_MIN;
|
||||||
|
for badge in msg.clone().badges {
|
||||||
|
if badge.name == "vip" {
|
||||||
|
result = VIP_GIVEN_DUR_MIN;
|
||||||
|
}
|
||||||
|
if badge.name == "moderator" {
|
||||||
|
result = MOD_GIVEN_DUR_MIN;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
result
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut badges_issued = false;
|
||||||
|
for (i, arg) in msg
|
||||||
|
.message_text
|
||||||
|
.replace("\u{e0000}", "")
|
||||||
|
.trim()
|
||||||
|
.split(" ")
|
||||||
|
.enumerate()
|
||||||
|
{
|
||||||
|
if i > 1 {
|
||||||
|
let mut already_vip = false;
|
||||||
|
|
||||||
|
for guest_badge in bot
|
||||||
|
.get_channel_guest_badges(arg.trim().to_string(), msg.channel_login.clone())
|
||||||
|
.await
|
||||||
|
{
|
||||||
|
if guest_badge.0 == Badge::Vip {
|
||||||
|
already_vip = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !already_vip {
|
||||||
|
badges_issued = true;
|
||||||
|
bot.issue_new_guest_badge(
|
||||||
|
arg.trim().to_string(),
|
||||||
|
msg.channel_login.clone(),
|
||||||
|
Badge::Vip,
|
||||||
|
Instant::now(),
|
||||||
|
Duration::from_secs(60 * guest_dur_min),
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if badges_issued {
|
||||||
|
let _ = bot
|
||||||
|
.chat
|
||||||
|
.lock()
|
||||||
|
.await
|
||||||
|
.say_in_reply_to(
|
||||||
|
&msg.clone(),
|
||||||
|
format!("Guest badges issued for {} min", guest_dur_min),
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
return Result::Ok("Success".to_string());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Result::Err("Not Valid message type".to_string())
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd.set_exec_fn(execution_async(execbody));
|
||||||
|
|
||||||
|
cmd.set_admin_only(false);
|
||||||
|
cmd.set_admin_override(true);
|
||||||
|
cmd.set_min_badge(Badge::Vip);
|
||||||
|
cmd
|
||||||
|
}
|
||||||
|
|
||||||
|
fn create_cmd_mod() -> Command {
|
||||||
|
let mut cmd = Command::new(vec!["mod".to_string()], "".to_string());
|
||||||
|
|
||||||
|
async fn execbody(bot: Arc<Bot>, message: ServerMessage) -> Result<String, String> {
|
||||||
|
if let ServerMessage::Privmsg(msg) = message {
|
||||||
|
let mut badges_issued = false;
|
||||||
|
for (i, arg) in msg
|
||||||
|
.message_text
|
||||||
|
.replace("\u{e0000}", "")
|
||||||
|
.trim()
|
||||||
|
.split(" ")
|
||||||
|
.enumerate()
|
||||||
|
{
|
||||||
|
if i > 1 {
|
||||||
|
let mut already_mod = false;
|
||||||
|
for guest_badge in bot
|
||||||
|
.get_channel_guest_badges(arg.trim().to_string(), msg.channel_login.clone())
|
||||||
|
.await
|
||||||
|
{
|
||||||
|
if guest_badge.0 == Badge::Moderator {
|
||||||
|
already_mod = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !already_mod {
|
||||||
|
badges_issued = true;
|
||||||
|
bot.issue_new_guest_badge(
|
||||||
|
arg.trim().to_string(),
|
||||||
|
msg.channel_login.clone(),
|
||||||
|
Badge::Moderator,
|
||||||
|
Instant::now(),
|
||||||
|
Duration::from_secs(60 * MOD_GIVEN_DUR_MIN),
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if badges_issued {
|
||||||
|
let _ = bot
|
||||||
|
.chat
|
||||||
|
.lock()
|
||||||
|
.await
|
||||||
|
.say_in_reply_to(
|
||||||
|
&msg,
|
||||||
|
format!("Guest badges issued for {} min", MOD_GIVEN_DUR_MIN),
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
return Result::Ok("Success".to_string());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Result::Err("Not Valid message type".to_string())
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd.set_exec_fn(execution_async(execbody));
|
||||||
|
|
||||||
|
cmd.set_admin_only(false);
|
||||||
|
cmd.set_admin_override(true);
|
||||||
|
cmd.set_min_badge(Badge::Moderator);
|
||||||
|
cmd
|
||||||
|
}
|
485
forcebot_core/src/custom_mods/pyramid.rs
Normal file
485
forcebot_core/src/custom_mods/pyramid.rs
Normal file
|
@ -0,0 +1,485 @@
|
||||||
|
use std::sync::{Arc, Mutex};
|
||||||
|
|
||||||
|
use twitch_irc::message::{PrivmsgMessage, ServerMessage};
|
||||||
|
|
||||||
|
// use crate::{execution_async, listener_condition_async, Badge, Bot, Command, Listener, Module};
|
||||||
|
use super::super::botcore::bot::Bot;
|
||||||
|
use super::super::botcore::bot_objects::command::Command;
|
||||||
|
use super::super::botcore::bot_objects::execution_async;
|
||||||
|
use super::super::botcore::bot_objects::listener::Listener;
|
||||||
|
use super::super::botcore::bot_objects::listener_condition_async;
|
||||||
|
use super::super::botcore::bot_objects::Badge;
|
||||||
|
use super::super::botcore::modules::Module;
|
||||||
|
|
||||||
|
/// pyramid module
|
||||||
|
///
|
||||||
|
/// for detecting & handling pyramids
|
||||||
|
///
|
||||||
|
/// - listener - detects pyramid
|
||||||
|
/// - cmd & listener - interrupts some chatters temporarily
|
||||||
|
///
|
||||||
|
///
|
||||||
|
use lazy_static::lazy_static;
|
||||||
|
|
||||||
|
/// Use this function when loading modules into the bot
|
||||||
|
///
|
||||||
|
/// For example
|
||||||
|
/// ```rust
|
||||||
|
/// bot.load_module(pyramid::create_module());
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
pub fn create_module() -> Module {
|
||||||
|
let mut custom_mod = Module::new(
|
||||||
|
vec!["pyramid".to_string(), "pyramids".to_string()],
|
||||||
|
"o7 I can handle pyramids".to_string(),
|
||||||
|
);
|
||||||
|
custom_mod.load_listener(create_pyramid_detector());
|
||||||
|
|
||||||
|
custom_mod
|
||||||
|
}
|
||||||
|
|
||||||
|
fn create_pyramid_detector() -> Listener {
|
||||||
|
/* 1. Create a new blank Listener */
|
||||||
|
let mut listener = Listener::new();
|
||||||
|
|
||||||
|
/* 2. Define an async trigger condition callback */
|
||||||
|
async fn condition01(bot: Arc<Bot>, message: ServerMessage) -> bool {
|
||||||
|
if let ServerMessage::Privmsg(msg) = message {
|
||||||
|
if detect_pyramid_complete_ok(bot.clone(), msg.clone()).await
|
||||||
|
&& get_pyramid_size(msg.channel_login) > 3
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
false
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 3. Set a trigger condition function for listener */
|
||||||
|
listener.set_trigger_cond_async(listener_condition_async(condition01));
|
||||||
|
|
||||||
|
/* 4. Define an async fn callback execution */
|
||||||
|
async fn execbody(bot: Arc<Bot>, message: ServerMessage) -> Result<String, String> {
|
||||||
|
if let ServerMessage::Privmsg(msg) = message {
|
||||||
|
dbg!("enter pyramid listener execution - after pyramid complete");
|
||||||
|
// if detect_pyramid_complete_ok(bot.clone(), msg.clone()).await {
|
||||||
|
|
||||||
|
// let _ = bot.chat.lock().await.
|
||||||
|
dbg!("> get start pattern");
|
||||||
|
let pattern = get_start_pattern(msg.channel_login.clone());
|
||||||
|
let mut outmsg;
|
||||||
|
|
||||||
|
/* Prefer emote before pattern in case pattern is command */
|
||||||
|
if pattern.len() < 50 {
|
||||||
|
outmsg = format!("Clap {}", pattern);
|
||||||
|
} else {
|
||||||
|
outmsg = "Clap".to_string();
|
||||||
|
}
|
||||||
|
|
||||||
|
dbg!(get_pyramid_size(msg.channel_login.clone()));
|
||||||
|
if get_pyramid_size(msg.channel_login.clone()) < 4 {
|
||||||
|
outmsg = format!("{} annytfMagniGlass", outmsg);
|
||||||
|
}
|
||||||
|
|
||||||
|
dbg!("> start pattern :", pattern);
|
||||||
|
|
||||||
|
dbg!("> say_in_reply_to completed :", outmsg.clone());
|
||||||
|
let _ = bot.chat.lock().await.say_in_reply_to(&msg, outmsg).await;
|
||||||
|
|
||||||
|
dbg!("> set pyramid started - false");
|
||||||
|
set_pyramid_started(msg.channel_login.clone(), false);
|
||||||
|
|
||||||
|
return Result::Ok("Success".to_string());
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
Result::Err("Not Valid message type".to_string())
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 5. Set and Store the execution body using `execution_async()` */
|
||||||
|
listener.set_exec_fn(Box::new(move |a, b| Box::pin(execbody(a, b))));
|
||||||
|
|
||||||
|
listener
|
||||||
|
}
|
||||||
|
|
||||||
|
/// detect pyramid based on latest message and channel
|
||||||
|
///
|
||||||
|
///
|
||||||
|
async fn detect_pyramid_complete_ok(_bot: Arc<Bot>, msg: PrivmsgMessage) -> bool {
|
||||||
|
dbg!("enter detect_pyramid_complete()");
|
||||||
|
|
||||||
|
let msgtext = msg
|
||||||
|
.message_text
|
||||||
|
.replace("", "")
|
||||||
|
.replace("\u{e0000}", "")
|
||||||
|
.trim()
|
||||||
|
.to_string();
|
||||||
|
let msgchannel = msg.channel_login;
|
||||||
|
let msgchatter = msg.sender.login;
|
||||||
|
|
||||||
|
// 1. Check if Pyramid started in chat > and recognize pyramid started
|
||||||
|
if !is_pyramid_started(msgchannel.clone())
|
||||||
|
& check_start_pyramid(msgchannel.clone(), msgtext.clone())
|
||||||
|
{
|
||||||
|
dbg!("> set pyramid started - true");
|
||||||
|
set_pyramid_started(msgchannel.clone(), true);
|
||||||
|
push_to_compare(
|
||||||
|
msgchannel.clone(),
|
||||||
|
msgchatter.clone(),
|
||||||
|
get_start_pattern(msgchannel.clone()),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if is_pyramid_started(msgchannel.clone()) {
|
||||||
|
push_to_compare(msgchannel.clone(), msgchatter.clone(), msgtext.clone());
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2a. If Pyramid Not Started, Assume message is a potential start pattern
|
||||||
|
if !is_pyramid_started(msgchannel.clone()) {
|
||||||
|
set_start_pattern(msgchannel.clone(), msgtext.clone());
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2b. If Pyramid is Started, and the latest message is the pattern, check for
|
||||||
|
// symmetry to determine pyramid
|
||||||
|
|
||||||
|
if is_pyramid_started(msgchannel.clone())
|
||||||
|
&& msgtext.clone() == get_start_pattern(msgchannel.clone())
|
||||||
|
{
|
||||||
|
if symmetry_ok(msgchannel.clone()) {
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
dbg!("> set pyramid started - false");
|
||||||
|
set_pyramid_started(msgchannel, false);
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2c. if Pyramid is strted but latest message does not ontain pattern
|
||||||
|
if is_pyramid_started(msgchannel.clone())
|
||||||
|
&& !msgtext
|
||||||
|
.clone()
|
||||||
|
.contains(get_start_pattern(msgchannel.clone()).as_str())
|
||||||
|
{
|
||||||
|
dbg!("> set pyramid started - false");
|
||||||
|
set_pyramid_started(msgchannel, false);
|
||||||
|
|
||||||
|
return false;
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
lazy_static! {
|
||||||
|
/// Message Compare stack per channel (channel:String,msgstack:Vec<(chatter:String,message:String)>)
|
||||||
|
pub static ref COMPARE_MSG_STACK_PER_CHNL: Mutex<Vec<(String,Mutex<Vec<(String,String)>>)>> = Mutex::new(vec![]);
|
||||||
|
#[derive(Debug)]
|
||||||
|
/// Pyramid Started per channel (channel:String,started:bool)
|
||||||
|
pub static ref PYRAMID_STARTED_PER_CHNL: Mutex<Vec<(String,Mutex<bool>)>> = Mutex::new(vec![]);
|
||||||
|
/// Start patterns per channel (channel:String,pattern:String)
|
||||||
|
pub static ref START_PATTERNS_PER_CHNL: Mutex<Vec<(String,Mutex<String>)>> = Mutex::new(vec![]);
|
||||||
|
/// Pyramid sze per channel (channel:String,started:bool)
|
||||||
|
pub static ref PYRAMID_SIZE_PER_CHNL: Mutex<Vec<(String,Mutex<i32>)>> = Mutex::new(vec![]);
|
||||||
|
/// temp message stack checker
|
||||||
|
pub static ref TEMP_MSG_STACK: Mutex<Vec<String>> = Mutex::new(vec![]);
|
||||||
|
|
||||||
|
/// interruptor targets - (channel:String,chatters:Vec<String>>)
|
||||||
|
pub static ref INTERRUPT_TRG_PER_CHNL: Mutex<Vec<(String,Mutex<Vec<String>>)>> = Mutex::new(vec![]);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
fn read_top_of_compare(channel: String) -> Option<(String, String)> {
|
||||||
|
let comp_perchnl = COMPARE_MSG_STACK_PER_CHNL.lock().unwrap();
|
||||||
|
|
||||||
|
for rec in comp_perchnl.iter() {
|
||||||
|
if rec.0 == channel {
|
||||||
|
let msg_stack = rec.1.lock().unwrap();
|
||||||
|
|
||||||
|
return msg_stack.last().cloned();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
|
fn pop_top_of_compare(channel: String) -> Option<(String, String)> {
|
||||||
|
let comp_perchnl = COMPARE_MSG_STACK_PER_CHNL.lock().unwrap();
|
||||||
|
|
||||||
|
for rec in comp_perchnl.iter() {
|
||||||
|
if rec.0 == channel {
|
||||||
|
let mut msg_stack = rec.1.lock().unwrap();
|
||||||
|
|
||||||
|
let popped = msg_stack.pop();
|
||||||
|
return popped;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_pyramid_started(channel: String, started: bool) {
|
||||||
|
let mut start_perchnl = PYRAMID_STARTED_PER_CHNL.lock().unwrap();
|
||||||
|
let mut found = false;
|
||||||
|
for rec in start_perchnl.iter() {
|
||||||
|
if rec.0 == channel {
|
||||||
|
found = true;
|
||||||
|
let mut rec_started = rec.1.lock().unwrap();
|
||||||
|
*rec_started = started;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !found {
|
||||||
|
start_perchnl.push((channel, Mutex::new(started)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_pyramid_started(channel: String) -> bool {
|
||||||
|
let start_perchnl = PYRAMID_STARTED_PER_CHNL.lock().unwrap();
|
||||||
|
for rec in start_perchnl.iter() {
|
||||||
|
if rec.0 == channel {
|
||||||
|
let rec_started = rec.1.lock().unwrap();
|
||||||
|
return *rec_started;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
false
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_start_pattern(channel: String, pattern: String) {
|
||||||
|
let mut start_patterns = START_PATTERNS_PER_CHNL.lock().unwrap();
|
||||||
|
|
||||||
|
let mut found = false;
|
||||||
|
for rec in start_patterns.iter() {
|
||||||
|
if rec.0 == channel {
|
||||||
|
found = true;
|
||||||
|
let mut patternlock = rec.1.lock().unwrap();
|
||||||
|
*patternlock = pattern.clone();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !found {
|
||||||
|
start_patterns.push((channel.clone(), Mutex::new(pattern.clone())));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_start_pattern(channel: String) -> String {
|
||||||
|
let start_patterns = START_PATTERNS_PER_CHNL.lock().unwrap();
|
||||||
|
|
||||||
|
for rec in start_patterns.iter() {
|
||||||
|
if rec.0 == channel {
|
||||||
|
let patternlock = rec.1.lock().unwrap();
|
||||||
|
return patternlock.clone();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return "".to_string();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// pushes message to compare stack
|
||||||
|
fn push_to_compare(channel: String, chatter: String, message: String) {
|
||||||
|
let mut comp_perchnl = COMPARE_MSG_STACK_PER_CHNL.lock().unwrap();
|
||||||
|
|
||||||
|
let mut found = false;
|
||||||
|
for rec in comp_perchnl.iter() {
|
||||||
|
if rec.0 == channel {
|
||||||
|
found = true;
|
||||||
|
let mut msg_stack = rec.1.lock().unwrap();
|
||||||
|
msg_stack.push((chatter.clone(), message.clone()));
|
||||||
|
// dbg!("Push message to cmp stack ; result last cmp_pchnl - ",comp_perchnl.last());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !found {
|
||||||
|
comp_perchnl.push((channel, Mutex::new(vec![(chatter, message)])));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// checks latest and next latest messages for potential start
|
||||||
|
fn check_start_pyramid(channel: String, msgtext: String) -> bool {
|
||||||
|
msgtext
|
||||||
|
== format!(
|
||||||
|
"{} {}",
|
||||||
|
get_start_pattern(channel.clone()),
|
||||||
|
get_start_pattern(channel.clone())
|
||||||
|
)
|
||||||
|
// msgtext == format!("{} {} {}",
|
||||||
|
// get_start_pattern(channel.clone()),
|
||||||
|
// get_start_pattern(channel.clone()),
|
||||||
|
// get_start_pattern(channel.clone())
|
||||||
|
// )
|
||||||
|
}
|
||||||
|
|
||||||
|
/// pops the compare stack to determine symmetry
|
||||||
|
fn symmetry_ok(channel: String) -> bool {
|
||||||
|
let mut temp_stack = TEMP_MSG_STACK.lock().unwrap();
|
||||||
|
let mut checking_started = false;
|
||||||
|
if !(read_top_of_compare(channel.clone())
|
||||||
|
.unwrap_or(("".to_string(), "".to_string()))
|
||||||
|
.1
|
||||||
|
== get_start_pattern(channel.clone()))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut pyramid_size = 0;
|
||||||
|
loop {
|
||||||
|
if !checking_started
|
||||||
|
&& read_top_of_compare(channel.clone())
|
||||||
|
.unwrap_or(("".to_string(), "".to_string()))
|
||||||
|
.1
|
||||||
|
== get_start_pattern(channel.clone())
|
||||||
|
{
|
||||||
|
checking_started = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if temp_stack.last().is_none()
|
||||||
|
|| read_top_of_compare(channel.clone())
|
||||||
|
.unwrap_or(("".to_string(), "".to_string()))
|
||||||
|
.1
|
||||||
|
.len()
|
||||||
|
> temp_stack.last().unwrap_or(&"".to_string()).len()
|
||||||
|
{
|
||||||
|
temp_stack.push(
|
||||||
|
pop_top_of_compare(channel.clone())
|
||||||
|
.unwrap_or(("".to_string(), "".to_string()))
|
||||||
|
.1,
|
||||||
|
);
|
||||||
|
pyramid_size += 1;
|
||||||
|
} else if temp_stack.last().is_some()
|
||||||
|
&& read_top_of_compare(channel.clone())
|
||||||
|
.unwrap_or(("".to_string(), "".to_string()))
|
||||||
|
.1
|
||||||
|
.len()
|
||||||
|
< temp_stack.last().unwrap_or(&"".to_string()).len()
|
||||||
|
{
|
||||||
|
temp_stack.pop();
|
||||||
|
if temp_stack.last().unwrap_or(&"".to_string()).clone()
|
||||||
|
== read_top_of_compare(channel.clone())
|
||||||
|
.unwrap_or(("".to_string(), "".to_string()))
|
||||||
|
.1
|
||||||
|
{
|
||||||
|
temp_stack.pop();
|
||||||
|
|
||||||
|
continue;
|
||||||
|
} else {
|
||||||
|
set_pyramid_size(channel.clone(), 0);
|
||||||
|
temp_stack.clear();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
set_pyramid_size(channel.clone(), 0);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if checking_started
|
||||||
|
&& read_top_of_compare(channel.clone()).unwrap().1 == get_start_pattern(channel.clone())
|
||||||
|
{
|
||||||
|
/* leave pyramid size set for exection */
|
||||||
|
set_pyramid_size(channel.clone(), pyramid_size * 2 - 1);
|
||||||
|
temp_stack.clear();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_pyramid_size(channel: String, size: i32) {
|
||||||
|
let mut size_perchnl = PYRAMID_SIZE_PER_CHNL.lock().unwrap();
|
||||||
|
let mut found = false;
|
||||||
|
for rec in size_perchnl.iter() {
|
||||||
|
if rec.0 == channel {
|
||||||
|
found = true;
|
||||||
|
let mut rec_started = rec.1.lock().unwrap();
|
||||||
|
*rec_started = size;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !found {
|
||||||
|
size_perchnl.push((channel, Mutex::new(size)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_pyramid_size(channel: String) -> i32 {
|
||||||
|
let size_perchnl = PYRAMID_SIZE_PER_CHNL.lock().unwrap();
|
||||||
|
for rec in size_perchnl.iter() {
|
||||||
|
if rec.0 == channel {
|
||||||
|
let rec_started = rec.1.lock().unwrap();
|
||||||
|
return *rec_started;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
0
|
||||||
|
}
|
||||||
|
|
||||||
|
/// #todo
|
||||||
|
///
|
||||||
|
/// pyramid interruptor
|
||||||
|
///
|
||||||
|
/// pick chatters that will be interrupted if they solo build
|
||||||
|
///
|
||||||
|
/// takes in arguments as chatters
|
||||||
|
///
|
||||||
|
/// chatters are then interrupted for a random duration under 15m
|
||||||
|
///
|
||||||
|
/// if a duration is given, take that duration eg 15m , 25m
|
||||||
|
///
|
||||||
|
///
|
||||||
|
fn _create_interruptor_cmd() -> Command {
|
||||||
|
let mut cmd = Command::new(
|
||||||
|
vec!["no pyramid".to_string(), "no pyramids".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(Box::new(move |a, b| Box::pin(execbody(a, b))));
|
||||||
|
|
||||||
|
/* 4. optionally, remove admin only default flag */
|
||||||
|
cmd.set_admin_only(false);
|
||||||
|
|
||||||
|
/* 5. optionally, set min badge*/
|
||||||
|
cmd.set_min_badge(Badge::Moderator);
|
||||||
|
|
||||||
|
cmd
|
||||||
|
}
|
||||||
|
|
||||||
|
/// #todo
|
||||||
|
fn _create_interruptor_module(channel: String) -> Module {
|
||||||
|
/* 1. Create a new module */
|
||||||
|
let modname = format!("interruptor {}", channel);
|
||||||
|
let mut custom_mod = Module::new(vec![modname], "".to_string());
|
||||||
|
|
||||||
|
/* 2. Load the cmd into a new module */
|
||||||
|
custom_mod.load_listener(_create_interruptor_listener());
|
||||||
|
|
||||||
|
custom_mod
|
||||||
|
}
|
||||||
|
|
||||||
|
/// #todo
|
||||||
|
fn _create_interruptor_listener() -> Listener {
|
||||||
|
/* 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));
|
||||||
|
|
||||||
|
listener
|
||||||
|
}
|
||||||
|
|
||||||
|
/// #todo
|
||||||
|
///
|
||||||
|
/// Returns Some(chatter) if the pyramid in progress is being built by a solo
|
||||||
|
fn _solo_building(_channel: String) -> Option<String> {
|
||||||
|
None
|
||||||
|
}
|
234
forcebot_core/src/lib.rs
Normal file
234
forcebot_core/src/lib.rs
Normal file
|
@ -0,0 +1,234 @@
|
||||||
|
//! `forcebot_core` library for `forcebot-rs-v2` Twitch chat bot
|
||||||
|
//!
|
||||||
|
//! Customize by adding additional bot objects
|
||||||
|
//!
|
||||||
|
//! # 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();
|
||||||
|
//!
|
||||||
|
//! /* 2. Run the bot */
|
||||||
|
//! bot.run().await;
|
||||||
|
//!
|
||||||
|
//! }
|
||||||
|
//!
|
||||||
|
//! ```
|
||||||
|
//!
|
||||||
|
//!
|
||||||
|
//! # Customize with 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::{guest_badge, pyramid}, Bot};
|
||||||
|
//!
|
||||||
|
//! #[tokio::main]
|
||||||
|
//! pub async fn main() {
|
||||||
|
//!
|
||||||
|
//! /* 1. Create the bot using env */
|
||||||
|
//! let mut bot = Bot::new();
|
||||||
|
//!
|
||||||
|
//! /* 2. Load Custom Modules */
|
||||||
|
//! bot.load_module(guest_badge::create_module()).await;
|
||||||
|
//! bot.load_module(pyramid::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 mut bot = Bot::new();
|
||||||
|
//!
|
||||||
|
//! /* 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_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.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(execution_async(execbody));
|
||||||
|
//! cmd.set_admin_only(false);
|
||||||
|
//! cmd.set_min_badge(Badge::Moderator);
|
||||||
|
//!
|
||||||
|
//! 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 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); /* 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);
|
||||||
|
//!
|
||||||
|
//! /* 4. Run the bot */
|
||||||
|
//! bot.run().await;
|
||||||
|
//!
|
||||||
|
//! }
|
||||||
|
//!
|
||||||
|
//! ```
|
||||||
|
//!
|
||||||
|
//! # Moderator Reactor
|
||||||
|
//!
|
||||||
|
//! ```
|
||||||
|
//!
|
||||||
|
//! 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 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 {
|
||||||
|
//! 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.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 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);
|
||||||
|
//!
|
||||||
|
//! /* Run the bot */
|
||||||
|
//! bot.run().await;
|
||||||
|
//!
|
||||||
|
//! }
|
||||||
|
|
||||||
|
pub mod botcore;
|
||||||
|
pub mod custom_mods;
|
||||||
|
pub use botcore::bot::Bot;
|
||||||
|
pub use botcore::bot_objects::command_condition_async;
|
||||||
|
pub use botcore::bot_objects::execution_async;
|
||||||
|
pub use botcore::bot_objects::listener::Listener;
|
||||||
|
pub use botcore::bot_objects::listener_condition_async;
|
||||||
|
// pub use crate::botcore::bot_objects::command::Command;
|
||||||
|
pub use botcore::bot_objects::command::Command;
|
||||||
|
pub use botcore::bot_objects::Badge;
|
||||||
|
pub use botcore::modules;
|
||||||
|
pub use botcore::modules::Module;
|
11
moderator_reactor/Cargo.toml
Normal file
11
moderator_reactor/Cargo.toml
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
[package]
|
||||||
|
name = "moderator_reactor"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2021"
|
||||||
|
|
||||||
|
[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"
|
67
moderator_reactor/src/main.rs
Normal file
67
moderator_reactor/src/main.rs
Normal file
|
@ -0,0 +1,67 @@
|
||||||
|
//! 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_core::execution_async;
|
||||||
|
use forcebot_core::Bot;
|
||||||
|
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;
|
||||||
|
}
|
11
new_empty_bot/Cargo.toml
Normal file
11
new_empty_bot/Cargo.toml
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
[package]
|
||||||
|
name = "new_empty_bot"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2021"
|
||||||
|
|
||||||
|
[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"
|
|
@ -1,25 +1,22 @@
|
||||||
//! Example simple Binary crate that creates & runs bot based on `.env`
|
//! Example simple Binary crate that creates & runs bot based on `.env`
|
||||||
//! Be sure the followig is defined in `.env`
|
//! Be sure the followig is defined in `.env`
|
||||||
//! - login_name
|
//! - login_name
|
||||||
//! - access_token
|
//! - access_token
|
||||||
//! - bot_channels
|
//! - bot_channels
|
||||||
//! - prefix
|
//! - prefix
|
||||||
//! - bot_admins
|
//! - bot_admins
|
||||||
//!
|
//!
|
||||||
//! Bot access tokens be generated here -
|
//! Bot access tokens be generated here -
|
||||||
//! - Get a Bot Chat Token here - <https://twitchtokengenerator.com>
|
//! - Get a Bot Chat Token here - <https://twitchtokengenerator.com>
|
||||||
//! - More Info - <https://dev.twitch.tv/docs/authentication>
|
//! - More Info - <https://dev.twitch.tv/docs/authentication>
|
||||||
|
|
||||||
use forcebot_rs_v2::botcore::bot::Bot;
|
use forcebot_core::Bot;
|
||||||
|
|
||||||
|
|
||||||
#[tokio::main]
|
#[tokio::main]
|
||||||
pub async fn main() {
|
pub async fn main() {
|
||||||
|
|
||||||
/* 1. Create the bot using env */
|
/* 1. Create the bot using env */
|
||||||
let bot = Bot::new();
|
let bot = Bot::new().await;
|
||||||
|
|
||||||
/* 2. Run the bot */
|
/* 2. Run the bot */
|
||||||
bot.run().await;
|
bot.run().await;
|
||||||
|
|
||||||
}
|
}
|
239
readme.md
239
readme.md
|
@ -1,4 +1,4 @@
|
||||||
Twitch chat bot written in rust
|
Customizable Twitch chat bot written in rust
|
||||||
|
|
||||||
# Quick Start
|
# Quick Start
|
||||||
|
|
||||||
|
@ -22,28 +22,66 @@ bot_admins=ADMIN
|
||||||
3. Build & run
|
3. Build & run
|
||||||
|
|
||||||
```
|
```
|
||||||
cargo 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
|
# Example Bots
|
||||||
|
|
||||||
Use the following commands to build and run built-in bots. No coding required!
|
Use the following to build and run built-in bots. No coding required!
|
||||||
|
|
||||||
## New Bot
|
## New Empty Bot
|
||||||
Run an empty simple bot that logs into chat and has minimum built in functions
|
Run an empty simple bot that logs into chat and has minimum built in functions
|
||||||
|
|
||||||
```
|
```
|
||||||
cargo run --bin new_bot
|
cargo run -p new_empty_bot
|
||||||
```
|
```
|
||||||
|
|
||||||
## WIP Customized Fun Bot
|
## Full Featured Forcebot
|
||||||
|
|
||||||
Run a forcebot with fun catered customizations
|
Run a forcebot with fun catered customizations
|
||||||
|
|
||||||
*ongoing work in progress*
|
|
||||||
|
|
||||||
```
|
```
|
||||||
cargo run --bin fun_bot
|
cargo run -p forcebot_core
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
|
@ -51,28 +89,69 @@ cargo run --bin fun_bot
|
||||||
Run a bot that listens to all messages and output to console
|
Run a bot that listens to all messages and output to console
|
||||||
|
|
||||||
```
|
```
|
||||||
cargo run --bin simple_debug_listener
|
cargo run -p simple_debug_listener
|
||||||
```
|
```
|
||||||
|
|
||||||
## Simple Command Bot
|
## 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
|
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 simple_command_bot
|
cargo run -p simple_command_bot
|
||||||
```
|
```
|
||||||
|
|
||||||
## Moderator Reactor
|
## Moderator Reactor
|
||||||
Run a bot that listens for messages with the `moderator` badge, and replies to that mod with an emote
|
Run a bot that listens for messages with the `moderator` badge, and replies to that mod with an emote
|
||||||
|
|
||||||
```
|
```
|
||||||
cargo run --bin moderator_reactor
|
cargo run -p moderator_reactor
|
||||||
```
|
```
|
||||||
|
|
||||||
## Module loaded Bot
|
## 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
|
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
|
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
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
|
@ -83,25 +162,55 @@ cargo run --bin simple_module
|
||||||
Uses Env defined variables to create and run the bot
|
Uses Env defined variables to create and run the bot
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
use forcebot_rs_v2::Bot;
|
use forcebot_core::Bot;
|
||||||
|
|
||||||
#[tokio::main]
|
#[tokio::main]
|
||||||
pub async fn main() {
|
pub async fn main() {
|
||||||
|
|
||||||
/* 1. Create the bot using env */
|
/* 1. Create the bot using env */
|
||||||
let bot = Bot::new();
|
let bot = Bot::new().await;
|
||||||
|
|
||||||
/* 2. Run the bot */
|
/* 2. Run the bot */
|
||||||
bot.run().await;
|
bot.run().await;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
## Customize with Modules
|
## 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
|
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 :
|
Create a custom `Module` by :
|
||||||
|
|
||||||
1. Defining Functions that create the Custom Bot Objects (eg `Command`)
|
1. Defining Functions that create the Custom Bot Objects (eg `Command`)
|
||||||
|
@ -110,16 +219,17 @@ Create a custom `Module` by :
|
||||||
|
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
use forcebot_rs_v2::Bot;
|
|
||||||
|
use forcebot_core::Bot;
|
||||||
|
|
||||||
#[tokio::main]
|
#[tokio::main]
|
||||||
pub async fn main() {
|
pub async fn main() {
|
||||||
|
|
||||||
/* Create the bot using env */
|
/* Create the bot using env */
|
||||||
let mut bot = Bot::new();
|
let bot = Bot::new().await;
|
||||||
|
|
||||||
/* load the Module */
|
/* load the Module */
|
||||||
bot.load_module(custom_mod::new());
|
bot.load_module(custom_mod::new()).await;
|
||||||
|
|
||||||
/* Run the bot */
|
/* Run the bot */
|
||||||
bot.run().await;
|
bot.run().await;
|
||||||
|
@ -130,14 +240,16 @@ pub async fn main() {
|
||||||
pub mod custom_mod {
|
pub mod custom_mod {
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
use forcebot_rs_v2::{asyncfn_box, Badge, Bot, Command, Module};
|
use forcebot_core::{execution_async, Badge, Bot, Command, Module};
|
||||||
use twitch_irc::message::ServerMessage;
|
use twitch_irc::message::ServerMessage;
|
||||||
|
|
||||||
|
|
||||||
/// Module with a loaded command
|
/// Module definition with a loaded command
|
||||||
pub fn new() -> Module {
|
pub fn new() -> Module {
|
||||||
/* 1. Create a new module */
|
/* 1. Create a new module */
|
||||||
let mut custom_mod = Module::new("test".to_string(), "".to_string());
|
let mut custom_mod = Module::new(
|
||||||
|
vec!["test".to_string()],
|
||||||
|
"".to_string());
|
||||||
|
|
||||||
/* 2. Load the cmd into a new module */
|
/* 2. Load the cmd into a new module */
|
||||||
custom_mod.load_command(cmd_test());
|
custom_mod.load_command(cmd_test());
|
||||||
|
@ -146,44 +258,45 @@ pub mod custom_mod {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Command definition
|
||||||
pub fn cmd_test() -> Command {
|
pub fn cmd_test() -> Command {
|
||||||
/* 1. Create a new cmd */
|
/* 1. Create a new cmd */
|
||||||
let mut cmd = Command::new("test".to_string(),"".to_string());
|
let mut cmd = Command::new(vec!["test".to_string()],"".to_string());
|
||||||
|
|
||||||
/* 2. Define exec callback */
|
/* 2. Define exec callback */
|
||||||
async fn execbody(bot:Arc<Bot>,message:ServerMessage) -> Result<String,String> {
|
async fn execbody(bot:Arc<Bot>,message:ServerMessage) -> Result<String,String> {
|
||||||
if let ServerMessage::Privmsg(msg) = message {
|
if let ServerMessage::Privmsg(msg) = message {
|
||||||
let _= bot.client.say_in_reply_to(
|
let _= bot.chat.lock().await.say_in_reply_to(
|
||||||
&msg, "test return".to_string()).await;
|
&msg, "test return".to_string()).await;
|
||||||
}
|
}
|
||||||
Result::Err("Not Valid message type".to_string())
|
Result::Err("Not Valid message type".to_string())
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 3. Set Command flags */
|
/* 3. Set Command flags */
|
||||||
cmd.set_exec_fn(asyncfn_box(execbody));
|
cmd.set_exec_fn(execution_async(execbody));
|
||||||
cmd.set_admin_only(false);
|
cmd.set_admin_only(false);
|
||||||
cmd.set_min_badge(Badge::Moderator);
|
cmd.set_min_badge(Badge::Vip);
|
||||||
|
|
||||||
cmd
|
cmd
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
## Simple Debug Listener
|
## Simple Debug Listener
|
||||||
Bot with a simple listener that listens for all messages and prints in output
|
Bot with a simple listener that listens for all messages and prints in output
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
|
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
use forcebot_rs_v2::{asyncfn_box, Bot, Listener};
|
use forcebot_core::{execution_async, Bot, Listener};
|
||||||
use twitch_irc::message::ServerMessage;
|
use twitch_irc::message::ServerMessage;
|
||||||
|
|
||||||
#[tokio::main]
|
#[tokio::main]
|
||||||
pub async fn main() {
|
pub async fn main() {
|
||||||
|
|
||||||
/* 1. Create the bot using env */
|
/* 1. Create the bot using env */
|
||||||
let mut bot = Bot::new();
|
let bot = Bot::new().await;
|
||||||
|
|
||||||
/* 2a. Create a new blank Listener */
|
/* 2a. Create a new blank Listener */
|
||||||
let mut listener = Listener::new();
|
let mut listener = Listener::new();
|
||||||
|
@ -195,15 +308,15 @@ pub async fn main() {
|
||||||
|
|
||||||
/* 2c. Define an async fn callback execution */
|
/* 2c. Define an async fn callback execution */
|
||||||
async fn execbody(_:Arc<Bot>,message:ServerMessage) -> Result<String,String> {
|
async fn execbody(_:Arc<Bot>,message:ServerMessage) -> Result<String,String> {
|
||||||
dbg!(message);
|
dbg!(message); /* outputs message to debug */
|
||||||
Result::Ok("Success".to_string())
|
Result::Ok("Success".to_string())
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 2d. Set and Store the execution body using `async_box()` */
|
/* 2d. Set and Store the execution body using `execution_async()` */
|
||||||
listener.set_exec_fn(asyncfn_box(execbody));
|
listener.set_exec_fn(execution_async(execbody));
|
||||||
|
|
||||||
/* 3. Load the listener into the bot */
|
/* 3. Load the listener into the bot */
|
||||||
bot.load_listener(listener);
|
bot.load_listener(listener).await;
|
||||||
|
|
||||||
/* 4. Run the bot */
|
/* 4. Run the bot */
|
||||||
bot.run().await;
|
bot.run().await;
|
||||||
|
@ -217,11 +330,12 @@ pub async fn main() {
|
||||||
Example listener listens for a moderator badge and reply in chat
|
Example listener listens for a moderator badge and reply in chat
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
|
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
use forcebot_rs_v2::Bot;
|
use forcebot_core::Bot;
|
||||||
use forcebot_rs_v2::asyncfn_box;
|
use forcebot_core::execution_async;
|
||||||
use forcebot_rs_v2::Listener;
|
use forcebot_core::Listener;
|
||||||
use twitch_irc::message::ServerMessage;
|
use twitch_irc::message::ServerMessage;
|
||||||
|
|
||||||
|
|
||||||
|
@ -229,16 +343,16 @@ use twitch_irc::message::ServerMessage;
|
||||||
pub async fn main() {
|
pub async fn main() {
|
||||||
|
|
||||||
/* Create the bot using env */
|
/* Create the bot using env */
|
||||||
let mut bot = Bot::new();
|
let bot = Bot::new().await;
|
||||||
|
|
||||||
/* 1. Create a new blank Listener */
|
/* 1. Create a new blank Listener */
|
||||||
let mut listener = Listener::new();
|
let mut listener = Listener::new();
|
||||||
|
|
||||||
/* 2. Set a trigger condition function for listener */
|
/* 2. Set a trigger condition function for listener */
|
||||||
|
|
||||||
listener.set_trigger_cond_fn(
|
listener.set_trigger_cond_fn(
|
||||||
|_:Arc<Bot>,message:ServerMessage|
|
|_:Arc<Bot>,message:ServerMessage|
|
||||||
if let ServerMessage::Privmsg(msg) = message {
|
if let ServerMessage::Privmsg(msg) = message {
|
||||||
|
|
||||||
for badge in msg.badges {
|
for badge in msg.badges {
|
||||||
if matches!(badge, x if x.name == "moderator") {
|
if matches!(badge, x if x.name == "moderator") {
|
||||||
// dbg!("moderator found");
|
// dbg!("moderator found");
|
||||||
|
@ -252,24 +366,23 @@ pub async fn main() {
|
||||||
/* 3. Define an async fn callback execution */
|
/* 3. Define an async fn callback execution */
|
||||||
async fn execbody(bot:Arc<Bot>,message:ServerMessage) -> Result<String,String> {
|
async fn execbody(bot:Arc<Bot>,message:ServerMessage) -> Result<String,String> {
|
||||||
if let ServerMessage::Privmsg(msg) = message {
|
if let ServerMessage::Privmsg(msg) = message {
|
||||||
let _ = bot.client.say_in_reply_to(&msg, "pepeKneel".to_string()).await ;
|
let _ = bot.chat.lock().await.say_in_reply_to(&msg, "pepeKneel".to_string()).await ;
|
||||||
return Result::Ok("Success".to_string()) ;
|
return Result::Ok("Success".to_string()) ;
|
||||||
}
|
}
|
||||||
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()` */
|
/* 4. Set and Store the execution body using `execution_async()` */
|
||||||
listener.set_exec_fn(asyncfn_box(execbody));
|
listener.set_exec_fn(execution_async(execbody));
|
||||||
|
|
||||||
/* 5. Load the listener into the bot */
|
/* 5. Load the listener into the bot */
|
||||||
bot.load_listener(listener);
|
bot.load_listener(listener).await;
|
||||||
|
|
||||||
/* Run the bot */
|
/* Run the bot */
|
||||||
bot.run().await;
|
bot.run().await;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
## Simple Test Command
|
## Simple Test Command
|
||||||
|
@ -277,9 +390,10 @@ pub async fn main() {
|
||||||
```rust
|
```rust
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
use forcebot_rs_v2::Bot;
|
use forcebot_core::Badge;
|
||||||
use forcebot_rs_v2::asyncfn_box;
|
use forcebot_core::Bot;
|
||||||
use forcebot_rs_v2::Command;
|
use forcebot_core::execution_async;
|
||||||
|
use forcebot_core::Command;
|
||||||
use twitch_irc::message::ServerMessage;
|
use twitch_irc::message::ServerMessage;
|
||||||
|
|
||||||
|
|
||||||
|
@ -287,31 +401,31 @@ use twitch_irc::message::ServerMessage;
|
||||||
pub async fn main() {
|
pub async fn main() {
|
||||||
|
|
||||||
/* Create the bot using env */
|
/* Create the bot using env */
|
||||||
let mut bot = Bot::new();
|
let bot = Bot::new().await;
|
||||||
|
|
||||||
/* 1. Create a new blank cmd */
|
/* 1. Create a new blank cmd */
|
||||||
let mut cmd = Command::new("test".to_string(),"".to_string());
|
let mut cmd = Command::new(vec!["test".to_string()],"".to_string());
|
||||||
|
|
||||||
/* 2. Define an async fn callback execution */
|
/* 2. Define an async fn callback execution */
|
||||||
async fn execbody(bot:Arc<Bot>,message:ServerMessage) -> Result<String,String> {
|
async fn execbody(bot:Arc<Bot>,message:ServerMessage) -> Result<String,String> {
|
||||||
if let ServerMessage::Privmsg(msg) = message {
|
if let ServerMessage::Privmsg(msg) = message {
|
||||||
let _ = bot.client.say_in_reply_to(&msg, String::from("test success")).await;
|
let _ = bot.chat.lock().await.say_in_reply_to(&msg, String::from("test success")).await;
|
||||||
return Result::Ok("Success".to_string()) ;
|
return Result::Ok("Success".to_string()) ;
|
||||||
}
|
}
|
||||||
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()` */
|
/* 3. Set and Store the execution body using `execution_async()` */
|
||||||
cmd.set_exec_fn(asyncfn_box(execbody));
|
cmd.set_exec_fn(execution_async(execbody));
|
||||||
|
|
||||||
/* 4. optionally, remove admin only default flag */
|
/* 4. optionally, remove admin only default flag */
|
||||||
cmd.set_admin_only(false);
|
cmd.set_admin_only(false);
|
||||||
|
|
||||||
/* 5. optionally, set min badge*/
|
/* 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 */
|
/* 6. Load the cmd into the bot */
|
||||||
bot.load_command(cmd);
|
bot.load_command(cmd).await;
|
||||||
|
|
||||||
/* Run the bot */
|
/* Run the bot */
|
||||||
bot.run().await;
|
bot.run().await;
|
||||||
|
@ -320,16 +434,13 @@ pub async fn main() {
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
# Crate Rust Documentation
|
# Crate Rust API Documentation
|
||||||
|
|
||||||
Create Crate documentation
|
Create `forcebot_rs_v2` Rust Crate documentation
|
||||||
|
|
||||||
Clean Build Documentation
|
Documentation - Clean Build & Open in Default Browser
|
||||||
```
|
```
|
||||||
cargo clean && cargo doc
|
cargo clean && cargo doc --open
|
||||||
```
|
```
|
||||||
|
|
||||||
Open Crate Doc
|
|
||||||
```
|
|
||||||
cargo doc --open
|
|
||||||
```
|
|
||||||
|
|
11
simple_command_bot/Cargo.toml
Normal file
11
simple_command_bot/Cargo.toml
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
[package]
|
||||||
|
name = "simple_command_bot"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2021"
|
||||||
|
|
||||||
|
[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"
|
|
@ -1,47 +1,50 @@
|
||||||
//! Bot with custom example commands that responds to caller if allowed
|
//! Bot with custom example commands that responds to caller if allowed
|
||||||
//!
|
//!
|
||||||
//! Commands that are passed a blank prefix will use the bot prefix
|
//! Commands that are passed a blank prefix will use the bot prefix
|
||||||
//!
|
//!
|
||||||
//! Be sure the followig is defined in `.env`
|
//! Be sure the followig is defined in `.env`
|
||||||
//! - login_name
|
//! - login_name
|
||||||
//! - access_token
|
//! - access_token
|
||||||
//! - bot_channels
|
//! - bot_channels
|
||||||
//! - prefix
|
//! - prefix
|
||||||
//! - bot_admins
|
//! - bot_admins
|
||||||
//!
|
//!
|
||||||
//! Bot access tokens be generated here -
|
//! Bot access tokens be generated here -
|
||||||
//! - Get a Bot Chat Token here - <https://twitchtokengenerator.com>
|
//! - Get a Bot Chat Token here - <https://twitchtokengenerator.com>
|
||||||
//! - More Info - <https://dev.twitch.tv/docs/authentication>
|
//! - More Info - <https://dev.twitch.tv/docs/authentication>
|
||||||
|
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
use forcebot_rs_v2::Badge;
|
use forcebot_core::execution_async;
|
||||||
use forcebot_rs_v2::Bot;
|
use forcebot_core::Badge;
|
||||||
use forcebot_rs_v2::asyncfn_box;
|
use forcebot_core::Bot;
|
||||||
use forcebot_rs_v2::Command;
|
use forcebot_core::Command;
|
||||||
use twitch_irc::message::ServerMessage;
|
use twitch_irc::message::ServerMessage;
|
||||||
|
|
||||||
|
|
||||||
#[tokio::main]
|
#[tokio::main]
|
||||||
pub async fn main() {
|
pub async fn main() {
|
||||||
|
|
||||||
/* Create the bot using env */
|
/* Create the bot using env */
|
||||||
let mut bot = Bot::new();
|
let bot = Bot::new().await;
|
||||||
|
|
||||||
/* 1. Create a new blank cmd */
|
/* 1. Create a new blank cmd */
|
||||||
let mut cmd = Command::new("test".to_string(),"".to_string());
|
let mut cmd = Command::new(vec!["test".to_string()], "".to_string());
|
||||||
|
|
||||||
/* 2. Define an async fn callback execution */
|
/* 2. Define an async fn callback execution */
|
||||||
async fn execbody(bot:Arc<Bot>,message:ServerMessage) -> Result<String,String> {
|
async fn execbody(bot: Arc<Bot>, message: ServerMessage) -> Result<String, String> {
|
||||||
if let ServerMessage::Privmsg(msg) = message {
|
if let ServerMessage::Privmsg(msg) = message {
|
||||||
let _ = bot.client.say_in_reply_to(&msg, String::from("test success")).await;
|
let _ = bot
|
||||||
return Result::Ok("Success".to_string()) ;
|
.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())
|
Result::Err("Not Valid message type".to_string())
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 3. Set and Store the execution body using `async_box()` */
|
/* 3. Set and Store the execution body using `execution_async()` */
|
||||||
cmd.set_exec_fn(asyncfn_box(execbody));
|
cmd.set_exec_fn(execution_async(execbody));
|
||||||
|
|
||||||
/* 4. optionally, remove admin only default flag */
|
/* 4. optionally, remove admin only default flag */
|
||||||
cmd.set_admin_only(false);
|
cmd.set_admin_only(false);
|
||||||
|
@ -50,9 +53,8 @@ pub async fn main() {
|
||||||
cmd.set_min_badge(Badge::Moderator);
|
cmd.set_min_badge(Badge::Moderator);
|
||||||
|
|
||||||
/* 6. Load the cmd into the bot */
|
/* 6. Load the cmd into the bot */
|
||||||
bot.load_command(cmd);
|
bot.load_command(cmd).await;
|
||||||
|
|
||||||
/* Run the bot */
|
/* Run the bot */
|
||||||
bot.run().await;
|
bot.run().await;
|
||||||
|
|
||||||
}
|
}
|
11
simple_debug_listener/Cargo.toml
Normal file
11
simple_debug_listener/Cargo.toml
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
[package]
|
||||||
|
name = "simple_debug_listener"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2021"
|
||||||
|
|
||||||
|
[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"
|
|
@ -1,47 +1,43 @@
|
||||||
//! Example simple Binary crate that creates & runs bot based on `.env`
|
//! Example simple Binary crate that creates & runs bot based on `.env`
|
||||||
//! Be sure the followig is defined in `.env`
|
//! Be sure the followig is defined in `.env`
|
||||||
//! - login_name
|
//! - login_name
|
||||||
//! - access_token
|
//! - access_token
|
||||||
//! - bot_channels
|
//! - bot_channels
|
||||||
//! - prefix
|
//! - prefix
|
||||||
//! - bot_admins
|
//! - bot_admins
|
||||||
//!
|
//!
|
||||||
//! Bot access tokens be generated here -
|
//! Bot access tokens be generated here -
|
||||||
//! - Get a Bot Chat Token here - <https://twitchtokengenerator.com>
|
//! - Get a Bot Chat Token here - <https://twitchtokengenerator.com>
|
||||||
//! - More Info - <https://dev.twitch.tv/docs/authentication>
|
//! - More Info - <https://dev.twitch.tv/docs/authentication>
|
||||||
|
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
use forcebot_rs_v2::{asyncfn_box, Bot, Listener};
|
use forcebot_core::{execution_async, Bot, Listener};
|
||||||
use twitch_irc::message::ServerMessage;
|
use twitch_irc::message::ServerMessage;
|
||||||
|
|
||||||
#[tokio::main]
|
#[tokio::main]
|
||||||
pub async fn main() {
|
pub async fn main() {
|
||||||
|
|
||||||
/* 1. Create the bot using env */
|
/* 1. Create the bot using env */
|
||||||
let mut bot = Bot::new();
|
let bot = Bot::new().await;
|
||||||
|
|
||||||
/* 2a. Create a new blank Listener */
|
/* 2a. Create a new blank Listener */
|
||||||
let mut listener = Listener::new();
|
let mut listener = Listener::new();
|
||||||
|
|
||||||
/* 2b. Set a trigger condition function for listener */
|
/* 2b. Set a trigger condition function for listener */
|
||||||
listener.set_trigger_cond_fn(
|
listener.set_trigger_cond_fn(|_: Arc<Bot>, _: ServerMessage| true);
|
||||||
|_:Arc<Bot>,_:ServerMessage| true
|
|
||||||
);
|
|
||||||
|
|
||||||
/* 2c. Define an async fn callback execution */
|
/* 2c. Define an async fn callback execution */
|
||||||
async fn execbody(_:Arc<Bot>,message:ServerMessage) -> Result<String,String> {
|
async fn execbody(_: Arc<Bot>, message: ServerMessage) -> Result<String, String> {
|
||||||
dbg!(message);
|
dbg!(message); /* outputs message to debug */
|
||||||
Result::Ok("Success".to_string())
|
Result::Ok("Success".to_string())
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 2d. Set and Store the execution body using `async_box()` */
|
/* 2d. Set and Store the execution body using `execution_async()` */
|
||||||
listener.set_exec_fn(asyncfn_box(execbody));
|
listener.set_exec_fn(execution_async(execbody));
|
||||||
|
|
||||||
/* 3. Load the listener into the bot */
|
/* 3. Load the listener into the bot */
|
||||||
bot.load_listener(listener);
|
bot.load_listener(listener).await;
|
||||||
|
|
||||||
/* 4. Run the bot */
|
/* 4. Run the bot */
|
||||||
bot.run().await;
|
bot.run().await;
|
||||||
|
|
||||||
}
|
}
|
11
simple_module_example/Cargo.toml
Normal file
11
simple_module_example/Cargo.toml
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
[package]
|
||||||
|
name = "simple_module_example"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2021"
|
||||||
|
|
||||||
|
[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"
|
76
simple_module_example/src/main.rs
Normal file
76
simple_module_example/src/main.rs
Normal file
|
@ -0,0 +1,76 @@
|
||||||
|
//! 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 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
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,70 +0,0 @@
|
||||||
//! 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
|
|
||||||
//! - 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::Bot;
|
|
||||||
|
|
||||||
#[tokio::main]
|
|
||||||
pub async fn main() {
|
|
||||||
|
|
||||||
/* Create the bot using env */
|
|
||||||
let mut bot = Bot::new();
|
|
||||||
|
|
||||||
/* 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, Badge, 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(Badge::Vip);
|
|
||||||
cmd
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,65 +0,0 @@
|
||||||
//! 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::Bot;
|
|
||||||
use forcebot_rs_v2::asyncfn_box;
|
|
||||||
use forcebot_rs_v2::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 {
|
|
||||||
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 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;
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,3 +0,0 @@
|
||||||
pub mod bot;
|
|
||||||
pub mod bot_objects;
|
|
||||||
pub mod modules;
|
|
|
@ -1,238 +0,0 @@
|
||||||
|
|
||||||
|
|
||||||
use tokio::sync::{mpsc::UnboundedReceiver, Mutex};
|
|
||||||
use twitch_irc::{login::StaticLoginCredentials, message::ServerMessage, SecureTCPTransport, TwitchIRCClient};
|
|
||||||
use dotenv::dotenv;
|
|
||||||
use std::{env, sync::Arc};
|
|
||||||
|
|
||||||
use crate::{Command, Listener, Module};
|
|
||||||
|
|
||||||
use super::bot_objects::built_in_objects;
|
|
||||||
|
|
||||||
|
|
||||||
/// Twitch chat bot
|
|
||||||
pub struct Bot
|
|
||||||
{
|
|
||||||
/// Prefix for commands
|
|
||||||
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>,
|
|
||||||
/// modules
|
|
||||||
modules: Vec<Module>,
|
|
||||||
/// channel module status
|
|
||||||
channel_module_status: Mutex<Vec<(String,String,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();
|
|
||||||
|
|
||||||
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:String,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());
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
let mut admins = Vec::new();
|
|
||||||
|
|
||||||
if let Ok(value) = env::var("bot_admins") {
|
|
||||||
for admin in value.split(',') {
|
|
||||||
admins.push(String::from(admin))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
let mut bot = Bot {
|
|
||||||
prefix,
|
|
||||||
incoming_msgs : Mutex::new(incoming_messages),
|
|
||||||
client,
|
|
||||||
botchannels : botchannels_all,
|
|
||||||
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
|
|
||||||
pub async fn run(self) {
|
|
||||||
|
|
||||||
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 a = bot.clone();
|
|
||||||
let mut in_msgs_lock = a.incoming_msgs.lock().await;
|
|
||||||
|
|
||||||
while let Some(message) = in_msgs_lock.recv().await {
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if let ServerMessage::Privmsg(msg) = message.clone() {
|
|
||||||
|
|
||||||
for cmd in &(*bot).commands {
|
|
||||||
|
|
||||||
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);
|
|
||||||
});
|
|
||||||
|
|
||||||
join_handle.await.unwrap();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Loads a `Listener` into the 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()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// 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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
|
@ -1,119 +0,0 @@
|
||||||
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;
|
|
||||||
|
|
||||||
|
|
||||||
/// 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,
|
|
||||||
>;
|
|
||||||
|
|
||||||
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)))
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/// 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, Badge, 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(Badge::Moderator);
|
|
||||||
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(Badge::Moderator);
|
|
||||||
cmd
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,162 +0,0 @@
|
||||||
use std::sync::Arc;
|
|
||||||
|
|
||||||
use twitch_irc::message::{PrivmsgMessage, ServerMessage};
|
|
||||||
|
|
||||||
use crate::{asyncfn_box, botcore::bot::Bot, Badge};
|
|
||||||
|
|
||||||
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 : Badge,
|
|
||||||
admin_only : bool,
|
|
||||||
prefix : String,
|
|
||||||
custom_cond_fn : fn(Arc<Bot>,PrivmsgMessage) -> 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 : Badge::Vip,
|
|
||||||
admin_only : 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>,PrivmsgMessage) -> 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:PrivmsgMessage) -> bool {
|
|
||||||
|
|
||||||
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());
|
|
||||||
} else {
|
|
||||||
prefixed_cmd.push_str(&cmd.prefix);
|
|
||||||
}
|
|
||||||
prefixed_cmd.push_str(&cmd.command);
|
|
||||||
return message.message_text.starts_with(prefixed_cmd.as_str())
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
fn caller_badge_ok(cmd:&Command,_bot:Arc<Bot>,message:PrivmsgMessage) -> bool {
|
|
||||||
for badge in message.badges {
|
|
||||||
|
|
||||||
match cmd.min_badge {
|
|
||||||
Badge::Broadcaster => {
|
|
||||||
if badge.name == "broadcaster" { return true }
|
|
||||||
else { return false }
|
|
||||||
},
|
|
||||||
Badge::Moderator => {
|
|
||||||
match badge.name.as_str() {
|
|
||||||
"moderator" | "broadcaster" => return true,
|
|
||||||
_ => (),
|
|
||||||
}
|
|
||||||
},
|
|
||||||
Badge::Vip => {
|
|
||||||
match badge.name.as_str() {
|
|
||||||
"vip" | "moderator" | "broadcaster" => return true,
|
|
||||||
_ => (),
|
|
||||||
}
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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: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:PrivmsgMessage) -> bool {
|
|
||||||
(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()) &&
|
|
||||||
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
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/// 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:Badge) {
|
|
||||||
self.min_badge = min_badge
|
|
||||||
}
|
|
||||||
|
|
||||||
/// sets admin_only
|
|
||||||
pub fn set_admin_only(&mut self,admin_only:bool) {
|
|
||||||
self.admin_only = admin_only
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,72 +0,0 @@
|
||||||
use std::sync::Arc;
|
|
||||||
|
|
||||||
use twitch_irc::message::ServerMessage;
|
|
||||||
|
|
||||||
use crate::{asyncfn_box, Bot};
|
|
||||||
|
|
||||||
use super::ExecBody;
|
|
||||||
|
|
||||||
/// 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 condition
|
|
||||||
trigger_cond_fn : fn(Arc<Bot>,ServerMessage) -> bool,
|
|
||||||
/// execution body
|
|
||||||
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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,51 +0,0 @@
|
||||||
|
|
||||||
|
|
||||||
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()
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
206
src/lib.rs
206
src/lib.rs
|
@ -1,206 +0,0 @@
|
||||||
//! `forcebot-rs-v2` : Twitch chat bot written in rust.
|
|
||||||
//!
|
|
||||||
//! Customize by adding additional bot objects
|
|
||||||
//!
|
|
||||||
//! ## 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;
|
|
||||||
//!
|
|
||||||
//! }
|
|
||||||
//!
|
|
||||||
//! ```
|
|
||||||
//!
|
|
||||||
//!
|
|
||||||
//! ## Customize with Modules
|
|
||||||
//!
|
|
||||||
//! 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 forcebot_rs_v2::Bot;
|
|
||||||
//!
|
|
||||||
//! #[tokio::main]
|
|
||||||
//! pub async fn main() {
|
|
||||||
//!
|
|
||||||
//! /* Create the bot using env */
|
|
||||||
//! let mut bot = Bot::new();
|
|
||||||
//!
|
|
||||||
//! /* 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
|
|
||||||
//! 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;
|
|
||||||
//! use forcebot_rs_v2::asyncfn_box;
|
|
||||||
//! use forcebot_rs_v2::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 {
|
|
||||||
//! 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 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;
|
|
||||||
pub use crate::botcore::modules::Module;
|
|
||||||
pub use crate::botcore::bot_objects::Badge;
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue