Rearranged some commands
Working on a macro to automatically add option wrappers
This commit is contained in:
63
src/commands/command_macro/finish_macro.rs
Normal file
63
src/commands/command_macro/finish_macro.rs
Normal 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(())
|
||||
}
|
13
src/commands/command_macro/macro_base.rs
Normal file
13
src/commands/command_macro/macro_base.rs
Normal 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(())
|
||||
}
|
@ -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;
|
||||
|
@ -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(())
|
||||
}
|
80
src/commands/command_proc/mod.rs
Normal file
80
src/commands/command_proc/mod.rs
Normal 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
|
||||
}
|
||||
|
||||
*/
|
@ -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;
|
||||
|
@ -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 {
|
||||
|
33
src/commands/timer/delete_timer.rs
Normal file
33
src/commands/timer/delete_timer.rs
Normal 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(())
|
||||
}
|
45
src/commands/timer/list_timer.rs
Normal file
45
src/commands/timer/list_timer.rs
Normal 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(())
|
||||
}
|
4
src/commands/timer/mod.rs
Normal file
4
src/commands/timer/mod.rs
Normal file
@ -0,0 +1,4 @@
|
||||
pub mod delete_timer;
|
||||
pub mod list_timer;
|
||||
pub mod start_timer;
|
||||
pub mod timer_base;
|
34
src/commands/timer/start_timer.rs
Normal file
34
src/commands/timer/start_timer.rs
Normal 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(())
|
||||
}
|
12
src/commands/timer/timer_base.rs
Normal file
12
src/commands/timer/timer_base.rs
Normal 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(())
|
||||
}
|
@ -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},
|
||||
},
|
||||
|
22
src/main.rs
22
src/main.rs
@ -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(),
|
||||
|
Reference in New Issue
Block a user