Rearranged some commands

Working on a macro to automatically add option wrappers
This commit is contained in:
jude 2024-02-17 14:09:01 +00:00
parent d0833b7bca
commit eb92eacb90
19 changed files with 340 additions and 238 deletions

61
Cargo.lock generated
View File

@ -123,7 +123,7 @@ checksum = "16e62a023e7c117e27523144c5d2459f4397fcc3cab0085af8e2224f643a0193"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.42",
"syn 2.0.49",
]
[[package]]
@ -134,7 +134,7 @@ checksum = "fdf6721fb0140e4f897002dd086c06f6c27775df19cfe1fccb21181a48fd2c98"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.42",
"syn 2.0.49",
]
[[package]]
@ -571,7 +571,7 @@ dependencies = [
"proc-macro2",
"quote",
"strsim",
"syn 2.0.42",
"syn 2.0.49",
]
[[package]]
@ -582,7 +582,7 @@ checksum = "836a9bbc7ad63342d6d6e7b815ccab164bc77a2d95d84bc3117a8c0d5c98e2d5"
dependencies = [
"darling_core",
"quote",
"syn 2.0.42",
"syn 2.0.49",
]
[[package]]
@ -673,7 +673,7 @@ dependencies = [
"proc-macro2",
"proc-macro2-diagnostics",
"quote",
"syn 2.0.42",
"syn 2.0.49",
]
[[package]]
@ -937,7 +937,7 @@ checksum = "53b153fd91e4b0147f4aced87be237c98248656bb01050b96bf3ee89220a8ddb"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.42",
"syn 2.0.49",
]
[[package]]
@ -1440,7 +1440,7 @@ dependencies = [
"proc-macro2",
"quote",
"regex",
"syn 2.0.42",
"syn 2.0.49",
]
[[package]]
@ -1840,7 +1840,7 @@ checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.42",
"syn 2.0.49",
]
[[package]]
@ -1925,7 +1925,7 @@ dependencies = [
"proc-macro2",
"proc-macro2-diagnostics",
"quote",
"syn 2.0.42",
"syn 2.0.49",
]
[[package]]
@ -1974,7 +1974,7 @@ dependencies = [
"pest_meta",
"proc-macro2",
"quote",
"syn 2.0.42",
"syn 2.0.49",
]
[[package]]
@ -2091,7 +2091,7 @@ dependencies = [
"darling",
"proc-macro2",
"quote",
"syn 2.0.42",
"syn 2.0.49",
]
[[package]]
@ -2136,9 +2136,9 @@ checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de"
[[package]]
name = "proc-macro2"
version = "1.0.71"
version = "1.0.78"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "75cb1540fadbd5b8fbccc4dddad2734eba435053f725621c070711a14bb5f4b8"
checksum = "e2422ad645d89c99f8f3e6b88a9fdeca7fabeac836b1002371c4367c8f984aae"
dependencies = [
"unicode-ident",
]
@ -2151,7 +2151,7 @@ checksum = "af066a9c399a26e020ada66a034357a868728e72cd426f3adcd35f80d88d88c8"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.42",
"syn 2.0.49",
"version_check",
"yansi",
]
@ -2190,9 +2190,9 @@ dependencies = [
[[package]]
name = "quote"
version = "1.0.33"
version = "1.0.35"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae"
checksum = "291ec9ab5efd934aaf503a6466c5d5251535d108ee747472c3977cc5acc868ef"
dependencies = [
"proc-macro2",
]
@ -2253,7 +2253,7 @@ checksum = "2566c4bf6845f2c2e83b27043c3f5dfcd5ba8f2937d6c00dc009bfb51a079dc4"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.42",
"syn 2.0.49",
]
[[package]]
@ -2316,6 +2316,8 @@ dependencies = [
"num-integer",
"poise",
"postman",
"proc-macro2",
"quote",
"rand",
"regex",
"reminder_web",
@ -2326,6 +2328,7 @@ dependencies = [
"serde_json",
"serde_repr",
"sqlx",
"syn 2.0.49",
"tokio",
]
@ -2481,7 +2484,7 @@ dependencies = [
"proc-macro2",
"quote",
"rocket_http",
"syn 2.0.42",
"syn 2.0.49",
"unicode-xid",
"version_check",
]
@ -2749,7 +2752,7 @@ checksum = "43576ca501357b9b071ac53cdc7da8ef0cbd9493d8df094cd821777ea6e894d3"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.42",
"syn 2.0.49",
]
[[package]]
@ -2781,7 +2784,7 @@ checksum = "3081f5ffbb02284dda55132aa26daecedd7372a42417bbbab6f14ab7d6bb9145"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.42",
"syn 2.0.49",
]
[[package]]
@ -3241,9 +3244,9 @@ dependencies = [
[[package]]
name = "syn"
version = "2.0.42"
version = "2.0.49"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5b7d0a2c048d661a1a59fcd7355baa232f7ed34e0ee4df2eef3c1c1c0d3852d8"
checksum = "915aea9e586f80826ee59f8453c1101f9d1c4b3964cd2460185ee8e299ada496"
dependencies = [
"proc-macro2",
"quote",
@ -3338,7 +3341,7 @@ checksum = "01742297787513b79cf8e29d1056ede1313e2420b7b3b15d0a768b4921f549df"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.42",
"syn 2.0.49",
]
[[package]]
@ -3422,7 +3425,7 @@ checksum = "5b8a1e28f2deaa14e508979454cb3a223b10b938b45af148bc0986de36f1923b"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.42",
"syn 2.0.49",
]
[[package]]
@ -3556,7 +3559,7 @@ checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.42",
"syn 2.0.49",
]
[[package]]
@ -3668,7 +3671,7 @@ checksum = "0b122284365ba8497be951b9a21491f70c9688eb6fddc582931a0703f6a00ece"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.42",
"syn 2.0.49",
]
[[package]]
@ -3898,7 +3901,7 @@ dependencies = [
"once_cell",
"proc-macro2",
"quote",
"syn 2.0.42",
"syn 2.0.49",
"wasm-bindgen-shared",
]
@ -3932,7 +3935,7 @@ checksum = "f0eb82fcb7930ae6219a7ecfd55b217f5f0893484b7a13022ebb2b2bf20b5283"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.42",
"syn 2.0.49",
"wasm-bindgen-backend",
"wasm-bindgen-shared",
]
@ -4204,7 +4207,7 @@ checksum = "9ce1b18ccd8e73a9321186f97e46f9f04b778851177567b1975109d26a08d2a6"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.42",
"syn 2.0.49",
]
[[package]]

