forcebot_rs/src/core/botinstance.rs

595 lines
19 KiB
Rust
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// 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::borrow::Borrow;
use std::borrow::BorrowMut;
use std::boxed;
use std::cell::Ref;
use std::env;
use dotenv::dotenv;
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::botmodules::BotAction;
use crate::core::botmodules::BotAction;
use crate::core::ratelimiter::RateLimiter;
use crate::core::ratelimiter;
use crate::core::botmodules;
use crate::core::botmodules::ModulesManager;
use crate::core::identity::{IdentityManager,Permissible,ChangeResult};
use std::rc::Rc;
use std::cell::RefCell;
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
fn trace(in_msg:&str,module:Option<String>,prvmsg:Option<PrivmsgMessage>,) -> () {
}
fn debug(prvmsg:Option<PrivmsgMessage>,in_msg:&str) -> () {
}
fn info(prvmsg:Option<PrivmsgMessage>,in_msg:&str) -> () {
}
fn notice(prvmsg:Option<PrivmsgMessage>,in_msg:&str) -> () {
}
fn warn(prvmsg:Option<PrivmsgMessage>,in_msg:&str) -> () {
}
fn error(prvmsg:Option<PrivmsgMessage>,in_msg:&str) -> () {
}
}
#[derive(Debug, PartialEq, Eq, Hash, Clone)]
pub enum ChType {
Channel(String),
}
pub use ChType::Channel;
#[derive(Clone)]
pub struct Chat {
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 async fn init_channel(&mut self, chnl:ChType) -> () {
let n = RateLimiter::new();
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(&self, msg:& PrivmsgMessage , mut outmsg:String) -> () {
/*
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
*/
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");
match contextratelimiter.check_limiter() {
ratelimiter::LimiterResp::Allow => {
let maxblanks = rand::thread_rng().gen_range(1..=20);
for _i in 1..maxblanks {
let blankspace: &str = "󠀀";
outmsg.push_str(blankspace);
}
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"));
contextratelimiter.increment_counter();
// println!("{:?}",self.ratelimiters);
Log::trace(&format!("{:?}",self.ratelimiters));
},
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();
}
}
#[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
}
}
pub struct ArcBox<T: Clone>(pub Arc<Mutex<T>>);
impl<T: Clone> ArcBox<T>{
pub fn inst(&self) -> &Mutex<T> {
&self.0
}
}
//#[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,
}
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");
/*
Vector of channels to join
*/
let mut botchannels = Vec::new();
for chnl in env::var("bot_channels").unwrap().split(',') {
// println!("(Env Var # {})",chnl);
botchannels.push(Channel(String::from(chnl)));
}
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);
// hashmap for channels and their associated ratelimiters
let mut ratelimiters = HashMap::new();
for Channel(chnl) in &botchannels {
// For each channel in botchannels
client.join(chnl.to_owned()).unwrap();
// ratelimiters are a hashmap of channel and a corresponding rate limiter
let n = RateLimiter::new();
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),
};
b
}
pub async fn runner(self) -> () {
let bot = Arc::new(RwLock::new(self));
let join_handle = tokio::spawn(async move {
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) => {
match &msg.channel_login {
Some(chnl) => {
// println!("NOTICE : (#{}) {}", chnl, msg.message_text)
Log::notice(&format!("NOTICE : (#{}) {}", chnl, msg.message_text));
},
None => {
// println!("NOTICE : {}", msg.message_text);
Log::notice(&format!("NOTICE : {}", msg.message_text));
},
}
}
ServerMessage::Privmsg(msg) => {
// println!("(#{}) {}: {}", msg.channel_login, msg.sender.name, msg.message_text);
Log::trace(&format!("(#{}) {}: {}", msg.channel_login, msg.sender.name, msg.message_text));
// println!("Privmsg section");
Log::debug(&format!("Privmsg section"));
BotInstance::listener_main_prvmsg(Arc::clone(&bot), &msg).await;
},
ServerMessage::Whisper(msg) => {
// println!("(w) {}: {}", msg.sender.name, msg.message_text);
Log::trace(&format!("(w) {}: {}", msg.sender.name, msg.message_text));
},
ServerMessage::Join(msg) => {
// println!("JOINED: {}", msg.channel_login);
Log::notice(&format!("JOINED: {}", msg.channel_login));
},
ServerMessage::Part(msg) => {
// println!("PARTED: {}", msg.channel_login);
Log::notice(&format!("PARTED: {}", msg.channel_login));
},
_ => {}
};
Log::flush();
}
});
join_handle.await.unwrap();
}
pub fn get_botmodules(self) -> Arc<ModulesManager> {
self.botmodules
}
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)
}
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()");
// 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)
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()));
// println!(">> Inner listenermain_prvmsg() >> before for loop of bot actions");
Log::trace(">> Inner listenermain_prvmsg() >> before for loop of bot actions");
for (_m,acts) in &*hacts.read().await {
// println!(">> Inner listenermain_prvmsg() >> checking bot actions");
Log::trace(">> Inner listenermain_prvmsg() >> checking bot actions");
// let bot = bot;
for a in acts {
// println!(">> Inner listenermain_prvmsg() >> checking bot actions >> 2");
Log::trace(">> Inner listenermain_prvmsg() >> checking bot actions >> 2");
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>)
*/
// for v in msg.message_text.split(" ") {
// println!("args : {v}");
// }
// println!("Reviewing internal commands");
Log::trace("Reviewing internal commands");
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
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 {
let instr = bot.read().await.get_prefix();
if inpt == String::from(instr) + alias.as_str() {
confirmed_bot_command = true;
}
}
if confirmed_bot_command {
// println!("Confirmed bot command");
Log::debug("Confirmed bot command");
// println!("Going for botlock");
Log::trace("Going for botlock");
let botlock = bot.read().await;
// println!("Going for identity");
Log::trace("Going for identity");
let id = botlock.get_identity();
let eval = {
let mut id = id.write().await;
// println!("Unlocking identity");
Log::trace("Unlocking identity");
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");
let (eval , rolechange) = eval;
if let ChangeResult::Success(b) = rolechange {
if b.to_lowercase().contains(&"Auto Promoted Mod".to_lowercase()) {
// println!("Read() lock Bot");
Log::trace("Read() lock Bot");
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");
let a = Arc::clone(&bot);
c.execute(a, msg.clone()).await;
// println!("exit out of execution");
Log::trace("exit out of execution");
}
Permissible::Block => {
// println!("User Not allowed to run command");
Log::info("User Not allowed to run command");
},
// _ => (),
};
};
}
},
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");
Log::trace("End of Separate Listener Main prvmsg");
// self
// bot
Log::flush();
}
}
// ======================================
// ======================================
// ======================================
// ======================================
// UNIT TEST MODULES
#[cfg(test)]
mod tests {
fn always() {
assert_eq!(1,1);
}
}