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