View File

@ -7,6 +7,9 @@ license = "AGPL-3.0 only"
description = "Reminder Bot for Discord, now in Rust"
[dependencies]
quote = "1.0.35"
proc-macro2 = "1.0.78"
syn = { version = "2.0.49", features = ["full"] }
poise = "0.6.1"
dotenv = "0.15"
tokio = { version = "1", features = ["process", "full"] }

View File

@ -0,0 +1,63 @@
use poise::{serenity_prelude::CreateEmbed, CreateReply};
use crate::{consts::THEME_COLOR, Context, Error};
/// Finish current macro recording
#[poise::command(
slash_command,
rename = "finish",
guild_only = true,
default_member_permissions = "MANAGE_GUILD",
identifying_name = "finish_macro"
)]
pub async fn finish_macro(ctx: Context<'_>) -> Result<(), Error> {
let key = (ctx.guild_id().unwrap(), ctx.author().id);
{
let lock = ctx.data().recording_macros.read().await;
let contained = lock.get(&key);
if contained.map_or(true, |r#macro| r#macro.commands.is_empty()) {
ctx.send(
CreateReply::default().embed(
CreateEmbed::new()
.title("No Macro Recorded")
.description("Use `/macro record` to start recording a macro")
.color(*THEME_COLOR),
),
)
.await?;
} else {
let command_macro = contained.unwrap();
let json = serde_json::to_string(&command_macro.commands).unwrap();
sqlx::query!(
"INSERT INTO command_macro (guild_id, name, description, commands) VALUES ((SELECT id FROM guilds WHERE guild = ?), ?, ?, ?)",
command_macro.guild_id.get(),
command_macro.name,
command_macro.description,
json
)
.execute(&ctx.data().database)
.await
.unwrap();
ctx.send(
CreateReply::default().embed(
CreateEmbed::new()
.title("Macro Recorded")
.description("Use `/macro run` to execute the macro")
.color(*THEME_COLOR),
),
)
.await?;
}
}
{
let mut lock = ctx.data().recording_macros.write().await;
lock.remove(&key);
}
Ok(())
}

