Compare commits

...

2 commits

Author SHA1 Message Date
0f40bc98c3
lua testing 2024-09-09 07:40:00 -03:00
2a5c13306e
working system 2024-09-09 03:36:59 -03:00
18 changed files with 879 additions and 549 deletions

285
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,56 @@ 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",
"num_cpus",
"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]]
@ -800,6 +861,16 @@ 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"
@ -815,6 +886,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 +921,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 +933,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 +1054,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 +1113,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 +1164,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 +1253,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 +1298,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 +1320,7 @@ dependencies = [
"reqwest",
"secrecy",
"serde",
"serde_cow",
"serde_json",
"static_assertions",
"time",
@ -1287,6 +1344,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 +1362,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 +1408,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 +1535,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 +1706,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 +1869,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 +2010,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 +2035,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,21 @@ 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"] }
mlua = { version = "0.9.9", features = ["async", "luau"] }
serenity = "0.12.1"
num_cpus = "1.16.0"
rand = "0.8.5"
regex = "1.10.4"
sqlite = { version = "0.36.1", features = ["bundled"] }
[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");
}
}

18
lua/help.lua Normal file
View file

@ -0,0 +1,18 @@
COMMAND["help"] = {
aliases = { "h" }, -- other strings that you can run the command from
pretty_name = "Help", -- the name of the command as it shows up in menues and other commands that use its pretty name
name = "help", -- the main required string used to call the command
command_category = commandCategory.info, -- this can be any string, but the bot provides a few categories already
help = "Shows you helpful information for the bot. Seems you already know how to use it. :loafplink:",
timeout = nil, -- time in milliseconds that the user must wait before running the command again
hidden = false, -- whether or not other commands should acknowledge its existance, for example we dont want dev commands showing up in the help info for regular users, regardless if they can use them or not
permissions = nil, -- which discord permissions they need to run the command
FUNC = function(context, message, guildid)
for t in COMMAND do
print("Help info for command " + t)
end
end
}
COMMAND["help"].FUNC()

40
lua/loader.lua Normal file
View file

@ -0,0 +1,40 @@
commands.help = {
aliases = { "h" }, -- other strings that you can run the command from
pretty_name = "Help", -- the name of the command as it shows up in menues and other commands that use its pretty name
name = "help", -- the main required string used to call the command
command_category = commandCategory.info, -- this can be any string, but the bot provides a few categories already
help = "Shows you helpful information for the bot. Seems you already know how to use it. :loafplink:",
timeout = nil, -- time in milliseconds that the user must wait before running the command again
hidden = false, -- whether or not other commands should acknowledge its existance, for example we dont want dev commands showing up in the help info for regular users, regardless if they can use them or not
permissions = nil, -- which discord permissions they need to run the command
func = function(context, message, guildid)
for k,v in pairs(commands) do
info("Help info for command", k)
end
end
}
commands["help"].func()
-- exit(0)
-- local seen={}
-- local function dump(t,i)
-- seen[t]=true
-- local s={}
-- local n=0
-- for k, v in pairs(t) do
-- n=n+1
-- s[n]=tostring(k)
-- end
-- table.sort(s)
-- for k,v in ipairs(s) do
-- print(i .. v)
-- v=t[v]
-- if type(v)=="table" and not seen[v] then
-- dump(v,i.."\t")
-- end
-- end
-- end
-- dump(_G,"")

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),
}

99
src/bot.rs Normal file
View file

@ -0,0 +1,99 @@
use std::collections::HashMap;
use std::sync::atomic::Ordering;
use crate::commands::{self, Command};
use crate::{constants, tasks, DO_SHUTDOWN};
use serenity::all::EventHandler;
use serenity::all::{ActivityData, Context, GuildId};
use serenity::model::channel::Message;
use serenity::model::gateway::Ready;
use tracing::*;
pub struct BotHandler {
// TODO use data field instead?
pub commands: HashMap<String, Command>,
}
unsafe impl Sync for BotHandler {}
unsafe impl Send for BotHandler {}
#[serenity::async_trait]
impl EventHandler for BotHandler {
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,
"NPO_PFX",
ctx.cache.current_user().id
);
let cmd_regex = format!(r"{}[A-Za-z0-9_\-]+", prefix_regex);
let result = match regex::Regex::new(cmd_regex.as_str())
.unwrap()
.find(&msg.content)
{
Some(result) => result,
None => return, // silently exit because not every message is meant for the bot
};
if DO_SHUTDOWN.0.load(Ordering::SeqCst) {
let _ = msg
.channel_id
.say(
&ctx.http,
"Sorry! Your request was cancelled because the bot is shutting down.",
)
.await;
return;
}
let target_cmd_name = regex::Regex::new(prefix_regex.as_str())
.unwrap()
.replace(result.as_str(), "")
.to_string();
msg.reply(&ctx.http, target_cmd_name.to_string())
.await
.unwrap();
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),
}
}
}
}
}
/// 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.");
}
}

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,3 +1,5 @@
pub const VERSION: u16 = 0;
pub const COMMAND_PREFIX: &'static str = ";";
pub const SHORT_ARGUMENT_PREFIX: &'static str = "-";
pub const LONG_ARGUMENT_PREFIX: &'static str = "--";
// pub const SHORT_ARGUMENT_PREFIX: &'static str = "-";
// pub const LONG_ARGUMENT_PREFIX: &'static str = "--";

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()
}
}

