Sync
This commit is contained in:
parent
578918b22f
commit
3da1cfc1f5
13 changed files with 352 additions and 120 deletions
5
Cargo.lock
generated
5
Cargo.lock
generated
|
@ -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",
|
||||
|
|
|
@ -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"
|
||||
|
|
51
docs/db-structure.md
Normal file
51
docs/db-structure.md
Normal file
|
@ -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
|
10
docs/sqlite.md
Normal file
10
docs/sqlite.md
Normal file
|
@ -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.
|
|
@ -4,10 +4,9 @@ use std::collections::HashMap;
|
|||
pub struct ArgumentStorage(HashMap<String, String>);
|
||||
|
||||
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)]
|
||||
|
|
209
src/bot.rs
209
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<Arc<ShardManager>> = 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<String> {
|
||||
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<String>) -> Vec<String> {
|
||||
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:?}");
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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())
|
||||
|
|
|
@ -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
|
||||
";
|
||||
";
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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>, /*>*/
|
||||
connection: Mutex<Connection>,
|
||||
}
|
||||
|
||||
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<dyn std::error::Error>> {
|
||||
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 {
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
133
src/main.rs
133
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<String>,
|
||||
pub dbpass: Option<String>,
|
||||
|
@ -41,6 +42,7 @@ pub struct CmdArgs {
|
|||
pub dbkind: Option<String>,
|
||||
pub use_cli: bool,
|
||||
pub lua_engine_count: Option<String>,
|
||||
pub log_level: Option<LevelFilter>,
|
||||
pub connect_remote: bool,
|
||||
pub remote_address: Option<String>,
|
||||
pub remote_port: Option<String>,
|
||||
|
@ -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<CmdArgs> = LazyLock::new(get_cmd_args);
|
||||
pub static PROPERTIES: LazyLock<ProgramProperties> = 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();
|
||||
}
|
||||
|
|
|
@ -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<Mutex<HashMap<ShardId, ShardRunnerInfo>
|
|||
];
|
||||
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() {
|
||||
|
|
Loading…
Add table
Reference in a new issue