View File

@ -0,0 +1,13 @@
use crate::{Context, Error};
/// Record and replay command sequences
#[poise::command(
slash_command,
rename = "macro",
guild_only = true,
default_member_permissions = "MANAGE_GUILD",
identifying_name = "macro_base"
)]
pub async fn macro_base(_ctx: Context<'_>) -> Result<(), Error> {
Ok(())
}

View File

@ -1,18 +1,6 @@
use crate::{Context, Error};
pub mod delete;
pub mod list;
pub mod record;
pub mod run;
/// Record and replay command sequences
#[poise::command(
slash_command,
rename = "macro",
guild_only = true,
default_member_permissions = "MANAGE_GUILD",
identifying_name = "macro_base"
)]
pub async fn macro_base(_ctx: Context<'_>) -> Result<(), Error> {
Ok(())
}
pub mod delete_macro;
pub mod finish_macro;
pub mod list_macro;
pub mod macro_base;
pub mod record_macro;
pub mod run_macro;

View File

@ -100,63 +100,3 @@ Please use `/macro finish` to end this recording before starting another.",
Ok(())
}
/// Finish current macro recording
#[poise::command(
slash_command,
rename = "finish",
guild_only = true,
default_member_permissions = "MANAGE_GUILD",
identifying_name = "finish_macro"
)]
pub async fn finish_macro(ctx: Context<'_>) -> Result<(), Error> {
let key = (ctx.guild_id().unwrap(), ctx.author().id);
{
let lock = ctx.data().recording_macros.read().await;
let contained = lock.get(&key);
if contained.map_or(true, |r#macro| r#macro.commands.is_empty()) {
ctx.send(
CreateReply::default().embed(
CreateEmbed::new()
.title("No Macro Recorded")
.description("Use `/macro record` to start recording a macro")
.color(*THEME_COLOR),
),
)
.await?;
} else {
let command_macro = contained.unwrap();
let json = serde_json::to_string(&command_macro.commands).unwrap();
sqlx::query!(
"INSERT INTO command_macro (guild_id, name, description, commands) VALUES ((SELECT id FROM guilds WHERE guild = ?), ?, ?, ?)",
command_macro.guild_id.get(),
command_macro.name,
command_macro.description,
json
)
.execute(&ctx.data().database)
.await
.unwrap();
ctx.send(
CreateReply::default().embed(
CreateEmbed::new()
.title("Macro Recorded")
.description("Use `/macro run` to execute the macro")
.color(*THEME_COLOR),
),
)
.await?;
}
}
{
let mut lock = ctx.data().recording_macros.write().await;
lock.remove(&key);
}
Ok(())
}

View File

