Link all top-level commands with macro recording/replaying logic
This commit is contained in:
		
							
								
								
									
										13
									
								
								Cargo.lock
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										13
									
								
								Cargo.lock
									
									
									
										generated
									
									
									
								
							@@ -774,7 +774,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0"
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "extract_macro"
 | 
			
		||||
name = "extract_derive"
 | 
			
		||||
version = "0.1.0"
 | 
			
		||||
dependencies = [
 | 
			
		||||
 "quote",
 | 
			
		||||
@@ -2235,6 +2235,14 @@ dependencies = [
 | 
			
		||||
 "getrandom",
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "recordable_derive"
 | 
			
		||||
version = "0.1.0"
 | 
			
		||||
dependencies = [
 | 
			
		||||
 "quote",
 | 
			
		||||
 "syn 2.0.49",
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "redox_syscall"
 | 
			
		||||
version = "0.4.1"
 | 
			
		||||
@@ -2317,7 +2325,7 @@ dependencies = [
 | 
			
		||||
 "chrono-tz",
 | 
			
		||||
 "dotenv",
 | 
			
		||||
 "env_logger",
 | 
			
		||||
 "extract_macro",
 | 
			
		||||
 "extract_derive",
 | 
			
		||||
 "lazy-regex",
 | 
			
		||||
 "lazy_static",
 | 
			
		||||
 "levenshtein",
 | 
			
		||||
@@ -2326,6 +2334,7 @@ dependencies = [
 | 
			
		||||
 "poise",
 | 
			
		||||
 "postman",
 | 
			
		||||
 "rand",
 | 
			
		||||
 "recordable_derive",
 | 
			
		||||
 "regex",
 | 
			
		||||
 "reminder_web",
 | 
			
		||||
 "reqwest",
 | 
			
		||||
 
 | 
			
		||||
@@ -35,8 +35,11 @@ path = "postman"
 | 
			
		||||
[dependencies.reminder_web]
 | 
			
		||||
path = "web"
 | 
			
		||||
 | 
			
		||||
[dependencies.extract_macro]
 | 
			
		||||
path = "extract_macro"
 | 
			
		||||
[dependencies.extract_derive]
 | 
			
		||||
path = "extract_derive"
 | 
			
		||||
 | 
			
		||||
[dependencies.recordable_derive]
 | 
			
		||||
path = "recordable_derive"
 | 
			
		||||
 | 
			
		||||
[package.metadata.deb]
 | 
			
		||||
depends = "$auto, python3-dateparser (>= 1.0.0)"
 | 
			
		||||
 
 | 
			
		||||
@@ -1,5 +1,5 @@
 | 
			
		||||
[package]
 | 
			
		||||
name = "extract_macro"
 | 
			
		||||
name = "extract_derive"
 | 
			
		||||
version = "0.1.0"
 | 
			
		||||
edition = "2021"
 | 
			
		||||
 | 
			
		||||
@@ -12,6 +12,7 @@ fn impl_extract(ast: &syn::DeriveInput) -> TokenStream {
 | 
			
		||||
    let name = &ast.ident;
 | 
			
		||||
 | 
			
		||||
    match &ast.data {
 | 
			
		||||
        // Dispatch over struct: extract args directly from context
 | 
			
		||||
        Data::Struct(st) => match &st.fields {
 | 
			
		||||
            Fields::Named(fields) => {
 | 
			
		||||
                let extracted = fields.named.iter().map(|field| {
 | 
			
		||||
							
								
								
									
										11
									
								
								recordable_derive/Cargo.toml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										11
									
								
								recordable_derive/Cargo.toml
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,11 @@
 | 
			
		||||
[package]
 | 
			
		||||
name = "recordable_derive"
 | 
			
		||||
version = "0.1.0"
 | 
			
		||||
edition = "2021"
 | 
			
		||||
 | 
			
		||||
[lib]
 | 
			
		||||
proc-macro = true
 | 
			
		||||
 | 
			
		||||
[dependencies]
 | 
			
		||||
quote = "1.0.35"
 | 
			
		||||
syn = { version = "2.0.49", features = ["full"] }
 | 
			
		||||
							
								
								
									
										42
									
								
								recordable_derive/src/lib.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										42
									
								
								recordable_derive/src/lib.rs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,42 @@
 | 
			
		||||
use proc_macro::TokenStream;
 | 
			
		||||
use syn::{spanned::Spanned, Data};
 | 
			
		||||
 | 
			
		||||
/// Macro to allow Recordable to be implemented on an enum by dispatching over each variant.
 | 
			
		||||
#[proc_macro_derive(Recordable)]
 | 
			
		||||
pub fn extract(input: TokenStream) -> TokenStream {
 | 
			
		||||
    let ast = syn::parse_macro_input!(input);
 | 
			
		||||
 | 
			
		||||
    impl_recordable(&ast)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
fn impl_recordable(ast: &syn::DeriveInput) -> TokenStream {
 | 
			
		||||
    let name = &ast.ident;
 | 
			
		||||
 | 
			
		||||
    match &ast.data {
 | 
			
		||||
        Data::Enum(en) => {
 | 
			
		||||
            let extracted = en.variants.iter().map(|var| {
 | 
			
		||||
                let ident = &var.ident;
 | 
			
		||||
 | 
			
		||||
                quote::quote_spanned! {var.span()=>
 | 
			
		||||
                    Self::#ident (opt) => opt.run(ctx).await?
 | 
			
		||||
                }
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            TokenStream::from(quote::quote! {
 | 
			
		||||
                impl Recordable for #name {
 | 
			
		||||
                    async fn run(self, ctx: crate::Context<'_>) -> Result<(), crate::Error> {
 | 
			
		||||
                        match self {
 | 
			
		||||
                            #(#extracted,)*
 | 
			
		||||
                        }
 | 
			
		||||
 | 
			
		||||
                        Ok(())
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            })
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        _ => {
 | 
			
		||||
            panic!("Only enums can derive Recordable");
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -2,12 +2,17 @@ use chrono::Utc;
 | 
			
		||||
use poise::CreateReply;
 | 
			
		||||
use serde::{Deserialize, Serialize};
 | 
			
		||||
 | 
			
		||||
use crate::{models::CtxData, utils::Extract, Context, Error};
 | 
			
		||||
use crate::{
 | 
			
		||||
    models::CtxData,
 | 
			
		||||
    utils::{Extract, Recordable},
 | 
			
		||||
    Context, Error,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
#[derive(Serialize, Deserialize, Extract)]
 | 
			
		||||
pub struct Options;
 | 
			
		||||
 | 
			
		||||
pub async fn clock(ctx: Context<'_>, _options: Options) -> Result<(), Error> {
 | 
			
		||||
impl Recordable for Options {
 | 
			
		||||
    async fn run(self, ctx: Context<'_>) -> Result<(), Error> {
 | 
			
		||||
        ctx.defer_ephemeral().await?;
 | 
			
		||||
 | 
			
		||||
        let tz = ctx.timezone().await;
 | 
			
		||||
@@ -22,9 +27,10 @@ pub async fn clock(ctx: Context<'_>, _options: Options) -> Result<(), Error> {
 | 
			
		||||
 | 
			
		||||
        Ok(())
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// View the current time in your selected timezone
 | 
			
		||||
#[poise::command(slash_command, rename = "clock")]
 | 
			
		||||
#[poise::command(slash_command, rename = "clock", identifying_name = "clock")]
 | 
			
		||||
pub async fn command(ctx: Context<'_>) -> Result<(), Error> {
 | 
			
		||||
    clock(ctx, Options {}).await
 | 
			
		||||
    (Options {}).run(ctx).await
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -1,7 +1,10 @@
 | 
			
		||||
use poise::{serenity_prelude::CreateEmbed, CreateReply};
 | 
			
		||||
 | 
			
		||||
use super::super::autocomplete::macro_name_autocomplete;
 | 
			
		||||
use crate::{models::command_macro::guild_command_macro, Context, Data, Error, THEME_COLOR};
 | 
			
		||||
use crate::{
 | 
			
		||||
    models::command_macro::guild_command_macro, utils::Recordable, Context, Data, Error,
 | 
			
		||||
    THEME_COLOR,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
/// Run a recorded macro
 | 
			
		||||
#[poise::command(
 | 
			
		||||
@@ -32,7 +35,9 @@ pub async fn run_macro(
 | 
			
		||||
                .await?;
 | 
			
		||||
 | 
			
		||||
            for command in command_macro.commands {
 | 
			
		||||
                command.execute(poise::ApplicationContext { ..ctx }).await?;
 | 
			
		||||
                command
 | 
			
		||||
                    .run(poise::Context::Application(poise::ApplicationContext { ..ctx }))
 | 
			
		||||
                    .await?;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -3,14 +3,15 @@ use serde::{Deserialize, Serialize};
 | 
			
		||||
 | 
			
		||||
use crate::{
 | 
			
		||||
    consts::THEME_COLOR,
 | 
			
		||||
    utils::{footer, Extract},
 | 
			
		||||
    utils::{footer, Extract, Recordable},
 | 
			
		||||
    Context, Error,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
#[derive(Serialize, Deserialize, Extract)]
 | 
			
		||||
pub struct Options;
 | 
			
		||||
 | 
			
		||||
pub async fn dashboard(ctx: Context<'_>, _options: Options) -> Result<(), Error> {
 | 
			
		||||
impl Recordable for Options {
 | 
			
		||||
    async fn run(self, ctx: Context<'_>) -> Result<(), Error> {
 | 
			
		||||
        let footer = footer(ctx);
 | 
			
		||||
 | 
			
		||||
        ctx.send(
 | 
			
		||||
@@ -26,9 +27,10 @@ pub async fn dashboard(ctx: Context<'_>, _options: Options) -> Result<(), Error>
 | 
			
		||||
 | 
			
		||||
        Ok(())
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// Get the link to the web dashboard
 | 
			
		||||
#[poise::command(slash_command, rename = "dashboard")]
 | 
			
		||||
#[poise::command(slash_command, rename = "dashboard", identifying_name = "dashboard")]
 | 
			
		||||
pub async fn command(ctx: Context<'_>) -> Result<(), Error> {
 | 
			
		||||
    dashboard(ctx, Options {}).await
 | 
			
		||||
    (Options {}).run(ctx).await
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -15,7 +15,7 @@ use crate::{
 | 
			
		||||
    },
 | 
			
		||||
    consts::{EMBED_DESCRIPTION_MAX_LENGTH, SELECT_MAX_ENTRIES, THEME_COLOR},
 | 
			
		||||
    models::{reminder::Reminder, CtxData},
 | 
			
		||||
    utils::Extract,
 | 
			
		||||
    utils::{Extract, Recordable},
 | 
			
		||||
    Context, Error,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
@@ -140,7 +140,8 @@ pub fn show_delete_page(reminders: &[Reminder], page: usize, timezone: Tz) -> Cr
 | 
			
		||||
#[derive(Serialize, Deserialize, Extract)]
 | 
			
		||||
pub struct Options;
 | 
			
		||||
 | 
			
		||||
pub async fn delete(ctx: Context<'_>, _options: Options) -> Result<(), Error> {
 | 
			
		||||
impl Recordable for Options {
 | 
			
		||||
    async fn run(self, ctx: Context<'_>) -> Result<(), Error> {
 | 
			
		||||
        let timezone = ctx.timezone().await;
 | 
			
		||||
 | 
			
		||||
        let reminders =
 | 
			
		||||
@@ -152,9 +153,15 @@ pub async fn delete(ctx: Context<'_>, _options: Options) -> Result<(), Error> {
 | 
			
		||||
 | 
			
		||||
        Ok(())
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// Delete reminders
 | 
			
		||||
#[poise::command(slash_command, rename = "del", default_member_permissions = "MANAGE_GUILD")]
 | 
			
		||||
#[poise::command(
 | 
			
		||||
    slash_command,
 | 
			
		||||
    rename = "delete",
 | 
			
		||||
    identifying_name = "delete",
 | 
			
		||||
    default_member_permissions = "MANAGE_GUILD"
 | 
			
		||||
)]
 | 
			
		||||
pub async fn command(ctx: Context<'_>) -> Result<(), Error> {
 | 
			
		||||
    delete(ctx, Options {}).await
 | 
			
		||||
    (Options {}).run(ctx).await
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -3,14 +3,15 @@ use serde::{Deserialize, Serialize};
 | 
			
		||||
 | 
			
		||||
use crate::{
 | 
			
		||||
    consts::THEME_COLOR,
 | 
			
		||||
    utils::{footer, Extract},
 | 
			
		||||
    utils::{footer, Extract, Recordable},
 | 
			
		||||
    Context, Error,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
#[derive(Serialize, Deserialize, Extract)]
 | 
			
		||||
pub struct Options;
 | 
			
		||||
 | 
			
		||||
pub async fn donate(ctx: Context<'_>, _options: Options) -> Result<(), Error> {
 | 
			
		||||
impl Recordable for Options {
 | 
			
		||||
    async fn run(self, ctx: Context<'_>) -> Result<(), Error> {
 | 
			
		||||
        let footer = footer(ctx);
 | 
			
		||||
 | 
			
		||||
        ctx.send(CreateReply::default().embed(CreateEmbed::new().title("Donate")
 | 
			
		||||
@@ -38,9 +39,10 @@ Just $2 USD/month!
 | 
			
		||||
 | 
			
		||||
        Ok(())
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// Details on supporting the bot and Patreon benefits
 | 
			
		||||
#[poise::command(slash_command, rename = "patreon")]
 | 
			
		||||
#[poise::command(slash_command, rename = "patreon", identifying_name = "patreon")]
 | 
			
		||||
pub async fn command(ctx: Context<'_>) -> Result<(), Error> {
 | 
			
		||||
    donate(ctx, Options {}).await
 | 
			
		||||
    (Options {}).run(ctx).await
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -3,14 +3,15 @@ use serde::{Deserialize, Serialize};
 | 
			
		||||
 | 
			
		||||
use crate::{
 | 
			
		||||
    consts::THEME_COLOR,
 | 
			
		||||
    utils::{footer, Extract},
 | 
			
		||||
    utils::{footer, Extract, Recordable},
 | 
			
		||||
    Context, Error,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
#[derive(Serialize, Deserialize, Extract)]
 | 
			
		||||
pub struct Options;
 | 
			
		||||
 | 
			
		||||
pub async fn help(ctx: Context<'_>, _options: Options) -> Result<(), Error> {
 | 
			
		||||
impl Recordable for Options {
 | 
			
		||||
    async fn run(self, ctx: Context<'_>) -> Result<(), Error> {
 | 
			
		||||
        let footer = footer(ctx);
 | 
			
		||||
 | 
			
		||||
        ctx.send(
 | 
			
		||||
@@ -52,9 +53,10 @@ __Advanced Commands__
 | 
			
		||||
 | 
			
		||||
        Ok(())
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// Get an overview of bot commands
 | 
			
		||||
#[poise::command(slash_command, rename = "help")]
 | 
			
		||||
#[poise::command(slash_command, rename = "help", identifying_name = "help")]
 | 
			
		||||
pub async fn command(ctx: Context<'_>) -> Result<(), Error> {
 | 
			
		||||
    help(ctx, Options {}).await
 | 
			
		||||
    (Options {}).run(ctx).await
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -3,14 +3,15 @@ use serde::{Deserialize, Serialize};
 | 
			
		||||
 | 
			
		||||
use crate::{
 | 
			
		||||
    consts::THEME_COLOR,
 | 
			
		||||
    utils::{footer, Extract},
 | 
			
		||||
    utils::{footer, Extract, Recordable},
 | 
			
		||||
    Context, Error,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
#[derive(Serialize, Deserialize, Extract)]
 | 
			
		||||
pub struct Options;
 | 
			
		||||
 | 
			
		||||
pub async fn info(ctx: Context<'_>, _options: Options) -> Result<(), Error> {
 | 
			
		||||
impl Recordable for Options {
 | 
			
		||||
    async fn run(self, ctx: Context<'_>) -> Result<(), Error> {
 | 
			
		||||
        let footer = footer(ctx);
 | 
			
		||||
 | 
			
		||||
        ctx.send(
 | 
			
		||||
@@ -36,9 +37,10 @@ Use our dashboard: https://reminder-bot.com/",
 | 
			
		||||
 | 
			
		||||
        Ok(())
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// Get information about the bot
 | 
			
		||||
#[poise::command(slash_command, rename = "info")]
 | 
			
		||||
#[poise::command(slash_command, rename = "info", identifying_name = "info")]
 | 
			
		||||
pub async fn command(ctx: Context<'_>) -> Result<(), Error> {
 | 
			
		||||
    info(ctx, Options {}).await
 | 
			
		||||
    (Options {}).run(ctx).await
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -9,7 +9,7 @@ use crate::{
 | 
			
		||||
    component_models::pager::{LookPager, Pager},
 | 
			
		||||
    consts::{EMBED_DESCRIPTION_MAX_LENGTH, THEME_COLOR},
 | 
			
		||||
    models::{reminder::Reminder, CtxData},
 | 
			
		||||
    utils::Extract,
 | 
			
		||||
    utils::{Extract, Recordable},
 | 
			
		||||
    Context, Error,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
@@ -40,13 +40,14 @@ pub struct Options {
 | 
			
		||||
    relative: Option<bool>,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
pub async fn look(ctx: Context<'_>, options: Options) -> Result<(), Error> {
 | 
			
		||||
impl Recordable for Options {
 | 
			
		||||
    async fn run(self, ctx: Context<'_>) -> Result<(), Error> {
 | 
			
		||||
        let timezone = ctx.timezone().await;
 | 
			
		||||
 | 
			
		||||
        let flags = LookFlags {
 | 
			
		||||
        show_disabled: options.disabled.unwrap_or(true),
 | 
			
		||||
        channel_id: options.channel.map(|c| c.id),
 | 
			
		||||
        time_display: options.relative.map_or(TimeDisplayType::Relative, |b| {
 | 
			
		||||
            show_disabled: self.disabled.unwrap_or(true),
 | 
			
		||||
            channel_id: self.channel.map(|c| c.id),
 | 
			
		||||
            time_display: self.relative.map_or(TimeDisplayType::Relative, |b| {
 | 
			
		||||
                if b {
 | 
			
		||||
                    TimeDisplayType::Relative
 | 
			
		||||
                } else {
 | 
			
		||||
@@ -114,14 +115,20 @@ pub async fn look(ctx: Context<'_>, options: Options) -> Result<(), Error> {
 | 
			
		||||
 | 
			
		||||
        Ok(())
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// View reminders on a specific channel
 | 
			
		||||
#[poise::command(slash_command, rename = "look", default_member_permissions = "MANAGE_GUILD")]
 | 
			
		||||
#[poise::command(
 | 
			
		||||
    slash_command,
 | 
			
		||||
    rename = "look",
 | 
			
		||||
    identifying_name = "look",
 | 
			
		||||
    default_member_permissions = "MANAGE_GUILD"
 | 
			
		||||
)]
 | 
			
		||||
pub async fn command(
 | 
			
		||||
    ctx: Context<'_>,
 | 
			
		||||
    #[description = "Channel to view reminders on"] channel: Option<PartialChannel>,
 | 
			
		||||
    #[description = "Whether to show disabled reminders or not"] disabled: Option<bool>,
 | 
			
		||||
    #[description = "Whether to display times as relative or exact times"] relative: Option<bool>,
 | 
			
		||||
) -> Result<(), Error> {
 | 
			
		||||
    look(ctx, Options { channel, disabled, relative }).await
 | 
			
		||||
    (Options { channel, disabled, relative }).run(ctx).await
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -6,7 +6,7 @@ use serde::{Deserialize, Serialize};
 | 
			
		||||
use crate::{
 | 
			
		||||
    commands::autocomplete::{time_hint_autocomplete, timezone_autocomplete},
 | 
			
		||||
    models::reminder::create_reminder,
 | 
			
		||||
    utils::Extract,
 | 
			
		||||
    utils::{Extract, Recordable},
 | 
			
		||||
    Context, Error,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
@@ -30,22 +30,23 @@ pub struct Options {
 | 
			
		||||
    timezone: Option<String>,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
pub async fn multiline(ctx: Context<'_>, options: Options) -> Result<(), Error> {
 | 
			
		||||
impl Recordable for Options {
 | 
			
		||||
    async fn run(self, ctx: Context<'_>) -> Result<(), Error> {
 | 
			
		||||
        match ctx {
 | 
			
		||||
            Context::Application(app_ctx) => {
 | 
			
		||||
            let tz = options.timezone.map(|t| t.parse::<Tz>().ok()).flatten();
 | 
			
		||||
                let tz = self.timezone.map(|t| t.parse::<Tz>().ok()).flatten();
 | 
			
		||||
                let data_opt = ContentModal::execute(app_ctx).await?;
 | 
			
		||||
 | 
			
		||||
                match data_opt {
 | 
			
		||||
                    Some(data) => {
 | 
			
		||||
                        create_reminder(
 | 
			
		||||
                            ctx,
 | 
			
		||||
                        options.time,
 | 
			
		||||
                            self.time,
 | 
			
		||||
                            data.content,
 | 
			
		||||
                        options.channels,
 | 
			
		||||
                        options.interval,
 | 
			
		||||
                        options.expires,
 | 
			
		||||
                        options.tts,
 | 
			
		||||
                            self.channels,
 | 
			
		||||
                            self.interval,
 | 
			
		||||
                            self.expires,
 | 
			
		||||
                            self.tts,
 | 
			
		||||
                            tz,
 | 
			
		||||
                        )
 | 
			
		||||
                        .await
 | 
			
		||||
@@ -54,7 +55,9 @@ pub async fn multiline(ctx: Context<'_>, options: Options) -> Result<(), Error>
 | 
			
		||||
                    None => {
 | 
			
		||||
                        warn!("Unexpected None encountered in /multiline");
 | 
			
		||||
                        Ok(ctx
 | 
			
		||||
                        .send(CreateReply::default().content("Unexpected error.").ephemeral(true))
 | 
			
		||||
                            .send(
 | 
			
		||||
                                CreateReply::default().content("Unexpected error.").ephemeral(true),
 | 
			
		||||
                            )
 | 
			
		||||
                            .await
 | 
			
		||||
                            .map(|_| ())?)
 | 
			
		||||
                    }
 | 
			
		||||
@@ -70,9 +73,15 @@ pub async fn multiline(ctx: Context<'_>, options: Options) -> Result<(), Error>
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// Create a reminder with multi-line content. Press "+4 more" for other options.
 | 
			
		||||
#[poise::command(slash_command, rename = "multiline", default_member_permissions = "MANAGE_GUILD")]
 | 
			
		||||
#[poise::command(
 | 
			
		||||
    slash_command,
 | 
			
		||||
    rename = "multiline",
 | 
			
		||||
    identifying_name = "multiline",
 | 
			
		||||
    default_member_permissions = "MANAGE_GUILD"
 | 
			
		||||
)]
 | 
			
		||||
pub async fn command(
 | 
			
		||||
    ctx: Context<'_>,
 | 
			
		||||
    #[description = "A description of the time to set the reminder for"]
 | 
			
		||||
@@ -89,5 +98,5 @@ pub async fn command(
 | 
			
		||||
    #[autocomplete = "timezone_autocomplete"]
 | 
			
		||||
    timezone: Option<String>,
 | 
			
		||||
) -> Result<(), Error> {
 | 
			
		||||
    multiline(ctx, Options { time, channels, interval, expires, tts, timezone }).await
 | 
			
		||||
    (Options { time, channels, interval, expires, tts, timezone }).run(ctx).await
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -1,6 +1,11 @@
 | 
			
		||||
use serde::{Deserialize, Serialize};
 | 
			
		||||
 | 
			
		||||
use crate::{consts::MINUTE, models::CtxData, utils::Extract, Context, Error};
 | 
			
		||||
use crate::{
 | 
			
		||||
    consts::MINUTE,
 | 
			
		||||
    models::CtxData,
 | 
			
		||||
    utils::{Extract, Recordable},
 | 
			
		||||
    Context, Error,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
#[derive(Serialize, Deserialize, Extract)]
 | 
			
		||||
pub struct Options {
 | 
			
		||||
@@ -8,9 +13,10 @@ pub struct Options {
 | 
			
		||||
    seconds: Option<i64>,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
pub async fn nudge(ctx: Context<'_>, options: Options) -> Result<(), Error> {
 | 
			
		||||
impl Recordable for Options {
 | 
			
		||||
    async fn run(self, ctx: Context<'_>) -> Result<(), Error> {
 | 
			
		||||
        let combined_time =
 | 
			
		||||
        options.minutes.map_or(0, |m| m * MINUTE as i64) + options.seconds.map_or(0, |s| s);
 | 
			
		||||
            self.minutes.map_or(0, |m| m * MINUTE as i64) + self.seconds.map_or(0, |s| s);
 | 
			
		||||
 | 
			
		||||
        if combined_time < i16::MIN as i64 || combined_time > i16::MAX as i64 {
 | 
			
		||||
            ctx.say("Nudge times must be less than 500 minutes").await?;
 | 
			
		||||
@@ -20,18 +26,25 @@ pub async fn nudge(ctx: Context<'_>, options: Options) -> Result<(), Error> {
 | 
			
		||||
            channel_data.nudge = combined_time as i16;
 | 
			
		||||
            channel_data.commit_changes(&ctx.data().database).await;
 | 
			
		||||
 | 
			
		||||
        ctx.say(format!("Future reminders will be nudged by {} seconds", combined_time)).await?;
 | 
			
		||||
            ctx.say(format!("Future reminders will be nudged by {} seconds", combined_time))
 | 
			
		||||
                .await?;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        Ok(())
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// Nudge all future reminders on this channel by a certain amount (don't use for DST! See `/offset`)
 | 
			
		||||
#[poise::command(slash_command, rename = "nudge", default_member_permissions = "MANAGE_GUILD")]
 | 
			
		||||
#[poise::command(
 | 
			
		||||
    slash_command,
 | 
			
		||||
    rename = "nudge",
 | 
			
		||||
    identifying_name = "nudge",
 | 
			
		||||
    default_member_permissions = "MANAGE_GUILD"
 | 
			
		||||
)]
 | 
			
		||||
pub async fn command(
 | 
			
		||||
    ctx: Context<'_>,
 | 
			
		||||
    #[description = "Number of minutes to nudge new reminders by"] minutes: Option<i64>,
 | 
			
		||||
    #[description = "Number of seconds to nudge new reminders by"] seconds: Option<i64>,
 | 
			
		||||
) -> Result<(), Error> {
 | 
			
		||||
    nudge(ctx, Options { minutes, seconds }).await
 | 
			
		||||
    (Options { minutes, seconds }).run(ctx).await
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -2,7 +2,7 @@ use serde::{Deserialize, Serialize};
 | 
			
		||||
 | 
			
		||||
use crate::{
 | 
			
		||||
    consts::{HOUR, MINUTE},
 | 
			
		||||
    utils::Extract,
 | 
			
		||||
    utils::{Extract, Recordable},
 | 
			
		||||
    Context, Error,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
@@ -13,12 +13,13 @@ pub struct Options {
 | 
			
		||||
    seconds: Option<i64>,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
async fn offset(ctx: Context<'_>, options: Options) -> Result<(), Error> {
 | 
			
		||||
impl Recordable for Options {
 | 
			
		||||
    async fn run(self, ctx: Context<'_>) -> Result<(), Error> {
 | 
			
		||||
        ctx.defer().await?;
 | 
			
		||||
 | 
			
		||||
    let combined_time = options.hours.map_or(0, |h| h * HOUR as i64)
 | 
			
		||||
        + options.minutes.map_or(0, |m| m * MINUTE as i64)
 | 
			
		||||
        + options.seconds.map_or(0, |s| s);
 | 
			
		||||
        let combined_time = self.hours.map_or(0, |h| h * HOUR as i64)
 | 
			
		||||
            + self.minutes.map_or(0, |m| m * MINUTE as i64)
 | 
			
		||||
            + self.seconds.map_or(0, |s| s);
 | 
			
		||||
 | 
			
		||||
        if combined_time == 0 {
 | 
			
		||||
            ctx.say("Please specify one of `hours`, `minutes` or `seconds`").await?;
 | 
			
		||||
@@ -68,14 +69,20 @@ async fn offset(ctx: Context<'_>, options: Options) -> Result<(), Error> {
 | 
			
		||||
 | 
			
		||||
        Ok(())
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// Move all reminders in the current server by a certain amount of time. Times get added together
 | 
			
		||||
#[poise::command(slash_command, rename = "offset", default_member_permissions = "MANAGE_GUILD")]
 | 
			
		||||
#[poise::command(
 | 
			
		||||
    slash_command,
 | 
			
		||||
    rename = "offset",
 | 
			
		||||
    identifying_name = "offset",
 | 
			
		||||
    default_member_permissions = "MANAGE_GUILD"
 | 
			
		||||
)]
 | 
			
		||||
pub async fn command(
 | 
			
		||||
    ctx: Context<'_>,
 | 
			
		||||
    #[description = "Number of hours to offset by"] hours: Option<i64>,
 | 
			
		||||
    #[description = "Number of minutes to offset by"] minutes: Option<i64>,
 | 
			
		||||
    #[description = "Number of seconds to offset by"] seconds: Option<i64>,
 | 
			
		||||
) -> Result<(), Error> {
 | 
			
		||||
    offset(ctx, Options { hours, minutes, seconds }).await
 | 
			
		||||
    (Options { hours, minutes, seconds }).run(ctx).await
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -1,19 +1,25 @@
 | 
			
		||||
use chrono::NaiveDateTime;
 | 
			
		||||
use serde::{Deserialize, Serialize};
 | 
			
		||||
 | 
			
		||||
use crate::{models::CtxData, time_parser::natural_parser, utils::Extract, Context, Error};
 | 
			
		||||
use crate::{
 | 
			
		||||
    models::CtxData,
 | 
			
		||||
    time_parser::natural_parser,
 | 
			
		||||
    utils::{Extract, Recordable},
 | 
			
		||||
    Context, Error,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
#[derive(Serialize, Deserialize, Extract)]
 | 
			
		||||
pub struct Options {
 | 
			
		||||
    until: Option<String>,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
pub async fn pause(ctx: Context<'_>, options: Options) -> Result<(), Error> {
 | 
			
		||||
impl Recordable for Options {
 | 
			
		||||
    async fn run(self, ctx: Context<'_>) -> Result<(), Error> {
 | 
			
		||||
        let timezone = ctx.timezone().await;
 | 
			
		||||
 | 
			
		||||
        let mut channel = ctx.channel_data().await.unwrap();
 | 
			
		||||
 | 
			
		||||
    match options.until {
 | 
			
		||||
        match self.until {
 | 
			
		||||
            Some(until) => {
 | 
			
		||||
                let parsed = natural_parser(&until, &timezone.to_string()).await;
 | 
			
		||||
 | 
			
		||||
@@ -62,12 +68,18 @@ pub async fn pause(ctx: Context<'_>, options: Options) -> Result<(), Error> {
 | 
			
		||||
 | 
			
		||||
        Ok(())
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// Pause all reminders on the current channel until a certain time or indefinitely
 | 
			
		||||
#[poise::command(slash_command, rename = "pause", default_member_permissions = "MANAGE_GUILD")]
 | 
			
		||||
#[poise::command(
 | 
			
		||||
    slash_command,
 | 
			
		||||
    rename = "pause",
 | 
			
		||||
    identifying_name = "pause",
 | 
			
		||||
    default_member_permissions = "MANAGE_GUILD"
 | 
			
		||||
)]
 | 
			
		||||
pub async fn command(
 | 
			
		||||
    ctx: Context<'_>,
 | 
			
		||||
    #[description = "When to pause until"] until: Option<String>,
 | 
			
		||||
) -> Result<(), Error> {
 | 
			
		||||
    pause(ctx, Options { until }).await
 | 
			
		||||
    (Options { until }).run(ctx).await
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -4,7 +4,7 @@ use serde::{Deserialize, Serialize};
 | 
			
		||||
use crate::{
 | 
			
		||||
    commands::autocomplete::{time_hint_autocomplete, timezone_autocomplete},
 | 
			
		||||
    models::reminder::create_reminder,
 | 
			
		||||
    utils::Extract,
 | 
			
		||||
    utils::{Extract, Recordable},
 | 
			
		||||
    Context, Error,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
@@ -19,24 +19,31 @@ pub struct Options {
 | 
			
		||||
    timezone: Option<String>,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
pub async fn remind(ctx: Context<'_>, options: Options) -> Result<(), Error> {
 | 
			
		||||
    let tz = options.timezone.map(|t| t.parse::<Tz>().ok()).flatten();
 | 
			
		||||
impl Recordable for Options {
 | 
			
		||||
    async fn run(self, ctx: Context<'_>) -> Result<(), Error> {
 | 
			
		||||
        let tz = self.timezone.map(|t| t.parse::<Tz>().ok()).flatten();
 | 
			
		||||
 | 
			
		||||
        create_reminder(
 | 
			
		||||
            ctx,
 | 
			
		||||
        options.time,
 | 
			
		||||
        options.content,
 | 
			
		||||
        options.channels,
 | 
			
		||||
        options.interval,
 | 
			
		||||
        options.expires,
 | 
			
		||||
        options.tts,
 | 
			
		||||
            self.time,
 | 
			
		||||
            self.content,
 | 
			
		||||
            self.channels,
 | 
			
		||||
            self.interval,
 | 
			
		||||
            self.expires,
 | 
			
		||||
            self.tts,
 | 
			
		||||
            tz,
 | 
			
		||||
        )
 | 
			
		||||
        .await
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// Create a reminder. Press "+4 more" for other options. Use "/multiline" for multiline content.
 | 
			
		||||
#[poise::command(slash_command, rename = "remind", default_member_permissions = "MANAGE_GUILD")]
 | 
			
		||||
#[poise::command(
 | 
			
		||||
    slash_command,
 | 
			
		||||
    rename = "remind",
 | 
			
		||||
    default_member_permissions = "MANAGE_GUILD",
 | 
			
		||||
    identifying_name = "remind"
 | 
			
		||||
)]
 | 
			
		||||
pub async fn command(
 | 
			
		||||
    ctx: Context<'_>,
 | 
			
		||||
    #[description = "The time (and optionally date) to set the reminder for"]
 | 
			
		||||
@@ -54,5 +61,5 @@ pub async fn command(
 | 
			
		||||
    #[autocomplete = "timezone_autocomplete"]
 | 
			
		||||
    timezone: Option<String>,
 | 
			
		||||
) -> Result<(), Error> {
 | 
			
		||||
    remind(ctx, Options { time, content, channels, interval, expires, tts, timezone }).await
 | 
			
		||||
    (Options { time, content, channels, interval, expires, tts, timezone }).run(ctx).await
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -8,8 +8,11 @@ use poise::{
 | 
			
		||||
use serde::{Deserialize, Serialize};
 | 
			
		||||
 | 
			
		||||
use crate::{
 | 
			
		||||
    commands::autocomplete::timezone_autocomplete, consts::THEME_COLOR, models::CtxData,
 | 
			
		||||
    utils::Extract, Context, Error,
 | 
			
		||||
    commands::autocomplete::timezone_autocomplete,
 | 
			
		||||
    consts::THEME_COLOR,
 | 
			
		||||
    models::CtxData,
 | 
			
		||||
    utils::{Extract, Recordable},
 | 
			
		||||
    Context, Error,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
#[derive(Serialize, Deserialize, Extract)]
 | 
			
		||||
@@ -17,12 +20,13 @@ pub struct Options {
 | 
			
		||||
    pub timezone: Option<String>,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
pub async fn timezone_fn(ctx: Context<'_>, options: Options) -> Result<(), Error> {
 | 
			
		||||
impl Recordable for Options {
 | 
			
		||||
    async fn run(self, ctx: Context<'_>) -> Result<(), Error> {
 | 
			
		||||
        let mut user_data = ctx.author_data().await.unwrap();
 | 
			
		||||
 | 
			
		||||
        let footer_text = format!("Current timezone: {}", user_data.timezone);
 | 
			
		||||
 | 
			
		||||
    if let Some(timezone) = options.timezone {
 | 
			
		||||
        if let Some(timezone) = self.timezone {
 | 
			
		||||
            match timezone.parse::<Tz>() {
 | 
			
		||||
                Ok(tz) => {
 | 
			
		||||
                    user_data.timezone = timezone.clone();
 | 
			
		||||
@@ -87,7 +91,11 @@ pub async fn timezone_fn(ctx: Context<'_>, options: Options) -> Result<(), Error
 | 
			
		||||
            }
 | 
			
		||||
        } else {
 | 
			
		||||
            let popular_timezones_iter = ctx.data().popular_timezones.iter().map(|t| {
 | 
			
		||||
            (t.to_string(), format!("🕗 `{}`", Utc::now().with_timezone(t).format("%H:%M")), true)
 | 
			
		||||
                (
 | 
			
		||||
                    t.to_string(),
 | 
			
		||||
                    format!("🕗 `{}`", Utc::now().with_timezone(t).format("%H:%M")),
 | 
			
		||||
                    true,
 | 
			
		||||
                )
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            ctx.send(
 | 
			
		||||
@@ -114,14 +122,15 @@ You may want to use one of the popular timezones below, otherwise click [here](h
 | 
			
		||||
 | 
			
		||||
        Ok(())
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// Select your timezone
 | 
			
		||||
#[poise::command(slash_command, rename = "timezone")]
 | 
			
		||||
#[poise::command(slash_command, rename = "timezone", identifying_name = "timezone")]
 | 
			
		||||
pub async fn command(
 | 
			
		||||
    ctx: Context<'_>,
 | 
			
		||||
    #[description = "Timezone to use from this list: https://gist.github.com/JellyWX/913dfc8b63d45192ad6cb54c829324ee"]
 | 
			
		||||
    #[autocomplete = "timezone_autocomplete"]
 | 
			
		||||
    timezone: Option<String>,
 | 
			
		||||
) -> Result<(), Error> {
 | 
			
		||||
    timezone_fn(ctx, Options { timezone }).await
 | 
			
		||||
    (Options { timezone }).run(ctx).await
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -2,12 +2,17 @@ use log::warn;
 | 
			
		||||
use poise::CreateReply;
 | 
			
		||||
use serde::{Deserialize, Serialize};
 | 
			
		||||
 | 
			
		||||
use crate::{models::CtxData, utils::Extract, Context, Error};
 | 
			
		||||
use crate::{
 | 
			
		||||
    models::CtxData,
 | 
			
		||||
    utils::{Extract, Recordable},
 | 
			
		||||
    Context, Error,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
#[derive(Serialize, Deserialize, Extract)]
 | 
			
		||||
pub struct Options;
 | 
			
		||||
 | 
			
		||||
pub async fn webhook(ctx: Context<'_>, _options: Options) -> Result<(), Error> {
 | 
			
		||||
impl Recordable for Options {
 | 
			
		||||
    async fn run(self, ctx: Context<'_>) -> Result<(), Error> {
 | 
			
		||||
        match ctx.channel_data().await {
 | 
			
		||||
            Ok(data) => {
 | 
			
		||||
                if let (Some(id), Some(token)) = (data.webhook_id, data.webhook_token) {
 | 
			
		||||
@@ -32,9 +37,15 @@ Do not share it!
 | 
			
		||||
 | 
			
		||||
        Ok(())
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// View the webhook being used to send reminders to this channel
 | 
			
		||||
#[poise::command(slash_command, rename = "webhook", required_permissions = "ADMINISTRATOR")]
 | 
			
		||||
#[poise::command(
 | 
			
		||||
    slash_command,
 | 
			
		||||
    rename = "webhook",
 | 
			
		||||
    identifying_name = "webhook",
 | 
			
		||||
    required_permissions = "ADMINISTRATOR"
 | 
			
		||||
)]
 | 
			
		||||
pub async fn command(ctx: Context<'_>) -> Result<(), Error> {
 | 
			
		||||
    webhook(ctx, Options {}).await
 | 
			
		||||
    (Options {}).run(ctx).await
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										27
									
								
								src/hooks.rs
									
									
									
									
									
								
							
							
						
						
									
										27
									
								
								src/hooks.rs
									
									
									
									
									
								
							@@ -13,18 +13,6 @@ async fn macro_check(ctx: Context<'_>) -> bool {
 | 
			
		||||
                let mut lock = ctx.data().recording_macros.write().await;
 | 
			
		||||
 | 
			
		||||
                if let Some(command_macro) = lock.get_mut(&(guild_id, ctx.author().id)) {
 | 
			
		||||
                    if ctx.command().identifying_name != "remind" {
 | 
			
		||||
                        let _ = ctx
 | 
			
		||||
                            .send(
 | 
			
		||||
                                CreateReply::default()
 | 
			
		||||
                                    .ephemeral(true)
 | 
			
		||||
                                    .content("Macro recording only supports `/remind`. Please stop recording with `/macro finish` before using other commands.")
 | 
			
		||||
                            )
 | 
			
		||||
                            .await;
 | 
			
		||||
 | 
			
		||||
                        return false;
 | 
			
		||||
                    }
 | 
			
		||||
 | 
			
		||||
                    if command_macro.commands.len() >= MACRO_MAX_COMMANDS {
 | 
			
		||||
                        let _ = ctx
 | 
			
		||||
                            .send(
 | 
			
		||||
@@ -34,7 +22,8 @@ async fn macro_check(ctx: Context<'_>) -> bool {
 | 
			
		||||
                            )
 | 
			
		||||
                            .await;
 | 
			
		||||
                    } else {
 | 
			
		||||
                        let recorded = RecordedCommand::from_context(app_ctx).unwrap();
 | 
			
		||||
                        match RecordedCommand::from_context(app_ctx) {
 | 
			
		||||
                            Some(recorded) => {
 | 
			
		||||
                                command_macro.commands.push(recorded);
 | 
			
		||||
 | 
			
		||||
                                let _ = ctx
 | 
			
		||||
@@ -46,6 +35,18 @@ async fn macro_check(ctx: Context<'_>) -> bool {
 | 
			
		||||
                                    .await;
 | 
			
		||||
                            }
 | 
			
		||||
 | 
			
		||||
                            None => {
 | 
			
		||||
                                let _ = ctx
 | 
			
		||||
                                    .send(
 | 
			
		||||
                                        CreateReply::default().ephemeral(true).content(
 | 
			
		||||
                                            "This command is not supported in macros yet.",
 | 
			
		||||
                                        ),
 | 
			
		||||
                                    )
 | 
			
		||||
                                    .await;
 | 
			
		||||
                            }
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
 | 
			
		||||
                    return false;
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
 
 | 
			
		||||
@@ -2,29 +2,64 @@ use poise::serenity_prelude::model::id::GuildId;
 | 
			
		||||
use serde::{Deserialize, Serialize};
 | 
			
		||||
use serde_json::Value;
 | 
			
		||||
 | 
			
		||||
use crate::{commands::remind, utils::Extract, ApplicationContext, Context, Error};
 | 
			
		||||
use crate::{
 | 
			
		||||
    utils::{Extract, Recordable},
 | 
			
		||||
    ApplicationContext, Context,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
#[derive(Serialize, Deserialize)]
 | 
			
		||||
#[derive(Serialize, Deserialize, Recordable)]
 | 
			
		||||
#[serde(tag = "command_name")]
 | 
			
		||||
pub enum RecordedCommand {
 | 
			
		||||
    Remind(remind::Options),
 | 
			
		||||
    #[serde(rename = "clock")]
 | 
			
		||||
    Clock(crate::commands::clock::Options),
 | 
			
		||||
    #[serde(rename = "dashboard")]
 | 
			
		||||
    Dashboard(crate::commands::dashboard::Options),
 | 
			
		||||
    #[serde(rename = "delete")]
 | 
			
		||||
    Delete(crate::commands::delete::Options),
 | 
			
		||||
    #[serde(rename = "donate")]
 | 
			
		||||
    Donate(crate::commands::donate::Options),
 | 
			
		||||
    #[serde(rename = "help")]
 | 
			
		||||
    Help(crate::commands::help::Options),
 | 
			
		||||
    #[serde(rename = "info")]
 | 
			
		||||
    Info(crate::commands::info::Options),
 | 
			
		||||
    #[serde(rename = "look")]
 | 
			
		||||
    Look(crate::commands::look::Options),
 | 
			
		||||
    #[serde(rename = "multiline")]
 | 
			
		||||
    Multiline(crate::commands::multiline::Options),
 | 
			
		||||
    #[serde(rename = "nudge")]
 | 
			
		||||
    Nudge(crate::commands::nudge::Options),
 | 
			
		||||
    #[serde(rename = "offset")]
 | 
			
		||||
    Offset(crate::commands::offset::Options),
 | 
			
		||||
    #[serde(rename = "pause")]
 | 
			
		||||
    Pause(crate::commands::pause::Options),
 | 
			
		||||
    #[serde(rename = "remind")]
 | 
			
		||||
    Remind(crate::commands::remind::Options),
 | 
			
		||||
    #[serde(rename = "timezone")]
 | 
			
		||||
    Timezone(crate::commands::timezone::Options),
 | 
			
		||||
    #[serde(rename = "webhook")]
 | 
			
		||||
    Webhook(crate::commands::webhook::Options),
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl RecordedCommand {
 | 
			
		||||
    pub fn from_context(ctx: ApplicationContext) -> Option<Self> {
 | 
			
		||||
        match ctx.command().identifying_name.as_str() {
 | 
			
		||||
            "remind" => Some(Self::Remind(remind::Options::extract(ctx))),
 | 
			
		||||
            "clock" => Some(Self::Clock(crate::commands::clock::Options::extract(ctx))),
 | 
			
		||||
            "dashboard" => Some(Self::Dashboard(crate::commands::dashboard::Options::extract(ctx))),
 | 
			
		||||
            "delete" => Some(Self::Delete(crate::commands::delete::Options::extract(ctx))),
 | 
			
		||||
            "donate" => Some(Self::Donate(crate::commands::donate::Options::extract(ctx))),
 | 
			
		||||
            "help" => Some(Self::Help(crate::commands::help::Options::extract(ctx))),
 | 
			
		||||
            "info" => Some(Self::Info(crate::commands::info::Options::extract(ctx))),
 | 
			
		||||
            "look" => Some(Self::Look(crate::commands::look::Options::extract(ctx))),
 | 
			
		||||
            "multiline" => Some(Self::Multiline(crate::commands::multiline::Options::extract(ctx))),
 | 
			
		||||
            "nudge" => Some(Self::Nudge(crate::commands::nudge::Options::extract(ctx))),
 | 
			
		||||
            "offset" => Some(Self::Offset(crate::commands::offset::Options::extract(ctx))),
 | 
			
		||||
            "pause" => Some(Self::Pause(crate::commands::pause::Options::extract(ctx))),
 | 
			
		||||
            "remind" => Some(Self::Remind(crate::commands::remind::Options::extract(ctx))),
 | 
			
		||||
            "timezone" => Some(Self::Timezone(crate::commands::timezone::Options::extract(ctx))),
 | 
			
		||||
            "webhook" => Some(Self::Webhook(crate::commands::webhook::Options::extract(ctx))),
 | 
			
		||||
            _ => None,
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pub async fn execute(self, ctx: ApplicationContext<'_>) -> Result<(), Error> {
 | 
			
		||||
        match self {
 | 
			
		||||
            RecordedCommand::Remind(options) => {
 | 
			
		||||
                remind::remind(Context::Application(ctx), options).await
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
pub struct CommandMacro {
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										10
									
								
								src/utils.rs
									
									
									
									
									
								
							
							
						
						
									
										10
									
								
								src/utils.rs
									
									
									
									
									
								
							@@ -9,7 +9,7 @@ use poise::{
 | 
			
		||||
 | 
			
		||||
use crate::{
 | 
			
		||||
    consts::{CNC_GUILD, SUBSCRIPTION_ROLES},
 | 
			
		||||
    ApplicationContext, Context,
 | 
			
		||||
    ApplicationContext, Context, Error,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
pub async fn check_subscription(cache_http: impl CacheHttp, user_id: impl Into<UserId>) -> bool {
 | 
			
		||||
@@ -65,11 +65,17 @@ pub fn footer(ctx: Context<'_>) -> CreateEmbedFooter {
 | 
			
		||||
    ))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
pub trait Recordable {
 | 
			
		||||
    async fn run(self, ctx: Context<'_>) -> Result<(), Error>;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
pub use recordable_derive::Recordable;
 | 
			
		||||
 | 
			
		||||
pub trait Extract {
 | 
			
		||||
    fn extract(ctx: ApplicationContext) -> Self;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
pub use extract_macro::Extract;
 | 
			
		||||
pub use extract_derive::Extract;
 | 
			
		||||
 | 
			
		||||
macro_rules! extract_arg {
 | 
			
		||||
    ($ctx:ident, $name:ident, String) => {
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user