Add option types for top-level commands

This commit is contained in:
jude
2024-02-18 11:04:43 +00:00
parent c1305cfb36
commit 5e39e16060
22 changed files with 395 additions and 268 deletions

View File

@ -1,11 +1,13 @@
use chrono::Utc;
use poise::CreateReply;
use serde::{Deserialize, Serialize};
use crate::{models::CtxData, Context, Error};
use crate::{models::CtxData, utils::Extract, Context, Error};
/// View the current time in your selected timezone
#[poise::command(slash_command)]
pub async fn clock(ctx: Context<'_>) -> Result<(), Error> {
#[derive(Serialize, Deserialize, Extract)]
pub struct Options;
pub async fn clock(ctx: Context<'_>, _options: Options) -> Result<(), Error> {
ctx.defer_ephemeral().await?;
let tz = ctx.timezone().await;
@ -20,3 +22,9 @@ pub async fn clock(ctx: Context<'_>) -> Result<(), Error> {
Ok(())
}
/// View the current time in your selected timezone
#[poise::command(slash_command, rename = "clock")]
pub async fn command(ctx: Context<'_>) -> Result<(), Error> {
clock(ctx, Options {}).await
}

View File

@ -1,10 +1,16 @@
use poise::{serenity_prelude::CreateEmbed, CreateReply};
use serde::{Deserialize, Serialize};
use crate::{consts::THEME_COLOR, utils::footer, Context, Error};
use crate::{
consts::THEME_COLOR,
utils::{footer, Extract},
Context, Error,
};
/// Get the link to the online dashboard
#[poise::command(slash_command)]
pub async fn dashboard(ctx: Context<'_>) -> Result<(), Error> {
#[derive(Serialize, Deserialize, Extract)]
pub struct Options;
pub async fn dashboard(ctx: Context<'_>, _options: Options) -> Result<(), Error> {
let footer = footer(ctx);
ctx.send(
@ -20,3 +26,9 @@ pub async fn dashboard(ctx: Context<'_>) -> Result<(), Error> {
Ok(())
}
/// Get the link to the web dashboard
#[poise::command(slash_command, rename = "dashboard")]
pub async fn command(ctx: Context<'_>) -> Result<(), Error> {
dashboard(ctx, Options {}).await
}

View File

@ -6,6 +6,7 @@ use poise::{
},
CreateReply,
};
use serde::{Deserialize, Serialize};
use crate::{
component_models::{
@ -14,29 +15,10 @@ use crate::{
},
consts::{EMBED_DESCRIPTION_MAX_LENGTH, SELECT_MAX_ENTRIES, THEME_COLOR},
models::{reminder::Reminder, CtxData},
utils::Extract,
Context, Error,
};
/// Delete reminders
#[poise::command(
slash_command,
rename = "del",
identifying_name = "delete",
default_member_permissions = "MANAGE_GUILD"
)]
pub async fn delete(ctx: Context<'_>) -> Result<(), Error> {
let timezone = ctx.timezone().await;
let reminders =
Reminder::from_guild(&ctx, &ctx.data().database, ctx.guild_id(), ctx.author().id).await;
let resp = show_delete_page(&reminders, 0, timezone);
ctx.send(resp).await?;
Ok(())
}
pub fn max_delete_page(reminders: &[Reminder], timezone: &Tz) -> usize {
let mut rows = 0;
let mut char_count = 0;
@ -154,3 +136,25 @@ pub fn show_delete_page(reminders: &[Reminder], page: usize, timezone: Tz) -> Cr
.embed(embed)
.components(vec![pager.create_button_row(pages), CreateActionRow::SelectMenu(select_menu)])
}
#[derive(Serialize, Deserialize, Extract)]
pub struct Options;
pub async fn delete(ctx: Context<'_>, _options: Options) -> Result<(), Error> {
let timezone = ctx.timezone().await;
let reminders =
Reminder::from_guild(&ctx, &ctx.data().database, ctx.guild_id(), ctx.author().id).await;
let resp = show_delete_page(&reminders, 0, timezone);
ctx.send(resp).await?;
Ok(())
}
/// Delete reminders
#[poise::command(slash_command, rename = "del", default_member_permissions = "MANAGE_GUILD")]
pub async fn command(ctx: Context<'_>) -> Result<(), Error> {
delete(ctx, Options {}).await
}

View File

@ -1,10 +1,16 @@
use poise::{serenity_prelude::CreateEmbed, CreateReply};
use serde::{Deserialize, Serialize};
use crate::{consts::THEME_COLOR, utils::footer, Context, Error};
use crate::{
consts::THEME_COLOR,
utils::{footer, Extract},
Context, Error,
};
/// Details on supporting the bot and Patreon benefits
#[poise::command(slash_command)]
pub async fn donate(ctx: Context<'_>) -> Result<(), Error> {
#[derive(Serialize, Deserialize, Extract)]
pub struct Options;
pub async fn donate(ctx: Context<'_>, _options: Options) -> Result<(), Error> {
let footer = footer(ctx);
ctx.send(CreateReply::default().embed(CreateEmbed::new().title("Donate")
@ -32,3 +38,9 @@ Just $2 USD/month!
Ok(())
}
/// Details on supporting the bot and Patreon benefits
#[poise::command(slash_command, rename = "patreon")]
pub async fn command(ctx: Context<'_>) -> Result<(), Error> {
donate(ctx, Options {}).await
}

View File

@ -1,10 +1,16 @@
use poise::{serenity_prelude::CreateEmbed, CreateReply};
use serde::{Deserialize, Serialize};
use crate::{consts::THEME_COLOR, utils::footer, Context, Error};
use crate::{
consts::THEME_COLOR,
utils::{footer, Extract},
Context, Error,
};
/// Get an overview of bot commands
#[poise::command(slash_command)]
pub async fn help(ctx: Context<'_>) -> Result<(), Error> {
#[derive(Serialize, Deserialize, Extract)]
pub struct Options;
pub async fn help(ctx: Context<'_>, _options: Options) -> Result<(), Error> {
let footer = footer(ctx);
ctx.send(
@ -46,3 +52,9 @@ __Advanced Commands__
Ok(())
}
/// Get an overview of bot commands
#[poise::command(slash_command, rename = "help")]
pub async fn command(ctx: Context<'_>) -> Result<(), Error> {
help(ctx, Options {}).await
}

View File

@ -1,19 +1,24 @@
use poise::{serenity_prelude::CreateEmbed, CreateReply};
use serde::{Deserialize, Serialize};
use crate::{consts::THEME_COLOR, utils::footer, Context, Error};
use crate::{
consts::THEME_COLOR,
utils::{footer, Extract},
Context, Error,
};
/// Get information about the bot
#[poise::command(slash_command)]
pub async fn info(ctx: Context<'_>) -> Result<(), Error> {
#[derive(Serialize, Deserialize, Extract)]
pub struct Options;
pub async fn info(ctx: Context<'_>, _options: Options) -> Result<(), Error> {
let footer = footer(ctx);
let _ = ctx
.send(
CreateReply::default().ephemeral(true).embed(
CreateEmbed::new()
.title("Info")
.description(
"Help: `/help`
ctx.send(
CreateReply::default().ephemeral(true).embed(
CreateEmbed::new()
.title("Info")
.description(
"Help: `/help`
**Welcome to Reminder Bot!**
Developer: <@203532103185465344>
@ -22,12 +27,18 @@ Find me on https://discord.jellywx.com and on https://github.com/JellyWX :)
Invite the bot: https://invite.reminder-bot.com/
Use our dashboard: https://reminder-bot.com/",
)
.footer(footer)
.color(*THEME_COLOR),
),
)
.await;
)
.footer(footer)
.color(*THEME_COLOR),
),
)
.await?;
Ok(())
}
/// Get information about the bot
#[poise::command(slash_command, rename = "info")]
pub async fn command(ctx: Context<'_>) -> Result<(), Error> {
info(ctx, Options {}).await
}

View File

@ -1,5 +1,5 @@
use poise::{
serenity_prelude::{model::id::ChannelId, Channel, CreateEmbed, CreateEmbedFooter},
serenity_prelude::{model::id::ChannelId, CreateEmbed, CreateEmbedFooter, PartialChannel},
CreateReply,
};
use serde::{Deserialize, Serialize};
@ -9,17 +9,18 @@ use crate::{
component_models::pager::{LookPager, Pager},
consts::{EMBED_DESCRIPTION_MAX_LENGTH, THEME_COLOR},
models::{reminder::Reminder, CtxData},
utils::Extract,
Context, Error,
};
#[derive(Serialize_repr, Deserialize_repr, Copy, Clone, Debug)]
#[derive(Serialize_repr, Deserialize_repr, Copy, Clone)]
#[repr(u8)]
pub enum TimeDisplayType {
Absolute = 0,
Relative = 1,
}
#[derive(Serialize, Deserialize, Copy, Clone, Debug)]
#[derive(Serialize, Deserialize, Copy, Clone)]
pub struct LookFlags {
pub show_disabled: bool,
pub channel_id: Option<ChannelId>,
@ -32,24 +33,20 @@ impl Default for LookFlags {
}
}
/// View reminders on a specific channel
#[poise::command(
slash_command,
identifying_name = "look",
default_member_permissions = "MANAGE_GUILD"
)]
pub async fn look(
ctx: Context<'_>,
#[description = "Channel to view reminders on"] channel: Option<Channel>,
#[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> {
#[derive(Serialize, Deserialize, Extract)]
pub struct Options {
channel: Option<PartialChannel>,
disabled: Option<bool>,
relative: Option<bool>,
}
pub async fn look(ctx: Context<'_>, options: Options) -> Result<(), Error> {
let timezone = ctx.timezone().await;
let flags = LookFlags {
show_disabled: disabled.unwrap_or(true),
channel_id: channel.map(|c| c.id()),
time_display: relative.map_or(TimeDisplayType::Relative, |b| {
show_disabled: options.disabled.unwrap_or(true),
channel_id: options.channel.map(|c| c.id),
time_display: options.relative.map_or(TimeDisplayType::Relative, |b| {
if b {
TimeDisplayType::Relative
} else {
@ -117,3 +114,14 @@ pub async fn look(
Ok(())
}
/// View reminders on a specific channel
#[poise::command(slash_command, rename = "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
}

View File

@ -1,11 +1,13 @@
use chrono_tz::Tz;
use log::warn;
use poise::{CreateReply, Modal};
use serde::{Deserialize, Serialize};
use crate::{
commands::autocomplete::{time_hint_autocomplete, timezone_autocomplete},
models::reminder::create_reminder,
ApplicationContext, Context, Error,
utils::Extract,
Context, Error,
};
#[derive(poise::Modal)]
@ -18,14 +20,61 @@ struct ContentModal {
content: String,
}
#[derive(Serialize, Deserialize, Extract)]
pub struct Options {
time: String,
channels: Option<String>,
interval: Option<String>,
expires: Option<String>,
tts: Option<bool>,
timezone: Option<String>,
}
pub async fn multiline(ctx: Context<'_>, options: Options) -> Result<(), Error> {
match ctx {
Context::Application(app_ctx) => {
let tz = options.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,
data.content,
options.channels,
options.interval,
options.expires,
options.tts,
tz,
)
.await
}
None => {
warn!("Unexpected None encountered in /multiline");
Ok(ctx
.send(CreateReply::default().content("Unexpected error.").ephemeral(true))
.await
.map(|_| ())?)
}
}
}
_ => {
warn!("Shouldn't be here");
Ok(ctx
.send(CreateReply::default().content("Unexpected error.").ephemeral(true))
.await
.map(|_| ())?)
}
}
}
/// Create a reminder with multi-line content. Press "+4 more" for other options.
#[poise::command(
slash_command,
identifying_name = "multiline",
default_member_permissions = "MANAGE_GUILD"
)]
pub async fn multiline(
ctx: ApplicationContext<'_>,
#[poise::command(slash_command, rename = "multiline", default_member_permissions = "MANAGE_GUILD")]
pub async fn command(
ctx: Context<'_>,
#[description = "A description of the time to set the reminder for"]
#[autocomplete = "time_hint_autocomplete"]
time: String,
@ -40,30 +89,5 @@ pub async fn multiline(
#[autocomplete = "timezone_autocomplete"]
timezone: Option<String>,
) -> Result<(), Error> {
let tz = timezone.map(|t| t.parse::<Tz>().ok()).flatten();
let data_opt = ContentModal::execute(ctx).await?;
match data_opt {
Some(data) => {
create_reminder(
Context::Application(ctx),
time,
data.content,
channels,
interval,
expires,
tts,
tz,
)
.await
}
None => {
warn!("Unexpected None encountered in /multiline");
Ok(Context::Application(ctx)
.send(CreateReply::default().content("Unexpected error.").ephemeral(true))
.await
.map(|_| ())?)
}
}
multiline(ctx, Options { time, channels, interval, expires, tts, timezone }).await
}

View File

@ -1,15 +1,18 @@
use crate::{consts::MINUTE, models::CtxData, Context, Error};
use serde::{Deserialize, Serialize};
use crate::{consts::MINUTE, models::CtxData, utils::Extract, Context, Error};
#[derive(Serialize, Deserialize, Extract)]
pub struct Options {
minutes: Option<isize>,
seconds: Option<isize>,
minutes: Option<i64>,
seconds: Option<i64>,
}
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);
options.minutes.map_or(0, |m| m * MINUTE as i64) + options.seconds.map_or(0, |s| s);
if combined_time < i16::MIN as isize || combined_time > i16::MAX as isize {
if combined_time < i16::MIN as i64 || combined_time > i16::MAX as i64 {
ctx.say("Nudge times must be less than 500 minutes").await?;
} else {
let mut channel_data = ctx.channel_data().await.unwrap();
@ -24,15 +27,11 @@ pub async fn nudge(ctx: Context<'_>, options: Options) -> Result<(), 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"
)]
#[poise::command(slash_command, rename = "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>,
#[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
}

View File

@ -2,21 +2,22 @@ use serde::{Deserialize, Serialize};
use crate::{
consts::{HOUR, MINUTE},
utils::Extract,
Context, Error,
};
#[derive(Serialize, Deserialize, Default)]
#[derive(Serialize, Deserialize, Extract)]
pub struct Options {
hours: Option<isize>,
minutes: Option<isize>,
seconds: Option<isize>,
hours: Option<i64>,
minutes: Option<i64>,
seconds: Option<i64>,
}
async fn offset(ctx: Context<'_>, options: Options) -> Result<(), Error> {
ctx.defer().await?;
let combined_time = options.hours.map_or(0, |h| h * HOUR as isize)
+ options.minutes.map_or(0, |m| m * MINUTE as isize)
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);
if combined_time == 0 {
@ -69,16 +70,12 @@ async fn offset(ctx: Context<'_>, options: Options) -> Result<(), 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"
)]
#[poise::command(slash_command, rename = "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>,
#[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
}

View File

@ -1,22 +1,13 @@
use chrono::NaiveDateTime;
use serde::{Deserialize, Serialize};
use crate::{
models::CtxData, time_parser::natural_parser, utils::Extract, ApplicationContext, Context,
Error,
};
use crate::{models::CtxData, time_parser::natural_parser, utils::Extract, Context, 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;
@ -73,11 +64,7 @@ pub async fn pause(ctx: Context<'_>, options: Options) -> Result<(), 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"
)]
#[poise::command(slash_command, rename = "pause", default_member_permissions = "MANAGE_GUILD")]
pub async fn command(
ctx: Context<'_>,
#[description = "When to pause until"] until: Option<String>,

View File

@ -4,11 +4,11 @@ use serde::{Deserialize, Serialize};
use crate::{
commands::autocomplete::{time_hint_autocomplete, timezone_autocomplete},
models::reminder::create_reminder,
utils::{extract_arg, Extract},
ApplicationContext, Context, Error,
utils::Extract,
Context, Error,
};
#[derive(Serialize, Deserialize)]
#[derive(Serialize, Deserialize, Extract)]
pub struct Options {
time: String,
content: String,
@ -19,20 +19,6 @@ pub struct Options {
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();
@ -50,11 +36,7 @@ pub async fn remind(ctx: Context<'_>, options: Options) -> Result<(), Error> {
}
/// Create a reminder. Press "+4 more" for other options. Use "/multiline" for multiline content.
#[poise::command(
slash_command,
identifying_name = "remind",
default_member_permissions = "MANAGE_GUILD"
)]
#[poise::command(slash_command, rename = "remind", default_member_permissions = "MANAGE_GUILD")]
pub async fn command(
ctx: Context<'_>,
#[description = "The time (and optionally date) to set the reminder for"]

View File

@ -8,11 +8,11 @@ use poise::{
use serde::{Deserialize, Serialize};
use crate::{
commands::autocomplete::timezone_autocomplete, consts::THEME_COLOR, models::CtxData, Context,
Error,
commands::autocomplete::timezone_autocomplete, consts::THEME_COLOR, models::CtxData,
utils::Extract, Context, Error,
};
#[derive(Serialize, Deserialize)]
#[derive(Serialize, Deserialize, Extract)]
pub struct Options {
pub timezone: Option<String>,
}
@ -116,7 +116,7 @@ You may want to use one of the popular timezones below, otherwise click [here](h
}
/// Select your timezone
#[poise::command(slash_command, identifying_name = "timezone")]
#[poise::command(slash_command, rename = "timezone")]
pub async fn command(
ctx: Context<'_>,
#[description = "Timezone to use from this list: https://gist.github.com/JellyWX/913dfc8b63d45192ad6cb54c829324ee"]

View File

@ -2,17 +2,11 @@ use log::warn;
use poise::CreateReply;
use serde::{Deserialize, Serialize};
use crate::{models::CtxData, utils::Extract, ApplicationContext, Context, Error};
use crate::{models::CtxData, utils::Extract, Context, Error};
#[derive(Serialize, Deserialize)]
#[derive(Serialize, Deserialize, Extract)]
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) => {
@ -40,11 +34,7 @@ Do not share it!
}
/// View the webhook being used to send reminders to this channel
#[poise::command(
slash_command,
identifying_name = "webhook_url",
required_permissions = "ADMINISTRATOR"
)]
#[poise::command(slash_command, rename = "webhook", required_permissions = "ADMINISTRATOR")]
pub async fn command(ctx: Context<'_>) -> Result<(), Error> {
webhook(ctx, Options {}).await
}