Extract trait
This commit is contained in:
		
							
								
								
									
										2
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							@@ -1,4 +1,4 @@
 | 
			
		||||
/target
 | 
			
		||||
target
 | 
			
		||||
.env
 | 
			
		||||
/venv
 | 
			
		||||
.cargo
 | 
			
		||||
 
 | 
			
		||||
@@ -1,17 +1,13 @@
 | 
			
		||||
use crate::{consts::MINUTE, models::CtxData, Context, Error};
 | 
			
		||||
 | 
			
		||||
/// Nudge all future reminders on this channel by a certain amount (don't use for DST! See `/offset`)
 | 
			
		||||
#[poise::command(
 | 
			
		||||
    slash_command,
 | 
			
		||||
    identifying_name = "nudge",
 | 
			
		||||
    default_member_permissions = "MANAGE_GUILD"
 | 
			
		||||
)]
 | 
			
		||||
pub async fn nudge(
 | 
			
		||||
    ctx: Context<'_>,
 | 
			
		||||
    #[description = "Number of minutes to nudge new reminders by"] minutes: Option<isize>,
 | 
			
		||||
    #[description = "Number of seconds to nudge new reminders by"] seconds: Option<isize>,
 | 
			
		||||
) -> Result<(), Error> {
 | 
			
		||||
    let combined_time = minutes.map_or(0, |m| m * MINUTE as isize) + seconds.map_or(0, |s| s);
 | 
			
		||||
pub struct Options {
 | 
			
		||||
    minutes: Option<isize>,
 | 
			
		||||
    seconds: Option<isize>,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
pub async fn nudge(ctx: Context<'_>, options: Options) -> Result<(), Error> {
 | 
			
		||||
    let combined_time =
 | 
			
		||||
        options.minutes.map_or(0, |m| m * MINUTE as isize) + options.seconds.map_or(0, |s| s);
 | 
			
		||||
 | 
			
		||||
    if combined_time < i16::MIN as isize || combined_time > i16::MAX as isize {
 | 
			
		||||
        ctx.say("Nudge times must be less than 500 minutes").await?;
 | 
			
		||||
@@ -26,3 +22,17 @@ pub async fn nudge(
 | 
			
		||||
 | 
			
		||||
    Ok(())
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// Nudge all future reminders on this channel by a certain amount (don't use for DST! See `/offset`)
 | 
			
		||||
#[poise::command(
 | 
			
		||||
    slash_command,
 | 
			
		||||
    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<isize>,
 | 
			
		||||
    #[description = "Number of seconds to nudge new reminders by"] seconds: Option<isize>,
 | 
			
		||||
) -> Result<(), Error> {
 | 
			
		||||
    nudge(ctx, Options { minutes, seconds }).await
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -1,25 +1,23 @@
 | 
			
		||||
use serde::{Deserialize, Serialize};
 | 
			
		||||
 | 
			
		||||
use crate::{
 | 
			
		||||
    consts::{HOUR, MINUTE},
 | 
			
		||||
    Context, Error,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
/// Move all reminders in the current server by a certain amount of time. Times get added together
 | 
			
		||||
#[poise::command(
 | 
			
		||||
    slash_command,
 | 
			
		||||
    identifying_name = "offset",
 | 
			
		||||
    default_member_permissions = "MANAGE_GUILD"
 | 
			
		||||
)]
 | 
			
		||||
pub async fn offset(
 | 
			
		||||
    ctx: Context<'_>,
 | 
			
		||||
    #[description = "Number of hours to offset by"] hours: Option<isize>,
 | 
			
		||||
    #[description = "Number of minutes to offset by"] minutes: Option<isize>,
 | 
			
		||||
    #[description = "Number of seconds to offset by"] seconds: Option<isize>,
 | 
			
		||||
) -> Result<(), Error> {
 | 
			
		||||
#[derive(Serialize, Deserialize, Default)]
 | 
			
		||||
pub struct Options {
 | 
			
		||||
    hours: Option<isize>,
 | 
			
		||||
    minutes: Option<isize>,
 | 
			
		||||
    seconds: Option<isize>,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
async fn offset(ctx: Context<'_>, options: Options) -> Result<(), Error> {
 | 
			
		||||
    ctx.defer().await?;
 | 
			
		||||
 | 
			
		||||
    let combined_time = hours.map_or(0, |h| h * HOUR as isize)
 | 
			
		||||
        + minutes.map_or(0, |m| m * MINUTE as isize)
 | 
			
		||||
        + seconds.map_or(0, |s| s);
 | 
			
		||||
    let combined_time = options.hours.map_or(0, |h| h * HOUR as isize)
 | 
			
		||||
        + options.minutes.map_or(0, |m| m * MINUTE as isize)
 | 
			
		||||
        + options.seconds.map_or(0, |s| s);
 | 
			
		||||
 | 
			
		||||
    if combined_time == 0 {
 | 
			
		||||
        ctx.say("Please specify one of `hours`, `minutes` or `seconds`").await?;
 | 
			
		||||
@@ -69,3 +67,18 @@ pub async fn offset(
 | 
			
		||||
 | 
			
		||||
    Ok(())
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// Move all reminders in the current server by a certain amount of time. Times get added together
 | 
			
		||||
#[poise::command(
 | 
			
		||||
    slash_command,
 | 
			
		||||
    identifying_name = "offset",
 | 
			
		||||
    default_member_permissions = "MANAGE_GUILD"
 | 
			
		||||
)]
 | 
			
		||||
pub async fn command(
 | 
			
		||||
    ctx: Context<'_>,
 | 
			
		||||
    #[description = "Number of hours to offset by"] hours: Option<isize>,
 | 
			
		||||
    #[description = "Number of minutes to offset by"] minutes: Option<isize>,
 | 
			
		||||
    #[description = "Number of seconds to offset by"] seconds: Option<isize>,
 | 
			
		||||
) -> Result<(), Error> {
 | 
			
		||||
    offset(ctx, Options { hours, minutes, seconds }).await
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -1,22 +1,28 @@
 | 
			
		||||
use chrono::NaiveDateTime;
 | 
			
		||||
use serde::{Deserialize, Serialize};
 | 
			
		||||
 | 
			
		||||
use crate::{models::CtxData, time_parser::natural_parser, Context, Error};
 | 
			
		||||
use crate::{
 | 
			
		||||
    models::CtxData, time_parser::natural_parser, utils::Extract, ApplicationContext, Context,
 | 
			
		||||
    Error,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
/// Pause all reminders on the current channel until a certain time or indefinitely
 | 
			
		||||
#[poise::command(
 | 
			
		||||
    slash_command,
 | 
			
		||||
    identifying_name = "pause",
 | 
			
		||||
    default_member_permissions = "MANAGE_GUILD"
 | 
			
		||||
)]
 | 
			
		||||
pub async fn pause(
 | 
			
		||||
    ctx: Context<'_>,
 | 
			
		||||
    #[description = "When to pause until"] until: Option<String>,
 | 
			
		||||
) -> Result<(), Error> {
 | 
			
		||||
#[derive(Serialize, Deserialize, Extract)]
 | 
			
		||||
pub struct Options {
 | 
			
		||||
    until: Option<String>,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl Extract for Options {
 | 
			
		||||
    fn extract(ctx: ApplicationContext) -> Self {
 | 
			
		||||
        Self { until: extract_arg!(ctx, "until", Option<String>) }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
pub async fn pause(ctx: Context<'_>, options: Options) -> Result<(), Error> {
 | 
			
		||||
    let timezone = ctx.timezone().await;
 | 
			
		||||
 | 
			
		||||
    let mut channel = ctx.channel_data().await.unwrap();
 | 
			
		||||
 | 
			
		||||
    match until {
 | 
			
		||||
    match options.until {
 | 
			
		||||
        Some(until) => {
 | 
			
		||||
            let parsed = natural_parser(&until, &timezone.to_string()).await;
 | 
			
		||||
 | 
			
		||||
@@ -65,3 +71,16 @@ pub async fn pause(
 | 
			
		||||
 | 
			
		||||
    Ok(())
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// Pause all reminders on the current channel until a certain time or indefinitely
 | 
			
		||||
#[poise::command(
 | 
			
		||||
    slash_command,
 | 
			
		||||
    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
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -4,18 +4,49 @@ use serde::{Deserialize, Serialize};
 | 
			
		||||
use crate::{
 | 
			
		||||
    commands::autocomplete::{time_hint_autocomplete, timezone_autocomplete},
 | 
			
		||||
    models::reminder::create_reminder,
 | 
			
		||||
    utils::{extract_arg, Extract},
 | 
			
		||||
    ApplicationContext, Context, Error,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
#[derive(Serialize, Deserialize, Default)]
 | 
			
		||||
pub struct RemindOptions {
 | 
			
		||||
    pub time: String,
 | 
			
		||||
    pub content: String,
 | 
			
		||||
    pub channels: Option<String>,
 | 
			
		||||
    pub interval: Option<String>,
 | 
			
		||||
    pub expires: Option<String>,
 | 
			
		||||
    pub tts: Option<bool>,
 | 
			
		||||
    pub timezone: Option<Tz>,
 | 
			
		||||
#[derive(Serialize, Deserialize)]
 | 
			
		||||
pub struct Options {
 | 
			
		||||
    time: String,
 | 
			
		||||
    content: String,
 | 
			
		||||
    channels: Option<String>,
 | 
			
		||||
    interval: Option<String>,
 | 
			
		||||
    expires: Option<String>,
 | 
			
		||||
    tts: Option<bool>,
 | 
			
		||||
    timezone: Option<String>,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl Extract for Options {
 | 
			
		||||
    fn extract(ctx: ApplicationContext) -> Self {
 | 
			
		||||
        Self {
 | 
			
		||||
            time: extract_arg!(ctx, "time", String),
 | 
			
		||||
            content: extract_arg!(ctx, "content", String),
 | 
			
		||||
            channels: extract_arg!(ctx, "channels", Option<String>),
 | 
			
		||||
            interval: extract_arg!(ctx, "interval", Option<String>),
 | 
			
		||||
            expires: extract_arg!(ctx, "expires", Option<String>),
 | 
			
		||||
            tts: extract_arg!(ctx, "tts", Option<bool>),
 | 
			
		||||
            timezone: extract_arg!(ctx, "timezone", Option<String>),
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
pub async fn remind(ctx: Context<'_>, options: Options) -> Result<(), Error> {
 | 
			
		||||
    let tz = options.timezone.map(|t| t.parse::<Tz>().ok()).flatten();
 | 
			
		||||
 | 
			
		||||
    create_reminder(
 | 
			
		||||
        ctx,
 | 
			
		||||
        options.time,
 | 
			
		||||
        options.content,
 | 
			
		||||
        options.channels,
 | 
			
		||||
        options.interval,
 | 
			
		||||
        options.expires,
 | 
			
		||||
        options.tts,
 | 
			
		||||
        tz,
 | 
			
		||||
    )
 | 
			
		||||
    .await
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// Create a reminder. Press "+4 more" for other options. Use "/multiline" for multiline content.
 | 
			
		||||
@@ -24,8 +55,8 @@ pub struct RemindOptions {
 | 
			
		||||
    identifying_name = "remind",
 | 
			
		||||
    default_member_permissions = "MANAGE_GUILD"
 | 
			
		||||
)]
 | 
			
		||||
pub async fn remind(
 | 
			
		||||
    ctx: ApplicationContext<'_>,
 | 
			
		||||
pub async fn command(
 | 
			
		||||
    ctx: Context<'_>,
 | 
			
		||||
    #[description = "The time (and optionally date) to set the reminder for"]
 | 
			
		||||
    #[autocomplete = "time_hint_autocomplete"]
 | 
			
		||||
    time: String,
 | 
			
		||||
@@ -41,8 +72,5 @@ pub async fn remind(
 | 
			
		||||
    #[autocomplete = "timezone_autocomplete"]
 | 
			
		||||
    timezone: Option<String>,
 | 
			
		||||
) -> Result<(), Error> {
 | 
			
		||||
    let tz = timezone.map(|t| t.parse::<Tz>().ok()).flatten();
 | 
			
		||||
 | 
			
		||||
    create_reminder(Context::Application(ctx), time, content, channels, interval, expires, tts, tz)
 | 
			
		||||
        .await
 | 
			
		||||
    remind(ctx, Options { time, content, channels, interval, expires, tts, timezone }).await
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -5,25 +5,24 @@ use poise::{
 | 
			
		||||
    serenity_prelude::{CreateEmbed, CreateEmbedFooter},
 | 
			
		||||
    CreateReply,
 | 
			
		||||
};
 | 
			
		||||
use serde::{Deserialize, Serialize};
 | 
			
		||||
 | 
			
		||||
use crate::{
 | 
			
		||||
    commands::autocomplete::timezone_autocomplete, consts::THEME_COLOR, models::CtxData, Context,
 | 
			
		||||
    Error,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
/// Select your timezone
 | 
			
		||||
#[poise::command(slash_command, identifying_name = "timezone")]
 | 
			
		||||
pub async fn timezone(
 | 
			
		||||
    ctx: Context<'_>,
 | 
			
		||||
    #[description = "Timezone to use from this list: https://gist.github.com/JellyWX/913dfc8b63d45192ad6cb54c829324ee"]
 | 
			
		||||
    #[autocomplete = "timezone_autocomplete"]
 | 
			
		||||
    timezone: Option<String>,
 | 
			
		||||
) -> Result<(), Error> {
 | 
			
		||||
#[derive(Serialize, Deserialize)]
 | 
			
		||||
pub struct Options {
 | 
			
		||||
    pub timezone: Option<String>,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
pub async fn timezone_fn(ctx: Context<'_>, options: Options) -> Result<(), Error> {
 | 
			
		||||
    let mut user_data = ctx.author_data().await.unwrap();
 | 
			
		||||
 | 
			
		||||
    let footer_text = format!("Current timezone: {}", user_data.timezone);
 | 
			
		||||
 | 
			
		||||
    if let Some(timezone) = timezone {
 | 
			
		||||
    if let Some(timezone) = options.timezone {
 | 
			
		||||
        match timezone.parse::<Tz>() {
 | 
			
		||||
            Ok(tz) => {
 | 
			
		||||
                user_data.timezone = timezone.clone();
 | 
			
		||||
@@ -115,3 +114,14 @@ You may want to use one of the popular timezones below, otherwise click [here](h
 | 
			
		||||
 | 
			
		||||
    Ok(())
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// Select your timezone
 | 
			
		||||
#[poise::command(slash_command, 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
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -1,15 +1,19 @@
 | 
			
		||||
use log::warn;
 | 
			
		||||
use poise::CreateReply;
 | 
			
		||||
use serde::{Deserialize, Serialize};
 | 
			
		||||
 | 
			
		||||
use crate::{models::CtxData, Context, Error};
 | 
			
		||||
use crate::{models::CtxData, utils::Extract, ApplicationContext, Context, Error};
 | 
			
		||||
 | 
			
		||||
/// View the webhook being used to send reminders to this channel
 | 
			
		||||
#[poise::command(
 | 
			
		||||
    slash_command,
 | 
			
		||||
    identifying_name = "webhook_url",
 | 
			
		||||
    required_permissions = "ADMINISTRATOR"
 | 
			
		||||
)]
 | 
			
		||||
pub async fn webhook(ctx: Context<'_>) -> Result<(), Error> {
 | 
			
		||||
#[derive(Serialize, Deserialize)]
 | 
			
		||||
pub struct Options;
 | 
			
		||||
 | 
			
		||||
impl Extract for Options {
 | 
			
		||||
    fn extract(_ctx: ApplicationContext) -> Self {
 | 
			
		||||
        Self {}
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
pub async fn webhook(ctx: Context<'_>, _options: Options) -> Result<(), Error> {
 | 
			
		||||
    match ctx.channel_data().await {
 | 
			
		||||
        Ok(data) => {
 | 
			
		||||
            if let (Some(id), Some(token)) = (data.webhook_id, data.webhook_token) {
 | 
			
		||||
@@ -34,3 +38,13 @@ Do not share it!
 | 
			
		||||
 | 
			
		||||
    Ok(())
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// View the webhook being used to send reminders to this channel
 | 
			
		||||
#[poise::command(
 | 
			
		||||
    slash_command,
 | 
			
		||||
    identifying_name = "webhook_url",
 | 
			
		||||
    required_permissions = "ADMINISTRATOR"
 | 
			
		||||
)]
 | 
			
		||||
pub async fn command(ctx: Context<'_>) -> Result<(), Error> {
 | 
			
		||||
    webhook(ctx, Options {}).await
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										46
									
								
								src/extract_macro/Cargo.lock
									
									
									
										generated
									
									
									
										Normal file
									
								
							
							
						
						
									
										46
									
								
								src/extract_macro/Cargo.lock
									
									
									
										generated
									
									
									
										Normal file
									
								
							@@ -0,0 +1,46 @@
 | 
			
		||||
# This file is automatically @generated by Cargo.
 | 
			
		||||
# It is not intended for manual editing.
 | 
			
		||||
version = 3
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "extract_macro"
 | 
			
		||||
version = "0.1.0"
 | 
			
		||||
dependencies = [
 | 
			
		||||
 "quote",
 | 
			
		||||
 "syn",
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "proc-macro2"
 | 
			
		||||
version = "1.0.78"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "e2422ad645d89c99f8f3e6b88a9fdeca7fabeac836b1002371c4367c8f984aae"
 | 
			
		||||
dependencies = [
 | 
			
		||||
 "unicode-ident",
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "quote"
 | 
			
		||||
version = "1.0.35"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "291ec9ab5efd934aaf503a6466c5d5251535d108ee747472c3977cc5acc868ef"
 | 
			
		||||
dependencies = [
 | 
			
		||||
 "proc-macro2",
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "syn"
 | 
			
		||||
version = "2.0.49"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "915aea9e586f80826ee59f8453c1101f9d1c4b3964cd2460185ee8e299ada496"
 | 
			
		||||
dependencies = [
 | 
			
		||||
 "proc-macro2",
 | 
			
		||||
 "quote",
 | 
			
		||||
 "unicode-ident",
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "unicode-ident"
 | 
			
		||||
version = "1.0.12"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b"
 | 
			
		||||
							
								
								
									
										11
									
								
								src/extract_macro/Cargo.toml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										11
									
								
								src/extract_macro/Cargo.toml
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,11 @@
 | 
			
		||||
[package]
 | 
			
		||||
name = "extract_macro"
 | 
			
		||||
version = "0.1.0"
 | 
			
		||||
edition = "2021"
 | 
			
		||||
 | 
			
		||||
[lib]
 | 
			
		||||
proc-macro = true
 | 
			
		||||
 | 
			
		||||
[dependencies]
 | 
			
		||||
quote = "1.0.35"
 | 
			
		||||
syn = { version = "2.0.49", features = ["full"] }
 | 
			
		||||
							
								
								
									
										70
									
								
								src/extract_macro/src/lib.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										70
									
								
								src/extract_macro/src/lib.rs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,70 @@
 | 
			
		||||
macro_rules! extract_arg {
 | 
			
		||||
    ($ctx:ident, $name:literal, String) => {
 | 
			
		||||
        $ctx.args.iter().find(|opt| opt.name == $name).map(|opt| &opt.value).map_or_else(
 | 
			
		||||
            || String::new(),
 | 
			
		||||
            |v| match v {
 | 
			
		||||
                poise::serenity_prelude::ResolvedValue::String(s) => s.to_string(),
 | 
			
		||||
                _ => String::new(),
 | 
			
		||||
            },
 | 
			
		||||
        )
 | 
			
		||||
    };
 | 
			
		||||
    ($ctx:ident, $name:literal, Option<String>) => {
 | 
			
		||||
        $ctx.args
 | 
			
		||||
            .iter()
 | 
			
		||||
            .find(|opt| opt.name == $name)
 | 
			
		||||
            .map(|opt| &opt.value)
 | 
			
		||||
            .map(|v| match v {
 | 
			
		||||
                poise::serenity_prelude::ResolvedValue::String(s) => Some(s.to_string()),
 | 
			
		||||
                _ => None,
 | 
			
		||||
            })
 | 
			
		||||
            .flatten()
 | 
			
		||||
    };
 | 
			
		||||
    ($ctx:ident, $name:literal, bool) => {
 | 
			
		||||
        $ctx.args.iter().find(|opt| opt.name == $name).map(|opt| &opt.value).map_or(false, |v| {
 | 
			
		||||
            match v {
 | 
			
		||||
                poise::serenity_prelude::ResolvedValue::Boolean(b) => b.to_owned(),
 | 
			
		||||
                _ => false,
 | 
			
		||||
            }
 | 
			
		||||
        })
 | 
			
		||||
    };
 | 
			
		||||
    ($ctx:ident, $name:literal, Option<bool>) => {
 | 
			
		||||
        $ctx.args
 | 
			
		||||
            .iter()
 | 
			
		||||
            .find(|opt| opt.name == $name)
 | 
			
		||||
            .map(|opt| &opt.value)
 | 
			
		||||
            .map(|v| match v {
 | 
			
		||||
                poise::serenity_prelude::ResolvedValue::Boolean(b) => Some(b.to_owned()),
 | 
			
		||||
                _ => None,
 | 
			
		||||
            })
 | 
			
		||||
            .flatten()
 | 
			
		||||
    };
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
use proc_macro::TokenStream;
 | 
			
		||||
use syn::parse::Parser;
 | 
			
		||||
 | 
			
		||||
#[proc_macro_derive(Extract)]
 | 
			
		||||
pub fn extract(input: TokenStream) -> TokenStream {
 | 
			
		||||
    // Construct a string representation of the type definition
 | 
			
		||||
    let s = input.to_string();
 | 
			
		||||
 | 
			
		||||
    // Parse the string representation
 | 
			
		||||
    let ast = syn::parse_derive_input(&s).unwrap();
 | 
			
		||||
 | 
			
		||||
    // Build the impl
 | 
			
		||||
    let gen = impl_extract(&ast);
 | 
			
		||||
 | 
			
		||||
    // Return the generated impl
 | 
			
		||||
    gen.parse().unwrap()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
fn impl_extract(ast: &syn::DeriveInput) -> TokenStream {
 | 
			
		||||
    let name = &ast.ident;
 | 
			
		||||
    TokenStream::from(quote::quote! {
 | 
			
		||||
        impl Extract for #name {
 | 
			
		||||
            fn extract(ctx: ) -> Self {
 | 
			
		||||
                println!("Hello, World! My name is {}", stringify!(#name));
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    })
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										20
									
								
								src/main.rs
									
									
									
									
									
								
							
							
						
						
									
										20
									
								
								src/main.rs
									
									
									
									
									
								
							@@ -37,7 +37,7 @@ use crate::{
 | 
			
		||||
    commands::{
 | 
			
		||||
        allowed_dm, clock::clock, clock_context_menu::clock_context_menu, command_macro,
 | 
			
		||||
        dashboard::dashboard, delete, donate::donate, help::help, info::info, look, multiline,
 | 
			
		||||
        nudge, offset, pause, remind, settings, timer, timezone::timezone, todo, webhook::webhook,
 | 
			
		||||
        nudge, offset, pause, remind, settings, timer, timezone, todo, webhook,
 | 
			
		||||
    },
 | 
			
		||||
    consts::THEME_COLOR,
 | 
			
		||||
    event_handlers::listener,
 | 
			
		||||
@@ -109,7 +109,7 @@ async fn _main(tx: Sender<()>) -> Result<(), Box<dyn StdError + Send + Sync>> {
 | 
			
		||||
            clock(),
 | 
			
		||||
            clock_context_menu(),
 | 
			
		||||
            dashboard(),
 | 
			
		||||
            timezone(),
 | 
			
		||||
            timezone::command(),
 | 
			
		||||
            poise::Command {
 | 
			
		||||
                subcommands: vec![
 | 
			
		||||
                    allowed_dm::set_allowed_dm::set_allowed_dm(),
 | 
			
		||||
@@ -127,7 +127,7 @@ async fn _main(tx: Sender<()>) -> Result<(), Box<dyn StdError + Send + Sync>> {
 | 
			
		||||
                }],
 | 
			
		||||
                ..settings::settings()
 | 
			
		||||
            },
 | 
			
		||||
            webhook(),
 | 
			
		||||
            webhook::command(),
 | 
			
		||||
            poise::Command {
 | 
			
		||||
                subcommands: vec![
 | 
			
		||||
                    command_macro::delete_macro::delete_macro(),
 | 
			
		||||
@@ -138,9 +138,9 @@ async fn _main(tx: Sender<()>) -> Result<(), Box<dyn StdError + Send + Sync>> {
 | 
			
		||||
                ],
 | 
			
		||||
                ..command_macro::command_macro()
 | 
			
		||||
            },
 | 
			
		||||
            pause::pause(),
 | 
			
		||||
            offset::offset(),
 | 
			
		||||
            nudge::nudge(),
 | 
			
		||||
            pause::command(),
 | 
			
		||||
            offset::command(),
 | 
			
		||||
            nudge::command(),
 | 
			
		||||
            look::look(),
 | 
			
		||||
            delete::delete(),
 | 
			
		||||
            poise::Command {
 | 
			
		||||
@@ -152,7 +152,7 @@ async fn _main(tx: Sender<()>) -> Result<(), Box<dyn StdError + Send + Sync>> {
 | 
			
		||||
                ..timer::timer()
 | 
			
		||||
            },
 | 
			
		||||
            multiline::multiline(),
 | 
			
		||||
            remind::remind(),
 | 
			
		||||
            remind::command(),
 | 
			
		||||
            poise::Command {
 | 
			
		||||
                subcommands: vec![
 | 
			
		||||
                    poise::Command {
 | 
			
		||||
@@ -197,12 +197,14 @@ async fn _main(tx: Sender<()>) -> Result<(), Box<dyn StdError + Send + Sync>> {
 | 
			
		||||
    sqlx::migrate!().run(&database).await?;
 | 
			
		||||
 | 
			
		||||
    let popular_timezones = sqlx::query!(
 | 
			
		||||
        "SELECT IFNULL(timezone, 'UTC') AS timezone
 | 
			
		||||
        "
 | 
			
		||||
        SELECT IFNULL(timezone, 'UTC') AS timezone
 | 
			
		||||
        FROM users
 | 
			
		||||
        WHERE timezone IS NOT NULL
 | 
			
		||||
        GROUP BY timezone
 | 
			
		||||
        ORDER BY COUNT(timezone) DESC
 | 
			
		||||
        LIMIT 21"
 | 
			
		||||
        LIMIT 21
 | 
			
		||||
        "
 | 
			
		||||
    )
 | 
			
		||||
    .fetch_all(&database)
 | 
			
		||||
    .await
 | 
			
		||||
 
 | 
			
		||||
@@ -1,84 +1,27 @@
 | 
			
		||||
use chrono_tz::Tz;
 | 
			
		||||
use poise::serenity_prelude::{model::id::GuildId, ResolvedValue};
 | 
			
		||||
use poise::serenity_prelude::model::id::GuildId;
 | 
			
		||||
use serde::{Deserialize, Serialize};
 | 
			
		||||
use serde_json::Value;
 | 
			
		||||
 | 
			
		||||
use crate::{
 | 
			
		||||
    commands::remind::RemindOptions, models::reminder::create_reminder, ApplicationContext,
 | 
			
		||||
    Context, Error,
 | 
			
		||||
};
 | 
			
		||||
use crate::{commands::remind, utils::Extract, ApplicationContext, Context, Error};
 | 
			
		||||
 | 
			
		||||
#[derive(Serialize, Deserialize)]
 | 
			
		||||
#[serde(tag = "command_name")]
 | 
			
		||||
pub enum RecordedCommand {
 | 
			
		||||
    Remind(RemindOptions),
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
macro_rules! extract_arg {
 | 
			
		||||
    ($ctx:ident, $name:literal, String) => {
 | 
			
		||||
        $ctx.args.iter().find(|opt| opt.name == $name).map(|opt| &opt.value).map_or_else(
 | 
			
		||||
            || String::new(),
 | 
			
		||||
            |v| match v {
 | 
			
		||||
                ResolvedValue::String(s) => s.to_string(),
 | 
			
		||||
                _ => String::new(),
 | 
			
		||||
            },
 | 
			
		||||
        )
 | 
			
		||||
    };
 | 
			
		||||
    ($ctx:ident, $name:literal, Option<String>) => {
 | 
			
		||||
        $ctx.args.iter().find(|opt| opt.name == $name).map(|opt| &opt.value).map(|v| match v {
 | 
			
		||||
            ResolvedValue::String(s) => s.to_string(),
 | 
			
		||||
            _ => String::new(),
 | 
			
		||||
        })
 | 
			
		||||
    };
 | 
			
		||||
    ($ctx:ident, $name:literal, bool) => {
 | 
			
		||||
        $ctx.args.iter().find(|opt| opt.name == $name).map(|opt| &opt.value).map(|v| match v {
 | 
			
		||||
            ResolvedValue::Boolean(b) => b.to_owned(),
 | 
			
		||||
            _ => false,
 | 
			
		||||
        })
 | 
			
		||||
    };
 | 
			
		||||
    ($ctx:ident, $name:literal, Option<Tz>) => {
 | 
			
		||||
        $ctx.args
 | 
			
		||||
            .iter()
 | 
			
		||||
            .find(|opt| opt.name == $name)
 | 
			
		||||
            .map(|opt| &opt.value)
 | 
			
		||||
            .map(|v| match v {
 | 
			
		||||
                ResolvedValue::String(s) => s.parse::<Tz>().ok(),
 | 
			
		||||
                _ => None,
 | 
			
		||||
            })
 | 
			
		||||
            .flatten()
 | 
			
		||||
    };
 | 
			
		||||
    Remind(remind::Options),
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl RecordedCommand {
 | 
			
		||||
    pub fn from_context(ctx: ApplicationContext) -> Option<Self> {
 | 
			
		||||
        match ctx.command().identifying_name.as_str() {
 | 
			
		||||
            "remind" => Some(Self::Remind(RemindOptions {
 | 
			
		||||
                time: extract_arg!(ctx, "time", String),
 | 
			
		||||
                content: extract_arg!(ctx, "content", String),
 | 
			
		||||
                channels: extract_arg!(ctx, "channels", Option<String>),
 | 
			
		||||
                interval: extract_arg!(ctx, "interval", Option<String>),
 | 
			
		||||
                expires: extract_arg!(ctx, "expires", Option<String>),
 | 
			
		||||
                tts: extract_arg!(ctx, "tts", bool),
 | 
			
		||||
                timezone: extract_arg!(ctx, "timezone", Option<Tz>),
 | 
			
		||||
            })),
 | 
			
		||||
            "remind" => Some(Self::Remind(remind::Options::extract(ctx))),
 | 
			
		||||
            _ => None,
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pub async fn execute(self, ctx: ApplicationContext<'_>) -> Result<(), Error> {
 | 
			
		||||
        match self {
 | 
			
		||||
            RecordedCommand::Remind(command_options) => {
 | 
			
		||||
                create_reminder(
 | 
			
		||||
                    Context::Application(ctx),
 | 
			
		||||
                    command_options.time,
 | 
			
		||||
                    command_options.content,
 | 
			
		||||
                    command_options.channels,
 | 
			
		||||
                    command_options.interval,
 | 
			
		||||
                    command_options.expires,
 | 
			
		||||
                    command_options.tts,
 | 
			
		||||
                    command_options.timezone,
 | 
			
		||||
                )
 | 
			
		||||
                .await
 | 
			
		||||
            RecordedCommand::Remind(options) => {
 | 
			
		||||
                remind::remind(Context::Application(ctx), options).await
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 
 | 
			
		||||
@@ -69,7 +69,9 @@ pub struct ReminderBuilder {
 | 
			
		||||
impl ReminderBuilder {
 | 
			
		||||
    pub async fn build(self) -> Result<Reminder, ReminderError> {
 | 
			
		||||
        let queried_time = sqlx::query!(
 | 
			
		||||
            "SELECT DATE_ADD(?, INTERVAL (SELECT nudge FROM channels WHERE id = ?) SECOND) AS `utc_time`",
 | 
			
		||||
            "
 | 
			
		||||
            SELECT DATE_ADD(?, INTERVAL (SELECT nudge FROM channels WHERE id = ?) SECOND) AS `utc_time`
 | 
			
		||||
            ",
 | 
			
		||||
            self.utc_time,
 | 
			
		||||
            self.channel,
 | 
			
		||||
        )
 | 
			
		||||
@@ -84,7 +86,7 @@ impl ReminderBuilder {
 | 
			
		||||
                } else {
 | 
			
		||||
                    sqlx::query!(
 | 
			
		||||
                        "
 | 
			
		||||
INSERT INTO reminders (
 | 
			
		||||
                        INSERT INTO reminders (
 | 
			
		||||
                            `uid`,
 | 
			
		||||
                            `channel_id`,
 | 
			
		||||
                            `utc_time`,
 | 
			
		||||
@@ -98,21 +100,9 @@ INSERT INTO reminders (
 | 
			
		||||
                            `attachment_name`,
 | 
			
		||||
                            `attachment`,
 | 
			
		||||
                            `set_by`
 | 
			
		||||
) VALUES (
 | 
			
		||||
    ?,
 | 
			
		||||
    ?,
 | 
			
		||||
    ?,
 | 
			
		||||
    ?,
 | 
			
		||||
    ?,
 | 
			
		||||
    ?,
 | 
			
		||||
    ?,
 | 
			
		||||
    ?,
 | 
			
		||||
    ?,
 | 
			
		||||
    ?,
 | 
			
		||||
    ?,
 | 
			
		||||
    ?,
 | 
			
		||||
    ?
 | 
			
		||||
)
 | 
			
		||||
                        ) VALUES (
 | 
			
		||||
                            ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?
 | 
			
		||||
                        )
 | 
			
		||||
                        ",
 | 
			
		||||
                        self.uid,
 | 
			
		||||
                        self.channel,
 | 
			
		||||
 
 | 
			
		||||
@@ -9,7 +9,7 @@ use poise::{
 | 
			
		||||
 | 
			
		||||
use crate::{
 | 
			
		||||
    consts::{CNC_GUILD, SUBSCRIPTION_ROLES},
 | 
			
		||||
    Context,
 | 
			
		||||
    ApplicationContext, Context,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
pub async fn check_subscription(cache_http: impl CacheHttp, user_id: impl Into<UserId>) -> bool {
 | 
			
		||||
@@ -64,3 +64,7 @@ pub fn footer(ctx: Context<'_>) -> CreateEmbedFooter {
 | 
			
		||||
        shard_count,
 | 
			
		||||
    ))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
pub trait Extract {
 | 
			
		||||
    fn extract(ctx: ApplicationContext) -> Self;
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user