working system

This commit is contained in:
deepCurse 2024-09-09 03:36:59 -03:00
parent 1afa975c6a
commit 2a5c13306e
Signed by: u1
GPG key ID: AD770D25A908AFF4
13 changed files with 597 additions and 484 deletions

274
Cargo.lock generated
View file

@ -88,6 +88,12 @@ version = "0.21.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567"
[[package]]
name = "base64"
version = "0.22.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6"
[[package]]
name = "bitflags"
version = "1.3.2"
@ -109,6 +115,16 @@ dependencies = [
"generic-array",
]
[[package]]
name = "bstr"
version = "1.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "40723b8fb387abc38f4f4a37c09073622e41dd12327033091ef8950659e6dc0c"
dependencies = [
"memchr",
"serde",
]
[[package]]
name = "bumpalo"
version = "3.16.0"
@ -191,9 +207,9 @@ dependencies = [
[[package]]
name = "command_attr"
version = "0.5.1"
version = "0.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "32f08c85a02e066b7b4f7dcb60eee6ae0793ef7d6452a3547d1f19665df070a9"
checksum = "88da8d7e9fe6f30d8e3fcf72d0f84102b49de70fece952633e8439e89bdc7631"
dependencies = [
"proc-macro2",
"quote",
@ -640,15 +656,6 @@ dependencies = [
"hashbrown",
]
[[package]]
name = "info"
version = "0.1.0"
dependencies = [
"nopalmo",
"tokio",
"tracing",
]
[[package]]
name = "ipnet"
version = "2.9.0"
@ -670,6 +677,12 @@ dependencies = [
"wasm-bindgen",
]
[[package]]
name = "lazy_static"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe"
[[package]]
name = "levenshtein"
version = "1.0.5"
@ -714,6 +727,15 @@ version = "0.4.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "90ed8c1e510134f979dbc4f070f87d4313098b704861a105fe34231c70a3901c"
[[package]]
name = "luau0-src"
version = "0.10.2+luau635"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e8bd6bc70c84fdd4e89b71b528f7ab7db7b97adf759faa8dbe15c5011468e290"
dependencies = [
"cc",
]
[[package]]
name = "memchr"
version = "2.7.2"
@ -772,17 +794,55 @@ dependencies = [
"windows-sys 0.52.0",
]
[[package]]
name = "mlua"
version = "0.9.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d111deb18a9c9bd33e1541309f4742523bfab01d276bfa9a27519f6de9c11dc7"
dependencies = [
"bstr",
"futures-util",
"libloading",
"mlua-sys",
"num-traits",
"once_cell",
"rustc-hash",
]
[[package]]
name = "mlua-sys"
version = "0.6.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3ab7a5b4756b8177a2dfa8e0bbcde63bd4000afbc4ab20cbb68d114a25470f29"
dependencies = [
"cc",
"cfg-if",
"luau0-src",
"pkg-config",
]
[[package]]
name = "nopalmo"
version = "0.1.0"
dependencies = [
"libloading",
"phf",
"mlua",
"rand",
"regex",
"serenity",
"sqlite",
"tokio",
"tracing",
"tracing-subscriber",
]
[[package]]
name = "nu-ansi-term"
version = "0.46.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "77a8165726e8236064dbb45459242600304b42a5ea24ee2948e18e023bf7ba84"
dependencies = [
"overload",
"winapi",
]
[[package]]
@ -815,6 +875,12 @@ version = "1.19.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92"
[[package]]
name = "overload"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39"
[[package]]
name = "parking_lot"
version = "0.12.2"
@ -844,48 +910,6 @@ version = "2.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e"
[[package]]
name = "phf"
version = "0.11.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ade2d8b8f33c7333b51bcf0428d37e217e9f32192ae4772156f65063b8ce03dc"
dependencies = [
"phf_macros",
"phf_shared",
]
[[package]]
name = "phf_generator"
version = "0.11.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "48e4cc64c2ad9ebe670cb8fd69dd50ae301650392e81c05f9bfcb2d5bdbc24b0"
dependencies = [
"phf_shared",
"rand",
]
[[package]]
name = "phf_macros"
version = "0.11.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3444646e286606587e49f3bcf1679b8cef1dc2c5ecc29ddacaffc305180d464b"
dependencies = [
"phf_generator",
"phf_shared",
"proc-macro2",
"quote",
"syn 2.0.65",
]
[[package]]
name = "phf_shared"
version = "0.11.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "90fcb95eef784c2ac79119d1dd819e162b5da872ce6f3c3abe1e8ca1c082f72b"
dependencies = [
"siphasher",
]
[[package]]
name = "pin-project-lite"
version = "0.2.14"
@ -898,6 +922,12 @@ version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
[[package]]
name = "pkg-config"
version = "0.3.30"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d231b230927b5e4ad203db57bbcbee2802f6bce620b1e4a9024a07d94e2907ec"
[[package]]
name = "powerfmt"
version = "0.2.0"
@ -1013,7 +1043,7 @@ version = "0.11.27"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dd67538700a17451e7cba03ac727fb961abb7607553461627b97de0b89cf4a62"
dependencies = [
"base64",
"base64 0.21.7",
"bytes",
"encoding_rs",
"futures-core",
@ -1072,6 +1102,12 @@ version = "0.1.24"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f"
[[package]]
name = "rustc-hash"
version = "2.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "583034fd73374156e66797ed8e5b0d5690409c9226b22d87cb7f19821c05d152"
[[package]]
name = "rustix"
version = "0.38.34"
@ -1117,7 +1153,7 @@ version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1c74cae0a4cf6ccbbf5f359f08efdf8ee7e1dc532573bf0db71968cb56b1448c"
dependencies = [
"base64",
"base64 0.21.7",
]
[[package]]
@ -1206,6 +1242,15 @@ dependencies = [
"serde_derive",
]
[[package]]
name = "serde_cow"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e7bbbec7196bfde255ab54b65e34087c0849629280028238e67ee25d6a4b7da"
dependencies = [
"serde",
]
[[package]]
name = "serde_derive"
version = "1.0.202"
@ -1242,13 +1287,13 @@ dependencies = [
[[package]]
name = "serenity"
version = "0.12.1"
version = "0.12.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c64da29158bb55d70677cacd4f4f8eab1acef005fb830d9c3bea411b090e96a9"
checksum = "880a04106592d0a8f5bdacb1d935889bfbccb4a14f7074984d9cd857235d34ac"
dependencies = [
"arrayvec",
"async-trait",
"base64",
"base64 0.22.1",
"bitflags 2.5.0",
"bytes",
"chrono",
@ -1264,6 +1309,7 @@ dependencies = [
"reqwest",
"secrecy",
"serde",
"serde_cow",
"serde_json",
"static_assertions",
"time",
@ -1287,6 +1333,15 @@ dependencies = [
"digest",
]
[[package]]
name = "sharded-slab"
version = "0.1.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6"
dependencies = [
"lazy_static",
]
[[package]]
name = "signal-hook-registry"
version = "1.4.2"
@ -1296,12 +1351,6 @@ dependencies = [
"libc",
]
[[package]]
name = "siphasher"
version = "0.3.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "38b58827f4464d87d377d175e90bf58eb00fd8716ff0a62f80356b5e61555d0d"
[[package]]
name = "skeptic"
version = "0.13.7"
@ -1348,6 +1397,34 @@ version = "0.9.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67"
[[package]]
name = "sqlite"
version = "0.36.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5dfe6fb16f2bee6452feeb4d12bfa404fbcd3cfc121b2950e501d1ae9cae718e"
dependencies = [
"sqlite3-sys",
]
[[package]]
name = "sqlite3-src"
version = "0.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "174d4a6df77c27db281fb23de1a6d968f3aaaa4807c2a1afa8056b971f947b4a"
dependencies = [
"cc",
"pkg-config",
]
[[package]]
name = "sqlite3-sys"
version = "0.17.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3901ada7090c3c3584dc92ec7ef1b7091868d13bfe6d7de9f0bcaffee7d0ade5"
dependencies = [
"sqlite3-src",
]
[[package]]
name = "static_assertions"
version = "1.1.0"
@ -1447,6 +1524,16 @@ dependencies = [
"syn 2.0.65",
]
[[package]]
name = "thread_local"
version = "1.1.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8b9ef9bad013ada3808854ceac7b46812a6465ba368859a37e2100283d2d719c"
dependencies = [
"cfg-if",
"once_cell",
]
[[package]]
name = "time"
version = "0.3.36"
@ -1608,6 +1695,33 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54"
dependencies = [
"once_cell",
"valuable",
]
[[package]]
name = "tracing-log"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3"
dependencies = [
"log",
"once_cell",
"tracing-core",
]
[[package]]
name = "tracing-subscriber"
version = "0.3.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ad0f048c97dbd9faa9b7df56362b8ebcaa52adb06b498c050d2f4e32f90a7a8b"
dependencies = [
"chrono",
"nu-ansi-term",
"sharded-slab",
"smallvec",
"thread_local",
"tracing-core",
"tracing-log",
]
[[package]]
@ -1744,6 +1858,12 @@ version = "0.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f4bf03e0ca70d626ecc4ba6b0763b934b6f2976e8c744088bb3c1d646fbb1ad0"
[[package]]
name = "valuable"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d"
[[package]]
name = "version_check"
version = "0.9.4"
@ -1879,6 +1999,22 @@ dependencies = [
"rustls-pki-types",
]
[[package]]
name = "winapi"
version = "0.3.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
dependencies = [
"winapi-i686-pc-windows-gnu",
"winapi-x86_64-pc-windows-gnu",
]
[[package]]
name = "winapi-i686-pc-windows-gnu"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
[[package]]
name = "winapi-util"
version = "0.1.8"
@ -1888,6 +2024,12 @@ dependencies = [
"windows-sys 0.52.0",
]
[[package]]
name = "winapi-x86_64-pc-windows-gnu"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
[[package]]
name = "windows-core"
version = "0.52.0"

View file

@ -3,28 +3,20 @@ name = "nopalmo"
version = "0.1.0"
edition = "2021"
[lib]
name = "nopalmo_lib"
path = "src/lib/lib.rs"
[[bin]]
name = "nopalmo"
path = "src/main.rs"
[workspace]
members = ["commands/info"]
[dependencies]
libloading = "0.8.5"
phf = { version = "0.11.2", features = ["phf_macros"] }
rand = "0.8.5"
regex = "1.10.4"
# rs-cord = { git = "https://github.com/jay3332/rs-cord" }
serenity = "0.12.1"
tokio = { version = "1.37.0", features = ["full"] }
tracing = "0.1.40"
tracing-subscriber = { version = "0.3.18", features = ["chrono"] }
rand = "0.8.5"
regex = "1.10.4"
sqlite = { version = "0.36.1", features = ["bundled"] }
mlua = { version = "0.9.9", features = ["async", "luau"] }
serenity = "0.12.1"
[features]
# slated for removal, originally this engine was going to be a multipurpose and publicly available bot, however plans change and im limiting the scope
nsfw_features = []
premium_features = []

View file

@ -1,10 +0,0 @@
[package]
name = "info"
version = "0.1.0"
edition = "2021"
crate-type = ["cdylib"]
[dependencies]
tokio = { version = "1.39.2", features = ["full"] }
tracing = "0.1.40"
nopalmo = { path = "../../" }

View file

@ -1,43 +0,0 @@
use nopalmo_lib::PluginError;
use std::collections::HashMap;
use std::sync::{Arc, Mutex};
use tokio::task;
type AsyncFn = Box<dyn Fn() -> task::JoinHandle<Result<(), PluginError>> + Send + Sync>;
static FUNCTION_MAP: ::std::sync::OnceLock<Arc<Mutex<HashMap<String, AsyncFn>>>> =
::std::sync::OnceLock::new();
#[allow(improper_ctypes_definitions)]
#[no_mangle]
pub extern "C" fn register_functions(function_map: Arc<Mutex<HashMap<String, AsyncFn>>>) {
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");
}
}

75
src/arguments.rs Normal file
View file

@ -0,0 +1,75 @@
use std::{collections::HashMap, sync::Arc};
#[derive(Debug, Clone, PartialEq, Default)]
pub struct ArgumentStorage {
// arguments: Vec<Argument>,
long_key_to_id: HashMap<String, Arc<Argument>>,
short_key_to_id: HashMap<String, Arc<Argument>>,
}
impl ArgumentStorage {
pub fn new() -> Self {
Self::default()
}
pub fn add(&mut self, argument: Argument) {
let argument = Arc::new(argument);
if let Some(long) = argument.long_name {
self.long_key_to_id
.insert(long.to_string(), argument.clone());
}
if let Some(short) = argument.short_name {
self.long_key_to_id.insert(short.to_string(), argument);
}
}
pub fn remove(&mut self, long: Option<String>, short: Option<String>) {
match (long, short) {
(None, None) => todo!(), // what
(None, Some(short_key)) => {
let argument = self.short_key_to_id.remove(&short_key);
if let Some(argument) = argument {
if let Some(long_name) = argument.long_name {
self.long_key_to_id.remove(long_name);
}
} // TODO case where name is invalid?
}
(Some(long_key), None) => {
let argument = self.long_key_to_id.remove(&long_key);
if let Some(argument) = argument {
if let Some(short_name) = argument.short_name {
self.short_key_to_id.remove(short_name);
}
} // TODO case where name is invalid?
}
(Some(long_key), Some(short_key)) => {
self.long_key_to_id.remove(&long_key);
self.short_key_to_id.remove(&short_key);
}
}
}
}
#[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 wildcard_name: Option<&'static str>,
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),
}

139
src/commands.rs Normal file
View file

@ -0,0 +1,139 @@
use std::{fmt::Debug, time::Duration};
use serenity::all::{Context, GuildId, Message, Permissions};
use crate::arguments::{Argument, ArgumentStorage};
// pub type DmCommand = fn(Context, Message);
// pub type GuildCommand = fn(Context, Message, GuildId);
pub enum CommandFnKind {
Lua(()),
Rust(fn(Context, Message, Option<GuildId>)),
}
pub struct Command {
pub run_dm_command: Option<CommandFnKind>,
pub run_guild_command: Option<CommandFnKind>,
pub aliases: Vec<String>,
pub pretty_name: Option<String>,
pub name: String,
pub command_category: Option<String>,
pub help: Option<String>,
// pub usage: String, // will be calculated from arguments automatically
pub timeout: Option<Duration>, // TODO make this dynamic?
pub hidden: bool,
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: usize,
}
impl Command {
pub fn new(name: String) -> Self {
Self {
run_dm_command: None,
run_guild_command: None,
aliases: vec![],
pretty_name: None,
name,
hidden: false,
command_category: None,
help: None,
timeout: None,
arguments: ArgumentStorage::new(),
required_caller_discord_permissions: Permissions::empty(),
#[cfg(feature = "nsfw_features")]
is_nsfw: false,
#[cfg(feature = "premium_features")]
premium_kind: 0,
}
}
pub fn dm_command(mut self, dm_command: CommandFnKind) -> Self {
self.run_dm_command = Some(dm_command);
self
}
pub fn guild_command(mut self, guild_command: CommandFnKind) -> Self {
self.run_guild_command = Some(guild_command);
self
}
pub fn pretty_name(mut self, pretty_name: String) -> Self {
self.pretty_name = Some(pretty_name);
self
}
// pub fn name(mut self, name: String) -> Self {
// self.name = name;
// self
// }
pub fn alias(mut self, alias: String) -> Self {
self.aliases.push(alias);
self
}
pub fn aliases(mut self, aliases: &mut Vec<String>) -> Self {
self.aliases.append(aliases);
self
}
pub fn category(mut self, category: String) -> Self {
self.command_category = Some(category);
self
}
pub fn help(mut self, help: String) -> Self {
self.help = Some(help);
self
}
pub fn timeout(mut self, timeout: Duration) -> Self {
self.timeout = Some(timeout);
self
}
pub fn argument(mut self, argument: Argument) -> Self {
self.arguments.add(argument);
self
}
pub fn hidden(mut self, hidden: bool) -> Self {
self.hidden = hidden;
self
}
}
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("pretty_name", &self.pretty_name)
.field("command_type", &self.command_category)
.field("help", &self.help)
.field("hidden", &self.hidden)
// .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()
}
}
// impl Default for Command {
// fn default() -> Self {
// }
// }

View file

@ -1,109 +0,0 @@
use std::collections::HashMap;
#[derive(Debug, Clone, PartialEq, Default)]
pub struct ArgumentStorage {
arguments: Vec<Argument>,
long_key_to_id: HashMap<String, usize>,
short_key_to_id: HashMap<String, usize>,
}
impl ArgumentStorage {
pub fn new() -> Self {
Self::default()
}
pub fn add(&mut self, long: Option<String>, short: Option<String>, 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<String>, short: Option<String>) {
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)
}

View file

@ -1,73 +0,0 @@
use std::{fmt::Debug, time::Duration};
pub mod arguments;
use arguments::ArgumentStorage;
use serenity::all::{Context, GuildId, Message};
#[derive(Debug)]
pub enum PluginError {
FunctionError(String),
Other(String),
}
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),
}
}
}
impl std::error::Error for PluginError {}
pub struct Command<DMC, GMC, CommandReturn> {
pub run_dm_command: Box<dyn Fn(&DMC, Context, Message) -> CommandReturn>,
pub run_guild_command: Box<dyn Fn(&GMC, Context, Message, GuildId) -> CommandReturn>,
pub aliases: Vec<String>,
pub name: String,
pub command_category: String,
pub help: String,
pub usage: String,
pub timeout: Duration, // TODO make this dynamic?
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: usize,
}
unsafe impl<DMC, GMC, CommandReturn> Sync for Command<DMC, GMC, CommandReturn> {}
unsafe impl<DMC, GMC, CommandReturn> Send for Command<DMC, GMC, CommandReturn> {}
impl<DMC, GMC, CommandReturn> Debug for Command<DMC, GMC, CommandReturn> {
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()
}
}