@ -0,0 +1,80 @@
use proc_macro2::{Ident, Span, TokenStream};
use serde::{de::DeserializeOwned, Serialize};
use syn::{punctuated::Punctuated, token::Comma, FnArg, Pat};
struct RecordableCommand<T: Serialize + DeserializeOwned, R> {
args: T,
func: dyn Fn(T) -> R,
}
/// Takes a function and produces a serializable struct of its args.
pub fn arg_struct(mut function: syn::ItemFn) -> TokenStream {
let struct_name = Ident::new(&format!("{}_args", function.sig.ident), Span::call_site());
let wrapped_fn_name = Ident::new(&format!("{}_fn", function.sig.ident), Span::call_site());
let fn_name = &function.sig.ident;
let fn_generics = &function.sig.generics;
let fn_inputs = &function.sig.inputs;
let fn_args = &function
.sig
.inputs
.iter()
.map(|arg| match arg {
FnArg::Receiver(_) => {
panic!("Can't accept Receiver arg")
}
FnArg::Typed(p) => p.pat.clone(),
})
.collect::<Punctuated<Box<Pat>, Comma>>();
let fn_retval = &function.sig.output;
let fn_body = &function.block;
quote::quote! {
pub async fn #fn_name #fn_generics(#fn_inputs) -> #fn_retval {
#wrapped_fn_name(#fn_args).await
}
pub async fn #wrapped_fn_name #fn_generics(#fn_inputs) -> #fn_retval {
#fn_body
}
}
}
/*
#[poise]
#[wrapper]
pub async fn command(...args) {
...block
}
... becomes ...
#[poise]
fn command(...args) {
command_fn(
...args
)
}
struct RecordableCommand<T> {
args: T
func: Func<T>
}
impl Execute<T> for RecordableCommand<T> {
fn execute() {
// Unpack self.args into self.func
}
}
struct command_args {
...args
}
fn command_fn(...args) {
...block
}
*/

View File

@ -1,6 +1,8 @@
mod autocomplete;
pub mod command_macro;
mod command_proc;
pub mod info_cmds;
pub mod moderation_cmds;
pub mod reminder_cmds;
pub mod timer;
pub mod todo_cmds;

View File

