diff --git a/Cargo.lock b/Cargo.lock index 1aa24f9..7c5fc4b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1517,18 +1517,18 @@ dependencies = [ [[package]] name = "thiserror" -version = "1.0.61" +version = "1.0.63" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c546c80d6be4bc6a00c0f01730c08df82eaa7a7a61f11d656526506112cc1709" +checksum = "c0342370b38b6a11b6cc11d6a805569958d54cfa061a29969c3b5ce2ea405724" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.61" +version = "1.0.63" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "46c3384250002a6d5af4d114f2845d37b57521033f30d5c3f46c4d70e1197533" +checksum = "a4558b58466b9ad7ca0f102865eccc95938dca1a74a856f2b57b6629050da261" dependencies = [ "proc-macro2", "quote", diff --git a/lua/loader.lua b/lua/loader.lua index 4a352bb..144e991 100644 --- a/lua/loader.lua +++ b/lua/loader.lua @@ -1,4 +1,4 @@ -commands.help = { +sys.registerCommand({ 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 @@ -9,13 +9,13 @@ commands.help = { permissions = nil, -- which discord permissions they need to run the command func = function(context, message, guildid) - for k,v in pairs(commands) do + for k, v in pairs(commands) do info("Help info for command", k) end end -} +}) -commands["help"].func() +sys.executeCommand("help", "E") -- exit(0) -- local seen={} @@ -37,4 +37,4 @@ commands["help"].func() -- end -- end --- dump(_G,"") \ No newline at end of file +-- dump(_G,"") diff --git a/src/bot.rs b/src/bot.rs index 658829a..39c2dc7 100644 --- a/src/bot.rs +++ b/src/bot.rs @@ -1,18 +1,19 @@ use std::collections::HashMap; use std::sync::atomic::Ordering; +use std::sync::{Arc, OnceLock}; -use crate::commands::{self, Command}; -use crate::{constants, tasks, DO_SHUTDOWN}; -use serenity::all::EventHandler; +use crate::commands::{self, COMMANDS}; +use crate::{constants, tasks, DO_SHUTDOWN, 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::*; -pub struct BotHandler { - // TODO use data field instead? - pub commands: HashMap, -} +pub static SHARD_MANAGER: OnceLock> = OnceLock::new(); + +pub struct BotHandler; unsafe impl Sync for BotHandler {} unsafe impl Send for BotHandler {} @@ -61,7 +62,7 @@ impl EventHandler for BotHandler { .await .unwrap(); - if let Some(command) = self.commands.get(&target_cmd_name) { + 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 { @@ -97,3 +98,47 @@ impl EventHandler for BotHandler { info!("Cache ready."); } } + +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."); + include_str!("bot_token.dev") + }; + + let intents = GatewayIntents::DIRECT_MESSAGES + | GatewayIntents::DIRECT_MESSAGE_REACTIONS + | GatewayIntents::GUILDS + | GatewayIntents::GUILD_MODERATION + // | GatewayIntents::GUILD_EMOJIS_AND_STICKERS + | GatewayIntents::GUILD_MEMBERS + | GatewayIntents::GUILD_MESSAGE_REACTIONS + | GatewayIntents::GUILD_MESSAGES + | GatewayIntents::MESSAGE_CONTENT; + + if *NO_DISCORD.get().unwrap() { + 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}`"), + }; + + SHARD_MANAGER.set(client.shard_manager.clone()).unwrap(); + + if let Err(why) = client.start_shards(2).await { + error!("Client error: {why:?}"); + } + } + + warn!("BOT EXITING"); +} diff --git a/src/commands.rs b/src/commands.rs index 7b092af..4c9d02c 100644 --- a/src/commands.rs +++ b/src/commands.rs @@ -1,11 +1,12 @@ -use std::{fmt::Debug, time::Duration}; +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}; -// pub type DmCommand = fn(Context, Message); -// pub type GuildCommand = fn(Context, Message, GuildId); +pub static COMMANDS: LazyLock>> = + LazyLock::new(|| RwLock::new(HashMap::new())); pub enum CommandFnKind { Lua(()), diff --git a/src/constants.rs b/src/constants.rs index bf4d161..d0d93e9 100644 --- a/src/constants.rs +++ b/src/constants.rs @@ -1,5 +1,12 @@ 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"; + pub const COMMAND_PREFIX: &'static str = ";"; // pub const SHORT_ARGUMENT_PREFIX: &'static str = "-"; // pub const LONG_ARGUMENT_PREFIX: &'static str = "--"; diff --git a/src/lua.rs b/src/lua.rs index 897400f..1f97bc7 100644 --- a/src/lua.rs +++ b/src/lua.rs @@ -1,7 +1,8 @@ use mlua::{Lua, LuaOptions, StdLib, Table, Variadic}; -use tracing::{debug, error, info, trace, warn}; +use tracing::info; + +use crate::constants::{CAT_FUN, CAT_GEN, CAT_INF, CAT_MOD, VERSION}; -use crate::{constants::VERSION, CAT_FUN, CAT_GEN, CAT_HID, CAT_INF, CAT_MOD}; pub fn initialize() -> Lua { // let mut runtimes = vec![]; @@ -36,15 +37,6 @@ pub fn initialize() -> Lua { set_logging(&lua, &globals); - lua.load(include_str!("../lua/loader.lua")) - .set_name("lua init script") - .exec() - .unwrap(); - - lua.sandbox(true).unwrap(); - - // return; - // runtimes.push(lua); // } @@ -71,6 +63,7 @@ fn clean_global(#[cfg(not(debug_assertions))] lua: &Lua, globals: &Table) { // we should be using the logging functions instead of raw print globals.raw_remove("print").unwrap(); } + fn prepare_global(lua: &Lua, globals: &Table) { let table = lua.create_table().unwrap(); table.set("info", CAT_INF).unwrap(); @@ -80,7 +73,12 @@ fn prepare_global(lua: &Lua, globals: &Table) { globals.set("commandCategory", table).unwrap(); let table = lua.create_table().unwrap(); - globals.set("commands", 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(); + + globals.set("sys", table).unwrap(); } fn set_logging(lua: &Lua, globals: &Table) { @@ -97,3 +95,166 @@ fn set_logging(lua: &Lua, globals: &Table) { .unwrap(); globals.set("info", log).unwrap(); } + +#[allow(non_snake_case)] +mod rust_lua_functions { + use mlua::{ExternalError, Lua, Result, Value, Variadic}; + use tracing::info; + + pub fn executeCommand(_lua: &Lua, arg_values: Variadic) -> Result<()> { + let mut command_name = String::new(); + let mut command_args = vec![]; + + for (idx, value) in arg_values.iter().enumerate() { + match value { + 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()); + } + }; + } + + info!( + "Running lua command `{:?}` with args `{:?}`", + command_name, command_args + ); + Ok(()) + } + pub fn registerCommand(_lua: &Lua, arg_values: Variadic) -> Result<()> { + let table = match arg_values.get(0) { + 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(), + ); + } + }; + + Ok(()) + } +} + +// pub mod luagen_module { + +// // macro_rules! luafunc { + +// // } + +// // macro_rules! luavalue { +// // () => { + +// // }; +// // } + +// macro_rules! luagen { + +// // $($item_ident:literal = $item_value:expr,)+ +// ($($item_ident:literal = $item_value:expr,)*) => { +// pub fn +// }; + +// ($($vis:vis $table:ident { $($any:tt)* } )+ ) => { +// $($vis mod $table { +// luagen!{ +// $($any)* +// } +// })+ +// }; + +// ($vis:vis fn $fn_ident:ident() { $($fn_body:tt)* } ) => { +// $vis fn $fn_ident(#[allow(unused_variables)] lua: &mlua::Lua, #[allow(unused_variables)] arg_values: mlua::Variadic) -> mlua::Result<()> { $($fn_body)* } +// }; +// } + +// luagen! { +// commandCategory { +// "info" = CAT_INF, +// "fun" = CAT_FUN, +// "general" = CAT_GEN, +// "moderation" = CAT_MOD, +// } +// pub sys { +// pub fn regiserCommand() { + +// dbg!(arg_values); + +// // let table = match arg_values.get(0) { +// // 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(), +// // ); +// // } +// // }; + +// Ok(()) +// } + +// // fn executeCommand() { +// // let mut command_name = String::new(); +// // let mut command_args = vec![]; + +// // for (idx, value) in arg_values.iter().enumerate() { +// // match value { +// // 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()); +// // } +// // }; +// // } + +// // info!( +// // "Running lua command `{:?}` with args `{:?}`", +// // command_name, command_args +// // ); +// // Ok(()) +// // } +// } +// } +// } diff --git a/src/main.rs b/src/main.rs index 4d6bea8..a7b44ee 100644 --- a/src/main.rs +++ b/src/main.rs @@ -11,25 +11,17 @@ mod tasks; use std::collections::HashMap; use std::sync::atomic::{AtomicBool, Ordering}; -use std::sync::{Arc, Condvar, OnceLock}; +use std::sync::{Condvar, OnceLock}; -use bot::BotHandler; -use commands::{Command, CommandFnKind}; +use commands::{Command, CommandFnKind, COMMANDS}; +use constants::CAT_HID; use metadata::LevelFilter; -use serenity::all::GatewayIntents; -use serenity::all::{Context, GuildId, ShardManager}; +use serenity::all::{Context, GuildId}; use serenity::model::channel::Message; -use serenity::Client; use tracing::*; -// pub const CAT_DEV: &'static str = "Dev"; -pub const CAT_FUN: &'static str = "Fun"; -pub const CAT_MOD: &'static str = "Moderation"; -pub const CAT_GEN: &'static str = "General"; -pub const CAT_INF: &'static str = "Info"; -pub const CAT_HID: &'static str = "Hidden"; +pub static NO_DISCORD: OnceLock = OnceLock::new(); -static SHARD_MANAGER: 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 @@ -41,66 +33,43 @@ async fn main() { .finish(), ) .unwrap(); - info!("Logging initialized."); - - let mut commands = HashMap::default(); + + info!("Grabbing commandline input."); + let mut nodiscord = false; + for i in std::env::args() { + match i.as_str() { + "nodiscord" => nodiscord = true, + _ => {} + } + } + NO_DISCORD.set(nodiscord).unwrap(); + + // let mut commands = HashMap::default(); info!("Loading stock commands."); - insert_stock(&mut commands); + insert_stock().await; info!("Loading rust dynamic commands."); - insert_rust(&mut commands); + insert_rust().await; info!("Initializing Lua runtime."); - let mut lua = lua::initialize(); // may not need to be mutable, but its fine for now + let lua = lua::initialize(); // may not need to be mutable, but its fine for now + + lua.load(include_str!("../lua/loader.lua")) + .set_name("lua init script") + .exec() + .unwrap(); info!("Loading lua commandlets."); - insert_lua(&mut commands, &mut lua); + insert_lua(&lua).await; - warn!("ABORTING CONNECTING TO DISCORD, BYE BYE!"); - return; - - // // TODO load this at runtime so the key will not be stored in the binary? - // #[cfg(not(debug_assertions))] - // let token = { - // info!("Initializing bot with production token."); - // include_str!("bot_token.prod") - // }; - // #[cfg(debug_assertions)] - // let token = { - // info!("Initializing bot with development token."); - // include_str!("bot_token.dev") - // }; - - // let intents = GatewayIntents::DIRECT_MESSAGES - // | GatewayIntents::DIRECT_MESSAGE_REACTIONS - // | GatewayIntents::GUILDS - // | GatewayIntents::GUILD_MODERATION - // // | GatewayIntents::GUILD_EMOJIS_AND_STICKERS - // | GatewayIntents::GUILD_MEMBERS - // | GatewayIntents::GUILD_MESSAGE_REACTIONS - // | GatewayIntents::GUILD_MESSAGES - // | GatewayIntents::MESSAGE_CONTENT; - - // let mut client = match Client::builder(&token, intents) - // .event_handler(BotHandler { commands }) - // .await - // { - // Ok(client) => client, - // Err(err) => panic!("Error starting client connection: `{err}`"), - // }; - - // SHARD_MANAGER.set(client.shard_manager.clone()).unwrap(); - - // if let Err(why) = client.start_shards(2).await { - // error!("Client error: {why:?}"); - // } - - // warn!("MAIN FUNCTION EXITING"); + lua.sandbox(true).unwrap(); + + bot::start().await; } /// The last set of commands, these are used by the hoster of the engine. -pub fn insert_lua(_commands: &mut HashMap, _lua: &mut mlua::Lua) { +pub async fn insert_lua(_lua: &mlua::Lua) { // commands.insert( // "stop".to_owned(), // Command::new("stop".to_owned()) @@ -115,7 +84,7 @@ pub fn insert_lua(_commands: &mut HashMap, _lua: &mut mlua::Lua } /// Cannot use any command names of stock commands, but gets to pick before lua commands are loaded. -pub fn insert_rust(_commands: &mut HashMap) { +pub async fn insert_rust() { // commands.insert( // "stop".to_owned(), // Command::new("stop".to_owned()) @@ -130,8 +99,8 @@ pub fn insert_rust(_commands: &mut HashMap) { } /// Will never fail, gets first pick of command names and properties. It is up to the maintainer to make damn sure this works. -pub fn insert_stock(commands: &mut HashMap) { - commands.insert( +pub async fn insert_stock() { + COMMANDS.write().await.insert( "stop".to_owned(), Command::new("stop".to_owned()) .alias("svs".to_owned()) @@ -156,6 +125,6 @@ fn stop_command(_: Context, msg: Message, _: Option) { let _eg = handle.enter(); handle.spawn(async { - SHARD_MANAGER.get().unwrap().shutdown_all().await; + bot::SHARD_MANAGER.get().unwrap().shutdown_all().await; }); } diff --git a/src/tasks.rs b/src/tasks.rs index 5b95627..936fff3 100644 --- a/src/tasks.rs +++ b/src/tasks.rs @@ -1,4 +1,4 @@ -use crate::constants; +use crate::{bot::SHARD_MANAGER, constants}; use std::{ sync::{Arc, LazyLock}, time::Duration, @@ -27,7 +27,7 @@ pub async fn start_tasks(ctx: Context) { .await .misc .push(tokio::spawn(status_timer( - crate::SHARD_MANAGER.get().unwrap().runners.clone(), + SHARD_MANAGER.get().unwrap().runners.clone(), ctx.cache, )));