Merge pull request 'dev into main' (#15) from dev into main

Reviewed-on: #15
This commit is contained in:
modulatingforce 2024-02-25 11:31:10 -05:00
commit abae060f45
12 changed files with 4244 additions and 453 deletions

11
.gitignore vendored
View file

@ -14,5 +14,12 @@ target/
/target
# env
.envrc
.env
*.envrc
*.env
# log
*.log
# debug
.vscode/

233
Cargo.lock generated
View file

@ -17,6 +17,30 @@ version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe"
[[package]]
name = "aho-corasick"
version = "1.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b2969dcb958b36655471fc61f7e416fa76033bdd4bfed0678d8fee1e2d07a1f0"
dependencies = [
"memchr",
]
[[package]]
name = "android-tzdata"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0"
[[package]]
name = "android_system_properties"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311"
dependencies = [
"libc",
]
[[package]]
name = "async-trait"
version = "0.1.77"
@ -61,12 +85,29 @@ version = "2.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ed570934406eb16438a4e976b1b4500774099c13b8cb96eec99f620f05090ddf"
[[package]]
name = "bumpalo"
version = "3.14.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7f30e7476521f6f8af1a1c4c0b8cc94f0bee37d91763d0ca2665f299b6cd8aec"
[[package]]
name = "bytes"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a2bd12c1caf447e69cd4528f47f94d203fd2582878ecb9e9465484c4148a8223"
[[package]]
name = "casual_logger"
version = "0.6.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "77d02b2f025328b7f0a232815634c840e206350cf7c8e8fb36ab7095e264f59c"
dependencies = [
"chrono",
"lazy_static",
"regex",
]
[[package]]
name = "cc"
version = "1.0.83"
@ -88,7 +129,12 @@ version = "0.4.34"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5bc015644b92d5890fab7489e49d21f879d5c990186827d42ec511919404f38b"
dependencies = [
"android-tzdata",
"iana-time-zone",
"js-sys",
"num-traits",
"wasm-bindgen",
"windows-targets 0.48.5",
]
[[package]]
@ -151,7 +197,10 @@ checksum = "25cbce373ec4653f1a01a31e8a5e5ec0c622dc27ff9c4e6606eefef5cbbed4a5"
name = "forcebot_rs"
version = "0.1.0"
dependencies = [
"async-trait",
"casual_logger",
"dotenv",
"futures",
"rand",
"tokio",
"twitch-irc",
@ -198,7 +247,67 @@ checksum = "3d6401deb83407ab3da39eba7e33987a73c3df0c82b4bb5813ee871c19c41d48"
dependencies = [
"futures-core",
"futures-sink",
]
[[package]]
name = "futures-core"
version = "0.3.30"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dfc6580bb841c5a68e9ef15c77ccc837b40a7504914d52e47b8b0e9bbda25a1d"
[[package]]
name = "futures-executor"
version = "0.3.30"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a576fc72ae164fca6b9db127eaa9a9dda0d61316034f33a0a0d4eda41f02b01d"
dependencies = [
"futures-core",
"futures-task",
"futures-util",
]
[[package]]
name = "futures-io"
version = "0.3.30"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a44623e20b9681a318efdd71c299b6b222ed6f231972bfe2f224ebad6311f0c1"
[[package]]
name = "futures-macro"
version = "0.3.30"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "futures-sink"
version = "0.3.30"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9fb8e00e87438d937621c1c6269e53f536c14d3fbd6a042bb24879e57d474fb5"
[[package]]
name = "futures-task"
version = "0.3.30"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "38d84fa142264698cdce1a9f9172cf383a0c82de1bddcf3092901442c4097004"
[[package]]
name = "futures-util"
version = "0.3.30"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3d6401deb83407ab3da39eba7e33987a73c3df0c82b4bb5813ee871c19c41d48"
dependencies = [
"futures-channel",
"futures-core",
"futures-io",
"futures-macro",
"futures-sink",
"futures-task",
"memchr",
"pin-project-lite",
"pin-utils",
"slab",
@ -227,6 +336,38 @@ version = "0.3.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bd5256b483761cd23699d0da46cc6fd2ee3be420bbe6d020ae4a091e70b7e9fd"
[[package]]
name = "iana-time-zone"
version = "0.1.60"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e7ffbb5a1b541ea2561f8c41c087286cc091e21e556a4f09a8f6cbf17b69b141"
dependencies = [
"android_system_properties",
"core-foundation-sys",
"iana-time-zone-haiku",
"js-sys",
"wasm-bindgen",
"windows-core",
]
[[package]]
name = "iana-time-zone-haiku"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f"
dependencies = [
"cc",
]
[[package]]
name = "js-sys"
version = "0.3.68"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "406cda4b368d531c842222cf9d2600a9a4acce8d29423695379c6868a143a9ee"
dependencies = [
"wasm-bindgen",
]
[[package]]
name = "lazy_static"
version = "1.4.0"
@ -487,6 +628,35 @@ dependencies = [
"bitflags 1.3.2",
]
[[package]]
name = "regex"
version = "1.10.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b62dbe01f0b06f9d8dc7d49e05a0785f153b00b2c227856282f671e0318c9b15"
dependencies = [
"aho-corasick",
"memchr",
"regex-automata",
"regex-syntax",
]
[[package]]
name = "regex-automata"
version = "0.4.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5bb987efffd3c6d0d8f5f89510bb458559eab11e4f869acb20bf845e016259cd"
dependencies = [
"aho-corasick",
"memchr",
"regex-syntax",
]
[[package]]
name = "regex-syntax"
version = "0.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f"
[[package]]
name = "rustc-demangle"
version = "0.1.23"
@ -756,6 +926,69 @@ version = "0.11.0+wasi-snapshot-preview1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
[[package]]
name = "wasm-bindgen"
version = "0.2.91"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c1e124130aee3fb58c5bdd6b639a0509486b0338acaaae0c84a5124b0f588b7f"
dependencies = [
"cfg-if",
"wasm-bindgen-macro",
]
[[package]]
name = "wasm-bindgen-backend"
version = "0.2.91"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c9e7e1900c352b609c8488ad12639a311045f40a35491fb69ba8c12f758af70b"
dependencies = [
"bumpalo",
"log",
"once_cell",
"proc-macro2",
"quote",
"syn",
"wasm-bindgen-shared",
]
[[package]]
name = "wasm-bindgen-macro"
version = "0.2.91"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b30af9e2d358182b5c7449424f017eba305ed32a7010509ede96cdc4696c46ed"
dependencies = [
"quote",
"wasm-bindgen-macro-support",
]
[[package]]
name = "wasm-bindgen-macro-support"
version = "0.2.91"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "642f325be6301eb8107a83d12a8ac6c1e1c54345a7ef1a9261962dfefda09e66"
dependencies = [
"proc-macro2",
"quote",
"syn",
"wasm-bindgen-backend",
"wasm-bindgen-shared",
]
[[package]]
name = "wasm-bindgen-shared"
version = "0.2.91"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4f186bd2dcf04330886ce82d6f33dd75a7bfcf69ecf5763b89fcde53b6ac9838"
[[package]]
name = "windows-core"
version = "0.52.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9"
dependencies = [
"windows-targets 0.52.0",
]
[[package]]
name = "windows-sys"
version = "0.48.0"

View file

@ -10,3 +10,11 @@ dotenv = "0.15.0"
tokio = { version = "1.33.0", features = ["full"] }
twitch-irc = "5.0.1"
rand = { version = "0.8.5", features = [] }
futures = "0.3"
async-trait = "0.1.77"
casual_logger = "0.6.5"
[lib]
name = "botLib"
path = "src/lib.rs"

View file

@ -1,6 +1,7 @@
pub mod botinstance;
pub mod ratelimiter;
pub mod botmodules;
pub mod identity;
pub mod ratelimiter;
// pub fn init() -> ()
// {

View file

@ -1,157 +1,406 @@
// use futures::lock::Mutex;
use tokio::sync::mpsc::UnboundedReceiver;
use tokio::sync::RwLock;
use twitch_irc::login::StaticLoginCredentials;
use twitch_irc::ClientConfig;
use twitch_irc::SecureTCPTransport;
use twitch_irc::TwitchIRCClient;
use twitch_irc::message::PrivmsgMessage;
use twitch_irc::message::ServerMessage;
use twitch_irc::transport::tcp::TCPTransport;
use twitch_irc::transport::tcp::TLS;
use std::env;
use twitch_irc::ClientConfig;
use twitch_irc::SecureTCPTransport;
use twitch_irc::TwitchIRCClient;
// use std::borrow::Borrow;
use dotenv::dotenv;
use std::borrow::BorrowMut;
use std::boxed;
use std::cell::Ref;
use std::env;
use std::collections::HashMap;
use rand::Rng;
// Important to use tokios Mutex here since std Mutex doesn't work with async functions
use tokio::sync::Mutex;
use crate::core::ratelimiter::RateLimiter;
use crate::core::botmodules::BotAction;
use crate::core::ratelimiter;
use crate::core::ratelimiter::RateLimiter;
use crate::core::botmodules;
use crate::core::botmodules::ModulesManager;
use crate::core::identity::{ChangeResult, IdentityManager, Permissible};
use std::cell::RefCell;
use std::rc::Rc;
use std::sync::Arc;
// use futures::lock::Mutex;
use std::pin::Pin;
//use std::borrow::Borrow;
use core::borrow::Borrow;
// pub type BotAR = Arc<RwLock<BotInstance>>;
use super::botmodules::bot_actions::actions_util::BotAR;
use casual_logger::{Level, Log};
pub mod botlog {
/*
Module intends to add some layers to logging with the module user only requiring to pass :
- String Log message
- Option<String> - Code_Module
- Option<PrivmsgMessage> - this is used to parse out Chatter & Channel into the logs
*/
use casual_logger::{Level, Log};
use twitch_irc::message::PrivmsgMessage;
// trace, debug, info, notice, warn, error, fatal
/*
in main : Log::debug("Checking bot actions", Some("main()".to_string()), None);
in log :
[blalba@timestmp]
debug = "Checking bot actions",
*/
pub fn trace(
in_msg: &str,
in_module: Option<String>,
in_prvmsg: Option<&PrivmsgMessage>,
) -> () {
let (chnl, chatter) = match in_prvmsg {
Some(prvmsg) => {
//Log::trace(&format!("(#{}) {}: {}", prvmsg.channel_login, prvmsg.sender.name, prvmsg.message_text));
(
Some(prvmsg.channel_login.clone()),
Some(prvmsg.sender.name.clone()),
) // <-- Clone fine atm while we're just working with Strings
}
None => (None, None),
};
Log::trace_t(
in_msg,
casual_logger::Table::default() //
.str("Channel", &format!("{:?}", chnl))
.str("Chatter", &format!("{:?}", chatter))
.str("Code_Module", &format!("{:?}", in_module)),
);
}
pub fn debug(
in_msg: &str,
in_module: Option<String>,
in_prvmsg: Option<&PrivmsgMessage>,
) -> () {
let (chnl, chatter) = match in_prvmsg {
Some(prvmsg) => {
//Log::trace(&format!("(#{}) {}: {}", prvmsg.channel_login, prvmsg.sender.name, prvmsg.message_text));
(
Some(prvmsg.channel_login.clone()),
Some(prvmsg.sender.name.clone()),
) // <-- Clone fine atm while we're just working with Strings
}
None => (None, None),
};
Log::debug_t(
in_msg,
casual_logger::Table::default() //
.str("Channel", &format!("{:?}", chnl))
.str("Chatter", &format!("{:?}", chatter))
.str("Code_Module", &format!("{:?}", in_module)),
);
}
pub fn info(in_msg: &str, in_module: Option<String>, in_prvmsg: Option<&PrivmsgMessage>) -> () {
let (chnl, chatter) = match in_prvmsg {
Some(prvmsg) => {
//Log::trace(&format!("(#{}) {}: {}", prvmsg.channel_login, prvmsg.sender.name, prvmsg.message_text));
(
Some(prvmsg.channel_login.clone()),
Some(prvmsg.sender.name.clone()),
) // <-- Clone fine atm while we're just working with Strings
}
None => (None, None),
};
Log::info_t(
in_msg,
casual_logger::Table::default() //
.str("Channel", &format!("{:?}", chnl))
.str("Chatter", &format!("{:?}", chatter))
.str("Code_Module", &format!("{:?}", in_module)),
);
}
pub fn notice(
in_msg: &str,
in_module: Option<String>,
in_prvmsg: Option<&PrivmsgMessage>,
) -> () {
let (chnl, chatter) = match in_prvmsg {
Some(prvmsg) => {
//Log::trace(&format!("(#{}) {}: {}", prvmsg.channel_login, prvmsg.sender.name, prvmsg.message_text));
(
Some(prvmsg.channel_login.clone()),
Some(prvmsg.sender.name.clone()),
) // <-- Clone fine atm while we're just working with Strings
}
None => (None, None),
};
Log::notice_t(
in_msg,
casual_logger::Table::default() //
.str("Channel", &format!("{:?}", chnl))
.str("Chatter", &format!("{:?}", chatter))
.str("Code_Module", &format!("{:?}", in_module)),
);
}
pub fn warn(in_msg: &str, in_module: Option<String>, in_prvmsg: Option<&PrivmsgMessage>) -> () {
let (chnl, chatter) = match in_prvmsg {
Some(prvmsg) => {
//Log::trace(&format!("(#{}) {}: {}", prvmsg.channel_login, prvmsg.sender.name, prvmsg.message_text));
(
Some(prvmsg.channel_login.clone()),
Some(prvmsg.sender.name.clone()),
) // <-- Clone fine atm while we're just working with Strings
}
None => (None, None),
};
Log::warn_t(
in_msg,
casual_logger::Table::default() //
.str("Channel", &format!("{:?}", chnl))
.str("Chatter", &format!("{:?}", chatter))
.str("Code_Module", &format!("{:?}", in_module)),
);
}
pub fn error(
in_msg: &str,
in_module: Option<String>,
in_prvmsg: Option<&PrivmsgMessage>,
) -> () {
let (chnl, chatter) = match in_prvmsg {
Some(prvmsg) => {
//Log::trace(&format!("(#{}) {}: {}", prvmsg.channel_login, prvmsg.sender.name, prvmsg.message_text));
(
Some(prvmsg.channel_login.clone()),
Some(prvmsg.sender.name.clone()),
) // <-- Clone fine atm while we're just working with Strings
}
None => (None, None),
};
Log::error_t(
in_msg,
casual_logger::Table::default() //
.str("Channel", &format!("{:?}", chnl))
.str("Chatter", &format!("{:?}", chatter))
.str("Code_Module", &format!("{:?}", in_module)),
);
}
pub fn fatal<'a>(
in_msg: &'a str,
in_module: Option<String>,
in_prvmsg: Option<&PrivmsgMessage>,
) -> &'a str {
let (chnl, chatter) = match in_prvmsg {
Some(prvmsg) => {
//Log::trace(&format!("(#{}) {}: {}", prvmsg.channel_login, prvmsg.sender.name, prvmsg.message_text));
(
Some(prvmsg.channel_login.clone()),
Some(prvmsg.sender.name.clone()),
) // <-- Clone fine atm while we're just working with Strings
}
None => (None, None),
};
Log::fatal_t(
in_msg,
casual_logger::Table::default() //
.str("Channel", &format!("{:?}", chnl))
.str("Chatter", &format!("{:?}", chatter))
.str("Code_Module", &format!("{:?}", in_module)),
);
in_msg
}
}
#[derive(Debug, PartialEq, Eq, Hash, Clone)]
pub enum ChType {
Channel(String),
}
pub use ChType::Channel;
pub enum ModType {
BotModule(String),
}
pub use ModType::BotModule;
#[derive(Clone)]
pub struct Chat {
pub ratelimiters : HashMap<ChType,RateLimiter>, // used to limit messages sent per channel
pub client : TwitchIRCClient<TCPTransport<TLS>,StaticLoginCredentials>,
pub ratelimiters: Arc<Mutex<HashMap<ChType, RateLimiter>>>, // used to limit messages sent per channel
pub client: TwitchIRCClient<TCPTransport<TLS>, StaticLoginCredentials>,
}
impl Chat {
pub fn init(
ratelimiters: HashMap<ChType, RateLimiter>,
client: TwitchIRCClient<TCPTransport<TLS>, StaticLoginCredentials>,
) -> Chat {
Chat {
ratelimiters: Arc::new(Mutex::new(ratelimiters)),
client: client,
}
}
pub fn init_channel(&mut self, chnl:ChType) -> () {
pub async fn init_channel(&mut self, chnl: ChType) -> () {
let n = RateLimiter::new();
self.ratelimiters.insert(chnl,n);
}
self.ratelimiters.lock().await.insert(chnl, n);
}
pub async fn say_in_reply_to(&mut self, msg:& PrivmsgMessage , mut outmsg:String) -> () {
// pub async fn say_in_reply_to(&mut self, msg:& PrivmsgMessage , mut outmsg:String) -> () {
pub async fn say_in_reply_to(&self, msg: &PrivmsgMessage, mut outmsg: String) -> () {
/*
formats message before sending to TwitchIRC
formats message before sending to TwitchIRC
- [x] Custom String Formatting (e.g., adding random black spaces)
- [x] Ratelimiter Handling
- [ ] Checkf if BotActions is Enabled & Caller is Allowed to Run
*/
// self.client.say_in_reply_to(msg,outmsg).await.unwrap();
// // let contextratelimiter = ratelimiters.get_mut(&msg.channel_login).expect("ERROR: Issue with Rate limiters");
let contextratelimiter = self.ratelimiters
let a = Arc::clone(&self.ratelimiters);
let mut a = a.lock().await;
let contextratelimiter = a
// .get_mut()
.get_mut(&Channel(String::from(&msg.channel_login)))
.expect("ERROR: Issue with Rate limiters");
// let contextratelimiter = self.ratelimiters.get(&msg.channel_login).expect("ERROR: Issue with Rate limiters");
match contextratelimiter.check_limiter() {
ratelimiter::LimiterResp::Allow => {
let maxblanks = rand::thread_rng().gen_range(1..=20);
//let mut outmsg = "GotTrolled ".to_owned();
// let mut outmsg = "annytfLurk ".to_owned();
for _i in 1..maxblanks {
let blankspace: &str = "󠀀";
outmsg.push_str(blankspace);
}
// client.say_in_reply_to(&msg,outmsg).await.unwrap();
self.client.say_in_reply_to(msg,outmsg).await.unwrap();
println!("(#{}) > {}", msg.channel_login, "rate limit counter increase");
self.client.say_in_reply_to(msg, outmsg).await.unwrap();
// println!("(#{}) > {}", msg.channel_login, "rate limit counter increase");
// Log::trace(&format!("(#{}) > {}", msg.channel_login, "rate limit counter increase"));
botlog::trace(
&format!(
"(#{}) > {}",
msg.channel_login, "rate limit counter increase"
),
Some("Chat > say_in_reply_to".to_string()),
Some(&msg),
);
contextratelimiter.increment_counter();
println!("{:?}",self.ratelimiters);
},
// println!("{:?}",self.ratelimiters);
// Log::trace(&format!("{:?}",self.ratelimiters));
botlog::trace(
&format!("{:?}", self.ratelimiters),
Some("Chat > say_in_reply_to".to_string()),
Some(&msg),
);
}
ratelimiter::LimiterResp::Skip => {
(); // do nothing otherwise
}
}
}
Log::flush();
}
async fn say(&self, _:String, _:String) -> () {
// more info https://docs.rs/twitch-irc/latest/twitch_irc/client/struct.TwitchIRCClient.html#method.say
// self.client.say(msg,outmsg).await.unwrap();
}
async fn me(&self, _:String, _:String) -> () {
// more info https://docs.rs/twitch-irc/latest/twitch_irc/client/struct.TwitchIRCClient.html#method.say
// self.client.me(msg,outmsg).await.unwrap();
}
async fn me_in_reply_to(&self, _:String, _:String) -> () {
// more info https://docs.rs/twitch-irc/latest/twitch_irc/client/struct.TwitchIRCClient.html#method.say
// self.client.me(msg,outmsg).await.unwrap();
}
async fn say(&self, _: String, _: String) -> () {
// more info https://docs.rs/twitch-irc/latest/twitch_irc/client/struct.TwitchIRCClient.html#method.say
// self.client.say(msg,outmsg).await.unwrap();
}
async fn me(&self, _: String, _: String) -> () {
// more info https://docs.rs/twitch-irc/latest/twitch_irc/client/struct.TwitchIRCClient.html#method.say
// self.client.me(msg,outmsg).await.unwrap();
}
async fn me_in_reply_to(&self, _: String, _: String) -> () {
// more info https://docs.rs/twitch-irc/latest/twitch_irc/client/struct.TwitchIRCClient.html#method.say
// self.client.me(msg,outmsg).await.unwrap();
}
}
pub struct BotInstance
{
prefix : char,
bot_channel : ChType,
pub incoming_messages : UnboundedReceiver<ServerMessage>,
pub chat : Chat,
pub botmodules : ModulesManager,
twitch_oauth : String,
pub bot_channels : Vec<ChType>,
#[derive(Clone)]
pub struct BotManagers {
// pub botmodules : ModulesManager,
pub identity: Arc<RwLock<IdentityManager>>,
pub chat: Chat,
}
impl BotManagers {
pub fn init(
ratelimiters: HashMap<ChType, RateLimiter>,
client: TwitchIRCClient<TCPTransport<TLS>, StaticLoginCredentials>,
) -> BotManagers {
BotManagers {
identity: Arc::new(RwLock::new(IdentityManager::init())),
chat: Chat::init(ratelimiters, client),
}
}
pub fn rIdentity(self) -> Arc<RwLock<IdentityManager>> {
self.identity
}
}
impl BotInstance
{
pub struct ArcBox<T: Clone>(pub Arc<Mutex<T>>);
impl<T: Clone> ArcBox<T> {
pub fn inst(&self) -> &Mutex<T> {
&self.0
}
}
pub fn init() -> BotInstance
{
//#[derive(Clone)]
// #[derive(Copy)] // <-- Cannot be derived
pub struct BotInstance {
pub prefix: char,
pub bot_channel: ChType,
pub incoming_messages: Arc<RwLock<UnboundedReceiver<ServerMessage>>>,
pub botmodules: Arc<ModulesManager>,
pub twitch_oauth: String,
pub bot_channels: Vec<ChType>,
pub botmgrs: BotManagers,
//modesmgr : ModesManager, // Silent/Quiet , uwu , frisky/horny
}
impl BotInstance {
pub async fn init() -> BotInstance {
dotenv().ok();
let login_name = env::var("login_name").unwrap().to_owned();
let oauth_token = env::var("access_token").unwrap().to_owned();
let prefix = env::var("prefix").unwrap().to_owned().chars().next().expect("ERROR : when defining prefix");
let prefix = env::var("prefix")
.unwrap()
.to_owned()
.chars()
.next()
.expect("ERROR : when defining prefix");
/*
Vector of channels to join
@ -164,12 +413,13 @@ impl BotInstance
botchannels.push(Channel(String::from(chnl)));
}
let config = ClientConfig::new_simple(
StaticLoginCredentials::new(login_name.to_owned(), Some(oauth_token.to_owned()))
);
let config = ClientConfig::new_simple(StaticLoginCredentials::new(
login_name.to_owned(),
Some(oauth_token.to_owned()),
));
let (incoming_messages, client) =
TwitchIRCClient::<SecureTCPTransport, StaticLoginCredentials>::new(config);
TwitchIRCClient::<SecureTCPTransport, StaticLoginCredentials>::new(config);
// hashmap for channels and their associated ratelimiters
let mut ratelimiters = HashMap::new();
@ -179,148 +429,390 @@ impl BotInstance
client.join(chnl.to_owned()).unwrap();
// ratelimiters are a hashmap of channel and a corresponding rate limiter
// ratelimiters are a hashmap of channel and a corresponding rate limiter
let n = RateLimiter::new();
ratelimiters.insert(Channel(String::from(chnl)),n);
ratelimiters.insert(Channel(String::from(chnl)), n);
//self.chat.ratelimiters.insert(Channel(String::from(chnl)),n);
}
// let b = BotInstance {
// prefix : prefix,
// bot_channel : Channel(login_name) ,
// incoming_messages : Arc::new(RwLock::new(incoming_messages)),
// botmodules : ModulesManager::init().await,
// twitch_oauth : oauth_token,
// bot_channels : botchannels,
// botmgrs : BotManagers::init(ratelimiters,client),
// };
BotInstance {
prefix: prefix,
bot_channel: Channel(login_name),
incoming_messages: Arc::new(RwLock::new(incoming_messages)),
botmodules: ModulesManager::init().await,
twitch_oauth: oauth_token,
bot_channels: botchannels,
botmgrs: BotManagers::init(ratelimiters, client),
}
// let bm = &mut ModulesManager::init();
let b = BotInstance {
prefix : prefix,
bot_channel : Channel(login_name) ,
incoming_messages : incoming_messages,
//client : client,
chat : Chat {
ratelimiters : ratelimiters,
client : client,
} ,
botmodules : ModulesManager::init(),
twitch_oauth : oauth_token,
bot_channels : botchannels,
};
println!("{:?}",b.chat.ratelimiters);
b
// b
}
pub async fn runner(mut self) -> () {
pub async fn runner(self) -> () {
let bot = Arc::new(RwLock::new(self));
let join_handle = tokio::spawn(async move {
while let Some(message) = self.incoming_messages.recv().await {
// Below can be used to debug if I want to capture all messages
//println!("Received message: {:?}", message);
let a = bot.read().await;
let mut a = a.incoming_messages.write().await;
while let Some(message) = a.recv().await {
match message {
ServerMessage::Notice(msg) => {
// if let Some(chnl) = msg.channel_login {
// println!("NOTICE : (#{}) {}", chnl, msg.message_text);
// }
match msg.channel_login {
Some(chnl) => println!("NOTICE : (#{}) {}", chnl, msg.message_text),
None => println!("NOTICE : {}", msg.message_text),
match &msg.channel_login {
Some(chnl) => {
// println!("NOTICE : (#{}) {}", chnl, msg.message_text)
// Log::notice(&format!("NOTICE : (#{}) {}", chnl, msg.message_text));
botlog::notice(
&format!("NOTICE : (#{}) {}", chnl, msg.message_text),
Some("BotInstance > runner()".to_string()),
None,
);
}
None => {
// println!("NOTICE : {}", msg.message_text);
// Log::notice(&format!("NOTICE : {}", msg.message_text));
botlog::notice(
&format!("NOTICE : {}", msg.message_text),
Some("BotInstance > runner()".to_string()),
None,
);
}
}
}
ServerMessage::Privmsg(msg) => {
println!("(#{}) {}: {}", msg.channel_login, msg.sender.name, msg.message_text);
// println!("(#{}) {}: {}", msg.channel_login, msg.sender.name, msg.message_text);
// Log::trace(&format!("(#{}) {}: {}", msg.channel_login, msg.sender.name, msg.message_text));
botlog::debug(
&format!(
"Twitch Chat > {} @ #{}: {}",
msg.channel_login, msg.sender.name, msg.message_text
),
Some("BotInstance > runner()".to_string()),
Some(&msg),
);
println!("Privmsg section");
// println!("Privmsg section");
// Log::debug(&format!("Privmsg section"));
botlog::trace(
&format!("Privmsg section"),
Some("BotInstance > runner()".to_string()),
Some(&msg),
);
// b.listener_main_prvmsg(&msg);
self.listener_main_prvmsg(msg).await;
// - BotCommand listener should likely need to be called within the above
},
BotInstance::listener_main_prvmsg(Arc::clone(&bot), &msg).await;
}
ServerMessage::Whisper(msg) => {
println!("(w) {}: {}", msg.sender.name, msg.message_text);
},
// println!("(w) {}: {}", msg.sender.name, msg.message_text);
// Log::trace(&format!("(w) {}: {}", msg.sender.name, msg.message_text));
botlog::trace(
&format!("(w) {}: {}", msg.sender.name, msg.message_text),
Some("BotInstance > runner()".to_string()),
None,
);
}
ServerMessage::Join(msg) => {
println!("JOINED: {}", msg.channel_login);
},
// println!("JOINED: {}", msg.channel_login);
// Log::notice(&format!("JOINED: {}", msg.channel_login));
botlog::notice(
&format!("JOINED: {}", msg.channel_login),
Some("BotInstance > runner()".to_string()),
None,
);
}
ServerMessage::Part(msg) => {
println!("PARTED: {}", msg.channel_login);
},
// println!("PARTED: {}", msg.channel_login);
// Log::notice(&format!("PARTED: {}", msg.channel_login));
botlog::notice(
&format!("PARTED: {}", msg.channel_login),
Some("BotInstance > runner()".to_string()),
None,
);
}
_ => {}
}
};
Log::flush();
}
});
join_handle.await.unwrap();
}
pub fn get_botmodules(self) -> Arc<ModulesManager> {
self.botmodules
}
// -----------------
// PRIVATE FUNCTIONS
pub async fn get_botmgrs(self) -> BotManagers {
let a = self.botmgrs;
a
}
pub fn get_identity(&self) -> Arc<RwLock<IdentityManager>> {
Arc::clone(&self.botmgrs.identity)
}
async fn listener_main_prvmsg(&mut self,msg:PrivmsgMessage) -> () {
pub fn get_prefix(&self) -> char {
(*self).prefix
}
// -----------------
// PRIVATE FUNCTIONS
pub async fn listener_main_prvmsg(bot: BotAR, msg: &PrivmsgMessage) -> () {
// println!(">> Inner listenermain_prvmsg()");
// Log::trace(">> Inner listenermain_prvmsg()");
botlog::trace(
">> Inner listenermain_prvmsg()",
Some("BotInstance > listener_main_prvmsg()".to_string()),
Some(&msg),
);
// let a = a;
// println!("(#{}) {}: {}", msg.channel_login, msg.sender.name, msg.message_text);
// // [ ] Need to run through all Listener Bodies for Enabled Modules for the context of the message (e.g., ModStatus is Enabled in the context for the channel)
for (_m,acts) in &self.botmodules.botactions {
let botlock = bot.read().await;
let hacts = Arc::clone(&botlock.botmodules.botactions);
// let hacts = hacts.read().await;
let a = hacts.read().await;
// println!("hacts size : {}",(*a).len());
// Log::debug(&format!("hacts size : {}",(*a).len()));
botlog::trace(
&format!("hacts size : {}", (*a).len()),
Some("BotInstance > listener_main_prvmsg()".to_string()),
Some(&msg),
);
// println!(">> Inner listenermain_prvmsg() >> before for loop of bot actions");
// Log::trace(">> Inner listenermain_prvmsg() >> before for loop of bot actions");
botlog::trace(
">> Inner listenermain_prvmsg() >> before for loop of bot actions",
Some("BotInstance > listener_main_prvmsg()".to_string()),
Some(&msg),
);
for (_m, acts) in &*hacts.read().await {
// println!(">> Inner listenermain_prvmsg() >> checking bot actions");
// Log::trace(">> Inner listenermain_prvmsg() >> checking bot actions");
botlog::trace(
">> Inner listenermain_prvmsg() >> checking bot actions",
Some("BotInstance > listener_main_prvmsg()".to_string()),
Some(&msg),
);
// let bot = bot;
for a in acts {
// println!(">> Inner listenermain_prvmsg() >> checking bot actions >> 2");
// Log::trace(">> Inner listenermain_prvmsg() >> checking bot actions >> 2");
botlog::trace(
">> Inner listenermain_prvmsg() >> checking bot actions >> 2",
Some("BotInstance > listener_main_prvmsg()".to_string()),
Some(&msg),
);
match a {
let _act = match a {
crate::core::botmodules::BotAction::C(c) => {
/*
/*
BotCommand handling -
- [x] Checks if the input message is a prefix with command name or alias
- [ ] Validate User can run based on identityModule(From_Bot)::can_user_run(
_usr:String,
_channelname:ChType,
_chat_badge:ChatBadge,
_cmdreqroles:Vec<UserRole>)
*/
let inpt = msg.message_text.split("\n").next().expect("ERROR during BotCommand");
// for v in msg.message_text.split(" ") {
// println!("args : {v}");
// }
// println!("Reviewing internal commands");
// Log::trace("Reviewing internal commands");
botlog::trace(
"Reviewing internal commands",
Some("BotInstance > listener_main_prvmsg()".to_string()),
Some(&msg),
);
// let inpt = msg.message_text.split("\n").next().expect("ERROR during BotCommand");
let inpt = msg
.message_text
.split(" ")
.next()
.expect("ERROR during BotCommand");
// [x] Check if a bot command based on ...
// [x] prefix + command
if inpt == self.prefix.to_string() + c.command.as_str() {
c.execute(self.chat.clone(), msg.clone()).await;
}
let mut confirmed_bot_command = false;
let instr = bot.read().await.get_prefix();
if inpt == String::from(instr) + c.command.as_str() {
confirmed_bot_command = true;
}
// [x] prefix + alias
for alias in &c.alias {
if inpt == self.prefix.to_string() + alias.as_str() {
c.execute(self.chat.clone(), msg.clone()).await;
}
let instr = bot.read().await.get_prefix();
if inpt == String::from(instr) + alias.as_str() {
confirmed_bot_command = true;
}
}
},
crate::core::botmodules::BotAction::L(l) => l.execute(self.chat.clone(), msg.clone()).await,
if confirmed_bot_command {
// println!("Confirmed bot command");
// Log::debug("Confirmed bot command");
botlog::debug(
"Confirmed bot command",
Some("BotInstance > listener_main_prvmsg()".to_string()),
Some(&msg),
);
// println!("Going for botlock");
// Log::trace("Going for botlock");
botlog::trace(
"Going for botlock",
Some("BotInstance > listener_main_prvmsg()".to_string()),
Some(&msg),
);
let botlock = bot.read().await;
// println!("Going for identity");
// Log::trace("Going for identity");
botlog::trace(
"Going for identity",
Some("BotInstance > listener_main_prvmsg()".to_string()),
Some(&msg),
);
let id = botlock.get_identity();
let eval = {
let mut id = id.write().await;
// println!("Unlocking identity");
// Log::trace("Unlocking identity");
botlog::trace(
"Unpacking identity",
Some("BotInstance > listener_main_prvmsg()".to_string()),
Some(&msg),
);
let (a, b) =
id.can_user_run_PRVMSG(&msg, c.required_roles.clone()).await;
// // [-] #todo : need ot add functionality around here to do an o7 when a mod has been promoted => Preferring to do this outside the mutex
// if let ChangeResult::Success(b) = b {
// // let b = b.to_lowercase();
// // let b = b.contains(&"Auto Promoted Mod".to_lowercase());
// if b.to_lowercase().contains(&"Auto Promoted Mod".to_lowercase()) {
// let chat =
// }
// }
(a, b)
};
// println!("Checking if permissible");
Log::trace("Checking if permissible");
botlog::trace(
"Checking if permissible",
Some("BotInstance > listener_main_prvmsg()".to_string()),
Some(&msg),
);
let (eval, rolechange) = eval;
if let ChangeResult::Success(b) = rolechange {
if b.to_lowercase()
.contains(&"Auto Promoted Mod".to_lowercase())
{
botlog::notice(
"Assigning Mod UserRole to Mod",
Some("botinstance > listener_main_prvmsg()".to_string()),
Some(&msg),
);
// println!("Read() lock Bot");
// Log::trace("Read() lock Bot");
botlog::trace(
"Read() lock Bot",
Some("BotInstance > listener_main_prvmsg()".to_string()),
Some(&msg),
);
let botlock = bot.read().await;
let outstr =
"o7 a Mod. I kneel to serve! pepeKneel ".to_string();
(*botlock).botmgrs.chat.say_in_reply_to(msg, outstr).await;
}
}
match eval {
Permissible::Allow => {
// println!("Executed as permissible");
// Log::debug("Executed as permissible");
botlog::debug(
"Executed as permissible",
Some("BotInstance > listener_main_prvmsg()".to_string()),
Some(&msg),
);
let a = Arc::clone(&bot);
c.execute(a, msg.clone()).await;
// println!("exit out of execution");
// Log::trace("exit out of execution");
botlog::trace(
"exit out of execution",
Some("BotInstance > listener_main_prvmsg()".to_string()),
Some(&msg),
);
}
Permissible::Block => {
// println!("User Not allowed to run command");
// Log::info("User Not allowed to run command");
botlog::info(
"User Not allowed to run command",
Some("BotInstance > listener_main_prvmsg()".to_string()),
Some(&msg),
);
} // _ => (),
};
}
}
crate::core::botmodules::BotAction::L(l) => {
let a = Arc::clone(&bot);
l.execute(a, msg.clone()).await;
}
_ => (),
}
};
}
};
}
// // [ ] There should be a BotCommand Listener to check for prefixes ran
println!("End of Separate Listener Main prvmsg");
// println!("End of Separate Listener Main prvmsg");
// Log::trace("End of Separate Listener Main prvmsg");
botlog::trace(
"End of Separate Listener Main prvmsg",
Some("BotInstance > listener_main_prvmsg()".to_string()),
Some(&msg),
);
// self
// bot
Log::flush();
}
}
// ======================================
// ======================================
// ======================================
@ -328,13 +820,9 @@ impl BotInstance
// UNIT TEST MODULES
#[cfg(test)]
mod tests {
fn always() {
assert_eq!(1,1);
assert_eq!(1, 1);
}
}