@ -1,9 +1,8 @@
use std::{collections::HashSet, string::ToString};
use chrono::{DateTime, NaiveDateTime, Utc};
use chrono::NaiveDateTime;
use chrono_tz::Tz;
use log::warn;
use num_integer::Integer;
use poise::{
serenity_prelude::{
builder::CreateEmbed, model::channel::Channel, ButtonStyle, CreateActionRow, CreateButton,
@ -32,7 +31,6 @@ use crate::{
look_flags::{LookFlags, TimeDisplayType},
Reminder,
},
timer::Timer,
CtxData,
},
time_parser::natural_parser,
@ -422,122 +420,6 @@ pub fn show_delete_page(reminders: &[Reminder], page: usize, timezone: Tz) -> Cr
.components(vec![pager.create_button_row(pages), CreateActionRow::SelectMenu(select_menu)])
}
fn time_difference(start_time: DateTime<Utc>) -> String {
let delta = (Utc::now() - start_time).num_seconds();
let (minutes, seconds) = delta.div_rem(&60);
let (hours, minutes) = minutes.div_rem(&60);
let (days, hours) = hours.div_rem(&24);
format!("{} days, {:02}:{:02}:{:02}", days, hours, minutes, seconds)
}
/// Manage timers
#[poise::command(
slash_command,
rename = "timer",
identifying_name = "timer_base",
default_member_permissions = "MANAGE_GUILD"
)]
pub async fn timer_base(_ctx: Context<'_>) -> Result<(), Error> {
Ok(())
}
/// List the timers in this server or DM channel
#[poise::command(
slash_command,
rename = "list",
identifying_name = "list_timer",
default_member_permissions = "MANAGE_GUILD"
)]
pub async fn list_timer(ctx: Context<'_>) -> Result<(), Error> {
let owner = ctx.guild_id().map(|g| g.get()).unwrap_or_else(|| ctx.author().id.get());
let timers = Timer::from_owner(owner, &ctx.data().database).await;
if !timers.is_empty() {
ctx.send(
CreateReply::default().embed(
CreateEmbed::new()
.fields(timers.iter().map(|timer| {
(&timer.name, format!("⌚ `{}`", time_difference(timer.start_time)), false)
}))
.color(*THEME_COLOR),
),
)
.await?;
} else {
ctx.say("No timers currently. Use `/timer start` to create a new timer").await?;
}
Ok(())
}
/// Start a new timer from now
#[poise::command(
slash_command,
rename = "start",
identifying_name = "start_timer",
default_member_permissions = "MANAGE_GUILD"
)]
pub async fn start_timer(
ctx: Context<'_>,
#[description = "Name for the new timer"] name: String,
) -> Result<(), Error> {
let owner = ctx.guild_id().map(|g| g.get()).unwrap_or_else(|| ctx.author().id.get());
let count = Timer::count_from_owner(owner, &ctx.data().database).await;
if count >= 25 {
ctx.say("You already have 25 timers. Please delete some timers before creating a new one")
.await?;
} else if name.len() <= 32 {
Timer::create(&name, owner, &ctx.data().database).await;
ctx.say("Created a new timer").await?;
} else {
ctx.say(format!(
"Please name your timer something shorted (max. 32 characters, you used {})",
name.len()
))
.await?;
}
Ok(())
}
/// Delete a timer
#[poise::command(
slash_command,
rename = "delete",
identifying_name = "delete_timer",
default_member_permissions = "MANAGE_GUILD"
)]
pub async fn delete_timer(
ctx: Context<'_>,
#[description = "Name of timer to delete"] name: String,
) -> Result<(), Error> {
let owner = ctx.guild_id().map(|g| g.get()).unwrap_or_else(|| ctx.author().id.get());
let exists =
sqlx::query!("SELECT 1 as _r FROM timers WHERE owner = ? AND name = ?", owner, name)
.fetch_one(&ctx.data().database)
.await;
if exists.is_ok() {
sqlx::query!("DELETE FROM timers WHERE owner = ? AND name = ?", owner, name)
.execute(&ctx.data().database)
.await
.unwrap();
ctx.say("Deleted a timer").await?;
} else {
ctx.say("Could not find a timer by that name").await?;
}
Ok(())
}
#[derive(poise::Modal)]
#[name = "Reminder"]
struct ContentModal {

View File

@ -0,0 +1,33 @@
use crate::{Context, Error};
/// Delete a timer
#[poise::command(
slash_command,
rename = "delete",
identifying_name = "delete_timer",
default_member_permissions = "MANAGE_GUILD"
)]
pub async fn delete_timer(
ctx: Context<'_>,
#[description = "Name of timer to delete"] name: String,
) -> Result<(), Error> {
let owner = ctx.guild_id().map(|g| g.get()).unwrap_or_else(|| ctx.author().id.get());
let exists =
sqlx::query!("SELECT 1 as _r FROM timers WHERE owner = ? AND name = ?", owner, name)
.fetch_one(&ctx.data().database)
.await;
if exists.is_ok() {
sqlx::query!("DELETE FROM timers WHERE owner = ? AND name = ?", owner, name)
.execute(&ctx.data().database)
.await
.unwrap();
ctx.say("Deleted a timer").await?;
} else {
ctx.say("Could not find a timer by that name").await?;
}
Ok(())
}

View File

@ -0,0 +1,45 @@
use chrono::{DateTime, Utc};
use num_integer::Integer;
use poise::{serenity_prelude::CreateEmbed, CreateReply};
use crate::{consts::THEME_COLOR, models::timer::Timer, Context, Error};
fn time_difference(start_time: DateTime<Utc>) -> String {
let delta = (Utc::now() - start_time).num_seconds();
let (minutes, seconds) = delta.div_rem(&60);
let (hours, minutes) = minutes.div_rem(&60);
let (days, hours) = hours.div_rem(&24);
format!("{} days, {:02}:{:02}:{:02}", days, hours, minutes, seconds)
}
/// List the timers in this server or DM channel
#[poise::command(
slash_command,
rename = "list",
identifying_name = "list_timer",
default_member_permissions = "MANAGE_GUILD"
)]
pub async fn list_timer(ctx: Context<'_>) -> Result<(), Error> {
let owner = ctx.guild_id().map(|g| g.get()).unwrap_or_else(|| ctx.author().id.get());
let timers = Timer::from_owner(owner, &ctx.data().database).await;
if !timers.is_empty() {
ctx.send(
CreateReply::default().embed(
CreateEmbed::new()
.fields(timers.iter().map(|timer| {
(&timer.name, format!("⌚ `{}`", time_difference(timer.start_time)), false)
}))
.color(*THEME_COLOR),
),
)
.await?;
} else {
ctx.say("No timers currently. Use `/timer start` to create a new timer").await?;
}
Ok(())
}

