3120 lines
107 KiB
Rust
3120 lines
107 KiB
Rust
/*
|
|
|
|
ModulesManager is used to manage Modules and BotActions associated with those modules
|
|
|
|
pub struct ModulesManager {
|
|
statusdb: HashMap<BotModule,Vec<ModStatusType>>,
|
|
botactions: HashMap<BotModule,Vec<BotAction>>,
|
|
}
|
|
|
|
- statusdb: HashMap<BotModule,Vec<ModStatusType>> - Defines Modules and their ModStatusType (e.g., Enabled at an Instance level, Disabled at a Channel Level)
|
|
- botactions: HashMap<BotModule,Vec<BotAction>> - Defines Modules and their BotActions (e.g., BotCommand , Listener, Routine)
|
|
|
|
Example
|
|
{
|
|
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" })]} }
|
|
}
|
|
|
|
*/
|
|
|
|
|
|
const OF_CMD_CHANNEL:Channel = Channel(String::new());
|
|
|
|
|
|
use core::panic;
|
|
|
|
use std::borrow::Borrow;
|
|
use std::borrow::BorrowMut;
|
|
use std::collections::HashMap;
|
|
use std::ops::DerefMut;
|
|
use std::sync::Arc;
|
|
|
|
use casual_logger::Log;
|
|
|
|
use chrono::DateTime;
|
|
// use chrono::Duration;
|
|
use chrono::Local;
|
|
use chrono::OutOfRangeError;
|
|
use tokio::sync::RwLock;
|
|
|
|
use async_trait::async_trait;
|
|
use tokio::task::JoinHandle;
|
|
|
|
use tokio::time::Instant;
|
|
use tokio::time::{sleep, Duration};
|
|
|
|
use crate::core::bot_actions::actions_util;
|
|
use crate::core::bot_actions::ExecBodyParams;
|
|
use crate::core::botinstance::{BotInstance, Channel,ChangeResult};
|
|
use crate::core::botlog;
|
|
use crate::core::identity::{self, Permissible,IdentityManager};
|
|
|
|
use crate::core::bot_actions;
|
|
|
|
use std::hash::{Hash, Hasher};
|
|
|
|
use super::bot_actions::ActAR;
|
|
use super::bot_actions::RoutineAR;
|
|
use super::identity::ChatBadge;
|
|
|
|
|
|
pub async fn init(mgr: Arc<ModulesManager>) {
|
|
|
|
// 1. Define the BotAction
|
|
let botc1 = BotCommand {
|
|
module: BotModule(String::from("core")),
|
|
command: String::from("enable"), // command call name
|
|
alias: vec![
|
|
String::from("e"),
|
|
String::from("en")], // String of alternative names
|
|
exec_body: actions_util::asyncbox(cmd_enable),
|
|
help: String::from("Test Command tester"),
|
|
required_roles: vec![
|
|
identity::UserRole::BotAdmin,
|
|
identity::UserRole::Mod(OF_CMD_CHANNEL),
|
|
identity::UserRole::SupMod(OF_CMD_CHANNEL),
|
|
identity::UserRole::Broadcaster,
|
|
],
|
|
};
|
|
|
|
// 2. Add the BotAction to ModulesManager
|
|
botc1.add_core_to_modmgr(Arc::clone(&mgr)).await;
|
|
|
|
async fn cmd_enable(params : ExecBodyParams) {
|
|
/*
|
|
There should be additional validation checks
|
|
- BotAdmins can only run instance level (-i) enables
|
|
- If BotAdmins need to enable/disable at Channel level, they must Promote themselves to be a Mod at least
|
|
- Other Special Roles (Mod,SupMod,Broadcaster) can run without issues to enable the module at Channel Level
|
|
*/
|
|
|
|
/*
|
|
enable -i <module> // enables at Instance
|
|
enable <module> // enables at Channel
|
|
*/
|
|
|
|
/*
|
|
|
|
1. Parse out Message Arguments
|
|
|
|
exec_enable()
|
|
|
|
2. Get Special Roles of CmdSender
|
|
3. If CmdSender is BotAdmin but not (Mod,SupMod,Broadcaster)
|
|
3a. , and is not -i (to instance) , return a Failure recommending BotAdmin promote themselves first
|
|
3b. , and is -i (to instance) , return a Success
|
|
4. If CmdSender not a BotAdmin but is (Mod,SupMod,Broadcaster)
|
|
4a. , and is not -i (to instance) , return a Success
|
|
4b. , and is -i (to instance) , return a Failure they are not allowed
|
|
5. If CmdSender is (Mod,SupMod,Broadcaster) and a BotAdmin
|
|
5a. , and is not -i (to instance) , return a Success
|
|
5b. , and is -i (to instance) , return a Success
|
|
|
|
*/
|
|
|
|
|
|
// [x] Unwraps arguments from message
|
|
|
|
let (arg1, arg2) = {
|
|
|
|
let mut argv = params.msg.message_text.split(' ');
|
|
|
|
argv.next(); // Skip the command name
|
|
|
|
let arg1 = argv.next();
|
|
|
|
let arg2 = argv.next();
|
|
|
|
(arg1, arg2)
|
|
};
|
|
|
|
|
|
/* -- Related function to call later
|
|
exec_enable(
|
|
&self,
|
|
requestor: String,
|
|
requestor_badge: Option<ChatBadge>,
|
|
trg_module: BotModule,
|
|
// channel: Option<Channel>,
|
|
trg_level: StatusLvl,
|
|
bot: BotAR,
|
|
) -> ChangeResult
|
|
*/
|
|
|
|
|
|
// [x] requestor: String,
|
|
let requestor = params.msg.clone().sender.name;
|
|
|
|
|
|
// [x] requestor_badge: Option<ChatBadge>,
|
|
|
|
let mut requestor_badge_mut: Option<ChatBadge> = None;
|
|
|
|
for b in ¶ms.msg.badges {
|
|
if b.name == "moderator" {
|
|
requestor_badge_mut = Some(ChatBadge::Mod);
|
|
} else if b.name == "broadcaster" {
|
|
requestor_badge_mut = Some(ChatBadge::Broadcaster);
|
|
} else if b.name == "vip" {
|
|
requestor_badge_mut = Some(ChatBadge::VIP);
|
|
}
|
|
}
|
|
|
|
let requestor_badge = requestor_badge_mut;
|
|
|
|
|
|
// [x] trg_module: BotModule,
|
|
// - [x] Need to validate an actual BotModule - otherwise, fail or exit the cmd
|
|
|
|
let trg_module = if (arg1 == Some("-i")) || (arg1 == Some("-f")) { arg2 } else { arg1 };
|
|
|
|
// if no trg_module was passed
|
|
// if let None = trg_module {
|
|
if trg_module.is_none() {
|
|
|
|
// let botlock = params.bot.read().await;
|
|
|
|
let outmsg = "uuh You need to pass a module";
|
|
|
|
botlog::debug(
|
|
outmsg,
|
|
Some("botmodules.rs > cmd_enable()".to_string()),
|
|
Some(¶ms.msg),
|
|
);
|
|
|
|
// We should call a notification around here
|
|
|
|
let bot = params.clone().bot;
|
|
|
|
let botclone = Arc::clone(&bot);
|
|
let botlock = botclone.read().await;
|
|
|
|
botlock.botmgrs.chat.send_botmsg(super::chat::BotMsgType::Notif(
|
|
outmsg.to_string()
|
|
),
|
|
params.clone(),
|
|
).await;
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
// [x] trg_level: StatusLvl,
|
|
|
|
let currchnl = params.msg.channel_login.to_lowercase();
|
|
|
|
let trg_level =
|
|
if arg1 == Some("-i") || arg1 == Some("-f") { StatusLvl::Instance }
|
|
else { StatusLvl::Ch(Channel(currchnl)) }
|
|
;
|
|
|
|
|
|
|
|
let botlock = params.bot.read().await;
|
|
let modmgr = Arc::clone(&botlock.botmodules);
|
|
let id = botlock.get_identity();
|
|
|
|
let rslt = modmgr.exec_enable(
|
|
requestor,
|
|
requestor_badge,
|
|
BotModule(trg_module.unwrap().to_string()),
|
|
trg_level,
|
|
id).await;
|
|
|
|
|
|
// We should call a notification around here
|
|
|
|
|
|
let outmsg = match rslt.clone() {
|
|
ChangeResult::Failed(a) => format!("Stare Failed : {}",a),
|
|
ChangeResult::NoChange(a) => format!("Hmm No Change : {}",a),
|
|
ChangeResult::Success(a) => format!("YAAY Success : {}",a),
|
|
};
|
|
|
|
botlock.botmgrs.chat.send_botmsg(super::chat::BotMsgType::Notif(
|
|
outmsg.to_string()
|
|
),
|
|
params.clone(),
|
|
).await;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
// 1. Define the BotAction
|
|
let botc1 = BotCommand {
|
|
module: BotModule(String::from("core")),
|
|
command: String::from("disable"), // command call name
|
|
alias: vec![
|
|
String::from("d")], // String of alternative names
|
|
exec_body: actions_util::asyncbox(cmd_disable),
|
|
help: String::from("Test Command tester"),
|
|
required_roles: vec![
|
|
identity::UserRole::BotAdmin,
|
|
identity::UserRole::Mod(OF_CMD_CHANNEL),
|
|
identity::UserRole::SupMod(OF_CMD_CHANNEL),
|
|
identity::UserRole::Broadcaster,
|
|
],
|
|
};
|
|
|
|
// 2. Add the BotAction to ModulesManager
|
|
botc1.add_core_to_modmgr(Arc::clone(&mgr)).await;
|
|
|
|
async fn cmd_disable(params : ExecBodyParams) {
|
|
/*
|
|
There should be additional validation checks
|
|
- BotAdmins can only run instance level (-i) disables and (-f) force disable
|
|
- If BotAdmins need to enable/disable at Channel level, they must Promote themselves to be a Mod at least
|
|
- Other Special Roles (Mod,SupMod,Broadcaster) can run without issues to disable the module at Channel Level
|
|
*/
|
|
|
|
/*
|
|
disable -i <module> // disables at Instance
|
|
disable <module> // disables at Channel
|
|
disable -f <module> // force disables (instance and enabled are removed)
|
|
*/
|
|
|
|
/*
|
|
|
|
1. If CmdSender is BotAdmin but not (Mod,SupMod,Broadcaster)
|
|
1. can_user_run for cmdreqRoles including BotAdmin & not can_user_run for cmdreqRoles (Mod,SupMod,Broadcaster)
|
|
1a. , and has no special flags (-i / -f) , return a Failure recommending BotAdmin promote themselves first
|
|
1b. , and is -i (to instance) , return a Success
|
|
1c. , and is -f (forced) , return a Success
|
|
|
|
2. If CmdSender not a BotAdmin but is (Mod,SupMod,Broadcaster)
|
|
2. not can_user_run for cmdreqRoles including BotAdmin & can_user_run for cmdreqRoles (Mod,SupMod,Broadcaster)
|
|
2a. , and has no special flags (-i / -f) , return a Success
|
|
2b. , and is -i (to instance) , return a Failure they are not allowed
|
|
2c. , and is -f (forced) , return a Failure they are not allowed
|
|
|
|
3. If CmdSender is (Mod,SupMod,Broadcaster) and a BotAdmin
|
|
3. can_user_run for cmdreqRoles (Mod,SupMod,Broadcaster) & can_user_run for cmdreqRoles including BotAdmin
|
|
3a. , and has no special flags (-i / -f) , return a Success
|
|
3b. , and is -i (to instance) , return a Success
|
|
3c. , and is -f (forced) , return a Success
|
|
*/
|
|
|
|
|
|
// [x] Unwraps arguments from message
|
|
|
|
let (arg1, arg2) = {
|
|
|
|
let mut argv = params.msg.message_text.split(' ');
|
|
|
|
argv.next(); // Skip the command name
|
|
|
|
let arg1 = argv.next();
|
|
|
|
let arg2 = argv.next();
|
|
|
|
(arg1, arg2)
|
|
};
|
|
|
|
|
|
/* -- Related function to call later
|
|
exec_disable(
|
|
&self,
|
|
requestor: String,
|
|
requestor_badge: Option<ChatBadge>,
|
|
trg_module: BotModule,
|
|
// channel: Option<Channel>,
|
|
trg_level: StatusLvl,
|
|
force: bool,
|
|
// bot: BotAR,
|
|
id: Arc<RwLock<IdentityManager>>,
|
|
) -> ChangeResult
|
|
*/
|
|
|
|
|
|
// [x] requestor: String,
|
|
let requestor = params.msg.clone().sender.name;
|
|
|
|
|
|
// [x] requestor_badge: Option<ChatBadge>,
|
|
|
|
let mut requestor_badge_mut: Option<ChatBadge> = None;
|
|
|
|
for b in ¶ms.msg.badges {
|
|
if b.name == "moderator" {
|
|
requestor_badge_mut = Some(ChatBadge::Mod);
|
|
} else if b.name == "broadcaster" {
|
|
requestor_badge_mut = Some(ChatBadge::Broadcaster);
|
|
} else if b.name == "vip" {
|
|
requestor_badge_mut = Some(ChatBadge::VIP);
|
|
}
|
|
}
|
|
|
|
let requestor_badge = requestor_badge_mut;
|
|
|
|
// [x] trg_module: BotModule,
|
|
// - [x] Need to validate an actual BotModule - otherwise, fail or exit the cmd
|
|
|
|
let trg_module = if (arg1 == Some("-i")) || (arg1 == Some("-f")) { arg2 } else { arg1 };
|
|
|
|
// if no trg_module was passed
|
|
if trg_module.is_none() {
|
|
|
|
let botlock = params.bot.read().await;
|
|
|
|
let outmsg = "uuh You need to pass a module";
|
|
|
|
botlog::debug(
|
|
outmsg,
|
|
Some("botmodules.rs > cmd_disable()".to_string()),
|
|
Some(¶ms.msg),
|
|
);
|
|
|
|
// We should call a notification around here
|
|
|
|
botlock.botmgrs.chat.send_botmsg(super::chat::BotMsgType::Notif(
|
|
outmsg.to_string()
|
|
),
|
|
params.clone(),
|
|
).await;
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// [x] trg_level: StatusLvl,
|
|
|
|
let currchnl = params.msg.channel_login.to_lowercase();
|
|
|
|
let trg_level =
|
|
if arg1 == Some("-i") || arg1 == Some("-f") { StatusLvl::Instance }
|
|
// else if arg1 == Some("-f") { StatusLvl::Instance }
|
|
else { StatusLvl::Ch(Channel(currchnl)) }
|
|
;
|
|
|
|
|
|
|
|
let botlock = params.bot.read().await;
|
|
let modmgr = Arc::clone(&botlock.botmodules);
|
|
let id = botlock.get_identity();
|
|
|
|
let force = arg1 == Some("-f");
|
|
|
|
let rslt = modmgr.exec_disable(
|
|
requestor,
|
|
requestor_badge,
|
|
BotModule(trg_module.unwrap().to_string()),
|
|
trg_level,
|
|
force,
|
|
id).await;
|
|
|
|
|
|
let outmsg = match rslt.clone() {
|
|
ChangeResult::Failed(a) => format!("Stare Failed : {}",a),
|
|
ChangeResult::NoChange(a) => format!("Hmm No Change : {}",a),
|
|
ChangeResult::Success(a) => format!("YAAY Success : {}",a),
|
|
};
|
|
|
|
// We should call a notification around here
|
|
|
|
botlock.botmgrs.chat.send_botmsg(super::chat::BotMsgType::Notif(
|
|
outmsg.to_string()
|
|
),
|
|
params.clone(),
|
|
).await;
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
#[derive(Debug, Clone)]
|
|
pub struct BotModule(pub String);
|
|
|
|
impl PartialEq for BotModule {
|
|
fn eq(&self, other: &Self) -> bool {
|
|
let BotModule(name1) = self.clone();
|
|
let BotModule(name2) = other.clone();
|
|
name1.to_lowercase() == name2.to_lowercase()
|
|
}
|
|
}
|
|
impl Eq for BotModule {}
|
|
|
|
impl Hash for BotModule{
|
|
fn hash<H: Hasher>(&self, state: &mut H) {
|
|
let BotModule(name) = self.clone();
|
|
name.to_lowercase().hash(state);
|
|
}
|
|
}
|
|
|
|
|
|
#[derive(Debug, PartialEq, Eq, Hash, Clone)]
|
|
pub enum ModGroup {
|
|
Core,
|
|
Custom,
|
|
}
|
|
|
|
#[derive(Debug, PartialEq, Eq, Hash, Clone)]
|
|
pub enum StatusLvl {
|
|
Instance,
|
|
Ch(Channel),
|
|
}
|
|
|
|
#[derive(Debug, PartialEq, Eq, Hash, Clone)]
|
|
pub enum StatusType {
|
|
Enabled(StatusLvl),
|
|
Disabled(StatusLvl),
|
|
}
|
|
|
|
pub enum BotAction {
|
|
C(BotCommand),
|
|
L(Listener),
|
|
R(Arc<RwLock<Routine>>),
|
|
}
|
|
|
|
impl BotAction {
|
|
pub async fn execute(&self, params : ExecBodyParams) {
|
|
match self {
|
|
BotAction::L(a) => a.execute(params).await,
|
|
BotAction::C(a) => a.execute(params).await,
|
|
_ => (),
|
|
}
|
|
}
|
|
}
|
|
|
|
#[async_trait]
|
|
pub trait BotActionTrait {
|
|
async fn add_to_bot(self, bot: BotInstance);
|
|
async fn add_to_modmgr(self, modmgr: Arc<ModulesManager>);
|
|
async fn add_core_to_bot(self, bot: BotInstance);
|
|
async fn add_core_to_modmgr(self, modmgr: Arc<ModulesManager>);
|
|
}
|
|
|
|
pub struct BotCommand {
|
|
pub module: BotModule,
|
|
pub command: String, // command call name
|
|
pub alias: Vec<String>, // String of alternative names
|
|
pub exec_body: bot_actions::actions_util::ExecBody,
|
|
pub help: String,
|
|
pub required_roles: Vec<identity::UserRole>,
|
|
}
|
|
|
|
impl BotCommand {
|
|
pub async fn execute(&self, params : ExecBodyParams) {
|
|
(*self.exec_body)(params).await;
|
|
}
|
|
}
|
|
|
|
#[async_trait]
|
|
impl BotActionTrait for BotCommand {
|
|
async fn add_to_bot(self, bot: BotInstance) {
|
|
self.add_to_modmgr(bot.botmodules).await;
|
|
}
|
|
|
|
async fn add_to_modmgr(self, modmgr: Arc<ModulesManager>) {
|
|
modmgr
|
|
.add_botaction(self.module.clone(), BotAction::C(self))
|
|
.await
|
|
}
|
|
|
|
async fn add_core_to_bot(self, bot: BotInstance) {
|
|
self.add_core_to_modmgr(bot.botmodules).await;
|
|
}
|
|
|
|
async fn add_core_to_modmgr(self, modmgr: Arc<ModulesManager>) {
|
|
modmgr
|
|
.add_core_act(self.module.clone(), BotAction::C(self))
|
|
.await
|
|
}
|
|
}
|
|
|
|
pub struct Listener {
|
|
pub module: BotModule,
|
|
pub name: String,
|
|
pub exec_body: bot_actions::actions_util::ExecBody,
|
|
pub help: String,
|
|
}
|
|
|
|
impl Listener {
|
|
pub async fn execute(&self, params : ExecBodyParams) {
|
|
(self.exec_body)(params).await;
|
|
}
|
|
}
|
|
|
|
#[async_trait]
|
|
impl BotActionTrait for Listener {
|
|
async fn add_to_bot(self, bot: BotInstance) {
|
|
botlog::trace(
|
|
"Adding action to bot",
|
|
Some("BotModules > BotActionTrait > add_to_bot()".to_string()),
|
|
None,
|
|
);
|
|
self.add_to_modmgr(bot.botmodules).await;
|
|
}
|
|
|
|
async fn add_to_modmgr(self, modmgr: Arc<ModulesManager>) {
|
|
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;
|
|
}
|
|
|
|
async fn add_core_to_bot(self, bot: BotInstance) {
|
|
self.add_core_to_modmgr(bot.botmodules).await;
|
|
}
|
|
|
|
async fn add_core_to_modmgr(self, modmgr: Arc<ModulesManager>) {
|
|
modmgr
|
|
.add_core_act(self.module.clone(), BotAction::L(self))
|
|
.await
|
|
}
|
|
}
|
|
|
|
// #[derive(Debug, PartialEq, Eq, Hash, Clone)]
|
|
#[derive(Debug, PartialEq, Eq, Hash)]
|
|
pub enum RoutineAttr {
|
|
DelayedStart,
|
|
ScheduledStart(DateTime<Local>), // Scheduled Date (if any) after which, if not started, may trigger
|
|
LoopDuration(Duration), // How long to wait between iterations
|
|
LoopInfinitely,
|
|
RunOnce,
|
|
MaxTimeThreshold(DateTime<Local>), // DateTime after which, it will abort/cancel or stop
|
|
MaxIterations(i64),
|
|
}
|
|
/*
|
|
a Routine can be given different combinations of the above, but business logic may validate
|
|
these at Routine construction
|
|
|
|
For example, a Routine could have the following characteristics
|
|
- DelayedStart (so it skips the first iteration)
|
|
- ScheduledStart(DateTime<Local>) - With a Start Date-Time for the first iteration to trigger
|
|
- LoopDuration(Duration) - How long to wait between iterations
|
|
|
|
The above without any other thresholds would loop infinitely with the above characteristics
|
|
|
|
Another example,
|
|
- LoopDuration(Duration) - How long to wait between iterations
|
|
- MaxTimeThreshold(DateTime<Local>)
|
|
- MaxIterations(i64)
|
|
|
|
The above has thresholds , so if either are reached, it would abort/cancel or stop . Since there is no
|
|
ScheduledStart, the routine would have to be started manually elsewhere
|
|
|
|
Another example ,
|
|
- (no RoutineAttr)
|
|
|
|
The above would only run once, and only when the Start() is called
|
|
|
|
*/
|
|
|
|
// For some key statuses and in particular Stopping to Gracefully stop
|
|
pub enum RoutineSignal {
|
|
Stopping, // Gracefully Stopping
|
|
Stopped, // When cancelling or aborting, this also is represented by Stopped
|
|
Started, // After Routine Started
|
|
NotStarted,
|
|
}
|
|
|
|
// #[derive(Debug)]
|
|
pub struct Routine {
|
|
pub name : String ,
|
|
pub module : BotModule , // from() can determine this if passed parents_params
|
|
pub channel : Channel , // Requiring some channel context
|
|
exec_body: bot_actions::actions_util::ExecBody,
|
|
pub parent_params : ExecBodyParams ,
|
|
pub join_handle : Option<Arc<RwLock<JoinHandle<RoutineAR>>>> ,
|
|
start_time : Option<DateTime<Local>> ,
|
|
pub complete_iterations : i64 ,
|
|
pub remaining_iterations : Option<i64> ,
|
|
routine_attr : Vec<RoutineAttr> ,
|
|
pub internal_signal : RoutineSignal ,
|
|
pub self_routine_ar : Option<RoutineAR> ,
|
|
pub self_act_ar : Option<ActAR> ,
|
|
}
|
|
|
|
|
|
impl Routine {
|
|
|
|
|
|
// // pub fn set
|
|
// // pub async fn refresh_self_ref(self) {
|
|
// pub async fn refresh_routine_internal(routine_ar : RoutineAR)
|
|
// {
|
|
|
|
// /*
|
|
// Execute after the Routine is constructed
|
|
// - If not, a start() will also call this
|
|
|
|
// */
|
|
|
|
// // 1. Update the self reference to itself
|
|
|
|
// let mut mut_lock = routine_ar.write().await;
|
|
// mut_lock.self_routine_ar = Some(routine_ar.clone());
|
|
|
|
// // 2. Update the current self_act_ar
|
|
// mut_lock.self_act_ar = Some(Arc::new(RwLock::new(BotAction::R(routine_ar.clone()))));
|
|
|
|
|
|
|
|
|
|
|
|
// }
|
|
|
|
|
|
pub async fn validate_attr(routine_attr : &Vec<RoutineAttr>)
|
|
-> Result<String,String>
|
|
// [ ] => 03.27 - REVIEW FOR COMPLETION
|
|
{
|
|
|
|
/*
|
|
|
|
GENERAL LOGIC :
|
|
[x] 1. Define RoutineAttr in a broad level that are known to be implented or are work in progress
|
|
[x] 2. Built in Logic will check these vectors, and return if Not Implemented
|
|
[x] 3. If Implemented , then there are additional internal validation based on combination done later
|
|
|
|
*/
|
|
|
|
|
|
// [x] 1. Define RoutineAttr in a broad level that are known to be implented or are work in progress
|
|
|
|
// adjust the below for those that are work in progress or that are implemented
|
|
// - This will allow other functions to validate that it is implemented
|
|
|
|
// // WORK IN PROGRESS VECTOR - Vec<$RoutineAttr>
|
|
|
|
// let wip_attr:Vec<RoutineAttr> = vec![
|
|
// RoutineAttr::DelayedStart,
|
|
// RoutineAttr::ScheduledStart(chrono::offset::Local::now()),
|
|
// RoutineAttr::LoopDuration(Duration::from_secs(1)),
|
|
// RoutineAttr::LoopInfinitely, // Note : There's no added implementation for this
|
|
// RoutineAttr::RunOnce,
|
|
// RoutineAttr::MaxTimeThreshold(chrono::offset::Local::now()),
|
|
// RoutineAttr::MaxIterations(1),
|
|
|
|
// ];
|
|
|
|
// let implemented_attr:Vec<RoutineAttr> = vec![
|
|
// ];
|
|
|
|
|
|
// [x] 2. Built in Logic will check these vectors, and return if Not Implemented
|
|
|
|
// let mut unimplemented = routine_attr.iter()
|
|
// .filter(|x| {
|
|
// let inx = x;
|
|
// wip_attr.iter().filter(|y| matches!(y,i if i == inx)).next().is_none()
|
|
// && implemented_attr.iter().filter(|y| matches!(y,i if i == inx)).next().is_none()
|
|
// }
|
|
// );
|
|
|
|
let mut attribute_supported = false;
|
|
|
|
for given_routine in routine_attr {
|
|
|
|
// if !matches!(given_routine,RoutineAttr::DelayedStart)
|
|
// && !matches!(given_routine,RoutineAttr::ScheduledStart(_))
|
|
// && !matches!(given_routine,RoutineAttr::LoopDuration(_))
|
|
// && !matches!(given_routine,RoutineAttr::LoopInfinitely)
|
|
// && !matches!(given_routine,RoutineAttr::RunOnce)
|
|
// && !matches!(given_routine,RoutineAttr::MaxTimeThreshold(_))
|
|
// && !matches!(given_routine,RoutineAttr::MaxIterations(_))
|
|
if matches!(given_routine,RoutineAttr::DelayedStart)
|
|
|| matches!(given_routine,RoutineAttr::ScheduledStart(_))
|
|
|| matches!(given_routine,RoutineAttr::LoopDuration(_))
|
|
|| matches!(given_routine,RoutineAttr::LoopInfinitely)
|
|
|| matches!(given_routine,RoutineAttr::RunOnce)
|
|
|| matches!(given_routine,RoutineAttr::MaxTimeThreshold(_))
|
|
|| matches!(given_routine,RoutineAttr::MaxIterations(_))
|
|
|
|
{
|
|
|
|
attribute_supported = true;
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
if !attribute_supported {
|
|
botlog::trace(
|
|
"[ERROR][Routine Feature NOT IMPLEMENTED]",
|
|
Some("Routine > Validate_attr()".to_string()),
|
|
None,
|
|
);
|
|
|
|
Log::flush();
|
|
|
|
botlog::trace(
|
|
format!(
|
|
"[ERROR][Routine Feature NOT IMPLEMENTED] > Problem Routine - {:?}"
|
|
,routine_attr).as_str(),
|
|
Some("Routine > Validate_attr()".to_string()),
|
|
None,
|
|
);
|
|
|
|
Log::flush();
|
|
|
|
return Err("NOT IMPLEMENTED".to_string());
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// if unimplemented.next().is_some() {
|
|
|
|
// botlog::trace(
|
|
// "[ERROR][Routine Feature NOT IMPLEMENTED]",
|
|
// Some("Routine > Validate_attr()".to_string()),
|
|
// None,
|
|
// );
|
|
|
|
// Log::flush();
|
|
|
|
// return Err("NOT IMPLEMENTED".to_string());
|
|
// }
|
|
|
|
|
|
|
|
|
|
|
|
// [x] 3. If Implemented , then there are additional internal validation based on combination done later to ERR
|
|
|
|
// Below ensures a routine_attr containing LoopInfinitely does not contain conflicit attributes
|
|
if routine_attr.contains(&RoutineAttr::LoopInfinitely) &&
|
|
( routine_attr.contains(&RoutineAttr::RunOnce)
|
|
|| routine_attr.iter().filter(|y| matches!(y,&&RoutineAttr::MaxIterations(_))).next().is_some()
|
|
|| routine_attr.iter().filter(|y| matches!(y,&&RoutineAttr::MaxTimeThreshold(_))).next().is_some()
|
|
)
|
|
{
|
|
return Err("Conflicting Routine Attributes".to_string())
|
|
}
|
|
|
|
// [x] if there is no RunOnce, there must be a LoopDuration
|
|
if !routine_attr.contains(&RoutineAttr::RunOnce)
|
|
&& routine_attr.iter()
|
|
.filter(|x| matches!(x,&&RoutineAttr::LoopDuration(_)) )
|
|
.next().is_none()
|
|
{
|
|
return Err("LoopDuration is required if not RunOnce".to_string());
|
|
}
|
|
|
|
|
|
// [x] Err if DelayedStart but no LoopDuration
|
|
if routine_attr.contains(&RoutineAttr::DelayedStart) &&
|
|
routine_attr.iter()
|
|
.filter(|x| matches!(x,&&RoutineAttr::LoopDuration(_)) )
|
|
.next().is_none()
|
|
{
|
|
return Err("DelayedStart must include a LoopDuration".to_string())
|
|
}
|
|
|
|
|
|
|
|
|
|
// [x] 4. If all other failure checks above works, ensure one more time that the atstribute is implemented
|
|
// - If not, routine NOT IMPLEMENTED error
|
|
|
|
// if routine_attr.iter()
|
|
// .filter(|x| {
|
|
// let inx = x;
|
|
// wip_attr.iter().filter(|y| matches!(y,i if i == inx)).next().is_none()
|
|
// || implemented_attr.iter().filter(|y| matches!(y,i if i == inx)).next().is_none()
|
|
// })
|
|
// .next()
|
|
// .is_none()
|
|
// {
|
|
|
|
botlog::trace(
|
|
"[OK][Implemented & Validated]",
|
|
Some("Routine > Validate_attr()".to_string()),
|
|
None,
|
|
);
|
|
|
|
Log::flush();
|
|
|
|
Ok("Implemented & Validated".to_string())
|
|
|
|
|
|
|
|
}
|
|
|
|
pub async fn validate_self_attr(self)
|
|
-> Result<String,String>
|
|
// [x] => 03.27 - COMPLETED
|
|
{
|
|
Routine::validate_attr(&self.routine_attr).await
|
|
}
|
|
|
|
// Constructor
|
|
pub async fn from(
|
|
name : String ,
|
|
module : BotModule ,
|
|
channel : Channel,
|
|
routine_attr : Vec<RoutineAttr> ,
|
|
exec_body : bot_actions::actions_util::ExecBody ,
|
|
parent_params : ExecBodyParams
|
|
) -> Result<
|
|
Arc<RwLock<Routine>>,
|
|
String
|
|
>
|
|
// [x] => 03.27 - COMPLETED
|
|
{
|
|
|
|
Routine::validate_attr(&routine_attr).await?;
|
|
|
|
let routine_ar = Arc::new(RwLock::new(Routine {
|
|
name ,
|
|
module ,
|
|
channel ,
|
|
exec_body ,
|
|
parent_params ,
|
|
join_handle : None ,
|
|
start_time : None ,
|
|
complete_iterations : 0 ,
|
|
remaining_iterations : None ,
|
|
routine_attr : routine_attr ,
|
|
internal_signal : RoutineSignal::NotStarted ,
|
|
self_routine_ar : None ,
|
|
self_act_ar : None ,
|
|
}));
|
|
|
|
let mut mut_lock = routine_ar.write().await;
|
|
mut_lock.self_routine_ar = Some(routine_ar.clone());
|
|
|
|
// 2. Update the current self_act_ar
|
|
mut_lock.self_act_ar = Some(Arc::new(RwLock::new(BotAction::R(routine_ar.clone()))));
|
|
|
|
Ok(routine_ar.clone())
|
|
|
|
// return Ok(Arc::new(RwLock::new(Routine {
|
|
// name ,
|
|
// module ,
|
|
// channel ,
|
|
// exec_body ,
|
|
// parent_params ,
|
|
// join_handle : None ,
|
|
// start_time : None ,
|
|
// complete_iterations : 0 ,
|
|
// remaining_iterations : None ,
|
|
// routine_attr : routine_attr ,
|
|
// internal_signal : RoutineSignal::NotStarted ,
|
|
// self_routine_ar : None ,
|
|
// self_act_ar : None ,
|
|
// }))) ;
|
|
|
|
|
|
}
|
|
|
|
pub async fn start(
|
|
trg_routine_ar : Arc<RwLock<Routine>>
|
|
// ) -> Result<String,String>
|
|
) -> Result<Arc<RwLock<Routine>>,String>
|
|
// [ ] => 03.27 - REVIEW FOR COMPLETION
|
|
{
|
|
|
|
|
|
// // [x] Prep by updating it's own self reference
|
|
// Routine::refresh_routine_internal(trg_routine_ar.clone()).await;
|
|
|
|
// [x] Asyncio Spawn likely around here
|
|
// [x] & Assigns self.join_handle
|
|
|
|
|
|
/*
|
|
UMBRELLA ROUTINE LOGIC
|
|
1. Create a loop scenario based on routine_attr such as RunOnce
|
|
2. Run the loop depending on how the Routine is setup
|
|
|
|
|
|
START LOGIC :
|
|
1. Ideally validate the routineattr that they're not a problem scenario (However, this should have been validated At Setup)
|
|
a. Extra helper validation function though would help in case the attributes were changes between setup
|
|
2. Use these attributes only in Critical areas of the loop logic to determine changes in Loop logic
|
|
|
|
*/
|
|
|
|
|
|
// Use the following to stop the function from going any further if not implemented
|
|
|
|
Routine::validate_attr(&trg_routine_ar.read().await.routine_attr).await?;
|
|
// if !trg_routine_ar.read().await.routine_attr.contains(&RoutineAttr::RunOnce) {
|
|
|
|
|
|
// botlog::trace(
|
|
// format!(
|
|
// "[ERROR][Routine Feature NOT IMPLEMENTED] {} in {}",
|
|
// trg_routine_ar.read().await.name,
|
|
// trg_routine_ar.read().await.channel.0
|
|
// )
|
|
// .as_str(),
|
|
// Some(format!(
|
|
// "Routine > start() > (In Tokio Spawn) > {:?}",
|
|
// trg_routine_ar.read().await.module
|
|
// )),
|
|
// Some(&trg_routine_ar.read().await.parent_params.msg),
|
|
// );
|
|
|
|
// Log::flush();
|
|
|
|
// return Err("NOT IMPLEMENTED".to_string())
|
|
|
|
// }
|
|
|
|
|
|
let trg_routine_arout = Arc::clone(&trg_routine_ar);
|
|
|
|
|
|
botlog::trace(
|
|
"innerhelper() started",
|
|
Some(format!(
|
|
"Routine > start() > (In Tokio Spawn)",
|
|
)),
|
|
Some(&trg_routine_ar.read().await.parent_params.msg),
|
|
);
|
|
|
|
Log::flush();
|
|
|
|
// Spawn the task
|
|
let join_handle = tokio::spawn(async move {
|
|
|
|
botlog::trace(
|
|
">> Within Spawn",
|
|
Some(format!(
|
|
"Routine > start() > (In Tokio Spawn)",
|
|
)),
|
|
Some(&trg_routine_ar.read().await.parent_params.msg),
|
|
);
|
|
|
|
Log::flush();
|
|
|
|
// [x] If Scheduled Start or Delayed Start, Handle that first
|
|
|
|
|
|
fn duration_to_datetime(future_dt: DateTime<Local>) -> Result<Duration,OutOfRangeError>
|
|
{
|
|
(future_dt - chrono::offset::Local::now()).to_std()
|
|
}
|
|
|
|
|
|
|
|
let delayduration = {
|
|
|
|
|
|
let lock = trg_routine_ar.read().await;
|
|
|
|
let mut related_attrs = lock
|
|
.routine_attr.iter()
|
|
.filter(|x| matches!(x,&&RoutineAttr::DelayedStart) || matches!(x,&&RoutineAttr::ScheduledStart(_)) );
|
|
|
|
// match related_attrs.next() {
|
|
|
|
// }
|
|
|
|
async fn duration_from_attr(attr: &RoutineAttr,trg_routine_ar : RoutineAR) -> Option<Duration> {
|
|
// let duration_from_attr = async {
|
|
let lock = trg_routine_ar.read().await;
|
|
|
|
match attr {
|
|
RoutineAttr::ScheduledStart(dt) => {
|
|
if let Ok(dur) = duration_to_datetime(*dt) {
|
|
Some(dur)
|
|
} else { None }
|
|
},
|
|
RoutineAttr::DelayedStart => {
|
|
let mut loopdur_attr_iter = lock
|
|
.routine_attr.iter()
|
|
.filter(|x| matches!(x,&&RoutineAttr::LoopDuration(_)) );
|
|
|
|
if let Some(loopdur_attr) = loopdur_attr_iter.next() {
|
|
if let RoutineAttr::LoopDuration(dur) = loopdur_attr {
|
|
Some(*dur)
|
|
} else { None }
|
|
} else { None }
|
|
// None
|
|
},
|
|
_ => { None } // Handle no other combination
|
|
}
|
|
}
|
|
|
|
// The following is done twice just in case ScheduledStart and DelayedStart are defined
|
|
let delayduration01 = if let Some(attr) = related_attrs.next() {
|
|
duration_from_attr(attr, trg_routine_ar.clone()).await
|
|
} else { None };
|
|
|
|
let delayduration02 = if let Some(attr) = related_attrs.next() {
|
|
duration_from_attr(attr, trg_routine_ar.clone()).await
|
|
} else { None };
|
|
|
|
// if there is a 2nd related duration, pick the minimum, otherwise, pick the results of delayduration01
|
|
if delayduration02.is_some() {
|
|
Some(Duration::min(delayduration01.unwrap(),delayduration02.unwrap()))
|
|
} else { delayduration01 }
|
|
|
|
};
|
|
|
|
|
|
botlog::trace(
|
|
format!(
|
|
"[TRACE][Routine Processing] {} in {} > Delay Duration - {:?} ",
|
|
trg_routine_ar.read().await.name,
|
|
trg_routine_ar.read().await.channel.0 ,
|
|
delayduration ,
|
|
)
|
|
.as_str(),
|
|
Some(format!(
|
|
"Routine > start() > (In Tokio Spawn) > {:?}",
|
|
trg_routine_ar.read().await.module
|
|
)),
|
|
Some(&trg_routine_ar.read().await.parent_params.msg),
|
|
);
|
|
|
|
if let Some(dur) = delayduration {
|
|
sleep(dur).await;
|
|
}
|
|
|
|
|
|
{ // [x] Loop Initialization - Prior to Loop that calls Custom Routine Execution Body
|
|
let mut a = trg_routine_ar.write().await;
|
|
a.start_time = Some(chrono::offset::Local::now());
|
|
|
|
if let Some(&RoutineAttr::MaxIterations(iternum)) =
|
|
a.routine_attr.iter()
|
|
.filter(|x| matches!(x,RoutineAttr::MaxIterations(_)))
|
|
.next()
|
|
{
|
|
a.remaining_iterations = Some(iternum);
|
|
}
|
|
}
|
|
|
|
loop { // [x] Routine loop
|
|
|
|
|
|
// [x] execution body
|
|
// trg_routine_ar.read().await.loopbody().await;
|
|
trg_routine_ar.write().await.loopbody().await;
|
|
|
|
|
|
{ // [x] End of Loop iteration
|
|
let mut a = trg_routine_ar.write().await;
|
|
|
|
// [x] Check if Gracefully Stopping Signal was sent
|
|
if matches!(a.internal_signal,RoutineSignal::Stopping) {
|
|
a.internal_signal = RoutineSignal::Stopped;
|
|
break ;
|
|
}
|
|
|
|
// [x] Check and adjust iterations
|
|
a.complete_iterations += 1;
|
|
if let Some(i) = a.remaining_iterations {
|
|
if i > 0 { a.remaining_iterations = Some(i-1) ; }
|
|
else { break ; } // if remaining iterations is 0, exit
|
|
}
|
|
|
|
}
|
|
|
|
// [x] End of Loop Validation
|
|
// These generally may include routine_attr related checks to , for example, break out of the loop
|
|
|
|
if trg_routine_ar.read().await.routine_attr.contains(&RoutineAttr::RunOnce) {
|
|
if trg_routine_ar.read().await.complete_iterations > 0 { break; }
|
|
}
|
|
|
|
// return if max time has passed
|
|
if let Some(&RoutineAttr::MaxTimeThreshold(dt)) = trg_routine_ar.read().await.routine_attr.iter()
|
|
.filter(|x| matches!(x,&&RoutineAttr::MaxTimeThreshold(_)) )
|
|
.next() {
|
|
if chrono::offset::Local::now() > dt { break; }
|
|
}
|
|
|
|
// [x] Checks for Loop duration to sleep
|
|
if let Some(&RoutineAttr::LoopDuration(dur)) = trg_routine_ar.read().await.routine_attr.iter()
|
|
.filter(|x| matches!(x,&&RoutineAttr::LoopDuration(_)) )
|
|
.next()
|
|
{
|
|
sleep(dur).await;
|
|
};
|
|
|
|
|
|
}
|
|
|
|
|
|
botlog::trace(
|
|
format!(
|
|
"[TRACE][Routine Completed] {} in {}",
|
|
trg_routine_ar.read().await.name,
|
|
trg_routine_ar.read().await.channel.0
|
|
)
|
|
.as_str(),
|
|
Some(format!(
|
|
"Routine > start() > (In Tokio Spawn) > {:?}",
|
|
trg_routine_ar.read().await.module
|
|
)),
|
|
Some(&trg_routine_ar.read().await.parent_params.msg),
|
|
);
|
|
|
|
botlog::trace(
|
|
format!(
|
|
"[TRACE][Routine Completed][Routine Header Test] {} in {} > Completed Iterations : {}",
|
|
trg_routine_ar.read().await.name,
|
|
trg_routine_ar.read().await.channel.0 ,
|
|
trg_routine_ar.read().await.complete_iterations,
|
|
)
|
|
.as_str(),
|
|
Some(format!(
|
|
"Routine > start() > (In Tokio Spawn) > {:?}",
|
|
trg_routine_ar.read().await.module
|
|
)),
|
|
Some(&trg_routine_ar.read().await.parent_params.msg),
|
|
);
|
|
|
|
Log::flush();
|
|
trg_routine_ar
|
|
});
|
|
|
|
|
|
{ // Recommendation to ensure a clean update is to use one write() lock that was awaited
|
|
// - We can isolate the write lock by ensuring it's in it's own block
|
|
|
|
let mut lock = trg_routine_arout.write().await;
|
|
lock.join_handle = Some(Arc::new(RwLock::new(join_handle)));
|
|
lock.internal_signal = RoutineSignal::Started;
|
|
|
|
}
|
|
trg_routine_arout.write().await.internal_signal = RoutineSignal::Started;
|
|
|
|
return Ok(trg_routine_arout);
|
|
|
|
}
|
|
|
|
async fn loopbody(&mut self)
|
|
// [x] => 03.27 - COMPLETED
|
|
{
|
|
botlog::trace(
|
|
"loopbody() started",
|
|
Some(format!(
|
|
"Routine > start() > (During Tokio Spawn) > Execution body",
|
|
)),
|
|
None,
|
|
);
|
|
|
|
Log::flush();
|
|
|
|
let self_ar = Arc::new(RwLock::new(self));
|
|
|
|
{
|
|
let mut mutlock = self_ar.write().await;
|
|
|
|
mutlock.parent_params.parent_act = Some(mutlock.parent_params.curr_act.clone());
|
|
mutlock.parent_params.curr_act = mutlock.self_act_ar.to_owned().unwrap();
|
|
}
|
|
|
|
(self_ar.read().await.exec_body)(
|
|
self_ar.read().await.parent_params.clone()
|
|
).await;
|
|
|
|
// (self.exec_body)(
|
|
// self.parent_params.clone()
|
|
// ).await;
|
|
}
|
|
|
|
pub async fn stop(&mut self) -> Result<String,String>
|
|
// [ ] => 03.27 - REVIEW FOR COMPLETION
|
|
{
|
|
|
|
|
|
let self_rw = Arc::new(RwLock::new(self));
|
|
|
|
{
|
|
let mut self_lock = self_rw.write().await;
|
|
self_lock.internal_signal = RoutineSignal::Stopping;
|
|
}
|
|
|
|
|
|
let self_lock = self_rw.read().await;
|
|
|
|
botlog::trace(
|
|
format!(
|
|
"[ROUTINE][Sent Gracefully Stop Signal] {} in {}",
|
|
self_lock.name,self_lock.channel.0
|
|
)
|
|
.as_str(),
|
|
Some(format!(
|
|
"Routine > stop() > {:?}",
|
|
self_lock.module
|
|
)),
|
|
Some(&self_lock.parent_params.msg),
|
|
);
|
|
|
|
Log::flush();
|
|
|
|
Ok("Sent Gracefully Stop Signal".to_string())
|
|
|
|
// botlog::trace(
|
|
// format!(
|
|
// "[ERROR][Routine NOT IMPLEMENTED] {} in {}",
|
|
// self_lock.name,self_lock.channel.0
|
|
// )
|
|
// .as_str(),
|
|
// Some(format!(
|
|
// "Routine > start() > (In Tokio Spawn) > {:?}",
|
|
// self_lock.module
|
|
// )),
|
|
// Some(&self_lock.parent_params.msg),
|
|
// );
|
|
|
|
// Log::flush();
|
|
|
|
// Err("NOT IMPLEMENTED".to_string())
|
|
}
|
|
|
|
pub async fn cancel(&mut self) -> Result<String,String>
|
|
// [ ] => 03.27 - REVIEW FOR COMPLETION
|
|
{
|
|
|
|
// [ ] Likely calls abort()
|
|
// Related :
|
|
// https://docs.rs/tokio/latest/tokio/task/struct.JoinHandle.html#method.abort
|
|
|
|
|
|
let self_rw = Arc::new(RwLock::new(self));
|
|
let self_lock = self_rw.read().await;
|
|
|
|
|
|
match &self_lock.join_handle {
|
|
None => return Err("No Join Handle on the Routine to Cancel".to_string()),
|
|
Some(a) => {
|
|
a.read().await.abort();
|
|
{
|
|
let mut lock_mut = self_rw.write().await;
|
|
lock_mut.internal_signal = RoutineSignal::Stopped;
|
|
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
botlog::trace(
|
|
format!(
|
|
"[ROUTINE][Cancelled Routine] {} in {}",
|
|
self_lock.name,self_lock.channel.0
|
|
)
|
|
.as_str(),
|
|
Some(format!(
|
|
"Routine > cancel() > {:?}",
|
|
self_lock.module
|
|
)),
|
|
Some(&self_lock.parent_params.msg),
|
|
);
|
|
|
|
Log::flush();
|
|
Ok("Cancelled Successfully".to_string())
|
|
|
|
|
|
// botlog::trace(
|
|
// format!(
|
|
// "[ERROR][Routine NOT IMPLEMENTED] {} in {}",
|
|
// self_lock.name,self_lock.channel.0
|
|
// )
|
|
// .as_str(),
|
|
// Some(format!(
|
|
// "Routine > start() > (In Tokio Spawn) > {:?}",
|
|
// self_lock.module
|
|
// )),
|
|
// Some(&self_lock.parent_params.msg),
|
|
// );
|
|
|
|
// Log::flush();
|
|
// Err("NOT IMPLEMENTED".to_string())
|
|
}
|
|
|
|
pub async fn restart(
|
|
// &mut self,
|
|
self,
|
|
force : bool
|
|
) -> Result<String,String>
|
|
// [ ] => 03.27 - REVIEW FOR COMPLETION
|
|
{
|
|
// force flag aborts the routine immediately (like cancel())
|
|
|
|
|
|
let self_rw = Arc::new(RwLock::new(self));
|
|
|
|
if force
|
|
{
|
|
let mut self_lock = self_rw.write().await;
|
|
self_lock.cancel().await?;
|
|
} else {
|
|
let mut self_lock = self_rw.write().await;
|
|
self_lock.stop().await?;
|
|
}
|
|
|
|
Routine::start(self_rw.clone()).await?;
|
|
|
|
let self_lock = self_rw.read().await;
|
|
|
|
botlog::trace(
|
|
format!(
|
|
"[ROUTINE][Restarted Routine] {} in {}",
|
|
self_lock.name,self_lock.channel.0
|
|
)
|
|
.as_str(),
|
|
Some(format!(
|
|
"Routine > restart() > {:?}",
|
|
self_lock.module
|
|
)),
|
|
Some(&self_lock.parent_params.msg),
|
|
);
|
|
|
|
Log::flush();
|
|
Ok("Restarted successfully".to_string())
|
|
}
|
|
|
|
pub async fn change_channel(
|
|
&mut self,
|
|
channel : Channel
|
|
) -> Result<String,String>
|
|
// [ ] => 03.28 - REVIEW FOR COMPLETION
|
|
{
|
|
// [x] Think Ideally it should try to
|
|
// change the target channel of the
|
|
// internal process too if possible?
|
|
|
|
self.channel = channel;
|
|
|
|
|
|
let self_rw = Arc::new(RwLock::new(self));
|
|
|
|
let self_lock = self_rw.read().await;
|
|
|
|
botlog::trace(
|
|
format!(
|
|
"[ROUTINE][Change Channel] {} in {}",
|
|
self_lock.name,self_lock.channel.0
|
|
)
|
|
.as_str(),
|
|
Some(format!(
|
|
"Routine > restart() > {:?}",
|
|
self_lock.module
|
|
)),
|
|
Some(&self_lock.parent_params.msg),
|
|
);
|
|
|
|
Log::flush();
|
|
|
|
// Err("NOT IMPLEMENTED".to_string())
|
|
Ok("Changed Successfully".to_string())
|
|
}
|
|
|
|
|
|
pub async fn set_routine_attributes(
|
|
&mut self,
|
|
routine_attr : Vec<RoutineAttr>
|
|
) -> Result<String,String>
|
|
// [ ] => 03.27 - WIP - NOT IMPLEMENTED
|
|
{
|
|
// This is way to custom set attributes first
|
|
// They will Be Validated before being applied
|
|
// IDEALLY the routine also be restarted afterwards externally
|
|
|
|
Routine::validate_attr(&routine_attr).await?;
|
|
|
|
|
|
let self_rw = Arc::new(RwLock::new(self));
|
|
{
|
|
let mut self_lock = self_rw.write().await;
|
|
self_lock.routine_attr = routine_attr;
|
|
}
|
|
|
|
// let self_rw = Arc::new(RwLock::new(self));
|
|
|
|
let self_lock = self_rw.read().await;
|
|
|
|
botlog::trace(
|
|
format!(
|
|
"[ROUTINE][Set Routine Attributes] {} in {}",
|
|
self_lock.name,self_lock.channel.0
|
|
)
|
|
.as_str(),
|
|
Some(format!(
|
|
"Routine > restart() > {:?}",
|
|
self_lock.module
|
|
)),
|
|
Some(&self_lock.parent_params.msg),
|
|
);
|
|
|
|
Log::flush();
|
|
|
|
|
|
Ok("Changed Successfully".to_string())
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
type StatusdbEntry = (ModGroup, Vec<StatusType>);
|
|
type ModuleActions = Vec<Arc<RwLock<BotAction>>>;
|
|
|
|
pub struct ModulesManager {
|
|
statusdb: Arc<RwLock<HashMap<BotModule, StatusdbEntry>>>,
|
|
pub botactions: Arc<RwLock<HashMap<BotModule, ModuleActions>>>,
|
|
}
|
|
|
|
/*
|
|
|
|
statusdb
|
|
<HashMap
|
|
<BotModule, <-- e.g., BotModule(String::from("experiments001"))
|
|
Vec<ModStatusType> <-- shows Enabled/Disabled per Status level
|
|
|
|
botactions
|
|
HashMap<
|
|
BotModule, <-- e.g., BotModule(String::from("experiments001"))
|
|
Vec<BotAction>> BotCommand, Listener
|
|
|
|
*/
|
|
|
|
impl ModulesManager {
|
|
pub async fn init() -> Arc<ModulesManager> {
|
|
let mgr = ModulesManager {
|
|
statusdb: Arc::new(RwLock::new(HashMap::new())),
|
|
botactions: Arc::new(RwLock::new(HashMap::new())),
|
|
};
|
|
|
|
// :: [x] initialize core modules
|
|
botlog::trace(
|
|
"ModulesManager > init() > Adding modules",
|
|
Some("ModulesManager > init()".to_string()),
|
|
None,
|
|
);
|
|
|
|
let mgrarc = Arc::new(mgr);
|
|
|
|
// 1. load core modules
|
|
crate::core::identity::init(Arc::clone(&mgrarc)).await;
|
|
crate::core::botmodules::init(Arc::clone(&mgrarc)).await;
|
|
|
|
// 2. load custom modules
|
|
crate::custom::init(Arc::clone(&mgrarc)).await;
|
|
|
|
botlog::trace(
|
|
">> Modules Manager : End of Init",
|
|
Some("ModulesManager > init()".to_string()),
|
|
None,
|
|
);
|
|
|
|
mgrarc
|
|
}
|
|
|
|
|
|
pub async fn moduleslist(&self) -> HashMap<BotModule,ModGroup>
|
|
{
|
|
|
|
// let db = Arc::clone(&self.statusdb);
|
|
let db = self.statusdb.clone();
|
|
let dblock = db.read().await;
|
|
|
|
let mut outmap = HashMap::new();
|
|
|
|
for (k,v) in &(*dblock) {
|
|
let (mgrp,_) = v;
|
|
let mtype = k;
|
|
outmap.insert((*mtype).clone(), (*mgrp).clone());
|
|
}
|
|
outmap
|
|
}
|
|
|
|
pub async fn modstatus(&self, in_module: BotModule, in_chnl: Channel) -> StatusType {
|
|
// Example usage : botmanager.modstatus(
|
|
// BotModule("GambaCore"),
|
|
// Channel("modulatingforce")
|
|
// )
|
|
// - 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
|
|
|
|
let dbt = self.statusdb.read().await;
|
|
|
|
let (mgrp,statusvector) = dbt.get(&in_module).unwrap();
|
|
|
|
match mgrp {
|
|
ModGroup::Core => {
|
|
StatusType::Enabled(StatusLvl::Instance) // This forces core to be validated as Enabled, even if undesired scenario of missing StatusLvl::Instance or empty vectors
|
|
},
|
|
ModGroup::Custom => {
|
|
|
|
/*
|
|
|
|
[x] 1. If Disabled at Instance Level ,
|
|
[x] a. And Enabled at a Channel Level > return Enabled(Channel)
|
|
[x] b. And Disabled at a Channel Level > return Disabled(Channel)
|
|
[x] c. And Not Defined at Channel Level > return Disabled(Instance)
|
|
[x] 2. If Enabled at Instance Level ,
|
|
[x] a. And Enabled at a Channel Level > return Enabled(Channel)
|
|
[x] b. And Disabled at a Channel Level > return Disabled(Channel)
|
|
[x] c. And Not Defined at Channel Level > return Enabled(Instance)
|
|
*/
|
|
|
|
|
|
|
|
if statusvector.contains(&StatusType::Disabled(StatusLvl::Instance)) {
|
|
// [x] 1. If Disabled at Instance Level ,
|
|
|
|
|
|
if statusvector.contains(&StatusType::Enabled(StatusLvl::Ch(in_chnl.clone()))) {
|
|
// [x] a. And Enabled at a Channel Level > return Enabled(Channel)
|
|
StatusType::Enabled(StatusLvl::Ch(in_chnl.clone()))
|
|
} else if statusvector.contains(&StatusType::Disabled(StatusLvl::Ch(in_chnl.clone()))) {
|
|
// [x] b. And Disabled at a Channel Level > return Disabled(Channel)
|
|
StatusType::Disabled(StatusLvl::Ch(in_chnl.clone()))
|
|
} else {
|
|
// [x] c. And Not Defined at Channel Level > return Disabled(Instance)
|
|
StatusType::Disabled(StatusLvl::Instance)
|
|
}
|
|
|
|
} else if statusvector.contains(&StatusType::Enabled(StatusLvl::Instance)) {
|
|
// [x] 2. If Enabled at Instance Level ,
|
|
|
|
if statusvector.contains(&StatusType::Enabled(StatusLvl::Ch(in_chnl.clone()))) {
|
|
// [x] a. And Enabled at a Channel Level > return Enabled(Channel)
|
|
StatusType::Enabled(StatusLvl::Ch(in_chnl.clone()))
|
|
} else if statusvector.contains(&StatusType::Disabled(StatusLvl::Ch(in_chnl.clone()))) {
|
|
// [x] b. And Disabled at a Channel Level > return Disabled(Channel)
|
|
StatusType::Disabled(StatusLvl::Ch(in_chnl.clone()))
|
|
} else {
|
|
// [x] c. And Not Defined at Channel Level > return Enabled(Instance)
|
|
StatusType::Enabled(StatusLvl::Instance)
|
|
}
|
|
|
|
} else {
|
|
// ? In some unexpected scenario (e.g., not define at instance level), assume Disabled at Instance level and set as this way
|
|
self.set_instance_disabled(in_module).await;
|
|
StatusType::Disabled(StatusLvl::Instance)
|
|
}
|
|
},
|
|
}
|
|
|
|
|
|
//StatusType::Enabled(StatusLvl::Instance)
|
|
}
|
|
|
|
pub async fn exec_enable(
|
|
&self,
|
|
requestor: String,
|
|
requestor_badge: Option<ChatBadge>,
|
|
trg_module: BotModule,
|
|
trg_level: StatusLvl,
|
|
id: Arc<RwLock<IdentityManager>>,
|
|
) -> ChangeResult
|
|
{
|
|
|
|
/*
|
|
|
|
1. If CmdSender is BotAdmin but not (Mod,SupMod,Broadcaster)
|
|
1. can_user_run for cmdreqRoles including BotAdmin & not can_user_run for cmdreqRoles (Mod,SupMod,Broadcaster)
|
|
1a. , and is not -i (to instance) , return a Failure recommending BotAdmin promote themselves first
|
|
1b. , and is -i (to instance) , return a Success
|
|
|
|
2. If CmdSender not a BotAdmin but is (Mod,SupMod,Broadcaster)
|
|
2. not can_user_run for cmdreqRoles including BotAdmin & can_user_run for cmdreqRoles (Mod,SupMod,Broadcaster)
|
|
2a. , and is not -i (to instance) , return a Success
|
|
2b. , and is -i (to instance) , return a Failure they are not allowed
|
|
|
|
3. If CmdSender is (Mod,SupMod,Broadcaster) and a BotAdmin
|
|
3. can_user_run for cmdreqRoles (Mod,SupMod,Broadcaster) & can_user_run for cmdreqRoles including BotAdmin
|
|
3a. , and is not -i (to instance) , return a Success
|
|
3b. , and is -i (to instance) , return a Success
|
|
*/
|
|
|
|
|
|
/*
|
|
[x] 1. If CmdSender is BotAdmin but not (Mod,SupMod,Broadcaster)
|
|
1. can_user_run for cmdreqRoles including BotAdmin & not can_user_run for cmdreqRoles (Mod,SupMod,Broadcaster)
|
|
1a. , and is -i (to instance) , return a Success
|
|
1b. , and is not -i (to instance) , return a Failure recommending BotAdmin promote themselves first
|
|
|
|
|
|
*/
|
|
|
|
|
|
// [x] Validate in trg_module first
|
|
|
|
let modlist = self.moduleslist().await;
|
|
let rslt = modlist.get(&trg_module);
|
|
|
|
if rslt.is_none() {
|
|
return ChangeResult::Failed("Module doesn't exist".to_string());
|
|
}
|
|
|
|
botlog::trace(
|
|
"ACQUIRING WRITE LOCK : ID",
|
|
Some("ModulesManager > Exec_enable".to_string()),
|
|
None,
|
|
);
|
|
|
|
|
|
let mut idlock = id.write().await;
|
|
|
|
// if trg_level = StatusLvl::Instance , the temp_chnl = the broadcaster's or the chatter's
|
|
|
|
let arb_chnl = match trg_level.clone() {
|
|
StatusLvl::Instance => Channel(requestor.to_lowercase()),
|
|
StatusLvl::Ch(a) => a,
|
|
};
|
|
|
|
const OF_CMD_CHANNEL:Channel = Channel(String::new());
|
|
|
|
let (admin_level_access,_) = idlock.can_user_run(requestor.clone(), arb_chnl.clone(), requestor_badge.clone(),
|
|
vec![
|
|
identity::UserRole::BotAdmin,
|
|
]).await;
|
|
|
|
|
|
let (chnl_elevated_access,_) = idlock.can_user_run(requestor, arb_chnl, requestor_badge.clone(),
|
|
vec![
|
|
identity::UserRole::Mod(OF_CMD_CHANNEL),
|
|
identity::UserRole::SupMod(OF_CMD_CHANNEL),
|
|
identity::UserRole::Broadcaster,
|
|
]).await;
|
|
|
|
|
|
if let Permissible::Allow = admin_level_access {
|
|
if let Permissible::Block = chnl_elevated_access {
|
|
|
|
botlog::debug(
|
|
&format!("?? REACHED INNER TIER :
|
|
admin_level_access : {:?} ; chnl_elevated_access : {:?}",
|
|
admin_level_access , chnl_elevated_access),
|
|
Some("botmodules.rs > exec_enable()".to_string()),
|
|
None,
|
|
);
|
|
match trg_level {
|
|
StatusLvl::Instance => {
|
|
self.set_instance_enabled(trg_module.clone()).await;
|
|
return ChangeResult::Success("Enabled at Instance Level".to_string());
|
|
},
|
|
StatusLvl::Ch(_) => {
|
|
return ChangeResult::Failed("Promote yourself Temporarily First".to_string());
|
|
},
|
|
};
|
|
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
[x] 2. If CmdSender not a BotAdmin but is (Mod,SupMod,Broadcaster)
|
|
2. not can_user_run for cmdreqRoles including BotAdmin & can_user_run for cmdreqRoles (Mod,SupMod,Broadcaster)
|
|
2a. , and is -i (to instance) , return a Failure they are not allowed
|
|
2b. , and is not -i (to instance) , return a Success
|
|
|
|
*/
|
|
|
|
if let Permissible::Block = admin_level_access {
|
|
if let Permissible::Allow = chnl_elevated_access {
|
|
match trg_level.clone() {
|
|
StatusLvl::Instance => {
|
|
return ChangeResult::Failed("You're not allowed".to_string());
|
|
},
|
|
StatusLvl::Ch(in_chnl) => {
|
|
self.set_ch_enabled(trg_module.clone(), in_chnl).await;
|
|
return ChangeResult::Success("Enabled at Channel Level".to_string());
|
|
},
|
|
};
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
|
|
[x] 3. If CmdSender is (Mod,SupMod,Broadcaster) and a BotAdmin
|
|
3. can_user_run for cmdreqRoles (Mod,SupMod,Broadcaster) & can_user_run for cmdreqRoles including BotAdmin
|
|
3a. , and is not -i (to instance) , return a Success
|
|
3b. , and is -i (to instance) , return a Success
|
|
*/
|
|
|
|
|
|
if let Permissible::Allow = admin_level_access {
|
|
if let Permissible::Allow = chnl_elevated_access {
|
|
match trg_level {
|
|
StatusLvl::Instance => {
|
|
self.set_instance_enabled(trg_module.clone()).await;
|
|
return ChangeResult::Success("Enabled at Instance Level".to_string());
|
|
},
|
|
StatusLvl::Ch(in_chnl) => {
|
|
self.set_ch_enabled(trg_module.clone(), in_chnl).await;
|
|
return ChangeResult::Success("Enabled at Channel Level".to_string());
|
|
},
|
|
};
|
|
}
|
|
}
|
|
|
|
|
|
// Respond in case of General Chatter
|
|
// The below should NOT be required , as current internal logic would prevent
|
|
// a BotCommand to be ran by a Chatter if it requires any special roles and
|
|
// that chatter does not have htose roles
|
|
// However, below is added to satisfy unit tests
|
|
|
|
|
|
if let Permissible::Block = admin_level_access {
|
|
if let Permissible::Block = chnl_elevated_access {
|
|
match trg_level {
|
|
StatusLvl::Instance => {
|
|
return ChangeResult::Failed("You're not allowed".to_string());
|
|
},
|
|
StatusLvl::Ch(_) => {
|
|
return ChangeResult::Failed("You're not allowed".to_string());
|
|
},
|
|
};
|
|
}
|
|
}
|
|
|
|
|
|
|
|
// =======================
|
|
// =======================
|
|
// =======================
|
|
|
|
|
|
|
|
botlog::debug(
|
|
&format!("FAILURE involves :
|
|
admin_level_access : {:?} ; chnl_elevated_access : {:?}",
|
|
admin_level_access , chnl_elevated_access),
|
|
Some("botmodules.rs > exec_enable()".to_string()),
|
|
None,
|
|
);
|
|
|
|
|
|
Log::flush();
|
|
|
|
ChangeResult::Failed("ERROR : Not implemented yet".to_string())
|
|
}
|
|
|
|
|
|
pub async fn exec_disable(
|
|
&self,
|
|
requestor: String,
|
|
requestor_badge: Option<ChatBadge>,
|
|
trg_module: BotModule,
|
|
trg_level: StatusLvl,
|
|
force: bool,
|
|
id: Arc<RwLock<IdentityManager>>,
|
|
) -> ChangeResult
|
|
{
|
|
|
|
/*
|
|
|
|
1. If CmdSender is BotAdmin but not (Mod,SupMod,Broadcaster)
|
|
1. can_user_run for cmdreqRoles including BotAdmin & not can_user_run for cmdreqRoles (Mod,SupMod,Broadcaster)
|
|
1a. , and has no special flags (-i / -f) , return a Failure recommending BotAdmin promote themselves first
|
|
1b. , and is -i (to instance) , return a Success
|
|
1c. , and is -f (forced) , return a Success
|
|
|
|
2. If CmdSender not a BotAdmin but is (Mod,SupMod,Broadcaster)
|
|
2. not can_user_run for cmdreqRoles including BotAdmin & can_user_run for cmdreqRoles (Mod,SupMod,Broadcaster)
|
|
2a. , and has no special flags (-i / -f) , return a Success
|
|
2b. , and is -i (to instance) , return a Failure they are not allowed
|
|
2c. , and is -f (forced) , return a Failure they are not allowed
|
|
|
|
3. If CmdSender is (Mod,SupMod,Broadcaster) and a BotAdmin
|
|
3. can_user_run for cmdreqRoles (Mod,SupMod,Broadcaster) & can_user_run for cmdreqRoles including BotAdmin
|
|
3a. , and has no special flags (-i / -f) , return a Success
|
|
3b. , and is -i (to instance) , return a Success
|
|
3c. , and is -f (forced) , return a Success
|
|
*/
|
|
|
|
// [x] Validate in trg_module first
|
|
|
|
let modlist = self.moduleslist().await;
|
|
let rslt = modlist.get(&trg_module);
|
|
|
|
if rslt.is_none() {
|
|
return ChangeResult::Failed("Module doesn't exist".to_string());
|
|
}
|
|
|
|
botlog::trace(
|
|
"ACQUIRING WRITE LOCK : ID",
|
|
Some("ModulesManager > Exec_disable".to_string()),
|
|
None,
|
|
);
|
|
|
|
|
|
|
|
let mut idlock = id.write().await;
|
|
|
|
// if trg_level = StatusLvl::Instance , the temp_chnl = the broadcaster's or the chatter's
|
|
|
|
let arb_chnl = match trg_level.clone() {
|
|
StatusLvl::Instance => Channel(requestor.to_lowercase()),
|
|
StatusLvl::Ch(a) => a,
|
|
};
|
|
|
|
const OF_CMD_CHANNEL:Channel = Channel(String::new());
|
|
|
|
let (admin_level_access,_) = idlock.can_user_run(requestor.clone(), arb_chnl.clone(), requestor_badge.clone(),
|
|
vec![
|
|
identity::UserRole::BotAdmin,
|
|
]).await;
|
|
|
|
|
|
let (chnl_elevated_access,_) = idlock.can_user_run(requestor, arb_chnl, requestor_badge.clone(),
|
|
vec![
|
|
identity::UserRole::Mod(OF_CMD_CHANNEL),
|
|
identity::UserRole::SupMod(OF_CMD_CHANNEL),
|
|
identity::UserRole::Broadcaster,
|
|
]).await;
|
|
|
|
|
|
/*
|
|
|
|
[x] 1. If CmdSender is BotAdmin but not (Mod,SupMod,Broadcaster)
|
|
1. can_user_run for cmdreqRoles including BotAdmin & not can_user_run for cmdreqRoles (Mod,SupMod,Broadcaster)
|
|
1a. , and is -f (forced) , return a Success
|
|
1b. , and is -i (to instance) , return a Success
|
|
1c. , and has no special flags (-i / -f) , return a Failure recommending BotAdmin promote themselves first
|
|
|
|
*/
|
|
|
|
if let Permissible::Allow = admin_level_access {
|
|
if let Permissible::Block = chnl_elevated_access {
|
|
if force {
|
|
self.force_disable(trg_module.clone()).await;
|
|
return ChangeResult::Success("Forced Disable".to_string());
|
|
} else {
|
|
match trg_level {
|
|
StatusLvl::Instance => {
|
|
self.set_instance_disabled(trg_module.clone()).await;
|
|
return ChangeResult::Success("Disabled at Instance Level".to_string());
|
|
},
|
|
StatusLvl::Ch(_) => {
|
|
return ChangeResult::Failed("Promote yourself Temporarily First".to_string());
|
|
},
|
|
};
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
[x] 2. If CmdSender not a BotAdmin but is (Mod,SupMod,Broadcaster)
|
|
2. not can_user_run for cmdreqRoles including BotAdmin & can_user_run for cmdreqRoles (Mod,SupMod,Broadcaster)
|
|
2a. , and is -f (forced) , return a Failure they are not allowed
|
|
2b. , and is -i (to instance) , return a Failure they are not allowed
|
|
2c. , and has no special flags (-i / -f) , return a Success
|
|
|
|
*/
|
|
|
|
if let Permissible::Block = admin_level_access {
|
|
if let Permissible::Allow = chnl_elevated_access {
|
|
if force {
|
|
return ChangeResult::Failed("You're not allowed".to_string());
|
|
} else {
|
|
match trg_level.clone() {
|
|
StatusLvl::Instance => {
|
|
return ChangeResult::Failed("You're not allowed".to_string());
|
|
},
|
|
StatusLvl::Ch(in_chnl) => {
|
|
self.set_ch_disabled(trg_module.clone(), in_chnl).await;
|
|
return ChangeResult::Success("Disabled at Channel Level".to_string());
|
|
},
|
|
};
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
[x] 3. If CmdSender is (Mod,SupMod,Broadcaster) and a BotAdmin
|
|
3. can_user_run for cmdreqRoles (Mod,SupMod,Broadcaster) & can_user_run for cmdreqRoles including BotAdmin
|
|
3a. , and is -f (forced) , return a Success
|
|
3b. , and is -i (to instance) , return a Success
|
|
3c. , and has no special flags (-i / -f) , return a Success
|
|
|
|
*/
|
|
|
|
if let Permissible::Allow = admin_level_access {
|
|
if let Permissible::Allow = chnl_elevated_access {
|
|
if force {
|
|
self.force_disable(trg_module.clone()).await;
|
|
return ChangeResult::Success("Forced Disable".to_string());
|
|
} else {
|
|
match trg_level {
|
|
StatusLvl::Instance => {
|
|
self.set_instance_disabled(trg_module.clone()).await;
|
|
return ChangeResult::Success("Disabled at Instance Level".to_string());
|
|
},
|
|
StatusLvl::Ch(in_chnl) => {
|
|
self.set_ch_disabled(trg_module.clone(), in_chnl).await;
|
|
return ChangeResult::Success("Disabled at Channel Level".to_string());
|
|
},
|
|
};
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
|
|
// Respond in case of General Chatter
|
|
// The below should NOT be required , as current internal logic would prevent
|
|
// a BotCommand to be ran by a Chatter if it requires any special roles and
|
|
// that chatter does not have htose roles
|
|
// However, below is added to satisfy unit tests
|
|
|
|
|
|
if let Permissible::Block = admin_level_access {
|
|
if let Permissible::Block = chnl_elevated_access {
|
|
match trg_level {
|
|
StatusLvl::Instance => {
|
|
return ChangeResult::Failed("You're not allowed".to_string());
|
|
},
|
|
StatusLvl::Ch(_) => {
|
|
return ChangeResult::Failed("You're not allowed".to_string());
|
|
},
|
|
};
|
|
}
|
|
}
|
|
|
|
|
|
|
|
ChangeResult::Failed("ERROR : Not implemented yet".to_string())
|
|
}
|
|
|
|
pub async fn set_instance_disabled(&self, in_module: BotModule) -> (StatusType,ChangeResult) {
|
|
// at Instance level
|
|
// - If core module, do nothing
|
|
|
|
let mut dbt = self.statusdb.write().await;
|
|
|
|
let (mgrp,statusvector) = dbt.get_mut(&in_module).unwrap();
|
|
|
|
match mgrp {
|
|
ModGroup::Core => {
|
|
(
|
|
StatusType::Enabled(StatusLvl::Instance),
|
|
ChangeResult::Failed("Core Modules cannot be disabled".to_string())
|
|
)
|
|
},
|
|
ModGroup::Custom => {
|
|
// remove all instance level pattern for the module
|
|
while let Some(index) = statusvector
|
|
.iter()
|
|
.position(|x| (*x == StatusType::Enabled(StatusLvl::Instance)) || (*x == StatusType::Disabled(StatusLvl::Instance))) {
|
|
|
|
statusvector.remove(index);
|
|
}
|
|
statusvector.push(StatusType::Disabled(StatusLvl::Instance));
|
|
|
|
(
|
|
StatusType::Disabled(StatusLvl::Instance),
|
|
ChangeResult::Success("Set Disabled at Instance".to_string())
|
|
)
|
|
},
|
|
}
|
|
|
|
}
|
|
|
|
pub async fn force_disable(&self, in_module: BotModule) -> (StatusType,ChangeResult) {
|
|
// Disables the module at Instance level, and removes all Enabled at Channel level
|
|
// - Bot Moderators MUST Re-enable if they were enabled before
|
|
// - If core module, do nothing
|
|
|
|
let mut dbt = self.statusdb.write().await;
|
|
|
|
let (mgrp,statusvector) = dbt.get_mut(&in_module).unwrap();
|
|
|
|
match mgrp {
|
|
ModGroup::Core => {
|
|
(
|
|
StatusType::Enabled(StatusLvl::Instance),
|
|
ChangeResult::Failed("Core Modules cannot be disabled".to_string())
|
|
)
|
|
},
|
|
ModGroup::Custom => {
|
|
// remove all instance level pattern & Enabled Channel patterns for the module
|
|
// Disabled at Channel level might be fine? That way if it gets Enabled at instance level, channel level disables are uninterrupted
|
|
while let Some(index) = statusvector
|
|
.iter()
|
|
.position(|x|
|
|
if (*x == StatusType::Enabled(StatusLvl::Instance))
|
|
|| (*x == StatusType::Disabled(StatusLvl::Instance)) {
|
|
true
|
|
} else {
|
|
matches!((*x).clone(), StatusType::Enabled(StatusLvl::Ch(_)))
|
|
}
|
|
)
|
|
{
|
|
statusvector.remove(index);
|
|
}
|
|
statusvector.push(StatusType::Disabled(StatusLvl::Instance));
|
|
|
|
(
|
|
StatusType::Disabled(StatusLvl::Instance),
|
|
ChangeResult::Success("Forced Disabled".to_string())
|
|
)
|
|
},
|
|
}
|
|
|
|
}
|
|
|
|
pub async fn set_instance_enabled(&self, in_module: BotModule) -> (StatusType,ChangeResult) {
|
|
// at Instance level
|
|
// - If core module, do nothing
|
|
|
|
let mut dbt = self.statusdb.write().await;
|
|
|
|
let (mgrp,statusvector) = dbt.get_mut(&in_module).unwrap();
|
|
|
|
match mgrp {
|
|
ModGroup::Core => {
|
|
(
|
|
StatusType::Enabled(StatusLvl::Instance),
|
|
ChangeResult::NoChange("Core Modules are always Enabled".to_string())
|
|
)
|
|
},
|
|
ModGroup::Custom => {
|
|
// remove all instance level pattern for the module
|
|
while let Some(index) = statusvector
|
|
.iter()
|
|
.position(|x| (*x == StatusType::Enabled(StatusLvl::Instance)) || (*x == StatusType::Disabled(StatusLvl::Instance))) {
|
|
|
|
statusvector.remove(index);
|
|
}
|
|
statusvector.push(StatusType::Enabled(StatusLvl::Instance));
|
|
|
|
(
|
|
StatusType::Enabled(StatusLvl::Instance),
|
|
ChangeResult::Success("Set Enabled at Instance".to_string())
|
|
)
|
|
},
|
|
}
|
|
|
|
}
|
|
|
|
pub async fn set_ch_disabled(&self, in_module: BotModule , in_chnl: Channel) -> (StatusType,ChangeResult) {
|
|
// at Instance level
|
|
// - If core module, do nothing
|
|
|
|
|
|
let mut dbt = self.statusdb.write().await;
|
|
|
|
let (mgrp,statusvector) = dbt.get_mut(&in_module).unwrap();
|
|
|
|
match mgrp {
|
|
ModGroup::Core => {
|
|
(
|
|
StatusType::Enabled(StatusLvl::Instance),
|
|
ChangeResult::Failed("Core Modules cannot be disabled".to_string())
|
|
)
|
|
},
|
|
ModGroup::Custom => {
|
|
// remove all channel level pattern for the module
|
|
while let Some(index) = statusvector
|
|
.iter()
|
|
.position(|x|
|
|
(*x == StatusType::Enabled(StatusLvl::Ch(in_chnl.clone()))) || (*x == StatusType::Disabled(StatusLvl::Ch(in_chnl.clone()))))
|
|
{
|
|
|
|
statusvector.remove(index);
|
|
}
|
|
statusvector.push(StatusType::Disabled(StatusLvl::Ch(in_chnl.clone())));
|
|
|
|
(
|
|
StatusType::Disabled(StatusLvl::Ch(in_chnl.clone())),
|
|
ChangeResult::Success("Set Disabled at Channel Level".to_string())
|
|
)
|
|
},
|
|
}
|
|
|
|
}
|
|
|
|
pub async fn set_ch_enabled(&self, in_module: BotModule , in_chnl: Channel) -> (StatusType,ChangeResult) {
|
|
// at Instance level
|
|
// - If core module, do nothing
|
|
|
|
let mut dbt = self.statusdb.write().await;
|
|
|
|
let (mgrp,statusvector) = dbt.get_mut(&in_module).unwrap();
|
|
|
|
match mgrp {
|
|
ModGroup::Core => {
|
|
(
|
|
StatusType::Enabled(StatusLvl::Instance),
|
|
ChangeResult::NoChange("Core Modules are always Enabled".to_string())
|
|
)
|
|
},
|
|
ModGroup::Custom => {
|
|
// remove all channel level pattern for the module
|
|
while let Some(index) = statusvector
|
|
.iter()
|
|
.position(|x|
|
|
(*x == StatusType::Enabled(StatusLvl::Ch(in_chnl.clone()))) || (*x == StatusType::Disabled(StatusLvl::Ch(in_chnl.clone()))))
|
|
{
|
|
|
|
statusvector.remove(index);
|
|
}
|
|
statusvector.push(StatusType::Enabled(StatusLvl::Ch(in_chnl.clone())));
|
|
|
|
(
|
|
StatusType::Enabled(StatusLvl::Ch(in_chnl.clone())),
|
|
ChangeResult::Success("Set Enabled at Channel Level".to_string())
|
|
)
|
|
},
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
pub async fn add_botaction(&self, in_module: BotModule, in_action: BotAction) {
|
|
self.int_add_botaction(in_module,ModGroup::Custom,in_action).await;
|
|
}
|
|
|
|
pub async fn add_core_act(&self, in_module: BotModule, in_action: BotAction) {
|
|
self.int_add_botaction(in_module,ModGroup::Core,in_action).await;
|
|
}
|
|
|
|
|
|
pub async fn affirm_in_statusdb(&self,in_module:BotModule,in_modgroup: ModGroup) {
|
|
|
|
let mut dbt = self.statusdb.write().await;
|
|
|
|
let (_,statusvector) = dbt.entry(in_module.clone()).or_insert((in_modgroup.clone(),Vec::new()));
|
|
|
|
if !statusvector.contains(&StatusType::Enabled(StatusLvl::Instance)) && !statusvector.contains(&StatusType::Disabled(StatusLvl::Instance))
|
|
{
|
|
match in_modgroup {
|
|
ModGroup::Core => statusvector.push(StatusType::Enabled(StatusLvl::Instance)) , // Pushes the Module as Enabled at Instance Level
|
|
ModGroup::Custom => statusvector.push(StatusType::Disabled(StatusLvl::Instance)),
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
async fn int_add_botaction(&self, in_module: BotModule, in_modgroup: ModGroup, in_action: BotAction) {
|
|
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
|
|
If it fails to add, either a PANIC or some default coded business rules that handles the botaction add
|
|
For example, this Should PANIC (ideally Panic?) if it does not successfully add a bot module
|
|
-- Being unable to indicates a Programming/Developer code logic issue : They cannot add botactions that already exists (?)
|
|
-- 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
|
|
*/
|
|
|
|
// [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
|
|
|
|
async fn find_conflict_module(mgr: &ModulesManager, act: &BotAction) -> Option<BotModule> {
|
|
|
|
if let BotAction::C(incmd) = act {
|
|
let actdb = mgr.botactions.read().await;
|
|
|
|
for (module, moduleactions) in &(*actdb) {
|
|
|
|
|
|
// for modact in moduleactions.iter() {
|
|
for modact_prelock in moduleactions.iter() {
|
|
|
|
let modact = modact_prelock.read().await;
|
|
|
|
// if let BotAction::C(dbcmd) = &modact {
|
|
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
|
|
|
|
if incmd.command.to_lowercase() == dbcmd.command.to_lowercase() {
|
|
// Returning State - with the identified module
|
|
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()); // 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()); // works
|
|
}
|
|
|
|
for a in &dbcmd.alias {
|
|
if inalias.to_lowercase() == a.to_lowercase() {
|
|
// Returning State - with the identified module
|
|
|
|
return Some(module.clone()); // works
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// for all other scenarios (e.g., Listener, Routine), find no conflicts
|
|
None
|
|
}
|
|
|
|
if let Some(c) = find_conflict_module(self, &in_action).await {
|
|
panic!(
|
|
"ERROR: Could not add module; there was a conflict with existing module {:?}",
|
|
c
|
|
)
|
|
}
|
|
|
|
self.affirm_in_statusdb(in_module.clone(),in_modgroup).await;
|
|
|
|
let mut a = self.botactions.write().await;
|
|
let modactions = a.entry(in_module.clone()).or_insert(Vec::new());
|
|
|
|
modactions.push(Arc::new(RwLock::new(in_action)));
|
|
|
|
botlog::trace(
|
|
format!(
|
|
"Modules Manager> add_botaction called - botactions size : {}",
|
|
modactions.len()
|
|
)
|
|
.as_str(),
|
|
Some("ModulesManager > init()".to_string()),
|
|
None,
|
|
);
|
|
}
|
|
|
|
fn _statuscleanup(&self, _: Option<Channel>) {
|
|
// 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
|
|
// Option<Channel> can pass Some(Channel("m")) (as an example) so statuscleanup only works on the given channel
|
|
// Passing None to chnl may be a heavy operation, as this will review and look at the whole table
|
|
}
|
|
}
|
|
|
|
|
|
// =====================
|
|
// =====================
|
|
// =====================
|
|
// =====================
|
|
// =====================
|
|
|
|
#[cfg(test)]
|
|
mod core_modulesmanager {
|
|
|
|
|
|
use casual_logger::Log;
|
|
use casual_logger::Extension;
|
|
|
|
use super::*;
|
|
|
|
#[test]
|
|
fn case_insensitive_test() {
|
|
Log::set_file_ext(Extension::Log);
|
|
assert_eq!(
|
|
BotModule("TEST".to_string()),
|
|
BotModule("test".to_string())
|
|
);
|
|
}
|
|
|
|
|
|
/*
|
|
Possible Tests
|
|
|
|
[x] Test 1 - Custom ModGroup Workflow
|
|
1. affirm_in_statusdb(Experiments01,Custom)
|
|
2. modstatus(Experiments01,TestChannel01) & modstatus(Experiments01,TestChannel02)
|
|
3. set_instance_enabled(Experiments01)
|
|
4. modstatus(Experiments01,TestChannel01) & modstatus(Experiments01,TestChannel02)
|
|
5. set_ch_disabled(Experiments01,TestChannel01)
|
|
6. modstatus(Experiments01,TestChannel01) & modstatus(Experiments01,TestChannel02)
|
|
7. set_ch_enabled(Experiments01,TestChannel01) & set_ch_disabled(Experiments01,TestChannel02)
|
|
8. modstatus(Experiments01,TestChannel01) & modstatus(Experiments01,TestChannel02)
|
|
9. set_instance_disabled(Experiments01)
|
|
10. modstatus(Experiments01,TestChannel01) & modstatus(Experiments01,TestChannel02)
|
|
11. force_disable(Experiments01)
|
|
12. modstatus(Experiments01,TestChannel01) & modstatus(Experiments01,TestChannel02)
|
|
|
|
|
|
[x] Test 2 - Core ModGroup Workflow
|
|
1. affirm_in_statusdb(CoreModule01,Core)
|
|
2. modstatus(CoreModule01,TestChannel01) & modstatus(CoreModule01,TestChannel02)
|
|
3. set_instance_enabled(CoreModule01)
|
|
4. modstatus(CoreModule01,TestChannel01) & modstatus(CoreModule01,TestChannel02)
|
|
5. set_ch_disabled(CoreModule01,TestChannel01)
|
|
6. modstatus(CoreModule01,TestChannel01) & modstatus(CoreModule01,TestChannel02)
|
|
7. set_ch_enabled(CoreModule01,TestChannel01) & set_ch_disabled(CoreModule01,TestChannel02)
|
|
8. modstatus(CoreModule01,TestChannel01) & modstatus(CoreModule01,TestChannel02)
|
|
9. set_instance_disabled(CoreModule01)
|
|
10. modstatus(CoreModule01,TestChannel01) & modstatus(CoreModule01,TestChannel02)
|
|
11. force_disable(CoreModule01)
|
|
12. modstatus(CoreModule01,TestChannel01) & modstatus(CoreModule01,TestChannel02)
|
|
|
|
|
|
*/
|
|
|
|
async fn complex_workflow(
|
|
in_module: BotModule ,
|
|
in_modgroup : ModGroup ,
|
|
in_chnl1 : Channel,
|
|
in_chnl2 : Channel)
|
|
{
|
|
|
|
|
|
let mgr = ModulesManager::init().await;
|
|
|
|
/*
|
|
1. affirm_in_statusdb(Experiments01,Custom)
|
|
2. modstatus(Experiments01,TestChannel01) & modstatus(Experiments01,TestChannel02)
|
|
*/
|
|
|
|
mgr.affirm_in_statusdb(in_module.clone(), in_modgroup.clone()).await;
|
|
|
|
match in_modgroup {
|
|
ModGroup::Custom => {
|
|
assert_eq!(mgr.modstatus(in_module.clone(), in_chnl1.clone()).await,
|
|
StatusType::Disabled(StatusLvl::Instance));
|
|
assert_eq!(mgr.modstatus(in_module.clone(), in_chnl2.clone()).await,
|
|
StatusType::Disabled(StatusLvl::Instance));
|
|
},
|
|
ModGroup::Core => {
|
|
assert_eq!(mgr.modstatus(in_module.clone(), in_chnl1.clone()).await,
|
|
StatusType::Enabled(StatusLvl::Instance));
|
|
assert_eq!(mgr.modstatus(in_module.clone(), in_chnl2.clone()).await,
|
|
StatusType::Enabled(StatusLvl::Instance));
|
|
},
|
|
}
|
|
|
|
|
|
/*
|
|
3. set_instance_enabled(Experiments01)
|
|
4. modstatus(Experiments01,TestChannel01) & modstatus(Experiments01,TestChannel02)
|
|
*/
|
|
mgr.set_instance_enabled(in_module.clone()).await;
|
|
|
|
match in_modgroup {
|
|
ModGroup::Custom => {
|
|
assert_eq!(mgr.modstatus(in_module.clone(), in_chnl1.clone()).await,
|
|
StatusType::Enabled(StatusLvl::Instance));
|
|
assert_eq!(mgr.modstatus(in_module.clone(), in_chnl2.clone()).await,
|
|
StatusType::Enabled(StatusLvl::Instance));
|
|
},
|
|
ModGroup::Core => {
|
|
assert_eq!(mgr.modstatus(in_module.clone(), in_chnl1.clone()).await,
|
|
StatusType::Enabled(StatusLvl::Instance));
|
|
assert_eq!(mgr.modstatus(in_module.clone(), in_chnl2.clone()).await,
|
|
StatusType::Enabled(StatusLvl::Instance));
|
|
},
|
|
}
|
|
|
|
/*
|
|
5. set_ch_disabled(Experiments01,TestChannel01)
|
|
6. modstatus(Experiments01,TestChannel01) & modstatus(Experiments01,TestChannel02)
|
|
*/
|
|
|
|
mgr.set_ch_disabled(in_module.clone(),in_chnl1.clone()).await;
|
|
|
|
//StatusType::Disabled(StatusLvl::Ch(in_chnl1.clone()))
|
|
|
|
match in_modgroup {
|
|
ModGroup::Custom => {
|
|
assert_eq!(mgr.modstatus(in_module.clone(), in_chnl1.clone()).await,
|
|
StatusType::Disabled(StatusLvl::Ch(in_chnl1.clone())));
|
|
assert_eq!(mgr.modstatus(in_module.clone(), in_chnl2.clone()).await,
|
|
StatusType::Enabled(StatusLvl::Instance));
|
|
},
|
|
ModGroup::Core => {
|
|
assert_eq!(mgr.modstatus(in_module.clone(), in_chnl1.clone()).await,
|
|
StatusType::Enabled(StatusLvl::Instance));
|
|
assert_eq!(mgr.modstatus(in_module.clone(), in_chnl2.clone()).await,
|
|
StatusType::Enabled(StatusLvl::Instance));
|
|
},
|
|
}
|
|
|
|
|
|
/*
|
|
7. set_ch_enabled(Experiments01,TestChannel01) & set_ch_disabled(Experiments01,TestChannel02)
|
|
8. modstatus(Experiments01,TestChannel01) & modstatus(Experiments01,TestChannel02)
|
|
*/
|
|
|
|
mgr.set_ch_enabled(in_module.clone(),in_chnl1.clone()).await;
|
|
|
|
//StatusType::Disabled(StatusLvl::Ch(in_chnl1.clone()))
|
|
|
|
match in_modgroup {
|
|
ModGroup::Custom => {
|
|
assert_eq!(mgr.modstatus(in_module.clone(), in_chnl1.clone()).await,
|
|
StatusType::Enabled(StatusLvl::Ch(in_chnl1.clone())));
|
|
assert_eq!(mgr.modstatus(in_module.clone(), in_chnl2.clone()).await,
|
|
StatusType::Enabled(StatusLvl::Instance));
|
|
},
|
|
ModGroup::Core => {
|
|
assert_eq!(mgr.modstatus(in_module.clone(), in_chnl1.clone()).await,
|
|
StatusType::Enabled(StatusLvl::Instance));
|
|
assert_eq!(mgr.modstatus(in_module.clone(), in_chnl2.clone()).await,
|
|
StatusType::Enabled(StatusLvl::Instance));
|
|
},
|
|
}
|
|
|
|
/*
|
|
9. set_instance_disabled(Experiments01)
|
|
10. modstatus(Experiments01,TestChannel01) & modstatus(Experiments01,TestChannel02)
|
|
*/
|
|
|
|
mgr.set_instance_disabled(in_module.clone()).await;
|
|
|
|
// StatusType::Disabled(StatusLvl::Ch(in_chnl1.clone()))
|
|
|
|
match in_modgroup {
|
|
ModGroup::Custom => {
|
|
assert_eq!(mgr.modstatus(in_module.clone(), in_chnl1.clone()).await,
|
|
StatusType::Enabled(StatusLvl::Ch(in_chnl1.clone())));
|
|
assert_eq!(mgr.modstatus(in_module.clone(), in_chnl2.clone()).await,
|
|
StatusType::Disabled(StatusLvl::Instance));
|
|
},
|
|
ModGroup::Core => {
|
|
assert_eq!(mgr.modstatus(in_module.clone(), in_chnl1.clone()).await,
|
|
StatusType::Enabled(StatusLvl::Instance));
|
|
assert_eq!(mgr.modstatus(in_module.clone(), in_chnl2.clone()).await,
|
|
StatusType::Enabled(StatusLvl::Instance));
|
|
},
|
|
}
|
|
/*
|
|
11. force_disable(Experiments01)
|
|
12. modstatus(Experiments01,TestChannel01) & modstatus(Experiments01,TestChannel02)
|
|
*/
|
|
|
|
mgr.force_disable(in_module.clone()).await;
|
|
|
|
match in_modgroup {
|
|
ModGroup::Custom => {
|
|
assert_eq!(mgr.modstatus(in_module.clone(), in_chnl1.clone()).await,
|
|
StatusType::Disabled(StatusLvl::Instance));
|
|
assert_eq!(mgr.modstatus(in_module.clone(), in_chnl2.clone()).await,
|
|
StatusType::Disabled(StatusLvl::Instance));
|
|
},
|
|
ModGroup::Core => {
|
|
assert_eq!(mgr.modstatus(in_module.clone(), in_chnl1.clone()).await,
|
|
StatusType::Enabled(StatusLvl::Instance));
|
|
assert_eq!(mgr.modstatus(in_module.clone(), in_chnl2.clone()).await,
|
|
StatusType::Enabled(StatusLvl::Instance));
|
|
},
|
|
}
|
|
|
|
}
|
|
|
|
|
|
#[tokio::test]
|
|
async fn custom_modgroup_workflow() {
|
|
Log::set_file_ext(Extension::Log);
|
|
|
|
/*
|
|
|
|
|
|
[x] Test 1 - Custom ModGroup Workflow
|
|
1. affirm_in_statusdb(Experiments01,Custom)
|
|
2. modstatus(Experiments01,TestChannel01) & modstatus(Experiments01,TestChannel02)
|
|
3. set_instance_enabled(Experiments01)
|
|
4. modstatus(Experiments01,TestChannel01) & modstatus(Experiments01,TestChannel02)
|
|
5. set_ch_disabled(Experiments01,TestChannel01)
|
|
6. modstatus(Experiments01,TestChannel01) & modstatus(Experiments01,TestChannel02)
|
|
7. set_ch_enabled(Experiments01,TestChannel01) & set_ch_disabled(Experiments01,TestChannel02)
|
|
8. modstatus(Experiments01,TestChannel01) & modstatus(Experiments01,TestChannel02)
|
|
9. set_instance_disabled(Experiments01)
|
|
10. modstatus(Experiments01,TestChannel01) & modstatus(Experiments01,TestChannel02)
|
|
11. force_disable(Experiments01)
|
|
12. modstatus(Experiments01,TestChannel01) & modstatus(Experiments01,TestChannel02)
|
|
|
|
*/
|
|
|
|
let in_module = BotModule("Experiments01".to_string());
|
|
let in_modgroup = ModGroup::Custom;
|
|
let (in_chnl1,in_chnl2) =
|
|
(Channel("TestChannel01".to_string()),Channel("TestChannel02".to_string()));
|
|
|
|
complex_workflow(in_module, in_modgroup, in_chnl1, in_chnl2).await;
|
|
|
|
|
|
}
|
|
|
|
|
|
#[tokio::test]
|
|
async fn core_modgroup_workflow() {
|
|
Log::set_file_ext(Extension::Log);
|
|
|
|
|
|
let in_module = BotModule("CoreModule01".to_string());
|
|
let in_modgroup = ModGroup::Core;
|
|
let (in_chnl1,in_chnl2) =
|
|
(Channel("TestChannel01".to_string()),Channel("TestChannel02".to_string()));
|
|
|
|
complex_workflow(in_module, in_modgroup, in_chnl1, in_chnl2).await;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/*
|
|
1. Create new ModulesManager & Identity Manager
|
|
2. modmgr.affirm_in_statusdb(Experiments01,Custom)
|
|
|
|
3. affirm when BotAdmin attempts to exec_enable on the following
|
|
a. Channel Level , where they are not a Mod
|
|
b. Channel Level , when they are a Mod
|
|
c. Instance Level
|
|
4. affirm when BotAdmin attempts to exec_disable on the following
|
|
a. Channel Level , where they are not a Mod
|
|
b. Channel Level , when they are a Mod
|
|
c. Instance Level
|
|
d. force disable
|
|
|
|
|
|
1. Create new ModulesManager & Identity Manager
|
|
2. modmgr.affirm_in_statusdb(Experiments01,Custom)
|
|
|
|
3. affirm when Mod attempts to exec_enable on the following
|
|
a. Channel Level , where they are not a Mod
|
|
b. Channel Level , when they are a Mod
|
|
c. Instance Level
|
|
4. affirm when Mod attempts to exec_disable on the following
|
|
a. Channel Level , where they are not a Mod
|
|
b. Channel Level , when they are a Mod
|
|
c. Instance Level
|
|
d. force disable
|
|
|
|
*/
|
|
|
|
async fn inner_enable_disable_complex(
|
|
requestor:String,
|
|
channel:Channel,
|
|
idmgr:IdentityManager,
|
|
modsmgr:Arc<ModulesManager>)
|
|
{
|
|
|
|
/*
|
|
Parent Tests would involve :
|
|
- Testing with a BotAdmin User
|
|
- Testing with a Mod User
|
|
- Testing with a Regular Chatter
|
|
*/
|
|
|
|
enum TestScenarios {
|
|
BotadminUser,
|
|
ModUser,
|
|
RegularChatter,
|
|
// ModuleDoesNotExist, // preferring instead to handle in it's own smaller test
|
|
}
|
|
|
|
let mut idlock = idmgr.clone();
|
|
|
|
let requestor_badge = None; // If they are a Mod on the Given Channel already, that can be evaluated without the current badge
|
|
|
|
const OF_CMD_CHANNEL:Channel = Channel(String::new());
|
|
|
|
let (admin_level_access,_) = idlock.can_user_run(requestor.clone(), channel.clone(), requestor_badge.clone(),
|
|
vec![
|
|
identity::UserRole::BotAdmin,
|
|
]).await;
|
|
|
|
|
|
let (chnl_elevated_access,_) = idlock.can_user_run(requestor.clone(), channel.clone(), requestor_badge.clone(),
|
|
vec![
|
|
identity::UserRole::Mod(OF_CMD_CHANNEL),
|
|
identity::UserRole::SupMod(OF_CMD_CHANNEL),
|
|
identity::UserRole::Broadcaster,
|
|
]).await;
|
|
|
|
|
|
let current_test_scenario =
|
|
match admin_level_access {
|
|
Permissible::Allow => {
|
|
match chnl_elevated_access {
|
|
Permissible::Allow => { TestScenarios::BotadminUser },
|
|
Permissible::Block => { TestScenarios::BotadminUser }
|
|
}
|
|
},
|
|
Permissible::Block => {
|
|
match chnl_elevated_access {
|
|
Permissible::Allow => { TestScenarios::ModUser },
|
|
Permissible::Block => { TestScenarios::RegularChatter }
|
|
}
|
|
}
|
|
};
|
|
|
|
|
|
|
|
// [x] 2. modmgr.affirm_in_statusdb(Experiments01,Custom)
|
|
|
|
let in_module = BotModule("Experiments01".to_string());
|
|
let in_modgroup = ModGroup::Custom;
|
|
|
|
modsmgr.affirm_in_statusdb(in_module.clone(), in_modgroup.clone()).await;
|
|
|
|
/*
|
|
[x] 3. affirm when BotAdmin attempts to exec_enable on the following
|
|
a. Channel Level , where they are not a Mod
|
|
*/
|
|
|
|
|
|
// [-] requestor_badge: Option<ChatBadge>,
|
|
|
|
// [x] trg_module: BotModule,
|
|
let trg_module = in_module;
|
|
|
|
// [x] trg_level: StatusLvl,
|
|
|
|
let trg_level = StatusLvl::Ch(channel.clone()); // setting to Channel Level
|
|
|
|
|
|
// [x] id: Arc<RwLock<IdentityManager>>,
|
|
let id = Arc::new(RwLock::new(idmgr.clone()));
|
|
|
|
|
|
let rslt = modsmgr.exec_enable(requestor.clone(),
|
|
None,
|
|
trg_module.clone(),
|
|
trg_level.clone(),
|
|
id.clone()).await;
|
|
|
|
match current_test_scenario {
|
|
TestScenarios::BotadminUser =>
|
|
assert_eq!(rslt,ChangeResult::Failed("Promote yourself Temporarily First".to_string())),
|
|
TestScenarios::ModUser =>
|
|
assert_eq!(rslt,ChangeResult::Success("Enabled at Channel Level".to_string())),
|
|
TestScenarios::RegularChatter =>
|
|
assert_eq!(rslt,ChangeResult::Failed("You're not allowed".to_string())),
|
|
}
|
|
|
|
|
|
/*
|
|
[x] 3. affirm when BotAdmin attempts to exec_enable on the following
|
|
b. Channel Level , when they are a Mod
|
|
*/
|
|
|
|
// [x] requestor_badge: Option<ChatBadge>,
|
|
|
|
let requestor_badge = match current_test_scenario {
|
|
TestScenarios::BotadminUser =>
|
|
Some(ChatBadge::Mod), // setting badge to Mod -- for the Problem Scenario . They are both BotAdmin & Mod
|
|
TestScenarios::ModUser =>
|
|
Some(ChatBadge::Mod), // setting badge to Mod
|
|
TestScenarios::RegularChatter =>
|
|
None, // setting badge to None
|
|
} ;
|
|
|
|
|
|
let rslt = modsmgr.exec_enable(requestor.clone(),
|
|
requestor_badge,
|
|
trg_module.clone(),
|
|
trg_level.clone(),
|
|
id.clone()).await;
|
|
|
|
match current_test_scenario {
|
|
TestScenarios::BotadminUser =>
|
|
assert_eq!(rslt,ChangeResult::Success("Enabled at Channel Level".to_string())),
|
|
TestScenarios::ModUser =>
|
|
assert_eq!(rslt,ChangeResult::Success("Enabled at Channel Level".to_string())),
|
|
TestScenarios::RegularChatter =>
|
|
assert_eq!(rslt,ChangeResult::Failed("You're not allowed".to_string())),
|
|
}
|
|
|
|
/*
|
|
[x] 3. affirm when BotAdmin attempts to exec_enable on the following
|
|
c. Instance Level
|
|
*/
|
|
|
|
let trg_level = StatusLvl::Instance; // setting to Instance level
|
|
|
|
let requestor_badge = match current_test_scenario {
|
|
TestScenarios::BotadminUser =>
|
|
None,
|
|
TestScenarios::ModUser =>
|
|
Some(ChatBadge::Mod),
|
|
TestScenarios::RegularChatter =>
|
|
None, // setting badge to None
|
|
};
|
|
|
|
let rslt = modsmgr.exec_enable(requestor.clone(),
|
|
requestor_badge, // passing based on scenario
|
|
trg_module.clone(),
|
|
trg_level.clone(),
|
|
id.clone()).await;
|
|
|
|
|
|
match current_test_scenario {
|
|
TestScenarios::BotadminUser =>
|
|
assert_eq!(rslt,ChangeResult::Success("Enabled at Instance Level".to_string())),
|
|
TestScenarios::ModUser =>
|
|
assert_eq!(rslt,ChangeResult::Failed("You're not allowed".to_string())),
|
|
TestScenarios::RegularChatter =>
|
|
assert_eq!(rslt,ChangeResult::Failed("You're not allowed".to_string())),
|
|
}
|
|
|
|
/*
|
|
[x] 4. affirm when BotAdmin attempts to exec_disable on the following
|
|
a. Channel Level , where they are not a Mod
|
|
*/
|
|
|
|
let trg_level = StatusLvl::Ch(channel.clone()); // setting to Channel Level
|
|
|
|
let rslt: ChangeResult = modsmgr.exec_disable(requestor.clone(),
|
|
None, // Does not have a ChatBadge like Mod
|
|
trg_module.clone(),
|
|
trg_level.clone(),
|
|
false,
|
|
id.clone()).await;
|
|
|
|
match current_test_scenario {
|
|
TestScenarios::BotadminUser =>
|
|
assert_eq!(rslt,ChangeResult::Success("Disabled at Channel Level".to_string())),
|
|
TestScenarios::ModUser =>
|
|
assert_eq!(rslt,ChangeResult::Success("Disabled at Channel Level".to_string())),
|
|
TestScenarios::RegularChatter =>
|
|
assert_eq!(rslt,ChangeResult::Failed("You're not allowed".to_string())),
|
|
}
|
|
|
|
|
|
/*
|
|
[x] 4. affirm when BotAdmin attempts to exec_disable on the following
|
|
b. Channel Level , when they are a Mod
|
|
*/
|
|
|
|
|
|
let trg_level = StatusLvl::Ch(channel.clone()); // setting to Channel Level
|
|
|
|
let requestor_badge = match current_test_scenario {
|
|
TestScenarios::BotadminUser =>
|
|
None,
|
|
TestScenarios::ModUser =>
|
|
Some(ChatBadge::Mod),
|
|
TestScenarios::RegularChatter =>
|
|
None, // setting badge to None
|
|
};
|
|
|
|
let rslt: ChangeResult = modsmgr.exec_disable(requestor.clone(),
|
|
requestor_badge,
|
|
trg_module.clone(),
|
|
trg_level.clone(),
|
|
false,
|
|
id.clone()).await;
|
|
|
|
|
|
match current_test_scenario {
|
|
TestScenarios::BotadminUser =>
|
|
assert_eq!(rslt,ChangeResult::Success("Disabled at Channel Level".to_string())),
|
|
TestScenarios::ModUser =>
|
|
assert_eq!(rslt,ChangeResult::Success("Disabled at Channel Level".to_string())),
|
|
TestScenarios::RegularChatter =>
|
|
assert_eq!(rslt,ChangeResult::Failed("You're not allowed".to_string())),
|
|
}
|
|
|
|
|
|
/*
|
|
[x] 4. affirm when BotAdmin attempts to exec_disable on the following
|
|
c. Instance Level
|
|
*/
|
|
|
|
let trg_level = StatusLvl::Instance; // setting to Instance level
|
|
|
|
|
|
let rslt: ChangeResult = modsmgr.exec_disable(requestor.clone(),
|
|
None, // Does not have a ChatBadge like Mod
|
|
trg_module.clone(),
|
|
trg_level.clone(),
|
|
false,
|
|
id.clone()).await;
|
|
|
|
match current_test_scenario {
|
|
TestScenarios::BotadminUser =>
|
|
assert_eq!(rslt,ChangeResult::Success("Disabled at Instance Level".to_string())),
|
|
TestScenarios::ModUser =>
|
|
assert_eq!(rslt,ChangeResult::Failed("You're not allowed".to_string())),
|
|
TestScenarios::RegularChatter =>
|
|
assert_eq!(rslt,ChangeResult::Failed("You're not allowed".to_string())),
|
|
}
|
|
|
|
/*
|
|
[ ] 4. affirm when BotAdmin attempts to exec_disable on the following
|
|
d. force disable
|
|
*/
|
|
|
|
let trg_level = StatusLvl::Instance; // setting to Instance level
|
|
|
|
let rslt: ChangeResult = modsmgr.exec_disable(requestor.clone(),
|
|
None, // Does not have a ChatBadge like Mod
|
|
trg_module.clone(),
|
|
trg_level.clone(),
|
|
true, // force flag - true
|
|
id.clone()).await;
|
|
|
|
match current_test_scenario {
|
|
TestScenarios::BotadminUser =>
|
|
assert_eq!(rslt,ChangeResult::Success("Forced Disable".to_string())),
|
|
TestScenarios::ModUser =>
|
|
assert_eq!(rslt,ChangeResult::Failed("You're not allowed".to_string())),
|
|
TestScenarios::RegularChatter =>
|
|
assert_eq!(rslt,ChangeResult::Failed("You're not allowed".to_string())),
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
#[tokio::test]
|
|
async fn enable_disable_bot_admin_workflow() {
|
|
Log::set_file_ext(Extension::Log);
|
|
/*
|
|
|
|
1. Create new ModulesManager & Identity Manager
|
|
2. modmgr.affirm_in_statusdb(Experiments01,Custom)
|
|
|
|
3. affirm when BotAdmin attempts to exec_enable on the following
|
|
a. Channel Level , where they are not a Mod
|
|
b. Channel Level , when they are a Mod
|
|
c. Instance Level
|
|
4. affirm when BotAdmin attempts to exec_disable on the following
|
|
a. Channel Level , where they are not a Mod
|
|
b. Channel Level , when they are a Mod
|
|
c. Instance Level
|
|
d. force disable
|
|
|
|
*/
|
|
|
|
|
|
// [x] 1. Create new ModulesManager & Identity Manager
|
|
let idmgr = IdentityManager::init();
|
|
let modsmgr = ModulesManager::init().await;
|
|
|
|
/*
|
|
[x] 3. affirm when BotAdmin attempts to exec_enable on the following
|
|
a. Channel Level , where they are not a Mod
|
|
*/
|
|
|
|
// [x] Create BotAdmin first
|
|
|
|
let requestor = "botadministrator".to_string();
|
|
|
|
idmgr.affirm_chatter_in_db(requestor.clone()).await;
|
|
idmgr
|
|
.add_role(requestor.clone(), identity::UserRole::BotAdmin)
|
|
.await;
|
|
|
|
let rslt = idmgr
|
|
.getspecialuserroles(requestor.clone(), None)
|
|
.await;
|
|
|
|
assert!(rslt.contains(&identity::UserRole::BotAdmin));
|
|
|
|
let channel = Channel("somechannel".to_string());
|
|
|
|
|
|
inner_enable_disable_complex(requestor, channel, idmgr, modsmgr).await;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
#[tokio::test]
|
|
async fn enable_disable_mod_workflow() {
|
|
Log::set_file_ext(Extension::Log);
|
|
|
|
/*
|
|
1. Create new ModulesManager & Identity Manager
|
|
2. modmgr.affirm_in_statusdb(Experiments01,Custom)
|
|
|
|
3. affirm when Mod attempts to exec_enable on the following
|
|
a. Channel Level , where they are not a Mod
|
|
b. Channel Level , when they are a Mod
|
|
c. Instance Level
|
|
4. affirm when Mod attempts to exec_disable on the following
|
|
a. Channel Level , where they are not a Mod
|
|
b. Channel Level , when they are a Mod
|
|
c. Instance Level
|
|
d. force disable
|
|
*/
|
|
|
|
|
|
// [x] 1. Create new ModulesManager & Identity Manager
|
|
let idmgr = IdentityManager::init();
|
|
let modsmgr = ModulesManager::init().await;
|
|
|
|
|
|
let requestor = "mod_user".to_string();
|
|
// let botadmin_badge = &None;
|
|
let channel = Channel("somechannel".to_string());
|
|
|
|
|
|
idmgr.affirm_chatter_in_db(requestor.clone()).await;
|
|
idmgr
|
|
.add_role(requestor.clone(), identity::UserRole::Mod(channel.clone()))
|
|
.await;
|
|
|
|
let rslt = idmgr
|
|
.getspecialuserroles(
|
|
requestor.clone(),
|
|
Some(channel.clone()) // None if BotAdmin ; Otherwise, pass Some(Channel)
|
|
)
|
|
.await;
|
|
|
|
assert!(rslt.contains(&identity::UserRole::Mod(channel.clone())));
|
|
|
|
|
|
inner_enable_disable_complex(requestor, channel, idmgr, modsmgr).await;
|
|
|
|
|
|
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn enable_disable_chatter_workflow() {
|
|
Log::set_file_ext(Extension::Log);
|
|
|
|
|
|
/*
|
|
1. Create new ModulesManager & Identity Manager
|
|
2. modmgr.affirm_in_statusdb(Experiments01,Custom)
|
|
|
|
3. affirm when Mod attempts to exec_enable on the following
|
|
a. Channel Level , where they are not a Mod
|
|
b. Channel Level , when they are a Mod
|
|
c. Instance Level
|
|
4. affirm when Mod attempts to exec_disable on the following
|
|
a. Channel Level , where they are not a Mod
|
|
b. Channel Level , when they are a Mod
|
|
c. Instance Level
|
|
d. force disable
|
|
*/
|
|
|
|
|
|
// [x] 1. Create new ModulesManager & Identity Manager
|
|
let idmgr = IdentityManager::init();
|
|
let modsmgr = ModulesManager::init().await;
|
|
|
|
|
|
let requestor = "regular_user".to_string();
|
|
let channel = Channel("somechannel".to_string());
|
|
|
|
|
|
idmgr.affirm_chatter_in_db(requestor.clone()).await;
|
|
|
|
let rslt = idmgr
|
|
.getspecialuserroles(
|
|
requestor.clone(),
|
|
Some(channel.clone()) // None if BotAdmin ; Otherwise, pass Some(Channel)
|
|
)
|
|
.await;
|
|
|
|
assert!(!rslt.contains(&identity::UserRole::Mod(channel.clone())) ||
|
|
!rslt.contains(&identity::UserRole::BotAdmin));
|
|
|
|
|
|
inner_enable_disable_complex(requestor, channel, idmgr, modsmgr).await;
|
|
|
|
}
|
|
|
|
|
|
#[tokio::test]
|
|
async fn enable_disable_modulenotexist_workflow() {
|
|
Log::set_file_ext(Extension::Log);
|
|
|
|
|
|
// [x] 1. Create new ModulesManager & Identity Manager
|
|
let idmgr = IdentityManager::init();
|
|
let modsmgr = ModulesManager::init().await;
|
|
|
|
|
|
let requestor = "regular_user".to_string();
|
|
|
|
let channel = Channel("somechannel".to_string());
|
|
|
|
|
|
idmgr.affirm_chatter_in_db(requestor.clone()).await;
|
|
|
|
|
|
let rslt = idmgr
|
|
.getspecialuserroles(
|
|
requestor.clone(),
|
|
Some(channel.clone()) // None if BotAdmin ; Otherwise, pass Some(Channel)
|
|
)
|
|
.await;
|
|
|
|
assert!(!rslt.contains(&identity::UserRole::Mod(channel.clone())) ||
|
|
!rslt.contains(&identity::UserRole::BotAdmin));
|
|
|
|
// After above, regular chatter is created
|
|
|
|
// [x] 2. modmgr.affirm_in_statusdb(Existing_Module,Custom)
|
|
|
|
let in_module = BotModule("Existing_Module".to_string());
|
|
let in_modgroup = ModGroup::Custom;
|
|
|
|
modsmgr.affirm_in_statusdb(in_module.clone(), in_modgroup.clone()).await;
|
|
|
|
|
|
let trg_level = StatusLvl::Ch(channel.clone()); // setting to Channel Level
|
|
|
|
|
|
// [x] Test with Non Existing module > exec
|
|
|
|
let trg_module = BotModule("Non_Existent_Module".to_string());
|
|
|
|
let rslt = modsmgr.exec_enable(requestor.clone(),
|
|
None,
|
|
trg_module.clone(),
|
|
trg_level.clone(),
|
|
Arc::new(RwLock::new(idmgr.clone()))).await;
|
|
|
|
assert_eq!(rslt,ChangeResult::Failed("Module doesn't exist".to_string()));
|
|
|
|
// [x] Test with Non Existing module > disable
|
|
|
|
let trg_module = BotModule("Non_Existent_Module".to_string());
|
|
|
|
let rslt = modsmgr.exec_disable(requestor.clone(),
|
|
None,
|
|
trg_module.clone(),
|
|
trg_level.clone(),
|
|
false,
|
|
Arc::new(RwLock::new(idmgr))).await;
|
|
|
|
assert_eq!(rslt,ChangeResult::Failed("Module doesn't exist".to_string()));
|
|
|
|
}
|
|
|
|
}
|