diff --git a/.cargo/config.toml b/.cargo/config.toml index 46ba45d..2bcdad5 100644 --- a/.cargo/config.toml +++ b/.cargo/config.toml @@ -1,4 +1,4 @@ [env] # Based on https://doc.rust-lang.org/cargo/reference/config.html -OtherBots = "Supibot,buttsbot,PotatBotat,StreamElements,yuumeibot" +OtherBots = "Supibot,buttsbot,PotatBotat,StreamElements" diff --git a/.gitignore b/.gitignore index e602fce..5c5e6b4 100644 --- a/.gitignore +++ b/.gitignore @@ -22,10 +22,4 @@ target/ *.log # debug -.vscode/ - -# nix -result/ - -# pre-commit -/.pre-commit-config.yaml +.vscode/ \ No newline at end of file diff --git a/.woodpecker/cargo-checks.yml b/.woodpecker/cargo-checks.yml index fa5ecae..1d698f2 100644 --- a/.woodpecker/cargo-checks.yml +++ b/.woodpecker/cargo-checks.yml @@ -1,5 +1,5 @@ when: - branch: master + branch: main event: [push, pull_request] path: include: diff --git a/.woodpecker/flake-update.yml b/.woodpecker/flake-update.yml index 37d88de..56c672a 100644 --- a/.woodpecker/flake-update.yml +++ b/.woodpecker/flake-update.yml @@ -37,7 +37,7 @@ steps: owner: ${CI_REPO_OWNER} repo: ${CI_REPO_NAME} branch: flake-lock-update - base_branch: master + base_branch: main pr_title: "flake.lock: update" pr_body: PR automatically created by Woodpecker CI close_pr_if_empty: true diff --git a/Cargo.lock b/Cargo.lock index da35376..18195c8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -41,17 +41,6 @@ dependencies = [ "libc", ] -[[package]] -name = "async-recursion" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "30c5ef0ede93efbf733c1a727f3b6b5a1060bbedd5600183e66f6e4be4af0ec5" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - [[package]] name = "async-trait" version = "0.1.77" @@ -133,9 +122,9 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "chrono" -version = "0.4.35" +version = "0.4.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8eaf5903dcbc0a39312feb77df2ff4c76387d591b9fc7b04a238dcf8bb62639a" +checksum = "5bc015644b92d5890fab7489e49d21f879d5c990186827d42ec511919404f38b" dependencies = [ "android-tzdata", "iana-time-zone", @@ -205,10 +194,8 @@ checksum = "25cbce373ec4653f1a01a31e8a5e5ec0c622dc27ff9c4e6606eefef5cbbed4a5" name = "forcebot_rs" version = "0.1.0" dependencies = [ - "async-recursion", "async-trait", "casual_logger", - "chrono", "dotenv", "futures", "rand", diff --git a/Cargo.toml b/Cargo.toml index f3f7724..87f8cfa 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,10 +12,7 @@ twitch-irc = "5.0.1" rand = { version = "0.8.5", features = [] } futures = "0.3" async-trait = "0.1.77" -async-recursion = "1.1.0" casual_logger = "0.6.5" -chrono = "0.4.35" - [lib] name = "bot_lib" diff --git a/README.md b/README.md index dafeb69..3b200de 100644 --- a/README.md +++ b/README.md @@ -9,10 +9,9 @@ test ModulatingForceBot ``` login_name = <botname> -access_token = <oauth token> +access_token = <oath token> bot_channels = <chnl1>,<chnl2> prefix = <prefix> -bot_admins = <admins> ``` diff --git a/flake.lock b/flake.lock index 7da4bc6..67734d7 100644 --- a/flake.lock +++ b/flake.lock @@ -6,11 +6,11 @@ "rust-analyzer-src": "rust-analyzer-src" }, "locked": { - "lastModified": 1711952616, - "narHash": "sha256-WJvDdOph001fA1Ap3AyaQtz/afJAe7meSG5uJAdSE+A=", + "lastModified": 1706768574, + "narHash": "sha256-4o6TMpzBHO659EiJTzd/EGQGUDdbgwKwhqf3u6b23U8=", "owner": "nix-community", "repo": "fenix", - "rev": "209048d7c545905c470f6f8c05c5061f391031a8", + "rev": "668102037129923cd0fc239d864fce71eabdc6a3", "type": "github" }, "original": { @@ -20,68 +20,13 @@ "type": "github" } }, - "flake-compat": { - "flake": false, - "locked": { - "lastModified": 1696426674, - "narHash": "sha256-kvjfFW7WAETZlt09AgDn1MrtKzP7t90Vf7vypd3OL1U=", - "owner": "edolstra", - "repo": "flake-compat", - "rev": "0f9255e01c2351cc7d116c072cb317785dd33b33", - "type": "github" - }, - "original": { - "owner": "edolstra", - "repo": "flake-compat", - "type": "github" - } - }, - "flake-utils": { - "inputs": { - "systems": "systems" - }, - "locked": { - "lastModified": 1710146030, - "narHash": "sha256-SZ5L6eA7HJ/nmkzGG7/ISclqe6oZdOZTNoesiInkXPQ=", - "owner": "numtide", - "repo": "flake-utils", - "rev": "b1d9ab70662946ef0850d488da1c9019f3a9752a", - "type": "github" - }, - "original": { - "owner": "numtide", - "repo": "flake-utils", - "type": "github" - } - }, - "gitignore": { - "inputs": { - "nixpkgs": [ - "pre-commit-hooks", - "nixpkgs" - ] - }, - "locked": { - "lastModified": 1709087332, - "narHash": "sha256-HG2cCnktfHsKV0s4XW83gU3F57gaTljL9KNSuG6bnQs=", - "owner": "hercules-ci", - "repo": "gitignore.nix", - "rev": "637db329424fd7e46cf4185293b9cc8c88c95394", - "type": "github" - }, - "original": { - "owner": "hercules-ci", - "repo": "gitignore.nix", - "type": "github" - } - }, "nixpkgs": { "locked": { - "lastModified": 1711703276, - "narHash": "sha256-iMUFArF0WCatKK6RzfUJknjem0H9m4KgorO/p3Dopkk=", + "lastModified": 1706550542, + "narHash": "sha256-UcsnCG6wx++23yeER4Hg18CXWbgNpqNXcHIo5/1Y+hc=", "owner": "nixos", "repo": "nixpkgs", - "rev": "d8fe5e6c92d0d190646fb9f1056741a229980089", + "rev": "97b17f32362e475016f942bbdfda4a4a72a8a652", "type": "github" }, "original": { @@ -91,92 +36,36 @@ "type": "github" } }, - "nixpkgs-stable": { - "locked": { - "lastModified": 1710695816, - "narHash": "sha256-3Eh7fhEID17pv9ZxrPwCLfqXnYP006RKzSs0JptsN84=", - "owner": "NixOS", - "repo": "nixpkgs", - "rev": "614b4613980a522ba49f0d194531beddbb7220d3", - "type": "github" - }, - "original": { - "owner": "NixOS", - "ref": "nixos-23.11", - "repo": "nixpkgs", - "type": "github" - } - }, "nixpkgs_2": { "locked": { - "lastModified": 1712573573, - "narHash": "sha256-xxon7WwNm4/EadMKg1eF40/5s0O78nXUy2ILZt6vT7E=", - "owner": "NixOS", + "lastModified": 1708296515, + "narHash": "sha256-FyF489fYNAUy7b6dkYV6rGPyzp+4tThhr80KNAaF/yY=", + "owner": "nixos", "repo": "nixpkgs", - "rev": "0d28066770464d19d637f6e8e42e8688420b6ac6", + "rev": "b98a4e1746acceb92c509bc496ef3d0e5ad8d4aa", "type": "github" }, "original": { - "owner": "NixOS", - "ref": "nixpkgs-unstable", + "owner": "nixos", + "ref": "nixos-unstable", "repo": "nixpkgs", "type": "github" } }, - "nixpkgs_3": { - "locked": { - "lastModified": 1710765496, - "narHash": "sha256-p7ryWEeQfMwTB6E0wIUd5V2cFTgq+DRRBz2hYGnJZyA=", - "owner": "NixOS", - "repo": "nixpkgs", - "rev": "e367f7a1fb93137af22a3908f00b9a35e2d286a7", - "type": "github" - }, - "original": { - "owner": "NixOS", - "ref": "nixpkgs-unstable", - "repo": "nixpkgs", - "type": "github" - } - }, - "pre-commit-hooks": { - "inputs": { - "flake-compat": "flake-compat", - "flake-utils": "flake-utils", - "gitignore": "gitignore", - "nixpkgs": "nixpkgs_3", - "nixpkgs-stable": "nixpkgs-stable" - }, - "locked": { - "lastModified": 1712579741, - "narHash": "sha256-igpsH+pa6yFwYOdah3cFciCk8gw+ytniG9quf5f/q84=", - "owner": "cachix", - "repo": "pre-commit-hooks.nix", - "rev": "70f504012f0a132ac33e56988e1028d88a48855c", - "type": "github" - }, - "original": { - "owner": "cachix", - "repo": "pre-commit-hooks.nix", - "type": "github" - } - }, "root": { "inputs": { "fenix": "fenix", - "nixpkgs": "nixpkgs_2", - "pre-commit-hooks": "pre-commit-hooks", - "systems": "systems_2" + "nixpkgs": "nixpkgs_2" } }, "rust-analyzer-src": { "flake": false, "locked": { - "lastModified": 1711885694, - "narHash": "sha256-dyezzeSbWMpflma+E9USmvSxuLgGcNGcGw3cOnX36ko=", + "lastModified": 1706735270, + "narHash": "sha256-IJk+UitcJsxzMQWm9pa1ZbJBriQ4ginXOlPyVq+Cu40=", "owner": "rust-lang", "repo": "rust-analyzer", - "rev": "e4a405f877efd820bef9c0e77a02494e47c17512", + "rev": "42cb1a2bd79af321b0cc503d2960b73f34e2f92b", "type": "github" }, "original": { @@ -185,36 +74,6 @@ "repo": "rust-analyzer", "type": "github" } - }, - "systems": { - "locked": { - "lastModified": 1681028828, - "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", - "owner": "nix-systems", - "repo": "default", - "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", - "type": "github" - }, - "original": { - "owner": "nix-systems", - "repo": "default", - "type": "github" - } - }, - "systems_2": { - "locked": { - "lastModified": 1689347949, - "narHash": "sha256-12tWmuL2zgBgZkdoB6qXZsgJEH9LR3oUgpaQq2RbI80=", - "owner": "nix-systems", - "repo": "default-linux", - "rev": "31732fcf5e8fea42e59c2488ad31a0e651500f68", - "type": "github" - }, - "original": { - "owner": "nix-systems", - "repo": "default-linux", - "type": "github" - } } }, "root": "root", diff --git a/flake.nix b/flake.nix index e645270..c6be631 100644 --- a/flake.nix +++ b/flake.nix @@ -1,80 +1,39 @@ { - description = "A basic flake"; + description = "forcebot_rs flake"; inputs = { - systems.url = "github:nix-systems/default-linux"; - nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable"; + nixpkgs.url = "github:nixos/nixpkgs/nixos-unstable"; fenix.url = "github:nix-community/fenix/monthly"; - pre-commit-hooks.url = "github:cachix/pre-commit-hooks.nix"; }; - outputs = { - self, - systems, nixpkgs, fenix, - pre-commit-hooks, - }: let - eachSystem = nixpkgs.lib.genAttrs (import systems); - pkgsFor = eachSystem (system: - import nixpkgs { - localSystem.system = system; - overlays = [fenix.overlays.default]; - }); + ... + } @ inputs: let + system = "x86_64-linux"; + overlays = [fenix.overlays.default]; + pkgs = import nixpkgs { + inherit system overlays; + }; in { - packages = eachSystem (system: let - pkgs = nixpkgs.legacyPackages.${system}; - inherit ((builtins.fromTOML (builtins.readFile ./Cargo.toml)).package) version; - in { - default = pkgsFor.${system}.rustPlatform.buildRustPackage { - pname = "forcebot_rs"; - version = "${version}"; - - src = self; - - cargoLock = { - lockFile = ./Cargo.lock; - }; - - nativeBuildInputs = with pkgs; [pkg-config]; - buildInputs = with pkgs; [openssl]; - - doCheck = false; - }; - }); - checks = eachSystem (system: { - pre-commit-check = pre-commit-hooks.lib.${system}.run { - src = ./.; - hooks = { - # rust - rustfmt.enable = true; - clippy.enable = true; - # nix - statix.enable = true; - alejandra.enable = true; - deadnix.enable = true; - }; - }; - }); - devShells = eachSystem (system: { - default = pkgsFor.${system}.mkShell { - inherit (self.checks.${system}.pre-commit-check) shellHook; - packages = with pkgsFor.${system}; [ - nil - alejandra - rust-analyzer-nightly - (fenix.packages.${system}.complete.withComponents [ - "cargo" - "clippy" - "rust-src" - "rustc" - "rustfmt" - ]) - ]; - RUST_BACKTRACE = 1; - RUST_SRC_PATH = "${fenix.packages.${system}.complete.rust-src}/lib/rustlib/src/rust/library"; - }; - }); - nixosModules.default = import ./nix/module.nix {inherit self;}; + devShells.${system}.default = pkgs.mkShell { + name = "forcebot_rs-devenv"; + nativeBuildInputs = [pkgs.pkg-config]; + buildInputs = with pkgs; [openssl libiconv]; + packages = with pkgs; [ + nil + alejandra + rust-analyzer-nightly + (fenix.packages.${system}.complete.withComponents [ + "cargo" + "clippy" + "rust-src" + "rustc" + "rustfmt" + ]) + ]; + RUST_BACKTRACE = 1; + RUST_SRC_PATH = "${fenix.packages.${system}.complete.rust-src}/lib/rustlib/src/rust/library"; + }; }; } diff --git a/nix/module.nix b/nix/module.nix deleted file mode 100644 index cdc3706..0000000 --- a/nix/module.nix +++ /dev/null @@ -1,30 +0,0 @@ -{self}: { - pkgs, - config, - lib, - ... -}: let - inherit (lib) types; - inherit (lib.modules) mkIf; - inherit (lib.options) mkOption mkEnableOption; - inherit (pkgs.stdenv.hostPlatform) system; - cfg = config.services.forcebot_rs; -in { - options.services.forcebot_rs = { - enable = mkEnableOption '' - Enable forcebot - ''; - - package = mkOption { - type = types.package; - inherit (self.packages.${system}) default; - }; - }; - - config = mkIf cfg.enable { - systemd.services.forcebot_rs = { - wantedBy = ["multi-user.target"]; - serviceConfig.ExecStart = "${cfg.package}/bin/forcebot_rs"; - }; - }; -} diff --git a/src/core/bot_actions.rs b/src/core/bot_actions.rs index 7ab34a0..2e6b456 100644 --- a/src/core/bot_actions.rs +++ b/src/core/bot_actions.rs @@ -1,141 +1,27 @@ -use twitch_irc::message::{PrivmsgMessage, TwitchUserBasics}; -use std::sync::Arc; -use tokio::sync::RwLock; - -use crate::core::botinstance::BotInstance; - -use super::{botmodules::{BotAction, BotModule}, identity::ChatBadge}; - - -pub type BotAR = Arc<RwLock<BotInstance>>; -pub type ActAR = Arc<RwLock<BotAction>>; - -#[derive(Clone)] -pub struct ExecBodyParams { - pub bot : BotAR, - pub msg : PrivmsgMessage, - pub parent_act : ActAR , -} - - -impl ExecBodyParams { - - pub async fn get_parent_module(&self) -> Option<BotModule> { - - let parent_act = Arc::clone(&self.parent_act); - let parent_act_lock = parent_act.read().await; - let act = &(*parent_act_lock); - match act { - BotAction::C(c) => { - let temp = c.module.clone(); - Some(temp) - }, - BotAction::L(l) => { - let temp = l.module.clone(); - Some(temp) - }, - _ => None - } - } - - pub fn get_sender(&self) -> String { - self.msg.sender.name.clone() - } - - pub fn get_sender_chatbadge(&self) -> Option<ChatBadge> { - - let mut requestor_badge_mut: Option<ChatBadge> = None; - - for b in &self.msg.badges { - if b.name == "moderator" { - requestor_badge_mut = Some(ChatBadge::Mod); - } else if b.name == "broadcaster" { - requestor_badge_mut = Some(ChatBadge::Broadcaster); - } - } - requestor_badge_mut - } - - /// Returns some information about the message that was replied to by the `PrivmsgMessage` contained - /// in the `msg` field of this struct. - /// - /// If that message replied to message return that information in form of `Some<ReplyParent>`. - /// Otherwise, return `None`. - pub fn get_parent_reply(&self) -> Option<ReplyParent> { - let map = &self.msg.source.tags.0; - let tags = [ - "reply-parent-user-id", - "reply-parent-user-login", - "reply-parent-display-name", - "reply-parent-msg-id", - "reply-parent-msg-body" - ]; - - // filter out all tags that do not have content. - let tag_contents: Vec<String> = tags.iter().filter_map(|tag| { - // if let Some(&Some(ref t)) = map.get(*tag) { - if let Some(Some(t)) = map.get(*tag) { - Some(t.clone()) - } else { - None - } - }).collect(); - - // if no tags got filtered out return the struct. - // else return `None`. - if tag_contents.len() == 5 { - Some(ReplyParent { - sender: TwitchUserBasics { - id: tag_contents[0].clone(), - login: tag_contents[1].clone(), - name: tag_contents[2].clone(), - }, - message_id: tag_contents[3].clone(), - message_text: tag_contents[4].clone(), - channel_login: self.msg.channel_login.clone(), - channel_id: self.msg.channel_id.clone(), - }) - } else { - None - } - } -} - -/// Represents the message a `PrivmsgMessage` replies to. -/// Similar to a less detailed `PrivmsgMessage`. -/// -/// This should not be constructed manually but only from calling `get_parent_reply()` on -/// `ExecBodyParams`. -/// -/// Fields that will be the same as the `PrivmsgMessage` this was generated from: -/// - `channel_login` -/// - `channel_id` -#[derive(Debug, Clone, PartialEq)] -pub struct ReplyParent { - pub sender: TwitchUserBasics, - pub message_id: String, - pub message_text: String, - pub channel_login: String, - pub channel_id: String, -} - - pub mod actions_util { - use super::*; - use std::boxed::Box; use std::future::Future; use std::pin::Pin; + use std::sync::Arc; + + use tokio::sync::{Mutex, RwLock}; + + use twitch_irc::message::PrivmsgMessage; + + use crate::core::botinstance::BotInstance; + + pub type BotAM = Arc<Mutex<BotInstance>>; + pub type BotAR = Arc<RwLock<BotInstance>>; pub type ExecBody = Box< - dyn Fn(ExecBodyParams) -> Pin<Box<dyn Future<Output = ()> + Send>> + Send + Sync, + dyn Fn(BotAR, PrivmsgMessage) -> Pin<Box<dyn Future<Output = ()> + Send>> + Send + Sync, >; - pub fn asyncbox<T>(f: fn(ExecBodyParams) -> T) -> ExecBody + pub fn asyncbox<T>(f: fn(BotAR, PrivmsgMessage) -> T) -> ExecBody where T: Future<Output = ()> + Send + 'static, { - Box::new(move |a| Box::pin(f(a))) + Box::new(move |a, b| Box::pin(f(a, b))) } } diff --git a/src/core/botinstance.rs b/src/core/botinstance.rs index efd0f9b..144e8ee 100644 --- a/src/core/botinstance.rs +++ b/src/core/botinstance.rs @@ -17,28 +17,20 @@ use casual_logger::Log; use crate::core::ratelimiter::RateLimiter; -use crate::core::bot_actions::BotAR; +use crate::core::bot_actions::actions_util::BotAR; use crate::core::botmodules::ModulesManager; -use crate::core::identity::{IdentityManager, Permissible}; +use crate::core::identity::{ChangeResult, IdentityManager, Permissible}; use crate::core::botlog; use crate::core::chat::Chat; -#[derive(Debug, PartialEq, Eq, Clone)] -pub enum ChangeResult { - Success(String), - Failed(String), - NoChange(String), +#[derive(Debug, PartialEq, Eq, Hash, Clone)] +pub enum ChType { + Channel(String), } - -#[derive(Debug, PartialEq, Eq, Hash, Clone)] - -pub struct Channel(pub String); - -use super::bot_actions::ExecBodyParams; -use super::botmodules::StatusType; +pub use ChType::Channel; #[derive(Clone)] pub struct BotManagers { @@ -48,7 +40,7 @@ pub struct BotManagers { impl BotManagers { pub fn init( - ratelimiters: HashMap<Channel, RateLimiter>, + ratelimiters: HashMap<ChType, RateLimiter>, client: TwitchIRCClient<TCPTransport<TLS>, StaticLoginCredentials>, ) -> BotManagers { BotManagers { @@ -72,11 +64,11 @@ impl<T: Clone> ArcBox<T> { pub struct BotInstance { pub prefix: char, - pub bot_channel: Channel, + pub bot_channel: ChType, pub incoming_messages: Arc<RwLock<UnboundedReceiver<ServerMessage>>>, pub botmodules: Arc<ModulesManager>, pub twitch_oauth: String, - pub bot_channels: Vec<Channel>, + pub bot_channels: Vec<ChType>, pub botmgrs: BotManagers, //modesmgr : ModesManager, // [FUTURE] Silent/Quiet , uwu , frisky/horny } @@ -122,7 +114,7 @@ impl BotInstance { tokio::spawn(async { loop { - let routine_mins = 60 * 24 ; // Every 1 Day + let routine_mins = 60 * 60 * 24 ; // Every 1 Day // let routine_mins = 1; // Every 1 Minute Log::remove_old_logs(); Log::info(&format!("Internal Purge Routine Triggered - running every {} mins",routine_mins)); @@ -154,18 +146,6 @@ impl BotInstance { while let Some(message) = msglock.recv().await { - - botlog::trace( - format!( - "[TRACE][ServerMessage] > {:?}", - message - ) - .as_str(), - Some("BotInstance > runner()".to_string()), - None, - ); - - match message { ServerMessage::Notice(msg) => { botlog::notice( @@ -174,10 +154,8 @@ impl BotInstance { Some("BotInstance > runner()".to_string()), None, ); - Log::flush(); } ServerMessage::Privmsg(msg) => { - botlog::debug( format!( "[Twitch Chat > {}] > {}: {}", @@ -188,18 +166,6 @@ impl BotInstance { Some(&msg), ); - - botlog::trace( - format!( - "[TRACE][Twitch Chat > {}] > {}: {:?}", - msg.channel_login, msg.sender.name, msg - ) - .as_str(), - Some("BotInstance > runner()".to_string()), - Some(&msg), - ); - Log::flush(); - BotInstance::listener_main_prvmsg(Arc::clone(&bot), &msg).await; } ServerMessage::Whisper(msg) => { @@ -208,7 +174,6 @@ impl BotInstance { Some("BotInstance > runner()".to_string()), None, ); - Log::flush(); } ServerMessage::Join(msg) => { botlog::notice( @@ -216,7 +181,6 @@ impl BotInstance { Some("BotInstance > runner()".to_string()), None, ); - Log::flush(); } ServerMessage::Part(msg) => { botlog::notice( @@ -224,7 +188,6 @@ impl BotInstance { Some("BotInstance > runner()".to_string()), None, ); - Log::flush(); } _ => {} }; @@ -255,42 +218,6 @@ impl BotInstance { // // [ ] #todo Need to run through all Listener Bodies for Enabled Modules for the context of the message (e.g., ModStatus is Enabled in the context for the channel) - - /* - [ ] What we should do instead is : - 1. Check if the message is related to a Reply (so we know how many arguments we should skip) - 2. If a reply, skip the first argument - */ - - let mut msgiter= msg - .message_text - .split(' '); - - let arg1 = msgiter.next(); - let arg2 = msgiter.next(); - - let reply = if let Some(Some(replyid)) = msg.source.tags.0.get("reply-thread-parent-msg-id") { - Some(replyid) - } else { None } - ; - - - let inpt = match reply { - None => { // Regular message, use the first arg as the command - match arg1 { - None => return, // return if no argument found - Some(a) => a, - } - }, - Some(_) => { - match arg2 { // A reply message, use the 2nd arg as the command - None => return, // return if no argument found - Some(a) => a, - } - }, - }; - - let botlock = bot.read().await; let actsdb = Arc::clone(&botlock.botmodules.botactions); let actsdblock = actsdb.read().await; @@ -301,20 +228,14 @@ impl BotInstance { Some(msg), ); - for acts in (*actsdblock).values() { - - for a in acts { - - let act_clone = Arc::clone(a); - - match &(*act_clone.read().await) { + match a { crate::core::botmodules::BotAction::C(c) => { /* BotCommand handling - - [x] Checks if the input message is a prefix with command name or alias - - [x] Validate User can run based on identityModule(From_Bot)::can_user_run( + - [ ] Validate User can run based on identityModule(From_Bot)::can_user_run( _usr:String, _channelname:ChType, _chat_badge:ChatBadge, @@ -327,7 +248,11 @@ impl BotInstance { Some(msg), ); - + let inpt = msg + .message_text + .split(' ') + .next() + .expect("ERROR during BotCommand"); // [x] Check if a bot command based on ... // [x] prefix + command @@ -359,72 +284,6 @@ impl BotInstance { let botlock = bot.read().await; let id = botlock.get_identity(); - // [x] Check first if the Module for that Given Command is Enabled or Disabled on the given Channel - let modmgr = Arc::clone(&botlock.botmodules); - let modstatus = modmgr.modstatus( - c.module.clone(), - Channel(msg.channel_login.to_string())).await; - - - if let StatusType::Disabled(a) = modstatus { - - // [x] Should only respond if a BotAdmin , Mod , SupMod , BroadCaster - // - Specifically it should respond only to those who may be able to enable the module - - botlog::trace( - &format!("Identified cmd is associated with Disabled Module : StatusLvl = {:?}", a), - Some("BotInstance > listener_main_prvmsg()".to_string()), - Some(msg), - ); - - - let botclone = Arc::clone(&bot); - let botlock = botclone.read().await; - let id = botlock.get_identity(); - let id = Arc::clone(&id); - let idlock = id.read().await; // <-- [ ] 03.24 - seems to work - let user_roles = idlock.getspecialuserroles( - msg.sender.name.clone(), - Some(Channel(msg.channel_login.clone())) - ).await; - - - botlog::trace( - &format!("For Disabled Command Evaluating User Roles {:?}", user_roles), - Some("BotInstance > listener_main_prvmsg()".to_string()), - Some(msg), - ); - - // Only respond to those with th ebelow User Roles - - let outstr = - format!("sadg Module is disabled : {:?}",a); - - - let params = ExecBodyParams { - bot : Arc::clone(&bot), - msg : (*msg).clone(), - parent_act : Arc::clone(&act_clone), - }; - - // When sending a BotMsgTypeNotif, send_botmsg does Roles related validation as required - - botlock.botmgrs.chat.send_botmsg(super::chat::BotMsgType::Notif( - outstr - ), - params, - ).await; - - return; - }; - - botlog::trace( - "ACQUIRING WRITE LOCK : ID", - Some("BotInstance > listener_main_prvmsg()".to_string()), - Some(msg), - ); - - let eval = { let mut idlock = id.write().await; let (permissability, chngrslt) = idlock @@ -456,72 +315,25 @@ impl BotInstance { let botlock = bot.read().await; let outstr = "o7 a Mod. I kneel to serve! pepeKneel ".to_string(); - - - let params = ExecBodyParams { - bot : Arc::clone(&bot), - msg : (*msg).clone(), - parent_act : Arc::clone(&act_clone), - - }; - - botlock.botmgrs.chat.send_botmsg(super::chat::BotMsgType::Notif( - outstr.to_string() - ), - params.clone(), - ).await; - - } - - if innerstr - .to_lowercase() - .contains(&"Auto Promoted VIP".to_lowercase()) - { - botlog::notice( - "Assigning VIP UserRole to VIP", - Some("botinstance > listener_main_prvmsg()".to_string()), - Some(msg), - ); - - let botlock = bot.read().await; - let outstr = - "❤️ a VIP - love ya!".to_string(); - - - let params = ExecBodyParams { - bot : Arc::clone(&bot), - msg : (*msg).clone(), - parent_act : Arc::clone(&act_clone), - - }; - - botlock.botmgrs.chat.send_botmsg(super::chat::BotMsgType::Notif( - outstr.to_string() - ), - params.clone(), - ).await; - + botlock.botmgrs.chat.say_in_reply_to(msg, outstr).await; } } match eval { Permissible::Allow => { botlog::debug( - "Executing as permissible", + "Executed as permissible", Some("BotInstance > listener_main_prvmsg()".to_string()), Some(msg), ); let a = Arc::clone(&bot); - c.execute(ExecBodyParams { - bot : a, - msg : msg.clone() , - parent_act : Arc::clone(&act_clone), - }).await; + c.execute(a, msg.clone()).await; botlog::trace( "exit out of execution", Some("BotInstance > listener_main_prvmsg()".to_string()), + // Some(&msg), Some(msg), ); } @@ -538,36 +350,8 @@ impl BotInstance { } crate::core::botmodules::BotAction::L(l) => { - - let botlock = bot.read().await; - - // [x] Check first if the Module for that Given Command is Enabled or Disabled on the given Channel - let modmgr = Arc::clone(&botlock.botmodules); - let modstatus = modmgr.modstatus( - l.module.clone(), - Channel(msg.channel_login.to_string())).await; - - - if let StatusType::Disabled(a) = modstatus { - - // [x] Should only respond if a BotAdmin , Mod , SupMod , BroadCaster - // - Specifically it should respond only to those who may be able to enable the module - - botlog::trace( - &format!("Identified listener is associated with Disabled Module : StatusLvl = {:?}", a), - Some("BotInstance > listener_main_prvmsg()".to_string()), - Some(msg), - ); - - } else { - let a = Arc::clone(&bot); - l.execute(ExecBodyParams { - bot : a, - msg : msg.clone() , - parent_act : Arc::clone(&act_clone), - } ).await; - } - + let a = Arc::clone(&bot); + l.execute(a, msg.clone()).await; } _ => (), diff --git a/src/core/botlog.rs b/src/core/botlog.rs index 48ae783..27272d2 100644 --- a/src/core/botlog.rs +++ b/src/core/botlog.rs @@ -24,10 +24,11 @@ debug = "Checking bot actions", pub fn trace(in_msg: &str, in_module: Option<String>, in_prvmsg: Option<&PrivmsgMessage>) { let (chnl, chatter) = match in_prvmsg { Some(prvmsg) => { + //Log::trace(&format!("(#{}) {}: {}", prvmsg.channel_login, prvmsg.sender.name, prvmsg.message_text)); ( Some(prvmsg.channel_login.clone()), Some(prvmsg.sender.name.clone()), - ) + ) // <-- Clone fine atm while we're just working with Strings } None => (None, None), }; @@ -44,10 +45,11 @@ pub fn trace(in_msg: &str, in_module: Option<String>, in_prvmsg: Option<&Privmsg pub fn debug(in_msg: &str, in_module: Option<String>, in_prvmsg: Option<&PrivmsgMessage>) { let (chnl, chatter) = match in_prvmsg { Some(prvmsg) => { + //Log::trace(&format!("(#{}) {}: {}", prvmsg.channel_login, prvmsg.sender.name, prvmsg.message_text)); ( Some(prvmsg.channel_login.clone()), Some(prvmsg.sender.name.clone()), - ) + ) // <-- Clone fine atm while we're just working with Strings } None => (None, None), }; @@ -64,10 +66,11 @@ pub fn debug(in_msg: &str, in_module: Option<String>, in_prvmsg: Option<&Privmsg pub fn info(in_msg: &str, in_module: Option<String>, in_prvmsg: Option<&PrivmsgMessage>) { let (chnl, chatter) = match in_prvmsg { Some(prvmsg) => { + //Log::trace(&format!("(#{}) {}: {}", prvmsg.channel_login, prvmsg.sender.name, prvmsg.message_text)); ( Some(prvmsg.channel_login.clone()), Some(prvmsg.sender.name.clone()), - ) + ) // <-- Clone fine atm while we're just working with Strings } None => (None, None), }; @@ -84,10 +87,11 @@ pub fn info(in_msg: &str, in_module: Option<String>, in_prvmsg: Option<&PrivmsgM pub fn notice(in_msg: &str, in_module: Option<String>, in_prvmsg: Option<&PrivmsgMessage>) { let (chnl, chatter) = match in_prvmsg { Some(prvmsg) => { + //Log::trace(&format!("(#{}) {}: {}", prvmsg.channel_login, prvmsg.sender.name, prvmsg.message_text)); ( Some(prvmsg.channel_login.clone()), Some(prvmsg.sender.name.clone()), - ) + ) // <-- Clone fine atm while we're just working with Strings } None => (None, None), }; @@ -104,10 +108,11 @@ pub fn notice(in_msg: &str, in_module: Option<String>, in_prvmsg: Option<&Privms pub fn warn(in_msg: &str, in_module: Option<String>, in_prvmsg: Option<&PrivmsgMessage>) { let (chnl, chatter) = match in_prvmsg { Some(prvmsg) => { + //Log::trace(&format!("(#{}) {}: {}", prvmsg.channel_login, prvmsg.sender.name, prvmsg.message_text)); ( Some(prvmsg.channel_login.clone()), Some(prvmsg.sender.name.clone()), - ) + ) // <-- Clone fine atm while we're just working with Strings } None => (None, None), }; @@ -124,10 +129,11 @@ pub fn warn(in_msg: &str, in_module: Option<String>, in_prvmsg: Option<&PrivmsgM pub fn error(in_msg: &str, in_module: Option<String>, in_prvmsg: Option<&PrivmsgMessage>) { let (chnl, chatter) = match in_prvmsg { Some(prvmsg) => { + //Log::trace(&format!("(#{}) {}: {}", prvmsg.channel_login, prvmsg.sender.name, prvmsg.message_text)); ( Some(prvmsg.channel_login.clone()), Some(prvmsg.sender.name.clone()), - ) + ) // <-- Clone fine atm while we're just working with Strings } None => (None, None), }; @@ -148,10 +154,11 @@ pub fn fatal<'a>( ) -> &'a str { let (chnl, chatter) = match in_prvmsg { Some(prvmsg) => { + //Log::trace(&format!("(#{}) {}: {}", prvmsg.channel_login, prvmsg.sender.name, prvmsg.message_text)); ( Some(prvmsg.channel_login.clone()), Some(prvmsg.sender.name.clone()), - ) + ) // <-- Clone fine atm while we're just working with Strings } None => (None, None), }; diff --git a/src/core/botmodules.rs b/src/core/botmodules.rs index 408b2d5..0bfe4fa 100644 --- a/src/core/botmodules.rs +++ b/src/core/botmodules.rs @@ -3,12 +3,12 @@ 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<ModType,Vec<ModStatusType>>, + botactions: HashMap<ModType,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) +- statusdb: HashMap<ModType,Vec<ModStatusType>> - Defines Modules and their ModStatusType (e.g., Enabled at an Instance level, Disabled at a Channel Level) +- botactions: HashMap<ModType,Vec<BotAction>> - Defines Modules and their BotActions (e.g., BotCommand , Listener, Routine) Example { @@ -19,441 +19,40 @@ Example */ - -const OF_CMD_CHANNEL:Channel = Channel(String::new()); - - use core::panic; use std::collections::HashMap; +use std::error::Error; use std::sync::Arc; -use casual_logger::Log; +use twitch_irc::message::PrivmsgMessage; use tokio::sync::RwLock; use async_trait::async_trait; -use crate::core::bot_actions::actions_util; -use crate::core::bot_actions::ExecBodyParams; -use crate::core::botinstance::{BotInstance, Channel,ChangeResult}; +use self::bot_actions::actions_util::BotAR; +use crate::core::botinstance::{BotInstance, ChType}; use crate::core::botlog; -use crate::core::identity::{self, Permissible,IdentityManager}; +use crate::core::identity; use crate::core::bot_actions; - -use std::hash::{Hash, Hasher}; - -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(bot: BotAR, msg: PrivmsgMessage) { - 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); - } -} - +pub use ChType::Channel; +pub use ModType::BotModule; #[derive(Debug, PartialEq, Eq, Hash, Clone)] -pub enum ModGroup { - Core, - Custom, +pub enum ModType { + BotModule(String), } -#[derive(Debug, PartialEq, Eq, Hash, Clone)] +#[derive(Debug)] pub enum StatusLvl { Instance, - Ch(Channel), + _Ch(ChType), } -#[derive(Debug, PartialEq, Eq, Hash, Clone)] -pub enum StatusType { +#[derive(Debug)] +pub enum ModStatusType { Enabled(StatusLvl), Disabled(StatusLvl), } @@ -465,10 +64,10 @@ pub enum BotAction { } impl BotAction { - pub async fn execute(&self, params : ExecBodyParams) { + pub async fn execute(&self, m: BotAR, n: PrivmsgMessage) { match self { - BotAction::L(a) => a.execute(params).await, - BotAction::C(a) => a.execute(params).await, + BotAction::L(a) => a.execute(m, n).await, + BotAction::C(a) => a.execute(m, n).await, _ => (), } } @@ -478,12 +77,10 @@ impl BotAction { 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 module: ModType, pub command: String, // command call name pub alias: Vec<String>, // String of alternative names pub exec_body: bot_actions::actions_util::ExecBody, @@ -492,8 +89,8 @@ pub struct BotCommand { } impl BotCommand { - pub async fn execute(&self, params : ExecBodyParams) { - (*self.exec_body)(params).await; + pub async fn execute(&self, m: BotAR, n: PrivmsgMessage) { + (*self.exec_body)(m, n).await; } } @@ -508,28 +105,18 @@ impl BotActionTrait for BotCommand { .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 module: ModType, 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; + pub async fn execute(&self, m: BotAR, n: PrivmsgMessage) { + (self.exec_body)(m, n).await; } } @@ -555,39 +142,26 @@ impl BotActionTrait for Listener { .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)] pub struct Routine {} -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: Arc<RwLock<HashMap<ModType, Vec<ModStatusType>>>>, + pub botactions: Arc<RwLock<HashMap<ModType, Vec<BotAction>>>>, } /* statusdb <HashMap - <BotModule, <-- e.g., BotModule(String::from("experiments001")) + <ModType, <-- e.g., BotModule(String::from("experiments001")) Vec<ModStatusType> <-- shows Enabled/Disabled per Status level botactions HashMap< - BotModule, <-- e.g., BotModule(String::from("experiments001")) + ModType, <-- e.g., BotModule(String::from("experiments001")) Vec<BotAction>> BotCommand, Listener */ @@ -610,7 +184,6 @@ impl ModulesManager { // 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; @@ -624,25 +197,7 @@ impl ModulesManager { 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 { + pub fn modstatus(&self, _: ModType, _: ChType) -> ModStatusType { // Example usage : botmanager.modstatus( // BotModule("GambaCore"), // Channel("modulatingforce") @@ -650,668 +205,21 @@ impl ModulesManager { // - 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) + ModStatusType::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 fn togglestatus(&self, _: ModType, _: ChType) -> ModStatusType { + // enables or disables based on current status + ModStatusType::Enabled(StatusLvl::Instance) } - - 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 fn setstatus(&self, _: ModType, _: ModStatusType) -> Result<&str, Box<dyn Error>> { + // sets the status based given ModSatusType + // e.g., b.setstatus(BodModule("GambaCore"), Enabled(Channel("modulatingforce"))).expect("ERROR") + Ok("") } - pub 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) { + pub async fn add_botaction(&self, in_module: ModType, in_action: BotAction) { botlog::trace( "Add botaction called", Some("ModulesManager > init()".to_string()), @@ -1334,21 +242,13 @@ impl ModulesManager { // - 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> { - + async fn find_conflict_module(mgr: &ModulesManager, act: &BotAction) -> Option<ModType> { 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) { + for modact in moduleactions.iter() { + if let BotAction::C(dbcmd) = &modact { // At this point, there is an command incmd and looked up dbcmd // [x] check if given botcommand c.command:String conflicts with any in botactions @@ -1399,12 +299,15 @@ impl ModulesManager { ) } - self.affirm_in_statusdb(in_module.clone(),in_modgroup).await; + let mut dbt = self.statusdb.write().await; + let statusvector = dbt.entry(in_module.clone()).or_insert(Vec::new()); + + statusvector.push(ModStatusType::Enabled(StatusLvl::Instance)); // Pushes the Module as Enabled at Instance Level 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))); + modactions.push(in_action); botlog::trace( format!( @@ -1417,807 +320,12 @@ impl ModulesManager { ); } - fn _statuscleanup(&self, _: Option<Channel>) { + fn _statuscleanup(&self, _: Option<ChType>) { // internal cleans up statusdb . For example : // - remove redudancies . If we see several Enabled("m"), only keep 1x // - Clarify Conflict. If we see Enabled("m") and Disabled("m") , we remove Enabled("m") and keep Disabled("m") // the IDEAL is that this is ran before every read/update operation to ensure quality - // Option<Channel> can pass Some(Channel("m")) (as an example) so statuscleanup only works on the given channel + // Option<ChType> 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())); - - } - -} diff --git a/src/core/chat.rs b/src/core/chat.rs index 43984bc..fe49896 100644 --- a/src/core/chat.rs +++ b/src/core/chat.rs @@ -4,7 +4,7 @@ use std::sync::Arc; use tokio::sync::Mutex; use twitch_irc::login::StaticLoginCredentials; -use twitch_irc::message::ReplyToMessage; +use twitch_irc::message::PrivmsgMessage; use twitch_irc::transport::tcp::{TCPTransport, TLS}; use twitch_irc::TwitchIRCClient; @@ -15,36 +15,21 @@ use rand::Rng; use crate::core::ratelimiter; use crate::core::ratelimiter::RateLimiter; -use crate::core::botinstance::Channel; +use crate::core::botinstance::ChType; use crate::core::botlog; - +pub use ChType::Channel; use tokio::time::{sleep, Duration}; -use super::bot_actions::ExecBodyParams; -use super::identity; - - -use async_recursion::async_recursion; - #[derive(Clone)] pub struct Chat { - pub ratelimiters: Arc<Mutex<HashMap<Channel, RateLimiter>>>, // used to limit messages sent per channel + pub ratelimiters: Arc<Mutex<HashMap<ChType, RateLimiter>>>, // used to limit messages sent per channel pub client: TwitchIRCClient<TCPTransport<TLS>, StaticLoginCredentials>, } - -#[derive(Clone,Debug)] -pub enum BotMsgType { - SayInReplyTo(Channel,String,String), // ( Destination Channel , Message ID to reply to , OutMessage ) // https://docs.rs/twitch-irc/latest/twitch_irc/client/struct.TwitchIRCClient.html#method.say_in_reply_to - Say(String,String), - Notif(String), // For Bot Sent Notifications -} - - impl Chat { pub fn init( - ratelimiters: HashMap<Channel, RateLimiter>, + ratelimiters: HashMap<ChType, RateLimiter>, client: TwitchIRCClient<TCPTransport<TLS>, StaticLoginCredentials>, ) -> Chat { Chat { @@ -53,16 +38,12 @@ impl Chat { } } - pub async fn init_channel(&mut self, chnl: Channel) { + pub async fn init_channel(&mut self, chnl: ChType) { let n = RateLimiter::new(); self.ratelimiters.lock().await.insert(chnl, n); } - #[async_recursion] - pub async fn send_botmsg(&self, msginput: BotMsgType, params : ExecBodyParams) { - - - + pub async fn say_in_reply_to(&self, msg: &PrivmsgMessage, mut outmsg: String) { /* formats message before sending to TwitchIRC @@ -72,258 +53,12 @@ impl Chat { */ - botlog::trace( - format!("send_bot_msg params : {:?}",msginput).as_str(), - Some("chat.rs > send_botmsg ".to_string()), - Some(¶ms.msg), - ); - Log::flush(); - - - let (channel_login,mut outmsg) = match msginput.clone() { - BotMsgType::SayInReplyTo(chnl, _, outmsg) => { - (chnl.0.to_lowercase(), // Desintation Channel - outmsg) - }, - BotMsgType::Say(a,b ) => { - (a.clone(),b.clone()) - }, - BotMsgType::Notif(outmsg) => { - (params.msg.channel_login.clone(),outmsg) - } - }; - - - - - botlog::trace( - "BEFORE parent_module call", - Some("chat.rs > send_botmsg ".to_string()), - Some(¶ms.msg), - ); - - let parent_module = params.get_parent_module().await; - - let params_clone = params.clone(); - let botclone = Arc::clone(¶ms_clone.bot); - let botlock = botclone.read().await; - let modmgr = Arc::clone(&botlock.botmodules); - let modstatus = (*modmgr).modstatus( - parent_module.clone().expect("ERROR - Expected a module"), - Channel(channel_login.clone()) - ).await; - - if !params.bot.read().await.bot_channels.contains(&Channel(channel_login.clone())) { - - dbg!("ISSUE : NONJOINED CHANNEL",¶ms.bot.read().await.bot_channels,Channel(channel_login.clone())); - botlog::warn( - &format!("A message attempted to send for a Non-Joined Channel : {}",channel_login.clone()), - Some("Chat > send_botmsg".to_string()), - None, - ); - - if let BotMsgType::SayInReplyTo(_chnl,_msgid, _outmsg) = msginput { - - self.send_botmsg(BotMsgType::Notif( - "uuh Bot can't send to a channel it isn't joined".to_string(), - ), - params).await; - - } - - return ; - } - - /* - [x] !! => 03.24 - Somewhere around here, we should be validating module for target channel - */ - - - /* - - Use ModulesManager.modstatus - - modstatus(&self, in_module: BotModule, in_chnl: Channel) -> StatusType - - */ - - botlog::trace( - format!("BEFORE modstatus check : modstatus = {:?}",modstatus).as_str(), - Some("chat.rs > send_botmsg ".to_string()), - Some(¶ms.msg), - ); - - - - if let super::botmodules::StatusType::Disabled(lvl) = modstatus { - // Note : At this point, chat was called in a channel where the parent module IS enabled - // - this type of validation is done outside of Chat() - // This though takes into account scenarios where we are targetting a different channel - - - botlog::trace( - "BEFORE msginput check", - Some("chat.rs > send_botmsg ".to_string()), - Some(¶ms.msg), - ); - - Log::flush(); - - match msginput { - BotMsgType::Notif(_) => (), // Do nothing with Notif > We'll validate the user later to handle - BotMsgType::SayInReplyTo(_, _, _) | BotMsgType::Say(_,_) => { - - botlog::trace( - "BEFORE potential Async recursion", - Some("chat.rs > send_botmsg ".to_string()), - Some(¶ms.clone().msg), - ); - - Log::flush(); - - - self.send_botmsg(BotMsgType::Notif( - format!("uuh {:?} is disabled on {} : {:?}", - parent_module.clone().unwrap(), - channel_login.clone(), - lvl - ), - ), params.clone() - ).await; - - - botlog::trace( - "AFTER potential Async recursion", - Some("chat.rs > send_botmsg ".to_string()), - Some(¶ms.msg), - ); - - - Log::flush(); - - return - }, - - } - - } - - - /* - - [x] !! => 03.24 - Would be nice if around here , validate the user has at least some special roles in target channel - - NOTE : If these need to be refined, they can be by the custom module developer at the parent calling function of say() - - This just prevents Chat from being triggered in a channel where the sending chatter does not have any special roles - - */ - - - - /* - - Use - pub async fn getspecialuserroles( - &self, - chattername: String, - channel: Option<Channel>, - ) -> Vec<UserRole> { - - */ - - // let params_clone = params.clone(); - - let botclone = Arc::clone(¶ms.bot); - let botlock = botclone.read().await; - let id = botlock.get_identity(); - let id = Arc::clone(&id); - let idlock = id.read().await; - let user_roles = idlock.getspecialuserroles( - params.get_sender(), - Some(Channel(channel_login.clone())) - ).await; - - botlog::trace( - format!("BEFORE user roles check check : userroles = {:?}",user_roles).as_str(), - Some("chat.rs > send_botmsg ".to_string()), - Some(¶ms.msg), - ); - - Log::flush(); - - // [x] If user has any of the following target roles, they will be allowed - otherwise, they will not be allowed to send - // - Otherwise if not (checked here) , this will not run - // - NOTE : For now, I've removed BotAdmin just for curiosity - BotAdmins can always elevate themselves if they want - // - Will be adding VIP to this as this should include Channel_Level Roles - - if !(user_roles.contains(&identity::UserRole::Mod(Channel(channel_login.clone()))) - || user_roles.contains(&identity::UserRole::SupMod(Channel(channel_login.clone()))) - || user_roles.contains(&identity::UserRole::Broadcaster) - || user_roles.contains(&identity::UserRole::VIP(Channel(channel_login.clone()))) - ) - { - - - match msginput { - BotMsgType::Notif(_) => { - // If Sender is Not a BotAdmin, don't do anything about the notification and return - if !user_roles.contains(&identity::UserRole::BotAdmin) { - return; - } - }, - BotMsgType::SayInReplyTo(_,_,_ ) | BotMsgType::Say(_,_) => { - // If the BotMsg a Say/SayInReplyTo (from Developer or Chatter) , and the Sender does not have Specific Roles in the Source Channel Sent - - self.send_botmsg(BotMsgType::Notif( - format!("uuh You do not have the right roles to send to {}", - channel_login.clone(), - ), - ), params.clone() - ).await; - - return; - - }, - }; - - - - } - - - /* - At this stage from the above Validations : - msginput would be : - a. BotMsgType::SayInReplyTo | BotMsgType::Say that is - - Towards a Destination Channel that the Sender has Elevated User Roles to Send to - b. BotMsgType::Notif that is - - Going to be sent to the Source Channel (rather than the original say/sayinreplyto was towards) - - A Sender that has Elevated User Roles in Source Channel will see a message ; otherwise, they will not - */ - - /* - - Use the following - - pub async fn can_user_run( - &mut self, - usr: String, - channelname: Channel, - chat_badge: Option<ChatBadge>, - cmdreqroles: Vec<UserRole>, // ) -> Result<Permissible,Box<dyn Error>> { - ) -> (Permissible, ChangeResult) { - - */ - let rl = Arc::clone(&self.ratelimiters); let mut rllock = rl.lock().await; - botlog::debug( - &format!("Ratelimiter being checked for channel : {}",channel_login.clone()), - Some("Chat > send_botmsg".to_string()), - None, - ); - let contextratelimiter = rllock - .get_mut(&Channel(channel_login.to_lowercase().clone())) + // .get_mut() + .get_mut(&Channel(String::from(&msg.channel_login))) .expect("ERROR: Issue with Rate limiters"); // Continue to check the limiter and sleep if required if the minimum is not reached @@ -340,49 +75,20 @@ impl Chat { outmsg.push_str(blankspace); } - match msginput.clone() { - BotMsgType::SayInReplyTo(chnl,msgid, _) => { + self.client.say_in_reply_to(msg, outmsg).await.unwrap(); - dbg!(chnl.clone(),msgid.clone(),outmsg.clone()); - - self.client.say_in_reply_to(&( - chnl.0, - msgid), - outmsg).await.unwrap(); - }, - BotMsgType::Say(a, _) => { - self.client.say(a, outmsg).await.unwrap(); - } - BotMsgType::Notif(outmsg) => { - - dbg!(params.msg.channel_login(),params.msg.message_id()); - self.client.say_in_reply_to(¶ms.msg, outmsg).await.unwrap(); - } - } - contextratelimiter.increment_counter(); let logstr = format!( "(#{}) > {} ; contextratelimiter : {:?}", - channel_login.clone(), "rate limit counter increase", contextratelimiter + msg.channel_login, "rate limit counter increase", contextratelimiter ); - if let BotMsgType::SayInReplyTo(_,_,_ ) = msginput { - botlog::trace( - logstr.as_str(), - Some("Chat > send_botmsg".to_string()), - None, - ); - } else { - botlog::trace( - logstr.as_str(), - Some("Chat > send_botmsg".to_string()), - None, - ); - } - - - + botlog::trace( + logstr.as_str(), + Some("Chat > say_in_reply_to".to_string()), + Some(msg), + ); } ratelimiter::LimiterResp::Skip => { // (); // do nothing otherwise @@ -392,54 +98,15 @@ impl Chat { } } - - Log::flush(); + Log::flush(); } - - pub async fn say_in_reply( - &self, - destination_channel : Channel , - outmsg: String , - params : ExecBodyParams) - { - - self.send_botmsg(BotMsgType::SayInReplyTo( - destination_channel, - params.msg.message_id().to_string(), - outmsg) , params).await; - - - } - - pub async fn say_in_reply_to( - &self, - destination_channel : Channel , - reply_message_id : String , - outmsg: String , - params : ExecBodyParams) - { - - self.send_botmsg(BotMsgType::SayInReplyTo( - destination_channel, - reply_message_id, - outmsg) , params).await; - - } - - - - - pub async fn say(&self, channel_login: String, message: String , params : ExecBodyParams) { + async fn _say(&self, _: String, _: String) { // more info https://docs.rs/twitch-irc/latest/twitch_irc/client/struct.TwitchIRCClient.html#method.say - self.send_botmsg(BotMsgType::Say(channel_login.to_lowercase(), message), params).await; + // self.client.say(msg,outmsg).await.unwrap(); } - - - - async fn _me(&self, _: String, _: String) { // more info https://docs.rs/twitch-irc/latest/twitch_irc/client/struct.TwitchIRCClient.html#method.say diff --git a/src/core/identity.rs b/src/core/identity.rs index 61c09dc..f5e710b 100644 --- a/src/core/identity.rs +++ b/src/core/identity.rs @@ -1,41 +1,29 @@ - - -const OF_CMD_CHANNEL:Channel = Channel(String::new()); - - use std::collections::HashMap; use std::sync::Arc; use tokio::sync::RwLock; -use tokio::time::{sleep,Duration}; use twitch_irc::message::PrivmsgMessage; use casual_logger::Log; -use crate::core::bot_actions::actions_util; -use crate::core::bot_actions::ExecBodyParams; -use crate::core::botinstance::{Channel,ChangeResult}; +use crate::core::bot_actions::actions_util::{self, BotAR}; +use crate::core::botinstance::ChType; use crate::core::botlog; use crate::core::botmodules::{BotActionTrait, BotCommand, BotModule, ModulesManager}; + use dotenv::dotenv; use std::env; fn adminvector() -> Vec<String> { - dotenv().ok(); - let mut admins = Vec::new(); - - if let Ok(value) = env::var("bot_admins") { - for admin in value.split(',') { - admins.push(String::from(admin)) - } - } - - admins + vec![String::from("ModulatingForce")] + //vec![] } pub fn otherbots_vector() -> Vec<String> { + // vec![String::from("ModulatingForce")] + // //vec![] dotenv().ok(); let mut other_bots = Vec::new(); @@ -68,22 +56,174 @@ pub async fn init(mgr: Arc<ModulesManager>) { let tempb = BotCommand { module: BotModule(String::from("identity")), command: String::from("promote"), // command call name - alias: vec![ - "cucked".to_string(), - "cuck".to_string(), - ], // String of alternative names + alias: vec![], // String of alternative names exec_body: actions_util::asyncbox(cmd_promote), help: String::from("promote"), required_roles: vec![ - UserRole::Mod(OF_CMD_CHANNEL), - UserRole::SupMod(OF_CMD_CHANNEL), + UserRole::Mod(ChType::Channel(String::new())), + UserRole::SupMod(ChType::Channel(String::new())), UserRole::Broadcaster, UserRole::BotAdmin, ], }; - tempb.add_core_to_modmgr(Arc::clone(&mgr)).await; - + tempb.add_to_modmgr(Arc::clone(&mgr)).await; + + async fn cmd_promote(bot: BotAR, msg: PrivmsgMessage) { + botlog::trace( + "Called cmd promote", + Some("identity.rs > cmd_prommote()".to_string()), + Some(&msg), + ); + + // -- If the BotCommand.command was called (e.g., promote) & required roles were validated OUTSIDE of this call + // , this is the current function body to execute + + /* + - `promote` / `demote` + - [ ] `SupMod` & `Broadcaster` & `BotAdmin` can run + - [ ] `UserRole`s that can run, can + - [ ] run `promote` on a regular `Chatter` to make them a `Mod` + - [ ] run `demote` on a `Mod` to make them a `Chatter` + - [ ] Only `BotAdmin` can : + - [ ] target themselves to `promote` / `demote` , in the case that they want to make themselves either a `Mod` or `SupMod` for the channel temporarily + - [ ] `promote admin <Chatter>` to assign them `BotAdmin` role + - `[ ] Broadcaster` & `BotAdmin` can `demote` a `SupMod` to make them a `Mod` or `promote` the other way + */ + + /* + Usage : + + promote <user> + + demote <user> + + promote -admin <user> + + */ + + // println!("{}",msg.message_text); + botlog::trace( + format!("Twich Message > {}", msg.message_text).as_str(), + Some("identity.rs > cmd_promote()".to_string()), + None, + ); + + let sendername = msg.clone().sender.name; + + let mut argv = msg.message_text.split(' '); + + argv.next(); // Skip the command name + + let arg1 = argv.next(); + + let arg2 = argv.next(); + + let mut sender_badge: Option<ChatBadge> = None; + + for b in &msg.badges { + if b.name == "moderator" { + sender_badge = Some(ChatBadge::Mod); + } else if b.name == "broadcaster" { + sender_badge = Some(ChatBadge::Broadcaster); + } + } + + let targetchnl = msg.channel_login.to_lowercase(); + + /* + + [x] 1. Get trgusr (regardless of -admin flag) + [x] 2. promote trguser + [x] 3. Output resulting change + + */ + + // [x] 1. Get trgusr (regardless of -admin flag) + + let targetusr = if arg1 == Some("-admin") { arg2 } else { arg1 }; + + // [x] 2. promote trguser + + // [x] Get a required lock first + + let botlock = bot.read().await; + let id = botlock.get_identity(); + let idlock = id.read().await; + + let rslt = match targetusr { + Some(targetusr) => { + botlog::debug( + "running promote()", + Some("identity.rs > cmd_promote()".to_string()), + None, + ); + Log::flush(); + + let target_bot_admin_role = if arg1 == Some("-admin") { + Some(UserRole::BotAdmin) + } else { + None + }; + + idlock + .promote( + sendername.clone(), + &sender_badge, + targetusr.to_string(), + Some(ChType::Channel(targetchnl.clone())), + target_bot_admin_role, + ) + .await + } + + None => { + botlog::debug( + // &format!("No Targer User argument"), + "No Targer User argument", + Some("identity.rs > cmd_demote()".to_string()), + None, + ); + Log::flush(); + + ChangeResult::NoChange("No Targer User".to_string()) + } + }; + + // [x] 3. Output resulting change + + let outmsg = match rslt { + ChangeResult::Success(a) => { + format!("o7 Successfully promoted : {a}") + } + ChangeResult::Failed(a) => { + format!("PoroSad failed to promote : {a}") + } + ChangeResult::NoChange(a) => { + format!("uuh No Promotion Change : {a}") + } + }; + + botlog::debug( + outmsg.as_str(), + Some("identity.rs > cmd_prommote()".to_string()), + Some(&msg), + ); + + botlock + .botmgrs + .chat + .say_in_reply_to(&msg, outmsg.to_string()) + .await; + + botlog::trace( + // &format!("End of cmd_promote()"), + "End of cmd_promote()", + Some("identity.rs > cmd_prommote()".to_string()), + None, + ); + } + let tempb = BotCommand { module: BotModule(String::from("identity")), command: String::from("demote"), // command call name @@ -91,15 +231,189 @@ pub async fn init(mgr: Arc<ModulesManager>) { exec_body: actions_util::asyncbox(cmd_demote), help: String::from("demote"), required_roles: vec![ - UserRole::Mod(OF_CMD_CHANNEL), - UserRole::SupMod(OF_CMD_CHANNEL), + UserRole::Mod(ChType::Channel(String::new())), + UserRole::SupMod(ChType::Channel(String::new())), UserRole::Broadcaster, UserRole::BotAdmin, ], }; - tempb.add_core_to_modmgr(Arc::clone(&mgr)).await; - + tempb.add_to_modmgr(Arc::clone(&mgr)).await; + + async fn cmd_demote(bot: BotAR, msg: PrivmsgMessage) { + botlog::debug( + "Called cmd demote", + Some("identity.rs > cmd_demote()".to_string()), + Some(&msg), + ); + Log::flush(); + + // -- If the BotCommand.command was called (e.g., demote) & required roles were validated OUTSIDE of this call + // , this is the current function body to execute + + /* + - `promote` / `demote` + - [ ] `SupMod` & `Broadcaster` & `BotAdmin` can run + - [ ] `UserRole`s that can run, can + - [ ] run `promote` on a regular `Chatter` to make them a `Mod` + - [ ] run `demote` on a `Mod` to make them a `Chatter` + - [ ] Only `BotAdmin` can : + - [ ] target themselves to `promote` / `demote` , in the case that they want to make themselves either a `Mod` or `SupMod` for the channel temporarily + - [ ] `promote admin <Chatter>` to assign them `BotAdmin` role + - `[ ] Broadcaster` & `BotAdmin` can `demote` a `SupMod` to make them a `Mod` or `promote` the other way + */ + + /* + Usage : + + promote <user> + + demote <user> + + promote -admin <user> + + */ + + // [x] Unwraps arguments from message + + let (arg1, _arg2) = { + let mut argv = msg.message_text.split(' '); + + argv.next(); // Skip the command name + + let arg1 = argv.next(); + + let arg2 = argv.next(); + + (arg1, arg2) + }; + + // --- + + /* + - [x] 1. Parse out the following + - Sender (e.g., Name & Badge) + - Target User (arg1) + - Target Channel (current channel) + - Msg or Msg.Message_Text (for later) + + - [x] 2. Run Demote() + - within demote(), getspecialuserroles() is called on both the sender and the target + - getspecialuserroles() only sends current db , while canuserrun() may change db depending on the most current state of the sender + - getspecialuserroles also borrows the sender's badge to evaluate + + - [x] 3. Take ChangeResult and output response + + */ + + /* + + - [x] 1. Parse out the following + - Sender (e.g., Name & Badge) + - Target User (arg1) + - Target Channel (current channel) + - (no need) Msg or Msg.Message_Text (for later) + + */ + + let sendername = msg.clone().sender.name; + + let mut sender_badge_mut: Option<ChatBadge> = None; + + for b in &msg.badges { + if b.name == "moderator" { + sender_badge_mut = Some(ChatBadge::Mod); + } else if b.name == "broadcaster" { + sender_badge_mut = Some(ChatBadge::Broadcaster); + } + } + + let sender_badge = sender_badge_mut; + + let targetusr = arg1; + + let targetchnl = msg.channel_login.to_lowercase(); + + /* + + - [x] 2. Run Demote() + - within demote(), getspecialuserroles() is called on both the sender and the target + - getspecialuserroles() only sends current db , while canuserrun() may change db depending on the most current state of the sender + - getspecialuserroles also borrows the sender's badge to evaluate + + + */ + + // [x] Get a required lock first + + let botlock = bot.read().await; + let id = botlock.get_identity(); + let idlock = id.read().await; + + let rslt = match targetusr { + Some(targetusr) => { + botlog::debug( + // &format!("running demote()"), + "running demote()", + Some("identity.rs > cmd_demote()".to_string()), + None, + ); + Log::flush(); + + idlock + .demote( + sendername.clone(), + &sender_badge, + targetusr.to_string(), + Some(ChType::Channel(targetchnl.clone())), + ) + .await + } + + None => { + botlog::debug( + // &format!("No Targer User argument"), + "No Targer User argument", + Some("identity.rs > cmd_demote()".to_string()), + None, + ); + Log::flush(); + + ChangeResult::NoChange("No Targer User".to_string()) + } + }; + + /* + + - [x] 3. Take ChangeResult and output response + + */ + + let outmsg = match rslt { + ChangeResult::Success(a) => { + format!("o7 Successfully demoted : {a}") + } + ChangeResult::Failed(a) => { + format!("PoroSad failed to demote : {a}") + } + ChangeResult::NoChange(a) => { + format!("uuh No Demotion Change : {a}") + } + }; + + botlog::debug( + outmsg.as_str(), + Some("identity.rs > cmd_demote()".to_string()), + Some(&msg), + ); + + botlock + .botmgrs + .chat + .say_in_reply_to(&msg, outmsg.to_string()) + .await; + } + let tempcomm = BotCommand { module: BotModule(String::from("identity")), command: String::from("getroles"), // command call name @@ -107,15 +421,146 @@ pub async fn init(mgr: Arc<ModulesManager>) { exec_body: actions_util::asyncbox(getroles), help: String::from("getroles"), required_roles: vec![ - UserRole::Mod(OF_CMD_CHANNEL), - UserRole::SupMod(OF_CMD_CHANNEL), + UserRole::Mod(ChType::Channel(String::new())), + UserRole::SupMod(ChType::Channel(String::new())), UserRole::Broadcaster, UserRole::BotAdmin, ], }; - tempcomm.add_core_to_modmgr(Arc::clone(&mgr)).await; - + tempcomm.add_to_modmgr(Arc::clone(&mgr)).await; + + async fn getroles(bot: BotAR, msg: PrivmsgMessage) { + botlog::debug( + "Called cmd getroles", + Some("identity.rs > cmd_getroles()".to_string()), + Some(&msg), + ); + + /* + Usage + + getroles <user> <Channel> + - If channel is provided, provide roles for that channel specifically + + */ + + let mut argv = msg.message_text.split(' '); + + argv.next(); // Skip the command name + + let arg1 = argv.next(); + + let targetuser = match arg1 { + None => return, // exit if no arguments + Some(arg) => arg, + }; + + let arg2 = argv.next(); + + let targetchnl = arg2; + + let botlock = bot.read().await; + + let id = botlock.get_identity(); + + let idlock = id.read().await; + + let sproles = match targetchnl { + None => { + // [ ] If targetchnl is not provided, default to pulling the current channel + idlock + .getspecialuserroles( + String::from(targetuser), + Some(ChType::Channel(msg.channel_login.to_lowercase())), + ) + .await + } + Some(targetchnl) => { + // [x] gets special roles for caller + let callersproles = idlock + .getspecialuserroles( + msg.sender.name.to_lowercase(), + Some(ChType::Channel(targetchnl.to_lowercase().to_string())), + ) + .await; + + if callersproles.contains(&UserRole::Mod(ChType::Channel( + targetchnl.to_lowercase().to_string(), + ))) || callersproles.contains(&UserRole::SupMod(ChType::Channel( + targetchnl.to_lowercase().to_string(), + ))) || callersproles.contains(&UserRole::Broadcaster) + { + idlock + .getspecialuserroles( + String::from(targetuser), + Some(ChType::Channel(targetchnl.to_lowercase())), + ) + .await + } else { + // Otherwise, don't get the target channel, return the current channel instead + idlock + .getspecialuserroles( + String::from(targetuser), + Some(ChType::Channel(msg.channel_login.to_lowercase())), + ) + .await + } + } + }; + + botlog::debug( + &format!("User roles of Target Chatter >> {:?}", sproles), + Some("identity.rs > init > getroles()".to_string()), + Some(&msg), + ); + + botlog::trace( + // &format!("Evaluating special roles"), + "Evaluating special roles", + Some("identity.rs > init > getroles()".to_string()), + Some(&msg), + ); + + let outmsg = if ((targetuser.to_lowercase() == msg.channel_login.to_lowercase()) + && arg2.is_none()) + || (arg2.is_some() && arg2.unwrap() == targetuser.to_lowercase()) + { + // First evaluates if they're broadcaster + + let mut outmsg = "FeelsWowMan they're the broadcaster. ".to_string(); + + if sproles.contains(&UserRole::Mod(ChType::Channel( + msg.channel_login.to_lowercase(), + ))) || sproles.contains(&UserRole::SupMod(ChType::Channel( + msg.channel_login.to_lowercase(), + ))) || sproles.contains(&UserRole::BotAdmin) + { + outmsg += format!("Target chatter's user roles are : {:?}", sproles).as_str(); + } + outmsg + } else if sproles.contains(&UserRole::Mod(ChType::Channel( + msg.channel_login.to_lowercase(), + ))) || sproles.contains(&UserRole::SupMod(ChType::Channel( + msg.channel_login.to_lowercase(), + ))) || sproles.contains(&UserRole::BotAdmin) + { + format!("Target chatter's user roles are : {:?}", sproles) + } else { + "Target chatter has no special roles LULE ".to_string() + }; + + botlog::debug( + format!("Chat Say Reply message : {}", outmsg).as_str(), + Some("identity.rs > init > getroles()".to_string()), + Some(&msg), + ); + + botlock.botmgrs.chat.say_in_reply_to(&msg, outmsg).await; + + // [ ] NOTE : After the above, I should receive only the roles in the context of the current channel I received this ideally and maybe BotAdmin ; not outside + } + botlog::trace( "End of Init MOdule add", Some("identity.rs > init ".to_string()), @@ -125,641 +570,11 @@ pub async fn init(mgr: Arc<ModulesManager>) { Log::flush(); } - - -async fn cmd_promote(params : ExecBodyParams) { - botlog::trace( - "Called cmd promote", - Some("identity.rs > cmd_prommote()".to_string()), - Some(¶ms.msg), - ); - - // -- If the BotCommand.command was called (e.g., promote) & required roles were validated OUTSIDE of this call - // , this is the current function body to execute - - /* - - `promote` / `demote` - - [ ] `SupMod` & `Broadcaster` & `BotAdmin` can run - - [ ] `UserRole`s that can run, can - - [ ] run `promote` on a regular `Chatter` to make them a `Mod` - - [ ] run `demote` on a `Mod` to make them a `Chatter` - - [ ] Only `BotAdmin` can : - - [ ] target themselves to `promote` / `demote` , in the case that they want to make themselves either a `Mod` or `SupMod` for the channel temporarily - - [ ] `promote admin <Chatter>` to assign them `BotAdmin` role - - `[ ] Broadcaster` & `BotAdmin` can `demote` a `SupMod` to make them a `Mod` or `promote` the other way - */ - - /* - Usage : - - promote <user> - promote -m <user> - promote -mod <user> - - demote <user> - - promote -v <user> - promote -vip <user> - - promote -admin <user> - - */ - - // println!("{}",params.msg.message_text); - botlog::trace( - format!("Twich Message > {}", params.msg.message_text).as_str(), - Some("identity.rs > cmd_promote()".to_string()), - None, - ); - - let sendername = params.msg.clone().sender.name; - - let mut argv = params.msg.message_text.split(' '); - - let cmdname = argv.next(); // Skip the command name - - let arg1 = argv.next(); - - let arg2 = argv.next(); - - let mut sender_badge: Option<ChatBadge> = None; - - for b in ¶ms.msg.badges { - if b.name == "moderator" { - sender_badge = Some(ChatBadge::Mod); - } else if b.name == "broadcaster" { - sender_badge = Some(ChatBadge::Broadcaster); - } else if b.name == "vip" { - sender_badge = Some(ChatBadge::VIP); - } - }; - - let targetchnl = params.msg.channel_login.to_lowercase(); - - /* - - [x] 1. Get trgusr (regardless of -admin flag) - [x] 2. promote trguser - [x] 3. Output resulting change - - */ - - - - let botlock = params.bot.read().await; - let id = botlock.get_identity(); - let idlock = id.read().await; - - - // [x] 1. Get trgusr (regardless of -admin flag) - - // let targetusr = if arg1 == Some("-admin") { arg2 } else { arg1 }; - let mut targetusr = if - arg1 == Some("-admin") - || arg1 == Some("-v") - || arg1 == Some("-vip") - || arg1 == Some("-m") - || arg1 == Some("-mod") - { arg2 } - else if let Some(a) = arg1 { - if a.starts_with('-') { - botlock.botmgrs.chat.send_botmsg( - super::chat::BotMsgType::Notif( - "Invalid Argument Flag".to_string() - ), - params.clone(), - ).await; - return - } else { arg1 } - } - else { arg1 }; - - - // [x] Check if target is an existing user - targetusr = if let Some(chkusr) = targetusr { - match twitch_irc::validate::validate_login(chkusr.to_lowercase().as_str()) { - Ok(_) => Some(chkusr), - Err(_) => None, - } - } else { None } ; - - - // [x] Check if cmd passed is cucked, then go through special cucked handling - if let Some(cmd_to_check) = cmdname { - if cmd_to_check.to_lowercase() == String::from(botlock.get_prefix()) + "cucked" - || cmd_to_check.to_lowercase() == String::from(botlock.get_prefix()) + "cuck" - { - - let idlock = botlock.botmgrs.identity.read().await; - let senderroles = idlock.getspecialuserroles(sendername.clone(), Some(Channel(targetchnl.to_lowercase()))).await; - - if senderroles.contains(&UserRole::BotAdmin) && targetusr.is_none() { - targetusr = Some(sendername.as_str()) - } - - botlock.botmgrs.chat.send_botmsg(super::chat::BotMsgType::Notif( - "uuh ".to_string() - ), - params.clone(), - ).await; - - sleep(Duration::from_secs_f64(2.0)).await; - - - botlock.botmgrs.chat.send_botmsg(super::chat::BotMsgType::Notif( - "... chatter getting cucked ...".to_string() - ), - params.clone(), - ).await; - - sleep(Duration::from_secs_f64(1.0)).await; - - } - } - - - // [x] 2. promote trguser - - // [x] Get a required lock first - let rslt = match targetusr { - Some(targetusr) => { - botlog::debug( - "running promote()", - Some("identity.rs > cmd_promote()".to_string()), - None, - ); - - Log::flush(); - - - // // Believe this is just using this as a Constaint depending on input - let target_role = - if arg1 == Some("-admin") { - Some(UserRole::BotAdmin) - } else if arg1 == Some("-vip") || arg1 == Some("-v") { - Some(UserRole::VIP(Channel(targetchnl.clone()))) - } else { - None // [x] Internal promote() logic automatically considers trg_role targetting -mod or -m - }; - - idlock - .promote( - sendername.clone(), - &sender_badge, - targetusr.to_string(), - Some(Channel(targetchnl.clone())), - target_role, - ) - .await - } - - None => { - botlog::debug( - "No Targer User argument", - Some("identity.rs > cmd_demote()".to_string()), - None, - ); - Log::flush(); - - ChangeResult::NoChange("No Targer User".to_string()) - } - }; - - // [x] 3. Output resulting change - - let outmsg = match rslt { - ChangeResult::Success(rsltstr) => { - format!("o7 Successfully promoted {} : {}", - targetusr.unwrap(), - rsltstr - ) - } - ChangeResult::Failed(rsltstr) => { - format!("PoroSad failed to promote {} : {}", - targetusr.unwrap(), - rsltstr - ) - } - ChangeResult::NoChange(rsltstr) => { - format!("uuh No Promotion Change {} : {}", - targetusr.unwrap(), - rsltstr - ) - } - }; - - botlog::debug( - outmsg.as_str(), - Some("identity.rs > cmd_prommote()".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; - - - botlog::trace( - "End of cmd_promote()", - Some("identity.rs > cmd_prommote()".to_string()), - None, - ); -} - - - -async fn cmd_demote(params : ExecBodyParams) { - botlog::debug( - "Called cmd demote", - Some("identity.rs > cmd_demote()".to_string()), - Some(¶ms.msg), - ); - Log::flush(); - - // -- If the BotCommand.command was called (e.g., demote) & required roles were validated OUTSIDE of this call - // , this is the current function body to execute - - /* - - `promote` / `demote` - - [ ] `SupMod` & `Broadcaster` & `BotAdmin` can run - - [ ] `UserRole`s that can run, can - - [ ] run `promote` on a regular `Chatter` to make them a `Mod` - - [ ] run `demote` on a `Mod` to make them a `Chatter` - - [ ] Only `BotAdmin` can : - - [ ] target themselves to `promote` / `demote` , in the case that they want to make themselves either a `Mod` or `SupMod` for the channel temporarily - - [ ] `promote admin <Chatter>` to assign them `BotAdmin` role - - `[ ] Broadcaster` & `BotAdmin` can `demote` a `SupMod` to make them a `Mod` or `promote` the other way - */ - - /* - Usage : - - promote <user> - - demote <user> - - demote -m <user> - demote -mod <user> - - demote -v <user> - demote -vip <user> - - // promote -admin <user> - - - - */ - - // [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) - }; - - // --- - - /* - - [x] 1. Parse out the following - - Sender (e.g., Name & Badge) - - Target User (arg1) - - Target Channel (current channel) - - Msg or Msg.Message_Text (for later) - - - [x] 2. Run Demote() - - within demote(), getspecialuserroles() is called on both the sender and the target - - getspecialuserroles() only sends current db , while canuserrun() may change db depending on the most current state of the sender - - getspecialuserroles also borrows the sender's badge to evaluate - - - [x] 3. Take ChangeResult and output response - - */ - - /* - - - [x] 1. Parse out the following - - Sender (e.g., Name & Badge) - - Target User (arg1) - - Target Channel (current channel) - - (no need) Msg or Msg.Message_Text (for later) - - */ - - - - // [x] Get a required lock first - - let botlock = params.bot.read().await; - let id = botlock.get_identity(); - let idlock = id.read().await; - - - let sendername = params.msg.clone().sender.name; - - let mut sender_badge_mut: Option<ChatBadge> = None; - - for b in ¶ms.msg.badges { - if b.name == "moderator" { - sender_badge_mut = Some(ChatBadge::Mod); - } else if b.name == "broadcaster" { - sender_badge_mut = Some(ChatBadge::Broadcaster); - } else if b.name == "vip" { - sender_badge_mut = Some(ChatBadge::VIP); - } - }; - - let sender_badge = sender_badge_mut; - - - let targetchnl = params.msg.channel_login.to_lowercase(); - - - // let targetusr = arg1; - let targetusr = if - arg1 == Some("-v") - || arg1 == Some("-vip") - || arg1 == Some("-m") - || arg1 == Some("-mod") - { arg2 } - else if let Some(a) = arg1 { - if a.starts_with('-') { - botlock.botmgrs.chat.send_botmsg( - super::chat::BotMsgType::Notif( - "Invalid Argument Flag".to_string() - ), - params.clone(), - ).await; - return - } else { arg1 } - } - else { arg1 }; - - // Note : At the moment, no handling of -admin - let target_role = - if arg1 == Some("-vip") || arg1 == Some("-v") { - Some(UserRole::VIP(Channel(targetchnl.clone()))) - } else { - None // [x] Internal promote() logic automatically considers trg_role targetting -mod or -m - }; - - - - /* - - - [x] 2. Run Demote() - - within demote(), getspecialuserroles() is called on both the sender and the target - - getspecialuserroles() only sends current db , while canuserrun() may change db depending on the most current state of the sender - - getspecialuserroles also borrows the sender's badge to evaluate - - - */ - - let rslt = match targetusr { - Some(targetusr) => { - botlog::debug( - "running demote()", - Some("identity.rs > cmd_demote()".to_string()), - None, - ); - Log::flush(); - - idlock - .demote( - sendername.clone(), - &sender_badge, - targetusr.to_string(), - Some(Channel(targetchnl.clone())), - target_role, - ) - .await - } - - None => { - botlog::debug( - "No Targer User argument", - Some("identity.rs > cmd_demote()".to_string()), - None, - ); - Log::flush(); - - ChangeResult::NoChange("No Targer User".to_string()) - } - }; - - /* - - - [x] 3. Take ChangeResult and output response - - */ - - let outmsg = match rslt { - ChangeResult::Success(rsltstr) => { - format!("o7 Successfully demoted {} : {}", - targetusr.unwrap(), - rsltstr - ) - } - ChangeResult::Failed(rsltstr) => { - format!("PoroSad failed to demote {} : {}", - targetusr.unwrap(), - rsltstr - ) - } - ChangeResult::NoChange(rsltstr) => { - format!("uuh No Demotion Change {} : {}", - targetusr.unwrap(), - rsltstr - ) - } - }; - - botlog::debug( - outmsg.as_str(), - Some("identity.rs > cmd_demote()".to_string()), - Some(¶ms.msg), - ); - - botlock.botmgrs.chat.send_botmsg(super::chat::BotMsgType::Notif( - outmsg.to_string() - ), - params.clone(), - ).await; - - -} - - - -async fn getroles(params : ExecBodyParams) { - botlog::debug( - "Called cmd getroles", - Some("identity.rs > cmd_getroles()".to_string()), - Some(¶ms.msg), - ); - - /* - Usage - - getroles <user> <Channel> - - If channel is provided, provide roles for that channel specifically - - */ - - let sendername = params.msg.clone().sender.name; - - - let mut argv = params.msg.message_text.split(' '); - - argv.next(); // Skip the command name - - let arg1 = argv.next(); - - let mut targetuser = match arg1 { - // None => return, // exit if no arguments - None => sendername.as_str(), // self-invoke in this case - Some(arg) => arg, - }; - - targetuser = match twitch_irc::validate::validate_login(targetuser.to_lowercase().as_str()) { - Ok(_) => targetuser, - Err(_) => sendername.as_str(), // self-invoke in this case - }; - - let arg2 = argv.next(); - - let targetchnl = arg2; - - let botlock = params.bot.read().await; - - let id = botlock.get_identity(); - - let idlock = id.read().await; - - let sproles = match targetchnl { - None => { - // [ ] If targetchnl is not provided, default to pulling the current channel - idlock - .getspecialuserroles( - String::from(targetuser), - Some(Channel(params.msg.channel_login.to_lowercase())), - ) - .await - } - Some(targetchnl) => { - // [x] gets special roles for caller - let callersproles = idlock - .getspecialuserroles( - params.msg.sender.name.to_lowercase(), - Some(Channel(targetchnl.to_lowercase().to_string())), - ) - .await; - - // Below appears to be validating if getroles() should run based on caller's specific roles - // - No Need to add VIP here - if callersproles.contains(&UserRole::Mod(Channel( - targetchnl.to_lowercase().to_string(), - ))) || callersproles.contains(&UserRole::SupMod(Channel( - targetchnl.to_lowercase().to_string(), - ))) || callersproles.contains(&UserRole::Broadcaster) - { - idlock - .getspecialuserroles( - String::from(targetuser), - Some(Channel(targetchnl.to_lowercase())), - ) - .await - } else { - // Otherwise, don't get the target channel, return the current channel instead - idlock - .getspecialuserroles( - String::from(targetuser), - Some(Channel(params.msg.channel_login.to_lowercase())), - ) - .await - } - } - }; - - botlog::debug( - &format!("User roles of Target Chatter >> {:?}", sproles), - Some("identity.rs > init > getroles()".to_string()), - Some(¶ms.msg), - ); - - botlog::trace( - "Evaluating special roles", - Some("identity.rs > init > getroles()".to_string()), - Some(¶ms.msg), - ); - - let outmsg = if ((targetuser.to_lowercase() == params.msg.channel_login.to_lowercase()) - && arg2.is_none()) - || (arg2.is_some() && arg2.unwrap() == targetuser.to_lowercase()) - { - // First evaluates if they're broadcaster - - let mut outmsg = "FeelsWowMan they're the broadcaster. ".to_string(); - - // Below appears to be validating if getroles() should run based on caller's specific roles - // - No Need to add VIP here - if sproles.contains(&UserRole::Mod(Channel( - params.msg.channel_login.to_lowercase(), - ))) || sproles.contains(&UserRole::SupMod(Channel( - params.msg.channel_login.to_lowercase(), - ))) || sproles.contains(&UserRole::BotAdmin) - { - // targetuser - // outmsg += format!("Target chatter's user roles are : {:?}", sproles).as_str(); - outmsg += format!("{}'s user roles are : {:?}", targetuser, sproles).as_str(); - } - outmsg - } else if sproles.contains(&UserRole::Mod(Channel( - params.msg.channel_login.to_lowercase(), - ))) || sproles.contains(&UserRole::SupMod(Channel( - params.msg.channel_login.to_lowercase(), - ))) || sproles.contains(&UserRole::BotAdmin) - { - // format!("Target chatter's user roles are : {:?}", sproles) - format!("{}'s user roles are : {:?}", targetuser, sproles) - } else { - format!("{} has no special roles LULE ",targetuser) - }; - - botlog::debug( - format!("Chat Say Reply message : {}", outmsg).as_str(), - Some("identity.rs > init > getroles()".to_string()), - Some(¶ms.msg), - ); - - botlock.botmgrs.chat.send_botmsg(super::chat::BotMsgType::Notif( - outmsg.to_string() - ), - params.clone(), - ).await; - - - // [ ] NOTE : After the above, I should receive only the roles in the context of the current channel I received this ideally and maybe BotAdmin ; not outside -} - - - #[derive(Debug, PartialEq, Eq, Clone)] pub enum UserRole { Chatter, - Mod(Channel), - SupMod(Channel), - VIP(Channel), + Mod(ChType), // String specifies Channel + SupMod(ChType), // String specifies Channel Broadcaster, BotAdmin, } @@ -782,11 +597,17 @@ pub struct IdentityManager { > */ -#[derive(Debug, Clone)] +#[derive(Debug)] pub enum ChatBadge { Broadcaster, Mod, - VIP, +} + +#[derive(Debug, PartialEq, Eq)] +pub enum ChangeResult { + Success(String), + Failed(String), + NoChange(String), } impl IdentityManager { @@ -804,8 +625,7 @@ impl IdentityManager { } } - // => 03.22 - Force - Made public because botmodules unit tests - pub async fn add_role(&self, trgchatter: String, trg_role: UserRole) { + async fn add_role(&self, trgchatter: String, trg_role: UserRole) { let mut srulock = self.special_roles_users.write().await; let mut usrrolelock = srulock .get_mut(&trgchatter) @@ -827,8 +647,7 @@ impl IdentityManager { } } - // => 03.22 - Force - Made public because botmodules unit tests - pub async fn affirm_chatter_in_db(&self, trgchatter: String) { + async fn affirm_chatter_in_db(&self, trgchatter: String) { let mut srulock = self.special_roles_users.write().await; srulock .entry(trgchatter.clone()) @@ -856,6 +675,7 @@ impl IdentityManager { botlog::trace( "Checking within PRVMSG", Some("identity.rs > can_user_run_PRVMSG()".to_string()), + // Some(&msg), Some(msg), ); @@ -866,28 +686,79 @@ impl IdentityManager { sender_badge = Some(ChatBadge::Mod); } else if b.name == "broadcaster" { sender_badge = Some(ChatBadge::Broadcaster); - } else if b.name == "vip" { - sender_badge = Some(ChatBadge::VIP); } - }; + } + + // if &msg.badges.contains(Badge{}) { + + // } + + // if let Some(sender_badge) = sender_badge { + // match sender_badge { + // Some(sender_badge) => { + // return &self.can_user_run(msg.sender.name.to_owned(), + // ChType::Channel(msg.channel_login.to_owned()), + // sender_badge, + // cmdreqroles + // return self.can_user_run(msg.sender.name.to_owned(), + // let a = Arc::new(Mutex::new(self)); + // let mut a = a.lock().await; + // let a = **a; + // let a = a.can_user_run(msg.sender.name.to_owned(), + // ChType::Channel(msg.channel_login.to_owned()), + // sender_badge, + // cmdreqroles + // ) ; + // let a = *self; + // let a = Arc::new(Mutex::new(a)); + // let a = a.lock().await.can_user_run(msg.sender.name.to_owned(), + // ChType::Channel(msg.channel_login.to_owned()), + // sender_badge, + // cmdreqroles + // ) ; + // return a; + // return self.can_user_run(msg.sender.name.to_owned(), + // ChType::Channel(msg.channel_login.to_owned()), + // sender_badge, + // cmdreqroles + // ).await + + // * NOTE : We're preferring to pass the ChangeResult up , where we have access to Chat via BotInstance + // that have more strained chatting rules + // let evalpermissible = self.can_user_run(msg.sender.name.to_owned(), + // ChType::Channel(msg.channel_login.to_owned()), + // sender_badge, + // cmdreqroles + // ).await ; + // evalpermissible + // // } + // None => { + + // } + // here , sender_badge is likely None + // This could be a regular chatter, BotAdmin,SupserMod + + // [ ] Call can_user_run() + // (self,Permissible::Block) + // (Permissible::Block,ChangeResult::NoChange("".to_string())) self.can_user_run( msg.sender.name.to_owned(), - Channel(msg.channel_login.to_owned()), + ChType::Channel(msg.channel_login.to_owned()), sender_badge, cmdreqroles, ) .await - } pub async fn can_user_run( &mut self, usr: String, - channelname: Channel, + channelname: ChType, chat_badge: Option<ChatBadge>, cmdreqroles: Vec<UserRole>, // ) -> Result<Permissible,Box<dyn Error>> { ) -> (Permissible, ChangeResult) { + // println!{"Checking within can_user_run()"}; botlog::debug( &format!( "Checking within can_user_run() : @@ -934,6 +805,8 @@ impl IdentityManager { // [x] If cmdreqroles is empty vector , automatically assume Ok(Permissible::Allow) + // let idar = Arc::new(RwLock::new(self)); + let usr = usr.to_lowercase(); @@ -954,25 +827,25 @@ impl IdentityManager { ); } + // if cmdreqroles.len() == 0 { if cmdreqroles.is_empty() { + // return Ok(Permissible::Allow) return ( Permissible::Allow, ChangeResult::NoChange("Command has no required cmdreqroles".to_string()), ); } - let mut rolechange = ChangeResult::NoChange("".to_string()); + let mut modrolechange = ChangeResult::NoChange("".to_string()); match chat_badge { - - // If ChatBadge::Broadcaster is observed, - // Check if cmdreqroles contains Channel Level Roles . Broadcaster should have Permissible::Allow for any of these - + // [x] If chatBadge::Broadcaster ... + // [x] and cmdreqroles includes UserRole::Broadcaster , Ok(Permissible::Allow) + // [x] and cmdreqroles includes UserRole::Mod("") OR UserRole::SupMod("") , Ok(Permissible::Allow) Some(ChatBadge::Broadcaster) => { if cmdreqroles.contains(&UserRole::Broadcaster) - || cmdreqroles.contains(&UserRole::Mod(OF_CMD_CHANNEL)) - || cmdreqroles.contains(&UserRole::SupMod(OF_CMD_CHANNEL)) - || cmdreqroles.contains(&UserRole::VIP(OF_CMD_CHANNEL)) + || cmdreqroles.contains(&UserRole::Mod(ChType::Channel(String::new()))) + || cmdreqroles.contains(&UserRole::SupMod(ChType::Channel(String::new()))) { // return Ok(Permissible::Allow) return ( @@ -1026,48 +899,7 @@ impl IdentityManager { usrroles_lock.push(UserRole::Mod(channelname.clone())); - rolechange = ChangeResult::Success("Auto Promoted Mod".to_string()); - } - } - } - Some(ChatBadge::VIP) => { - botlog::info( - "VIP Chatbadge detected", - Some("identity.rs > can_user_run()".to_string()), - None, - ); - - let rolesdb = Arc::clone(&self.special_roles_users); - - self.affirm_chatter_in_db(usr.clone()).await; - - let rolesdb_lock = rolesdb.write().await; - - match (*rolesdb_lock).get(&usr.to_lowercase()) { - Some(usrroles) - if usrroles - .read() - .await - .contains(&UserRole::VIP(channelname.clone())) => - { - // Do nothing when theh have a VIP badge and have VIP badge for the channel - botlog::trace( - "Already a VIP in roles", - Some("identity.rs > can_user_run()".to_string()), - None, - ); - } - - _ => { - // In the event they have a VIP badge , are running a bot command, but don't have a channel mod role yet... - - let mut rolesdb_lock_mut = rolesdb_lock; - let usrroles = rolesdb_lock_mut.get_mut(&usr.to_lowercase()).unwrap(); - let mut usrroles_lock = usrroles.write().await; - - usrroles_lock.push(UserRole::VIP(channelname.clone())); - - rolechange = ChangeResult::Success("Auto Promoted VIP".to_string()); + modrolechange = ChangeResult::Success("Auto Promoted Mod".to_string()); } } } @@ -1082,7 +914,7 @@ impl IdentityManager { None, ); - if cmdreqroles.contains(&UserRole::Mod(OF_CMD_CHANNEL)) { + if cmdreqroles.contains(&UserRole::Mod(ChType::Channel(String::new()))) { botlog::trace( "Command requires Mod Role", Some("identity.rs > can_user_run()".to_string()), @@ -1111,14 +943,14 @@ impl IdentityManager { Some("identity.rs > can_user_run()".to_string()), None, ); - return (Permissible::Allow, rolechange); + return (Permissible::Allow, modrolechange); } } } // [x] If cmdreqroles includes UserRole::SupMod("") , checks if chatter has UserRole::SupMod(channelname::ChType) to determine if Ok(Permissible::Allow) - if cmdreqroles.contains(&UserRole::SupMod(OF_CMD_CHANNEL)) { + if cmdreqroles.contains(&UserRole::SupMod(ChType::Channel(String::new()))) { if let Some(a) = self .special_roles_users .read() @@ -1129,7 +961,7 @@ impl IdentityManager { .await .contains(&UserRole::SupMod(channelname.clone())) { - return (Permissible::Allow, rolechange); + return (Permissible::Allow, modrolechange); } } } @@ -1176,47 +1008,11 @@ impl IdentityManager { ); if a.read().await.contains(&UserRole::BotAdmin) { - return (Permissible::Allow, rolechange); + return (Permissible::Allow, modrolechange); } } } - // [x] If cmdreqroles includes UserRole::VIP and chatter has UserRole::VIP , Ok(Permissible::Allow) - - if cmdreqroles.contains(&UserRole::VIP(OF_CMD_CHANNEL)) { - - botlog::trace( - "Command requires VIP Role", - Some("identity.rs > can_user_run()".to_string()), - None, - ); - - if let Some(a) = self - .special_roles_users - .read() - .await - .get(&usr.to_lowercase()) - { - botlog::trace( - "Special roles found for user", - Some("identity.rs > can_user_run()".to_string()), - None, - ); - - if a.read().await.contains(&UserRole::VIP(channelname.clone())) - { - botlog::trace( - "> Special Role Identified : VIP ", - Some("identity.rs > can_user_run()".to_string()), - None, - ); - return (Permissible::Allow, rolechange); - } - } - - } - - ( Permissible::Block, ChangeResult::NoChange("Not any permissiable condition".to_string()), @@ -1228,7 +1024,7 @@ impl IdentityManager { authorizer: String, authorizer_badge: &Option<ChatBadge>, trgchatter: String, - channel: Option<Channel>, + channel: Option<ChType>, trg_role: Option<UserRole>, ) -> ChangeResult { botlog::trace( @@ -1241,27 +1037,20 @@ impl IdentityManager { Log::flush(); /* - - - // [x] => 03.25 - Q. Would there need to be extra handling here for VIP? - - [x] 1. Check if Authorizer Mod Badge then Auto Promote to Mod if not Mod [x] 2. Get Authorizer & Target Chatter Roles with a Given Channel [x] 3. If the authorizer & Target Chatters are the same, and the Authorizer is not a Admin, return no change [x] 4a. If Authorizer is BotAdmin & trg_role is Some(BotAdmin) , set Target as BotAdmin and return - [x] 4b. If Authorizer is a Mod,SupMod,Broadcaster & trg_role is Some(VIP(channel)), can Promote a Target Chatter > VIP - [x] 4c. If target is Broadcaster, return NoChange - [x] 4d. If Authorizer is a SupMod,Broadcaster,BotAdmin , can Promote Target Chatter > Mod + [x] 4b. If target is Broadcaster, return NoChange + [ ] 4c. If Authorizer is a SupMod,Broadcaster,BotAdmin , can Promote Target Chatter > Mod - NOTE : We do not validate trg_role here - app logic requires you to promote 1 to Mod and 1 more to SupMod - [x] 4e. If Authorizer is a Broadcaster,BotAdmin , can Promote a Target Mod > SupMod + [ ] 4d. If Authorizer is a Broadcaster,BotAdmin , can Promote a Target Mod > SupMod - NOTE : We do not validate trg_role here - app logic requires you to promote 1 to Mod and 1 more to SupMod - */ - // [x] 1. Check if Authorizer Mod or VIP Badge then Auto Promote to matching UserRole if not already assigned + // [x] 1. Check if Authorizer Mod Badge then Auto Promote to Mod if not Mod let trgchatter = trgchatter.to_lowercase(); @@ -1283,23 +1072,10 @@ impl IdentityManager { .await; } - - // [x] - May want to Auto VIP Authorizer here - Some(ChatBadge::VIP) - if (!authusrroles.contains(&UserRole::VIP(channel.clone()))) => - { - authusrroles.push(UserRole::VIP(channel.clone())); - - self.affirm_chatter_in_db(authorizer.clone()).await; - self.add_role(authorizer.clone(), UserRole::VIP(channel.clone())) - .await; - } - _ => (), } } - // [x] 2. Get Authorizer & Target Chatter Roles let trgusrroles = self @@ -1317,6 +1093,7 @@ impl IdentityManager { (authusrroles, trgusrroles) }; + // [x] 3. If the authorizer & Target Chatters are the same, and the Authorizer is not a Admin, return no change if trgchatter == authorizer && !authusrroles.contains(&UserRole::BotAdmin) { return ChangeResult::NoChange("Can't target yourself".to_string()); @@ -1335,59 +1112,20 @@ impl IdentityManager { } } - botlog::debug( - format!("VIP Evaluation : Channel = {:?} ; trg_role = {:?} ", - channel.clone(),trg_role - ).as_str(), - Some("identity.rs > promote()".to_string()), - None, - ); - - // [x] 4b. If Authorizer is a Mod,SupMod,Broadcaster & trg_role is Some(VIP(channel)), can Promote a Target Chatter > VIP - if let Some(trg_chnl) = channel.clone() { - if trg_role == Some(UserRole::VIP(trg_chnl.clone())) - && ( authusrroles.contains(&UserRole::Mod(trg_chnl.clone())) - || authusrroles.contains(&UserRole::SupMod(trg_chnl.clone())) - || authusrroles.contains(&UserRole::Broadcaster) - ) - { - if trgusrroles.contains(&UserRole::VIP(trg_chnl.clone())) { - return ChangeResult::NoChange("Already has the role".to_string()); - } - else { - self.affirm_chatter_in_db(trgchatter.clone()).await; - - self.add_role(trgchatter.clone(), UserRole::VIP(trg_chnl.clone())).await; - - return ChangeResult::Success("Promotion Successful".to_string()); - } - } else if trg_role == Some(UserRole::VIP(trg_chnl.clone())) - { - return ChangeResult::Failed(String::from("You're not permitted to do that")); - } - } - - - - - - // [x] 4c. If target is Broadcaster, return NoChange + // [x] 4b. If target is Broadcaster, return NoChange if trgusrroles.contains(&UserRole::Broadcaster) { return ChangeResult::NoChange("Can't target broadcaster".to_string()); } - /* - [x] 4d. If Authorizer is a SupMod,Broadcaster,BotAdmin , can Promote Target Chatter > Mod + [ ] 4c. If Authorizer is a SupMod,Broadcaster,BotAdmin , can Promote Target Chatter > Mod - NOTE : We do not validate trg_role here - app logic requires you to promote 1 to Mod and 1 more to SupMod - [x] 4e. If Authorizer is a Broadcaster,BotAdmin , can Promote a Target Mod > SupMod + [ ] 4d. If Authorizer is a Broadcaster,BotAdmin , can Promote a Target Mod > SupMod - NOTE : We do not validate trg_role here - app logic requires you to promote 1 to Mod and 1 more to SupMod */ if let Some(trg_chnl) = channel.clone() { - - // 1. Checks first if Target User's Roles do not Include Broadcaster,Mod,SupMod for the Channel if !trgusrroles.contains(&UserRole::Broadcaster) && !trgusrroles.contains(&UserRole::Mod(trg_chnl.clone())) && !trgusrroles.contains(&UserRole::SupMod(trg_chnl.clone())) @@ -1396,8 +1134,6 @@ impl IdentityManager { // target's Next Role would be Mod // Authorizer must be SupMod,Broadcaster,BotAdmin // > Promote target to Mod - - // 2. If Authorizer has Elevated Admin Roles for the Channel (SupMod,Broadcaster,BotAdmin) > set target to MOD if authusrroles.contains(&UserRole::SupMod(trg_chnl.clone())) || authusrroles.contains(&UserRole::Broadcaster) || authusrroles.contains(&UserRole::BotAdmin) @@ -1459,8 +1195,6 @@ impl IdentityManager { } }; - - botlog::warn( "Runtime reached undeveloped code", Some("identity.rs > promote()".to_string()), @@ -1474,8 +1208,7 @@ impl IdentityManager { authorizer: String, authorizer_badge: &Option<ChatBadge>, trgchatter: String, - channel: Option<Channel>, - trg_role: Option<UserRole>, + channel: Option<ChType>, ) -> ChangeResult { botlog::trace(&format!("IN VARS for demote() : Authorizer : {:?} ; Target Chatter : {} ; Target Channel : {:?}", authorizer,trgchatter,channel), Some("identity.rs > demote()".to_string()), None); @@ -1489,7 +1222,7 @@ impl IdentityManager { Use the roles of the above to determine whether the authorizer can demote the target user or not */ - // [x] 1. If Authorizer's Badge is Mod/VIP, ensuring Sender is in DB as Mod(Channel) + // [x] 1. If Authorizer's Badge is Mod, ensuring Sender is in DB as Mod(Channel) let trgchatter = trgchatter.to_lowercase(); @@ -1512,17 +1245,6 @@ impl IdentityManager { self.add_role(authorizer.clone(), UserRole::Mod(channel.clone())) .await; } - // [x] - May want to Auto VIP Authorizer here - Some(ChatBadge::VIP) - if (!authusrroles.contains(&UserRole::VIP(channel.clone()))) => - { - authusrroles.push(UserRole::VIP(channel.clone())); - - self.affirm_chatter_in_db(authorizer.clone()).await; - self.add_role(authorizer.clone(), UserRole::VIP(channel.clone())) - .await; - } - _ => (), } } @@ -1538,29 +1260,7 @@ impl IdentityManager { return ChangeResult::NoChange("Can't target yourself".to_string()); } - // [x] 4. If Authorizer is a Mod,SupMod,Broadcaster & trg_role is Some(VIP(channel)), can Promote a Target Chatter > VIP - - if ( authusrroles.contains(&UserRole::Mod(channel.clone())) - || authusrroles.contains(&UserRole::SupMod(channel.clone())) - || authusrroles.contains(&UserRole::Broadcaster) - ) - && trg_role == Some(UserRole::VIP(channel.clone())) { - if !trgusrroles.contains(&UserRole::VIP(channel.clone())) { - return ChangeResult::NoChange("Already does not have VIP role".to_string()); - } - else { - - self.remove_role(trgchatter.clone(), UserRole::VIP(channel.clone())).await; - - return ChangeResult::Success("Demotion Successful".to_string()); - } - } - - - // [x] 5. - Mod/SupMod Logic - - - // [x] 5a. Authorizers who are BotAdmin, Broadcaster or Supermod can demote a Mod + // [x] 4a. Authorizers who are BotAdmin, Broadcaster or Supermod can demote a Mod if (authusrroles.contains(&UserRole::BotAdmin) || authusrroles.contains(&UserRole::Broadcaster) @@ -1571,7 +1271,7 @@ impl IdentityManager { .await; return ChangeResult::Success("Demoted successfully".to_string()); } - // [x] 5b. Authorizers who are BotAdmin, Broadcaster can demote a SupMod + // [x] 4b. Authorizers who are BotAdmin, Broadcaster can demote a SupMod else if (authusrroles.contains(&UserRole::BotAdmin) || authusrroles.contains(&UserRole::Broadcaster)) && trgusrroles.contains(&UserRole::SupMod(channel.clone())) @@ -1582,7 +1282,7 @@ impl IdentityManager { .await; return ChangeResult::Success("Demoted successfully".to_string()); } - // [x] 5c. When Target chatter isnt a Mod or SupMod to demote + // [x] 4c. When Target chatter isnt a Mod or SupMod to demote else if !trgusrroles.contains(&UserRole::Mod(channel.clone())) && !trgusrroles.contains(&UserRole::SupMod(channel.clone())) { @@ -1590,7 +1290,7 @@ impl IdentityManager { "Target chatter does not have a role that can be demoted".to_string(), ); } - // [x] 5d. When they're only a Mod + // [x] 4d. When they're only a Mod else if authusrroles.contains(&UserRole::Mod(channel.clone())) { return ChangeResult::Failed("You're not permitted to do that".to_string()); } @@ -1605,13 +1305,8 @@ impl IdentityManager { pub async fn getspecialuserroles( &self, chattername: String, - channel: Option<Channel>, + channel: Option<ChType>, ) -> Vec<UserRole> { - /* - NOTE : Any NEW or CHANGES to UserRole type should have additional handling here - Specifically for Channel Elevated Roles - */ - /* Note : Ideally this be called for a given chatter name ? */ @@ -1633,20 +1328,22 @@ impl IdentityManager { // Checks if broadcaster let channel_out = match channel { - Some(chnl) => { - // In this block, Some input channel is given - // We're comparing the channel name with chattername to determine if they're a broadcaster - // if chattername == chnl.0 - if chattername == chnl.0.to_lowercase() - { - evalsproles.push(UserRole::Broadcaster); + Some(channel_tmp) => { + match channel_tmp { + ChType::Channel(channel_tmp) => { + // In this block, Some input channel is given + // We're comparing the channel name with chattername to determine if they're a broadcaster + if chattername == channel_tmp.to_lowercase() { + evalsproles.push(UserRole::Broadcaster); + } + + Some(ChType::Channel(channel_tmp)) + } // _ => () } - Some(chnl) - }, + } None => None, }; - let rolesdb = Arc::clone(&self.special_roles_users); let rolesdb_lock = rolesdb.read().await; @@ -1682,9 +1379,6 @@ impl IdentityManager { if a.read().await.contains(&UserRole::SupMod(channel.clone())) { evalsproles.push(UserRole::SupMod(channel.clone())); } - if a.read().await.contains(&UserRole::VIP(channel.clone())) { - evalsproles.push(UserRole::VIP(channel.clone())); - } // else {}; } None => { @@ -1730,8 +1424,8 @@ mod core_identity { fn user_role_identity() { Log::set_file_ext(Extension::Log); assert_eq!( - UserRole::SupMod(Channel("strong".to_string())), - UserRole::SupMod(Channel("Strong".to_lowercase())) + UserRole::SupMod(ChType::Channel("strong".to_string())), + UserRole::SupMod(ChType::Channel("Strong".to_lowercase())) ); } @@ -1746,8 +1440,7 @@ mod core_identity { let (usr, channelname, chat_badge, cmdreqroles) = ( bot, - // Channel::construct("twitchchanneltest".to_string()), - Channel("twitchchanneltest".to_string()), + ChType::Channel("twitchchanneltest".to_string()), None, vec![] ); @@ -1772,8 +1465,7 @@ mod core_identity { let test_id_mgr = IdentityManager::init(); // [x] Mod Attempts to Promote User - // let channel = Some(Channel::construct("twitchchanneltest".to_string())); - let channel = Some(Channel("twitchchanneltest".to_string())); + let channel = Some(ChType::Channel("twitchchanneltest".to_string())); let trgchatter = "regularChatter".to_string(); let authorizer_badge = &Some(ChatBadge::Mod); let authorizer = "chatMod".to_string(); @@ -1803,7 +1495,7 @@ mod core_identity { let test_id_mgr = IdentityManager::init(); // [x] Broadcaster Promotes Chatter to SupMod - let channel = Some(Channel("broadcasterer".to_string())); + let channel = Some(ChType::Channel("broadcasterer".to_string())); let trgchatter = "regularChatter".to_string(); let authorizer_badge = &Some(ChatBadge::Broadcaster); let authorizer = "broadcasterer".to_string(); @@ -1828,7 +1520,7 @@ mod core_identity { .getspecialuserroles(trgchatter.clone(), channel.clone()) .await; - assert!(rslt.contains(&UserRole::Mod(Channel("broadcasterer".to_string())))); + assert!(rslt.contains(&UserRole::Mod(ChType::Channel("broadcasterer".to_string())))); let rslt = test_id_mgr .promote( @@ -1849,7 +1541,7 @@ mod core_identity { .getspecialuserroles(trgchatter.clone(), channel.clone()) .await; - assert!(rslt.contains(&UserRole::SupMod(Channel( + assert!(rslt.contains(&UserRole::SupMod(ChType::Channel( "broadcasterer".to_string() )))); @@ -1882,7 +1574,8 @@ mod core_identity { let broadcaster = "broadcasterer".to_string(); let broadcaster_badge = &Some(ChatBadge::Broadcaster); - let channel = Channel(broadcaster.clone()); + // let channel = Some(ChType::Channel(broadcaster.clone())); + let channel = ChType::Channel(broadcaster.clone()); let supchatter = "superModerator".to_string(); let trg_role = None; @@ -1924,9 +1617,10 @@ mod core_identity { // [x] SupMod Attempts to Promote Chatter to SupMod + // let broadcaster = "broadcasterer".to_string(); let authorizer = supchatter; let authorizer_badge = &Some(ChatBadge::Broadcaster); - let channel = Some(Channel(broadcaster.clone())); + let channel = Some(ChType::Channel(broadcaster.clone())); let trgchatter = "regularChatter".to_string(); let trg_role = None; @@ -1994,9 +1688,10 @@ mod core_identity { // [x] SupMod Attempts to Promote Chatter to SupMod + // let broadcaster = "broadcasterer".to_string(); let authorizer = botadmin; let authorizer_badge = botadmin_badge; - let channel = Some(Channel("somechannel".to_string())); + let channel = Some(ChType::Channel("somechannel".to_string())); let trgchatter = "regularChatter".to_string(); let trg_role = None; @@ -2071,7 +1766,7 @@ mod core_identity { let supmod = "supmoder".to_string(); - let channel = Some(Channel("somechannel".to_string())); + let channel = Some(ChType::Channel("somechannel".to_string())); test_id_mgr.affirm_chatter_in_db(supmod.clone()).await; test_id_mgr @@ -2104,7 +1799,6 @@ mod core_identity { let authorizer = regmod.clone(); let authorizer_badge = &None; let trgchatter = supmod.clone(); - let trg_role = None; let rslt = test_id_mgr .demote( @@ -2112,7 +1806,6 @@ mod core_identity { authorizer_badge, trgchatter.clone(), channel.clone(), - trg_role.clone(), ) .await; @@ -2133,7 +1826,6 @@ mod core_identity { authorizer_badge, trgchatter.clone(), channel.clone(), - trg_role.clone(), ) .await; @@ -2148,7 +1840,6 @@ mod core_identity { authorizer_badge, trgchatter.clone(), channel.clone(), - trg_role.clone(), ) .await; @@ -2159,213 +1850,4 @@ mod core_identity { ) ); } - - - - #[tokio::test] - async fn vip_workflow_01() { - Log::set_file_ext(Extension::Log); - //Log::set_level(Level::Trace); - - // Channel Elevated User Promotes/Demotes VIP - - let test_id_mgr = IdentityManager::init(); - - - - // [x] 1. Requester has a Mod Role - - let channel = Some(Channel("somechannel".to_string())); - let authorizer_badge = &Some(ChatBadge::Mod); - let authorizer = "chatMod".to_string(); - let trgchatter = "regularChatter".to_string(); - let trg_role = Some(UserRole::VIP(channel.clone().unwrap())); - - let authorizer = authorizer.to_lowercase(); - let trgchatter = trgchatter.to_lowercase(); - - test_id_mgr.affirm_chatter_in_db(authorizer.clone()).await; - test_id_mgr.affirm_chatter_in_db(trgchatter.clone()).await; - - test_id_mgr - .add_role(authorizer.clone(), UserRole::Mod(channel.clone().unwrap())) - .await; - - let rslt = test_id_mgr - .getspecialuserroles(authorizer.clone(), channel.clone()) - .await; - - assert_eq!(rslt,vec![UserRole::Mod(channel.clone().unwrap())]); - - // [x] 2. assert getspecialuserroles for Target Chatter - - let rslt = test_id_mgr - .getspecialuserroles(trgchatter.clone(), channel.clone()) - .await; - - assert_eq!(rslt,vec![]); - - // [x] 3. Requester Promotes a Target Chatter to VIP - - let rslt = test_id_mgr - .promote( - authorizer.clone(), - authorizer_badge, - trgchatter.clone(), - channel.clone(), - trg_role.clone(), - ) - .await; - - assert_eq!( - rslt, - ChangeResult::Success("Promotion Successful".to_string()) - ); - - - // [x] 4. assert getspecialuserroles for Target Chatter - - let rslt = test_id_mgr - .getspecialuserroles(trgchatter.clone(), channel.clone()) - .await; - - assert!(rslt.contains(&UserRole::VIP(channel.clone().unwrap()))); - - // [x] 5. Requester Promotes a Target Chatter to VIP - - let rslt = test_id_mgr - .promote( - authorizer.clone(), - authorizer_badge, - trgchatter.clone(), - channel.clone(), - trg_role.clone(), - ) - .await; - - assert_eq!( - rslt, - ChangeResult::NoChange("Already has the role".to_string()) - ); - - - // [x] 6. assert getspecialuserroles for Target Chatter - - let rslt = test_id_mgr - .getspecialuserroles(trgchatter.clone(), channel.clone()) - .await; - - assert!(rslt.contains(&UserRole::VIP(channel.clone().unwrap()))); - - - // [x] 7. Requester Demotes a Target Chatter from VIP - - let rslt = test_id_mgr - .demote( - authorizer.clone(), - authorizer_badge, - trgchatter.clone(), - channel.clone(), - trg_role.clone(), - ) - .await; - - assert_eq!( - rslt, - ChangeResult::Success("Demotion Successful".to_string()) - ); - - // [x] 8. assert getspecialuserroles for Target Chatter - - let rslt = test_id_mgr - .getspecialuserroles(trgchatter.clone(), channel.clone()) - .await; - - // assert!(rslt.contains(&UserRole::VIP(channel.clone().unwrap()))); - assert_eq!(rslt,vec![]); - - - - // [x] 9. Requester Demotes a Target Chatter from VIP - - let rslt = test_id_mgr - .demote( - authorizer.clone(), - authorizer_badge, - trgchatter.clone(), - channel.clone(), - trg_role.clone(), - ) - .await; - - assert_eq!( - rslt, - ChangeResult::NoChange("Already does not have VIP role".to_string()) - ); - - // [x] 10. assert getspecialuserroles for Target Chatter - - let rslt = test_id_mgr - .getspecialuserroles(trgchatter.clone(), channel.clone()) - .await; - - assert_eq!(rslt,vec![]); - - - - - - } - - - #[tokio::test] - async fn auto_vip_workflow() { - Log::set_file_ext(Extension::Log); - - let mut test_id_mgr = IdentityManager::init(); - - - - // let channel = Some(Channel("somechannel".to_string())); - let channel = Channel("somechannel".to_string()); - let authorizer_badge = Some(ChatBadge::VIP); - let authorizer = "chatMod".to_string(); - - let authorizer = authorizer.to_lowercase(); - - - // [x] 1. assert getspecialuserroles for Target Chatter - - let rslt = test_id_mgr - .getspecialuserroles(authorizer.clone(), Some(channel.clone())) - .await; - - assert_eq!(rslt,vec![]); - - // [x] 2. Run canuserrun() for the Requester . (This is ran after BotCommands are ran) - - let rslt = test_id_mgr - .can_user_run( - authorizer.clone(), - channel.clone(), - authorizer_badge, - vec![ - UserRole::VIP(OF_CMD_CHANNEL), - ] - ).await; - - assert_eq!(rslt, - (Permissible::Allow,ChangeResult::Success("Auto Promoted VIP".to_string()))); - - // [x] 3. assert getspecialuserroles for Target Chatter - - let rslt = test_id_mgr - .getspecialuserroles(authorizer.clone(), Some(channel.clone())) - .await; - - assert_eq!(rslt,vec![UserRole::VIP(channel)]); - - - - } } diff --git a/src/custom.rs b/src/custom.rs index 56c6257..fc802e6 100644 --- a/src/custom.rs +++ b/src/custom.rs @@ -1,5 +1,5 @@ /* - `custom` will : + `modules` will : - be a starting refrence point for the bot instance to pull module definitions for */ @@ -11,9 +11,7 @@ pub use crate::core::botmodules::ModulesManager; // [ ] Load submodules -// mod experiments; -mod experimental; -mod thisguy; +mod experiments; // [ ] init() function that accepts bot instance - this is passed to init() on submodules @@ -21,7 +19,5 @@ pub async fn init(mgr: Arc<ModulesManager>) { // Modules initializer loads modules into the bot // this is achieved by calling submodules that also have fn init() defined - // experiments::init(mgr).await - experimental::init(mgr.clone()).await; - thisguy::init(&mgr).await; + experiments::init(mgr).await } diff --git a/src/custom/experimental.rs b/src/custom/experimental.rs deleted file mode 100644 index 409abd1..0000000 --- a/src/custom/experimental.rs +++ /dev/null @@ -1,24 +0,0 @@ -/* - `experimental` will : - - be for mostly experimental -*/ - -use std::sync::Arc; - -// pub use crate::core::botinstance::BotInstance; -pub use crate::core::botmodules::ModulesManager; - -// [ ] Load submodules - -mod experiment001; -mod experiment002; - -// [ ] init() function that accepts bot instance - this is passed to init() on submodules - -pub async fn init(mgr: Arc<ModulesManager>) { - // Modules initializer loads modules into the bot - // this is achieved by calling submodules that also have fn init() defined - - experiment001::init(Arc::clone(&mgr)).await; - experiment002::init(Arc::clone(&mgr)).await; -} diff --git a/src/custom/experimental/experiment002.rs b/src/custom/experimental/experiment002.rs deleted file mode 100644 index a7d1610..0000000 --- a/src/custom/experimental/experiment002.rs +++ /dev/null @@ -1,196 +0,0 @@ -/* - Custom Modules - - - Usage : - [ ] within the file's init(), define BotActions & Load them into the ModulesManager - [ ] Define Execution Bodies for these BotActions - [ ] Afterwards, add the following to parent modules.rs file - - mod <modulename>; - - within init(), <modulename>::init(mgr).await - -*/ - - -const OF_CMD_CHANNEL:Channel = Channel(String::new()); - - -use std::sync::Arc; - -use chrono::{TimeZone,Local}; -use twitch_irc::message::ReplyToMessage; - - -use crate::core::bot_actions::ExecBodyParams; -use crate::core::botinstance::Channel; -use crate::core::botlog; - -use casual_logger::Log; - -use crate::core::bot_actions::actions_util; -use crate::core::botmodules::{BotActionTrait, BotCommand, BotModule, ModulesManager}; - -use crate::core::identity::UserRole::*; - -pub async fn init(mgr: Arc<ModulesManager>) { - - - // 1. Define the BotAction - let botc1 = BotCommand { - module: BotModule(String::from("experiments002")), - command: String::from("say"), // command call name - alias: vec![ - "s".to_string(), - ], // String of alternative names - exec_body: actions_util::asyncbox(sayout), - help: String::from("Test Command tester"), - required_roles: vec![ - BotAdmin, - // Mod(OF_CMD_CHANNEL), - VIP(OF_CMD_CHANNEL), - ], - }; - - // 2. Add the BotAction to ModulesManager - botc1.add_to_modmgr(Arc::clone(&mgr)).await; - - // If enabling by defauling at instance level , uncomment the following - // mgr.set_instance_enabled(BotModule(String::from("experiments002"))).await; - - -} - - -async fn sayout(params : ExecBodyParams) { - - - /* - usage : - <target channel> <message> - */ - - - - let reply_parent = if let Some(Some(reply)) = params.msg.source.tags.0.get("reply-parent-msg-body") { - Some(reply) - } else { None } - ; - - - let reply_parent_ts = if let Some(Some(replyts)) = params.msg.source.tags.0.get("tmi-sent-ts") { - - let a: i64 = replyts.parse().unwrap(); - let b = Local.timestamp_millis_opt(a).unwrap(); - Some(b.format("%m-%d %H:%M")) - } else { None } - ; - - // [x] Unwraps arguments from message - - - let argrslt = - if let Some((_,str1)) = params.msg.message_text.split_once(' ') { - if reply_parent.is_none() { - if let Some((channelstr,msgstr)) = str1.split_once(' ') { - Some((channelstr,msgstr)) - } - else { None } - } else if let Some((_,str2)) = str1.split_once(' ') { - if let Some((channelstr,msgstr)) = str2.split_once(' ') { - Some((channelstr,msgstr)) - } - else { None } - } else { None } - } - else { None }; - - - - - match argrslt { - Some((trgchnl,outmsg)) => { - - let bot = Arc::clone(¶ms.bot); - - let botlock = bot.read().await; - - // [x] Validate first if trgchnl exists - - botlog::trace( - &format!("[TRACE] Evaluated status of {} : {:?}", - trgchnl.to_string().clone(),botlock.botmgrs.chat.client.get_channel_status(trgchnl.to_string().clone()).await), - Some("Chat > send_botmsg".to_string()), - None, - ); - - /* - 1. If a Reply , - [ ] Get Parent Content message - reply_parent - [ ] Get Parent Chatter - reply_parent_usr - [ ] Get Parent Channel - msg.channel_login - -> Share this first then - [ ] Get Reply Message (that triggered bot command) - msgstr - [ ] Get Reply Sender - msg.sender.name - [ ] Get Target Channel - trgchnl - - 2. If not a reply - [ ] Get Reply Message (that triggered bot command) - msgstr - [ ] Get Reply Sender - msg.sender.name - [ ] Get Target Channel - trgchnl - */ - - // reply_parent_ts - - let newoutmsg = if let Some(srcmsg) = reply_parent { - - format!("{} {} @ {} : {}", - reply_parent_ts.unwrap(), - params.msg.sender.name, - params.msg.channel_login, - srcmsg) - } else { - format!("in {} - {} : {}", - params.msg.channel_login, - params.msg.sender.name, - outmsg) - }; - - botlock - .botmgrs - .chat - .say( - trgchnl.to_string(), - newoutmsg.to_string(), - params.clone(), - ).await; - - - }, - None => { - botlog::debug( - "sayout had issues trying to parse arguments", - Some("experiment002 > sayout".to_string()), - Some(¶ms.msg), - ); - - let bot = Arc::clone(¶ms.bot); - - let botlock = bot.read().await; - - // uses chat.say_in_reply_to() for the bot controls for messages - botlock - .botmgrs - .chat - .say_in_reply_to( - Channel(params.clone().msg.channel_login().to_string()), - params.clone().msg.message_id().to_string(), - String::from("Invalid arguments"), - params.clone() - ).await; - - - }, - - } - - Log::flush(); -} \ No newline at end of file diff --git a/src/custom/experimental/experiment001.rs b/src/custom/experiments.rs similarity index 57% rename from src/custom/experimental/experiment001.rs rename to src/custom/experiments.rs index 18ef6f6..de2f424 100644 --- a/src/custom/experimental/experiment001.rs +++ b/src/custom/experiments.rs @@ -10,19 +10,17 @@ */ - -const OF_CMD_CHANNEL:Channel = Channel(String::new()); - - use rand::Rng; -use twitch_irc::message::ReplyToMessage; use std::sync::Arc; -use crate::core::bot_actions::ExecBodyParams; -use crate::core::botinstance::Channel; +use twitch_irc::message::PrivmsgMessage; + +// use crate::core::botinstance::ChType::Channel; +use crate::core::botinstance::ChType; +use ChType::Channel; use crate::core::botlog; -use crate::core::bot_actions::actions_util; +use crate::core::bot_actions::actions_util::{self, BotAR}; use crate::core::botmodules::{BotActionTrait, BotCommand, BotModule, Listener, ModulesManager}; use crate::core::identity::UserRole::*; @@ -31,6 +29,8 @@ use tokio::time::{sleep, Duration}; pub async fn init(mgr: Arc<ModulesManager>) { + const OF_CMD_CHANNEL:ChType = Channel(String::new()); + // 1. Define the BotAction let botc1 = BotCommand { module: BotModule(String::from("experiments001")), @@ -60,6 +60,18 @@ pub async fn init(mgr: Arc<ModulesManager>) { // 2. Add the BotAction to ModulesManager list1.add_to_modmgr(Arc::clone(&mgr)).await; + + // // 1. Define the BotAction + // let list1 = Listener { + // module: BotModule(String::from("experiments001")), + // name: String::from("babygirl Listener"), + // exec_body: actions_util::asyncbox(babygirl), + // help: String::from(""), + // }; + + // // 2. Add the BotAction to ModulesManager + // list1.add_to_modmgr(Arc::clone(&mgr)).await; + // 1. Define the BotAction let botc1 = BotCommand { module: BotModule(String::from("experiments001")), @@ -96,141 +108,75 @@ pub async fn init(mgr: Arc<ModulesManager>) { // 2. Add the BotAction to ModulesManager botc1.add_to_modmgr(Arc::clone(&mgr)).await; - let bc1 = BotCommand { - module: BotModule(String::from("experiments001")), - command: String::from("rp1"), // command call name - alias: vec![ - String::from("rp2"), - String::from("rp3")], // String of alternative names - exec_body: actions_util::asyncbox(rp), - help: String::from("Test Command tester"), - required_roles: vec![ - BotAdmin, - Mod(OF_CMD_CHANNEL), - ], - }; - bc1.add_core_to_modmgr(Arc::clone(&mgr)).await; } -async fn rp(params : ExecBodyParams) -{ - //triggers if the message is a reply - if params.get_parent_reply().is_some(){ - - //getting the channel id where the message was sent - let channel_id = params.get_parent_reply().unwrap().channel_login; - - //getting the first message id that was sent - let message_id = params.get_parent_reply().unwrap().message_id; - - //just for testing purposes - //print!("{} , {}",channel_id, message_id); - - //creating a tuple with the channel id and message id - let answear = - ( - channel_id.clone(), - message_id.clone() - ); - - let bot = Arc::clone(¶ms.bot); - let botlock = bot.read().await; - // uses chat.say_in_reply_to() for the bot controls for messages - botlock - .botmgrs - .chat - .say_in_reply_to( - //using the tuple as param to the message being replied - Channel(answear.0), - answear.1, - String::from("hey there"), - params.clone() - ).await; - } - else - { - println!("no reply") - } -} - - - -async fn good_girl(params : ExecBodyParams) { - +async fn good_girl(bot: BotAR, msg: PrivmsgMessage) { // [ ] Uses gen_ratio() to output bool based on a ratio probability . // - For example gen_ratio(2,3) is 2 out of 3 or 0.67% (numerator,denomitator) // - More Info : https://rust-random.github.io/rand/rand/trait.Rng.html#method.gen_ratio - if params.msg.sender.name.to_lowercase() == "ModulatingForce".to_lowercase() - || params.msg.sender.name.to_lowercase() == "mzNToRi".to_lowercase() - || params.msg.sender.name.to_lowercase() == "haruyuumei".to_lowercase() + if msg.sender.name.to_lowercase() == "ModulatingForce".to_lowercase() + || msg.sender.name.to_lowercase() == "mzNToRi".to_lowercase() + // if msg.sender.name.to_lowercase() == "mzNToRi".to_lowercase() { botlog::debug( "Good Girl Detected > Pausechamp", Some("experiments > goodgirl()".to_string()), - Some(¶ms.msg), + Some(&msg), ); - let rollwin = rand::thread_rng().gen_ratio(1, 10); + let rollwin = rand::thread_rng().gen_ratio(1, 8); if rollwin { botlog::debug( "Oh that's a good girl!", Some("experiments > goodgirl()".to_string()), - Some(¶ms.msg), + Some(&msg), ); - let bot = Arc::clone(¶ms.bot); + let bot = Arc::clone(&bot); + let botlock = bot.read().await; // uses chat.say_in_reply_to() for the bot controls for messages botlock - .botmgrs - .chat - .say_in_reply_to( - Channel(params.clone().msg.channel_login().to_string()), - params.clone().msg.message_id().to_string(), - String::from("GoodGirl xdd "), - params.clone() - ).await; + .botmgrs + .chat + .say_in_reply_to(&msg, String::from("GoodGirl xdd ")) + .await; } } } -async fn testy(params : ExecBodyParams) { +async fn testy(mut _chat: BotAR, msg: PrivmsgMessage) { println!("testy triggered!"); // NOTE : This test function intends to print (e.g., to stdout) at fn call botlog::debug( "testy triggered!", Some("experiments > testy()".to_string()), - Some(¶ms.msg), + Some(&msg), ); } -async fn babygirl(params : ExecBodyParams) { - - +async fn babygirl(bot: BotAR, msg: PrivmsgMessage) { println!("babygirl triggered!"); // NOTE : This test function intends to print (e.g., to stdout) at fn call botlog::debug( "babygirl triggered!", Some("experiments > babygirl()".to_string()), - Some(¶ms.msg), + Some(&msg), ); - let bot = Arc::clone(¶ms.bot); + let bot = Arc::clone(&bot); let botlock = bot.read().await; - + // uses chat.say_in_reply_to() for the bot controls for messages botlock .botmgrs .chat - .say_in_reply( - Channel(params.clone().msg.channel_login().to_string()), - String::from("16:13 notohh: cafdk"), - params.clone() - ).await; + .say_in_reply_to(&msg, String::from("16:13 notohh: cafdk")) + .await; sleep(Duration::from_secs_f64(0.5)).await; @@ -238,11 +184,8 @@ async fn babygirl(params : ExecBodyParams) { botlock .botmgrs .chat - .say_in_reply( - Channel(params.clone().msg.channel_login().to_string()), - String::from("16:13 notohh: have fun eating princess"), - params.clone() - ).await; + .say_in_reply_to(&msg, String::from("16:13 notohh: have fun eating princess")) + .await; sleep(Duration::from_secs_f64(2.0)).await; @@ -250,23 +193,21 @@ async fn babygirl(params : ExecBodyParams) { botlock .botmgrs .chat - .say_in_reply( - Channel(params.clone().msg.channel_login().to_string()), - String::from("16:13 notohh: baby girl"), - params.clone() - ).await; - + .say_in_reply_to(&msg, String::from("16:13 notohh: baby girl")) + .await; } -async fn routinelike(params : ExecBodyParams) { + + +async fn routinelike(_bot: BotAR, msg: PrivmsgMessage) { println!("routinelike triggered!"); // NOTE : This test function intends to print (e.g., to stdout) at fn call botlog::debug( "routinelike triggered!", Some("experiments > routinelike()".to_string()), - Some(¶ms.msg), + Some(&msg), ); // spawn an async block that runs independently from others diff --git a/src/custom/thisguy.rs b/src/custom/thisguy.rs deleted file mode 100644 index cc53129..0000000 --- a/src/custom/thisguy.rs +++ /dev/null @@ -1,62 +0,0 @@ -use crate::core::bot_actions::*; -use crate::core::botinstance::Channel; -use crate::core::botlog; -use crate::core::botmodules::{BotActionTrait, BotCommand, BotModule, ModulesManager}; -use crate::core::identity::UserRole::*; -use rand::Rng; -use twitch_irc::message::ReplyToMessage; -use std::sync::Arc; -use tokio::time::{sleep, Duration}; -const OF_CMD_CHANNEL:Channel = Channel(String::new()); - -async fn tsg(params: ExecBodyParams) { - - let phrases: [String; 6] = [ - "Clueless ".to_string(), - "ICANT This guy....".to_string(), - "He is right tho".to_string(), - "KEKW true!".to_string(), - "OMEGALUL wth man...".to_string(), - "PepeLaugh El no sabe".to_string(), - ]; - - let r = rand::thread_rng().gen_range(0..=4); - let a = phrases[r].clone(); - - botlog::debug( - "This guy works!", - Some("modules > thisguy()".to_string()), - Some(¶ms.msg), - ); - let bot = Arc::clone(¶ms.bot); - let botlock = bot.read().await; - - // uses chat.say_in_reply_to() for the bot controls for messages - botlock - .botmgrs - .chat - .say_in_reply( - Channel(params.clone().msg.channel_login().to_string()), - a, - params.clone() - ) - .await; - sleep(Duration::from_secs_f64(0.5)).await; -} - -pub async fn init(mgr: &Arc<ModulesManager>) { - BotCommand { - module: BotModule(String::from("thisguy")), - command: String::from("thisguy"), - alias: vec![String::from("Thisguy")], - exec_body: actions_util::asyncbox(tsg), - help: String::from("test"), - required_roles: vec![ - BotAdmin, - Mod(OF_CMD_CHANNEL), - Broadcaster - ], - } - .add_to_modmgr(Arc::clone(mgr)) - .await; -} \ No newline at end of file diff --git a/src/lib.rs b/src/lib.rs index 667ce02..c5ba775 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,2 +1,2 @@ pub mod core; -pub mod custom; \ No newline at end of file +pub mod custom; diff --git a/src/main.rs b/src/main.rs index 6bc6c0f..40b5598 100644 --- a/src/main.rs +++ b/src/main.rs @@ -33,11 +33,7 @@ pub async fn main() { for acts in (*actsdb_lock).values() { for act in acts { - - let act_prelock = act; - let act = act_prelock.read().await; - - let outstr = match &(*act) { + let outstr = match act { botmodules::BotAction::C(b) => { format!("bot actions > Command : {}", b.command) } diff --git a/statix.toml b/statix.toml deleted file mode 100644 index fbe25a9..0000000 --- a/statix.toml +++ /dev/null @@ -1,3 +0,0 @@ -disabled = [] -nix_version = '2.4' -ignore = ['.direnv']