99
src/lua.rs Normal file
View file

@ -0,0 +1,99 @@
use mlua::{Lua, LuaOptions, StdLib, Table, Variadic};
use tracing::{debug, error, info, trace, warn};
use crate::{constants::VERSION, CAT_FUN, CAT_GEN, CAT_HID, CAT_INF, CAT_MOD};
pub fn initialize() -> Lua {
// let mut runtimes = vec![];
// for i in 0..1 {
// coroutines could be useful, but too complicated to throw onto the others immediately
// DO NOT add io or os, both contain functions that can easily mess with the host system
// investigate package and debug
// may need debug to set limitations to the runtime such as execution time and iteration count
let lua = Lua::new_with(
StdLib::BIT | StdLib::BUFFER | StdLib::MATH | StdLib::STRING | StdLib::TABLE | StdLib::UTF8,
LuaOptions::new()
.catch_rust_panics(true)
.thread_pool_size(num_cpus::get()),
)
.expect("Lua could not initialize properly");
// set max to 2mb
lua.set_memory_limit(2 * 1024 * 1024).unwrap();
let globals = lua.globals();
globals.set("NP_VERSION", VERSION).unwrap();
clean_global(
#[cfg(not(debug_assertions))]
&lua,
&globals,
);
prepare_global(&lua, &globals);
set_logging(&lua, &globals);
lua.load(include_str!("../lua/loader.lua"))
.set_name("lua init script")
.exec()
.unwrap();
lua.sandbox(true).unwrap();
// return;
// runtimes.push(lua);
// }
drop(globals);
lua
}
fn clean_global(#[cfg(not(debug_assertions))] lua: &Lua, globals: &Table) {
// allows us to assert things in debug mode, and keep the asserts in release without hard crashing
#[cfg(not(debug_assertions))]
{
let assert = lua.create_function(|_lua, ()| Ok(())).unwrap();
globals.set("assert", assert).unwrap();
}
// disabling this for now as mlua should do a decent job of collecting itself, and if not we can manage it in rust, no need for the users to worry about it
globals.raw_remove("collectgarbage").unwrap();
// remove this for use later with the error log kind (it also panics the interpereter when used so thats a nono)
globals.raw_remove("error").unwrap();
// depricated and unneeded
globals.raw_remove("gcinfo").unwrap();
// undocumented as far as i can tell and serves no immediate user facing purpose
globals.raw_remove("newproxy").unwrap();
// we should be using the logging functions instead of raw print
globals.raw_remove("print").unwrap();
}
fn prepare_global(lua: &Lua, globals: &Table) {
let table = lua.create_table().unwrap();
table.set("info", CAT_INF).unwrap();
table.set("fun", CAT_FUN).unwrap();
table.set("general", CAT_GEN).unwrap();
table.set("moderation", CAT_MOD).unwrap();
globals.set("commandCategory", table).unwrap();
let table = lua.create_table().unwrap();
globals.set("commands", table).unwrap();
}
fn set_logging(lua: &Lua, globals: &Table) {
let log = lua
.create_function(|_lua, strings: Variadic<String>| {
let mut st = String::new();
for i in strings {
st.push(' ');
st.push_str(i.as_str());
}
info!("Lua: {}", st);
Ok(())
})
.unwrap();
globals.set("info", log).unwrap();
}

View file

