Link all top-level commands with macro recording/replaying logic
This commit is contained in:
parent
5e39e16060
commit
76a286076b
13
Cargo.lock
generated
13
Cargo.lock
generated
@ -774,7 +774,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0"
|
checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "extract_macro"
|
name = "extract_derive"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"quote",
|
"quote",
|
||||||
@ -2235,6 +2235,14 @@ dependencies = [
|
|||||||
"getrandom",
|
"getrandom",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "recordable_derive"
|
||||||
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"quote",
|
||||||
|
"syn 2.0.49",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "redox_syscall"
|
name = "redox_syscall"
|
||||||
version = "0.4.1"
|
version = "0.4.1"
|
||||||
@ -2317,7 +2325,7 @@ dependencies = [
|
|||||||
"chrono-tz",
|
"chrono-tz",
|
||||||
"dotenv",
|
"dotenv",
|
||||||
"env_logger",
|
"env_logger",
|
||||||
"extract_macro",
|
"extract_derive",
|
||||||
"lazy-regex",
|
"lazy-regex",
|
||||||
"lazy_static",
|
"lazy_static",
|
||||||
"levenshtein",
|
"levenshtein",
|
||||||
@ -2326,6 +2334,7 @@ dependencies = [
|
|||||||
"poise",
|
"poise",
|
||||||
"postman",
|
"postman",
|
||||||
"rand",
|
"rand",
|
||||||
|
"recordable_derive",
|
||||||
"regex",
|
"regex",
|
||||||
"reminder_web",
|
"reminder_web",
|
||||||
"reqwest",
|
"reqwest",
|
||||||
|
@ -35,8 +35,11 @@ path = "postman"
|
|||||||
[dependencies.reminder_web]
|
[dependencies.reminder_web]
|
||||||
path = "web"
|
path = "web"
|
||||||
|
|
||||||
[dependencies.extract_macro]
|
[dependencies.extract_derive]
|
||||||
path = "extract_macro"
|
path = "extract_derive"
|
||||||
|
|
||||||
|
[dependencies.recordable_derive]
|
||||||
|
path = "recordable_derive"
|
||||||
|
|
||||||
[package.metadata.deb]
|
[package.metadata.deb]
|
||||||
depends = "$auto, python3-dateparser (>= 1.0.0)"
|
depends = "$auto, python3-dateparser (>= 1.0.0)"
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "extract_macro"
|
name = "extract_derive"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
|
|
@ -12,6 +12,7 @@ fn impl_extract(ast: &syn::DeriveInput) -> TokenStream {
|
|||||||
let name = &ast.ident;
|
let name = &ast.ident;
|
||||||
|
|
||||||
match &ast.data {
|
match &ast.data {
|
||||||
|
// Dispatch over struct: extract args directly from context
|
||||||
Data::Struct(st) => match &st.fields {
|
Data::Struct(st) => match &st.fields {
|
||||||
Fields::Named(fields) => {
|
Fields::Named(fields) => {
|
||||||
let extracted = fields.named.iter().map(|field| {
|
let extracted = fields.named.iter().map(|field| {
|
11
recordable_derive/Cargo.toml
Normal file
11
recordable_derive/Cargo.toml
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
[package]
|
||||||
|
name = "recordable_derive"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2021"
|
||||||
|
|
||||||
|
[lib]
|
||||||
|
proc-macro = true
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
quote = "1.0.35"
|
||||||
|
syn = { version = "2.0.49", features = ["full"] }
|
42
recordable_derive/src/lib.rs
Normal file
42
recordable_derive/src/lib.rs
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
use proc_macro::TokenStream;
|
||||||
|
use syn::{spanned::Spanned, Data};
|
||||||
|
|
||||||
|
/// Macro to allow Recordable to be implemented on an enum by dispatching over each variant.
|
||||||
|
#[proc_macro_derive(Recordable)]
|
||||||
|
pub fn extract(input: TokenStream) -> TokenStream {
|
||||||
|
let ast = syn::parse_macro_input!(input);
|
||||||
|
|
||||||
|
impl_recordable(&ast)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn impl_recordable(ast: &syn::DeriveInput) -> TokenStream {
|
||||||
|
let name = &ast.ident;
|
||||||
|
|
||||||
|
match &ast.data {
|
||||||
|
Data::Enum(en) => {
|
||||||
|
let extracted = en.variants.iter().map(|var| {
|
||||||
|
let ident = &var.ident;
|
||||||
|
|
||||||
|
quote::quote_spanned! {var.span()=>
|
||||||
|
Self::#ident (opt) => opt.run(ctx).await?
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
TokenStream::from(quote::quote! {
|
||||||
|
impl Recordable for #name {
|
||||||
|
async fn run(self, ctx: crate::Context<'_>) -> Result<(), crate::Error> {
|
||||||
|
match self {
|
||||||
|
#(#extracted,)*
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
_ => {
|
||||||
|
panic!("Only enums can derive Recordable");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -2,29 +2,35 @@ use chrono::Utc;
|
|||||||
use poise::CreateReply;
|
use poise::CreateReply;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
use crate::{models::CtxData, utils::Extract, Context, Error};
|
use crate::{
|
||||||
|
models::CtxData,
|
||||||
|
utils::{Extract, Recordable},
|
||||||
|
Context, Error,
|
||||||
|
};
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Extract)]
|
#[derive(Serialize, Deserialize, Extract)]
|
||||||
pub struct Options;
|
pub struct Options;
|
||||||
|
|
||||||
pub async fn clock(ctx: Context<'_>, _options: Options) -> Result<(), Error> {
|
impl Recordable for Options {
|
||||||
ctx.defer_ephemeral().await?;
|
async fn run(self, ctx: Context<'_>) -> Result<(), Error> {
|
||||||
|
ctx.defer_ephemeral().await?;
|
||||||
|
|
||||||
let tz = ctx.timezone().await;
|
let tz = ctx.timezone().await;
|
||||||
let now = Utc::now().with_timezone(&tz);
|
let now = Utc::now().with_timezone(&tz);
|
||||||
|
|
||||||
ctx.send(CreateReply::default().ephemeral(true).content(format!(
|
ctx.send(CreateReply::default().ephemeral(true).content(format!(
|
||||||
"Time in **{}**: `{}`",
|
"Time in **{}**: `{}`",
|
||||||
tz,
|
tz,
|
||||||
now.format("%H:%M")
|
now.format("%H:%M")
|
||||||
)))
|
)))
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// View the current time in your selected timezone
|
/// View the current time in your selected timezone
|
||||||
#[poise::command(slash_command, rename = "clock")]
|
#[poise::command(slash_command, rename = "clock", identifying_name = "clock")]
|
||||||
pub async fn command(ctx: Context<'_>) -> Result<(), Error> {
|
pub async fn command(ctx: Context<'_>) -> Result<(), Error> {
|
||||||
clock(ctx, Options {}).await
|
(Options {}).run(ctx).await
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,10 @@
|
|||||||
use poise::{serenity_prelude::CreateEmbed, CreateReply};
|
use poise::{serenity_prelude::CreateEmbed, CreateReply};
|
||||||
|
|
||||||
use super::super::autocomplete::macro_name_autocomplete;
|
use super::super::autocomplete::macro_name_autocomplete;
|
||||||
use crate::{models::command_macro::guild_command_macro, Context, Data, Error, THEME_COLOR};
|
use crate::{
|
||||||
|
models::command_macro::guild_command_macro, utils::Recordable, Context, Data, Error,
|
||||||
|
THEME_COLOR,
|
||||||
|
};
|
||||||
|
|
||||||
/// Run a recorded macro
|
/// Run a recorded macro
|
||||||
#[poise::command(
|
#[poise::command(
|
||||||
@ -32,7 +35,9 @@ pub async fn run_macro(
|
|||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
for command in command_macro.commands {
|
for command in command_macro.commands {
|
||||||
command.execute(poise::ApplicationContext { ..ctx }).await?;
|
command
|
||||||
|
.run(poise::Context::Application(poise::ApplicationContext { ..ctx }))
|
||||||
|
.await?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -3,32 +3,34 @@ use serde::{Deserialize, Serialize};
|
|||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
consts::THEME_COLOR,
|
consts::THEME_COLOR,
|
||||||
utils::{footer, Extract},
|
utils::{footer, Extract, Recordable},
|
||||||
Context, Error,
|
Context, Error,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Extract)]
|
#[derive(Serialize, Deserialize, Extract)]
|
||||||
pub struct Options;
|
pub struct Options;
|
||||||
|
|
||||||
pub async fn dashboard(ctx: Context<'_>, _options: Options) -> Result<(), Error> {
|
impl Recordable for Options {
|
||||||
let footer = footer(ctx);
|
async fn run(self, ctx: Context<'_>) -> Result<(), Error> {
|
||||||
|
let footer = footer(ctx);
|
||||||
|
|
||||||
ctx.send(
|
ctx.send(
|
||||||
CreateReply::default().ephemeral(true).embed(
|
CreateReply::default().ephemeral(true).embed(
|
||||||
CreateEmbed::new()
|
CreateEmbed::new()
|
||||||
.title("Dashboard")
|
.title("Dashboard")
|
||||||
.description("**https://beta.reminder-bot.com/dashboard**")
|
.description("**https://beta.reminder-bot.com/dashboard**")
|
||||||
.footer(footer)
|
.footer(footer)
|
||||||
.color(*THEME_COLOR),
|
.color(*THEME_COLOR),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get the link to the web dashboard
|
/// Get the link to the web dashboard
|
||||||
#[poise::command(slash_command, rename = "dashboard")]
|
#[poise::command(slash_command, rename = "dashboard", identifying_name = "dashboard")]
|
||||||
pub async fn command(ctx: Context<'_>) -> Result<(), Error> {
|
pub async fn command(ctx: Context<'_>) -> Result<(), Error> {
|
||||||
dashboard(ctx, Options {}).await
|
(Options {}).run(ctx).await
|
||||||
}
|
}
|
||||||
|
@ -15,7 +15,7 @@ use crate::{
|
|||||||
},
|
},
|
||||||
consts::{EMBED_DESCRIPTION_MAX_LENGTH, SELECT_MAX_ENTRIES, THEME_COLOR},
|
consts::{EMBED_DESCRIPTION_MAX_LENGTH, SELECT_MAX_ENTRIES, THEME_COLOR},
|
||||||
models::{reminder::Reminder, CtxData},
|
models::{reminder::Reminder, CtxData},
|
||||||
utils::Extract,
|
utils::{Extract, Recordable},
|
||||||
Context, Error,
|
Context, Error,
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -140,21 +140,28 @@ pub fn show_delete_page(reminders: &[Reminder], page: usize, timezone: Tz) -> Cr
|
|||||||
#[derive(Serialize, Deserialize, Extract)]
|
#[derive(Serialize, Deserialize, Extract)]
|
||||||
pub struct Options;
|
pub struct Options;
|
||||||
|
|
||||||
pub async fn delete(ctx: Context<'_>, _options: Options) -> Result<(), Error> {
|
impl Recordable for Options {
|
||||||
let timezone = ctx.timezone().await;
|
async fn run(self, ctx: Context<'_>) -> Result<(), Error> {
|
||||||
|
let timezone = ctx.timezone().await;
|
||||||
|
|
||||||
let reminders =
|
let reminders =
|
||||||
Reminder::from_guild(&ctx, &ctx.data().database, ctx.guild_id(), ctx.author().id).await;
|
Reminder::from_guild(&ctx, &ctx.data().database, ctx.guild_id(), ctx.author().id).await;
|
||||||
|
|
||||||
let resp = show_delete_page(&reminders, 0, timezone);
|
let resp = show_delete_page(&reminders, 0, timezone);
|
||||||
|
|
||||||
ctx.send(resp).await?;
|
ctx.send(resp).await?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Delete reminders
|
/// Delete reminders
|
||||||
#[poise::command(slash_command, rename = "del", default_member_permissions = "MANAGE_GUILD")]
|
#[poise::command(
|
||||||
|
slash_command,
|
||||||
|
rename = "delete",
|
||||||
|
identifying_name = "delete",
|
||||||
|
default_member_permissions = "MANAGE_GUILD"
|
||||||
|
)]
|
||||||
pub async fn command(ctx: Context<'_>) -> Result<(), Error> {
|
pub async fn command(ctx: Context<'_>) -> Result<(), Error> {
|
||||||
delete(ctx, Options {}).await
|
(Options {}).run(ctx).await
|
||||||
}
|
}
|
||||||
|
@ -3,17 +3,18 @@ use serde::{Deserialize, Serialize};
|
|||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
consts::THEME_COLOR,
|
consts::THEME_COLOR,
|
||||||
utils::{footer, Extract},
|
utils::{footer, Extract, Recordable},
|
||||||
Context, Error,
|
Context, Error,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Extract)]
|
#[derive(Serialize, Deserialize, Extract)]
|
||||||
pub struct Options;
|
pub struct Options;
|
||||||
|
|
||||||
pub async fn donate(ctx: Context<'_>, _options: Options) -> Result<(), Error> {
|
impl Recordable for Options {
|
||||||
let footer = footer(ctx);
|
async fn run(self, ctx: Context<'_>) -> Result<(), Error> {
|
||||||
|
let footer = footer(ctx);
|
||||||
|
|
||||||
ctx.send(CreateReply::default().embed(CreateEmbed::new().title("Donate")
|
ctx.send(CreateReply::default().embed(CreateEmbed::new().title("Donate")
|
||||||
.description("Thinking of adding a monthly contribution?
|
.description("Thinking of adding a monthly contribution?
|
||||||
Click below for my Patreon and official bot server :)
|
Click below for my Patreon and official bot server :)
|
||||||
|
|
||||||
@ -36,11 +37,12 @@ Just $2 USD/month!
|
|||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Details on supporting the bot and Patreon benefits
|
/// Details on supporting the bot and Patreon benefits
|
||||||
#[poise::command(slash_command, rename = "patreon")]
|
#[poise::command(slash_command, rename = "patreon", identifying_name = "patreon")]
|
||||||
pub async fn command(ctx: Context<'_>) -> Result<(), Error> {
|
pub async fn command(ctx: Context<'_>) -> Result<(), Error> {
|
||||||
donate(ctx, Options {}).await
|
(Options {}).run(ctx).await
|
||||||
}
|
}
|
||||||
|
@ -3,23 +3,24 @@ use serde::{Deserialize, Serialize};
|
|||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
consts::THEME_COLOR,
|
consts::THEME_COLOR,
|
||||||
utils::{footer, Extract},
|
utils::{footer, Extract, Recordable},
|
||||||
Context, Error,
|
Context, Error,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Extract)]
|
#[derive(Serialize, Deserialize, Extract)]
|
||||||
pub struct Options;
|
pub struct Options;
|
||||||
|
|
||||||
pub async fn help(ctx: Context<'_>, _options: Options) -> Result<(), Error> {
|
impl Recordable for Options {
|
||||||
let footer = footer(ctx);
|
async fn run(self, ctx: Context<'_>) -> Result<(), Error> {
|
||||||
|
let footer = footer(ctx);
|
||||||
|
|
||||||
ctx.send(
|
ctx.send(
|
||||||
CreateReply::default().ephemeral(true).embed(
|
CreateReply::default().ephemeral(true).embed(
|
||||||
CreateEmbed::new()
|
CreateEmbed::new()
|
||||||
.title("Help")
|
.title("Help")
|
||||||
.color(*THEME_COLOR)
|
.color(*THEME_COLOR)
|
||||||
.description(
|
.description(
|
||||||
"__Info Commands__
|
"__Info Commands__
|
||||||
`/help` `/info` `/donate` `/dashboard` `/clock`
|
`/help` `/info` `/donate` `/dashboard` `/clock`
|
||||||
*run these commands with no options*
|
*run these commands with no options*
|
||||||
|
|
||||||
@ -44,17 +45,18 @@ __Setup Commands__
|
|||||||
__Advanced Commands__
|
__Advanced Commands__
|
||||||
`/macro` - Record and replay command sequences
|
`/macro` - Record and replay command sequences
|
||||||
",
|
",
|
||||||
)
|
)
|
||||||
.footer(footer),
|
.footer(footer),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get an overview of bot commands
|
/// Get an overview of bot commands
|
||||||
#[poise::command(slash_command, rename = "help")]
|
#[poise::command(slash_command, rename = "help", identifying_name = "help")]
|
||||||
pub async fn command(ctx: Context<'_>) -> Result<(), Error> {
|
pub async fn command(ctx: Context<'_>) -> Result<(), Error> {
|
||||||
help(ctx, Options {}).await
|
(Options {}).run(ctx).await
|
||||||
}
|
}
|
||||||
|
@ -3,22 +3,23 @@ use serde::{Deserialize, Serialize};
|
|||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
consts::THEME_COLOR,
|
consts::THEME_COLOR,
|
||||||
utils::{footer, Extract},
|
utils::{footer, Extract, Recordable},
|
||||||
Context, Error,
|
Context, Error,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Extract)]
|
#[derive(Serialize, Deserialize, Extract)]
|
||||||
pub struct Options;
|
pub struct Options;
|
||||||
|
|
||||||
pub async fn info(ctx: Context<'_>, _options: Options) -> Result<(), Error> {
|
impl Recordable for Options {
|
||||||
let footer = footer(ctx);
|
async fn run(self, ctx: Context<'_>) -> Result<(), Error> {
|
||||||
|
let footer = footer(ctx);
|
||||||
|
|
||||||
ctx.send(
|
ctx.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>
|
||||||
@ -27,18 +28,19 @@ 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
|
/// Get information about the bot
|
||||||
#[poise::command(slash_command, rename = "info")]
|
#[poise::command(slash_command, rename = "info", identifying_name = "info")]
|
||||||
pub async fn command(ctx: Context<'_>) -> Result<(), Error> {
|
pub async fn command(ctx: Context<'_>) -> Result<(), Error> {
|
||||||
info(ctx, Options {}).await
|
(Options {}).run(ctx).await
|
||||||
}
|
}
|
||||||
|
@ -9,7 +9,7 @@ use crate::{
|
|||||||
component_models::pager::{LookPager, Pager},
|
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,
|
utils::{Extract, Recordable},
|
||||||
Context, Error,
|
Context, Error,
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -40,88 +40,95 @@ pub struct Options {
|
|||||||
relative: Option<bool>,
|
relative: Option<bool>,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn look(ctx: Context<'_>, options: Options) -> Result<(), Error> {
|
impl Recordable for Options {
|
||||||
let timezone = ctx.timezone().await;
|
async fn run(self, ctx: Context<'_>) -> Result<(), Error> {
|
||||||
|
let timezone = ctx.timezone().await;
|
||||||
|
|
||||||
let flags = LookFlags {
|
let flags = LookFlags {
|
||||||
show_disabled: options.disabled.unwrap_or(true),
|
show_disabled: self.disabled.unwrap_or(true),
|
||||||
channel_id: options.channel.map(|c| c.id),
|
channel_id: self.channel.map(|c| c.id),
|
||||||
time_display: options.relative.map_or(TimeDisplayType::Relative, |b| {
|
time_display: self.relative.map_or(TimeDisplayType::Relative, |b| {
|
||||||
if b {
|
if b {
|
||||||
TimeDisplayType::Relative
|
TimeDisplayType::Relative
|
||||||
|
} else {
|
||||||
|
TimeDisplayType::Absolute
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
|
||||||
|
let channel_id = if let Some(channel) = ctx.channel_id().to_channel_cached(&ctx.cache()) {
|
||||||
|
if Some(channel.guild_id) == ctx.guild_id() {
|
||||||
|
flags.channel_id.unwrap_or_else(|| ctx.channel_id())
|
||||||
} else {
|
} else {
|
||||||
TimeDisplayType::Absolute
|
ctx.channel_id()
|
||||||
}
|
}
|
||||||
}),
|
|
||||||
};
|
|
||||||
|
|
||||||
let channel_id = if let Some(channel) = ctx.channel_id().to_channel_cached(&ctx.cache()) {
|
|
||||||
if Some(channel.guild_id) == ctx.guild_id() {
|
|
||||||
flags.channel_id.unwrap_or_else(|| ctx.channel_id())
|
|
||||||
} else {
|
} else {
|
||||||
ctx.channel_id()
|
ctx.channel_id()
|
||||||
|
};
|
||||||
|
|
||||||
|
let channel_name =
|
||||||
|
channel_id.to_channel_cached(&ctx.cache()).map(|channel| channel.name.clone());
|
||||||
|
|
||||||
|
let reminders = Reminder::from_channel(&ctx.data().database, channel_id, &flags).await;
|
||||||
|
|
||||||
|
if reminders.is_empty() {
|
||||||
|
let _ = ctx.say("No reminders on specified channel").await;
|
||||||
|
} else {
|
||||||
|
let mut char_count = 0;
|
||||||
|
|
||||||
|
let display = reminders
|
||||||
|
.iter()
|
||||||
|
.map(|reminder| reminder.display(&flags, &timezone))
|
||||||
|
.take_while(|p| {
|
||||||
|
char_count += p.len();
|
||||||
|
|
||||||
|
char_count < EMBED_DESCRIPTION_MAX_LENGTH
|
||||||
|
})
|
||||||
|
.collect::<Vec<String>>()
|
||||||
|
.join("");
|
||||||
|
|
||||||
|
let pages = reminders
|
||||||
|
.iter()
|
||||||
|
.map(|reminder| reminder.display(&flags, &timezone))
|
||||||
|
.fold(0, |t, r| t + r.len())
|
||||||
|
.div_ceil(EMBED_DESCRIPTION_MAX_LENGTH);
|
||||||
|
|
||||||
|
let pager = LookPager::new(flags, timezone);
|
||||||
|
|
||||||
|
ctx.send(
|
||||||
|
CreateReply::default()
|
||||||
|
.ephemeral(true)
|
||||||
|
.embed(
|
||||||
|
CreateEmbed::new()
|
||||||
|
.title(format!(
|
||||||
|
"Reminders{}",
|
||||||
|
channel_name.map_or(String::new(), |n| format!(" on #{}", n))
|
||||||
|
))
|
||||||
|
.description(display)
|
||||||
|
.footer(CreateEmbedFooter::new(format!("Page {} of {}", 1, pages)))
|
||||||
|
.color(*THEME_COLOR),
|
||||||
|
)
|
||||||
|
.components(vec![pager.create_button_row(pages)]),
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
ctx.channel_id()
|
|
||||||
};
|
|
||||||
|
|
||||||
let channel_name =
|
Ok(())
|
||||||
channel_id.to_channel_cached(&ctx.cache()).map(|channel| channel.name.clone());
|
|
||||||
|
|
||||||
let reminders = Reminder::from_channel(&ctx.data().database, channel_id, &flags).await;
|
|
||||||
|
|
||||||
if reminders.is_empty() {
|
|
||||||
let _ = ctx.say("No reminders on specified channel").await;
|
|
||||||
} else {
|
|
||||||
let mut char_count = 0;
|
|
||||||
|
|
||||||
let display = reminders
|
|
||||||
.iter()
|
|
||||||
.map(|reminder| reminder.display(&flags, &timezone))
|
|
||||||
.take_while(|p| {
|
|
||||||
char_count += p.len();
|
|
||||||
|
|
||||||
char_count < EMBED_DESCRIPTION_MAX_LENGTH
|
|
||||||
})
|
|
||||||
.collect::<Vec<String>>()
|
|
||||||
.join("");
|
|
||||||
|
|
||||||
let pages = reminders
|
|
||||||
.iter()
|
|
||||||
.map(|reminder| reminder.display(&flags, &timezone))
|
|
||||||
.fold(0, |t, r| t + r.len())
|
|
||||||
.div_ceil(EMBED_DESCRIPTION_MAX_LENGTH);
|
|
||||||
|
|
||||||
let pager = LookPager::new(flags, timezone);
|
|
||||||
|
|
||||||
ctx.send(
|
|
||||||
CreateReply::default()
|
|
||||||
.ephemeral(true)
|
|
||||||
.embed(
|
|
||||||
CreateEmbed::new()
|
|
||||||
.title(format!(
|
|
||||||
"Reminders{}",
|
|
||||||
channel_name.map_or(String::new(), |n| format!(" on #{}", n))
|
|
||||||
))
|
|
||||||
.description(display)
|
|
||||||
.footer(CreateEmbedFooter::new(format!("Page {} of {}", 1, pages)))
|
|
||||||
.color(*THEME_COLOR),
|
|
||||||
)
|
|
||||||
.components(vec![pager.create_button_row(pages)]),
|
|
||||||
)
|
|
||||||
.await?;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// View reminders on a specific channel
|
/// View reminders on a specific channel
|
||||||
#[poise::command(slash_command, rename = "look", default_member_permissions = "MANAGE_GUILD")]
|
#[poise::command(
|
||||||
|
slash_command,
|
||||||
|
rename = "look",
|
||||||
|
identifying_name = "look",
|
||||||
|
default_member_permissions = "MANAGE_GUILD"
|
||||||
|
)]
|
||||||
pub async fn command(
|
pub async fn command(
|
||||||
ctx: Context<'_>,
|
ctx: Context<'_>,
|
||||||
#[description = "Channel to view reminders on"] channel: Option<PartialChannel>,
|
#[description = "Channel to view reminders on"] channel: Option<PartialChannel>,
|
||||||
#[description = "Whether to show disabled reminders or not"] disabled: Option<bool>,
|
#[description = "Whether to show disabled reminders or not"] disabled: Option<bool>,
|
||||||
#[description = "Whether to display times as relative or exact times"] relative: Option<bool>,
|
#[description = "Whether to display times as relative or exact times"] relative: Option<bool>,
|
||||||
) -> Result<(), Error> {
|
) -> Result<(), Error> {
|
||||||
look(ctx, Options { channel, disabled, relative }).await
|
(Options { channel, disabled, relative }).run(ctx).await
|
||||||
}
|
}
|
||||||
|
@ -6,7 +6,7 @@ use serde::{Deserialize, Serialize};
|
|||||||
use crate::{
|
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,
|
utils::{Extract, Recordable},
|
||||||
Context, Error,
|
Context, Error,
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -30,49 +30,58 @@ pub struct Options {
|
|||||||
timezone: Option<String>,
|
timezone: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn multiline(ctx: Context<'_>, options: Options) -> Result<(), Error> {
|
impl Recordable for Options {
|
||||||
match ctx {
|
async fn run(self, ctx: Context<'_>) -> Result<(), Error> {
|
||||||
Context::Application(app_ctx) => {
|
match ctx {
|
||||||
let tz = options.timezone.map(|t| t.parse::<Tz>().ok()).flatten();
|
Context::Application(app_ctx) => {
|
||||||
let data_opt = ContentModal::execute(app_ctx).await?;
|
let tz = self.timezone.map(|t| t.parse::<Tz>().ok()).flatten();
|
||||||
|
let data_opt = ContentModal::execute(app_ctx).await?;
|
||||||
|
|
||||||
match data_opt {
|
match data_opt {
|
||||||
Some(data) => {
|
Some(data) => {
|
||||||
create_reminder(
|
create_reminder(
|
||||||
ctx,
|
ctx,
|
||||||
options.time,
|
self.time,
|
||||||
data.content,
|
data.content,
|
||||||
options.channels,
|
self.channels,
|
||||||
options.interval,
|
self.interval,
|
||||||
options.expires,
|
self.expires,
|
||||||
options.tts,
|
self.tts,
|
||||||
tz,
|
tz,
|
||||||
)
|
)
|
||||||
.await
|
|
||||||
}
|
|
||||||
|
|
||||||
None => {
|
|
||||||
warn!("Unexpected None encountered in /multiline");
|
|
||||||
Ok(ctx
|
|
||||||
.send(CreateReply::default().content("Unexpected error.").ephemeral(true))
|
|
||||||
.await
|
.await
|
||||||
.map(|_| ())?)
|
}
|
||||||
|
|
||||||
|
None => {
|
||||||
|
warn!("Unexpected None encountered in /multiline");
|
||||||
|
Ok(ctx
|
||||||
|
.send(
|
||||||
|
CreateReply::default().content("Unexpected error.").ephemeral(true),
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.map(|_| ())?)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
_ => {
|
_ => {
|
||||||
warn!("Shouldn't be here");
|
warn!("Shouldn't be here");
|
||||||
Ok(ctx
|
Ok(ctx
|
||||||
.send(CreateReply::default().content("Unexpected error.").ephemeral(true))
|
.send(CreateReply::default().content("Unexpected error.").ephemeral(true))
|
||||||
.await
|
.await
|
||||||
.map(|_| ())?)
|
.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(slash_command, rename = "multiline", default_member_permissions = "MANAGE_GUILD")]
|
#[poise::command(
|
||||||
|
slash_command,
|
||||||
|
rename = "multiline",
|
||||||
|
identifying_name = "multiline",
|
||||||
|
default_member_permissions = "MANAGE_GUILD"
|
||||||
|
)]
|
||||||
pub async fn command(
|
pub async fn command(
|
||||||
ctx: Context<'_>,
|
ctx: Context<'_>,
|
||||||
#[description = "A description of the time to set the reminder for"]
|
#[description = "A description of the time to set the reminder for"]
|
||||||
@ -89,5 +98,5 @@ pub async fn command(
|
|||||||
#[autocomplete = "timezone_autocomplete"]
|
#[autocomplete = "timezone_autocomplete"]
|
||||||
timezone: Option<String>,
|
timezone: Option<String>,
|
||||||
) -> Result<(), Error> {
|
) -> Result<(), Error> {
|
||||||
multiline(ctx, Options { time, channels, interval, expires, tts, timezone }).await
|
(Options { time, channels, interval, expires, tts, timezone }).run(ctx).await
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,11 @@
|
|||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
use crate::{consts::MINUTE, models::CtxData, utils::Extract, Context, Error};
|
use crate::{
|
||||||
|
consts::MINUTE,
|
||||||
|
models::CtxData,
|
||||||
|
utils::{Extract, Recordable},
|
||||||
|
Context, Error,
|
||||||
|
};
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Extract)]
|
#[derive(Serialize, Deserialize, Extract)]
|
||||||
pub struct Options {
|
pub struct Options {
|
||||||
@ -8,30 +13,38 @@ pub struct Options {
|
|||||||
seconds: Option<i64>,
|
seconds: Option<i64>,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn nudge(ctx: Context<'_>, options: Options) -> Result<(), Error> {
|
impl Recordable for Options {
|
||||||
let combined_time =
|
async fn run(self, ctx: Context<'_>) -> Result<(), Error> {
|
||||||
options.minutes.map_or(0, |m| m * MINUTE as i64) + options.seconds.map_or(0, |s| s);
|
let combined_time =
|
||||||
|
self.minutes.map_or(0, |m| m * MINUTE as i64) + self.seconds.map_or(0, |s| s);
|
||||||
|
|
||||||
if combined_time < i16::MIN as i64 || combined_time > i16::MAX as i64 {
|
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();
|
||||||
|
|
||||||
channel_data.nudge = combined_time as i16;
|
channel_data.nudge = combined_time as i16;
|
||||||
channel_data.commit_changes(&ctx.data().database).await;
|
channel_data.commit_changes(&ctx.data().database).await;
|
||||||
|
|
||||||
ctx.say(format!("Future reminders will be nudged by {} seconds", combined_time)).await?;
|
ctx.say(format!("Future reminders will be nudged by {} seconds", combined_time))
|
||||||
|
.await?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 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(slash_command, rename = "nudge", default_member_permissions = "MANAGE_GUILD")]
|
#[poise::command(
|
||||||
|
slash_command,
|
||||||
|
rename = "nudge",
|
||||||
|
identifying_name = "nudge",
|
||||||
|
default_member_permissions = "MANAGE_GUILD"
|
||||||
|
)]
|
||||||
pub async fn command(
|
pub async fn command(
|
||||||
ctx: Context<'_>,
|
ctx: Context<'_>,
|
||||||
#[description = "Number of minutes to nudge new reminders by"] minutes: Option<i64>,
|
#[description = "Number of minutes to nudge new reminders by"] minutes: Option<i64>,
|
||||||
#[description = "Number of seconds to nudge new reminders by"] seconds: Option<i64>,
|
#[description = "Number of seconds to nudge new reminders by"] seconds: Option<i64>,
|
||||||
) -> Result<(), Error> {
|
) -> Result<(), Error> {
|
||||||
nudge(ctx, Options { minutes, seconds }).await
|
(Options { minutes, seconds }).run(ctx).await
|
||||||
}
|
}
|
||||||
|
@ -2,7 +2,7 @@ use serde::{Deserialize, Serialize};
|
|||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
consts::{HOUR, MINUTE},
|
consts::{HOUR, MINUTE},
|
||||||
utils::Extract,
|
utils::{Extract, Recordable},
|
||||||
Context, Error,
|
Context, Error,
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -13,69 +13,76 @@ pub struct Options {
|
|||||||
seconds: Option<i64>,
|
seconds: Option<i64>,
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn offset(ctx: Context<'_>, options: Options) -> Result<(), Error> {
|
impl Recordable for Options {
|
||||||
ctx.defer().await?;
|
async fn run(self, ctx: Context<'_>) -> Result<(), Error> {
|
||||||
|
ctx.defer().await?;
|
||||||
|
|
||||||
let combined_time = options.hours.map_or(0, |h| h * HOUR as i64)
|
let combined_time = self.hours.map_or(0, |h| h * HOUR as i64)
|
||||||
+ options.minutes.map_or(0, |m| m * MINUTE as i64)
|
+ self.minutes.map_or(0, |m| m * MINUTE as i64)
|
||||||
+ options.seconds.map_or(0, |s| s);
|
+ self.seconds.map_or(0, |s| s);
|
||||||
|
|
||||||
if combined_time == 0 {
|
if combined_time == 0 {
|
||||||
ctx.say("Please specify one of `hours`, `minutes` or `seconds`").await?;
|
ctx.say("Please specify one of `hours`, `minutes` or `seconds`").await?;
|
||||||
} else {
|
} else {
|
||||||
if let Some(channels) = ctx.guild().map(|guild| {
|
if let Some(channels) = ctx.guild().map(|guild| {
|
||||||
guild
|
guild
|
||||||
.channels
|
.channels
|
||||||
.iter()
|
.iter()
|
||||||
.filter(|(_, channel)| channel.is_text_based())
|
.filter(|(_, channel)| channel.is_text_based())
|
||||||
.map(|(id, _)| id.get().to_string())
|
.map(|(id, _)| id.get().to_string())
|
||||||
.collect::<Vec<String>>()
|
.collect::<Vec<String>>()
|
||||||
.join(",")
|
.join(",")
|
||||||
}) {
|
}) {
|
||||||
sqlx::query!(
|
sqlx::query!(
|
||||||
"
|
"
|
||||||
UPDATE reminders
|
UPDATE reminders
|
||||||
INNER JOIN `channels`
|
INNER JOIN `channels`
|
||||||
ON `channels`.id = reminders.channel_id
|
ON `channels`.id = reminders.channel_id
|
||||||
SET reminders.`utc_time` = DATE_ADD(reminders.`utc_time`, INTERVAL ? SECOND)
|
SET reminders.`utc_time` = DATE_ADD(reminders.`utc_time`, INTERVAL ? SECOND)
|
||||||
WHERE FIND_IN_SET(channels.`channel`, ?)
|
WHERE FIND_IN_SET(channels.`channel`, ?)
|
||||||
",
|
",
|
||||||
combined_time as i64,
|
combined_time as i64,
|
||||||
channels
|
channels
|
||||||
)
|
)
|
||||||
.execute(&ctx.data().database)
|
.execute(&ctx.data().database)
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
} else {
|
} else {
|
||||||
sqlx::query!(
|
sqlx::query!(
|
||||||
"
|
"
|
||||||
UPDATE reminders
|
UPDATE reminders
|
||||||
INNER JOIN `channels`
|
INNER JOIN `channels`
|
||||||
ON `channels`.id = reminders.channel_id
|
ON `channels`.id = reminders.channel_id
|
||||||
SET reminders.`utc_time` = reminders.`utc_time` + ?
|
SET reminders.`utc_time` = reminders.`utc_time` + ?
|
||||||
WHERE channels.`channel` = ?
|
WHERE channels.`channel` = ?
|
||||||
",
|
",
|
||||||
combined_time as i64,
|
combined_time as i64,
|
||||||
ctx.channel_id().get()
|
ctx.channel_id().get()
|
||||||
)
|
)
|
||||||
.execute(&ctx.data().database)
|
.execute(&ctx.data().database)
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.say(format!("All reminders offset by {} seconds", combined_time)).await?;
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx.say(format!("All reminders offset by {} seconds", combined_time)).await?;
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 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(slash_command, rename = "offset", default_member_permissions = "MANAGE_GUILD")]
|
#[poise::command(
|
||||||
|
slash_command,
|
||||||
|
rename = "offset",
|
||||||
|
identifying_name = "offset",
|
||||||
|
default_member_permissions = "MANAGE_GUILD"
|
||||||
|
)]
|
||||||
pub async fn command(
|
pub async fn command(
|
||||||
ctx: Context<'_>,
|
ctx: Context<'_>,
|
||||||
#[description = "Number of hours to offset by"] hours: Option<i64>,
|
#[description = "Number of hours to offset by"] hours: Option<i64>,
|
||||||
#[description = "Number of minutes to offset by"] minutes: Option<i64>,
|
#[description = "Number of minutes to offset by"] minutes: Option<i64>,
|
||||||
#[description = "Number of seconds to offset by"] seconds: Option<i64>,
|
#[description = "Number of seconds to offset by"] seconds: Option<i64>,
|
||||||
) -> Result<(), Error> {
|
) -> Result<(), Error> {
|
||||||
offset(ctx, Options { hours, minutes, seconds }).await
|
(Options { hours, minutes, seconds }).run(ctx).await
|
||||||
}
|
}
|
||||||
|
@ -1,73 +1,85 @@
|
|||||||
use chrono::NaiveDateTime;
|
use chrono::NaiveDateTime;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
use crate::{models::CtxData, time_parser::natural_parser, utils::Extract, Context, Error};
|
use crate::{
|
||||||
|
models::CtxData,
|
||||||
|
time_parser::natural_parser,
|
||||||
|
utils::{Extract, Recordable},
|
||||||
|
Context, Error,
|
||||||
|
};
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Extract)]
|
#[derive(Serialize, Deserialize, Extract)]
|
||||||
pub struct Options {
|
pub struct Options {
|
||||||
until: Option<String>,
|
until: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn pause(ctx: Context<'_>, options: Options) -> Result<(), Error> {
|
impl Recordable for Options {
|
||||||
let timezone = ctx.timezone().await;
|
async fn run(self, ctx: Context<'_>) -> Result<(), Error> {
|
||||||
|
let timezone = ctx.timezone().await;
|
||||||
|
|
||||||
let mut channel = ctx.channel_data().await.unwrap();
|
let mut channel = ctx.channel_data().await.unwrap();
|
||||||
|
|
||||||
match options.until {
|
match self.until {
|
||||||
Some(until) => {
|
Some(until) => {
|
||||||
let parsed = natural_parser(&until, &timezone.to_string()).await;
|
let parsed = natural_parser(&until, &timezone.to_string()).await;
|
||||||
|
|
||||||
if let Some(timestamp) = parsed {
|
if let Some(timestamp) = parsed {
|
||||||
match NaiveDateTime::from_timestamp_opt(timestamp, 0) {
|
match NaiveDateTime::from_timestamp_opt(timestamp, 0) {
|
||||||
Some(dt) => {
|
Some(dt) => {
|
||||||
channel.paused = true;
|
channel.paused = true;
|
||||||
channel.paused_until = Some(dt);
|
channel.paused_until = Some(dt);
|
||||||
|
|
||||||
channel.commit_changes(&ctx.data().database).await;
|
channel.commit_changes(&ctx.data().database).await;
|
||||||
|
|
||||||
ctx.say(format!(
|
ctx.say(format!(
|
||||||
"Reminders in this channel have been silenced until **<t:{}:D>**",
|
"Reminders in this channel have been silenced until **<t:{}:D>**",
|
||||||
timestamp
|
timestamp
|
||||||
))
|
))
|
||||||
.await?;
|
.await?;
|
||||||
}
|
}
|
||||||
|
|
||||||
None => {
|
None => {
|
||||||
ctx.say(
|
ctx.say(
|
||||||
"Time processed could not be interpreted as `DateTime`. Please write the time as clearly as possible",
|
"Time processed could not be interpreted as `DateTime`. Please write the time as clearly as possible",
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
ctx.say(
|
||||||
|
"Time could not be processed. Please write the time as clearly as possible",
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
channel.paused = !channel.paused;
|
||||||
|
channel.paused_until = None;
|
||||||
|
|
||||||
|
channel.commit_changes(&ctx.data().database).await;
|
||||||
|
|
||||||
|
if channel.paused {
|
||||||
|
ctx.say("Reminders in this channel have been silenced indefinitely").await?;
|
||||||
|
} else {
|
||||||
|
ctx.say("Reminders in this channel have been unsilenced").await?;
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
ctx.say(
|
|
||||||
"Time could not be processed. Please write the time as clearly as possible",
|
|
||||||
)
|
|
||||||
.await?;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_ => {
|
|
||||||
channel.paused = !channel.paused;
|
|
||||||
channel.paused_until = None;
|
|
||||||
|
|
||||||
channel.commit_changes(&ctx.data().database).await;
|
Ok(())
|
||||||
|
|
||||||
if channel.paused {
|
|
||||||
ctx.say("Reminders in this channel have been silenced indefinitely").await?;
|
|
||||||
} else {
|
|
||||||
ctx.say("Reminders in this channel have been unsilenced").await?;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 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(slash_command, rename = "pause", default_member_permissions = "MANAGE_GUILD")]
|
#[poise::command(
|
||||||
|
slash_command,
|
||||||
|
rename = "pause",
|
||||||
|
identifying_name = "pause",
|
||||||
|
default_member_permissions = "MANAGE_GUILD"
|
||||||
|
)]
|
||||||
pub async fn command(
|
pub async fn command(
|
||||||
ctx: Context<'_>,
|
ctx: Context<'_>,
|
||||||
#[description = "When to pause until"] until: Option<String>,
|
#[description = "When to pause until"] until: Option<String>,
|
||||||
) -> Result<(), Error> {
|
) -> Result<(), Error> {
|
||||||
pause(ctx, Options { until }).await
|
(Options { until }).run(ctx).await
|
||||||
}
|
}
|
||||||
|
@ -4,7 +4,7 @@ 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,
|
utils::{Extract, Recordable},
|
||||||
Context, Error,
|
Context, Error,
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -19,24 +19,31 @@ pub struct Options {
|
|||||||
timezone: Option<String>,
|
timezone: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn remind(ctx: Context<'_>, options: Options) -> Result<(), Error> {
|
impl Recordable for Options {
|
||||||
let tz = options.timezone.map(|t| t.parse::<Tz>().ok()).flatten();
|
async fn run(self, ctx: Context<'_>) -> Result<(), Error> {
|
||||||
|
let tz = self.timezone.map(|t| t.parse::<Tz>().ok()).flatten();
|
||||||
|
|
||||||
create_reminder(
|
create_reminder(
|
||||||
ctx,
|
ctx,
|
||||||
options.time,
|
self.time,
|
||||||
options.content,
|
self.content,
|
||||||
options.channels,
|
self.channels,
|
||||||
options.interval,
|
self.interval,
|
||||||
options.expires,
|
self.expires,
|
||||||
options.tts,
|
self.tts,
|
||||||
tz,
|
tz,
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 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(slash_command, rename = "remind", default_member_permissions = "MANAGE_GUILD")]
|
#[poise::command(
|
||||||
|
slash_command,
|
||||||
|
rename = "remind",
|
||||||
|
default_member_permissions = "MANAGE_GUILD",
|
||||||
|
identifying_name = "remind"
|
||||||
|
)]
|
||||||
pub async fn command(
|
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"]
|
||||||
@ -54,5 +61,5 @@ pub async fn command(
|
|||||||
#[autocomplete = "timezone_autocomplete"]
|
#[autocomplete = "timezone_autocomplete"]
|
||||||
timezone: Option<String>,
|
timezone: Option<String>,
|
||||||
) -> Result<(), Error> {
|
) -> Result<(), Error> {
|
||||||
remind(ctx, Options { time, content, channels, interval, expires, tts, timezone }).await
|
(Options { time, content, channels, interval, expires, tts, timezone }).run(ctx).await
|
||||||
}
|
}
|
||||||
|
@ -8,8 +8,11 @@ use poise::{
|
|||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
commands::autocomplete::timezone_autocomplete, consts::THEME_COLOR, models::CtxData,
|
commands::autocomplete::timezone_autocomplete,
|
||||||
utils::Extract, Context, Error,
|
consts::THEME_COLOR,
|
||||||
|
models::CtxData,
|
||||||
|
utils::{Extract, Recordable},
|
||||||
|
Context, Error,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Extract)]
|
#[derive(Serialize, Deserialize, Extract)]
|
||||||
@ -17,55 +20,56 @@ pub struct Options {
|
|||||||
pub timezone: Option<String>,
|
pub timezone: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn timezone_fn(ctx: Context<'_>, options: Options) -> Result<(), Error> {
|
impl Recordable for Options {
|
||||||
let mut user_data = ctx.author_data().await.unwrap();
|
async fn run(self, ctx: Context<'_>) -> Result<(), Error> {
|
||||||
|
let mut user_data = ctx.author_data().await.unwrap();
|
||||||
|
|
||||||
let footer_text = format!("Current timezone: {}", user_data.timezone);
|
let footer_text = format!("Current timezone: {}", user_data.timezone);
|
||||||
|
|
||||||
if let Some(timezone) = options.timezone {
|
if let Some(timezone) = self.timezone {
|
||||||
match timezone.parse::<Tz>() {
|
match timezone.parse::<Tz>() {
|
||||||
Ok(tz) => {
|
Ok(tz) => {
|
||||||
user_data.timezone = timezone.clone();
|
user_data.timezone = timezone.clone();
|
||||||
user_data.commit_changes(&ctx.data().database).await;
|
user_data.commit_changes(&ctx.data().database).await;
|
||||||
|
|
||||||
let now = Utc::now().with_timezone(&tz);
|
let now = Utc::now().with_timezone(&tz);
|
||||||
|
|
||||||
ctx.send(
|
ctx.send(
|
||||||
CreateReply::default().embed(
|
CreateReply::default().embed(
|
||||||
CreateEmbed::new()
|
CreateEmbed::new()
|
||||||
.title("Timezone Set")
|
.title("Timezone Set")
|
||||||
.description(format!(
|
.description(format!(
|
||||||
"Timezone has been set to **{}**. Your current time should be `{}`",
|
"Timezone has been set to **{}**. Your current time should be `{}`",
|
||||||
timezone,
|
timezone,
|
||||||
now.format("%H:%M")
|
now.format("%H:%M")
|
||||||
))
|
))
|
||||||
.color(*THEME_COLOR),
|
.color(*THEME_COLOR),
|
||||||
),
|
),
|
||||||
)
|
|
||||||
.await?;
|
|
||||||
}
|
|
||||||
|
|
||||||
Err(_) => {
|
|
||||||
let filtered_tz = TZ_VARIANTS
|
|
||||||
.iter()
|
|
||||||
.filter(|tz| {
|
|
||||||
timezone.contains(&tz.to_string())
|
|
||||||
|| tz.to_string().contains(&timezone)
|
|
||||||
|| levenshtein(&tz.to_string(), &timezone) < 4
|
|
||||||
})
|
|
||||||
.take(25)
|
|
||||||
.map(|t| t.to_owned())
|
|
||||||
.collect::<Vec<Tz>>();
|
|
||||||
|
|
||||||
let fields = filtered_tz.iter().map(|tz| {
|
|
||||||
(
|
|
||||||
tz.to_string(),
|
|
||||||
format!("🕗 `{}`", Utc::now().with_timezone(tz).format("%H:%M")),
|
|
||||||
true,
|
|
||||||
)
|
)
|
||||||
});
|
.await?;
|
||||||
|
}
|
||||||
|
|
||||||
ctx.send(
|
Err(_) => {
|
||||||
|
let filtered_tz = TZ_VARIANTS
|
||||||
|
.iter()
|
||||||
|
.filter(|tz| {
|
||||||
|
timezone.contains(&tz.to_string())
|
||||||
|
|| tz.to_string().contains(&timezone)
|
||||||
|
|| levenshtein(&tz.to_string(), &timezone) < 4
|
||||||
|
})
|
||||||
|
.take(25)
|
||||||
|
.map(|t| t.to_owned())
|
||||||
|
.collect::<Vec<Tz>>();
|
||||||
|
|
||||||
|
let fields = filtered_tz.iter().map(|tz| {
|
||||||
|
(
|
||||||
|
tz.to_string(),
|
||||||
|
format!("🕗 `{}`", Utc::now().with_timezone(tz).format("%H:%M")),
|
||||||
|
true,
|
||||||
|
)
|
||||||
|
});
|
||||||
|
|
||||||
|
ctx.send(
|
||||||
CreateReply::default().embed(
|
CreateReply::default().embed(
|
||||||
CreateEmbed::new()
|
CreateEmbed::new()
|
||||||
.title("Timezone Not Recognized")
|
.title("Timezone Not Recognized")
|
||||||
@ -83,14 +87,18 @@ pub async fn timezone_fn(ctx: Context<'_>, options: Options) -> Result<(), Error
|
|||||||
),
|
),
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
} else {
|
||||||
} else {
|
let popular_timezones_iter = ctx.data().popular_timezones.iter().map(|t| {
|
||||||
let popular_timezones_iter = ctx.data().popular_timezones.iter().map(|t| {
|
(
|
||||||
(t.to_string(), format!("🕗 `{}`", Utc::now().with_timezone(t).format("%H:%M")), true)
|
t.to_string(),
|
||||||
});
|
format!("🕗 `{}`", Utc::now().with_timezone(t).format("%H:%M")),
|
||||||
|
true,
|
||||||
|
)
|
||||||
|
});
|
||||||
|
|
||||||
ctx.send(
|
ctx.send(
|
||||||
CreateReply::default().embed(
|
CreateReply::default().embed(
|
||||||
CreateEmbed::new()
|
CreateEmbed::new()
|
||||||
.title("Timezone Usage")
|
.title("Timezone Usage")
|
||||||
@ -110,18 +118,19 @@ You may want to use one of the popular timezones below, otherwise click [here](h
|
|||||||
),
|
),
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Select your timezone
|
/// Select your timezone
|
||||||
#[poise::command(slash_command, rename = "timezone")]
|
#[poise::command(slash_command, rename = "timezone", identifying_name = "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"]
|
||||||
#[autocomplete = "timezone_autocomplete"]
|
#[autocomplete = "timezone_autocomplete"]
|
||||||
timezone: Option<String>,
|
timezone: Option<String>,
|
||||||
) -> Result<(), Error> {
|
) -> Result<(), Error> {
|
||||||
timezone_fn(ctx, Options { timezone }).await
|
(Options { timezone }).run(ctx).await
|
||||||
}
|
}
|
||||||
|
@ -2,39 +2,50 @@ use log::warn;
|
|||||||
use poise::CreateReply;
|
use poise::CreateReply;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
use crate::{models::CtxData, utils::Extract, Context, Error};
|
use crate::{
|
||||||
|
models::CtxData,
|
||||||
|
utils::{Extract, Recordable},
|
||||||
|
Context, Error,
|
||||||
|
};
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Extract)]
|
#[derive(Serialize, Deserialize, Extract)]
|
||||||
pub struct Options;
|
pub struct Options;
|
||||||
|
|
||||||
pub async fn webhook(ctx: Context<'_>, _options: Options) -> Result<(), Error> {
|
impl Recordable for Options {
|
||||||
match ctx.channel_data().await {
|
async fn run(self, ctx: Context<'_>) -> Result<(), Error> {
|
||||||
Ok(data) => {
|
match ctx.channel_data().await {
|
||||||
if let (Some(id), Some(token)) = (data.webhook_id, data.webhook_token) {
|
Ok(data) => {
|
||||||
ctx.send(CreateReply::default().ephemeral(true).content(format!(
|
if let (Some(id), Some(token)) = (data.webhook_id, data.webhook_token) {
|
||||||
"**Warning!**
|
ctx.send(CreateReply::default().ephemeral(true).content(format!(
|
||||||
|
"**Warning!**
|
||||||
This link can be used by users to anonymously send messages, with or without permissions.
|
This link can be used by users to anonymously send messages, with or without permissions.
|
||||||
Do not share it!
|
Do not share it!
|
||||||
|| https://discord.com/api/webhooks/{}/{} ||",
|
|| https://discord.com/api/webhooks/{}/{} ||",
|
||||||
id, token,
|
id, token,
|
||||||
)))
|
)))
|
||||||
.await?;
|
.await?;
|
||||||
} else {
|
} else {
|
||||||
|
ctx.say("No webhook configured on this channel.").await?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
warn!("Error fetching channel data: {:?}", e);
|
||||||
|
|
||||||
ctx.say("No webhook configured on this channel.").await?;
|
ctx.say("No webhook configured on this channel.").await?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Err(e) => {
|
|
||||||
warn!("Error fetching channel data: {:?}", e);
|
|
||||||
|
|
||||||
ctx.say("No webhook configured on this channel.").await?;
|
Ok(())
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// View the webhook being used to send reminders to this channel
|
/// View the webhook being used to send reminders to this channel
|
||||||
#[poise::command(slash_command, rename = "webhook", required_permissions = "ADMINISTRATOR")]
|
#[poise::command(
|
||||||
|
slash_command,
|
||||||
|
rename = "webhook",
|
||||||
|
identifying_name = "webhook",
|
||||||
|
required_permissions = "ADMINISTRATOR"
|
||||||
|
)]
|
||||||
pub async fn command(ctx: Context<'_>) -> Result<(), Error> {
|
pub async fn command(ctx: Context<'_>) -> Result<(), Error> {
|
||||||
webhook(ctx, Options {}).await
|
(Options {}).run(ctx).await
|
||||||
}
|
}
|
||||||
|
43
src/hooks.rs
43
src/hooks.rs
@ -13,18 +13,6 @@ async fn macro_check(ctx: Context<'_>) -> bool {
|
|||||||
let mut lock = ctx.data().recording_macros.write().await;
|
let mut lock = ctx.data().recording_macros.write().await;
|
||||||
|
|
||||||
if let Some(command_macro) = lock.get_mut(&(guild_id, ctx.author().id)) {
|
if let Some(command_macro) = lock.get_mut(&(guild_id, ctx.author().id)) {
|
||||||
if ctx.command().identifying_name != "remind" {
|
|
||||||
let _ = ctx
|
|
||||||
.send(
|
|
||||||
CreateReply::default()
|
|
||||||
.ephemeral(true)
|
|
||||||
.content("Macro recording only supports `/remind`. Please stop recording with `/macro finish` before using other commands.")
|
|
||||||
)
|
|
||||||
.await;
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if command_macro.commands.len() >= MACRO_MAX_COMMANDS {
|
if command_macro.commands.len() >= MACRO_MAX_COMMANDS {
|
||||||
let _ = ctx
|
let _ = ctx
|
||||||
.send(
|
.send(
|
||||||
@ -34,16 +22,29 @@ async fn macro_check(ctx: Context<'_>) -> bool {
|
|||||||
)
|
)
|
||||||
.await;
|
.await;
|
||||||
} else {
|
} else {
|
||||||
let recorded = RecordedCommand::from_context(app_ctx).unwrap();
|
match RecordedCommand::from_context(app_ctx) {
|
||||||
command_macro.commands.push(recorded);
|
Some(recorded) => {
|
||||||
|
command_macro.commands.push(recorded);
|
||||||
|
|
||||||
let _ = ctx
|
let _ = ctx
|
||||||
.send(
|
.send(
|
||||||
CreateReply::default()
|
CreateReply::default()
|
||||||
.ephemeral(true)
|
.ephemeral(true)
|
||||||
.content("Command recorded to macro"),
|
.content("Command recorded to macro"),
|
||||||
)
|
)
|
||||||
.await;
|
.await;
|
||||||
|
}
|
||||||
|
|
||||||
|
None => {
|
||||||
|
let _ = ctx
|
||||||
|
.send(
|
||||||
|
CreateReply::default().ephemeral(true).content(
|
||||||
|
"This command is not supported in macros yet.",
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
|
@ -2,29 +2,64 @@ use poise::serenity_prelude::model::id::GuildId;
|
|||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use serde_json::Value;
|
use serde_json::Value;
|
||||||
|
|
||||||
use crate::{commands::remind, utils::Extract, ApplicationContext, Context, Error};
|
use crate::{
|
||||||
|
utils::{Extract, Recordable},
|
||||||
|
ApplicationContext, Context,
|
||||||
|
};
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize)]
|
#[derive(Serialize, Deserialize, Recordable)]
|
||||||
#[serde(tag = "command_name")]
|
#[serde(tag = "command_name")]
|
||||||
pub enum RecordedCommand {
|
pub enum RecordedCommand {
|
||||||
Remind(remind::Options),
|
#[serde(rename = "clock")]
|
||||||
|
Clock(crate::commands::clock::Options),
|
||||||
|
#[serde(rename = "dashboard")]
|
||||||
|
Dashboard(crate::commands::dashboard::Options),
|
||||||
|
#[serde(rename = "delete")]
|
||||||
|
Delete(crate::commands::delete::Options),
|
||||||
|
#[serde(rename = "donate")]
|
||||||
|
Donate(crate::commands::donate::Options),
|
||||||
|
#[serde(rename = "help")]
|
||||||
|
Help(crate::commands::help::Options),
|
||||||
|
#[serde(rename = "info")]
|
||||||
|
Info(crate::commands::info::Options),
|
||||||
|
#[serde(rename = "look")]
|
||||||
|
Look(crate::commands::look::Options),
|
||||||
|
#[serde(rename = "multiline")]
|
||||||
|
Multiline(crate::commands::multiline::Options),
|
||||||
|
#[serde(rename = "nudge")]
|
||||||
|
Nudge(crate::commands::nudge::Options),
|
||||||
|
#[serde(rename = "offset")]
|
||||||
|
Offset(crate::commands::offset::Options),
|
||||||
|
#[serde(rename = "pause")]
|
||||||
|
Pause(crate::commands::pause::Options),
|
||||||
|
#[serde(rename = "remind")]
|
||||||
|
Remind(crate::commands::remind::Options),
|
||||||
|
#[serde(rename = "timezone")]
|
||||||
|
Timezone(crate::commands::timezone::Options),
|
||||||
|
#[serde(rename = "webhook")]
|
||||||
|
Webhook(crate::commands::webhook::Options),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl RecordedCommand {
|
impl RecordedCommand {
|
||||||
pub fn from_context(ctx: ApplicationContext) -> Option<Self> {
|
pub fn from_context(ctx: ApplicationContext) -> Option<Self> {
|
||||||
match ctx.command().identifying_name.as_str() {
|
match ctx.command().identifying_name.as_str() {
|
||||||
"remind" => Some(Self::Remind(remind::Options::extract(ctx))),
|
"clock" => Some(Self::Clock(crate::commands::clock::Options::extract(ctx))),
|
||||||
|
"dashboard" => Some(Self::Dashboard(crate::commands::dashboard::Options::extract(ctx))),
|
||||||
|
"delete" => Some(Self::Delete(crate::commands::delete::Options::extract(ctx))),
|
||||||
|
"donate" => Some(Self::Donate(crate::commands::donate::Options::extract(ctx))),
|
||||||
|
"help" => Some(Self::Help(crate::commands::help::Options::extract(ctx))),
|
||||||
|
"info" => Some(Self::Info(crate::commands::info::Options::extract(ctx))),
|
||||||
|
"look" => Some(Self::Look(crate::commands::look::Options::extract(ctx))),
|
||||||
|
"multiline" => Some(Self::Multiline(crate::commands::multiline::Options::extract(ctx))),
|
||||||
|
"nudge" => Some(Self::Nudge(crate::commands::nudge::Options::extract(ctx))),
|
||||||
|
"offset" => Some(Self::Offset(crate::commands::offset::Options::extract(ctx))),
|
||||||
|
"pause" => Some(Self::Pause(crate::commands::pause::Options::extract(ctx))),
|
||||||
|
"remind" => Some(Self::Remind(crate::commands::remind::Options::extract(ctx))),
|
||||||
|
"timezone" => Some(Self::Timezone(crate::commands::timezone::Options::extract(ctx))),
|
||||||
|
"webhook" => Some(Self::Webhook(crate::commands::webhook::Options::extract(ctx))),
|
||||||
_ => None,
|
_ => None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn execute(self, ctx: ApplicationContext<'_>) -> Result<(), Error> {
|
|
||||||
match self {
|
|
||||||
RecordedCommand::Remind(options) => {
|
|
||||||
remind::remind(Context::Application(ctx), options).await
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct CommandMacro {
|
pub struct CommandMacro {
|
||||||
|
10
src/utils.rs
10
src/utils.rs
@ -9,7 +9,7 @@ use poise::{
|
|||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
consts::{CNC_GUILD, SUBSCRIPTION_ROLES},
|
consts::{CNC_GUILD, SUBSCRIPTION_ROLES},
|
||||||
ApplicationContext, Context,
|
ApplicationContext, Context, Error,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub async fn check_subscription(cache_http: impl CacheHttp, user_id: impl Into<UserId>) -> bool {
|
pub async fn check_subscription(cache_http: impl CacheHttp, user_id: impl Into<UserId>) -> bool {
|
||||||
@ -65,11 +65,17 @@ pub fn footer(ctx: Context<'_>) -> CreateEmbedFooter {
|
|||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub trait Recordable {
|
||||||
|
async fn run(self, ctx: Context<'_>) -> Result<(), Error>;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub use recordable_derive::Recordable;
|
||||||
|
|
||||||
pub trait Extract {
|
pub trait Extract {
|
||||||
fn extract(ctx: ApplicationContext) -> Self;
|
fn extract(ctx: ApplicationContext) -> Self;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub use extract_macro::Extract;
|
pub use extract_derive::Extract;
|
||||||
|
|
||||||
macro_rules! extract_arg {
|
macro_rules! extract_arg {
|
||||||
($ctx:ident, $name:ident, String) => {
|
($ctx:ident, $name:ident, String) => {
|
||||||
|
Loading…
Reference in New Issue
Block a user