From 991feb82a1b5644fe5fae92d6b9a70fa52d02527 Mon Sep 17 00:00:00 2001 From: deepCurse Date: Fri, 11 Oct 2024 01:46:51 -0300 Subject: [PATCH 1/5] dev sync attempt 2 --- lua/loader.lua | 46 +++++++++-- src/arguments.rs | 136 +++++++++++++++++-------------- src/bot.rs | 15 ++-- src/commands.rs | 11 +-- src/lua.rs | 206 ++++++++++++++++++++++++++++++++++------------- src/main.rs | 78 +++++++++++------- 6 files changed, 329 insertions(+), 163 deletions(-) diff --git a/lua/loader.lua b/lua/loader.lua index 144e991..ef7ec35 100644 --- a/lua/loader.lua +++ b/lua/loader.lua @@ -5,17 +5,52 @@ sys.registerCommand({ 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 + hidden = false, -- whether or not other commands should acknowledge its existence, 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) + arguments = { + { + type = "positional", + position = 0, + optional = true, + data_type = "string", + checker = function(var) + for index, value in ipairs(sys.getCommandTable()) do + if value == var then + return; -- exit normally because the command was found + end + end + + -- throw an error to whoever ran this function saying the command does not exist + error("Command `" .. var .. "` does not exist.", 2) + end, + }, + }, + + func = function(context, message, guildid, commandArguments) + if commandArguments.getArgument(0) ~= nil then + log.info("Help info for command `" .. commandArguments.getArgument(0) .. "`") + else + log.info("Here is a list of all commands:") + for index, value in pairs(sys.getCommandTable()) do + if not value.hidden then + log.info("\tCommand pretty name: " .. value.pretty_name) + log.info("\tCommand name: " .. value.name) + log.info("\tCommand aliases: " .. value.aliases) + log.info("\tCommand category: " .. value.category) + log.info("\tCommand help info: " .. value.help) + end + end end end }) -sys.executeCommand("help", "E") +sys.executeCommand("help", "help") +sys.executeCommand("help") +-- since the next usage will throw an error, catch the error +log.info(pcall(sys.executeCommand, "help", "invalid_command")) + +log.info("end") -- exit(0) -- local seen={} @@ -38,3 +73,4 @@ sys.executeCommand("help", "E") -- end -- dump(_G,"") + diff --git a/src/arguments.rs b/src/arguments.rs index 7fd6b19..af1b0a8 100644 --- a/src/arguments.rs +++ b/src/arguments.rs @@ -1,75 +1,87 @@ use std::{collections::HashMap, sync::Arc}; #[derive(Debug, Clone, PartialEq, Default)] -pub struct ArgumentStorage { - // arguments: Vec, - long_key_to_id: HashMap>, - short_key_to_id: HashMap>, -} +pub struct ArgumentStorage(HashMap); 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, short: Option) { - 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); - } - } - } + pub fn add_argument() {} } -#[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, +// #[derive(Debug, Clone, PartialEq, Default)] +// pub struct ArgumentStorage { +// // arguments: Vec, +// long_key_to_id: HashMap>, +// short_key_to_id: HashMap>, +// } - 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, -} +// 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, short: Option) { +// 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), +// } -#[derive(Debug, Clone, PartialEq, PartialOrd)] -pub enum ArgumentContainer { - Value(String), - Argument(usize), -} diff --git a/src/bot.rs b/src/bot.rs index 39c2dc7..e7e22c0 100644 --- a/src/bot.rs +++ b/src/bot.rs @@ -1,18 +1,18 @@ -use std::collections::HashMap; -use std::sync::atomic::Ordering; -use std::sync::{Arc, OnceLock}; +use std::sync::atomic::{AtomicBool, Ordering}; +use std::sync::{Arc, Condvar, OnceLock}; use crate::commands::{self, COMMANDS}; -use crate::{constants, tasks, DO_SHUTDOWN, NO_DISCORD}; +use crate::{constants, tasks, CARG_NO_DISCORD}; use serenity::all::{ActivityData, Context, GuildId}; use serenity::all::{EventHandler, GatewayIntents, ShardManager}; use serenity::model::channel::Message; use serenity::model::gateway::Ready; use serenity::Client; -use tracing::*; +use tracing::{error, info, warn}; + +pub 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 pub static SHARD_MANAGER: OnceLock> = OnceLock::new(); - pub struct BotHandler; unsafe impl Sync for BotHandler {} @@ -122,7 +122,7 @@ pub async fn start() { | GatewayIntents::GUILD_MESSAGES | GatewayIntents::MESSAGE_CONTENT; - if *NO_DISCORD.get().unwrap() { + if *CARG_NO_DISCORD.get().unwrap() { warn!("ABORTING CONNECTING TO DISCORD, BYE BYE!"); } else { let mut client = match Client::builder(&token, intents) @@ -142,3 +142,4 @@ pub async fn start() { warn!("BOT EXITING"); } + diff --git a/src/commands.rs b/src/commands.rs index 4c9d02c..935cdab 100644 --- a/src/commands.rs +++ b/src/commands.rs @@ -3,7 +3,7 @@ use std::{collections::HashMap, fmt::Debug, sync::LazyLock, time::Duration}; use serenity::all::{Context, GuildId, Message, Permissions}; use tokio::sync::RwLock; -use crate::arguments::{Argument, ArgumentStorage}; +use crate::arguments::ArgumentStorage; pub static COMMANDS: LazyLock>> = LazyLock::new(|| RwLock::new(HashMap::new())); @@ -90,10 +90,10 @@ impl Command { self.timeout = Some(timeout); self } - pub fn argument(mut self, argument: Argument) -> Self { - self.arguments.add(argument); - 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 @@ -138,3 +138,4 @@ impl Debug for Command { // } // } + diff --git a/src/lua.rs b/src/lua.rs index 1f97bc7..e4a28ca 100644 --- a/src/lua.rs +++ b/src/lua.rs @@ -1,13 +1,37 @@ -use mlua::{Lua, LuaOptions, StdLib, Table, Variadic}; -use tracing::info; +use mlua::{Lua, LuaOptions, Result, StdLib, Table, Value, Variadic}; +use tracing::{debug, error, info, trace, warn}; -use crate::constants::{CAT_FUN, CAT_GEN, CAT_INF, CAT_MOD, VERSION}; +use crate::constants::{CAT_DEV, CAT_FUN, CAT_GEN, CAT_INF, CAT_MOD, VERSION}; +// if there is a better way please tell me or replace this garbage +pub fn value_to_string(value: Value) -> String { + match value { + Value::Nil => "".to_owned(), + Value::Boolean(boolean) => (if boolean { "true" } else { "false" }).to_owned(), + Value::LightUserData(ludata) => (ludata.0 as usize).to_string(), + Value::Integer(int) => int.to_string(), + Value::Number(num) => num.to_string(), + Value::Vector(vec) => vec.to_string(), + Value::String(string2) => String::from_utf8_lossy(string2.as_bytes()).to_string(), + Value::Table(table) => format!("{table:?}"), // could probably handle this one better but whatever + Value::Function(function) => { + let info = function.info(); + format!( + "`{}` at `{}`", + info.name.map_or_else(|| "Unnamed".to_owned(), |val| val), + info.line_defined + .map_or_else(|| "Unknown location".to_owned(), |val| val.to_string()), + ) + } + Value::Thread(thread) => { + format!("`{}`:`{:?}`", thread.to_pointer() as usize, thread.status()) + } + Value::UserData(userdata) => format!("`{userdata:?}`"), + Value::Error(err) => err.to_string(), + } +} -pub fn initialize() -> Lua { - // let mut runtimes = vec![]; - // for i in 0..1 { - +pub fn initialize() -> Result { // 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 @@ -18,14 +42,14 @@ pub fn initialize() -> Lua { LuaOptions::new() .catch_rust_panics(true) .thread_pool_size(num_cpus::get()), - ) - .expect("Lua could not initialize properly"); + )?; + // .expect("Lua could not initialize properly"); // set max to 2mb - lua.set_memory_limit(2 * 1024 * 1024).unwrap(); + info!("Previous memory limit `{}`", lua.set_memory_limit(2 * 1024 * 1024)?); let globals = lua.globals(); - globals.set("NP_VERSION", VERSION).unwrap(); + globals.set("NP_VERSION", VERSION)?; clean_global( #[cfg(not(debug_assertions))] @@ -37,12 +61,9 @@ pub fn initialize() -> Lua { set_logging(&lua, &globals); - // runtimes.push(lua); - // } - drop(globals); - lua + Ok(lua) } fn clean_global(#[cfg(not(debug_assertions))] lua: &Lua, globals: &Table) { @@ -54,8 +75,8 @@ fn clean_global(#[cfg(not(debug_assertions))] lua: &Lua, globals: &Table) { } // 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(); + // keep error as lua needs trace errors instead of rust like return types (its easier to keep things the way they are so we dont confuse newcomers) + // 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 @@ -65,87 +86,152 @@ fn clean_global(#[cfg(not(debug_assertions))] lua: &Lua, globals: &Table) { } 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 command_category_table = lua.create_table().unwrap(); + command_category_table.set("dev", CAT_DEV).unwrap(); + command_category_table.set("info", CAT_INF).unwrap(); + command_category_table.set("fun", CAT_FUN).unwrap(); + command_category_table.set("general", CAT_GEN).unwrap(); + command_category_table.set("moderation", CAT_MOD).unwrap(); + globals + .set("commandCategory", command_category_table) + .unwrap(); - let table = lua.create_table().unwrap(); - #[rustfmt::skip] - table.set("registerCommand", lua.create_function(rust_lua_functions::registerCommand).unwrap()).unwrap(); - #[rustfmt::skip] - table.set("executeCommand", lua.create_function(rust_lua_functions::executeCommand).unwrap()).unwrap(); + let sys_table = lua.create_table().unwrap(); + #[allow(clippy::deprecated_cfg_attr)] // shut up clippy thats unstable + #[cfg_attr(rustfmt, rustfmt_skip)] // wish this was comment based but whatever + { + sys_table.set("registerCommand", lua.create_function(rust_lua_functions::register_command).unwrap()).unwrap(); + sys_table.set("executeCommand", lua.create_function(rust_lua_functions::execute_command).unwrap()).unwrap(); + sys_table.set("luaValueToString", lua.create_function(rust_lua_functions::lua_value_to_string).unwrap()).unwrap(); + } - globals.set("sys", table).unwrap(); + globals.set("sys", sys_table).unwrap(); } fn set_logging(lua: &Lua, globals: &Table) { - let log = lua - .create_function(|_lua, strings: Variadic| { + // i wish i could loop these but i cant instantiate macros + let log_table = lua.create_table().unwrap(); + let trace = lua + .create_function(|_lua, strings: Variadic| { let mut st = String::new(); for i in strings { + st.push_str(value_to_string(i).as_str()); st.push(' '); - st.push_str(i.as_str()); } + st.pop(); + trace!("Lua: {st}"); + Ok(()) + }) + .unwrap(); + log_table.set("trace", trace).unwrap(); + let debug = lua + .create_function(|_lua, strings: Variadic| { + let mut st = String::new(); + for i in strings { + st.push_str(value_to_string(i).as_str()); + st.push(' '); + } + st.pop(); + debug!("Lua: {}", st); + Ok(()) + }) + .unwrap(); + log_table.set("debug", debug).unwrap(); + let info = lua + .create_function(|_lua, strings: Variadic| { + let mut st = String::new(); + for i in strings { + st.push_str(value_to_string(i).as_str()); + st.push(' '); + } + st.pop(); info!("Lua: {}", st); Ok(()) }) .unwrap(); - globals.set("info", log).unwrap(); + log_table.set("info", info).unwrap(); + let warn = lua + .create_function(|_lua, strings: Variadic| { + let mut st = String::new(); + for i in strings { + st.push_str(value_to_string(i).as_str()); + st.push(' '); + } + st.pop(); + warn!("Lua: {}", st); + Ok(()) + }) + .unwrap(); + log_table.set("warn", warn).unwrap(); + let error = lua + .create_function(|_lua, strings: Variadic| { + let mut st = String::new(); + for i in strings { + st.push_str(value_to_string(i).as_str()); + st.push(' '); + } + st.pop(); + error!("Lua: {}", st); + Ok(()) + }) + .unwrap(); + log_table.set("error", error).unwrap(); + globals.set("log", log_table).unwrap(); } -#[allow(non_snake_case)] mod rust_lua_functions { - use mlua::{ExternalError, Lua, Result, Value, Variadic}; + use mlua::prelude::LuaResult; + use mlua::{ExternalError, Lua, Value, Variadic}; use tracing::info; - pub fn executeCommand(_lua: &Lua, arg_values: Variadic) -> Result<()> { - let mut command_name = String::new(); + use crate::lua::value_to_string; + + pub fn execute_command(_lua: &Lua, mut arg_values: Variadic) -> LuaResult<()> { + let command_name = match arg_values.get(0).cloned() { + Some(name) => name, + None => return Err("executeCommand requires a command name to execute! please supply a valid command name as the first argument.".into_lua_err()), + }; + let mut command_args = vec![]; - for (idx, value) in arg_values.iter().enumerate() { + for value in arg_values.split_off(1) { match value { + Value::String(string) => { + command_args.push(string.to_string_lossy().to_string()); + } Value::Nil => { return Err( "Nil argument provided! executeCommand accepts no nil arguments" .into_lua_err(), ) } - Value::String(string) => { - if idx == 0 { - command_name = string.to_string_lossy().to_string(); - } else { - command_args.push(string.to_string_lossy().to_string()); - } - } Value::Table(_) => { return Err("Direct run commands are not supported yet.".into_lua_err()) } - // Value::Function(_) => todo!(), - _ => { - return Err("Invalid type used! Only `String` and `Table` are supported by executeCommand".into_lua_err()); - } - }; + _ => return Err( + "Invalid type used! Only `String` and `Table` are supported by executeCommand" + .into_lua_err(), + ), + } } info!( - "Running lua command `{:?}` with args `{:?}`", - command_name, command_args + "Running lua command `{}` with args `{:?}`", + value_to_string(command_name), + command_args ); Ok(()) } - pub fn registerCommand(_lua: &Lua, arg_values: Variadic) -> Result<()> { + pub fn register_command(_lua: &Lua, arg_values: Variadic) -> LuaResult<()> { let table = match arg_values.get(0) { + Some(Value::Table(table)) => table, Some(Value::Nil) | None => { return Err( "Nil argument provided! registerCommand accepts no nil arguments" .into_lua_err(), ) } - Some(Value::Table(table)) => table, - Some(_) => { + _ => { return Err( "Invalid type used! Only `Table` is supported by registerCommand" .into_lua_err(), @@ -153,8 +239,17 @@ mod rust_lua_functions { } }; + info!("{:?}", table); + Ok(()) } + + pub fn lua_value_to_string(_lua: &Lua, mut arg_values: Variadic) -> LuaResult { + match arg_values.len() { + 1 => Ok(value_to_string(arg_values.remove(0))), + _ => Err("luaValueToString requires exactly one argument.".into_lua_err()), + } + } } // pub mod luagen_module { @@ -258,3 +353,4 @@ mod rust_lua_functions { // } // } // } + diff --git a/src/main.rs b/src/main.rs index a7b44ee..ddd81ee 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,6 +1,8 @@ #![deny(clippy::unwrap_used)] #![deny(clippy::expect_used)] #![deny(clippy::pedantic)] +#![allow(clippy::unreadable_literal)] +#![allow(clippy::needless_pass_by_value)] mod arguments; mod bot; @@ -9,67 +11,82 @@ mod constants; mod lua; mod tasks; -use std::collections::HashMap; -use std::sync::atomic::{AtomicBool, Ordering}; -use std::sync::{Condvar, OnceLock}; +use std::process::exit; +use std::sync::atomic::Ordering; +use std::sync::OnceLock; + +use serenity::all::{Context, GuildId}; +use serenity::model::channel::Message; +use tracing::metadata::LevelFilter; + +use tracing::{error, info}; use commands::{Command, CommandFnKind, COMMANDS}; use constants::CAT_HID; -use metadata::LevelFilter; -use serenity::all::{Context, GuildId}; -use serenity::model::channel::Message; -use tracing::*; -pub static NO_DISCORD: OnceLock = 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 +pub static CARG_NO_DISCORD: OnceLock = OnceLock::new(); #[tokio::main] async fn main() { - tracing::subscriber::set_global_default( + if let Err(err) = tracing::subscriber::set_global_default( tracing_subscriber::fmt() .with_max_level(LevelFilter::INFO) .finish(), - ) - .unwrap(); + ) { + eprintln!("Encountered an error while initializing logging! `{err}`"); + exit(1); + }; info!("Logging initialized."); info!("Grabbing commandline input."); let mut nodiscord = false; for i in std::env::args() { + #[allow(clippy::single_match)] // here for convienience when adding additional flags match i.as_str() { "nodiscord" => nodiscord = true, _ => {} } } - NO_DISCORD.set(nodiscord).unwrap(); - - // let mut commands = HashMap::default(); + if let Err(err) = CARG_NO_DISCORD.set(nodiscord) { + error!("Encountered an error while setting the `nodiscord` flag! `{err}`"); + exit(1); + }; info!("Loading stock commands."); insert_stock().await; info!("Loading rust dynamic commands."); - insert_rust().await; + insert_rust(); info!("Initializing Lua runtime."); - let lua = lua::initialize(); // may not need to be mutable, but its fine for now + let lua = match lua::initialize() { + Ok(lua) => lua, + Err(err) => { + error!("Encountered an error while initializing lua! `{err}`"); + exit(1); + }, + }; // may not need to be mutable, but its fine for now - lua.load(include_str!("../lua/loader.lua")) - .set_name("lua init script") + if let Err(err) = lua + .load(include_str!("../lua/loader.lua")) + .set_name("loader.lua") .exec() - .unwrap(); + { + error!("Encountered an error while running loader.lua: {err}"); + }; info!("Loading lua commandlets."); - insert_lua(&lua).await; + insert_lua(&lua); + + if let Err(err) = lua.sandbox(true) { + error!("Encountered an error while setting the lua runtime to sandbox mode! `{err}`"); + exit(1); + }; - lua.sandbox(true).unwrap(); - bot::start().await; } /// The last set of commands, these are used by the hoster of the engine. -pub async fn insert_lua(_lua: &mlua::Lua) { +pub fn insert_lua(_lua: &mlua::Lua) { // commands.insert( // "stop".to_owned(), // Command::new("stop".to_owned()) @@ -84,7 +101,7 @@ pub async fn insert_lua(_lua: &mlua::Lua) { } /// Cannot use any command names of stock commands, but gets to pick before lua commands are loaded. -pub async fn insert_rust() { +pub fn insert_rust() { // commands.insert( // "stop".to_owned(), // Command::new("stop".to_owned()) @@ -118,13 +135,16 @@ fn stop_command(_: Context, msg: Message, _: Option) { if msg.author.id != 380045419381784576 { return; } - DO_SHUTDOWN.0.store(true, Ordering::SeqCst); - DO_SHUTDOWN.1.notify_all(); + bot::DO_SHUTDOWN.0.store(true, Ordering::SeqCst); + bot::DO_SHUTDOWN.1.notify_all(); let handle = tokio::runtime::Handle::current(); let _eg = handle.enter(); handle.spawn(async { + // this is literally impossible to fail as the SHARD_MANAGER is required to exist for this function to ever run + #[allow(clippy::unwrap_used)] bot::SHARD_MANAGER.get().unwrap().shutdown_all().await; }); } + From 578918b22fbdbaba6dc079e6c8874d2547523277 Mon Sep 17 00:00:00 2001 From: deepCurse Date: Tue, 18 Feb 2025 16:46:25 -0400 Subject: [PATCH 2/5] Updated dependencies Updated formatting file Changed the command line argument system Updated error checking and matching logic to be easier to work with Moved groups of functions to more correct modules Updated the constants.rs file and added a help string for use with the new argument system Disabled lua for now, the rust side of the bot takes priority Changed the shutdown handler to be easier to work with Added shutdown monitor task that gets started after the bot is ready, allowing a graceful shutdown from anywhere in the program, authentication is expected to be handled at the place of calling, not within the shutdown function Added a basic database interface, SQLite will be implemented first as it is the simplest and most useful to the spiff team --- Cargo.lock | 1027 +++++++++++++++++++++++++------------ Cargo.toml | 7 +- lua/help.lua | 20 +- rust-toolchain.toml | 2 + rustfmt.toml | 34 +- src/arguments.rs | 10 +- src/bot.rs | 113 ++-- src/commands.rs | 146 ++++-- src/constants.rs | 66 ++- src/databases/access.rs | 11 + src/databases/mariadb.rs | 11 + src/databases/mod.rs | 60 +++ src/databases/postgres.rs | 11 + src/databases/sqlite.rs | 72 +++ src/errors.rs | 40 ++ src/lua.rs | 148 +++--- src/main.rs | 325 ++++++++---- src/tasks.rs | 105 +++- 18 files changed, 1564 insertions(+), 644 deletions(-) create mode 100644 rust-toolchain.toml create mode 100644 src/databases/access.rs create mode 100644 src/databases/mariadb.rs create mode 100644 src/databases/mod.rs create mode 100644 src/databases/postgres.rs create mode 100644 src/databases/sqlite.rs create mode 100644 src/errors.rs diff --git a/Cargo.lock b/Cargo.lock index 7c5fc4b..e939c77 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1,21 +1,21 @@ # This file is automatically @generated by Cargo. # It is not intended for manual editing. -version = 3 +version = 4 [[package]] name = "addr2line" -version = "0.21.0" +version = "0.24.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a30b2e23b9e17a9f90641c7ab1549cd9b44f296d3ccbf309d2863cfe398a0cb" +checksum = "dfbe277e56a376000877090da837660b4427aad530e3028d44e0bffe4f89a1c1" dependencies = [ "gimli", ] [[package]] -name = "adler" -version = "1.0.2" +name = "adler2" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" +checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627" [[package]] name = "aho-corasick" @@ -43,43 +43,43 @@ dependencies = [ [[package]] name = "arrayvec" -version = "0.7.4" +version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96d30a06541fbafbc7f82ed10c06164cfbd2c401138f6addd8404629c4b16711" +checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" dependencies = [ "serde", ] [[package]] name = "async-trait" -version = "0.1.80" +version = "0.1.83" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c6fa2087f2753a7da8cc1c0dbfcf89579dd57458e36769de5ac750b4671737ca" +checksum = "721cae7de5c34fbb2acd27e21e6d2cf7b886dce0c27388d46c4e6c47ea4318dd" dependencies = [ "proc-macro2", "quote", - "syn 2.0.65", + "syn 2.0.93", ] [[package]] name = "autocfg" -version = "1.3.0" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0" +checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" [[package]] name = "backtrace" -version = "0.3.71" +version = "0.3.74" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26b05800d2e817c8b3b4b54abd461726265fa9789ae34330622f2db9ee696f9d" +checksum = "8d82cb332cdfaed17ae235a638438ac4d4839913cc2af585c3c6746e8f8bee1a" dependencies = [ "addr2line", - "cc", "cfg-if", "libc", "miniz_oxide", "object", "rustc-demangle", + "windows-targets 0.52.6", ] [[package]] @@ -102,9 +102,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bitflags" -version = "2.5.0" +version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf4b9d6a944f767f8e5e0db018570623c85f3d925ac718db4e06d0187adb21c1" +checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" [[package]] name = "block-buffer" @@ -117,9 +117,9 @@ dependencies = [ [[package]] name = "bstr" -version = "1.10.0" +version = "1.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "40723b8fb387abc38f4f4a37c09073622e41dd12327033091ef8950659e6dc0c" +checksum = "786a307d683a5bf92e6fd5fd69a7eb613751668d1d8d67d802846dfe367c62c8" dependencies = [ "memchr", "serde", @@ -145,24 +145,24 @@ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] name = "bytes" -version = "1.6.0" +version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "514de17de45fdb8dc022b1a7975556c53c86f9f0aa5f534b98977b171857c2c9" +checksum = "325918d6fe32f23b19878fe4b34794ae41fc19ddbe53b10571a4874d44ffd39b" [[package]] name = "camino" -version = "1.1.7" +version = "1.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e0ec6b951b160caa93cc0c7b209e5a3bff7aae9062213451ac99493cd844c239" +checksum = "8b96ec4966b5813e2c0507c1f86115c8c5abaadc3980879c3424042a02fd1ad3" dependencies = [ "serde", ] [[package]] name = "cargo-platform" -version = "0.1.8" +version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24b1f0365a6c6bb4020cd05806fd0d33c44d38046b8bd7f0e40814b9763cabfc" +checksum = "e35af189006b9c0f00a064685c727031e3ed2d8020f7ba284d78cc2671bd36ea" dependencies = [ "serde", ] @@ -182,9 +182,12 @@ dependencies = [ [[package]] name = "cc" -version = "1.0.98" +version = "1.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41c270e7540d725e65ac7f1b212ac8ce349719624d7bcff99f8e2e488e8cf03f" +checksum = "8d6dbb628b8f8555f86d0323c2eb39e3ec81901f4b83e091db8a6a76d316a333" +dependencies = [ + "shlex", +] [[package]] name = "cfg-if" @@ -193,23 +196,29 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] -name = "chrono" -version = "0.4.38" +name = "cfg_aliases" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a21f936df1771bf62b77f047b726c4625ff2e8aa607c01ec06e5a05bd8463401" +checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" + +[[package]] +name = "chrono" +version = "0.4.39" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e36cc9d416881d2e24f9a963be5fb1cd90966419ac844274161d10488b3e825" dependencies = [ "android-tzdata", "iana-time-zone", "num-traits", "serde", - "windows-targets 0.52.5", + "windows-targets 0.52.6", ] [[package]] name = "command_attr" -version = "0.5.2" +version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "88da8d7e9fe6f30d8e3fcf72d0f84102b49de70fece952633e8439e89bdc7631" +checksum = "6fcc89439e1bb4e19050a9586a767781a3060000d2f3296fd2a40597ad9421c5" dependencies = [ "proc-macro2", "quote", @@ -228,15 +237,15 @@ dependencies = [ [[package]] name = "core-foundation-sys" -version = "0.8.6" +version = "0.8.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "06ea2b9bc92be3c2baa9334a323ebca2d6f074ff852cd1d7b11064035cd3868f" +checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" [[package]] name = "cpufeatures" -version = "0.2.12" +version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53fe5e26ff1b7aef8bca9c6080520cfb8d9333c7568e1829cef191a9723e5504" +checksum = "16b80225097f2e5ae4e7179dd2266824648f3e2f49d9134d584b76389d31c4c3" dependencies = [ "libc", ] @@ -252,18 +261,18 @@ dependencies = [ [[package]] name = "crossbeam-channel" -version = "0.5.13" +version = "0.5.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33480d6946193aa8033910124896ca395333cae7e2d1113d1fef6c3272217df2" +checksum = "06ba6d68e24814cb8de6bb986db8222d3a027d15872cabc0d18817bc3c0e4471" dependencies = [ "crossbeam-utils", ] [[package]] name = "crossbeam-utils" -version = "0.8.20" +version = "0.8.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22ec99545bb0ed0ea7bb9b8e1e9122ea386ff8a48c0922e43f36d45ab09e0e80" +checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" [[package]] name = "crypto-common" @@ -275,6 +284,16 @@ dependencies = [ "typenum", ] +[[package]] +name = "ctrlc" +version = "3.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90eeab0aa92f3f9b4e87f258c72b139c207d251f9cbc1080a0086b86a8870dd3" +dependencies = [ + "nix", + "windows-sys 0.59.0", +] + [[package]] name = "dashmap" version = "5.5.3" @@ -282,7 +301,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "978747c1d849a7d2ee5e8adc0159961c48fb7e5db2f06af6723b80123bb53856" dependencies = [ "cfg-if", - "hashbrown", + "hashbrown 0.14.5", "lock_api", "once_cell", "parking_lot_core", @@ -316,10 +335,27 @@ dependencies = [ ] [[package]] -name = "encoding_rs" -version = "0.8.34" +name = "displaydoc" +version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b45de904aa0b010bce2ab45264d0631681847fa7b6f2eaa7dab7619943bc4f59" +checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.93", +] + +[[package]] +name = "either" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0" + +[[package]] +name = "encoding_rs" +version = "0.8.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75030f3c4f45dafd7586dd6780965a8c7e8e285a5ecb86713e63a79c5b2766f3" dependencies = [ "cfg-if", ] @@ -332,12 +368,12 @@ checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" [[package]] name = "errno" -version = "0.3.9" +version = "0.3.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "534c5cf6194dfab3db3242765c03bbe257cf92f22b38f6bc0c58d59108a820ba" +checksum = "33d852cb9b869c2a9b3df2f71a3074817f01e1844f839a144f5fcef059a4eb5d" dependencies = [ "libc", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -351,15 +387,15 @@ dependencies = [ [[package]] name = "fastrand" -version = "2.1.0" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9fc0510504f03c51ada170672ac806f1f105a88aa97a5281117e1ddc3368e51a" +checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" [[package]] name = "flate2" -version = "1.0.30" +version = "1.0.35" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f54427cfd1c7829e2a139fcefea601bf088ebca651d2bf53ebc600eac295dae" +checksum = "c936bfdafb507ebbf50b8074c54fa31c5be9a1e7e5f467dd659697041407d07c" dependencies = [ "crc32fast", "miniz_oxide", @@ -382,9 +418,9 @@ dependencies = [ [[package]] name = "futures" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "645c6916888f6cb6350d2550b80fb63e734897a8498abe35cfb732b6487804b0" +checksum = "65bc07b1a8bc7c85c5f2e110c476c7389b4554ba72af57d8445ea63a576b0876" dependencies = [ "futures-channel", "futures-core", @@ -396,9 +432,9 @@ dependencies = [ [[package]] name = "futures-channel" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eac8f7d7865dcb88bd4373ab671c8cf4508703796caa2b1985a9ca867b3fcb78" +checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" dependencies = [ "futures-core", "futures-sink", @@ -406,44 +442,44 @@ dependencies = [ [[package]] name = "futures-core" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dfc6580bb841c5a68e9ef15c77ccc837b40a7504914d52e47b8b0e9bbda25a1d" +checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" [[package]] name = "futures-io" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a44623e20b9681a318efdd71c299b6b222ed6f231972bfe2f224ebad6311f0c1" +checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" [[package]] name = "futures-macro" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac" +checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" dependencies = [ "proc-macro2", "quote", - "syn 2.0.65", + "syn 2.0.93", ] [[package]] name = "futures-sink" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9fb8e00e87438d937621c1c6269e53f536c14d3fbd6a042bb24879e57d474fb5" +checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" [[package]] name = "futures-task" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38d84fa142264698cdce1a9f9172cf383a0c82de1bddcf3092901442c4097004" +checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" [[package]] name = "futures-util" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d6401deb83407ab3da39eba7e33987a73c3df0c82b4bb5813ee871c19c41d48" +checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" dependencies = [ "futures-channel", "futures-core", @@ -484,20 +520,32 @@ checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" dependencies = [ "cfg-if", "libc", - "wasi", + "wasi 0.11.0+wasi-snapshot-preview1", +] + +[[package]] +name = "getrandom" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43a49c392881ce6d5c3b8cb70f98717b7c07aabbdff06687b9030dbfbe2725f8" +dependencies = [ + "cfg-if", + "libc", + "wasi 0.13.3+wasi-0.2.2", + "windows-targets 0.52.6", ] [[package]] name = "gimli" -version = "0.28.1" +version = "0.31.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4271d37baee1b8c7e4b708028c57d816cf9d2434acb33a549475f78c181f6253" +checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" [[package]] name = "glob" -version = "0.3.1" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" +checksum = "a8d1add55171497b4705a648c6b583acafb01d58050a51727785f0b2c8e0a2b2" [[package]] name = "h2" @@ -524,6 +572,12 @@ version = "0.14.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" +[[package]] +name = "hashbrown" +version = "0.15.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289" + [[package]] name = "hermit-abi" version = "0.3.9" @@ -543,9 +597,9 @@ dependencies = [ [[package]] name = "http" -version = "1.1.0" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "21b9ddb458710bc376481b842f5da65cdf31522de232c1ca8146abce2a358258" +checksum = "f16ca2af56261c99fba8bac40a10251ce8188205a4c448fbb745a2e4daa76fea" dependencies = [ "bytes", "fnv", @@ -565,9 +619,9 @@ dependencies = [ [[package]] name = "httparse" -version = "1.8.0" +version = "1.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d897f394bad6a705d5f4104762e116a75639e470d80901eed05a860a95cb1904" +checksum = "7d71d3574edd2771538b901e6549113b4006ece66150fb69c0fb6d9a2adae946" [[package]] name = "httpdate" @@ -577,9 +631,9 @@ checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" [[package]] name = "hyper" -version = "0.14.28" +version = "0.14.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf96e135eb83a2a8ddf766e426a841d8ddd7449d5f00d34ea02b41d2f19eef80" +checksum = "41dfc780fdec9373c01bae43289ea34c972e40ee3c9f6b3c8801a35f35586ce7" dependencies = [ "bytes", "futures-channel", @@ -615,9 +669,9 @@ dependencies = [ [[package]] name = "iana-time-zone" -version = "0.1.60" +version = "0.1.61" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e7ffbb5a1b541ea2561f8c41c087286cc091e21e556a4f09a8f6cbf17b69b141" +checksum = "235e081f3925a06703c2d0117ea8b91f042756fd6e7a6e5d901e8ca1a996b220" dependencies = [ "android_system_properties", "core-foundation-sys", @@ -637,43 +691,173 @@ dependencies = [ ] [[package]] -name = "idna" -version = "0.5.0" +name = "icu_collections" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "634d9b1461af396cad843f47fdba5597a4f9e6ddd4bfb6ff5d85028c25cb12f6" +checksum = "db2fa452206ebee18c4b5c2274dbf1de17008e874b4dc4f0aea9d01ca79e4526" dependencies = [ - "unicode-bidi", - "unicode-normalization", + "displaydoc", + "yoke", + "zerofrom", + "zerovec", +] + +[[package]] +name = "icu_locid" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13acbb8371917fc971be86fc8057c41a64b521c184808a698c02acc242dbf637" +dependencies = [ + "displaydoc", + "litemap", + "tinystr", + "writeable", + "zerovec", +] + +[[package]] +name = "icu_locid_transform" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "01d11ac35de8e40fdeda00d9e1e9d92525f3f9d887cdd7aa81d727596788b54e" +dependencies = [ + "displaydoc", + "icu_locid", + "icu_locid_transform_data", + "icu_provider", + "tinystr", + "zerovec", +] + +[[package]] +name = "icu_locid_transform_data" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fdc8ff3388f852bede6b579ad4e978ab004f139284d7b28715f773507b946f6e" + +[[package]] +name = "icu_normalizer" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19ce3e0da2ec68599d193c93d088142efd7f9c5d6fc9b803774855747dc6a84f" +dependencies = [ + "displaydoc", + "icu_collections", + "icu_normalizer_data", + "icu_properties", + "icu_provider", + "smallvec", + "utf16_iter", + "utf8_iter", + "write16", + "zerovec", +] + +[[package]] +name = "icu_normalizer_data" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8cafbf7aa791e9b22bec55a167906f9e1215fd475cd22adfcf660e03e989516" + +[[package]] +name = "icu_properties" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93d6020766cfc6302c15dbbc9c8778c37e62c14427cb7f6e601d849e092aeef5" +dependencies = [ + "displaydoc", + "icu_collections", + "icu_locid_transform", + "icu_properties_data", + "icu_provider", + "tinystr", + "zerovec", +] + +[[package]] +name = "icu_properties_data" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67a8effbc3dd3e4ba1afa8ad918d5684b8868b3b26500753effea8d2eed19569" + +[[package]] +name = "icu_provider" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ed421c8a8ef78d3e2dbc98a973be2f3770cb42b606e3ab18d6237c4dfde68d9" +dependencies = [ + "displaydoc", + "icu_locid", + "icu_provider_macros", + "stable_deref_trait", + "tinystr", + "writeable", + "yoke", + "zerofrom", + "zerovec", +] + +[[package]] +name = "icu_provider_macros" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ec89e9337638ecdc08744df490b221a7399bf8d164eb52a665454e60e075ad6" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.93", +] + +[[package]] +name = "idna" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "686f825264d630750a544639377bae737628043f20d38bbc029e8f29ea968a7e" +dependencies = [ + "idna_adapter", + "smallvec", + "utf8_iter", +] + +[[package]] +name = "idna_adapter" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "daca1df1c957320b2cf139ac61e7bd64fed304c5040df000a745aa1de3b4ef71" +dependencies = [ + "icu_normalizer", + "icu_properties", ] [[package]] name = "indexmap" -version = "2.2.6" +version = "2.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "168fb715dda47215e360912c096649d23d58bf392ac62f73919e831745e40f26" +checksum = "62f822373a4fe84d4bb149bf54e584a7f4abec90e072ed49cda0edea5b95471f" dependencies = [ "equivalent", - "hashbrown", + "hashbrown 0.15.2", ] [[package]] name = "ipnet" -version = "2.9.0" +version = "2.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f518f335dce6725a761382244631d86cf0ccb2863413590b31338feb467f9c3" +checksum = "ddc24109865250148c2e0f3d25d4f0f479571723792d3802153c60922a4fb708" [[package]] name = "itoa" -version = "1.0.11" +version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" +checksum = "d75a2a4b1b190afb6f5425f10f6a8f959d2ea0b9c2b1d79553551850539e4674" [[package]] name = "js-sys" -version = "0.3.69" +version = "0.3.76" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "29c15563dc2726973df627357ce0c9ddddbea194836909d655df6a75d2cf296d" +checksum = "6717b6b5b077764fb5966237269cb3c64edddde4b14ce42647430a78ced9e7b7" dependencies = [ + "once_cell", "wasm-bindgen", ] @@ -691,18 +875,18 @@ checksum = "db13adb97ab515a3691f56e4dbab09283d0b86cb45abd991d8634a9d6f501760" [[package]] name = "libc" -version = "0.2.155" +version = "0.2.169" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97b3888a4aecf77e811145cadf6eef5901f4782c53886191b2f693f24761847c" +checksum = "b5aba8db14291edd000dfcc4d620c7ebfb122c613afb886ca8803fa4e128a20a" [[package]] name = "libloading" -version = "0.8.5" +version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4979f22fdb869068da03c9f7528f8297c6fd2606bc3a4affe42e6a823fdb8da4" +checksum = "fc2f4eb4bc735547cfed7c0a4922cbd04a4655978c09b54f1f7b228750664c34" dependencies = [ "cfg-if", - "windows-targets 0.52.5", + "windows-targets 0.52.6", ] [[package]] @@ -711,6 +895,12 @@ version = "0.4.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89" +[[package]] +name = "litemap" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ee93343901ab17bd981295f2cf0026d4ad018c7c31ba84549a4ddbb47a45104" + [[package]] name = "lock_api" version = "0.4.12" @@ -723,24 +913,24 @@ dependencies = [ [[package]] name = "log" -version = "0.4.21" +version = "0.4.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90ed8c1e510134f979dbc4f070f87d4313098b704861a105fe34231c70a3901c" +checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" [[package]] name = "luau0-src" -version = "0.10.2+luau635" +version = "0.11.2+luau653" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8bd6bc70c84fdd4e89b71b528f7ab7db7b97adf759faa8dbe15c5011468e290" +checksum = "02313a53daf1fae25e82f7e7ca56180b72d1f08c514426672877cd957298201c" dependencies = [ "cc", ] [[package]] name = "memchr" -version = "2.7.2" +version = "2.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c8640c5d730cb13ebd907d8d04b52f55ac9a2eec55b440c8892f40d56c76c1d" +checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" [[package]] name = "mime" @@ -750,9 +940,9 @@ checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" [[package]] name = "mime_guess" -version = "2.0.4" +version = "2.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4192263c238a5f0d0c6bfd21f336a313a4ce1c450542449ca191bb657b4642ef" +checksum = "f7c44f8e672c00fe5308fa235f821cb4198414e1c77935c1ab6948d3fd78550e" dependencies = [ "mime", "unicase", @@ -775,45 +965,45 @@ dependencies = [ [[package]] name = "miniz_oxide" -version = "0.7.3" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87dfd01fe195c66b572b37921ad8803d010623c0aca821bea2302239d155cdae" +checksum = "4ffbe83022cedc1d264172192511ae958937694cd57ce297164951b8b3568394" dependencies = [ - "adler", + "adler2", ] [[package]] name = "mio" -version = "1.0.1" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4569e456d394deccd22ce1c1913e6ea0e54519f577285001215d33557431afe4" +checksum = "2886843bf800fba2e3377cff24abf6379b4c4d5c6681eaf9ea5b0d15090450bd" dependencies = [ - "hermit-abi", "libc", - "wasi", + "wasi 0.11.0+wasi-snapshot-preview1", "windows-sys 0.52.0", ] [[package]] name = "mlua" -version = "0.9.9" +version = "0.10.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d111deb18a9c9bd33e1541309f4742523bfab01d276bfa9a27519f6de9c11dc7" +checksum = "d3f763c1041eff92ffb5d7169968a327e1ed2ebfe425dac0ee5a35f29082534b" dependencies = [ "bstr", + "either", "futures-util", "libloading", "mlua-sys", "num-traits", - "once_cell", + "parking_lot", "rustc-hash", ] [[package]] name = "mlua-sys" -version = "0.6.2" +version = "0.6.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ab7a5b4756b8177a2dfa8e0bbcde63bd4000afbc4ab20cbb68d114a25470f29" +checksum = "63a11d485edf0f3f04a508615d36c7d50d299cf61a7ee6d3e2530651e0a31771" dependencies = [ "cc", "cfg-if", @@ -821,13 +1011,26 @@ dependencies = [ "pkg-config", ] +[[package]] +name = "nix" +version = "0.29.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "71e2746dc3a24dd78b3cfcb7be93368c6de9963d30f43a6a73998a9cf4b17b46" +dependencies = [ + "bitflags 2.6.0", + "cfg-if", + "cfg_aliases", + "libc", +] + [[package]] name = "nopalmo" version = "0.1.0" dependencies = [ + "ctrlc", "mlua", "num_cpus", - "rand", + "rand 0.9.0", "regex", "serenity", "sqlite", @@ -873,18 +1076,18 @@ dependencies = [ [[package]] name = "object" -version = "0.32.2" +version = "0.36.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a6a622008b6e321afc04970976f62ee297fdbaa6f95318ca343e3eebb9648441" +checksum = "62948e14d923ea95ea2c7c86c71013138b66525b86bdc08d2dcc262bdb497b87" dependencies = [ "memchr", ] [[package]] name = "once_cell" -version = "1.19.0" +version = "1.20.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" +checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775" [[package]] name = "overload" @@ -894,9 +1097,9 @@ checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" [[package]] name = "parking_lot" -version = "0.12.2" +version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e4af0ca4f6caed20e900d564c242b8e5d4903fdacf31d3daf527b66fe6f42fb" +checksum = "f1bf18183cf54e8d6059647fc3063646a1801cf30896933ec2311622cc4b9a27" dependencies = [ "lock_api", "parking_lot_core", @@ -912,7 +1115,7 @@ dependencies = [ "libc", "redox_syscall", "smallvec", - "windows-targets 0.52.5", + "windows-targets 0.52.6", ] [[package]] @@ -923,9 +1126,9 @@ checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" [[package]] name = "pin-project-lite" -version = "0.2.14" +version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bda66fc9667c18cb2758a2ac84d1167245054bcf85d5d1aaa6923f45801bdd02" +checksum = "915a1e146535de9163f3987b8944ed8cf49a18bb0056bcebcdcece385cece4ff" [[package]] name = "pin-utils" @@ -935,9 +1138,9 @@ checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" [[package]] name = "pkg-config" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d231b230927b5e4ad203db57bbcbee2802f6bce620b1e4a9024a07d94e2907ec" +checksum = "953ec861398dccce10c670dfeaf3ec4911ca479e9c02154b3a215178c5f566f2" [[package]] name = "powerfmt" @@ -947,15 +1150,18 @@ checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" [[package]] name = "ppv-lite86" -version = "0.2.17" +version = "0.2.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" +checksum = "77957b295656769bb8ad2b6a6b09d897d94f05c41b069aede1fcdaa675eaea04" +dependencies = [ + "zerocopy 0.7.35", +] [[package]] name = "proc-macro2" -version = "1.0.83" +version = "1.0.92" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b33eb56c327dec362a9e55b3ad14f9d2f0904fb5a5b03b513ab5465399e9f43" +checksum = "37d3544b3f2748c54e147655edb5025752e2303145b5aefb3c3ea2c78b973bb0" dependencies = [ "unicode-ident", ] @@ -966,16 +1172,16 @@ version = "0.9.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "57206b407293d2bcd3af849ce869d52068623f19e1b5ff8e8778e3309439682b" dependencies = [ - "bitflags 2.5.0", + "bitflags 2.6.0", "memchr", "unicase", ] [[package]] name = "quote" -version = "1.0.36" +version = "1.0.38" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7" +checksum = "0e4dccaaaf89514f546c693ddc140f729f958c247918a13380cccc6078391acc" dependencies = [ "proc-macro2", ] @@ -987,8 +1193,19 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" dependencies = [ "libc", - "rand_chacha", - "rand_core", + "rand_chacha 0.3.1", + "rand_core 0.6.4", +] + +[[package]] +name = "rand" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3779b94aeb87e8bd4e834cee3650289ee9e0d5677f976ecdb6d219e5f4f6cd94" +dependencies = [ + "rand_chacha 0.9.0", + "rand_core 0.9.0", + "zerocopy 0.8.17", ] [[package]] @@ -998,7 +1215,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" dependencies = [ "ppv-lite86", - "rand_core", + "rand_core 0.6.4", +] + +[[package]] +name = "rand_chacha" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" +dependencies = [ + "ppv-lite86", + "rand_core 0.9.0", ] [[package]] @@ -1007,23 +1234,33 @@ version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" dependencies = [ - "getrandom", + "getrandom 0.2.15", +] + +[[package]] +name = "rand_core" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b08f3c9802962f7e1b25113931d94f43ed9725bebc59db9d0c3e9a23b67e15ff" +dependencies = [ + "getrandom 0.3.1", + "zerocopy 0.8.17", ] [[package]] name = "redox_syscall" -version = "0.5.1" +version = "0.5.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "469052894dcb553421e483e4209ee581a45100d31b4018de03e5a7ad86374a7e" +checksum = "03a862b389f93e68874fbf580b9de08dd02facb9a788ebadaf4a3fd33cf58834" dependencies = [ - "bitflags 2.5.0", + "bitflags 2.6.0", ] [[package]] name = "regex" -version = "1.10.4" +version = "1.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c117dbdfde9c8308975b6a18d71f3f385c89461f7b3fb054288ecf2a2058ba4c" +checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191" dependencies = [ "aho-corasick", "memchr", @@ -1033,9 +1270,9 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.4.6" +version = "0.4.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "86b83b8b9847f9bf95ef68afb0b8e6cdb80f498442f5179a29fad448fcc1eaea" +checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908" dependencies = [ "aho-corasick", "memchr", @@ -1044,9 +1281,9 @@ dependencies = [ [[package]] name = "regex-syntax" -version = "0.8.3" +version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "adad44e29e4c806119491a7f06f03de4d1af22c3a680dd47f1e6e179439d1f56" +checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" [[package]] name = "reqwest" @@ -1100,7 +1337,7 @@ checksum = "c17fa4cb658e3583423e915b9f3acc01cceaee1860e33d59ebae66adc3a2dc0d" dependencies = [ "cc", "cfg-if", - "getrandom", + "getrandom 0.2.15", "libc", "spin", "untrusted", @@ -1115,21 +1352,21 @@ checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" [[package]] name = "rustc-hash" -version = "2.0.0" +version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "583034fd73374156e66797ed8e5b0d5690409c9226b22d87cb7f19821c05d152" +checksum = "c7fb8039b3032c191086b10f11f319a6e99e1e82889c5cc6046f515c9db1d497" [[package]] name = "rustix" -version = "0.38.34" +version = "0.38.42" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70dc5ec042f7a43c4a73241207cecc9873a06d45debb38b329f8541d85c2730f" +checksum = "f93dc38ecbab2eb790ff964bb77fa94faf256fd3e73285fd7ba0903b76bedb85" dependencies = [ - "bitflags 2.5.0", + "bitflags 2.6.0", "errno", "libc", "linux-raw-sys", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -1153,7 +1390,7 @@ dependencies = [ "log", "ring", "rustls-pki-types", - "rustls-webpki 0.102.4", + "rustls-webpki 0.102.8", "subtle", "zeroize", ] @@ -1169,9 +1406,9 @@ dependencies = [ [[package]] name = "rustls-pki-types" -version = "1.7.0" +version = "1.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "976295e77ce332211c0d24d92c0e83e50f5c5f046d11082cea19f3df13a3562d" +checksum = "d2bf47e6ff922db3825eb750c4e2ff784c6ff8fb9e13046ef6a1d1c5401b0b37" [[package]] name = "rustls-webpki" @@ -1185,9 +1422,9 @@ dependencies = [ [[package]] name = "rustls-webpki" -version = "0.102.4" +version = "0.102.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff448f7e92e913c4b7d4c6d8e4540a1724b319b4152b8aef6d4cf8339712b33e" +checksum = "64ca1bc8749bd4cf37b5ce386cc146580777b4e8572c7b97baf22c83f444bee9" dependencies = [ "ring", "rustls-pki-types", @@ -1237,18 +1474,18 @@ dependencies = [ [[package]] name = "semver" -version = "1.0.23" +version = "1.0.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61697e0a1c7e512e84a621326239844a24d8207b4669b41bc18b32ea5cbf988b" +checksum = "3cb6eb87a131f756572d7fb904f6e7b68633f09cca868c5df1c4b8d1a694bbba" dependencies = [ "serde", ] [[package]] name = "serde" -version = "1.0.202" +version = "1.0.217" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "226b61a0d411b2ba5ff6d7f73a476ac4f8bb900373459cd00fab8512828ba395" +checksum = "02fc4265df13d6fa1d00ecff087228cc0a2b5f3c0e87e258d8b94a156e984c70" dependencies = [ "serde_derive", ] @@ -1264,22 +1501,23 @@ dependencies = [ [[package]] name = "serde_derive" -version = "1.0.202" +version = "1.0.217" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6048858004bcff69094cd972ed40a32500f153bd3be9f716b2eed2e8217c4838" +checksum = "5a9bf7cf98d04a2b28aead066b7496853d4779c9cc183c440dbac457641e19a0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.65", + "syn 2.0.93", ] [[package]] name = "serde_json" -version = "1.0.117" +version = "1.0.134" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "455182ea6142b14f93f4bc5320a2b31c1f266b66a4a5c858b013302a5d8cbfc3" +checksum = "d00f4175c42ee48b15416f6193a959ba3a0d67fc699a0db9ad12df9f83991c7d" dependencies = [ "itoa", + "memchr", "ryu", "serde", ] @@ -1298,14 +1536,14 @@ dependencies = [ [[package]] name = "serenity" -version = "0.12.2" +version = "0.12.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "880a04106592d0a8f5bdacb1d935889bfbccb4a14f7074984d9cd857235d34ac" +checksum = "3d72ec4323681bf9a3cabe40fd080abc2435859b502a1b5aa9bf693f125bfa76" dependencies = [ "arrayvec", "async-trait", "base64 0.22.1", - "bitflags 2.5.0", + "bitflags 2.6.0", "bytes", "chrono", "command_attr", @@ -1353,6 +1591,12 @@ dependencies = [ "lazy_static", ] +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + [[package]] name = "signal-hook-registry" version = "1.4.2" @@ -1394,9 +1638,9 @@ checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" [[package]] name = "socket2" -version = "0.5.7" +version = "0.5.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce305eb0b4296696835b71df73eb912e0f1ffd2556a501fcede6e0c50349191c" +checksum = "c970269d99b64e60ec3bd6ad27270092a5394c4e309314b18ae3fe575695fbe8" dependencies = [ "libc", "windows-sys 0.52.0", @@ -1436,6 +1680,12 @@ dependencies = [ "sqlite3-src", ] +[[package]] +name = "stable_deref_trait" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" + [[package]] name = "static_assertions" version = "1.1.0" @@ -1444,9 +1694,9 @@ checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" [[package]] name = "subtle" -version = "2.5.0" +version = "2.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81cdd64d312baedb58e21336b31bc043b77e01cc99033ce76ef539f78e965ebc" +checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" [[package]] name = "syn" @@ -1461,9 +1711,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.65" +version = "2.0.93" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2863d96a84c6439701d7a38f9de935ec562c8832cc55d1dde0f513b52fad106" +checksum = "9c786062daee0d6db1132800e623df74274a0a87322d8e183338e01b3d98d058" dependencies = [ "proc-macro2", "quote", @@ -1476,6 +1726,17 @@ version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2047c6ded9c721764247e62cd3b03c09ffc529b2ba5b10ec482ae507a4a70160" +[[package]] +name = "synstructure" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8af7666ab7b6390ab78131fb5b0fce11d6b7a6951602017c35fa82800708971" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.93", +] + [[package]] name = "system-configuration" version = "0.5.1" @@ -1505,34 +1766,35 @@ checksum = "7b2093cf4c8eb1e67749a6762251bc9cd836b6fc171623bd0a9d324d37af2417" [[package]] name = "tempfile" -version = "3.10.1" +version = "3.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85b77fafb263dd9d05cbeac119526425676db3784113aa9295c88498cbf8bff1" +checksum = "28cce251fcbc87fac86a866eeb0d6c2d536fc16d06f184bb61aeae11aa4cee0c" dependencies = [ "cfg-if", "fastrand", + "once_cell", "rustix", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] name = "thiserror" -version = "1.0.63" +version = "1.0.69" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0342370b38b6a11b6cc11d6a805569958d54cfa061a29969c3b5ce2ea405724" +checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.63" +version = "1.0.69" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4558b58466b9ad7ca0f102865eccc95938dca1a74a856f2b57b6629050da261" +checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" dependencies = [ "proc-macro2", "quote", - "syn 2.0.65", + "syn 2.0.93", ] [[package]] @@ -1547,9 +1809,9 @@ dependencies = [ [[package]] name = "time" -version = "0.3.36" +version = "0.3.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5dfd88e563464686c916c7e46e623e520ddc6d79fa6641390f2e3fa86e83e885" +checksum = "35e7868883861bd0e56d9ac6efcaaca0d6d5d82a2a7ec8209ff492c07cf37b21" dependencies = [ "deranged", "itoa", @@ -1568,34 +1830,29 @@ checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3" [[package]] name = "time-macros" -version = "0.2.18" +version = "0.2.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f252a68540fde3a3877aeea552b832b40ab9a69e318efd078774a01ddee1ccf" +checksum = "2834e6017e3e5e4b9834939793b282bc03b37a3336245fa820e35e233e2a85de" dependencies = [ "num-conv", "time-core", ] [[package]] -name = "tinyvec" -version = "1.6.0" +name = "tinystr" +version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50" +checksum = "9117f5d4db391c1cf6927e7bea3db74b9a1c1add8f7eda9ffd5364f40f57b82f" dependencies = [ - "tinyvec_macros", + "displaydoc", + "zerovec", ] -[[package]] -name = "tinyvec_macros" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" - [[package]] name = "tokio" -version = "1.39.2" +version = "1.42.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "daa4fb1bc778bd6f04cbfc4bb2d06a7396a8f299dc33ea1900cedaa316f467b1" +checksum = "5cec9b21b0450273377fc97bd4c33a8acffc8c996c987a7c5b319a0083707551" dependencies = [ "backtrace", "bytes", @@ -1617,7 +1874,7 @@ checksum = "693d596312e88961bc67d7f1f97af8a70227d9f90c31bba5806eec004978d752" dependencies = [ "proc-macro2", "quote", - "syn 2.0.65", + "syn 2.0.93", ] [[package]] @@ -1654,14 +1911,14 @@ dependencies = [ "tokio", "tokio-rustls 0.25.0", "tungstenite", - "webpki-roots 0.26.1", + "webpki-roots 0.26.7", ] [[package]] name = "tokio-util" -version = "0.7.11" +version = "0.7.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9cf6b47b3771c49ac75ad09a6162f53ad4b8088b76ac60e8ec1455b31a189fe1" +checksum = "d7fcaa8d55a2bdd6b83ace262b016eca0d79ee02818c5c1bcdf0305114081078" dependencies = [ "bytes", "futures-core", @@ -1672,15 +1929,15 @@ dependencies = [ [[package]] name = "tower-service" -version = "0.3.2" +version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6bc1c9ce2b5135ac7f93c72918fc37feb872bdc6a5533a8b85eb4b86bfdae52" +checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" [[package]] name = "tracing" -version = "0.1.40" +version = "0.1.41" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef" +checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0" dependencies = [ "log", "pin-project-lite", @@ -1690,20 +1947,20 @@ dependencies = [ [[package]] name = "tracing-attributes" -version = "0.1.27" +version = "0.1.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" +checksum = "395ae124c09f9e6918a2310af6038fba074bcf474ac352496d5910dd59a2226d" dependencies = [ "proc-macro2", "quote", - "syn 2.0.65", + "syn 2.0.93", ] [[package]] name = "tracing-core" -version = "0.1.32" +version = "0.1.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54" +checksum = "e672c95779cf947c5311f83787af4fa8fffd12fb27e4993211a84bdfd9610f9c" dependencies = [ "once_cell", "valuable", @@ -1722,9 +1979,9 @@ dependencies = [ [[package]] name = "tracing-subscriber" -version = "0.3.18" +version = "0.3.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ad0f048c97dbd9faa9b7df56362b8ebcaa52adb06b498c050d2f4e32f90a7a8b" +checksum = "e8189decb5ac0fa7bc8b96b7cb9b2701d60d48805aca84a238004d665fcc4008" dependencies = [ "chrono", "nu-ansi-term", @@ -1737,9 +1994,9 @@ dependencies = [ [[package]] name = "triomphe" -version = "0.1.11" +version = "0.1.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "859eb650cfee7434994602c3a68b25d77ad9e68c8a6cd491616ef86661382eb3" +checksum = "ef8f7726da4807b58ea5c96fdc122f80702030edc33b35aff9190a51148ccc85" [[package]] name = "try-lock" @@ -1756,10 +2013,10 @@ dependencies = [ "byteorder", "bytes", "data-encoding", - "http 1.1.0", + "http 1.2.0", "httparse", "log", - "rand", + "rand 0.8.5", "rustls 0.22.4", "rustls-pki-types", "sha1", @@ -1782,13 +2039,13 @@ checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" [[package]] name = "typesize" -version = "0.1.7" +version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eb704842c709bc76f63e99e704cb208beeccca2abbabd0d9aec02e48ca1cee0f" +checksum = "549e54551d85ba6718a95333d9bc4367f69793d7aba638de30f8d25a1f554a1d" dependencies = [ "chrono", "dashmap", - "hashbrown", + "hashbrown 0.14.5", "mini-moka", "parking_lot", "secrecy", @@ -1800,44 +2057,26 @@ dependencies = [ [[package]] name = "typesize-derive" -version = "0.1.7" +version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "905e88c2a4cc27686bd57e495121d451f027e441388a67f773be729ad4be1ea8" +checksum = "fd9fc0ad9e03a2b0c2e2a0eafaecccef2121829e1ab6ce9c9d790e6c6766bd1c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.65", + "syn 2.0.93", ] [[package]] name = "unicase" -version = "2.7.0" +version = "2.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f7d2d4dafb69621809a81864c9c1b864479e1235c0dd4e199924b9742439ed89" -dependencies = [ - "version_check", -] - -[[package]] -name = "unicode-bidi" -version = "0.3.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08f95100a766bf4f8f28f90d77e0a5461bbdb219042e7679bebe79004fed8d75" +checksum = "75b844d17643ee918803943289730bec8aac480150456169e647ed0b576ba539" [[package]] name = "unicode-ident" -version = "1.0.12" +version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" - -[[package]] -name = "unicode-normalization" -version = "0.1.23" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a56d1686db2308d901306f92a263857ef59ea39678a5458e7cb17f01415101f5" -dependencies = [ - "tinyvec", -] +checksum = "adb9e6ca4f869e1180728b7950e35922a7fc6397f7b641499e8f3ef06e50dc83" [[package]] name = "untrusted" @@ -1847,9 +2086,9 @@ checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" [[package]] name = "url" -version = "2.5.0" +version = "2.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "31e6302e3bb753d46e83516cae55ae196fc0c309407cf11ab35cc51a4c2a4633" +checksum = "32f8b686cadd1473f4bd0117a5d28d36b1ade384ea9b5069a1c40aefed7fda60" dependencies = [ "form_urlencoded", "idna", @@ -1863,6 +2102,18 @@ version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9" +[[package]] +name = "utf16_iter" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8232dd3cdaed5356e0f716d285e4b40b932ac434100fe9b7e0e8e935b9e6246" + +[[package]] +name = "utf8_iter" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" + [[package]] name = "uwl" version = "0.6.0" @@ -1877,9 +2128,9 @@ checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" [[package]] name = "version_check" -version = "0.9.4" +version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" [[package]] name = "walkdir" @@ -1907,47 +2158,57 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] -name = "wasm-bindgen" -version = "0.2.92" +name = "wasi" +version = "0.13.3+wasi-0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4be2531df63900aeb2bca0daaaddec08491ee64ceecbee5076636a3b026795a8" +checksum = "26816d2e1a4a36a2940b96c5296ce403917633dff8f3440e9b236ed6f6bacad2" +dependencies = [ + "wit-bindgen-rt", +] + +[[package]] +name = "wasm-bindgen" +version = "0.2.99" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a474f6281d1d70c17ae7aa6a613c87fce69a127e2624002df63dcb39d6cf6396" dependencies = [ "cfg-if", + "once_cell", "wasm-bindgen-macro", ] [[package]] name = "wasm-bindgen-backend" -version = "0.2.92" +version = "0.2.99" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "614d787b966d3989fa7bb98a654e369c762374fd3213d212cfc0251257e747da" +checksum = "5f89bb38646b4f81674e8f5c3fb81b562be1fd936d84320f3264486418519c79" dependencies = [ "bumpalo", "log", - "once_cell", "proc-macro2", "quote", - "syn 2.0.65", + "syn 2.0.93", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-futures" -version = "0.4.42" +version = "0.4.49" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76bc14366121efc8dbb487ab05bcc9d346b3b5ec0eaa76e46594cabbe51762c0" +checksum = "38176d9b44ea84e9184eff0bc34cc167ed044f816accfe5922e54d84cf48eca2" dependencies = [ "cfg-if", "js-sys", + "once_cell", "wasm-bindgen", "web-sys", ] [[package]] name = "wasm-bindgen-macro" -version = "0.2.92" +version = "0.2.99" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1f8823de937b71b9460c0c34e25f3da88250760bec0ebac694b49997550d726" +checksum = "2cc6181fd9a7492eef6fef1f33961e3695e4579b9872a6f7c83aee556666d4fe" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -1955,28 +2216,28 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.92" +version = "0.2.99" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e94f17b526d0a461a191c78ea52bbce64071ed5c04c9ffe424dcb38f74171bb7" +checksum = "30d7a95b763d3c45903ed6c81f156801839e5ee968bb07e534c44df0fcd330c2" dependencies = [ "proc-macro2", "quote", - "syn 2.0.65", + "syn 2.0.93", "wasm-bindgen-backend", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" -version = "0.2.92" +version = "0.2.99" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af190c94f2773fdb3729c55b007a722abb5384da03bc0986df4c289bf5567e96" +checksum = "943aab3fdaaa029a6e0271b35ea10b72b943135afe9bffca82384098ad0e06a6" [[package]] name = "wasm-streams" -version = "0.4.0" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b65dc4c90b63b118468cf747d8bf3566c1913ef60be765b5730ead9e0a3ba129" +checksum = "15053d8d85c7eccdbefef60f06769760a563c7f0a9d6902a13d35c7800b0ad65" dependencies = [ "futures-util", "js-sys", @@ -1987,9 +2248,9 @@ dependencies = [ [[package]] name = "web-sys" -version = "0.3.69" +version = "0.3.76" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77afa9a11836342370f4817622a2f0f418b134426d91a82dfb48f532d2ec13ef" +checksum = "04dd7223427d52553d3702c004d3b2fe07c148165faa56313cb00211e31c12bc" dependencies = [ "js-sys", "wasm-bindgen", @@ -2003,9 +2264,9 @@ checksum = "5f20c57d8d7db6d3b86154206ae5d8fba62dd39573114de97c2cb0578251f8e1" [[package]] name = "webpki-roots" -version = "0.26.1" +version = "0.26.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b3de34ae270483955a94f4b21bdaaeb83d508bb84a01435f393818edb0012009" +checksum = "5d642ff16b7e79272ae451b7322067cdc17cadf68c23264be9d94a32319efe7e" dependencies = [ "rustls-pki-types", ] @@ -2028,11 +2289,11 @@ checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" [[package]] name = "winapi-util" -version = "0.1.8" +version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4d4cc384e1e73b93bafa6fb4f1df8c41695c8a91cf9c4c64358067d15a7b6c6b" +checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" dependencies = [ - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -2047,7 +2308,7 @@ version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9" dependencies = [ - "windows-targets 0.52.5", + "windows-targets 0.52.6", ] [[package]] @@ -2065,7 +2326,16 @@ version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" dependencies = [ - "windows-targets 0.52.5", + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-sys" +version = "0.59.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" +dependencies = [ + "windows-targets 0.52.6", ] [[package]] @@ -2085,18 +2355,18 @@ dependencies = [ [[package]] name = "windows-targets" -version = "0.52.5" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f0713a46559409d202e70e28227288446bf7841d3211583a4b53e3f6d96e7eb" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" dependencies = [ - "windows_aarch64_gnullvm 0.52.5", - "windows_aarch64_msvc 0.52.5", - "windows_i686_gnu 0.52.5", + "windows_aarch64_gnullvm 0.52.6", + "windows_aarch64_msvc 0.52.6", + "windows_i686_gnu 0.52.6", "windows_i686_gnullvm", - "windows_i686_msvc 0.52.5", - "windows_x86_64_gnu 0.52.5", - "windows_x86_64_gnullvm 0.52.5", - "windows_x86_64_msvc 0.52.5", + "windows_i686_msvc 0.52.6", + "windows_x86_64_gnu 0.52.6", + "windows_x86_64_gnullvm 0.52.6", + "windows_x86_64_msvc 0.52.6", ] [[package]] @@ -2107,9 +2377,9 @@ checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" [[package]] name = "windows_aarch64_gnullvm" -version = "0.52.5" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7088eed71e8b8dda258ecc8bac5fb1153c5cffaf2578fc8ff5d61e23578d3263" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" [[package]] name = "windows_aarch64_msvc" @@ -2119,9 +2389,9 @@ checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" [[package]] name = "windows_aarch64_msvc" -version = "0.52.5" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9985fd1504e250c615ca5f281c3f7a6da76213ebd5ccc9561496568a2752afb6" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" [[package]] name = "windows_i686_gnu" @@ -2131,15 +2401,15 @@ checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" [[package]] name = "windows_i686_gnu" -version = "0.52.5" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "88ba073cf16d5372720ec942a8ccbf61626074c6d4dd2e745299726ce8b89670" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" [[package]] name = "windows_i686_gnullvm" -version = "0.52.5" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87f4261229030a858f36b459e748ae97545d6f1ec60e5e0d6a3d32e0dc232ee9" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" [[package]] name = "windows_i686_msvc" @@ -2149,9 +2419,9 @@ checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" [[package]] name = "windows_i686_msvc" -version = "0.52.5" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db3c2bf3d13d5b658be73463284eaf12830ac9a26a90c717b7f771dfe97487bf" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" [[package]] name = "windows_x86_64_gnu" @@ -2161,9 +2431,9 @@ checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" [[package]] name = "windows_x86_64_gnu" -version = "0.52.5" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4e4246f76bdeff09eb48875a0fd3e2af6aada79d409d33011886d3e1581517d9" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" [[package]] name = "windows_x86_64_gnullvm" @@ -2173,9 +2443,9 @@ checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" [[package]] name = "windows_x86_64_gnullvm" -version = "0.52.5" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "852298e482cd67c356ddd9570386e2862b5673c85bd5f88df9ab6802b334c596" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" [[package]] name = "windows_x86_64_msvc" @@ -2185,9 +2455,9 @@ checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" [[package]] name = "windows_x86_64_msvc" -version = "0.52.5" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bec47e5bfd1bff0eeaf6d8b485cc1074891a197ab4225d504cb7a1ab88b02bf0" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" [[package]] name = "winreg" @@ -2200,7 +2470,136 @@ dependencies = [ ] [[package]] -name = "zeroize" -version = "1.7.0" +name = "wit-bindgen-rt" +version = "0.33.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "525b4ec142c6b68a2d10f01f7bbf6755599ca3f81ea53b8431b7dd348f5fdb2d" +checksum = "3268f3d866458b787f390cf61f4bbb563b922d091359f9608842999eaee3943c" +dependencies = [ + "bitflags 2.6.0", +] + +[[package]] +name = "write16" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d1890f4022759daae28ed4fe62859b1236caebfc61ede2f63ed4e695f3f6d936" + +[[package]] +name = "writeable" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e9df38ee2d2c3c5948ea468a8406ff0db0b29ae1ffde1bcf20ef305bcc95c51" + +[[package]] +name = "yoke" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "120e6aef9aa629e3d4f52dc8cc43a015c7724194c97dfaf45180d2daf2b77f40" +dependencies = [ + "serde", + "stable_deref_trait", + "yoke-derive", + "zerofrom", +] + +[[package]] +name = "yoke-derive" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2380878cad4ac9aac1e2435f3eb4020e8374b5f13c296cb75b4620ff8e229154" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.93", + "synstructure", +] + +[[package]] +name = "zerocopy" +version = "0.7.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0" +dependencies = [ + "byteorder", + "zerocopy-derive 0.7.35", +] + +[[package]] +name = "zerocopy" +version = "0.8.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa91407dacce3a68c56de03abe2760159582b846c6a4acd2f456618087f12713" +dependencies = [ + "zerocopy-derive 0.8.17", +] + +[[package]] +name = "zerocopy-derive" +version = "0.7.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.93", +] + +[[package]] +name = "zerocopy-derive" +version = "0.8.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06718a168365cad3d5ff0bb133aad346959a2074bd4a85c121255a11304a8626" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.93", +] + +[[package]] +name = "zerofrom" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cff3ee08c995dee1859d998dea82f7374f2826091dd9cd47def953cae446cd2e" +dependencies = [ + "zerofrom-derive", +] + +[[package]] +name = "zerofrom-derive" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "595eed982f7d355beb85837f651fa22e90b3c044842dc7f2c2842c086f295808" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.93", + "synstructure", +] + +[[package]] +name = "zeroize" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde" + +[[package]] +name = "zerovec" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa2b893d79df23bfb12d5461018d408ea19dfafe76c2c7ef6d4eba614f8ff079" +dependencies = [ + "yoke", + "zerofrom", + "zerovec-derive", +] + +[[package]] +name = "zerovec-derive" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6eafa6dfb17584ea3e2bd6e76e0cc15ad7af12b09abdd1ca55961bed9b1063c6" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.93", +] diff --git a/Cargo.toml b/Cargo.toml index c6744c0..d9f4ea2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,13 +8,14 @@ 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" +mlua = { version = "0.10.3", features = ["async", "luau"] } +serenity = "0.12.4" num_cpus = "1.16.0" -rand = "0.8.5" +rand = "0.9.0" regex = "1.10.4" sqlite = { version = "0.36.1", features = ["bundled"] } +ctrlc = "3.4.5" [features] diff --git a/lua/help.lua b/lua/help.lua index e740fba..b09ff4d 100644 --- a/lua/help.lua +++ b/lua/help.lua @@ -1,18 +1,18 @@ -COMMAND["help"] = { +Metadata = { + name = "help", -- the main required string used to call the command 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:", + + command_category = commandCategory.info, -- this can be any string, but the bot provides a few categories already 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() \ No newline at end of file + +function Command(context, message, guildid) + for t in COMMAND do + print("Help info for command " + t) + end +end diff --git a/rust-toolchain.toml b/rust-toolchain.toml new file mode 100644 index 0000000..31578d3 --- /dev/null +++ b/rust-toolchain.toml @@ -0,0 +1,2 @@ +[toolchain] +channel = "stable" \ No newline at end of file diff --git a/rustfmt.toml b/rustfmt.toml index 218e203..1abdef1 100644 --- a/rustfmt.toml +++ b/rustfmt.toml @@ -1 +1,33 @@ -hard_tabs = true +# i use tabs so stop bugging me and go elsewhere already + +hard_tabs=true +binop_separator="Back" +condense_wildcard_suffixes = true +empty_item_single_line = false +enum_discrim_align_threshold = 30 +struct_field_align_threshold = 30 +short_array_element_width_threshold = 30 +inline_attribute_width = 50 +fn_params_layout = "Compressed" +fn_single_line = true +format_code_in_doc_comments = true +format_macro_matchers = true +hex_literal_case = "Upper" +imports_indent = "Visual" +imports_layout = "Vertical" +# indent_style = "Visual" +match_arm_blocks = false +match_block_trailing_comma = true +max_width = 160 +imports_granularity = "Item" +newline_style = "Unix" +normalize_doc_attributes = true +overflow_delimited_expr = true +reorder_impl_items = true +# group_imports = "StdExternalCrate" +space_after_colon = false +# trailing_comma = "Always" +type_punctuation_density = "Compressed" +use_field_init_shorthand = true +use_try_shorthand = true +where_single_line = true diff --git a/src/arguments.rs b/src/arguments.rs index af1b0a8..eed1204 100644 --- a/src/arguments.rs +++ b/src/arguments.rs @@ -1,14 +1,13 @@ -use std::{collections::HashMap, sync::Arc}; +use std::collections::HashMap; #[derive(Debug, Clone, PartialEq, Default)] pub struct ArgumentStorage(HashMap); impl ArgumentStorage { - pub fn new() -> Self { - Self::default() - } + pub fn new() -> Self { Self::default() } - pub fn add_argument() {} + pub fn add_argument() { + } } // #[derive(Debug, Clone, PartialEq, Default)] @@ -84,4 +83,3 @@ impl ArgumentStorage { // Value(String), // Argument(usize), // } - diff --git a/src/bot.rs b/src/bot.rs index e7e22c0..1558cd3 100644 --- a/src/bot.rs +++ b/src/bot.rs @@ -1,18 +1,34 @@ -use std::sync::atomic::{AtomicBool, Ordering}; -use std::sync::{Arc, Condvar, OnceLock}; +use std::sync::atomic::AtomicBool; +use std::sync::Arc; +use std::sync::OnceLock; -use crate::commands::{self, COMMANDS}; -use crate::{constants, tasks, CARG_NO_DISCORD}; -use serenity::all::{ActivityData, Context, GuildId}; -use serenity::all::{EventHandler, GatewayIntents, ShardManager}; +use serenity::all::ActivityData; +use serenity::all::Context; +use serenity::all::EventHandler; +use serenity::all::GatewayIntents; +use serenity::all::GuildId; +use serenity::all::ShardManager; use serenity::model::channel::Message; use serenity::model::gateway::Ready; use serenity::Client; -use tracing::{error, info, warn}; -pub 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 +use tracing::error; +use tracing::info; +use tracing::warn; +use crate::commands::COMMANDS; +use crate::commands::{self}; +use crate::constants; +use crate::tasks; +use crate::CMD_ARGS; + +pub static DO_SHUTDOWN: AtomicBool = AtomicBool::new(false); + +// TODO remove from static memory pub static SHARD_MANAGER: OnceLock> = OnceLock::new(); +// cant remember why this was here, it isnt used anywhere else so ill just remove it for now +//pub static BOT_STARTED: AtomicBool = AtomicBool::new(false); + pub struct BotHandler; unsafe impl Sync for BotHandler {} @@ -26,65 +42,59 @@ impl EventHandler for BotHandler { return; } + // TODO remove regular expressions let prefix_regex = format!( r"^({}|{}|<@{}>)\s?", constants::COMMAND_PREFIX, - "NPO_PFX", + constants::CONSTANT_PREFIX, ctx.cache.current_user().id ); - let cmd_regex = format!(r"{}[A-Za-z0-9_\-]+", prefix_regex); + let cmd_regex = format!(r"{prefix_regex}[A-Za-z0-9_\-]+"); - 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 + let Ok(result) = regex::Regex::new(cmd_regex.as_str()) else { + error!("The following regex function has failed to compile, this should not be possible. `{prefix_regex}`"); + return; }; - if DO_SHUTDOWN.0.load(Ordering::SeqCst) { + let Some(result) = result.find(&msg.content) else { + return; // message not meant for us :( + }; + + if 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.", - ) + .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(); + let Ok(target_cmd_name) = regex::Regex::new(prefix_regex.as_str()) else { + error!("The following regex function has failed to compile, this should not be possible. `{prefix_regex}`"); + return; + }; + let target_cmd_name = dbg!(target_cmd_name.replace(result.as_str(), "").to_string()); - msg.reply(&ctx.http, target_cmd_name.to_string()) - .await - .unwrap(); + let _ = msg.reply(&ctx.http, target_cmd_name.to_string()).await; - if let Some(command) = COMMANDS.read().await.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), - } - } + if let Some(command) = dbg!(COMMANDS.read().await).get(&target_cmd_name) { + match (msg.guild_id, &command.run_guild_command, &command.run_dm_command) { + //(Some(gid), Some(commands::CommandFnKind::Lua(())), _) => todo!(), + (Some(gid), Some(commands::CommandFnKind::Rust(gcmd)), _) => (gcmd)(ctx, msg, Some(gid)), + //(None, _, Some(commands::CommandFnKind::Lua(()))) => todo!(), + (None, _, Some(commands::CommandFnKind::Rust(dcmd))) => (dcmd)(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."))) + let Some(shart) = ready.shard else { + error!("Bot ready function called while shards are not ready, this should be impossible."); + return; + }; + info!("Shart `{}` is connected!", shart.id); + ctx.set_activity(Some(ActivityData::custom("Initializing."))); } /// Runs once when all shards are ready @@ -122,18 +132,18 @@ pub async fn start() { | GatewayIntents::GUILD_MESSAGES | GatewayIntents::MESSAGE_CONTENT; - if *CARG_NO_DISCORD.get().unwrap() { + if CMD_ARGS.nodiscord { warn!("ABORTING CONNECTING TO DISCORD, BYE BYE!"); } else { - let mut client = match Client::builder(&token, intents) - .event_handler(BotHandler) - .await - { + let mut client = match Client::builder(token, intents).event_handler(BotHandler).await { Ok(client) => client, Err(err) => panic!("Error starting client connection: `{err}`"), }; - SHARD_MANAGER.set(client.shard_manager.clone()).unwrap(); + // just ignore the response, its impossible to fail here anyway + let _ = SHARD_MANAGER.set(client.shard_manager.clone()); + + //BOT_STARTED.store(true, std::sync::atomic::Ordering::SeqCst); if let Err(why) = client.start_shards(2).await { error!("Client error: {why:?}"); @@ -142,4 +152,3 @@ pub async fn start() { warn!("BOT EXITING"); } - diff --git a/src/commands.rs b/src/commands.rs index 935cdab..0f5a03e 100644 --- a/src/commands.rs +++ b/src/commands.rs @@ -1,19 +1,28 @@ -use std::{collections::HashMap, fmt::Debug, sync::LazyLock, time::Duration}; +use std::collections::HashMap; +use std::fmt::Debug; +use std::sync::LazyLock; +use std::time::Duration; + +use serenity::all::Context; +use serenity::all::GuildId; +use serenity::all::Message; +use serenity::all::Permissions; -use serenity::all::{Context, GuildId, Message, Permissions}; use tokio::sync::RwLock; use crate::arguments::ArgumentStorage; +use crate::constants::CAT_HID; -pub static COMMANDS: LazyLock>> = - LazyLock::new(|| RwLock::new(HashMap::new())); +pub static COMMANDS: LazyLock>> = LazyLock::new(|| RwLock::new(HashMap::new())); +#[derive(Debug)] pub enum CommandFnKind { Lua(()), Rust(fn(Context, Message, Option)), } -pub struct Command { +#[derive(Debug)] +pub struct BotCommand { pub run_dm_command: Option, pub run_guild_command: Option, pub aliases: Vec, @@ -26,7 +35,7 @@ pub struct Command { pub hidden: bool, pub arguments: ArgumentStorage, - pub required_caller_discord_permissions: ::serenity::all::Permissions, + pub required_caller_discord_permissions: serenity::all::Permissions, #[cfg(feature = "nsfw_features")] pub is_nsfw: bool, @@ -34,7 +43,7 @@ pub struct Command { pub premium_kind: usize, } -impl Command { +impl BotCommand { pub fn new(name: String) -> Self { Self { run_dm_command: None, @@ -54,18 +63,22 @@ impl Command { 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 @@ -74,22 +87,27 @@ impl Command { self.aliases.push(alias); self } + pub fn aliases(mut self, aliases: &mut Vec) -> 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 @@ -100,38 +118,38 @@ impl Command { } } -unsafe impl Sync for Command {} -unsafe impl Send for Command {} +unsafe impl Sync for BotCommand {} +unsafe impl Send for BotCommand {} -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 Debug for BotCommand { +// 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 { @@ -139,3 +157,55 @@ impl Debug for Command { // } // } +/// The last set of commands, these are used by the hoster of the engine. +pub fn insert_lua(_lua: &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.blocking_write().insert( + // "stop2".to_owned(), + // BotCommand::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 async fn insert_stock() { + let shutdown_command = |_, msg: Message, _| { + // hardcode my id for now + if crate::databases::get_db().is_dev(msg.author.id) { + return; + } + crate::shutdown_handler(); + }; + + COMMANDS.write().await.insert( + "stop".to_owned(), + BotCommand::new("stop".to_owned()) + .alias("svs".to_owned()) + .category(CAT_HID.to_owned()) + .hidden(true) + .dm_command(CommandFnKind::Rust(shutdown_command)) + .guild_command(CommandFnKind::Rust(shutdown_command)) + .pretty_name("Stop the bot".to_owned()) + .help("Stops the bot. Does nothing unless you are a developer.".to_owned()), + ); +} diff --git a/src/constants.rs b/src/constants.rs index d0d93e9..4e0e909 100644 --- a/src/constants.rs +++ b/src/constants.rs @@ -1,12 +1,60 @@ +#![allow(unused)] + pub const VERSION: u16 = 0; -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"; +// This variable should never change for any reason, it will be used to identify this instance of the bot +//pub const BOT_INTERNAL: &str = "npobot"; +/// Should be limited to lowercase alphanumeric characters with periods, dashes, and underscores only +/// If you want to stylize the bot name use the fancy version +pub const BOT_NAME: &str = "nopalmo"; +pub const BOT_NAME_FANCY: &str = "Nopalmo"; -pub const COMMAND_PREFIX: &'static str = ";"; -// pub const SHORT_ARGUMENT_PREFIX: &'static str = "-"; -// pub const LONG_ARGUMENT_PREFIX: &'static str = "--"; +pub const CAT_DEV: &str = "Dev"; +pub const CAT_FUN: &str = "Fun"; +pub const CAT_MOD: &str = "Moderation"; +pub const CAT_GEN: &str = "General"; +pub const CAT_INF: &str = "Info"; +pub const CAT_HID: &str = "Hidden"; + +pub const EMOJI_THUMB_UP: &str = "<:thumbsup:1339301449645166665>"; + +pub const CONSTANT_PREFIX: &str = "NPO_PFX"; +pub const COMMAND_PREFIX: &str = ";"; +pub const SHORT_ARGUMENT_PREFIX: &str = "-"; +pub const LONG_ARGUMENT_PREFIX: &str = "--"; + +pub const HELP_STRING: &str = r" +Nopalmo command help page: + +Description: + Lorem Ipsum Dolar Sit Amet... + +Arguments: + nodiscord Skips connecting to discord + Eventually this will connect to a simulated discord instance + + dbuser The username to be used when connecting to a database type that requires one + dbpass The password used like dbuser + dbaddress The network address of the desired database + dbport The port to use with dbaddress + dbpath The path to a unix socket to substitute an address or the path to an SQLite database file + dbkind The kind of database we will be using + + use_cli Enables a CLI management interface for the bot + use_gui The GUI version of the CLI + + lua_engine_count The number of lua execution engines to create, This defaults to the number of cores the computer has + This allows executing multiple lua tasks at once, including user commands and timer tasks + + connect_remote Connect to a remote instance of the bot + host_remote Host an instance of the bot for remote connections + remote_address The remote address of the bot, or the address you wish to host the bot on + remote_port The port used like remote_address + remote_key The path or literal value of the key required to authenticate with the bot on a remote connection + remote_key_type Decides the key type for remote connections + +Remote Connections: + Remote access is disabled by default and must be enabled via the `connect_remote` or `host_remote` flags + A key is required for remote connections, if you just need something quick use the `password` key type, the password must be the same for both server and client + The key can be managed by the database as well, and the key type `database` will disable the `remote_key` variable and instead load authentication information from the server +"; \ No newline at end of file diff --git a/src/databases/access.rs b/src/databases/access.rs new file mode 100644 index 0000000..d441754 --- /dev/null +++ b/src/databases/access.rs @@ -0,0 +1,11 @@ +use std::process::exit; + +use tracing::error; + +pub fn create_interface() -> Result<(), Box> { + //warn!("You must provide both a username and password via `dbuser` and `dbpass`"); + //exit(-1); + + error!("Sorry! this database kind is not implemented right now."); + exit(-1); +} diff --git a/src/databases/mariadb.rs b/src/databases/mariadb.rs new file mode 100644 index 0000000..d441754 --- /dev/null +++ b/src/databases/mariadb.rs @@ -0,0 +1,11 @@ +use std::process::exit; + +use tracing::error; + +pub fn create_interface() -> Result<(), Box> { + //warn!("You must provide both a username and password via `dbuser` and `dbpass`"); + //exit(-1); + + error!("Sorry! this database kind is not implemented right now."); + exit(-1); +} diff --git a/src/databases/mod.rs b/src/databases/mod.rs new file mode 100644 index 0000000..8efada9 --- /dev/null +++ b/src/databases/mod.rs @@ -0,0 +1,60 @@ +use std::{ + fmt::Debug, + panic::Location, + sync::{Arc, OnceLock}, +}; + +use serenity::all::UserId; + +use crate::errors::DatabaseStoredError; + +pub mod access; +pub mod mariadb; +pub mod postgres; +pub mod sqlite; + +#[derive(Debug)] +pub enum DBKind { + Access, + SQLite, + MariaDB, + PostgreSQL, +} + +static DATABASE_ACCESSOR: OnceLock>> = OnceLock::new(); + +/// # Panics +/// This function will panic if used after the database has already been set +/// +/// You should not worry about this as this function only ever gets called once at the start of the program and you are doing something wrong if you need to call this a second time +pub fn set_db(dba: Box) { + assert!(DATABASE_ACCESSOR.set(Arc::new(dba)).is_ok(), "attempted to set database accessor after init"); +} + +/// # Panics +/// This function will panic if used before the database has been set +/// +/// You should not worry about this as this function only ever gets called after the database has been set and you are doing something wrong if you are calling this before the database is live +pub fn get_db() -> Arc> { + #[allow(clippy::expect_used)] + DATABASE_ACCESSOR.get().expect("attempted to get database before init").clone() +} + +/// This trait will provide a very high level interface to all supported databases +/// Its implementations should also sanitize all inputs regardless of what type +/// The implementation may be multithreaded/multiconnection, but for sqlite it is limited to a single thread/locked direct access to the connection via a mutex +/// If you need more performance use a different database type +pub trait DatabaseAccessor: Sync + Send { + // TODO make a db upgrade table + + fn get_db_version(&self); + + fn check_db_health(&self); + fn fix_db_health(&self); + + fn is_dev(&self, user_id: UserId) -> bool; + + fn set_dev(&self, user_id: UserId); + + fn store_error(&self, err: &DatabaseStoredError); +} diff --git a/src/databases/postgres.rs b/src/databases/postgres.rs new file mode 100644 index 0000000..d441754 --- /dev/null +++ b/src/databases/postgres.rs @@ -0,0 +1,11 @@ +use std::process::exit; + +use tracing::error; + +pub fn create_interface() -> Result<(), Box> { + //warn!("You must provide both a username and password via `dbuser` and `dbpass`"); + //exit(-1); + + error!("Sorry! this database kind is not implemented right now."); + exit(-1); +} diff --git a/src/databases/sqlite.rs b/src/databases/sqlite.rs new file mode 100644 index 0000000..7439201 --- /dev/null +++ b/src/databases/sqlite.rs @@ -0,0 +1,72 @@ +use std::{ + fmt::Debug, + sync::{Arc, Mutex}, +}; + +use sqlite::Connection; +use tracing::{error, info}; + +use crate::{errors::DatabaseStoredError, CMD_ARGS}; + +use super::DatabaseAccessor; + +struct SQLiteDatabase { + connection: Mutex, /*>*/ +} + +impl SQLiteDatabase { + //fn lock(&self) { + + //} +} + +impl Debug for SQLiteDatabase { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("SQLiteDatabase")/*.field("connection", &"")*/.finish() + } +} + +impl DatabaseAccessor for SQLiteDatabase { + fn get_db_version(&self) { + todo!() + } + + fn check_db_health(&self) { + todo!() + } + + fn fix_db_health(&self) { + todo!() + } + + fn is_dev(&self, user_id: serenity::all::UserId) -> bool { + todo!() + } + + fn set_dev(&self, user_id: serenity::all::UserId) { + todo!() + } + + fn store_error(&self, err: &DatabaseStoredError) { + todo!() + } +} + +/// # Panics +/// This function will panic if used after the database has already been set +/// +/// You should not worry about this as this function only ever gets called once at the start of the program and you are doing something wrong if you need to call this a second time +pub fn create_interface() -> Result<(), Box> { + info!("Loading SQLite."); + if let Some(db_path) = CMD_ARGS.dbpath.clone() { + let connection = sqlite::open(&db_path).map_err(|err| format!("Could not open file {db_path:?} with error: {err}"))?; + + super::set_db(Box::new(SQLiteDatabase { + connection: /*Arc::new(*/Mutex::new(connection)/*)*/, + })); + + Ok(()) + } else { + Err("You must provide a path using `dbpath` for sqlite to function!".into()) + } +} diff --git a/src/errors.rs b/src/errors.rs new file mode 100644 index 0000000..1ef1f83 --- /dev/null +++ b/src/errors.rs @@ -0,0 +1,40 @@ +use std::{ + fmt::Display, + panic::Location, + time::{SystemTime, UNIX_EPOCH}, +}; + +use crate::databases; + +#[derive(Debug)] +pub struct DatabaseStoredError { + epoch: u64, + caller: std::panic::Location<'static>, + cause: String, +} + +impl Display for DatabaseStoredError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str("") + } +} + +impl std::error::Error for DatabaseStoredError { + fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { + None + } +} + +impl DatabaseStoredError { + #[track_caller] + pub fn new(cause: impl ToString) -> Self { + let caller = *Location::caller(); + let cause = cause.to_string(); + #[allow(clippy::unwrap_used)] // Since epoch is 0, now cannot be less than 0 as the datatype is unsigned, making this unwrap impossible to fail. + let epoch = SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_secs(); + + let err = Self { epoch, caller, cause }; + databases::get_db().store_error(&err); + err + } +} diff --git a/src/lua.rs b/src/lua.rs index e4a28ca..c4bd642 100644 --- a/src/lua.rs +++ b/src/lua.rs @@ -1,7 +1,25 @@ -use mlua::{Lua, LuaOptions, Result, StdLib, Table, Value, Variadic}; -use tracing::{debug, error, info, trace, warn}; +// FIXME remove later, used for now while in active development +#![allow(clippy::unwrap_used)] -use crate::constants::{CAT_DEV, CAT_FUN, CAT_GEN, CAT_INF, CAT_MOD, VERSION}; +use mlua::Lua; +use mlua::LuaOptions; +use mlua::Result; +use mlua::StdLib; +use mlua::Table; +use mlua::Value; +use mlua::Variadic; +use tracing::debug; +use tracing::error; +use tracing::info; +use tracing::trace; +use tracing::warn; + +use crate::constants::CAT_DEV; +use crate::constants::CAT_FUN; +use crate::constants::CAT_GEN; +use crate::constants::CAT_INF; +use crate::constants::CAT_MOD; +use crate::constants::VERSION; // if there is a better way please tell me or replace this garbage pub fn value_to_string(value: Value) -> String { @@ -12,22 +30,25 @@ pub fn value_to_string(value: Value) -> String { Value::Integer(int) => int.to_string(), Value::Number(num) => num.to_string(), Value::Vector(vec) => vec.to_string(), - Value::String(string2) => String::from_utf8_lossy(string2.as_bytes()).to_string(), + Value::String(string2) => string2.to_string_lossy(), Value::Table(table) => format!("{table:?}"), // could probably handle this one better but whatever Value::Function(function) => { let info = function.info(); format!( "`{}` at `{}`", info.name.map_or_else(|| "Unnamed".to_owned(), |val| val), - info.line_defined - .map_or_else(|| "Unknown location".to_owned(), |val| val.to_string()), + info.line_defined.map_or_else(|| "Unknown location".to_owned(), |val| val.to_string()), ) - } + }, Value::Thread(thread) => { format!("`{}`:`{:?}`", thread.to_pointer() as usize, thread.status()) - } + }, Value::UserData(userdata) => format!("`{userdata:?}`"), Value::Error(err) => err.to_string(), + Value::Buffer(buffer) => format!("{buffer:?}"), + // TODO figure out a better way to figure out what the reference is + // or just leave it as is because if youre converting a generic value ref to a string, what else were you expecting to happen + Value::Other(value_ref) => format!("{value_ref:?}"), } } @@ -39,40 +60,41 @@ pub fn initialize() -> Result { // 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()), + LuaOptions::new().catch_rust_panics(true).thread_pool_size(num_cpus::get()), )?; // .expect("Lua could not initialize properly"); // set max to 2mb - info!("Previous memory limit `{}`", lua.set_memory_limit(2 * 1024 * 1024)?); + let limit = 2 * 1024 * 1024; + info!("Setting memory limit to `{limit}` The previous limit was: `{}`", lua.set_memory_limit(limit)?); + + // TODO make a mapping file that is processed at compile time that dictates what gets mapped where inside the lua engines + // This can be used to easily modify what is present in the sandbox and where it is/how to use it + // Once this mapping file is stable make a plugin or editor that can use the mapping file and associated type information + // To give syntax highlighting and autofill information, as well as documentation let globals = lua.globals(); globals.set("NP_VERSION", VERSION)?; - clean_global( - #[cfg(not(debug_assertions))] - &lua, - &globals, - ); + // TODO asserts should be handled differently + // 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(); + //} + + clean_global(&globals); prepare_global(&lua, &globals); set_logging(&lua, &globals); - drop(globals); - Ok(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(); - } +/// Remove definitions that either A: should not be present in a sandbox, or B: we supply an alternative for to better suit the program +fn clean_global(globals: &Table) { // 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(); // keep error as lua needs trace errors instead of rust like return types (its easier to keep things the way they are so we dont confuse newcomers) @@ -85,6 +107,7 @@ fn clean_global(#[cfg(not(debug_assertions))] lua: &Lua, globals: &Table) { globals.raw_remove("print").unwrap(); } +/// Fill global with our new definitions meant to either extend functionality or suppliment things we have removed fn prepare_global(lua: &Lua, globals: &Table) { let command_category_table = lua.create_table().unwrap(); command_category_table.set("dev", CAT_DEV).unwrap(); @@ -92,24 +115,26 @@ fn prepare_global(lua: &Lua, globals: &Table) { command_category_table.set("fun", CAT_FUN).unwrap(); command_category_table.set("general", CAT_GEN).unwrap(); command_category_table.set("moderation", CAT_MOD).unwrap(); - globals - .set("commandCategory", command_category_table) - .unwrap(); + globals.set("commandCategory", command_category_table).unwrap(); let sys_table = lua.create_table().unwrap(); - #[allow(clippy::deprecated_cfg_attr)] // shut up clippy thats unstable - #[cfg_attr(rustfmt, rustfmt_skip)] // wish this was comment based but whatever - { - sys_table.set("registerCommand", lua.create_function(rust_lua_functions::register_command).unwrap()).unwrap(); - sys_table.set("executeCommand", lua.create_function(rust_lua_functions::execute_command).unwrap()).unwrap(); - sys_table.set("luaValueToString", lua.create_function(rust_lua_functions::lua_value_to_string).unwrap()).unwrap(); - } + + sys_table + .set("registerCommand", lua.create_function(rust_lua_functions::register_command).unwrap()) + .unwrap(); + sys_table + .set("executeCommand", lua.create_function(rust_lua_functions::execute_command).unwrap()) + .unwrap(); + sys_table + .set("luaValueToString", lua.create_function(rust_lua_functions::lua_value_to_string).unwrap()) + .unwrap(); globals.set("sys", sys_table).unwrap(); } fn set_logging(lua: &Lua, globals: &Table) { // i wish i could loop these but i cant instantiate macros + // TODO maybe i can wrap each log tool in a function and use function pointers let log_table = lua.create_table().unwrap(); let trace = lua .create_function(|_lua, strings: Variadic| { @@ -181,15 +206,17 @@ fn set_logging(lua: &Lua, globals: &Table) { mod rust_lua_functions { use mlua::prelude::LuaResult; - use mlua::{ExternalError, Lua, Value, Variadic}; + use mlua::ExternalError; + use mlua::Lua; + use mlua::Value; + use mlua::Variadic; use tracing::info; use crate::lua::value_to_string; pub fn execute_command(_lua: &Lua, mut arg_values: Variadic) -> LuaResult<()> { - let command_name = match arg_values.get(0).cloned() { - Some(name) => name, - None => return Err("executeCommand requires a command name to execute! please supply a valid command name as the first argument.".into_lua_err()), + let Some(command_name) = arg_values.first().cloned() else { + return Err("executeCommand requires a command name to execute! please supply a valid command name as the first argument.".into_lua_err()); }; let mut command_args = vec![]; @@ -198,45 +225,23 @@ mod rust_lua_functions { match value { Value::String(string) => { command_args.push(string.to_string_lossy().to_string()); - } - Value::Nil => { - return Err( - "Nil argument provided! executeCommand accepts no nil arguments" - .into_lua_err(), - ) - } - Value::Table(_) => { - return Err("Direct run commands are not supported yet.".into_lua_err()) - } - _ => return Err( - "Invalid type used! Only `String` and `Table` are supported by executeCommand" - .into_lua_err(), - ), + }, + Value::Nil => return Err("Nil argument provided! executeCommand accepts no nil arguments".into_lua_err()), + Value::Table(_) => return Err("Direct run commands are not supported yet.".into_lua_err()), + _ => return Err("Invalid type used! Only `String` and `Table` are supported by executeCommand".into_lua_err()), } } - info!( - "Running lua command `{}` with args `{:?}`", - value_to_string(command_name), - command_args - ); + info!("Running lua command `{}` with args `{:?}`", value_to_string(command_name), command_args); Ok(()) } pub fn register_command(_lua: &Lua, arg_values: Variadic) -> LuaResult<()> { - let table = match arg_values.get(0) { + let table = match arg_values.first() { Some(Value::Table(table)) => table, - Some(Value::Nil) | None => { - return Err( - "Nil argument provided! registerCommand accepts no nil arguments" - .into_lua_err(), - ) - } + Some(Value::Nil) | None => return Err("Nil argument provided! registerCommand accepts no nil arguments".into_lua_err()), _ => { - return Err( - "Invalid type used! Only `Table` is supported by registerCommand" - .into_lua_err(), - ); - } + return Err("Invalid type used! Only `Table` is supported by registerCommand".into_lua_err()); + }, }; info!("{:?}", table); @@ -353,4 +358,3 @@ mod rust_lua_functions { // } // } // } - diff --git a/src/main.rs b/src/main.rs index ddd81ee..02b82ff 100644 --- a/src/main.rs +++ b/src/main.rs @@ -3,148 +3,247 @@ #![deny(clippy::pedantic)] #![allow(clippy::unreadable_literal)] #![allow(clippy::needless_pass_by_value)] +#![allow(clippy::unused_async)] mod arguments; mod bot; mod commands; mod constants; +mod databases; +mod errors; mod lua; mod tasks; use std::process::exit; -use std::sync::atomic::Ordering; +use std::sync::mpsc::Sender; +use std::sync::LazyLock; use std::sync::OnceLock; -use serenity::all::{Context, GuildId}; -use serenity::model::channel::Message; +use tracing::debug; +use tracing::error; +use tracing::info; use tracing::metadata::LevelFilter; +use tracing::warn; -use tracing::{error, info}; +use constants::HELP_STRING; -use commands::{Command, CommandFnKind, COMMANDS}; -use constants::CAT_HID; +use databases::DBKind; -pub static CARG_NO_DISCORD: OnceLock = OnceLock::new(); +#[derive(Debug, Clone)] +pub struct CmdArgs { + // TODO bool_bucket: u8, + pub nodiscord: bool, + pub dbuser: Option, + pub dbpass: Option, + pub dbaddress: Option, + pub dbport: Option, + pub dbpath: Option, + pub dbkind: Option, + pub use_cli: bool, + pub lua_engine_count: Option, + pub connect_remote: bool, + pub remote_address: Option, + pub remote_port: Option, + pub remote_key: Option, + pub remote_key_type: Option, +} -#[tokio::main] -async fn main() { - if let Err(err) = tracing::subscriber::set_global_default( - tracing_subscriber::fmt() - .with_max_level(LevelFilter::INFO) - .finish(), - ) { - eprintln!("Encountered an error while initializing logging! `{err}`"); - exit(1); - }; - info!("Logging initialized."); +/// In most cases this would just be a once lock or similar +/// however i dont like needing to unwrap it everywhere in the program after i know for a fact it was already initialized +/// at the start of execution and will never be written to again +/// +/// So we just force the once lock when we want to load the args like we would for a once lock +pub static CMD_ARGS: LazyLock = LazyLock::new(get_cmd_args); +/// A simple no questions asked way to shut down the bot gracefully +// maybe we should include a reason enum that includes other info? no that should be handled wherever the message is sent from +// TODO custom panic handler? +pub static SHUTDOWN_SENDER: OnceLock> = OnceLock::new(); + +#[allow(clippy::missing_panics_doc)] +pub fn shutdown_handler() { + static SHUTDOWN_COUNT: std::sync::atomic::AtomicU8 = std::sync::atomic::AtomicU8::new(1); + if SHUTDOWN_COUNT.fetch_add(1, std::sync::atomic::Ordering::SeqCst) >= 3 { + warn!("3 exit events have piled up. Forcing exit."); + std::process::exit(-1); + } + #[allow(clippy::unwrap_used)] // it is impossible for this to be an error + if let Err(err) = SHUTDOWN_SENDER.get().unwrap().send(()) { + error!("Failed to send shutdown signal: {err}"); + } +} + +pub fn get_cmd_args() -> CmdArgs { info!("Grabbing commandline input."); + let mut nodiscord = false; - for i in std::env::args() { - #[allow(clippy::single_match)] // here for convienience when adding additional flags - match i.as_str() { - "nodiscord" => nodiscord = true, - _ => {} + let mut dbuser = None; + let mut dbpass = None; + let mut dbaddress = None; + let mut dbport = None; + let mut dbpath = None; + let mut dbkind = None; + let mut use_cli = false; + let mut lua_engine_count = None; + let mut connect_remote = false; + let mut remote_address = None; + let mut remote_port = None; + let mut remote_key = None; + let mut remote_key_type = None; + + let mut args = std::env::args().peekable(); + + // The first arg is the path to the executable on all systems that i know of + // But this is not always guaranteed, however this program does not expect file paths as the first element so we can safely skip it if the path exists + // We could check this to see if it matches our environment executable path but that needs too much error handling for something like this + if let Some(argpath) = args.peek() { + if std::path::PathBuf::from(argpath).exists() { + args.next(); } } - if let Err(err) = CARG_NO_DISCORD.set(nodiscord) { - error!("Encountered an error while setting the `nodiscord` flag! `{err}`"); - exit(1); + + while let Some(item) = args.next() { + match item.to_lowercase().trim() { + // Execution args + "help" | "-h" | "--help" | "-help" | "/help" | "/h" | "?" | "/?" => { + info!("{HELP_STRING}"); + exit(0); + }, + // Data args + "nodiscord" => nodiscord = true, + "dbuser" => dbuser = args.next(), + "dbpass" => dbpass = args.next(), + "dbaddress" => dbaddress = args.next(), + "dbport" => dbport = args.next(), + "dbpath" => dbpath = args.next(), + "dbkind" => dbkind = args.next(), + "use_cli" => use_cli = true, + "lua_engine_count" => lua_engine_count = args.next(), + "connect_remote" => connect_remote = true, + "remote_address" => remote_address = args.next(), + "remote_port" => remote_port = args.next(), + "remote_key" => remote_key = args.next(), + "remote_key_type" => remote_key_type = args.next(), + value => warn!("Unknown or misplaced value: {value}"), // TODO move help argument here? + } + } + + CmdArgs { + nodiscord, + dbuser, + dbpass, + dbaddress, + dbport, + dbpath, + dbkind, + use_cli, + lua_engine_count, + connect_remote, + remote_address, + remote_port, + remote_key, + remote_key_type, + } +} + +// TODO remove async from the main function and restrict its usage to just the discord bot +// We can manually manage worker threads everywhere else, especially with the lua engines +#[tokio::main] +async fn main() { + // why do we need to set global default again? + tracing_subscriber::fmt() + .compact() + .with_timer(tracing_subscriber::fmt::time::uptime()) + .with_ansi(true) + .with_level(true) + .with_file(cfg!(debug_assertions)) + .with_line_number(cfg!(debug_assertions)) + .with_thread_names(cfg!(debug_assertions)) + .with_max_level(if cfg!(debug_assertions) { LevelFilter::DEBUG } else { LevelFilter::INFO }) + .init(); + info!("Logging initialized."); + + // Manually init the lazy lock + LazyLock::force(&CMD_ARGS); + debug!("{:?}", *CMD_ARGS); + + let (shutdown_tx, shutdown_rx) = std::sync::mpsc::channel(); + #[allow(clippy::unwrap_used)] // it is impossible for this to be an error + SHUTDOWN_SENDER.set(shutdown_tx.clone()).unwrap(); + #[allow(clippy::unwrap_used)] // it is impossible for this to be an error + tasks::SHUTDOWN_RECEIVER.set(tokio::sync::Mutex::new(shutdown_rx)).unwrap(); + + if let Err(err) = ctrlc::set_handler(shutdown_handler) { + error!("Error setting Ctrl-C handler: {err}"); + } + + let dbkind = if let Some(arg) = &CMD_ARGS.dbkind { + info!("Loading database accessor."); + match arg.to_lowercase().trim() { + "access" => DBKind::Access, + "postgresql" => DBKind::PostgreSQL, + "sqlite" => DBKind::SQLite, + "mariadb" => DBKind::MariaDB, + _ => { + error!("Invalid database kind! Choose one of `sqlite` `mariadb` `postgresql` `access`"); + exit(-1); + }, + } + } else { + warn!("You must select a database kind with `dbkind`"); + exit(-1); }; + if let Err(err) = match dbkind { + DBKind::Access => databases::access::create_interface(), + DBKind::MariaDB => databases::mariadb::create_interface(), + DBKind::PostgreSQL => databases::postgres::create_interface(), + DBKind::SQLite => databases::sqlite::create_interface(), + } { + error!("Could not start database {dbkind:?} with error: {err}"); + exit(-1); + } + + // TODO if the db is new fill it with data + // TODO if the db health check fails or is old attempt backup and upgrade + info!("Loading stock commands."); - insert_stock().await; + commands::insert_stock().await; info!("Loading rust dynamic commands."); - insert_rust(); + commands::insert_rust(); - info!("Initializing Lua runtime."); - let lua = match lua::initialize() { - Ok(lua) => lua, - Err(err) => { - error!("Encountered an error while initializing lua! `{err}`"); - exit(1); - }, - }; // may not need to be mutable, but its fine for now + // TODO lua mpmc with N lua engines, use the fire and forget strategy, lua engines should handle their own errors! and using a custom panic hook they should be replaced on death! - if let Err(err) = lua - .load(include_str!("../lua/loader.lua")) - .set_name("loader.lua") - .exec() - { - error!("Encountered an error while running loader.lua: {err}"); - }; + // TODO lua::init_lua(); + //info!("Loading lua commandlets."); + //insert_lua(&lua); - info!("Loading lua commandlets."); - insert_lua(&lua); + // old - if let Err(err) = lua.sandbox(true) { - error!("Encountered an error while setting the lua runtime to sandbox mode! `{err}`"); - exit(1); - }; + //info!("Initializing Lua runtime."); + //let lua = match lua::initialize() { + // Ok(lua) => lua, + // Err(err) => { + // error!("Encountered an error while initializing lua! `{err}`"); + // exit(1); + // }, + //}; + + //if let Err(err) = lua.load(include_str!("../lua/loader.lua")).set_name("loader.lua").exec() { + // error!("Encountered an error while running loader.lua: {err}"); + //} + + //info!("Loading lua commandlets."); + //insert_lua(&lua); + + //if let Err(err) = lua.sandbox(true) { + // error!("Encountered an error while setting the lua runtime to sandbox mode! `{err}`"); + // exit(1); + //} + + if CMD_ARGS.use_cli { + // TODO create cli interface for bot + } bot::start().await; } - -/// The last set of commands, these are used by the hoster of the engine. -pub fn insert_lua(_lua: &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.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 async fn insert_stock() { - COMMANDS.write().await.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) { - // hardcode my id for now - if msg.author.id != 380045419381784576 { - return; - } - bot::DO_SHUTDOWN.0.store(true, Ordering::SeqCst); - bot::DO_SHUTDOWN.1.notify_all(); - - let handle = tokio::runtime::Handle::current(); - let _eg = handle.enter(); - - handle.spawn(async { - // this is literally impossible to fail as the SHARD_MANAGER is required to exist for this function to ever run - #[allow(clippy::unwrap_used)] - bot::SHARD_MANAGER.get().unwrap().shutdown_all().await; - }); -} - diff --git a/src/tasks.rs b/src/tasks.rs index 936fff3..0fe1685 100644 --- a/src/tasks.rs +++ b/src/tasks.rs @@ -1,17 +1,31 @@ -use crate::{bot::SHARD_MANAGER, constants}; -use std::{ - sync::{Arc, LazyLock}, - time::Duration, -}; +use std::collections::hash_map::HashMap; +use std::sync::Arc; +use std::sync::LazyLock; +use std::sync::OnceLock; +use std::time::Duration; -use ::std::collections::hash_map::HashMap; -use rand::{distributions::uniform::SampleRange, rngs::OsRng}; -use serenity::all::{ActivityData, Cache, Context, ShardId, ShardRunnerInfo}; -use tokio::{sync::Mutex, task::JoinHandle}; +use rand::Rng; +use rand::SeedableRng; + +use serenity::all::ActivityData; +use serenity::all::Cache; +use serenity::all::Context; +use serenity::all::ShardId; +use serenity::all::ShardRunnerInfo; + +use tokio::sync::Mutex; +use tokio::task::JoinHandle; + +use tracing::error; use tracing::info; +use tracing::warn; -static TASK_HANDLES: LazyLock>> = - LazyLock::new(|| Arc::new(Mutex::new(TaskContainer::default()))); +use crate::bot::SHARD_MANAGER; +use crate::constants; + +// TODO remove SHUTDOWN_RECEIVER from static memory +pub static SHUTDOWN_RECEIVER: OnceLock>> = OnceLock::new(); +static TASK_HANDLES: LazyLock>> = LazyLock::new(|| Arc::new(Mutex::new(TaskContainer::default()))); #[derive(Debug, Default)] pub struct TaskContainer { @@ -21,24 +35,64 @@ pub struct TaskContainer { pub async fn start_tasks(ctx: Context) { info!("Starting MISC tasks."); - info!("Starting activity switcher task."); + info!("Starting activity switcher task..."); + let Some(sharts) = SHARD_MANAGER.get() else { + error!("Could not obtain the shard manager."); + return; + }; TASK_HANDLES .lock() .await .misc - .push(tokio::spawn(status_timer( - SHARD_MANAGER.get().unwrap().runners.clone(), - ctx.cache, - ))); + .push(tokio::spawn(status_timer(sharts.runners.clone(), ctx.cache))); + info!("Starting forum task..."); + TASK_HANDLES.lock().await.misc.push(tokio::spawn(forum_checker())); + info!("Starting shutdown monitor task..."); + TASK_HANDLES.lock().await.misc.push(tokio::spawn(shutdown_monitor())); info!("MISC tasks started."); + + warn!("Task health checker is currently unimplemented!"); + warn!("If a task panics we will not be able to restart it or fix any issues!"); } -pub async fn status_timer( - shard_runners: Arc>>, - cache: Arc, -) { - let mut rand = OsRng::default(); +async fn forum_checker() { + let mut interval = tokio::time::interval(Duration::from_millis(16)); + + loop { + interval.tick().await; + } +} + +pub async fn check_forum() {} + +async fn shutdown_monitor() { + // TODO tokio interval waiting may be more efficient than just a recv on an std mpsc + // tokio mpsc may be better than both as the std mpsc may halt the tokio task + // without letting it yeild properly + + //let mut interval = tokio::time::interval(Duration::from_millis(16)); + #[allow(clippy::unwrap_used)] // it is impossible for this to be an error + let lock = SHUTDOWN_RECEIVER.get().unwrap().lock().await; + //loop { + if lock.recv().is_ok() { + warn!("The bot is shutting down now!"); + + crate::bot::DO_SHUTDOWN.store(true, std::sync::atomic::Ordering::SeqCst); + + // TODO wait till all existing actions are completed or timeout + + // impossible to fail + #[allow(clippy::unwrap_used)] + crate::bot::SHARD_MANAGER.get().unwrap().shutdown_all().await; + //break; + } + //interval.tick().await; + //} +} + +async fn status_timer(shard_runners: Arc>>, cache: Arc) { + // TODO make this a vec and able to be updated from external sources like lua engines, maybe static let activities = [ ActivityData::watching("my developer eat a watermelon whole."), ActivityData::watching(format!( @@ -50,13 +104,12 @@ pub async fn status_timer( ActivityData::listening("Infected Mushroom"), ]; let mut interval = tokio::time::interval(Duration::from_secs(20 * 60)); - + let mut rand = rand::rngs::SmallRng::from_os_rng(); + loop { - let activity = SampleRange::sample_single(0..activities.len() - 1, &mut rand); + let activity = rand.random_range(0..activities.len() - 1); for (_shard_id, shard_info) in shard_runners.lock().await.iter() { - shard_info - .runner_tx - .set_activity(Some(activities[activity].clone())); + shard_info.runner_tx.set_activity(Some(activities[activity].clone())); } interval.tick().await; From 3da1cfc1f557aa9bee28d2dba0a57bae25e5e53f Mon Sep 17 00:00:00 2001 From: lever1209 Date: Sun, 23 Mar 2025 13:53:22 -0300 Subject: [PATCH 3/5] Sync --- Cargo.lock | 5 +- Cargo.toml | 8 +- docs/db-structure.md | 51 ++++++++++ docs/sqlite.md | 10 ++ src/arguments.rs | 5 +- src/bot.rs | 209 +++++++++++++++++++++++++++++++--------- src/commands.rs | 8 +- src/constants.rs | 2 +- src/databases/mod.rs | 8 +- src/databases/sqlite.rs | 16 +-- src/errors.rs | 9 +- src/main.rs | 133 ++++++++++++++++--------- src/tasks.rs | 8 +- 13 files changed, 352 insertions(+), 120 deletions(-) create mode 100644 docs/db-structure.md create mode 100644 docs/sqlite.md diff --git a/Cargo.lock b/Cargo.lock index e939c77..8b0683c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -913,9 +913,9 @@ dependencies = [ [[package]] name = "log" -version = "0.4.22" +version = "0.4.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" +checksum = "30bde2b3dc3671ae49d8e2e9f044c7c005836e7a023ee57cffa25ab82764bb9e" [[package]] name = "luau0-src" @@ -1028,6 +1028,7 @@ name = "nopalmo" version = "0.1.0" dependencies = [ "ctrlc", + "log", "mlua", "num_cpus", "rand 0.9.0", diff --git a/Cargo.toml b/Cargo.toml index d9f4ea2..88e0d58 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,14 +1,18 @@ [package] name = "nopalmo" version = "0.1.0" -edition = "2021" +edition = "2024" [dependencies] -tokio = { version = "1.37.0", features = ["full"] } +# Logging tracing = "0.1.40" tracing-subscriber = { version = "0.3.18", features = ["chrono"] } +log = "0.4.26" mlua = { version = "0.10.3", features = ["async", "luau"] } +# Tokio will be useful at a later date but as of now i will be limiting its usage +tokio = { version = "1.37.0", features = ["full"] } +# tossing this lib at some point, it makes too many assumptions about how i code and i dislike how often im forced to async serenity = "0.12.4" num_cpus = "1.16.0" diff --git a/docs/db-structure.md b/docs/db-structure.md new file mode 100644 index 0000000..865b157 --- /dev/null +++ b/docs/db-structure.md @@ -0,0 +1,51 @@ + +--- + +# Preface + +### Primitive Data Types + +#### Null + +This value type can override any other type and essentially means "nothing" not even 0, its the lack of a value whatsoever + +If you open a box thats supposed to have a number in it, and you found null, the box would just be empty + +#### Boolean + +A boolean value is true or false, it cannot be anything else + +See [Sqlite](sqlite.md#Boolean) for more information on boolean values within sqlite. + +#### Integer + +An integer is any valid number without a decimal, such as 1, 5, 68, 421, and even negative numbers like -42, -99999, or -0 in some cases + +#### Real Number (floating point number) + +A real number is any real number, such as 1, 73, 45, 0.6, 255, -9.8, 200000000, and so on, it may use a decimal value unlike integers. + +We should prefer Integers over Real Numbers unless a decimal place is absolutely required: + +There are resolution limitations to consider, the number of bits a computer can store in a single value is finite, a 32 bit number only has 32 bits, meaning there can only be about 2 billion and some possible combinations. + +Real numbers do not change this, there are only 2 billion or so different values to a real number, so the smaller you go, the more likely you are to accidentally "snap" to a nearby number that can actually be stored, + +The same thing happens when you go high enough. + +Here are some good videos on the topic. + +Simple [Computerphile](https://www.youtube.com/watch?v=PZRI1IfStY0) + +Technical [Spanning Tree](https://www.youtube.com/watch?v=bbkcEiUjehk) + +### Complex Data Types + +#### Users + - UID: Integer + - isDeveloper: Boolean + +--- +# Structure + +### Stuff, eventually diff --git a/docs/sqlite.md b/docs/sqlite.md new file mode 100644 index 0000000..f834630 --- /dev/null +++ b/docs/sqlite.md @@ -0,0 +1,10 @@ + +This file will store any corrections or nuance about using sqlite. + +### Boolean + +Due to a restriction of sqlite, we must use Integer data types to represent a boolean value. + +To do so the value must be either 0 meaning false, or 1 meaning true. + +The database accessor will do this conversion for you automatically, so this will only matter to people working on the database accessor. \ No newline at end of file diff --git a/src/arguments.rs b/src/arguments.rs index eed1204..c18e3dd 100644 --- a/src/arguments.rs +++ b/src/arguments.rs @@ -4,10 +4,9 @@ use std::collections::HashMap; pub struct ArgumentStorage(HashMap); impl ArgumentStorage { - pub fn new() -> Self { Self::default() } + pub fn new() -> Self { Self(HashMap::new()) } - pub fn add_argument() { - } + pub fn add_argument() {} } // #[derive(Debug, Clone, PartialEq, Default)] diff --git a/src/bot.rs b/src/bot.rs index 1558cd3..53dc9e2 100644 --- a/src/bot.rs +++ b/src/bot.rs @@ -1,7 +1,8 @@ -use std::sync::atomic::AtomicBool; use std::sync::Arc; use std::sync::OnceLock; +use std::sync::atomic::AtomicBool; +use serenity::Client; use serenity::all::ActivityData; use serenity::all::Context; use serenity::all::EventHandler; @@ -10,30 +11,148 @@ use serenity::all::GuildId; use serenity::all::ShardManager; use serenity::model::channel::Message; use serenity::model::gateway::Ready; -use serenity::Client; use tracing::error; use tracing::info; use tracing::warn; +use crate::PROPERTIES; use crate::commands::COMMANDS; -use crate::commands::{self}; use crate::constants; use crate::tasks; -use crate::CMD_ARGS; +// TODO remove from static memory pub static DO_SHUTDOWN: AtomicBool = AtomicBool::new(false); // TODO remove from static memory pub static SHARD_MANAGER: OnceLock> = OnceLock::new(); -// cant remember why this was here, it isnt used anywhere else so ill just remove it for now -//pub static BOT_STARTED: AtomicBool = AtomicBool::new(false); pub struct BotHandler; unsafe impl Sync for BotHandler {} unsafe impl Send for BotHandler {} +const ESCAPE_CHAR: char = '\\'; + +fn group(string: &dyn ToString) -> Vec { + let string = string.to_string(); // binding + + let mut argument_results = vec![]; + + let mut quote_depth = 0; + let mut double_quote_depth = 0; + let mut bucket = String::new(); + //let mut bucket2 = String::new(); + + let mut chars = string.chars().peekable(); + while let Some(c) = chars.next() { + //dbg!(c); + + // skip current char and move next char into the bucket, doesnt matter what it is, its been escaped + if c == ESCAPE_CHAR { + if let Some(c) = chars.next() { + bucket.push(c); + } else { + error!("Expected another character after escape char"); + bucket.push(ESCAPE_CHAR); + } + continue; + } + + //dbg!(quote_depth); + //dbg!(double_quote_depth); + + if c == '\'' { + // outside quotes + if quote_depth % 2 == 0 { + quote_depth += 1; + } else { + // inside quotes + quote_depth -= 1; + } + // outside double quotes + if double_quote_depth % 2 == 0 { + continue; // continue to avoid adding this to the bucket + } + } + + if c == '"' { + // outside double quotes + if double_quote_depth % 2 == 0 { + double_quote_depth += 1; + } else { + // inside double quotes + double_quote_depth -= 1; + } + // outside quotes + if quote_depth % 2 == 0 { + continue; // continue to avoid adding this to the bucket + } + } + + //dbg!(&bucket); + + // if in quotes of some kind, just add to the bucket, if not we must move the bucket to the result and empty the bucket + if c == ' ' { + if quote_depth == 0 && double_quote_depth == 0 { + if !bucket.is_empty() { + argument_results.push(bucket.clone()); + bucket.clear(); + } + //dbg!(&argument_results); + continue; + } + } + + bucket.push(c); + } + + if !bucket.is_empty() { + argument_results.push(bucket); + } + + argument_results +} + +fn bash(str_vec: Vec) -> Vec { + let mut return_vec = vec![]; + + let mut str_vec = str_vec.iter(); + + //let mut enabled = true; + while let Some(item) = str_vec.next() { + // abandon parsing since a bare -- means end parsing + if item.len() == 2 && &item[..2] == "--" { + return_vec.push(item.to_string()); + while let Some(item) = str_vec.next() { + return_vec.push(item.to_string()); + } + break; + } + + match (item.get(0..1), item.get(1..1)) { + // long option + (Some("-"), Some("-")) => { + for (cidx, c) in item.char_indices() { + if c == '=' { + let (rhs, lhs) = item.split_at(cidx); + dbg!(lhs); + return_vec.push(rhs.to_string()); + } + } + }, + // short option + (Some("-"), Some(s)) if s != "-" => { + + }, + // not an option, just ignore it + _ => return_vec.push(item.to_string()), + } + } + + return_vec +} + #[serenity::async_trait] impl EventHandler for BotHandler { async fn message(&self, ctx: Context, msg: Message) { @@ -42,23 +161,20 @@ impl EventHandler for BotHandler { return; } - // TODO remove regular expressions - let prefix_regex = format!( - r"^({}|{}|<@{}>)\s?", - constants::COMMAND_PREFIX, - constants::CONSTANT_PREFIX, - ctx.cache.current_user().id - ); - let cmd_regex = format!(r"{prefix_regex}[A-Za-z0-9_\-]+"); + let offset; - let Ok(result) = regex::Regex::new(cmd_regex.as_str()) else { - error!("The following regex function has failed to compile, this should not be possible. `{prefix_regex}`"); + if msg.content.starts_with(constants::COMMAND_PREFIX) { + offset = constants::COMMAND_PREFIX.len(); + } else if msg.content.starts_with(constants::CONSTANT_PREFIX) { + offset = constants::COMMAND_PREFIX.len(); + } else if msg.content.starts_with(format!("<@{}>", ctx.cache.current_user().id).as_str()) { + // Magic numbers: +1 is for ilog10, +3 is for the <@> characters + offset = ctx.cache.current_user().id.get().checked_ilog10().unwrap_or(0) as usize + 1 + 3; + } else { return; - }; + } - let Some(result) = result.find(&msg.content) else { - return; // message not meant for us :( - }; + dbg!(&offset); if DO_SHUTDOWN.load(std::sync::atomic::Ordering::SeqCst) { let _ = msg @@ -68,15 +184,15 @@ impl EventHandler for BotHandler { return; } - let Ok(target_cmd_name) = regex::Regex::new(prefix_regex.as_str()) else { - error!("The following regex function has failed to compile, this should not be possible. `{prefix_regex}`"); - return; - }; - let target_cmd_name = dbg!(target_cmd_name.replace(result.as_str(), "").to_string()); + let grouped = group(&&msg.content[offset..]); + let bashed = bash(grouped); + + let target_cmd_name = dbg!(&msg.content[offset..]); let _ = msg.reply(&ctx.http, target_cmd_name.to_string()).await; - if let Some(command) = dbg!(COMMANDS.read().await).get(&target_cmd_name) { + // TODO fix this unwrap + if let Some(command) = COMMANDS.read().unwrap().get(target_cmd_name) { match (msg.guild_id, &command.run_guild_command, &command.run_dm_command) { //(Some(gid), Some(commands::CommandFnKind::Lua(())), _) => todo!(), (Some(gid), Some(commands::CommandFnKind::Rust(gcmd)), _) => (gcmd)(ctx, msg, Some(gid)), @@ -109,17 +225,18 @@ impl EventHandler for BotHandler { } } -pub async fn start() { - // 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."); +pub fn start() { + info!( + "Initializing bot with the {} token.", + if cfg!(debug_assertions) { "development" } else { "production" } + ); + + // Load this at runtime so the key will not be stored in the binary? at the very least we could disguise the string as garbage data so it cannot be easily extracted via static analysis, it seems like the bot library only uses references as well, so it should not be stored plaintext in memory either + // Will need a protected storage medium for the token, if we get to that point the user will need to suggest something, storing the token is not my responsibility, databases might be a good alternative? + const TOKEN: &'static str = if cfg!(debug_assertions) { include_str!("bot_token.dev") + } else { + include_str!("bot_token.prod") }; let intents = GatewayIntents::DIRECT_MESSAGES @@ -132,20 +249,24 @@ pub async fn start() { | GatewayIntents::GUILD_MESSAGES | GatewayIntents::MESSAGE_CONTENT; - if CMD_ARGS.nodiscord { + if PROPERTIES.nodiscord { + // TODO fake discord api? we are probably tossing out serenity at some point anyway, could emulate something warn!("ABORTING CONNECTING TO DISCORD, BYE BYE!"); } else { - let mut client = match Client::builder(token, intents).event_handler(BotHandler).await { - Ok(client) => client, - Err(err) => panic!("Error starting client connection: `{err}`"), - }; + // TODO proper error handling + let reactor = tokio::runtime::Builder::new_multi_thread().enable_io().enable_time().build().unwrap(); + + let mut client = reactor.block_on(async { + match Client::builder(TOKEN, intents).event_handler(BotHandler).await { + Ok(client) => client, + Err(err) => panic!("Error starting client connection: `{err}`"), + } + }); // just ignore the response, its impossible to fail here anyway let _ = SHARD_MANAGER.set(client.shard_manager.clone()); - //BOT_STARTED.store(true, std::sync::atomic::Ordering::SeqCst); - - if let Err(why) = client.start_shards(2).await { + if let Err(why) = reactor.block_on(client.start_shards(2)) { error!("Client error: {why:?}"); } } diff --git a/src/commands.rs b/src/commands.rs index 0f5a03e..c9182e5 100644 --- a/src/commands.rs +++ b/src/commands.rs @@ -1,6 +1,7 @@ use std::collections::HashMap; use std::fmt::Debug; use std::sync::LazyLock; +use std::sync::RwLock; use std::time::Duration; use serenity::all::Context; @@ -8,8 +9,6 @@ use serenity::all::GuildId; use serenity::all::Message; use serenity::all::Permissions; -use tokio::sync::RwLock; - use crate::arguments::ArgumentStorage; use crate::constants::CAT_HID; @@ -188,7 +187,7 @@ pub fn insert_rust() { } /// Will never fail, gets first pick of command names and properties. It is up to the maintainer to make damn sure this works. -pub async fn insert_stock() { +pub fn insert_stock() { let shutdown_command = |_, msg: Message, _| { // hardcode my id for now if crate::databases::get_db().is_dev(msg.author.id) { @@ -197,7 +196,8 @@ pub async fn insert_stock() { crate::shutdown_handler(); }; - COMMANDS.write().await.insert( + // TODO fix this unwrap + COMMANDS.write().unwrap().insert( "stop".to_owned(), BotCommand::new("stop".to_owned()) .alias("svs".to_owned()) diff --git a/src/constants.rs b/src/constants.rs index 4e0e909..c589ce9 100644 --- a/src/constants.rs +++ b/src/constants.rs @@ -57,4 +57,4 @@ Remote Connections: Remote access is disabled by default and must be enabled via the `connect_remote` or `host_remote` flags A key is required for remote connections, if you just need something quick use the `password` key type, the password must be the same for both server and client The key can be managed by the database as well, and the key type `database` will disable the `remote_key` variable and instead load authentication information from the server -"; \ No newline at end of file +"; diff --git a/src/databases/mod.rs b/src/databases/mod.rs index 8efada9..54b07db 100644 --- a/src/databases/mod.rs +++ b/src/databases/mod.rs @@ -1,8 +1,6 @@ -use std::{ - fmt::Debug, - panic::Location, - sync::{Arc, OnceLock}, -}; +use std::fmt::Debug; +use std::sync::Arc; +use std::sync::OnceLock; use serenity::all::UserId; diff --git a/src/databases/sqlite.rs b/src/databases/sqlite.rs index 7439201..6efa7bc 100644 --- a/src/databases/sqlite.rs +++ b/src/databases/sqlite.rs @@ -1,17 +1,17 @@ -use std::{ - fmt::Debug, - sync::{Arc, Mutex}, -}; +use std::fmt::Debug; +use std::sync::Mutex; use sqlite::Connection; -use tracing::{error, info}; +use tracing::error; +use tracing::info; -use crate::{errors::DatabaseStoredError, CMD_ARGS}; +use crate::errors::DatabaseStoredError; +use crate::PROPERTIES; use super::DatabaseAccessor; struct SQLiteDatabase { - connection: Mutex, /*>*/ + connection: Mutex, } impl SQLiteDatabase { @@ -58,7 +58,7 @@ impl DatabaseAccessor for SQLiteDatabase { /// You should not worry about this as this function only ever gets called once at the start of the program and you are doing something wrong if you need to call this a second time pub fn create_interface() -> Result<(), Box> { info!("Loading SQLite."); - if let Some(db_path) = CMD_ARGS.dbpath.clone() { + if let Some(db_path) = PROPERTIES.dbpath.clone() { let connection = sqlite::open(&db_path).map_err(|err| format!("Could not open file {db_path:?} with error: {err}"))?; super::set_db(Box::new(SQLiteDatabase { diff --git a/src/errors.rs b/src/errors.rs index 1ef1f83..1e95cf2 100644 --- a/src/errors.rs +++ b/src/errors.rs @@ -1,8 +1,7 @@ -use std::{ - fmt::Display, - panic::Location, - time::{SystemTime, UNIX_EPOCH}, -}; +use std::fmt::Display; +use std::panic::Location; +use std::time::SystemTime; +use std::time::UNIX_EPOCH; use crate::databases; diff --git a/src/main.rs b/src/main.rs index 02b82ff..4e8ea56 100644 --- a/src/main.rs +++ b/src/main.rs @@ -15,9 +15,9 @@ mod lua; mod tasks; use std::process::exit; -use std::sync::mpsc::Sender; use std::sync::LazyLock; use std::sync::OnceLock; +use std::sync::mpsc::Sender; use tracing::debug; use tracing::error; @@ -30,8 +30,9 @@ use constants::HELP_STRING; use databases::DBKind; #[derive(Debug, Clone)] -pub struct CmdArgs { +pub struct ProgramProperties { // TODO bool_bucket: u8, + pub print_help: bool, pub nodiscord: bool, pub dbuser: Option, pub dbpass: Option, @@ -41,6 +42,7 @@ pub struct CmdArgs { pub dbkind: Option, pub use_cli: bool, pub lua_engine_count: Option, + pub log_level: Option, pub connect_remote: bool, pub remote_address: Option, pub remote_port: Option, @@ -53,7 +55,7 @@ pub struct CmdArgs { /// at the start of execution and will never be written to again /// /// So we just force the once lock when we want to load the args like we would for a once lock -pub static CMD_ARGS: LazyLock = LazyLock::new(get_cmd_args); +pub static PROPERTIES: LazyLock = LazyLock::new(get_cmd_args); /// A simple no questions asked way to shut down the bot gracefully // maybe we should include a reason enum that includes other info? no that should be handled wherever the message is sent from @@ -71,25 +73,33 @@ pub fn shutdown_handler() { if let Err(err) = SHUTDOWN_SENDER.get().unwrap().send(()) { error!("Failed to send shutdown signal: {err}"); } + debug!("Shutdown requested!"); } -pub fn get_cmd_args() -> CmdArgs { +pub fn get_cmd_args() -> ProgramProperties { info!("Grabbing commandline input."); - let mut nodiscord = false; - let mut dbuser = None; - let mut dbpass = None; - let mut dbaddress = None; - let mut dbport = None; - let mut dbpath = None; - let mut dbkind = None; - let mut use_cli = false; - let mut lua_engine_count = None; - let mut connect_remote = false; - let mut remote_address = None; - let mut remote_port = None; - let mut remote_key = None; - let mut remote_key_type = None; + fn string_to_bool(str: String) -> bool { + str.to_lowercase().trim() == "true" + } + + // TODO macro this? could simplify the amount of str definitions and repetitiveness + let mut print_help = std::env::var("print_help").map(string_to_bool).unwrap_or(false); + let mut nodiscord = std::env::var("nodiscord").map(string_to_bool).unwrap_or(false); + let mut dbuser = std::env::var("dbuser").map_or(None, |val| Some(val)); + let mut dbpass = std::env::var("dbpass").map_or(None, |val| Some(val)); + let mut dbaddress = std::env::var("dbaddress").map_or(None, |val| Some(val)); + let mut dbport = std::env::var("dbport").map_or(None, |val| Some(val)); + let mut dbpath = std::env::var("dbpath").map_or(None, |val| Some(val)); + let mut dbkind = std::env::var("dbkind").map_or(None, |val| Some(val)); + let mut use_cli = std::env::var("use_cli").map(string_to_bool).unwrap_or(false); + let mut lua_engine_count = std::env::var("lua_engine_count").map_or(None, |val| Some(val)); + let mut log_level = std::env::var("log_level").map_or(None, |val| Some(val)); + let mut connect_remote = std::env::var("connect_remote").map(string_to_bool).unwrap_or(false); + let mut remote_address = std::env::var("remote_address").map_or(None, |val| Some(val)); + let mut remote_port = std::env::var("remote_port").map_or(None, |val| Some(val)); + let mut remote_key = std::env::var("remote_key").map_or(None, |val| Some(val)); + let mut remote_key_type = std::env::var("remote_key_type").map_or(None, |val| Some(val)); let mut args = std::env::args().peekable(); @@ -102,14 +112,10 @@ pub fn get_cmd_args() -> CmdArgs { } } + // TODO basic type/validity/enum checking via helper functions while let Some(item) = args.next() { match item.to_lowercase().trim() { - // Execution args - "help" | "-h" | "--help" | "-help" | "/help" | "/h" | "?" | "/?" => { - info!("{HELP_STRING}"); - exit(0); - }, - // Data args + "help" | "-h" | "--help" | "-help" | "/help" | "/h" | "?" | "/?" => print_help = true, "nodiscord" => nodiscord = true, "dbuser" => dbuser = args.next(), "dbpass" => dbpass = args.next(), @@ -119,16 +125,30 @@ pub fn get_cmd_args() -> CmdArgs { "dbkind" => dbkind = args.next(), "use_cli" => use_cli = true, "lua_engine_count" => lua_engine_count = args.next(), + "log_level" => log_level = args.next(), "connect_remote" => connect_remote = true, "remote_address" => remote_address = args.next(), "remote_port" => remote_port = args.next(), "remote_key" => remote_key = args.next(), "remote_key_type" => remote_key_type = args.next(), - value => warn!("Unknown or misplaced value: {value}"), // TODO move help argument here? + value => { + warn!("Unknown or misplaced value: {value}"); // TODO make custom tracing message formatter that moves newlines forward to match the message offset from header info + warn!("Use argument help for more information.") + }, } } - CmdArgs { + let log_level = match log_level.unwrap_or_default().to_lowercase().trim() { + "trace" => Some(tracing_subscriber::filter::LevelFilter::TRACE), + "debug" => Some(tracing_subscriber::filter::LevelFilter::DEBUG), + "info" => Some(tracing_subscriber::filter::LevelFilter::INFO), + "warn" => Some(tracing_subscriber::filter::LevelFilter::WARN), + "error" => Some(tracing_subscriber::filter::LevelFilter::ERROR), + _ => None, + }; + + ProgramProperties { + print_help, nodiscord, dbuser, dbpass, @@ -138,6 +158,7 @@ pub fn get_cmd_args() -> CmdArgs { dbkind, use_cli, lua_engine_count, + log_level, connect_remote, remote_address, remote_port, @@ -148,24 +169,42 @@ pub fn get_cmd_args() -> CmdArgs { // TODO remove async from the main function and restrict its usage to just the discord bot // We can manually manage worker threads everywhere else, especially with the lua engines -#[tokio::main] -async fn main() { +//#[tokio::main] +fn main() { // why do we need to set global default again? - tracing_subscriber::fmt() - .compact() - .with_timer(tracing_subscriber::fmt::time::uptime()) - .with_ansi(true) - .with_level(true) - .with_file(cfg!(debug_assertions)) - .with_line_number(cfg!(debug_assertions)) - .with_thread_names(cfg!(debug_assertions)) - .with_max_level(if cfg!(debug_assertions) { LevelFilter::DEBUG } else { LevelFilter::INFO }) + + use tracing_subscriber::layer::SubscriberExt; + use tracing_subscriber::util::SubscriberInitExt; + + let (level_filter, level_filter_reload_handle) = + tracing_subscriber::reload::Layer::new(if cfg!(debug_assertions) { LevelFilter::DEBUG } else { LevelFilter::INFO }); + tracing_subscriber::registry() + .with(level_filter) + .with( + tracing_subscriber::fmt::Layer::default() + .compact() + .with_level(true) + .with_file(cfg!(debug_assertions)) + .with_line_number(cfg!(debug_assertions)) + // TODO make configurable + .with_timer(tracing_subscriber::fmt::time::uptime()) + // TODO detect ansi capabilities do something to allow colors if none supported, if fails just disable colors + .with_ansi(true) + // TODO allow in release? + .with_thread_names(cfg!(debug_assertions)), + ) .init(); - info!("Logging initialized."); // Manually init the lazy lock - LazyLock::force(&CMD_ARGS); - debug!("{:?}", *CMD_ARGS); + LazyLock::force(&PROPERTIES); + //debug!("{:?}", *PROPERTIES); + + if let Some(val) = PROPERTIES.log_level { + level_filter_reload_handle.modify(|filter| *filter = val).unwrap(); + } + + // Store non fatal errors from behind this point to be logged and handled afterwards, for example ansi failures, log file failures, or invalid properties + // It is assumed there is no failure state before this point, if something goes wrong, shamble onwards let (shutdown_tx, shutdown_rx) = std::sync::mpsc::channel(); #[allow(clippy::unwrap_used)] // it is impossible for this to be an error @@ -177,7 +216,12 @@ async fn main() { error!("Error setting Ctrl-C handler: {err}"); } - let dbkind = if let Some(arg) = &CMD_ARGS.dbkind { + if PROPERTIES.print_help { + info!("{HELP_STRING}"); + exit(0); + } + + let dbkind = if let Some(arg) = &PROPERTIES.dbkind { info!("Loading database accessor."); match arg.to_lowercase().trim() { "access" => DBKind::Access, @@ -208,7 +252,7 @@ async fn main() { // TODO if the db health check fails or is old attempt backup and upgrade info!("Loading stock commands."); - commands::insert_stock().await; + commands::insert_stock(); info!("Loading rust dynamic commands."); commands::insert_rust(); @@ -241,9 +285,10 @@ async fn main() { // exit(1); //} - if CMD_ARGS.use_cli { + // Spawn new thread for cli, if no terminal is detected exit program + if PROPERTIES.use_cli { // TODO create cli interface for bot } - bot::start().await; + bot::start(); } diff --git a/src/tasks.rs b/src/tasks.rs index 0fe1685..516ed13 100644 --- a/src/tasks.rs +++ b/src/tasks.rs @@ -60,17 +60,21 @@ async fn forum_checker() { let mut interval = tokio::time::interval(Duration::from_millis(16)); loop { + + //check_forum().await; + interval.tick().await; } } +// check forums listed in DB, grab all messages, rank based on emotes, mark old/completed posts pub async fn check_forum() {} async fn shutdown_monitor() { // TODO tokio interval waiting may be more efficient than just a recv on an std mpsc // tokio mpsc may be better than both as the std mpsc may halt the tokio task // without letting it yeild properly - + //let mut interval = tokio::time::interval(Duration::from_millis(16)); #[allow(clippy::unwrap_used)] // it is impossible for this to be an error let lock = SHUTDOWN_RECEIVER.get().unwrap().lock().await; @@ -105,7 +109,7 @@ async fn status_timer(shard_runners: Arc ]; let mut interval = tokio::time::interval(Duration::from_secs(20 * 60)); let mut rand = rand::rngs::SmallRng::from_os_rng(); - + loop { let activity = rand.random_range(0..activities.len() - 1); for (_shard_id, shard_info) in shard_runners.lock().await.iter() { From baa14a47e6a118158ef1f696872e040fceeef16d Mon Sep 17 00:00:00 2001 From: deepCurse Date: Mon, 24 Mar 2025 18:45:43 -0300 Subject: [PATCH 4/5] .env file support introduced a macro to make management of program properties easier program properties are obtained in this order: environment, .env file, commandline arguments a configuration file will be included at a later date and will be loaded between .env and commandline args --- .gitignore | 2 + Cargo.lock | 39 ---- Cargo.toml | 5 +- src/arguments.rs | 4 +- src/bot.rs | 139 ++------------ src/databases/sqlite.rs | 2 +- src/lua.rs | 2 +- src/main.rs | 404 ++++++++++++++++++++++++++++------------ src/tasks.rs | 3 +- 9 files changed, 305 insertions(+), 295 deletions(-) diff --git a/.gitignore b/.gitignore index c68512b..7599ba2 100644 --- a/.gitignore +++ b/.gitignore @@ -1,7 +1,9 @@ bot_token* +.env /target /.vscode /.rust-analyzer **/*.rs.bk *.pdb + diff --git a/Cargo.lock b/Cargo.lock index 8b0683c..a242c30 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -17,15 +17,6 @@ version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627" -[[package]] -name = "aho-corasick" -version = "1.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" -dependencies = [ - "memchr", -] - [[package]] name = "android-tzdata" version = "0.1.1" @@ -1032,7 +1023,6 @@ dependencies = [ "mlua", "num_cpus", "rand 0.9.0", - "regex", "serenity", "sqlite", "tokio", @@ -1257,35 +1247,6 @@ dependencies = [ "bitflags 2.6.0", ] -[[package]] -name = "regex" -version = "1.11.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191" -dependencies = [ - "aho-corasick", - "memchr", - "regex-automata", - "regex-syntax", -] - -[[package]] -name = "regex-automata" -version = "0.4.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908" -dependencies = [ - "aho-corasick", - "memchr", - "regex-syntax", -] - -[[package]] -name = "regex-syntax" -version = "0.8.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" - [[package]] name = "reqwest" version = "0.11.27" diff --git a/Cargo.toml b/Cargo.toml index 88e0d58..e2151ea 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,11 +17,14 @@ serenity = "0.12.4" num_cpus = "1.16.0" rand = "0.9.0" -regex = "1.10.4" +#regex = "1.10.4" sqlite = { version = "0.36.1", features = ["bundled"] } ctrlc = "3.4.5" [features] +dot_env_file = [] + +default = ["dot_env_file"] # 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 = [] diff --git a/src/arguments.rs b/src/arguments.rs index c18e3dd..3ffc51f 100644 --- a/src/arguments.rs +++ b/src/arguments.rs @@ -4,7 +4,9 @@ use std::collections::HashMap; pub struct ArgumentStorage(HashMap); impl ArgumentStorage { - pub fn new() -> Self { Self(HashMap::new()) } + pub fn new() -> Self { + Self(HashMap::new()) + } pub fn add_argument() {} } diff --git a/src/bot.rs b/src/bot.rs index 53dc9e2..fa67d69 100644 --- a/src/bot.rs +++ b/src/bot.rs @@ -17,6 +17,7 @@ use tracing::info; use tracing::warn; use crate::PROPERTIES; +use crate::commands; use crate::commands::COMMANDS; use crate::constants; use crate::tasks; @@ -32,126 +33,6 @@ pub struct BotHandler; unsafe impl Sync for BotHandler {} unsafe impl Send for BotHandler {} -const ESCAPE_CHAR: char = '\\'; - -fn group(string: &dyn ToString) -> Vec { - let string = string.to_string(); // binding - - let mut argument_results = vec![]; - - let mut quote_depth = 0; - let mut double_quote_depth = 0; - let mut bucket = String::new(); - //let mut bucket2 = String::new(); - - let mut chars = string.chars().peekable(); - while let Some(c) = chars.next() { - //dbg!(c); - - // skip current char and move next char into the bucket, doesnt matter what it is, its been escaped - if c == ESCAPE_CHAR { - if let Some(c) = chars.next() { - bucket.push(c); - } else { - error!("Expected another character after escape char"); - bucket.push(ESCAPE_CHAR); - } - continue; - } - - //dbg!(quote_depth); - //dbg!(double_quote_depth); - - if c == '\'' { - // outside quotes - if quote_depth % 2 == 0 { - quote_depth += 1; - } else { - // inside quotes - quote_depth -= 1; - } - // outside double quotes - if double_quote_depth % 2 == 0 { - continue; // continue to avoid adding this to the bucket - } - } - - if c == '"' { - // outside double quotes - if double_quote_depth % 2 == 0 { - double_quote_depth += 1; - } else { - // inside double quotes - double_quote_depth -= 1; - } - // outside quotes - if quote_depth % 2 == 0 { - continue; // continue to avoid adding this to the bucket - } - } - - //dbg!(&bucket); - - // if in quotes of some kind, just add to the bucket, if not we must move the bucket to the result and empty the bucket - if c == ' ' { - if quote_depth == 0 && double_quote_depth == 0 { - if !bucket.is_empty() { - argument_results.push(bucket.clone()); - bucket.clear(); - } - //dbg!(&argument_results); - continue; - } - } - - bucket.push(c); - } - - if !bucket.is_empty() { - argument_results.push(bucket); - } - - argument_results -} - -fn bash(str_vec: Vec) -> Vec { - let mut return_vec = vec![]; - - let mut str_vec = str_vec.iter(); - - //let mut enabled = true; - while let Some(item) = str_vec.next() { - // abandon parsing since a bare -- means end parsing - if item.len() == 2 && &item[..2] == "--" { - return_vec.push(item.to_string()); - while let Some(item) = str_vec.next() { - return_vec.push(item.to_string()); - } - break; - } - - match (item.get(0..1), item.get(1..1)) { - // long option - (Some("-"), Some("-")) => { - for (cidx, c) in item.char_indices() { - if c == '=' { - let (rhs, lhs) = item.split_at(cidx); - dbg!(lhs); - return_vec.push(rhs.to_string()); - } - } - }, - // short option - (Some("-"), Some(s)) if s != "-" => { - - }, - // not an option, just ignore it - _ => return_vec.push(item.to_string()), - } - } - - return_vec -} #[serenity::async_trait] impl EventHandler for BotHandler { @@ -166,7 +47,7 @@ impl EventHandler for BotHandler { if msg.content.starts_with(constants::COMMAND_PREFIX) { offset = constants::COMMAND_PREFIX.len(); } else if msg.content.starts_with(constants::CONSTANT_PREFIX) { - offset = constants::COMMAND_PREFIX.len(); + offset = constants::CONSTANT_PREFIX.len(); } else if msg.content.starts_with(format!("<@{}>", ctx.cache.current_user().id).as_str()) { // Magic numbers: +1 is for ilog10, +3 is for the <@> characters offset = ctx.cache.current_user().id.get().checked_ilog10().unwrap_or(0) as usize + 1 + 3; @@ -174,6 +55,11 @@ impl EventHandler for BotHandler { return; } + // whitespace should be handled by group() + //if msg.content.chars().nth(offset).map_or(false, char::is_whitespace) { + // offset += 1; + //} + dbg!(&offset); if DO_SHUTDOWN.load(std::sync::atomic::Ordering::SeqCst) { @@ -184,8 +70,8 @@ impl EventHandler for BotHandler { return; } - let grouped = group(&&msg.content[offset..]); - let bashed = bash(grouped); + //let grouped = group(&&msg.content[offset..]); + //let bashed = bash(grouped); let target_cmd_name = dbg!(&msg.content[offset..]); @@ -194,10 +80,11 @@ impl EventHandler for BotHandler { // TODO fix this unwrap if let Some(command) = COMMANDS.read().unwrap().get(target_cmd_name) { match (msg.guild_id, &command.run_guild_command, &command.run_dm_command) { - //(Some(gid), Some(commands::CommandFnKind::Lua(())), _) => todo!(), (Some(gid), Some(commands::CommandFnKind::Rust(gcmd)), _) => (gcmd)(ctx, msg, Some(gid)), - //(None, _, Some(commands::CommandFnKind::Lua(()))) => todo!(), (None, _, Some(commands::CommandFnKind::Rust(dcmd))) => (dcmd)(ctx, msg, None), + + (Some(gid), Some(commands::CommandFnKind::Lua(gcmd)), _) => todo!(), + (None, _, Some(commands::CommandFnKind::Lua(dcmd))) => todo!(), _ => (), } } @@ -233,7 +120,7 @@ pub fn start() { // Load this at runtime so the key will not be stored in the binary? at the very least we could disguise the string as garbage data so it cannot be easily extracted via static analysis, it seems like the bot library only uses references as well, so it should not be stored plaintext in memory either // Will need a protected storage medium for the token, if we get to that point the user will need to suggest something, storing the token is not my responsibility, databases might be a good alternative? - const TOKEN: &'static str = if cfg!(debug_assertions) { + const TOKEN: &str = if cfg!(debug_assertions) { include_str!("bot_token.dev") } else { include_str!("bot_token.prod") diff --git a/src/databases/sqlite.rs b/src/databases/sqlite.rs index 6efa7bc..1d58f12 100644 --- a/src/databases/sqlite.rs +++ b/src/databases/sqlite.rs @@ -5,8 +5,8 @@ use sqlite::Connection; use tracing::error; use tracing::info; -use crate::errors::DatabaseStoredError; use crate::PROPERTIES; +use crate::errors::DatabaseStoredError; use super::DatabaseAccessor; diff --git a/src/lua.rs b/src/lua.rs index c4bd642..f4658fb 100644 --- a/src/lua.rs +++ b/src/lua.rs @@ -205,11 +205,11 @@ fn set_logging(lua: &Lua, globals: &Table) { } mod rust_lua_functions { - use mlua::prelude::LuaResult; use mlua::ExternalError; use mlua::Lua; use mlua::Value; use mlua::Variadic; + use mlua::prelude::LuaResult; use tracing::info; use crate::lua::value_to_string; diff --git a/src/main.rs b/src/main.rs index 4e8ea56..661e6f1 100644 --- a/src/main.rs +++ b/src/main.rs @@ -29,27 +29,7 @@ use constants::HELP_STRING; use databases::DBKind; -#[derive(Debug, Clone)] -pub struct ProgramProperties { - // TODO bool_bucket: u8, - pub print_help: bool, - pub nodiscord: bool, - pub dbuser: Option, - pub dbpass: Option, - pub dbaddress: Option, - pub dbport: Option, - pub dbpath: Option, - pub dbkind: Option, - pub use_cli: bool, - pub lua_engine_count: Option, - pub log_level: Option, - pub connect_remote: bool, - pub remote_address: Option, - pub remote_port: Option, - pub remote_key: Option, - pub remote_key_type: Option, -} - +// TODO remove this from static memory /// In most cases this would just be a once lock or similar /// however i dont like needing to unwrap it everywhere in the program after i know for a fact it was already initialized /// at the start of execution and will never be written to again @@ -62,6 +42,272 @@ pub static PROPERTIES: LazyLock = LazyLock::new(get_cmd_args) // TODO custom panic handler? pub static SHUTDOWN_SENDER: OnceLock> = OnceLock::new(); +fn string_to_bool(str: String) -> bool { + str.to_lowercase().trim() == "true" +} +fn string_to_log_level(str: String) -> Option { + match str.to_lowercase().trim() { + "trace" => Some(tracing_subscriber::filter::LevelFilter::TRACE), + "debug" => Some(tracing_subscriber::filter::LevelFilter::DEBUG), + "info" => Some(tracing_subscriber::filter::LevelFilter::INFO), + "warn" => Some(tracing_subscriber::filter::LevelFilter::WARN), + "error" => Some(tracing_subscriber::filter::LevelFilter::ERROR), + _ => None, + } +} + +/// ### This macro has a "header" of sorts which is used to configure the macro +/// --- +/// This is the first line of an example usage +/// ``` +/// #[derive(Debug, Clone)] pub struct ProgramProperties; +/// ``` +/// `#[]` is required and you may put `,` seperated attributes inside +/// +/// `pub` is omittable but will be the visibility of the generated struct +/// +/// `struct` is required for readability sake +/// +/// `ProgramProperties` is any identifier you want and will be used to name the generated structure +/// +/// --- +/// The second line is identical in all ways to the first line, however instead of creating a struct we create a function +/// ``` +/// #[] pub fn get_cmd_args; +/// ``` +/// +/// --- +/// These two lines are used to name variables used later in the macro, the selected names being `item` and `arglist` +/// ``` +/// envfixer item; +/// argumentfixer arglist; +/// ``` +/// +/// --- +/// Just like with the fn and struct you may change the visibility before the items name +/// +/// The type within the structure is defined afterwards just like a normal struct +/// +/// However the next item must be `,` and afterwards we shift to assigning data +/// +/// `optnames`: is an 'array' of &str values which will be transparently used in a match statement when reading `env::args` +/// +/// `envfixer`: is a 'datafixer' used to map the data to the correct type, optionally using the variable name defined in the header +/// +/// `argumentfixer`: is identical to `envfixer` but used for the `env::args` input, in this case all we want is whether the option is present or not +/// ``` +/// pub print_help: bool, +/// optnames: ["h", "help","-h", "--help", "-help","/h", "/help","/?", "?"], +/// envfixer: item.is_some_and(|x| x.to_lowercase().trim() == "true"), +/// argumentfixer: true; +/// pub nodiscord: bool, +/// optnames: [], +/// envfixer: item.is_some_and(string_to_bool), +/// argumentfixer: true; +/// pub dbuser : Option, +/// optnames: [], +/// envfixer: item, +/// argumentfixer: arglist.next(); +/// pub dbpass : Option, +/// optnames: [], +/// envfixer: item, +/// argumentfixer: arglist.next(); +/// pub dbaddress : Option, +/// optnames: [], +/// envfixer: item, +/// argumentfixer: arglist.next(); +/// pub dbport: Option, +/// optnames: [], +/// envfixer: item, +/// argumentfixer: arglist.next(); +/// pub dbpath: Option, +/// optnames: [], +/// envfixer: item, +/// argumentfixer: arglist.next(); +/// pub dbkind: Option, +/// optnames: [], +/// envfixer: item, +/// argumentfixer: arglist.next(); +/// ``` +/// +/// This macro is licensed under [The Unlicense](https://spdx.org/licenses/Unlicense.html) +/// +macro_rules! program_properties { + { + #[$($sattr:meta),*] $svis:vis struct $sident:ident; + #[$($fattr:meta),*] $fvis:vis fn $fident:ident; + envfixer $vname:ident; + argumentfixer $vargsname:ident; + $($itemvis:vis $itemident:ident: $itemtype:ty, optnames: [$($options:literal),*], envfixer: $envfixer:expr, argumentfixer: $argumentfixer:expr;)+ + } => { + $(#[$sattr])* $svis struct $sident { + $($itemvis $itemident: $itemtype,)+ + } + + $(#[$fattr])* $fvis fn $fident() -> $sident { + info!("Initializing with env variables."); + let mut resval = $sident { + $($itemident: { + #[allow(unused)] + let $vname = std::env::var(stringify!($itemident)).ok(); + $envfixer + },)+ + }; + + #[cfg(feature = "dot_env_file")] { + info!("Grabbing .env file input."); + + if let Ok(cdir) = std::env::current_dir() { + if let Ok(string) = std::fs::read_to_string(cdir.join(".env")) { + let mut strbuf = vec![]; + let mut bucket = String::new(); + for c in string.chars() { + if c == '\n' { + strbuf.push(bucket); + bucket = String::new(); + } else { + bucket.push(c); + } + } + if !bucket.trim().is_empty() { + strbuf.push(bucket); + } + + let mut eqvec = vec![]; + for s in &strbuf { + if let Some(eq) = s.split_once('=') { + eqvec.push(eq); + } else { + warn!("Invalid value in .env file! {s}"); + } + } + + for (lhs,rhs) in eqvec { + match lhs.trim() { + $(stringify!($itemident) => { + #[allow(unused)] + let $vname = Some(rhs.to_string()); + resval.$itemident = $envfixer; + },)+ + value => { + warn!("Unknown or misplaced value: {value}={rhs}"); // TODO make custom tracing message formatter that moves newlines forward to match the message offset from header info + warn!("Use argument help for more information."); + }, + } + } + + } + }; + + + } + + info!("Grabbing commandline options."); + let mut args = std::env::args().peekable(); + + // The first arg is the path to the executable on all systems that i know of + // But this is not always guaranteed, however this program does not expect file paths as the first element so we can safely skip it if the path exists + // We could check this to see if it matches our environment executable path but that needs too much error handling for something like this + if let Some(argpath) = args.peek() { + if std::path::PathBuf::from(argpath).exists() { + args.next(); + } + } + + while let Some(item) = args.next() { + match item.trim() { + $($($options)|* | stringify!($itemident) => { + #[allow(unused)] + let $vargsname = &mut args; + resval.$itemident = $argumentfixer; + },)+ + value => { + warn!("Unknown or misplaced value: {value}"); // TODO make custom tracing message formatter that moves newlines forward to match the message offset from header info + warn!("Use argument help for more information."); + }, + } + }; + + resval + } + + }; +} + +// TODO basic type/validity/enum checking via helper functions +program_properties! { + + #[derive(Debug, Clone)] pub struct ProgramProperties; + #[] pub fn get_cmd_args; + envfixer item; + argumentfixer arglist; + + pub print_help: bool, + optnames: ["h", "help","-h", "--help", "-help","/h", "/help","/?", "?"], + envfixer: item.is_some_and(string_to_bool), + argumentfixer: true; + pub nodiscord: bool, + optnames: [], + envfixer: item.is_some_and(string_to_bool), + argumentfixer: true; + pub dbuser : Option, + optnames: [], + envfixer: item, + argumentfixer: arglist.next(); + pub dbpass : Option, + optnames: [], + envfixer: item, + argumentfixer: arglist.next(); + pub dbaddress : Option, + optnames: [], + envfixer: item, + argumentfixer: arglist.next(); + pub dbport: Option, + optnames: [], + envfixer: item, + argumentfixer: arglist.next(); + pub dbpath: Option, + optnames: [], + envfixer: item, + argumentfixer: arglist.next(); + pub dbkind: Option, + optnames: [], + envfixer: item, + argumentfixer: arglist.next(); + pub use_cli: bool, + optnames: [], + envfixer: item.is_some_and(string_to_bool), + argumentfixer: true; + pub lua_engine_count: Option, + optnames: [], + envfixer: item, + argumentfixer: arglist.next(); + pub log_level: Option, + optnames: [], + envfixer: string_to_log_level(item.unwrap_or_default()), + argumentfixer: string_to_log_level(arglist.next().unwrap_or_default()); + pub connect_remote: bool, + optnames: [], + envfixer: item.is_some_and(string_to_bool), + argumentfixer: true; + pub remote_address: Option, + optnames: [], + envfixer: item, + argumentfixer: arglist.next(); + pub remote_port: Option, + optnames: [], + envfixer: item, + argumentfixer: arglist.next(); + pub remote_key: Option, + optnames: [], + envfixer: item, + argumentfixer: arglist.next(); + pub remote_key_type: Option, + optnames: [], + envfixer: item, + argumentfixer: arglist.next(); +} + #[allow(clippy::missing_panics_doc)] pub fn shutdown_handler() { static SHUTDOWN_COUNT: std::sync::atomic::AtomicU8 = std::sync::atomic::AtomicU8::new(1); @@ -76,112 +322,21 @@ pub fn shutdown_handler() { debug!("Shutdown requested!"); } -pub fn get_cmd_args() -> ProgramProperties { - info!("Grabbing commandline input."); - - fn string_to_bool(str: String) -> bool { - str.to_lowercase().trim() == "true" - } - - // TODO macro this? could simplify the amount of str definitions and repetitiveness - let mut print_help = std::env::var("print_help").map(string_to_bool).unwrap_or(false); - let mut nodiscord = std::env::var("nodiscord").map(string_to_bool).unwrap_or(false); - let mut dbuser = std::env::var("dbuser").map_or(None, |val| Some(val)); - let mut dbpass = std::env::var("dbpass").map_or(None, |val| Some(val)); - let mut dbaddress = std::env::var("dbaddress").map_or(None, |val| Some(val)); - let mut dbport = std::env::var("dbport").map_or(None, |val| Some(val)); - let mut dbpath = std::env::var("dbpath").map_or(None, |val| Some(val)); - let mut dbkind = std::env::var("dbkind").map_or(None, |val| Some(val)); - let mut use_cli = std::env::var("use_cli").map(string_to_bool).unwrap_or(false); - let mut lua_engine_count = std::env::var("lua_engine_count").map_or(None, |val| Some(val)); - let mut log_level = std::env::var("log_level").map_or(None, |val| Some(val)); - let mut connect_remote = std::env::var("connect_remote").map(string_to_bool).unwrap_or(false); - let mut remote_address = std::env::var("remote_address").map_or(None, |val| Some(val)); - let mut remote_port = std::env::var("remote_port").map_or(None, |val| Some(val)); - let mut remote_key = std::env::var("remote_key").map_or(None, |val| Some(val)); - let mut remote_key_type = std::env::var("remote_key_type").map_or(None, |val| Some(val)); - - let mut args = std::env::args().peekable(); - - // The first arg is the path to the executable on all systems that i know of - // But this is not always guaranteed, however this program does not expect file paths as the first element so we can safely skip it if the path exists - // We could check this to see if it matches our environment executable path but that needs too much error handling for something like this - if let Some(argpath) = args.peek() { - if std::path::PathBuf::from(argpath).exists() { - args.next(); - } - } - - // TODO basic type/validity/enum checking via helper functions - while let Some(item) = args.next() { - match item.to_lowercase().trim() { - "help" | "-h" | "--help" | "-help" | "/help" | "/h" | "?" | "/?" => print_help = true, - "nodiscord" => nodiscord = true, - "dbuser" => dbuser = args.next(), - "dbpass" => dbpass = args.next(), - "dbaddress" => dbaddress = args.next(), - "dbport" => dbport = args.next(), - "dbpath" => dbpath = args.next(), - "dbkind" => dbkind = args.next(), - "use_cli" => use_cli = true, - "lua_engine_count" => lua_engine_count = args.next(), - "log_level" => log_level = args.next(), - "connect_remote" => connect_remote = true, - "remote_address" => remote_address = args.next(), - "remote_port" => remote_port = args.next(), - "remote_key" => remote_key = args.next(), - "remote_key_type" => remote_key_type = args.next(), - value => { - warn!("Unknown or misplaced value: {value}"); // TODO make custom tracing message formatter that moves newlines forward to match the message offset from header info - warn!("Use argument help for more information.") - }, - } - } - - let log_level = match log_level.unwrap_or_default().to_lowercase().trim() { - "trace" => Some(tracing_subscriber::filter::LevelFilter::TRACE), - "debug" => Some(tracing_subscriber::filter::LevelFilter::DEBUG), - "info" => Some(tracing_subscriber::filter::LevelFilter::INFO), - "warn" => Some(tracing_subscriber::filter::LevelFilter::WARN), - "error" => Some(tracing_subscriber::filter::LevelFilter::ERROR), - _ => None, - }; - - ProgramProperties { - print_help, - nodiscord, - dbuser, - dbpass, - dbaddress, - dbport, - dbpath, - dbkind, - use_cli, - lua_engine_count, - log_level, - connect_remote, - remote_address, - remote_port, - remote_key, - remote_key_type, - } -} - // TODO remove async from the main function and restrict its usage to just the discord bot // We can manually manage worker threads everywhere else, especially with the lua engines //#[tokio::main] fn main() { // why do we need to set global default again? - use tracing_subscriber::layer::SubscriberExt; - use tracing_subscriber::util::SubscriberInitExt; - let (level_filter, level_filter_reload_handle) = tracing_subscriber::reload::Layer::new(if cfg!(debug_assertions) { LevelFilter::DEBUG } else { LevelFilter::INFO }); - tracing_subscriber::registry() - .with(level_filter) - .with( - tracing_subscriber::fmt::Layer::default() + { + use tracing_subscriber::layer::SubscriberExt; + use tracing_subscriber::util::SubscriberInitExt; + tracing_subscriber::registry() + .with(level_filter) + .with( + tracing_subscriber::fmt::Layer::default() .compact() .with_level(true) .with_file(cfg!(debug_assertions)) @@ -189,15 +344,16 @@ fn main() { // TODO make configurable .with_timer(tracing_subscriber::fmt::time::uptime()) // TODO detect ansi capabilities do something to allow colors if none supported, if fails just disable colors - .with_ansi(true) + .with_ansi(true) // TODO allow in release? .with_thread_names(cfg!(debug_assertions)), - ) - .init(); + ) + .init(); + } - // Manually init the lazy lock + // Manually init the lazy lock here rather than later for reliability sake LazyLock::force(&PROPERTIES); - //debug!("{:?}", *PROPERTIES); + debug!("{:?}", *PROPERTIES); if let Some(val) = PROPERTIES.log_level { level_filter_reload_handle.modify(|filter| *filter = val).unwrap(); diff --git a/src/tasks.rs b/src/tasks.rs index 516ed13..ecafed5 100644 --- a/src/tasks.rs +++ b/src/tasks.rs @@ -60,9 +60,8 @@ async fn forum_checker() { let mut interval = tokio::time::interval(Duration::from_millis(16)); loop { - //check_forum().await; - + interval.tick().await; } } From fcd2c3c35d6fde32cd1e12519b3580dfbf9fb546 Mon Sep 17 00:00:00 2001 From: deepCurse Date: Tue, 25 Mar 2025 01:18:05 -0300 Subject: [PATCH 5/5] Polishing the turd --- src/constants.rs | 72 +++++++++++++++++++----- src/databases/mod.rs | 2 +- src/main.rs | 127 ++++++++++++++++++++++++++++--------------- 3 files changed, 142 insertions(+), 59 deletions(-) diff --git a/src/constants.rs b/src/constants.rs index c589ce9..52488c9 100644 --- a/src/constants.rs +++ b/src/constants.rs @@ -1,11 +1,12 @@ #![allow(unused)] -pub const VERSION: u16 = 0; +/// TODO semver +pub const VERSION: u16 = 20; // setup reminder or git hook of some kind to inc this? // This variable should never change for any reason, it will be used to identify this instance of the bot //pub const BOT_INTERNAL: &str = "npobot"; /// Should be limited to lowercase alphanumeric characters with periods, dashes, and underscores only -/// If you want to stylize the bot name use the fancy version +/// If you want to stylize the bot name use `BOT_NAME_FANCY` pub const BOT_NAME: &str = "nopalmo"; pub const BOT_NAME_FANCY: &str = "Nopalmo"; @@ -29,32 +30,77 @@ Nopalmo command help page: Description: Lorem Ipsum Dolar Sit Amet... -Arguments: - nodiscord Skips connecting to discord + See Usage section for more information about how this help menu functions + +Configuration: + print_help Prints this help information + h, help, -h, --help, -help, /h, /help, /?, ? + + nodiscord Shuts down the program when the first discord connection usually takes place Eventually this will connect to a simulated discord instance + -N, --no-discord dbuser The username to be used when connecting to a database type that requires one + -U, --db-user dbpass The password used like dbuser + -P, --db-pass dbaddress The network address of the desired database + -A, --db-address dbport The port to use with dbaddress + -P, --db-port dbpath The path to a unix socket to substitute an address or the path to an SQLite database file - dbkind The kind of database we will be using + -F, --db-path + dbkind The kind of database we will be using, one of `sqlite` `mariadb` `postgresql` `access` + -D, --db-kind use_cli Enables a CLI management interface for the bot + -i, --interactive use_gui The GUI version of the CLI + -g, --gui - lua_engine_count The number of lua execution engines to create, This defaults to the number of cores the computer has - This allows executing multiple lua tasks at once, including user commands and timer tasks + lua_engine_count The number of lua execution engines to create, This defaults to the number of cores the computer has + This allows executing multiple lua tasks at once, including user commands and timer tasks + -l, --lua-engine-count - connect_remote Connect to a remote instance of the bot - host_remote Host an instance of the bot for remote connections - remote_address The remote address of the bot, or the address you wish to host the bot on - remote_port The port used like remote_address - remote_key The path or literal value of the key required to authenticate with the bot on a remote connection - remote_key_type Decides the key type for remote connections + log_level The level the logging system will use, one of `trace` `debug` `info` `warning` `error` + -L, --log-level + + connect_remote Connect to a remote instance of the bot + -C, --remote-connection + host_remote Host an instance of the bot for remote connections + -H, --host-remote-connection + + remote_address The remote address of the bot, or the address you wish to host the bot on + -a, --remote-address + remote_port The port used like remote_address + -p, --remote-port + remote_key The path of the key required to authenticate with the bot on a remote connection + The remote_key_type flag decides what this value is used for + -k, --remote-key + remote_key_type Decides the key type for remote connections, one of `database` `password` or default `file` + -K, --remote-key-type Remote Connections: Remote access is disabled by default and must be enabled via the `connect_remote` or `host_remote` flags A key is required for remote connections, if you just need something quick use the `password` key type, the password must be the same for both server and client The key can be managed by the database as well, and the key type `database` will disable the `remote_key` variable and instead load authentication information from the server + +Usage: + ./nopalmo --help + ./nopalmo -H -a 0.0.0.0 -p 2022 -K file -k ./private.key + ./nopalmo -C -a 127.0.0.1 -p 2022 -K file -k ./public.key + + The Configuration section is layout like this: + Name Description + Commandline opts Option arguments + + Each configurable value can be changed in multiple ways for ease of use by the user + The order of configuration application is like so: + 1. System Environment + 2. .env file (if enabled by your maintainer using the dot_env_file feature) + 3. Configuration file (currently not supported) + 4. Commandline options + +Environment Variables: + Environment Variables may be set in addition or in place of commandline arguments "; diff --git a/src/databases/mod.rs b/src/databases/mod.rs index 54b07db..93e56a7 100644 --- a/src/databases/mod.rs +++ b/src/databases/mod.rs @@ -11,7 +11,7 @@ pub mod mariadb; pub mod postgres; pub mod sqlite; -#[derive(Debug)] +#[derive(Debug, PartialEq, PartialOrd, Clone, Copy)] pub enum DBKind { Access, SQLite, diff --git a/src/main.rs b/src/main.rs index 661e6f1..36aa520 100644 --- a/src/main.rs +++ b/src/main.rs @@ -23,6 +23,7 @@ use tracing::debug; use tracing::error; use tracing::info; use tracing::metadata::LevelFilter; +use tracing::trace; use tracing::warn; use constants::HELP_STRING; @@ -52,11 +53,27 @@ fn string_to_log_level(str: String) -> Option { "info" => Some(tracing_subscriber::filter::LevelFilter::INFO), "warn" => Some(tracing_subscriber::filter::LevelFilter::WARN), "error" => Some(tracing_subscriber::filter::LevelFilter::ERROR), - _ => None, + _ => { + error!("Invalid log level! {str} Choose one of `trace` `debug` `info` `warn` `error`"); + None + }, + } +} +fn string_to_db_kind(str: String) -> Option { + match str.to_lowercase().trim() { + "access" => Some(DBKind::Access), + "postgresql" => Some(DBKind::PostgreSQL), + "sqlite" => Some(DBKind::SQLite), + "mariadb" => Some(DBKind::MariaDB), + _ => { + error!("Invalid database kind! {str} Choose one of `sqlite` `mariadb` `postgresql` `access`"); + None + }, } } /// ### This macro has a "header" of sorts which is used to configure the macro +/// The variables will also be initialized with the system enviroment values, so try not to use unwrap or expect, use a sane default value /// --- /// This is the first line of an example usage /// ``` @@ -145,9 +162,10 @@ macro_rules! program_properties { } $(#[$fattr])* $fvis fn $fident() -> $sident { - info!("Initializing with env variables."); + debug!("Initializing with env variables."); let mut resval = $sident { $($itemident: { + trace!("Reading env: {}", stringify!($itemident)); #[allow(unused)] let $vname = std::env::var(stringify!($itemident)).ok(); $envfixer @@ -155,7 +173,7 @@ macro_rules! program_properties { }; #[cfg(feature = "dot_env_file")] { - info!("Grabbing .env file input."); + debug!("Grabbing .env file input."); if let Ok(cdir) = std::env::current_dir() { if let Ok(string) = std::fs::read_to_string(cdir.join(".env")) { @@ -185,6 +203,7 @@ macro_rules! program_properties { for (lhs,rhs) in eqvec { match lhs.trim() { $(stringify!($itemident) => { + trace!("Reading .env file: {}", stringify!($itemident)); #[allow(unused)] let $vname = Some(rhs.to_string()); resval.$itemident = $envfixer; @@ -202,7 +221,7 @@ macro_rules! program_properties { } - info!("Grabbing commandline options."); + debug!("Grabbing commandline options."); let mut args = std::env::args().peekable(); // The first arg is the path to the executable on all systems that i know of @@ -217,6 +236,7 @@ macro_rules! program_properties { while let Some(item) = args.next() { match item.trim() { $($($options)|* | stringify!($itemident) => { + trace!("Reading commandline options: {}", stringify!($itemident)); #[allow(unused)] let $vargsname = &mut args; resval.$itemident = $argumentfixer; @@ -247,63 +267,71 @@ program_properties! { envfixer: item.is_some_and(string_to_bool), argumentfixer: true; pub nodiscord: bool, - optnames: [], + optnames: ["-N", "--no-discord"], envfixer: item.is_some_and(string_to_bool), argumentfixer: true; pub dbuser : Option, - optnames: [], + optnames: ["-U", "--db-user"], envfixer: item, argumentfixer: arglist.next(); pub dbpass : Option, - optnames: [], + optnames: ["-P","--db-pass"], envfixer: item, argumentfixer: arglist.next(); pub dbaddress : Option, - optnames: [], + optnames: ["-A", "--db-address"], envfixer: item, argumentfixer: arglist.next(); pub dbport: Option, - optnames: [], + optnames: ["-P", "--db-port"], envfixer: item, argumentfixer: arglist.next(); pub dbpath: Option, - optnames: [], - envfixer: item, - argumentfixer: arglist.next(); - pub dbkind: Option, - optnames: [], + optnames: ["-F","--db-path"], envfixer: item, argumentfixer: arglist.next(); + pub dbkind: Option, + optnames: ["-D", "--db-kind"], + envfixer: item.and_then(string_to_db_kind), + argumentfixer: arglist.next().and_then(string_to_db_kind); pub use_cli: bool, - optnames: [], - envfixer: item.is_some_and(string_to_bool), + optnames: ["-i", "--interactive"], + envfixer: item.is_some_and(string_to_bool), + argumentfixer: true; + pub use_gui: bool, + optnames: ["-g", "--gui"], + envfixer: item.is_some_and(string_to_bool), argumentfixer: true; pub lua_engine_count: Option, - optnames: [], + optnames: ["-l", "--lua-engine-count"], envfixer: item, argumentfixer: arglist.next(); pub log_level: Option, - optnames: [], - envfixer: string_to_log_level(item.unwrap_or_default()), - argumentfixer: string_to_log_level(arglist.next().unwrap_or_default()); + optnames: ["-L", "--log-level"], + envfixer: item.and_then(string_to_log_level), + argumentfixer: arglist.next().and_then(string_to_log_level); pub connect_remote: bool, - optnames: [], + optnames: ["-C", "--remote-connection"], + envfixer: item.is_some_and(string_to_bool), + argumentfixer: true; + pub host_remote: bool, + optnames: ["-H", "--host-remote-connection"], envfixer: item.is_some_and(string_to_bool), argumentfixer: true; pub remote_address: Option, - optnames: [], + optnames: ["-a", "--remote-address"], envfixer: item, argumentfixer: arglist.next(); pub remote_port: Option, - optnames: [], + optnames: ["-p", "--remote-port"], envfixer: item, argumentfixer: arglist.next(); pub remote_key: Option, - optnames: [], + optnames: ["-k", "--remote-key"], envfixer: item, argumentfixer: arglist.next(); pub remote_key_type: Option, - optnames: [], + optnames: ["-K", "--remote-key-type"], envfixer: item, argumentfixer: arglist.next(); } @@ -328,8 +356,22 @@ pub fn shutdown_handler() { fn main() { // why do we need to set global default again? - let (level_filter, level_filter_reload_handle) = - tracing_subscriber::reload::Layer::new(if cfg!(debug_assertions) { LevelFilter::DEBUG } else { LevelFilter::INFO }); + // TODO ansi color support + + let (level_filter, level_filter_reload_handle) = tracing_subscriber::reload::Layer::new({ + if let Ok(var) = std::env::var("log_level") { + let Some(var) = string_to_log_level(var) else { + eprintln!("CRITICAL: Could not read `log_level` system environment variable for initialization phase!"); + eprintln!("CRITICAL: The value must be one of `trace` `debug` `info` `warn` `error` or it must not be present!"); + exit(-1); + }; + var + } else if cfg!(debug_assertions) { + LevelFilter::DEBUG + } else { + LevelFilter::INFO + } + }); { use tracing_subscriber::layer::SubscriberExt; use tracing_subscriber::util::SubscriberInitExt; @@ -351,17 +393,23 @@ fn main() { .init(); } + // TODO set logging output to a file via a reload handle like above, store logged data and flush to file when file is registered and opened for writing as to not lose any data + // Manually init the lazy lock here rather than later for reliability sake LazyLock::force(&PROPERTIES); + #[cfg(debug_assertions)] debug!("{:?}", *PROPERTIES); - if let Some(val) = PROPERTIES.log_level { - level_filter_reload_handle.modify(|filter| *filter = val).unwrap(); + // TODO make this a global function + if let Some(nval) = PROPERTIES.log_level { + if let Some(cval) = level_filter_reload_handle.clone_current() { + if cval != nval { + trace!("Switching from log level {cval} to {nval}"); + level_filter_reload_handle.modify(|filter| *filter = nval).unwrap(); + } + } } - // Store non fatal errors from behind this point to be logged and handled afterwards, for example ansi failures, log file failures, or invalid properties - // It is assumed there is no failure state before this point, if something goes wrong, shamble onwards - let (shutdown_tx, shutdown_rx) = std::sync::mpsc::channel(); #[allow(clippy::unwrap_used)] // it is impossible for this to be an error SHUTDOWN_SENDER.set(shutdown_tx.clone()).unwrap(); @@ -377,23 +425,12 @@ fn main() { exit(0); } - let dbkind = if let Some(arg) = &PROPERTIES.dbkind { - info!("Loading database accessor."); - match arg.to_lowercase().trim() { - "access" => DBKind::Access, - "postgresql" => DBKind::PostgreSQL, - "sqlite" => DBKind::SQLite, - "mariadb" => DBKind::MariaDB, - _ => { - error!("Invalid database kind! Choose one of `sqlite` `mariadb` `postgresql` `access`"); - exit(-1); - }, - } - } else { + let Some(dbkind) = PROPERTIES.dbkind else { warn!("You must select a database kind with `dbkind`"); exit(-1); }; + info!("Loading database accessor."); if let Err(err) = match dbkind { DBKind::Access => databases::access::create_interface(), DBKind::MariaDB => databases::mariadb::create_interface(),