adding more stuff for lua command support

This commit is contained in:
deepCurse 2024-09-09 21:03:46 -03:00
parent 0f40bc98c3
commit c7b54b0ed5
Signed by: u1
GPG key ID: AD770D25A908AFF4
8 changed files with 282 additions and 99 deletions

8
Cargo.lock generated
View file

@ -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",

View file

@ -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,"")
-- dump(_G,"")

View file

@ -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<String, Command>,
}
pub static SHARD_MANAGER: OnceLock<Arc<ShardManager>> = 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");
}

View file

@ -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<RwLock<HashMap<String, Command>>> =
LazyLock::new(|| RwLock::new(HashMap::new()));
pub enum CommandFnKind {
Lua(()),

View file

@ -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 = "--";

View file

@ -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<Value>) -> 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<Value>) -> 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::Value>) -> 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(())
// // }
// }
// }
// }

View file

@ -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<bool> = OnceLock::new();
static SHARD_MANAGER: OnceLock<Arc<ShardManager>> = OnceLock::new();
// static SHARDS_READY: AtomicBool = AtomicBool::new(false);
static DO_SHUTDOWN: (AtomicBool, Condvar) = (AtomicBool::new(false), Condvar::new()); // atombool and condvar combo to ensure maximum coverage when the bot needs to power off
@ -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<String, Command>, _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<String, Command>, _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<String, Command>) {
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<String, Command>) {
}
/// Will never fail, gets first pick of command names and properties. It is up to the maintainer to make damn sure this works.
pub fn insert_stock(commands: &mut HashMap<String, Command>) {
commands.insert(
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<GuildId>) {
let _eg = handle.enter();
handle.spawn(async {
SHARD_MANAGER.get().unwrap().shutdown_all().await;
bot::SHARD_MANAGER.get().unwrap().shutdown_all().await;
});
}

View file

@ -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,
)));