View File

@ -0,0 +1,4 @@
pub mod delete_timer;
pub mod list_timer;
pub mod start_timer;
pub mod timer_base;

View File

@ -0,0 +1,34 @@
use crate::{models::timer::Timer, Context, Error};
/// Start a new timer from now
#[poise::command(
slash_command,
rename = "start",
identifying_name = "start_timer",
default_member_permissions = "MANAGE_GUILD"
)]
pub async fn start_timer(
ctx: Context<'_>,
#[description = "Name for the new timer"] name: String,
) -> Result<(), Error> {
let owner = ctx.guild_id().map(|g| g.get()).unwrap_or_else(|| ctx.author().id.get());
let count = Timer::count_from_owner(owner, &ctx.data().database).await;
if count >= 25 {
ctx.say("You already have 25 timers. Please delete some timers before creating a new one")
.await?;
} else if name.len() <= 32 {
Timer::create(&name, owner, &ctx.data().database).await;
ctx.say("Created a new timer").await?;
} else {
ctx.say(format!(
"Please name your timer something shorted (max. 32 characters, you used {})",
name.len()
))
.await?;
}
Ok(())
}

View File

@ -0,0 +1,12 @@
use crate::{Context, Error};
/// Manage timers
#[poise::command(
slash_command,
rename = "timer",
identifying_name = "timer_base",
default_member_permissions = "MANAGE_GUILD"
)]
pub async fn timer_base(_ctx: Context<'_>) -> Result<(), Error> {
Ok(())
}

View File

@ -17,7 +17,7 @@ use serde::{Deserialize, Serialize};
use crate::{
commands::{
command_macro::list::{max_macro_page, show_macro_page},
command_macro::list_macro::{max_macro_page, show_macro_page},
reminder_cmds::{max_delete_page, show_delete_page},
todo_cmds::{max_todo_page, show_todo_page},
},

View File

@ -34,7 +34,7 @@ use sqlx::{MySql, Pool};
use tokio::sync::{broadcast, broadcast::Sender, RwLock};
use crate::{
commands::{command_macro, info_cmds, moderation_cmds, reminder_cmds, todo_cmds},
commands::{command_macro, info_cmds, moderation_cmds, reminder_cmds, timer, todo_cmds},
consts::THEME_COLOR,
event_handlers::listener,
hooks::all_checks,
@ -126,13 +126,13 @@ async fn _main(tx: Sender<()>) -> Result<(), Box<dyn StdError + Send + Sync>> {
moderation_cmds::webhook(),
poise::Command {
subcommands: vec![
command_macro::delete::delete_macro(),
command_macro::record::finish_macro(),
command_macro::list::list_macro(),
command_macro::record::record_macro(),
command_macro::run::run_macro(),
command_macro::delete_macro::delete_macro(),
command_macro::finish_macro::finish_macro(),
command_macro::list_macro::list_macro(),
command_macro::record_macro::record_macro(),
command_macro::run_macro::run_macro(),
],
..command_macro::macro_base()
..command_macro::macro_base::macro_base()
},
reminder_cmds::pause(),
reminder_cmds::offset(),
@ -141,11 +141,11 @@ async fn _main(tx: Sender<()>) -> Result<(), Box<dyn StdError + Send + Sync>> {
reminder_cmds::delete(),
poise::Command {
subcommands: vec![
reminder_cmds::list_timer(),
reminder_cmds::start_timer(),
reminder_cmds::delete_timer(),
timer::list_timer::list_timer(),
timer::start_timer::start_timer(),
timer::delete_timer::delete_timer(),
],
..reminder_cmds::timer_base()
..timer::timer_base::timer_base()
},
reminder_cmds::multiline(),
reminder_cmds::remind(),