View file

@ -1,25 +1,44 @@
use core::{panic};
use core::panic;
use std::error::Error;
use std::collections::HashMap;
use crate::core::identity;
use std::cell::RefCell;
use std::sync::Arc;
use tokio::sync::RwLock;
use std::future::Future;
// use futures::lock::Mutex;
// Important to use tokios Mutex here since std Mutex doesn't work with async functions
use tokio::sync::Mutex;
use crate::core::botinstance::{self, botlog, BotInstance};
use std::rc::Rc;
// use tokio::sync::RwLock;
use async_trait::async_trait;
use casual_logger::{Level, Log};
/*
ModulesManager is used to manage Modules and BotActions associated with those modules
ModulesManager is used to manage Modules and BotActions associated with those modules
pub struct ModulesManager {
statusdb: HashMap<ModType,Vec<ModStatusType>>,
botactions: HashMap<ModType,Vec<BotAction>>,
botactions: HashMap<ModType,Vec<BotAction>>,
}
- statusdb: HashMap<ModType,Vec<ModStatusType>> - Defines Modules and their ModStatusType (e.g., Enabled at an Instance level, Disabled at a Channel Level)
- botactions: HashMap<ModType,Vec<BotAction>> - Defines Modules and their BotActions (e.g., BotCommand , Listener, Routine)
- botactions: HashMap<ModType,Vec<BotAction>> - Defines Modules and their BotActions (e.g., BotCommand , Listener, Routine)
Example
{
ModulesManager {
statusdb: {BotModule("experiments 004"): [Enabled(Instance)]},
ModulesManager {
statusdb: {BotModule("experiments 004"): [Enabled(Instance)]},
botactions: {BotModule("experiments 004"): [C(BotCommand { module: BotModule("experiments 004"), command: "DUPCMD4", alias: ["DUPALIAS4A", "DUPALIAS4B"], help: "DUPCMD4 tester" })]} }
}
@ -29,20 +48,21 @@ Example
pub enum ModType {
BotModule(String),
}
pub use ModType::BotModule;
#[derive(Debug, PartialEq, Eq, Hash)]
pub enum ChType {
Channel(String),
}
// #[derive(Debug, PartialEq, Eq, Hash, Clone)]
// pub enum ChType {
// Channel(String),
// }
use botinstance::ChType;
pub use ChType::Channel;
use twitch_irc::message::PrivmsgMessage;
pub use ChType::Channel;
use crate::core::botinstance::{self, BotInstance};
use self::bot_actions::actions_util;
use self::bot_actions::actions_util::BotAR;
#[derive(Debug)]
enum StatusLvl {
@ -55,186 +75,225 @@ pub enum ModStatusType {
Enabled(StatusLvl),
Disabled(StatusLvl),
}
pub enum BotAction
{
C(BotCommand),
// #[derive(Clone)]
pub enum BotAction {
C(BotCommand),
L(Listener),
R(Routine),
R(Routine),
}
impl BotAction {
pub async fn execute(&self,m:botinstance::Chat,n:PrivmsgMessage){
pub async fn execute(&self, m: BotAR, n: PrivmsgMessage) -> () {
match self {
BotAction::L(a) => a.execute(m,n).await,
BotAction::C(a) => a.execute(m,n).await,
BotAction::L(a) => a.execute(m, n).await,
BotAction::C(a) => a.execute(m, n).await,
_ => (),
}
}
}
pub trait BotActionTrait
{
fn add_to_bot(self, bot:BotInstance);
fn add_to_modmgr(self,modmgr:&mut ModulesManager);
#[async_trait]
pub trait BotActionTrait {
async fn add_to_bot(self, bot: BotInstance);
async fn add_to_modmgr(self, modmgr: Arc<ModulesManager>);
}
// #[derive(Clone)]
pub struct BotCommand {
pub module : ModType,
pub command : String, // command call name
pub alias : Vec<String>, // String of alternative names
pub module: ModType,
pub command: String, // command call name
pub alias: Vec<String>, // String of alternative names
// bot_prefix : char, // although should be global?
pub exec_body : bot_actions::actions_util::ExecBody,
pub help : String,
pub exec_body: bot_actions::actions_util::ExecBody,
pub help: String,
pub required_roles: Vec<identity::UserRole>,
}
impl BotCommand
{
pub async fn execute(&self,m:botinstance::Chat,n:PrivmsgMessage){
(self.exec_body)(m,n).await;
impl BotCommand {
pub async fn execute(&self, m: BotAR, n: PrivmsgMessage) -> () {
((*self).exec_body)(m, n).await;
}
}
impl BotActionTrait for BotCommand
{
fn add_to_bot(self, mut bot:BotInstance) {
let mgr = &mut bot.botmodules;
self.add_to_modmgr(mgr);
#[async_trait]
impl BotActionTrait for BotCommand {
async fn add_to_bot(self, bot: BotInstance) {
self.add_to_modmgr(bot.botmodules).await;
}
fn add_to_modmgr(self, modmgr:&mut ModulesManager) {
modmgr.add_botaction(self.module.clone(), BotAction::C(self))
// async fn add_to_modmgr(self, modmgr:Arc<Mutex<ModulesManager>>) {
async fn add_to_modmgr(self, modmgr: Arc<ModulesManager>) {
modmgr
.add_botaction(self.module.clone(), BotAction::C(self))
.await
}
}
pub mod bot_actions {
pub mod actions_util {
use std::future::Future;
use std::boxed::Box;
use std::future::Future;
use std::pin::Pin;
use crate::core::botinstance::Chat;
use twitch_irc::message::PrivmsgMessage;
pub type ExecBody = Box<dyn Fn(Chat,PrivmsgMessage) -> Pin<Box<dyn Future<Output=()> + Send>> + Send + Sync>;
use std::rc::Rc;
pub fn asyncbox<T>(f: fn(Chat,PrivmsgMessage) -> T) -> ExecBody
use crate::core::botinstance::{BotInstance, BotManagers, Chat};
use std::cell::RefCell;
use std::sync::Arc;
use twitch_irc::message::PrivmsgMessage;
// use futures::lock::Mutex;
// Important to use tokios Mutex here since std Mutex doesn't work with async functions
use tokio::sync::{Mutex, RwLock};
pub type BotAM = Arc<Mutex<BotInstance>>;
pub type BotAR = Arc<RwLock<BotInstance>>;
pub type ExecBody = Box<
dyn Fn(BotAR, PrivmsgMessage) -> Pin<Box<dyn Future<Output = ()> + Send>> + Send + Sync,
>;
pub fn asyncbox<T>(f: fn(BotAR, PrivmsgMessage) -> T) -> ExecBody
where
T: Future<Output=()> + Send + 'static,
T: Future<Output = ()> + Send + 'static,
{
Box::new(move |a,b| Box::pin(f(a,b)))
Box::new(move |a, b| Box::pin(f(a, b)))
}
}
}
pub struct Listener
{
pub module : ModType,
pub name : String,
pub exec_body : bot_actions::actions_util::ExecBody,
pub help : String
pub struct Listener {
pub module: ModType,
pub name: String,
pub exec_body: bot_actions::actions_util::ExecBody,
pub help: String,
}
impl Listener
{
pub async fn execute(&self,m:botinstance::Chat,n:PrivmsgMessage){
(self.exec_body)(m,n).await;
impl Listener {
pub async fn execute(&self, m: BotAR, n: PrivmsgMessage) -> () {
((*self).exec_body)(m, n).await;
}
}
impl BotActionTrait for Listener
{
fn add_to_bot(self, mut bot:BotInstance) {
let mgr = &mut bot.botmodules;
self.add_to_modmgr(mgr);
#[async_trait]
impl BotActionTrait for Listener {
async fn add_to_bot(self, bot: BotInstance) {
// println!("Adding action to bot");
// Log::trace("Adding action to bot");
botinstance::botlog::trace(
"Adding action to bot",
Some("BotModules > BotActionTrait > add_to_bot()".to_string()),
None,
);
self.add_to_modmgr(bot.botmodules).await;
}
fn add_to_modmgr(self, modmgr:&mut ModulesManager) {
modmgr.add_botaction(self.module.clone(), BotAction::L(self))
}
async fn add_to_modmgr(self, modmgr: Arc<ModulesManager>) {
// let modmgr = *modmgr.lock().await;
// println!("Adding action to module manager");
// Log::trace("Adding action to module manager");
botinstance::botlog::trace(
"Adding action to module manager",
Some("BotModules > BotActionTrait > add_to_bot()".to_string()),
None,
);
modmgr
.add_botaction(self.module.clone(), BotAction::L(self))
.await;
}
}
#[derive(Debug)]
struct Routine {}
pub struct ModulesManager
{
statusdb: HashMap<ModType,Vec<ModStatusType>>,
pub botactions: HashMap<ModType,Vec<BotAction>>,
// #[derive(Clone)]
pub struct ModulesManager {
statusdb: Arc<RwLock<HashMap<ModType, Vec<ModStatusType>>>>,
pub botactions: Arc<RwLock<HashMap<ModType, Vec<BotAction>>>>,
}
impl ModulesManager
{
/*
pub fn init() -> ModulesManager
{
statusdb
<HashMap
<ModType, <-- e.g., BotModule(String::from("experiments001"))
Vec<ModStatusType> <-- shows Enabled/Disabled per Status level
botactions
HashMap<
ModType, <-- e.g., BotModule(String::from("experiments001"))
Vec<BotAction>> BotCommand, Listener
*/
impl ModulesManager {
pub async fn init() -> Arc<ModulesManager> {
let m = HashMap::new();
let act = HashMap::new();
let mut mgr = ModulesManager {
statusdb : m,
botactions : act,
statusdb: Arc::new(RwLock::new(m)),
botactions: Arc::new(RwLock::new(act)),
};
// :: [x] initialize core modules
// initialize custom crate modules
crate::modules::init(&mut mgr);
// println!("ModulesManager > init() > Adding modules");
botlog::debug(
"ModulesManager > init() > Adding modules",
Some("ModulesManager > init()".to_string()),
None,
);
let mgra = Arc::new(mgr);
crate::core::identity::init(Arc::clone(&mgra)).await;
crate::modules::init(Arc::clone(&mgra)).await;
// println!(">> Modules Manager : End of Init");
botlog::trace(
">> Modules Manager : End of Init",
Some("ModulesManager > init()".to_string()),
None,
);
println!(">> Modules Manager : End of Init");
mgra
}
mgr
}
pub fn modstatus(&self, _:ModType, _:ChType) -> ModStatusType {
pub fn modstatus(&self, _: ModType, _: ChType) -> ModStatusType {
// Example usage : botmanager.modstatus(
// BotModule("GambaCore"),
// Channel("modulatingforce")
// )
// - The ModStatusType checks in the context of the given channel ,
// )
// - The ModStatusType checks in the context of the given channel ,
// but also validates based on wheher the module is disabled at a bot instance
// level as well
ModStatusType::Enabled(StatusLvl::Instance)
}
pub fn togglestatus(&self, _:ModType, _:ChType) -> ModStatusType {
pub fn togglestatus(&self, _: ModType, _: ChType) -> ModStatusType {
// enables or disables based on current status
ModStatusType::Enabled(StatusLvl::Instance)
}
pub fn setstatus(&self, _:ModType, _:ModStatusType) -> Result<&str,Box<dyn Error>> {
pub fn setstatus(&self, _: ModType, _: ModStatusType) -> Result<&str, Box<dyn Error>> {
// sets the status based given ModSatusType
// e.g., b.setstatus(BodModule("GambaCore"), Enabled(Channel("modulatingforce"))).expect("ERROR")
Ok("")
}
//pub fn add_botaction(mut self, in_module:ModType, in_action:BotAction ) -> ModulesManager {
// pub fn add_botaction(mut self, in_module:ModType, in_action:BotAction<F> ) -> ModulesManager<F> {
//pub fn add_botaction(&mut self, in_module:ModType, in_action:BotAction ) -> () {
pub fn add_botaction(&mut self, in_module:ModType, in_action:BotAction ) {
pub async fn add_botaction(&self, in_module: ModType, in_action: BotAction) {
// println!("Add botaction called");
botlog::trace(
"Add botaction called",
Some("ModulesManager > init()".to_string()),
None,
);
/*
adds a BotAction to the Modules Manager - This will require a BotModule passed as well
This will including the logic of a valid add
@ -244,7 +303,7 @@ impl ModulesManager
-- In particular to BotCommands, which must have Unique command call names and aliases that to not conflict with any other
already BotCommand added name or alias
Other types might be fine? For example, if 2 modules have their own listeners but each have the name "targetchatter" ,
both would be called separately, even if they both have the same or different logic
both would be called separately, even if they both have the same or different logic
*/
// let newlistener = Listener {
@ -254,20 +313,15 @@ impl ModulesManager
// help : String::from("This will listen and react to sock randomly"),
// };
// As a Demonstration, the listener's Module is added and Enabled at Instance level
// [x] Before Adding, validate the following :
// - If BotAction to Add is a BotCommand , In Module Manager DB (botactions),
// [x] Before Adding, validate the following :
// - If BotAction to Add is a BotCommand , In Module Manager DB (botactions),
// Check All Other BotAction Command Names & Aliases to ensure they don't conflict
fn find_conflict_module(mgr:& ModulesManager, act:& BotAction) -> Option<ModType>
{
async fn find_conflict_module(mgr: &ModulesManager, act: &BotAction) -> Option<ModType> {
// Some(BotModule(String::from("GambaCore")))
// match act {
// BotAction::C(c) => {
// Some(BotModule(String::from("GambaCore")))
@ -276,118 +330,110 @@ impl ModulesManager
// BotAction::R(r) => None,
// }
// if let BotAction::C(incmd) = act {
if let BotAction::C(incmd) = act {
// let n = & mgr.botactions;
// // let n = & mgr.botactions;
let d = mgr.botactions.read().await;
let d = &(*d);
// let d = &mgr.botactions;
for (module, moduleactions) in d {
for modact in moduleactions.iter() {
if let BotAction::C(dbcmd) = &modact {
// At this point, there is an command incmd and looked up dbcmd
// for (module,moduleactions) in d {
// for modact in moduleactions.iter() {
// if let BotAction::C(dbcmd) = &modact {
// // At this point, there is an command incmd and looked up dbcmd
// [x] check if given botcommand c.command:String conflicts with any in botactions
// // [x] check if given botcommand c.command:String conflicts with any in botactions
if incmd.command.to_lowercase() == dbcmd.command.to_lowercase() {
// Returning State - with the identified module
// return Some((module.clone(),BotAction::C(*dbcmd.clone())));
// return Some(incmd); // for some reason I keep getting issues
//return Some(BotModule(String::from("GambaCore"))); // works
return Some(module.clone()); // works
// return Some(dbcmd.clone());
}
// if incmd.command.to_lowercase() == dbcmd.command.to_lowercase() {
// // Returning State - with the identified module
// // return Some((module.clone(),BotAction::C(*dbcmd.clone())));
// // return Some(incmd); // for some reason I keep getting issues
// //return Some(BotModule(String::from("GambaCore"))); // works
// return Some(module.clone()); // works
// // return Some(dbcmd.clone());
// }
for a in &dbcmd.alias {
if incmd.command.to_lowercase() == a.to_lowercase() {
// Returning State - with the identified module
// return Some((module.clone(),BotAction::C(dbcmd)));
return Some(module.clone()); // works
}
}
// for a in &dbcmd.alias {
// if incmd.command.to_lowercase() == a.to_lowercase() {
// // Returning State - with the identified module
// // return Some((module.clone(),BotAction::C(dbcmd)));
// return Some(module.clone()); // works
// [x] Then do the same check except for each c.alias
// }
// }
for inalias in &incmd.alias {
if inalias.to_lowercase() == dbcmd.command.to_lowercase() {
// Returning State - with the identified module
// return Some((module.clone(),BotAction::C(dbcmd)));
return Some(module.clone()); // works
}
for a in &dbcmd.alias {
if inalias.to_lowercase() == a.to_lowercase() {
// Returning State - with the identified module
// return Some((module.clone(),BotAction::C(dbcmd)));
return Some(module.clone()); // works
}
}
}
}
}
}
// // [x] Then do the same check except for each c.alias
// for inalias in &incmd.alias {
// if inalias.to_lowercase() == dbcmd.command.to_lowercase() {
// // Returning State - with the identified module
// // return Some((module.clone(),BotAction::C(dbcmd)));
// return Some(module.clone()); // works
// }
// for a in &dbcmd.alias {
// if inalias.to_lowercase() == a.to_lowercase() {
// // Returning State - with the identified module
// // return Some((module.clone(),BotAction::C(dbcmd)));
// return Some(module.clone()); // works
// }
// }
// }
// }
// }
// }
// // return Some(BotModule(String::from("GambaCore")))
// }
// return Some(BotModule(String::from("GambaCore")))
}
// for all other scenarios (e.g., Listener, Routine), find no conflicts
None
}
// if let probmod = find_conflict_module(&self, &in_action) {
// // () // return because there was a conflict?
// panic!("ERROR: Could not add {:?} ; there was a conflict with existing module {:?}", in_action , probmod );
// }
match find_conflict_module(&self, &in_action) {
match find_conflict_module(&self, &in_action).await {
// Some(c) => panic!("ERROR: Could not add {:?} ; there was a conflict with existing module {:?}", in_action , c ),
Some(c) => panic!("ERROR: Could not add module; there was a conflict with existing module {:?}", c ),
Some(c) => panic!(
"ERROR: Could not add module; there was a conflict with existing module {:?}",
c
),
None => (),
}
let statusvector = self.statusdb
let mut dbt = self.statusdb.write().await;
let statusvector = dbt
// .entry(BotModule(String::from("experiments")))
.entry(in_module.clone())
.or_insert(Vec::new());
statusvector.push(ModStatusType::Enabled(StatusLvl::Instance)); // Pushes the Module as Enabled at Instance Level
let modactions = self.botactions
let mut a = self.botactions.write().await;
let modactions = a
//.entry( BotModule(String::from("experiments")))
.entry( in_module.clone())
.entry(in_module.clone())
.or_insert(Vec::new());
// modactions.push(BotAction::L(newlistener));
modactions.push(in_action);
println!(">> Modules Manager : Called Add bot Action");
//println!(">> Modules Manager : {:?}",&self);
//();
//let mgr = self;
//mgr
//self
// println!(">> Modules Manager : Called Add bot Action");
botlog::trace(
">> Modules Manager : Called Add bot Action",
Some("ModulesManager > init()".to_string()),
None,
);
// println!("add_botaction - botactions size : {}",modactions.len());
botlog::trace(
&format!("add_botaction - botactions size : {}", modactions.len()),
Some("ModulesManager > init()".to_string()),
None,
);
}
fn statuscleanup(&self,_:Option<ChType>) -> () {
// internal cleans up statusdb . For example :
fn statuscleanup(&self, _: Option<ChType>) -> () {
// internal cleans up statusdb . For example :
// - remove redudancies . If we see several Enabled("m"), only keep 1x
// - Clarify Conflict. If we see Enabled("m") and Disabled("m") , we remove Enabled("m") and keep Disabled("m")
// the IDEAL is that this is ran before every read/update operation to ensure quality
@ -395,7 +441,4 @@ impl ModulesManager
// Passing None to chnl may be a heavy operation, as this will review and look at the whole table
()
}
}
}

2900
src/core/identity.rs Normal file

File diff suppressed because it is too large Load diff

View file

@ -1,10 +1,8 @@
use std::time::Instant;
const TIME_THRESHOLD_S: u64 = 30;
const MSG_THRESHOLD: u32 = 20;
#[derive(Debug, Clone)]
pub struct RateLimiter {
timer: Instant,
@ -13,10 +11,9 @@ pub struct RateLimiter {
pub enum LimiterResp {
Allow, // when it's evaluated to be within limits
Skip, // as outside of rate limits
Skip, // as outside of rate limits
}
impl RateLimiter {
pub fn new() -> Self {
Self {
@ -25,24 +22,23 @@ impl RateLimiter {
}
}
pub fn check_limiter(&mut self) -> LimiterResp {
if self.timer.elapsed().as_secs() >= TIME_THRESHOLD_S {
// # [x] elapsed >= TIME_THRESHOLD_S
self.timer = Instant::now();
self.msgcounter = 0;
LimiterResp::Allow
} else if self.msgcounter < MSG_THRESHOLD {
// # [x] elapsed < TIME_THRESHOLD_S && msgcounter < MSG_THRESHOLD
LimiterResp::Allow
// } else if self.msgcounter >= MSG_THRESHOLD {
} else {
// # [x] elapsed < TIME_THRESHOLD_S && msgcounter >= MSG_THRESHOLD
LimiterResp::Skip
pub fn check_limiter(&mut self) -> LimiterResp {
if self.timer.elapsed().as_secs() >= TIME_THRESHOLD_S {
// # [x] elapsed >= TIME_THRESHOLD_S
self.timer = Instant::now();
self.msgcounter = 0;
LimiterResp::Allow
} else if self.msgcounter < MSG_THRESHOLD {
// # [x] elapsed < TIME_THRESHOLD_S && msgcounter < MSG_THRESHOLD
LimiterResp::Allow
// } else if self.msgcounter >= MSG_THRESHOLD {
} else {
// # [x] elapsed < TIME_THRESHOLD_S && msgcounter >= MSG_THRESHOLD
LimiterResp::Skip
}
}
}
pub fn increment_counter(&mut self) -> () {
self.msgcounter += 1;
self.msgcounter += 1;
}
}
}

2
src/lib.rs Normal file
View file

@ -0,0 +1,2 @@
pub mod core;
pub mod modules;

View file

@ -1,16 +1,82 @@
pub mod core;
pub mod modules;
// pub mod core;
// pub mod modules;
//use myLib;
//pub mod lib;
use std::process::Output;
use crate::core::botinstance::BotInstance;
// use crate::core::botinstance::ArcBox;
use botLib::core::botinstance::ArcBox;
use botLib::core::botinstance::{self, BotInstance};
// use core::botinstance::{self,BotInstance};
use casual_logger::Extension;
use std::sync::Arc;
use tokio::sync::RwLock;
pub type BotAR = Arc<RwLock<BotInstance>>;
use casual_logger::{Level, Log};
#[tokio::main]
pub async fn main() {
Log::set_file_ext(Extension::Log);
Log::set_level(Level::Trace);
// Log::set_level(Level::Notice);
let bot = BotInstance::init();
let bot = BotInstance::init().await;
// Log::debug("Checking bot actions");
botinstance::botlog::debug("Checking bot actions", Some("main()".to_string()), None);
let a = Arc::clone(&bot.botmodules.botactions);
let a = a.read().await;
// let a = *a;
for (_, acts) in &*a {
for act in acts {
match act {
botLib::core::botmodules::BotAction::C(b) => {
// println!("bot actiions: {}",b.command)
// Log::info(&format!("bot actions: {}",b.command));
botinstance::botlog::info(
&format!("bot actions: {}", b.command),
Some("main()".to_string()),
None,
);
}
botLib::core::botmodules::BotAction::L(l) => {
// println!("bot actiions: {}",l.name)
// Log::info(&format!("bot actions: {}",l.name));
botinstance::botlog::info(
&format!("bot actions: {}", l.name),
Some("main()".to_string()),
None,
);
}
_ => {
// println!("Not a valid match??")
// Log::info("Not a valid match??");
botinstance::botlog::info(
"Not a valid match??",
Some("main()".to_string()),
None,
);
}
}
}
}
// println!("Starting runner..");
// Log::notice("Starting Bot Runner");
botinstance::botlog::notice("Starting Bot Runner", Some("main()".to_string()), None);
println!("Starting Bot Runner");
Log::flush();
bot.runner().await;
}
// println!("ERROR : EXIT Game loop");
// let msg = Log::fatal("ERROR : EXIT Game loop");
// panic!("{}",Log::fatal("ERROR : EXIT Game loop"));
let a = botinstance::botlog::fatal("ERROR : EXIT Game loop", Some("main()".to_string()), None);
panic!("{}", a);
}

View file

@ -10,27 +10,20 @@ pub use crate::core::botmodules::ModulesManager;
// use crate::core::botinstance;
pub use crate::core::botinstance::BotInstance;
use futures::lock::Mutex;
use std::sync::Arc;
// [ ] Load submodules
mod experiments;
// [ ] init() function that accepts bot instance - this is passed to init() on submodules
// pub fn init<F>(mgr:ModulesManager<F>) -> ModulesManager<F>
// pub fn init<F>(mgr:ModulesManager<F>)
// where
// // F: std::future::Future + Send,
// // F : Send,
// F : Send + ?Sized,
pub fn init(mgr:&mut ModulesManager)
{
pub async fn init(mgr: Arc<ModulesManager>) {
// Modules initializer loads modules into the bot
// this is achieved by calling submodules that also have fn init() defined
experiments::init(mgr)
experiments::init(mgr).await
//();
}
}

View file

@ -1,9 +1,8 @@
/*
Submodules -
- should have definitions of BotAction that will be added to a bit
- therefore, will be defined in modules.rs file
- therefore, will be defined in modules.rs file
- will define one init(&BotInstance) take within the module that will contain :
- BotAction definitions that each call &BotInstance module manager to add itself
@ -14,52 +13,107 @@
use std::future::Future;
use crate::core::botmodules::{ModulesManager,Listener,BotModule,BotActionTrait, BotCommand};
use crate::core::botmodules::bot_actions::actions_util;
use crate::core::botmodules::bot_actions::actions_util::{self, BotAR};
use crate::core::botmodules::{BotActionTrait, BotCommand, BotModule, Listener, ModulesManager};
use crate::core::botinstance::{self};
use crate::core::botinstance::{self, BotInstance, ChType};
use futures::lock::Mutex;
use twitch_irc::message::PrivmsgMessage;
pub fn init(mgr:&mut ModulesManager)
{
use crate::core::identity;
use rand::Rng;
BotCommand {
module : BotModule(String::from("experiments 004")),
command : String::from("test"), // command call name
alias : vec![String::from("tester"),String::from("testy")], // String of alternative names
exec_body : actions_util::asyncbox(testy) ,
help : String::from("DUPCMD4 tester"),
}.add_to_modmgr(mgr);
use std::rc::Rc;
use std::sync::{Arc, RwLock};
let list1 = Listener {
module : BotModule(String::from("experiments 004")),
name : String::from("GoodGirl Listener"),
exec_body : actions_util::asyncbox(good_girl) ,
help : String::from("")
// pub fn init(mgr:&mut ModulesManager)
pub async fn init(mgr: Arc<ModulesManager>) {
// BotCommand {
// module : BotModule(String::from("experiments 004")),
// command : String::from("test1"), // command call name
// alias : vec![String::from("tester1"),String::from("testy1")], // String of alternative names
// exec_body : actions_util::asyncbox(testy) ,
// help : String::from("DUPCMD4 tester"),
// required_roles : vec![
// //identity::UserRole::Mod(ChType::Channel(String::new())),
// identity::UserRole::SupMod(ChType::Channel(String::new()))
// ],
// }.add_to_modmgr(mgr);
let botc1 = BotCommand {
module: BotModule(String::from("experiments001")),
command: String::from("test1"), // command call name
alias: vec![String::from("tester1"), String::from("testy1")], // String of alternative names
exec_body: actions_util::asyncbox(testy),
help: String::from("Test Command tester"),
required_roles: vec![identity::UserRole::BotAdmin],
};
list1.add_to_modmgr(mgr);
botc1.add_to_modmgr(Arc::clone(&mgr)).await;
let list1 = Listener {
module: BotModule(String::from("experiments001")),
name: String::from("GoodGirl Listener"),
exec_body: actions_util::asyncbox(good_girl),
help: String::from(""),
};
list1.add_to_modmgr(Arc::clone(&mgr)).await;
}
async fn good_girl(mut bot: BotAR, msg: PrivmsgMessage) {
// println!("In GoodGirl() Listener");
// Change below from debug to trace if required later
botinstance::botlog::debug(
"In GoodGirl() Listener",
Some("experiments > goodgirl()".to_string()),
Some(&msg),
);
async fn good_girl(mut chat:botinstance::Chat,msg:PrivmsgMessage)
{
println!("In GoodGirl()");
//println!("(#{}) {}: {}", msg.channel_login, msg.sender.name, msg.message_text);
if msg.sender.name == "ModulatingForce" && msg.message_text.contains("GoodGirl") {
chat.say_in_reply_to(&msg,String::from("GoodGirl")).await;
}
// [ ] Uses gen_ratio() to output bool based on a ratio probability .
// - For example gen_ratio(2,3) is 2 out of 3 or 0.67% (numerator,denomitator)
// - More Info : https://rust-random.github.io/rand/rand/trait.Rng.html#method.gen_ratio
if msg.sender.name.to_lowercase() == "ModulatingForce".to_lowercase()
|| msg.sender.name.to_lowercase() == "mzNToRi".to_lowercase()
// && msg.message_text.contains("GoodGirl")
{
// chat.say_in_reply_to(&msg,String::from("GoodGirl")).await;
//if rng.gen_ratio(1,5) {
// println!("In GoodGirl() > Pausechamp");
botinstance::botlog::debug(
"In GoodGirl() > Pausechamp",
Some("experiments > goodgirl()".to_string()),
Some(&msg),
);
let rollwin = rand::thread_rng().gen_ratio(1, 8);
if rollwin {
// println!("In GoodGirl() > Win");
botinstance::botlog::debug(
"In GoodGirl() > Win",
Some("experiments > goodgirl()".to_string()),
Some(&msg),
);
let a = Arc::clone(&bot);
let botlock = a.read().await;
botlock
.botmgrs
.chat
.say_in_reply_to(&msg, String::from("GoodGirl xdd "))
.await;
}
}
}
async fn testy(mut _chat:botinstance::Chat,_msg:PrivmsgMessage)
{
println!("testy triggered!")
}
async fn testy(mut _chat: BotAR, msg: PrivmsgMessage) {
println!("testy triggered!"); // NOTE : This test function intends to print (e.g., to stdout) at fn call
botinstance::botlog::debug(
"testy triggered!",
Some("experiments > testy()".to_string()),
Some(&msg),
);
}