View file

@ -2,88 +2,51 @@
#![deny(clippy::expect_used)]
#![deny(clippy::pedantic)]
//#![feature(async_fn_traits)]
// mod commands;
// use nopalmo_lib;
mod arguments;
mod commands;
mod constants;
// mod permissions;
mod system_messages;
mod tasks;
// use ::std::collections::hash_map::HashMap;
use std::collections::HashMap;
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::{Arc, Condvar, OnceLock};
// 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 discord_permissions::DiscordPermission;
// use nopalmo_lib::Command;
// use serenity::all::GuildId;
// use commands::Command;
use commands::Command;
use metadata::LevelFilter;
use serenity::all::{ActivityData, Context, GuildId, ShardManager};
use serenity::all::{EventHandler, GatewayIntents};
use serenity::model::channel::Message;
use serenity::model::gateway::Ready;
use serenity::prelude::*;
// use tokio::sync::mpsc;
// type DMCommandArguments = Handler;
// type GCommandArguments = Handler;
// pub type CommandReturn = Pin<Box<dyn Future<Output = ()>>>;
// 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,
// }
use serenity::Client;
use tracing::*;
// pub const CAT_DEV: &'static str = "Dev";
pub const CAT_FUN: &'static str = "Fun";
pub const CAT_MOD: &'static str = "Moderation";
pub const CAT_GEN: &'static str = "General";
pub const CAT_INF: &'static str = "Info";
pub const CAT_HID: &'static str = "Hidden";
static SHARD_MANAGER: OnceLock<Arc<ShardManager>> = OnceLock::new();
// static SHARDS_READY: AtomicBool = AtomicBool::new(false);
static DO_SHUTDOWN: (AtomicBool, Condvar) = (AtomicBool::new(false), Condvar::new()); // atombool and condvar combo to ensure maximum coverage when the bot needs to power off
struct Handler {
// TODO use data field instead?
// system_sender: Mutex<mpsc::Sender<system_messages::SystemMessage>>,
// system_receiver: Mutex<mpsc::Receiver<system_messages::SystemMessage>>,
do_shutdown: AtomicBool,
// task_count: AtomicUsize,
// command_map: ::std::collections::hash_map::HashMap<
// String,
// Arc<Command<DMCommandArguments, GCommandArguments, CommandReturn>>,
// >,
commands: HashMap<String, Command>,
}
unsafe impl Sync for Handler {}
unsafe impl Send for Handler {}
// unsafe impl Sync for CommandReturn {}
// unsafe impl Send for CommandReturn {}
#[serenity::async_trait]
impl EventHandler for Handler {
async fn message(&self, ctx: Context, msg: Message) {
// We do not reply to bots https://en.wikipedia.org/wiki/Email_storm
if msg.author.bot {
return;
}
let prefix_regex = format!(
r"^({}|{}|<@{}>)\s?",
constants::COMMAND_PREFIX,
@ -100,7 +63,7 @@ impl EventHandler for Handler {
None => return, // silently exit because not every message is meant for the bot
};
if self.do_shutdown.load(std::sync::atomic::Ordering::SeqCst) {
if DO_SHUTDOWN.0.load(Ordering::SeqCst) {
let _ = msg
.channel_id
.say(
@ -113,34 +76,70 @@ impl EventHandler for Handler {
let target_cmd_name = regex::Regex::new(prefix_regex.as_str())
.unwrap()
.replace(result.as_str(), "");
dbg!(&target_cmd_name);
.replace(result.as_str(), "")
.to_string();
msg.reply(&ctx.http, target_cmd_name.to_string())
.await
.unwrap();
//if let Some(command) = self.command_map.get(target_cmd_name) {
// if let Some(guild_id) = msg.guild_id {
// (command.run_guild_command)(&self, ctx, msg, guild_id);
// } else {
// (command.run_dm_command)(&self, ctx, msg);
// }
//}
if let Some(command) = self.commands.get(&target_cmd_name) {
if let Some(guild_id) = msg.guild_id {
if let Some(run_guild_command) = &command.run_guild_command {
match run_guild_command {
commands::CommandFnKind::Lua(_) => todo!(),
commands::CommandFnKind::Rust(cmd) => (cmd)(ctx, msg, Some(guild_id)),
}
}
} else {
if let Some(run_dm_command) = &command.run_dm_command {
match run_dm_command {
commands::CommandFnKind::Lua(_) => todo!(),
commands::CommandFnKind::Rust(cmd) => (cmd)(ctx, msg, None),
}
}
}
}
}
async fn ready(&self, _: Context, ready: Ready) {
println!("Shart `{}` is connected!", ready.shard.unwrap().id);
/// Runs once for every shard once its ready
async fn ready(&self, ctx: Context, ready: Ready) {
info!("Shart `{}` is connected!", ready.shard.unwrap().id);
ctx.set_activity(Some(ActivityData::custom("Initializing.")))
}
/// Runs once when all shards are ready
async fn shards_ready(&self, ctx: Context, total_shards: u32) {
tasks::start_tasks(ctx).await;
info!("{total_shards} shards ready.");
}
// Runs once for every shard when its cache is ready
async fn cache_ready(&self, _ctx: Context, _guild_id_list: Vec<GuildId>) {
info!("Cache ready.");
}
}
#[tokio::main]
async fn main() {
tracing::subscriber::set_global_default(
tracing_subscriber::fmt()
.with_max_level(LevelFilter::INFO)
.finish(),
)
.unwrap();
// TODO load this at runtime so the key will not be stored in the binary?
#[cfg(not(debug_assertions))]
let token = include_str!("bot_token.prod");
let token = {
info!("Initializing bot with production token.");
include_str!("bot_token.prod")
};
#[cfg(debug_assertions)]
let token = include_str!("bot_token.dev");
let token = {
info!("Initializing bot with development token.");
include_str!("bot_token.dev")
};
let intents = GatewayIntents::DIRECT_MESSAGES
| GatewayIntents::DIRECT_MESSAGE_REACTIONS
@ -152,89 +151,64 @@ async fn main() {
| GatewayIntents::GUILD_MESSAGES
| GatewayIntents::MESSAGE_CONTENT;
// let (system_sender, system_receiver) = mpsc::channel(256);
// let system_sender = Mutex::new(system_sender);
// let system_receiver = Mutex::new(system_receiver);
let mut commands = HashMap::default();
// let mut command_map = HashMap::new();
// fn ping_dm_run(handler: &Handler, ctx: Context, msg: Message) -> () {}
// fn ping_guild_run(handler: &Handler, ctx: Context, msg: Message, guild_id: GuildId) -> () {
// // MessageChannel channel = blob.getChannel();
// channel
// .sendMessage("Pong!\n" + ctx. + "ms\n")
// .queue();
// // if (argumentMap.get("all") != null) {
// // channel.sendMessage("Gathering data. . .").queue(msg -> {
// // long timeToProcess = System.currentTimeMillis();
// // long jdaPing = blob.getJDA().getGatewayPing();
// // long googlePing = -1;
// // try {
// // googlePing = UptimePing.sendPing("www.google.com");
// // } catch (Exception e) {
// // e.printStackTrace();
// // }
// // long discordPing = -1;
// // try {
// // discordPing = UptimePing.sendPing("www.discord.com");
// // } catch (Exception e) {
// // e.printStackTrace();
// // }
// // String out = "Ping:\n"
// // + (googlePing > 0 ? "Google: " + googlePing + "ms\n" : "Could not connect to www.google.com\n")
// // + (discordPing > 0 ? "Discord: " + discordPing + "ms\n"
// // : "Could not connect to www.discord.com\n")
// // + "JDA-Discord heartbeat: " + jdaPing + "ms";
// // msg.editMessage(out + "\nTime to process: " + (System.currentTimeMillis() - timeToProcess) + "ms")
// // .queue();
// // });
// // }
// }
// command_map.insert(
// "ping".to_owned(),
// Arc::new(Command {
// aliases: vec!["ping".to_owned()],
// arguments: HashMap::new(),
// command_type: nopalmo_lib::CommandType::General,
// help: "Some help info".to_owned(),
// name: "Ping".to_owned(),
// usage: "ping".to_owned(),
// permissions: vec![],
// timeout: Duration::from_secs(0),
// run_dm_command: Box::new(ping_dm_run),
// run_guild_command: Box::new(ping_guild_run),
// }),
// );
stock_commands::insert_all(&mut commands);
let mut client = match Client::builder(&token, intents)
.event_handler(Handler {
// system_receiver,
// system_sender,
do_shutdown: AtomicBool::new(false),
// task_count: AtomicUsize::new(0),
// command_map,
})
.event_handler(Handler { commands })
.await
{
Ok(client) => client,
Err(err) => panic!("Error starting client connection: `{err}`"),
};
let status_timer = tokio::spawn(tasks::status_timer(
client.shard_manager.runners.clone(),
client.cache.clone(),
));
SHARD_MANAGER.set(client.shard_manager.clone()).unwrap();
if let Err(why) = client.start_shards(2).await {
println!("Client error: {why:?}");
error!("Client error: {why:?}");
}
status_timer.abort();
warn!("EXITING");
}
mod stock_commands {
use std::{collections::HashMap, sync::atomic::Ordering};
use serenity::all::{Context, GuildId, Message};
use crate::{
commands::{Command, CommandFnKind},
CAT_HID, DO_SHUTDOWN, SHARD_MANAGER,
};
pub fn insert_all(commands: &mut HashMap<String, Command>) {
commands.insert(
"stop".to_owned(),
Command::new("stop".to_owned())
.alias("svs".to_owned())
.category(CAT_HID.to_owned())
.hidden(true)
.dm_command(CommandFnKind::Rust(stop_command))
.guild_command(CommandFnKind::Rust(stop_command))
.pretty_name("Stop the bot".to_owned())
.help("Stops the bot. Does nothing unless you are a developer.".to_owned()),
);
}
fn stop_command(_: Context, msg: Message, _: Option<GuildId>) {
// hardcode my id for now
if msg.author.id != 380045419381784576 {
return;
}
DO_SHUTDOWN.0.store(true, Ordering::SeqCst);
DO_SHUTDOWN.1.notify_all();
let handle = tokio::runtime::Handle::current();
let _eg = handle.enter();
handle.spawn(async {
SHARD_MANAGER.get().unwrap().shutdown_all().await;
});
}
}

View file

@ -1,4 +0,0 @@
pub enum SystemMessage {
}

View file

@ -1,34 +1,64 @@
use crate::constants;
use std::{sync::Arc, time::Duration};
use std::{
sync::{Arc, LazyLock},
time::Duration,
};
use ::std::collections::hash_map::HashMap;
use rand::{distributions::uniform::SampleRange, rngs::OsRng};
use serenity::all::{ActivityData, Cache, ShardId, ShardRunnerInfo};
use tokio::sync::Mutex;
use serenity::all::{ActivityData, Cache, Context, ShardId, ShardRunnerInfo};
use tokio::{sync::Mutex, task::JoinHandle};
use tracing::info;
static TASK_HANDLES: LazyLock<Arc<Mutex<TaskContainer>>> =
LazyLock::new(|| Arc::new(Mutex::new(TaskContainer::default())));
#[derive(Debug, Default)]
pub struct TaskContainer {
pub misc: Vec<JoinHandle<()>>,
}
pub async fn start_tasks(ctx: Context) {
info!("Starting MISC tasks.");
info!("Starting activity switcher task.");
TASK_HANDLES
.lock()
.await
.misc
.push(tokio::spawn(status_timer(
crate::SHARD_MANAGER.get().unwrap().runners.clone(),
ctx.cache,
)));
info!("MISC tasks started.");
}
pub async fn status_timer(
shard_runners: Arc<Mutex<HashMap<ShardId, ShardRunnerInfo>>>,
cache: Arc<Cache>,
) {
let mut rand = OsRng::default();
let activity_list = [
let activities = [
ActivityData::watching("my developer eat a watermelon whole."),
ActivityData::watching(format!(
"{} users in {} guilds.",
cache.user_count(),
cache.guild_count()
cache.user_count() as u64 + cache.unknown_members(),
cache.guild_count() + cache.unavailable_guilds().len()
)),
ActivityData::watching(format!("for {}help", constants::COMMAND_PREFIX)),
ActivityData::listening("Infected Mushroom"),
];
let mut interval = tokio::time::interval(Duration::from_secs(20 * 60));
loop {
let activity = SampleRange::sample_single(0..activities.len() - 1, &mut rand);
for (_shard_id, shard_info) in shard_runners.lock().await.iter() {
shard_info.runner_tx.set_activity(Some(
activity_list[SampleRange::sample_single(0..activity_list.len() - 1, &mut rand)]
.clone(),
));
shard_info
.runner_tx
.set_activity(Some(activities[activity].clone()));
}
tokio::time::sleep(Duration::from_secs(20 * 60)).await;
interval.tick().await;
}
}

BIN
testdb Normal file

Binary file not shown.