@ -2,239 +2,160 @@
#![deny(clippy::expect_used)]
#![deny(clippy::pedantic)]
//#![feature(async_fn_traits)]
// mod commands;
// use nopalmo_lib;
mod arguments;
mod bot;
mod commands;
mod constants;
// mod permissions;
mod system_messages;
mod lua;
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 bot::BotHandler;
use commands::{Command, CommandFnKind};
use metadata::LevelFilter;
use serenity::all::GatewayIntents;
use serenity::all::{Context, GuildId, ShardManager};
use serenity::model::channel::Message;
use serenity::model::gateway::Ready;
use serenity::prelude::*;
// use tokio::sync::mpsc;
use serenity::Client;
use tracing::*;
// type DMCommandArguments = Handler;
// type GCommandArguments = Handler;
// pub type CommandReturn = Pin<Box<dyn Future<Output = ()>>>;
// pub type CommandReturn = ();
// 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";
// 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<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>>,
// >,
}
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) {
let prefix_regex = format!(
r"^({}|{}|<@{}>)\s?",
constants::COMMAND_PREFIX,
"NPO_PFX",
ctx.cache.current_user().id
);
let cmd_regex = format!(r"{}[A-Za-z0-9_\-]+", prefix_regex);
let result = match regex::Regex::new(cmd_regex.as_str())
.unwrap()
.find(&msg.content)
{
Some(result) => result,
None => return, // silently exit because not every message is meant for the bot
};
if self.do_shutdown.load(std::sync::atomic::Ordering::SeqCst) {
let _ = msg
.channel_id
.say(
&ctx.http,
"Sorry! Your request was cancelled because the bot is shutting down.",
)
.await;
return;
}
let target_cmd_name = regex::Regex::new(prefix_regex.as_str())
.unwrap()
.replace(result.as_str(), "");
dbg!(&target_cmd_name);
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);
// }
//}
}
async fn ready(&self, _: Context, ready: Ready) {
println!("Shart `{}` is connected!", ready.shard.unwrap().id);
}
}
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
#[tokio::main]
async fn main() {
#[cfg(not(debug_assertions))]
let token = include_str!("bot_token.prod");
#[cfg(debug_assertions)]
let token = include_str!("bot_token.dev");
tracing::subscriber::set_global_default(
tracing_subscriber::fmt()
.with_max_level(LevelFilter::INFO)
.finish(),
)
.unwrap();
let intents = GatewayIntents::DIRECT_MESSAGES
| GatewayIntents::DIRECT_MESSAGE_REACTIONS
| GatewayIntents::GUILDS
| GatewayIntents::GUILD_MODERATION
// | GatewayIntents::GUILD_EMOJIS_AND_STICKERS
| GatewayIntents::GUILD_MEMBERS
| GatewayIntents::GUILD_MESSAGE_REACTIONS
| GatewayIntents::GUILD_MESSAGES
| GatewayIntents::MESSAGE_CONTENT;
info!("Logging initialized.");
let mut commands = HashMap::default();
// let (system_sender, system_receiver) = mpsc::channel(256);
// let system_sender = Mutex::new(system_sender);
// let system_receiver = Mutex::new(system_receiver);
info!("Loading stock commands.");
insert_stock(&mut commands);
info!("Loading rust dynamic commands.");
insert_rust(&mut commands);
// let mut command_map = HashMap::new();
info!("Initializing Lua runtime.");
let mut lua = lua::initialize(); // may not need to be mutable, but its fine for now
// 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();
info!("Loading lua commandlets.");
insert_lua(&mut commands, &mut lua);
// channel
// .sendMessage("Pong!\n" + ctx. + "ms\n")
// .queue();
warn!("ABORTING CONNECTING TO DISCORD, BYE BYE!");
return;
// // if (argumentMap.get("all") != null) {
// // TODO load this at runtime so the key will not be stored in the binary?
// #[cfg(not(debug_assertions))]
// let token = {
// info!("Initializing bot with production token.");
// include_str!("bot_token.prod")
// };
// #[cfg(debug_assertions)]
// let token = {
// info!("Initializing bot with development token.");
// include_str!("bot_token.dev")
// };
// // channel.sendMessage("Gathering data. . .").queue(msg -> {
// // long timeToProcess = System.currentTimeMillis();
// let intents = GatewayIntents::DIRECT_MESSAGES
// | GatewayIntents::DIRECT_MESSAGE_REACTIONS
// | GatewayIntents::GUILDS
// | GatewayIntents::GUILD_MODERATION
// // | GatewayIntents::GUILD_EMOJIS_AND_STICKERS
// | GatewayIntents::GUILD_MEMBERS
// | GatewayIntents::GUILD_MESSAGE_REACTIONS
// | GatewayIntents::GUILD_MESSAGES
// | GatewayIntents::MESSAGE_CONTENT;
// // 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();
// // }
// let mut client = match Client::builder(&token, intents)
// .event_handler(BotHandler { commands })
// .await
// {
// Ok(client) => client,
// Err(err) => panic!("Error starting client connection: `{err}`"),
// };
// // 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";
// SHARD_MANAGER.set(client.shard_manager.clone()).unwrap();
// // msg.editMessage(out + "\nTime to process: " + (System.currentTimeMillis() - timeToProcess) + "ms")
// // .queue();
// // });
// // }
// if let Err(why) = client.start_shards(2).await {
// error!("Client error: {why:?}");
// }
// 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),
// }),
// );
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,
})
.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(),
));
if let Err(why) = client.start_shards(2).await {
println!("Client error: {why:?}");
}
status_timer.abort();
// warn!("MAIN FUNCTION EXITING");
}
/// The last set of commands, these are used by the hoster of the engine.
pub fn insert_lua(_commands: &mut HashMap<String, Command>, _lua: &mut mlua::Lua) {
// 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()),
// );
}
/// Cannot use any command names of stock commands, but gets to pick before lua commands are loaded.
pub fn insert_rust(_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()),
// );
}
/// Will never fail, gets first pick of command names and properties. It is up to the maintainer to make damn sure this works.
pub fn insert_stock(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.