Add option types for top-level commands
This commit is contained in:
parent
c1305cfb36
commit
5e39e16060
11
Cargo.lock
generated
11
Cargo.lock
generated
@ -773,6 +773,14 @@ version = "2.5.3"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0"
|
checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "extract_macro"
|
||||||
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"quote",
|
||||||
|
"syn 2.0.49",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "fastrand"
|
name = "fastrand"
|
||||||
version = "2.0.1"
|
version = "2.0.1"
|
||||||
@ -2301,7 +2309,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f"
|
checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "reminder-rs"
|
name = "reminder_rs"
|
||||||
version = "1.6.50"
|
version = "1.6.50"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"base64 0.21.5",
|
"base64 0.21.5",
|
||||||
@ -2309,6 +2317,7 @@ dependencies = [
|
|||||||
"chrono-tz",
|
"chrono-tz",
|
||||||
"dotenv",
|
"dotenv",
|
||||||
"env_logger",
|
"env_logger",
|
||||||
|
"extract_macro",
|
||||||
"lazy-regex",
|
"lazy-regex",
|
||||||
"lazy_static",
|
"lazy_static",
|
||||||
"levenshtein",
|
"levenshtein",
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "reminder-rs"
|
name = "reminder_rs"
|
||||||
version = "1.6.50"
|
version = "1.6.50"
|
||||||
authors = ["Jude Southworth <judesouthworth@pm.me>"]
|
authors = ["Jude Southworth <judesouthworth@pm.me>"]
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
@ -35,6 +35,9 @@ path = "postman"
|
|||||||
[dependencies.reminder_web]
|
[dependencies.reminder_web]
|
||||||
path = "web"
|
path = "web"
|
||||||
|
|
||||||
|
[dependencies.extract_macro]
|
||||||
|
path = "extract_macro"
|
||||||
|
|
||||||
[package.metadata.deb]
|
[package.metadata.deb]
|
||||||
depends = "$auto, python3-dateparser (>= 1.0.0)"
|
depends = "$auto, python3-dateparser (>= 1.0.0)"
|
||||||
suggests = "mysql-server-8.0, nginx"
|
suggests = "mysql-server-8.0, nginx"
|
||||||
|
52
extract_macro/src/lib.rs
Normal file
52
extract_macro/src/lib.rs
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
use proc_macro::TokenStream;
|
||||||
|
use syn::{spanned::Spanned, Data, Fields};
|
||||||
|
|
||||||
|
#[proc_macro_derive(Extract)]
|
||||||
|
pub fn extract(input: TokenStream) -> TokenStream {
|
||||||
|
let ast = syn::parse_macro_input!(input);
|
||||||
|
|
||||||
|
impl_extract(&ast)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn impl_extract(ast: &syn::DeriveInput) -> TokenStream {
|
||||||
|
let name = &ast.ident;
|
||||||
|
|
||||||
|
match &ast.data {
|
||||||
|
Data::Struct(st) => match &st.fields {
|
||||||
|
Fields::Named(fields) => {
|
||||||
|
let extracted = fields.named.iter().map(|field| {
|
||||||
|
let ident = &field.ident;
|
||||||
|
let ty = &field.ty;
|
||||||
|
|
||||||
|
quote::quote_spanned! {field.span()=>
|
||||||
|
#ident : crate::utils::extract_arg!(ctx, #ident, #ty)
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
TokenStream::from(quote::quote! {
|
||||||
|
impl Extract for #name {
|
||||||
|
fn extract(ctx: crate::ApplicationContext) -> Self {
|
||||||
|
Self {
|
||||||
|
#(#extracted,)*
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
Fields::Unit => TokenStream::from(quote::quote! {
|
||||||
|
impl Extract for #name {
|
||||||
|
fn extract(ctx: crate::ApplicationContext) -> Self {
|
||||||
|
Self {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
_ => {
|
||||||
|
panic!("Only named/unit structs can derive Extract");
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
_ => {
|
||||||
|
panic!("Only structs can derive Extract");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,11 +1,13 @@
|
|||||||
use chrono::Utc;
|
use chrono::Utc;
|
||||||
use poise::CreateReply;
|
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
|
#[derive(Serialize, Deserialize, Extract)]
|
||||||
#[poise::command(slash_command)]
|
pub struct Options;
|
||||||
pub async fn clock(ctx: Context<'_>) -> Result<(), Error> {
|
|
||||||
|
pub async fn clock(ctx: Context<'_>, _options: Options) -> Result<(), Error> {
|
||||||
ctx.defer_ephemeral().await?;
|
ctx.defer_ephemeral().await?;
|
||||||
|
|
||||||
let tz = ctx.timezone().await;
|
let tz = ctx.timezone().await;
|
||||||
@ -20,3 +22,9 @@ pub async fn clock(ctx: Context<'_>) -> Result<(), Error> {
|
|||||||
|
|
||||||
Ok(())
|
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
|
||||||
|
}
|
||||||
|
@ -1,10 +1,16 @@
|
|||||||
use poise::{serenity_prelude::CreateEmbed, CreateReply};
|
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
|
#[derive(Serialize, Deserialize, Extract)]
|
||||||
#[poise::command(slash_command)]
|
pub struct Options;
|
||||||
pub async fn dashboard(ctx: Context<'_>) -> Result<(), Error> {
|
|
||||||
|
pub async fn dashboard(ctx: Context<'_>, _options: Options) -> Result<(), Error> {
|
||||||
let footer = footer(ctx);
|
let footer = footer(ctx);
|
||||||
|
|
||||||
ctx.send(
|
ctx.send(
|
||||||
@ -20,3 +26,9 @@ pub async fn dashboard(ctx: Context<'_>) -> Result<(), Error> {
|
|||||||
|
|
||||||
Ok(())
|
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
|
||||||
|
}
|
||||||
|
@ -6,6 +6,7 @@ use poise::{
|
|||||||
},
|
},
|
||||||
CreateReply,
|
CreateReply,
|
||||||
};
|
};
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
component_models::{
|
component_models::{
|
||||||
@ -14,29 +15,10 @@ use crate::{
|
|||||||
},
|
},
|
||||||
consts::{EMBED_DESCRIPTION_MAX_LENGTH, SELECT_MAX_ENTRIES, THEME_COLOR},
|
consts::{EMBED_DESCRIPTION_MAX_LENGTH, SELECT_MAX_ENTRIES, THEME_COLOR},
|
||||||
models::{reminder::Reminder, CtxData},
|
models::{reminder::Reminder, CtxData},
|
||||||
|
utils::Extract,
|
||||||
Context, Error,
|
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 {
|
pub fn max_delete_page(reminders: &[Reminder], timezone: &Tz) -> usize {
|
||||||
let mut rows = 0;
|
let mut rows = 0;
|
||||||
let mut char_count = 0;
|
let mut char_count = 0;
|
||||||
@ -154,3 +136,25 @@ pub fn show_delete_page(reminders: &[Reminder], page: usize, timezone: Tz) -> Cr
|
|||||||
.embed(embed)
|
.embed(embed)
|
||||||
.components(vec![pager.create_button_row(pages), CreateActionRow::SelectMenu(select_menu)])
|
.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
|
||||||
|
}
|
||||||
|
@ -1,10 +1,16 @@
|
|||||||
use poise::{serenity_prelude::CreateEmbed, CreateReply};
|
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
|
#[derive(Serialize, Deserialize, Extract)]
|
||||||
#[poise::command(slash_command)]
|
pub struct Options;
|
||||||
pub async fn donate(ctx: Context<'_>) -> Result<(), Error> {
|
|
||||||
|
pub async fn donate(ctx: Context<'_>, _options: Options) -> Result<(), Error> {
|
||||||
let footer = footer(ctx);
|
let footer = footer(ctx);
|
||||||
|
|
||||||
ctx.send(CreateReply::default().embed(CreateEmbed::new().title("Donate")
|
ctx.send(CreateReply::default().embed(CreateEmbed::new().title("Donate")
|
||||||
@ -32,3 +38,9 @@ Just $2 USD/month!
|
|||||||
|
|
||||||
Ok(())
|
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
|
||||||
|
}
|
||||||
|
@ -1,10 +1,16 @@
|
|||||||
use poise::{serenity_prelude::CreateEmbed, CreateReply};
|
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
|
#[derive(Serialize, Deserialize, Extract)]
|
||||||
#[poise::command(slash_command)]
|
pub struct Options;
|
||||||
pub async fn help(ctx: Context<'_>) -> Result<(), Error> {
|
|
||||||
|
pub async fn help(ctx: Context<'_>, _options: Options) -> Result<(), Error> {
|
||||||
let footer = footer(ctx);
|
let footer = footer(ctx);
|
||||||
|
|
||||||
ctx.send(
|
ctx.send(
|
||||||
@ -46,3 +52,9 @@ __Advanced Commands__
|
|||||||
|
|
||||||
Ok(())
|
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
|
||||||
|
}
|
||||||
|
@ -1,19 +1,24 @@
|
|||||||
use poise::{serenity_prelude::CreateEmbed, CreateReply};
|
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
|
#[derive(Serialize, Deserialize, Extract)]
|
||||||
#[poise::command(slash_command)]
|
pub struct Options;
|
||||||
pub async fn info(ctx: Context<'_>) -> Result<(), Error> {
|
|
||||||
|
pub async fn info(ctx: Context<'_>, _options: Options) -> Result<(), Error> {
|
||||||
let footer = footer(ctx);
|
let footer = footer(ctx);
|
||||||
|
|
||||||
let _ = ctx
|
ctx.send(
|
||||||
.send(
|
CreateReply::default().ephemeral(true).embed(
|
||||||
CreateReply::default().ephemeral(true).embed(
|
CreateEmbed::new()
|
||||||
CreateEmbed::new()
|
.title("Info")
|
||||||
.title("Info")
|
.description(
|
||||||
.description(
|
"Help: `/help`
|
||||||
"Help: `/help`
|
|
||||||
|
|
||||||
**Welcome to Reminder Bot!**
|
**Welcome to Reminder Bot!**
|
||||||
Developer: <@203532103185465344>
|
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/
|
Invite the bot: https://invite.reminder-bot.com/
|
||||||
Use our dashboard: https://reminder-bot.com/",
|
Use our dashboard: https://reminder-bot.com/",
|
||||||
)
|
)
|
||||||
.footer(footer)
|
.footer(footer)
|
||||||
.color(*THEME_COLOR),
|
.color(*THEME_COLOR),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
.await;
|
.await?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Get information about the bot
|
||||||
|
#[poise::command(slash_command, rename = "info")]
|
||||||
|
pub async fn command(ctx: Context<'_>) -> Result<(), Error> {
|
||||||
|
info(ctx, Options {}).await
|
||||||
|
}
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
use poise::{
|
use poise::{
|
||||||
serenity_prelude::{model::id::ChannelId, Channel, CreateEmbed, CreateEmbedFooter},
|
serenity_prelude::{model::id::ChannelId, CreateEmbed, CreateEmbedFooter, PartialChannel},
|
||||||
CreateReply,
|
CreateReply,
|
||||||
};
|
};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
@ -9,17 +9,18 @@ use crate::{
|
|||||||
component_models::pager::{LookPager, Pager},
|
component_models::pager::{LookPager, Pager},
|
||||||
consts::{EMBED_DESCRIPTION_MAX_LENGTH, THEME_COLOR},
|
consts::{EMBED_DESCRIPTION_MAX_LENGTH, THEME_COLOR},
|
||||||
models::{reminder::Reminder, CtxData},
|
models::{reminder::Reminder, CtxData},
|
||||||
|
utils::Extract,
|
||||||
Context, Error,
|
Context, Error,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Serialize_repr, Deserialize_repr, Copy, Clone, Debug)]
|
#[derive(Serialize_repr, Deserialize_repr, Copy, Clone)]
|
||||||
#[repr(u8)]
|
#[repr(u8)]
|
||||||
pub enum TimeDisplayType {
|
pub enum TimeDisplayType {
|
||||||
Absolute = 0,
|
Absolute = 0,
|
||||||
Relative = 1,
|
Relative = 1,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Copy, Clone, Debug)]
|
#[derive(Serialize, Deserialize, Copy, Clone)]
|
||||||
pub struct LookFlags {
|
pub struct LookFlags {
|
||||||
pub show_disabled: bool,
|
pub show_disabled: bool,
|
||||||
pub channel_id: Option<ChannelId>,
|
pub channel_id: Option<ChannelId>,
|
||||||
@ -32,24 +33,20 @@ impl Default for LookFlags {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// View reminders on a specific channel
|
#[derive(Serialize, Deserialize, Extract)]
|
||||||
#[poise::command(
|
pub struct Options {
|
||||||
slash_command,
|
channel: Option<PartialChannel>,
|
||||||
identifying_name = "look",
|
disabled: Option<bool>,
|
||||||
default_member_permissions = "MANAGE_GUILD"
|
relative: Option<bool>,
|
||||||
)]
|
}
|
||||||
pub async fn look(
|
|
||||||
ctx: Context<'_>,
|
pub async fn look(ctx: Context<'_>, options: Options) -> Result<(), Error> {
|
||||||
#[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> {
|
|
||||||
let timezone = ctx.timezone().await;
|
let timezone = ctx.timezone().await;
|
||||||
|
|
||||||
let flags = LookFlags {
|
let flags = LookFlags {
|
||||||
show_disabled: disabled.unwrap_or(true),
|
show_disabled: options.disabled.unwrap_or(true),
|
||||||
channel_id: channel.map(|c| c.id()),
|
channel_id: options.channel.map(|c| c.id),
|
||||||
time_display: relative.map_or(TimeDisplayType::Relative, |b| {
|
time_display: options.relative.map_or(TimeDisplayType::Relative, |b| {
|
||||||
if b {
|
if b {
|
||||||
TimeDisplayType::Relative
|
TimeDisplayType::Relative
|
||||||
} else {
|
} else {
|
||||||
@ -117,3 +114,14 @@ pub async fn look(
|
|||||||
|
|
||||||
Ok(())
|
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
|
||||||
|
}
|
||||||
|
@ -1,11 +1,13 @@
|
|||||||
use chrono_tz::Tz;
|
use chrono_tz::Tz;
|
||||||
use log::warn;
|
use log::warn;
|
||||||
use poise::{CreateReply, Modal};
|
use poise::{CreateReply, Modal};
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
commands::autocomplete::{time_hint_autocomplete, timezone_autocomplete},
|
commands::autocomplete::{time_hint_autocomplete, timezone_autocomplete},
|
||||||
models::reminder::create_reminder,
|
models::reminder::create_reminder,
|
||||||
ApplicationContext, Context, Error,
|
utils::Extract,
|
||||||
|
Context, Error,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(poise::Modal)]
|
#[derive(poise::Modal)]
|
||||||
@ -18,14 +20,61 @@ struct ContentModal {
|
|||||||
content: String,
|
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.
|
/// Create a reminder with multi-line content. Press "+4 more" for other options.
|
||||||
#[poise::command(
|
#[poise::command(slash_command, rename = "multiline", default_member_permissions = "MANAGE_GUILD")]
|
||||||
slash_command,
|
pub async fn command(
|
||||||
identifying_name = "multiline",
|
ctx: Context<'_>,
|
||||||
default_member_permissions = "MANAGE_GUILD"
|
|
||||||
)]
|
|
||||||
pub async fn multiline(
|
|
||||||
ctx: ApplicationContext<'_>,
|
|
||||||
#[description = "A description of the time to set the reminder for"]
|
#[description = "A description of the time to set the reminder for"]
|
||||||
#[autocomplete = "time_hint_autocomplete"]
|
#[autocomplete = "time_hint_autocomplete"]
|
||||||
time: String,
|
time: String,
|
||||||
@ -40,30 +89,5 @@ pub async fn multiline(
|
|||||||
#[autocomplete = "timezone_autocomplete"]
|
#[autocomplete = "timezone_autocomplete"]
|
||||||
timezone: Option<String>,
|
timezone: Option<String>,
|
||||||
) -> Result<(), Error> {
|
) -> Result<(), Error> {
|
||||||
let tz = timezone.map(|t| t.parse::<Tz>().ok()).flatten();
|
multiline(ctx, Options { time, channels, interval, expires, tts, timezone }).await
|
||||||
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(|_| ())?)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -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 {
|
pub struct Options {
|
||||||
minutes: Option<isize>,
|
minutes: Option<i64>,
|
||||||
seconds: Option<isize>,
|
seconds: Option<i64>,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn nudge(ctx: Context<'_>, options: Options) -> Result<(), Error> {
|
pub async fn nudge(ctx: Context<'_>, options: Options) -> Result<(), Error> {
|
||||||
let combined_time =
|
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?;
|
ctx.say("Nudge times must be less than 500 minutes").await?;
|
||||||
} else {
|
} else {
|
||||||
let mut channel_data = ctx.channel_data().await.unwrap();
|
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`)
|
/// Nudge all future reminders on this channel by a certain amount (don't use for DST! See `/offset`)
|
||||||
#[poise::command(
|
#[poise::command(slash_command, rename = "nudge", default_member_permissions = "MANAGE_GUILD")]
|
||||||
slash_command,
|
|
||||||
identifying_name = "nudge",
|
|
||||||
default_member_permissions = "MANAGE_GUILD"
|
|
||||||
)]
|
|
||||||
pub async fn command(
|
pub async fn command(
|
||||||
ctx: Context<'_>,
|
ctx: Context<'_>,
|
||||||
#[description = "Number of minutes to nudge new reminders by"] minutes: 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<isize>,
|
#[description = "Number of seconds to nudge new reminders by"] seconds: Option<i64>,
|
||||||
) -> Result<(), Error> {
|
) -> Result<(), Error> {
|
||||||
nudge(ctx, Options { minutes, seconds }).await
|
nudge(ctx, Options { minutes, seconds }).await
|
||||||
}
|
}
|
||||||
|
@ -2,21 +2,22 @@ use serde::{Deserialize, Serialize};
|
|||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
consts::{HOUR, MINUTE},
|
consts::{HOUR, MINUTE},
|
||||||
|
utils::Extract,
|
||||||
Context, Error,
|
Context, Error,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Default)]
|
#[derive(Serialize, Deserialize, Extract)]
|
||||||
pub struct Options {
|
pub struct Options {
|
||||||
hours: Option<isize>,
|
hours: Option<i64>,
|
||||||
minutes: Option<isize>,
|
minutes: Option<i64>,
|
||||||
seconds: Option<isize>,
|
seconds: Option<i64>,
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn offset(ctx: Context<'_>, options: Options) -> Result<(), Error> {
|
async fn offset(ctx: Context<'_>, options: Options) -> Result<(), Error> {
|
||||||
ctx.defer().await?;
|
ctx.defer().await?;
|
||||||
|
|
||||||
let combined_time = options.hours.map_or(0, |h| h * HOUR as isize)
|
let combined_time = options.hours.map_or(0, |h| h * HOUR as i64)
|
||||||
+ options.minutes.map_or(0, |m| m * MINUTE as isize)
|
+ options.minutes.map_or(0, |m| m * MINUTE as i64)
|
||||||
+ options.seconds.map_or(0, |s| s);
|
+ options.seconds.map_or(0, |s| s);
|
||||||
|
|
||||||
if combined_time == 0 {
|
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
|
/// Move all reminders in the current server by a certain amount of time. Times get added together
|
||||||
#[poise::command(
|
#[poise::command(slash_command, rename = "offset", default_member_permissions = "MANAGE_GUILD")]
|
||||||
slash_command,
|
|
||||||
identifying_name = "offset",
|
|
||||||
default_member_permissions = "MANAGE_GUILD"
|
|
||||||
)]
|
|
||||||
pub async fn command(
|
pub async fn command(
|
||||||
ctx: Context<'_>,
|
ctx: Context<'_>,
|
||||||
#[description = "Number of hours to offset by"] hours: Option<isize>,
|
#[description = "Number of hours to offset by"] hours: Option<i64>,
|
||||||
#[description = "Number of minutes to offset by"] minutes: Option<isize>,
|
#[description = "Number of minutes to offset by"] minutes: Option<i64>,
|
||||||
#[description = "Number of seconds to offset by"] seconds: Option<isize>,
|
#[description = "Number of seconds to offset by"] seconds: Option<i64>,
|
||||||
) -> Result<(), Error> {
|
) -> Result<(), Error> {
|
||||||
offset(ctx, Options { hours, minutes, seconds }).await
|
offset(ctx, Options { hours, minutes, seconds }).await
|
||||||
}
|
}
|
||||||
|
@ -1,22 +1,13 @@
|
|||||||
use chrono::NaiveDateTime;
|
use chrono::NaiveDateTime;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
use crate::{
|
use crate::{models::CtxData, time_parser::natural_parser, utils::Extract, Context, Error};
|
||||||
models::CtxData, time_parser::natural_parser, utils::Extract, ApplicationContext, Context,
|
|
||||||
Error,
|
|
||||||
};
|
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Extract)]
|
#[derive(Serialize, Deserialize, Extract)]
|
||||||
pub struct Options {
|
pub struct Options {
|
||||||
until: Option<String>,
|
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> {
|
pub async fn pause(ctx: Context<'_>, options: Options) -> Result<(), Error> {
|
||||||
let timezone = ctx.timezone().await;
|
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
|
/// Pause all reminders on the current channel until a certain time or indefinitely
|
||||||
#[poise::command(
|
#[poise::command(slash_command, rename = "pause", default_member_permissions = "MANAGE_GUILD")]
|
||||||
slash_command,
|
|
||||||
identifying_name = "pause",
|
|
||||||
default_member_permissions = "MANAGE_GUILD"
|
|
||||||
)]
|
|
||||||
pub async fn command(
|
pub async fn command(
|
||||||
ctx: Context<'_>,
|
ctx: Context<'_>,
|
||||||
#[description = "When to pause until"] until: Option<String>,
|
#[description = "When to pause until"] until: Option<String>,
|
||||||
|
@ -4,11 +4,11 @@ use serde::{Deserialize, Serialize};
|
|||||||
use crate::{
|
use crate::{
|
||||||
commands::autocomplete::{time_hint_autocomplete, timezone_autocomplete},
|
commands::autocomplete::{time_hint_autocomplete, timezone_autocomplete},
|
||||||
models::reminder::create_reminder,
|
models::reminder::create_reminder,
|
||||||
utils::{extract_arg, Extract},
|
utils::Extract,
|
||||||
ApplicationContext, Context, Error,
|
Context, Error,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize)]
|
#[derive(Serialize, Deserialize, Extract)]
|
||||||
pub struct Options {
|
pub struct Options {
|
||||||
time: String,
|
time: String,
|
||||||
content: String,
|
content: String,
|
||||||
@ -19,20 +19,6 @@ pub struct Options {
|
|||||||
timezone: Option<String>,
|
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> {
|
pub async fn remind(ctx: Context<'_>, options: Options) -> Result<(), Error> {
|
||||||
let tz = options.timezone.map(|t| t.parse::<Tz>().ok()).flatten();
|
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.
|
/// Create a reminder. Press "+4 more" for other options. Use "/multiline" for multiline content.
|
||||||
#[poise::command(
|
#[poise::command(slash_command, rename = "remind", default_member_permissions = "MANAGE_GUILD")]
|
||||||
slash_command,
|
|
||||||
identifying_name = "remind",
|
|
||||||
default_member_permissions = "MANAGE_GUILD"
|
|
||||||
)]
|
|
||||||
pub async fn command(
|
pub async fn command(
|
||||||
ctx: Context<'_>,
|
ctx: Context<'_>,
|
||||||
#[description = "The time (and optionally date) to set the reminder for"]
|
#[description = "The time (and optionally date) to set the reminder for"]
|
||||||
|
@ -8,11 +8,11 @@ use poise::{
|
|||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
commands::autocomplete::timezone_autocomplete, consts::THEME_COLOR, models::CtxData, Context,
|
commands::autocomplete::timezone_autocomplete, consts::THEME_COLOR, models::CtxData,
|
||||||
Error,
|
utils::Extract, Context, Error,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize)]
|
#[derive(Serialize, Deserialize, Extract)]
|
||||||
pub struct Options {
|
pub struct Options {
|
||||||
pub timezone: Option<String>,
|
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
|
/// Select your timezone
|
||||||
#[poise::command(slash_command, identifying_name = "timezone")]
|
#[poise::command(slash_command, rename = "timezone")]
|
||||||
pub async fn command(
|
pub async fn command(
|
||||||
ctx: Context<'_>,
|
ctx: Context<'_>,
|
||||||
#[description = "Timezone to use from this list: https://gist.github.com/JellyWX/913dfc8b63d45192ad6cb54c829324ee"]
|
#[description = "Timezone to use from this list: https://gist.github.com/JellyWX/913dfc8b63d45192ad6cb54c829324ee"]
|
||||||
|
@ -2,17 +2,11 @@ use log::warn;
|
|||||||
use poise::CreateReply;
|
use poise::CreateReply;
|
||||||
use serde::{Deserialize, Serialize};
|
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;
|
pub struct Options;
|
||||||
|
|
||||||
impl Extract for Options {
|
|
||||||
fn extract(_ctx: ApplicationContext) -> Self {
|
|
||||||
Self {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn webhook(ctx: Context<'_>, _options: Options) -> Result<(), Error> {
|
pub async fn webhook(ctx: Context<'_>, _options: Options) -> Result<(), Error> {
|
||||||
match ctx.channel_data().await {
|
match ctx.channel_data().await {
|
||||||
Ok(data) => {
|
Ok(data) => {
|
||||||
@ -40,11 +34,7 @@ Do not share it!
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// View the webhook being used to send reminders to this channel
|
/// View the webhook being used to send reminders to this channel
|
||||||
#[poise::command(
|
#[poise::command(slash_command, rename = "webhook", required_permissions = "ADMINISTRATOR")]
|
||||||
slash_command,
|
|
||||||
identifying_name = "webhook_url",
|
|
||||||
required_permissions = "ADMINISTRATOR"
|
|
||||||
)]
|
|
||||||
pub async fn command(ctx: Context<'_>) -> Result<(), Error> {
|
pub async fn command(ctx: Context<'_>) -> Result<(), Error> {
|
||||||
webhook(ctx, Options {}).await
|
webhook(ctx, Options {}).await
|
||||||
}
|
}
|
||||||
|
@ -1,70 +0,0 @@
|
|||||||
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));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
22
src/main.rs
22
src/main.rs
@ -35,9 +35,9 @@ use tokio::sync::{broadcast, broadcast::Sender, RwLock};
|
|||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
commands::{
|
commands::{
|
||||||
allowed_dm, clock::clock, clock_context_menu::clock_context_menu, command_macro,
|
allowed_dm, clock, clock_context_menu::clock_context_menu, command_macro, dashboard,
|
||||||
dashboard::dashboard, delete, donate::donate, help::help, info::info, look, multiline,
|
delete, donate, help, info, look, multiline, nudge, offset, pause, remind, settings, timer,
|
||||||
nudge, offset, pause, remind, settings, timer, timezone, todo, webhook,
|
timezone, todo, webhook,
|
||||||
},
|
},
|
||||||
consts::THEME_COLOR,
|
consts::THEME_COLOR,
|
||||||
event_handlers::listener,
|
event_handlers::listener,
|
||||||
@ -103,12 +103,12 @@ async fn _main(tx: Sender<()>) -> Result<(), Box<dyn StdError + Send + Sync>> {
|
|||||||
|
|
||||||
let options = poise::FrameworkOptions {
|
let options = poise::FrameworkOptions {
|
||||||
commands: vec![
|
commands: vec![
|
||||||
help(),
|
help::command(),
|
||||||
info(),
|
info::command(),
|
||||||
donate(),
|
clock::command(),
|
||||||
clock(),
|
donate::command(),
|
||||||
clock_context_menu(),
|
clock_context_menu(),
|
||||||
dashboard(),
|
dashboard::command(),
|
||||||
timezone::command(),
|
timezone::command(),
|
||||||
poise::Command {
|
poise::Command {
|
||||||
subcommands: vec![
|
subcommands: vec![
|
||||||
@ -141,8 +141,8 @@ async fn _main(tx: Sender<()>) -> Result<(), Box<dyn StdError + Send + Sync>> {
|
|||||||
pause::command(),
|
pause::command(),
|
||||||
offset::command(),
|
offset::command(),
|
||||||
nudge::command(),
|
nudge::command(),
|
||||||
look::look(),
|
look::command(),
|
||||||
delete::delete(),
|
delete::command(),
|
||||||
poise::Command {
|
poise::Command {
|
||||||
subcommands: vec![
|
subcommands: vec![
|
||||||
timer::list_timer::list_timer(),
|
timer::list_timer::list_timer(),
|
||||||
@ -151,7 +151,7 @@ async fn _main(tx: Sender<()>) -> Result<(), Box<dyn StdError + Send + Sync>> {
|
|||||||
],
|
],
|
||||||
..timer::timer()
|
..timer::timer()
|
||||||
},
|
},
|
||||||
multiline::multiline(),
|
multiline::command(),
|
||||||
remind::command(),
|
remind::command(),
|
||||||
poise::Command {
|
poise::Command {
|
||||||
subcommands: vec![
|
subcommands: vec![
|
||||||
|
87
src/utils.rs
87
src/utils.rs
@ -68,3 +68,90 @@ pub fn footer(ctx: Context<'_>) -> CreateEmbedFooter {
|
|||||||
pub trait Extract {
|
pub trait Extract {
|
||||||
fn extract(ctx: ApplicationContext) -> Self;
|
fn extract(ctx: ApplicationContext) -> Self;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub use extract_macro::Extract;
|
||||||
|
|
||||||
|
macro_rules! extract_arg {
|
||||||
|
($ctx:ident, $name:ident, String) => {
|
||||||
|
$ctx.args
|
||||||
|
.iter()
|
||||||
|
.find(|opt| opt.name == stringify!($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:ident, Option<String>) => {
|
||||||
|
$ctx.args
|
||||||
|
.iter()
|
||||||
|
.find(|opt| opt.name == stringify!($name))
|
||||||
|
.map(|opt| &opt.value)
|
||||||
|
.map(|v| match v {
|
||||||
|
poise::serenity_prelude::ResolvedValue::String(s) => Some(s.to_string()),
|
||||||
|
_ => None,
|
||||||
|
})
|
||||||
|
.flatten()
|
||||||
|
};
|
||||||
|
($ctx:ident, $name:ident, bool) => {
|
||||||
|
$ctx.args.iter().find(|opt| opt.name == stringify!($name)).map(|opt| &opt.value).map_or(
|
||||||
|
false,
|
||||||
|
|v| match v {
|
||||||
|
poise::serenity_prelude::ResolvedValue::Boolean(b) => b.to_owned(),
|
||||||
|
_ => false,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
};
|
||||||
|
($ctx:ident, $name:ident, Option<bool>) => {
|
||||||
|
$ctx.args
|
||||||
|
.iter()
|
||||||
|
.find(|opt| opt.name == stringify!($name))
|
||||||
|
.map(|opt| &opt.value)
|
||||||
|
.map(|v| match v {
|
||||||
|
poise::serenity_prelude::ResolvedValue::Boolean(b) => Some(b.to_owned()),
|
||||||
|
_ => None,
|
||||||
|
})
|
||||||
|
.flatten()
|
||||||
|
};
|
||||||
|
($ctx:ident, $name:ident, Option<PartialChannel>) => {
|
||||||
|
$ctx.args
|
||||||
|
.iter()
|
||||||
|
.find(|opt| opt.name == stringify!($name))
|
||||||
|
.map(|opt| &opt.value)
|
||||||
|
.map(|v| match v {
|
||||||
|
poise::serenity_prelude::ResolvedValue::Channel(partial) => {
|
||||||
|
Some(partial.to_owned())
|
||||||
|
}
|
||||||
|
_ => None,
|
||||||
|
})
|
||||||
|
.flatten()
|
||||||
|
.cloned()
|
||||||
|
};
|
||||||
|
($ctx:ident, $name:ident, i64) => {
|
||||||
|
$ctx.args
|
||||||
|
.iter()
|
||||||
|
.find(|opt| opt.name == stringify!($name))
|
||||||
|
.map(|opt| &opt.value)
|
||||||
|
.map(|v| match v {
|
||||||
|
poise::serenity_prelude::ResolvedValue::Integer(int) => *int,
|
||||||
|
_ => 0,
|
||||||
|
})
|
||||||
|
.flatten()
|
||||||
|
};
|
||||||
|
($ctx:ident, $name:ident, Option<i64>) => {
|
||||||
|
$ctx.args
|
||||||
|
.iter()
|
||||||
|
.find(|opt| opt.name == stringify!($name))
|
||||||
|
.map(|opt| &opt.value)
|
||||||
|
.map(|v| match v {
|
||||||
|
poise::serenity_prelude::ResolvedValue::Integer(int) => Some(*int),
|
||||||
|
_ => None,
|
||||||
|
})
|
||||||
|
.flatten()
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) use extract_arg;
|
||||||
|
Loading…
Reference in New Issue
Block a user