diff --git a/Cargo.lock b/Cargo.lock index a3bb95b..04ffcae 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -645,6 +645,8 @@ name = "info" version = "0.1.0" dependencies = [ "nopalmo", + "tokio", + "tracing", ] [[package]] @@ -680,6 +682,16 @@ version = "0.2.155" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "97b3888a4aecf77e811145cadf6eef5901f4782c53886191b2f693f24761847c" +[[package]] +name = "libloading" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4979f22fdb869068da03c9f7528f8297c6fd2606bc3a4affe42e6a823fdb8da4" +dependencies = [ + "cfg-if", + "windows-targets 0.52.5", +] + [[package]] name = "linux-raw-sys" version = "0.4.14" @@ -750,19 +762,21 @@ dependencies = [ [[package]] name = "mio" -version = "0.8.11" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4a650543ca06a924e8b371db273b2756685faae30f8487da1b56505a8f78b0c" +checksum = "4569e456d394deccd22ce1c1913e6ea0e54519f577285001215d33557431afe4" dependencies = [ + "hermit-abi", "libc", "wasi", - "windows-sys 0.48.0", + "windows-sys 0.52.0", ] [[package]] name = "nopalmo" version = "0.1.0" dependencies = [ + "libloading", "phf", "rand", "regex", @@ -786,16 +800,6 @@ dependencies = [ "autocfg", ] -[[package]] -name = "num_cpus" -version = "1.16.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43" -dependencies = [ - "hermit-abi", - "libc", -] - [[package]] name = "object" version = "0.32.2" @@ -1491,28 +1495,27 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.37.0" +version = "1.39.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1adbebffeca75fcfd058afa480fb6c0b81e165a0323f9c9d39c9697e37c46787" +checksum = "daa4fb1bc778bd6f04cbfc4bb2d06a7396a8f299dc33ea1900cedaa316f467b1" dependencies = [ "backtrace", "bytes", "libc", "mio", - "num_cpus", "parking_lot", "pin-project-lite", "signal-hook-registry", "socket2", "tokio-macros", - "windows-sys 0.48.0", + "windows-sys 0.52.0", ] [[package]] name = "tokio-macros" -version = "2.2.0" +version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b8a1e28f2deaa14e508979454cb3a223b10b938b45af148bc0986de36f1923b" +checksum = "693d596312e88961bc67d7f1f97af8a70227d9f90c31bba5806eec004978d752" dependencies = [ "proc-macro2", "quote", diff --git a/Cargo.toml b/Cargo.toml index 64bf0b9..5cc8254 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -16,6 +16,7 @@ path = "src/main.rs" members = ["commands/info"] [dependencies] +libloading = "0.8.5" phf = { version = "0.11.2", features = ["phf_macros"] } rand = "0.8.5" regex = "1.10.4" diff --git a/commands/info/Cargo.toml b/commands/info/Cargo.toml index 48f5ce3..6fd3ba5 100644 --- a/commands/info/Cargo.toml +++ b/commands/info/Cargo.toml @@ -2,9 +2,9 @@ name = "info" version = "0.1.0" edition = "2021" - -[lib] -crate-type = ["rlib"] +crate-type = ["cdylib"] [dependencies] +tokio = { version = "1.39.2", features = ["full"] } +tracing = "0.1.40" nopalmo = { path = "../../" } \ No newline at end of file diff --git a/commands/info/src/lib.rs b/commands/info/src/lib.rs index e69de29..1def1f0 100644 --- a/commands/info/src/lib.rs +++ b/commands/info/src/lib.rs @@ -0,0 +1,43 @@ +use nopalmo_lib::PluginError; +use std::collections::HashMap; +use std::sync::{Arc, Mutex}; +use tokio::task; + +type AsyncFn = Box task::JoinHandle> + Send + Sync>; + +static FUNCTION_MAP: ::std::sync::OnceLock>>> = + ::std::sync::OnceLock::new(); + +#[allow(improper_ctypes_definitions)] +#[no_mangle] +pub extern "C" fn register_functions(function_map: Arc>>) { + if let Err(_) = FUNCTION_MAP.set(Arc::clone(&function_map)) { + tracing::error!("Could not set function map oncelock in `info` plugin."); + return; + } + + let example_function: AsyncFn = Box::new(|| { + task::spawn(async { + tracing::info!("example_function is running"); + // Simulate some async work + tokio::time::sleep(tokio::time::Duration::from_secs(1)).await; + tracing::info!("example_function completed"); + // Return an error for demonstration + Err(PluginError::FunctionError( + "Something went wrong".to_string(), + )) + }) + }); + + function_map + .lock() + .unwrap() + .insert("example_function".to_string(), example_function); +} + +#[no_mangle] +pub extern "C" fn unregister_functions() { + if let Some(function_map) = FUNCTION_MAP.get() { + function_map.lock().unwrap().remove("example_function"); + } +} diff --git a/src/constants.rs b/src/constants.rs index 45c0f07..11c57b5 100644 --- a/src/constants.rs +++ b/src/constants.rs @@ -1 +1,3 @@ -pub const GLOBAL_PREFIX: char = ';'; \ No newline at end of file +pub const COMMAND_PREFIX: &'static str = ";"; +pub const SHORT_ARGUMENT_PREFIX: &'static str = "-"; +pub const LONG_ARGUMENT_PREFIX: &'static str = "--"; diff --git a/src/lib/arguments.rs b/src/lib/arguments.rs new file mode 100644 index 0000000..ff64b37 --- /dev/null +++ b/src/lib/arguments.rs @@ -0,0 +1,109 @@ +use std::collections::HashMap; + +#[derive(Debug, Clone, PartialEq, Default)] +pub struct ArgumentStorage { + arguments: Vec, + long_key_to_id: HashMap, + short_key_to_id: HashMap, +} + +impl ArgumentStorage { + pub fn new() -> Self { + Self::default() + } + + pub fn add(&mut self, long: Option, short: Option, argument: Argument) { + let mut index = None; + for i in 0..self.arguments.len() { + if self.arguments.get(i).is_none() { + index = Some(i); + } + } + + if let Some(index) = index { + self.arguments[index] = argument; + + if let Some(long) = long { + self.long_key_to_id.insert(long, index); + } + if let Some(short) = short { + self.long_key_to_id.insert(short, index); + } + } + } + + pub fn remove(&mut self, long: Option, short: Option) { + + match (long, short) { + (None, None) => todo!(), + (None, Some(short_key)) => { + let short_index = *self.short_key_to_id.get(&short_key).unwrap(); + self.short_key_to_id.remove(&short_key); + + let mut long_to_remove = None; + + for (long_key, long_index) in &self.long_key_to_id { + // hate cloning here but i couldnt figure out how to avoid it + if short_index == *long_index { + long_to_remove = Some(long_key.clone()); + } + } + + if let Some(long_to_remove) = long_to_remove { + self.long_key_to_id.remove(&long_to_remove); + } + } + (Some(long_key), None) => { + let long_index = *self.long_key_to_id.get(&long_key).unwrap(); + self.long_key_to_id.remove(&long_key); + + let mut short_to_remove = None; + + for (short_key, short_index) in &self.short_key_to_id { + // hate cloning here but i couldnt figure out how to avoid it + if long_index == *short_index { + short_to_remove = Some(short_key.clone()); + } + } + + if let Some(short_to_remove) = short_to_remove { + self.short_key_to_id.remove(&short_to_remove); + } + } + (Some(long_key), Some(short_key)) => { + + + + self.long_key_to_id.remove(&long_key); + self.short_key_to_id.remove(&short_key); + + } + } + } +} + +pub enum ArgumentKind { + Short, + Long, + WildCard, +} + +#[derive(Debug, Clone, PartialEq, PartialOrd)] +pub struct Argument { + pub pretty_name: &'static str, + // we will use usize as an argument id system, the arguments will be available inside the argument cache + // the bool is for if its required or not + pub sub_arguments: Vec<(ArgumentContainer, bool)>, + pub requires_prefix: bool, + + pub long_name: Option<&'static str>, // change to vec? + pub short_name: Option<&'static str>, // change to vec? + // /// use 0 for any position, 1 or more for positionals + // pub position: usize, +} + +#[derive(Debug, Clone, PartialEq, PartialOrd)] +pub enum ArgumentContainer { + Value(String), + Argument(usize) +} \ No newline at end of file diff --git a/src/lib/arguments/mod.rs b/src/lib/arguments/mod.rs deleted file mode 100644 index 3eee3eb..0000000 --- a/src/lib/arguments/mod.rs +++ /dev/null @@ -1,3 +0,0 @@ -pub struct Argument { - -} \ No newline at end of file diff --git a/src/lib/discord_permissions.rs b/src/lib/discord_permissions.rs deleted file mode 100644 index 44d62c2..0000000 --- a/src/lib/discord_permissions.rs +++ /dev/null @@ -1,3 +0,0 @@ -pub struct DiscordPermission { - -} \ No newline at end of file diff --git a/src/lib/lib.rs b/src/lib/lib.rs index 21b3f0c..72c3dd3 100644 --- a/src/lib/lib.rs +++ b/src/lib/lib.rs @@ -1,54 +1,73 @@ +use std::{fmt::Debug, time::Duration}; + pub mod arguments; -pub mod discord_permissions; -use self::arguments::Argument; -use ::serenity::all::{Context, GuildId, Message}; -use ::std::collections::HashMap; -use ::std::time::Duration; -use discord_permissions::DiscordPermission; +use arguments::ArgumentStorage; +use serenity::all::{Context, GuildId, Message}; -#[cfg(feature = "premium_features")] -pub enum PremiumLevel { - Free, - Tier1, - Tier2, - Tier3, - Super, +#[derive(Debug)] +pub enum PluginError { + FunctionError(String), + Other(String), } -pub enum CommandType { - General, - Moderation, - Fun, - Info, - Extra, - Unknown, +impl core::fmt::Display for PluginError { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + match self { + PluginError::FunctionError(msg) => write!(f, "Function error: {}", msg), + PluginError::Other(msg) => write!(f, "Other error: {}", msg), + } + } } -pub struct Command { +impl std::error::Error for PluginError {} + +pub struct Command { pub run_dm_command: Box CommandReturn>, pub run_guild_command: Box CommandReturn>, pub aliases: Vec, pub name: String, - pub command_type: CommandType, + pub command_category: String, pub help: String, pub usage: String, pub timeout: Duration, // TODO make this dynamic? - pub arguments: HashMap, - pub permissions: Vec, + pub arguments: ArgumentStorage, + pub required_caller_discord_permissions: ::serenity::all::Permissions, #[cfg(feature = "nsfw_features")] pub is_nsfw: bool, #[cfg(feature = "premium_features")] - pub premium_kind: PremiumLevel, + pub premium_kind: usize, } -unsafe impl Sync - for Command -{ -} -unsafe impl Send - for Command -{ +unsafe impl Sync for Command {} +unsafe impl Send for Command {} + +impl Debug for Command { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let mut binding = f.debug_struct("Command"); + binding + .field( + "run_dm_command", + &::std::any::type_name_of_val(&self.run_dm_command), + ) + .field( + "run_guild_command", + &::std::any::type_name_of_val(&self.run_guild_command), + ) + .field("aliases", &self.aliases) + .field("name", &self.name) + .field("command_type", &self.command_category) + .field("help", &self.help) + .field("usage", &self.usage) + .field("timeout", &self.timeout) + .field("arguments", &self.arguments) + .field("permissions", &self.required_caller_discord_permissions); + #[cfg(feature = "nsfw_features")] + binding.field("is_nsfw", &self.is_nsfw); + #[cfg(feature = "premium_features")] + binding.field("premium_kind", &self.premium_kind); + binding.finish() + } } diff --git a/src/main.rs b/src/main.rs index 0c35034..73ca4f8 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,25 +1,29 @@ +#![deny(clippy::unwrap_used)] +#![deny(clippy::expect_used)] +#![deny(clippy::pedantic)] + //#![feature(async_fn_traits)] // mod commands; // use nopalmo_lib; mod constants; -mod permissions; +// mod permissions; mod system_messages; mod tasks; -use ::std::collections::hash_map::HashMap; +// use ::std::collections::hash_map::HashMap; -use std::error::Error; -use std::future::Future; -use std::pin::Pin; +// use std::error::Error; +// use std::future::Future; +// use std::pin::Pin; use std::sync::atomic::AtomicBool; -use std::sync::Arc; -use std::time::Duration; +// use std::sync::Arc; +// use std::time::Duration; -use nopalmo_lib::discord_permissions::DiscordPermission; -use nopalmo_lib::Command; -use serenity::all::GuildId; +// use discord_permissions::DiscordPermission; +// use nopalmo_lib::Command; +// use serenity::all::GuildId; // use commands::Command; use serenity::model::channel::Message; use serenity::model::gateway::Ready; @@ -31,6 +35,34 @@ use serenity::prelude::*; // pub type CommandReturn = Pin>>; // pub type CommandReturn = (); + +// pub mod discord_permissions; + +// use self::arguments::Argument; +use ::serenity::all::Context; + +// use discord_permissions::DiscordPermission; + +// #[cfg(feature = "premium_features")] +// pub enum PremiumLevel { +// Free, +// Tier1, +// Tier2, +// Tier3, +// Super, +// } + +// pub enum CommandType { +// General, +// Moderation, +// Fun, +// Info, +// Extra, +// Unknown, +// } + + + struct Handler { // TODO use data field instead? // system_sender: Mutex>, @@ -54,7 +86,7 @@ impl EventHandler for Handler { async fn message(&self, ctx: Context, msg: Message) { let prefix_regex = format!( r"^({}|{}|<@{}>)\s?", - constants::GLOBAL_PREFIX, + constants::COMMAND_PREFIX, "NPO_PFX", ctx.cache.current_user().id ); diff --git a/src/permissions.rs b/src/permissions.rs deleted file mode 100644 index 9a53419..0000000 --- a/src/permissions.rs +++ /dev/null @@ -1,3 +0,0 @@ -pub struct Permission { - -} \ No newline at end of file diff --git a/src/tasks.rs b/src/tasks.rs index 830ef86..166745c 100644 --- a/src/tasks.rs +++ b/src/tasks.rs @@ -18,7 +18,7 @@ pub async fn status_timer( cache.user_count(), cache.guild_count() )), - ActivityData::watching(format!("for {}help", constants::GLOBAL_PREFIX)), + ActivityData::watching(format!("for {}help", constants::COMMAND_PREFIX)), ActivityData::listening("Infected Mushroom"), ];