...
This commit is contained in:
parent
a9c91bee93
commit
a0974795e1
@ -32,7 +32,7 @@ async fn info(ctx: &Context, invoke: &(dyn CommandInvoke + Send + Sync)) {
|
|||||||
let current_user = ctx.cache.current_user();
|
let current_user = ctx.cache.current_user();
|
||||||
let footer = footer(ctx);
|
let footer = footer(ctx);
|
||||||
|
|
||||||
invoke
|
let _ = invoke
|
||||||
.respond(
|
.respond(
|
||||||
ctx.http.clone(),
|
ctx.http.clone(),
|
||||||
CreateGenericResponse::new().embed(|e| {
|
CreateGenericResponse::new().embed(|e| {
|
||||||
@ -40,15 +40,13 @@ async fn info(ctx: &Context, invoke: &(dyn CommandInvoke + Send + Sync)) {
|
|||||||
.description(format!(
|
.description(format!(
|
||||||
"Default prefix: `{default_prefix}`
|
"Default prefix: `{default_prefix}`
|
||||||
Reset prefix: `@{user} prefix {default_prefix}`
|
Reset prefix: `@{user} prefix {default_prefix}`
|
||||||
Help: `{prefix}help`**Welcome \
|
Help: `{prefix}help`**Welcome to Reminder Bot!**
|
||||||
to Reminder Bot!**
|
|
||||||
Developer: <@203532103185465344>
|
Developer: <@203532103185465344>
|
||||||
Icon: <@253202252821430272>
|
Icon: <@253202252821430272>
|
||||||
Find me on https://discord.jellywx.com \
|
Find me on https://discord.jellywx.com and on https://github.com/JellyWX :)
|
||||||
and on https://github.com/JellyWX :)
|
|
||||||
|
|
||||||
Invite the bot: https://invite.reminder-bot.com/Use our dashboard: \
|
Invite the bot: https://invite.reminder-bot.com/
|
||||||
https://reminder-bot.com/",
|
Use our dashboard: https://reminder-bot.com/",
|
||||||
default_prefix = *DEFAULT_PREFIX,
|
default_prefix = *DEFAULT_PREFIX,
|
||||||
user = current_user.name,
|
user = current_user.name,
|
||||||
prefix = prefix
|
prefix = prefix
|
||||||
@ -66,7 +64,7 @@ Invite the bot: https://invite.reminder-bot.com/Use our dashboard: \
|
|||||||
async fn donate(ctx: &Context, invoke: &(dyn CommandInvoke + Send + Sync)) {
|
async fn donate(ctx: &Context, invoke: &(dyn CommandInvoke + Send + Sync)) {
|
||||||
let footer = footer(ctx);
|
let footer = footer(ctx);
|
||||||
|
|
||||||
invoke
|
let _ = invoke
|
||||||
.respond(
|
.respond(
|
||||||
ctx.http.clone(),
|
ctx.http.clone(),
|
||||||
CreateGenericResponse::new().embed(|e| {
|
CreateGenericResponse::new().embed(|e| {
|
||||||
@ -99,7 +97,7 @@ Just $2 USD/month!
|
|||||||
async fn dashboard(ctx: &Context, invoke: &(dyn CommandInvoke + Send + Sync)) {
|
async fn dashboard(ctx: &Context, invoke: &(dyn CommandInvoke + Send + Sync)) {
|
||||||
let footer = footer(ctx);
|
let footer = footer(ctx);
|
||||||
|
|
||||||
invoke
|
let _ = invoke
|
||||||
.respond(
|
.respond(
|
||||||
ctx.http.clone(),
|
ctx.http.clone(),
|
||||||
CreateGenericResponse::new().embed(|e| {
|
CreateGenericResponse::new().embed(|e| {
|
||||||
@ -119,7 +117,7 @@ async fn clock(ctx: &Context, invoke: &(dyn CommandInvoke + Send + Sync)) {
|
|||||||
let ud = ctx.user_data(&invoke.author_id()).await.unwrap();
|
let ud = ctx.user_data(&invoke.author_id()).await.unwrap();
|
||||||
let now = Utc::now().with_timezone(&ud.timezone());
|
let now = Utc::now().with_timezone(&ud.timezone());
|
||||||
|
|
||||||
invoke
|
let _ = invoke
|
||||||
.respond(
|
.respond(
|
||||||
ctx.http.clone(),
|
ctx.http.clone(),
|
||||||
CreateGenericResponse::new().content(format!("Current time: {}", now.format("%H:%M"))),
|
CreateGenericResponse::new().content(format!("Current time: {}", now.format("%H:%M"))),
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
use std::{
|
use std::{
|
||||||
default::Default,
|
collections::HashSet,
|
||||||
string::ToString,
|
string::ToString,
|
||||||
time::{SystemTime, UNIX_EPOCH},
|
time::{SystemTime, UNIX_EPOCH},
|
||||||
};
|
};
|
||||||
@ -7,7 +7,15 @@ use std::{
|
|||||||
use chrono::NaiveDateTime;
|
use chrono::NaiveDateTime;
|
||||||
use num_integer::Integer;
|
use num_integer::Integer;
|
||||||
use regex_command_attr::command;
|
use regex_command_attr::command;
|
||||||
use serenity::{client::Context, model::channel::Channel};
|
use serenity::{
|
||||||
|
builder::CreateEmbed,
|
||||||
|
client::Context,
|
||||||
|
model::{
|
||||||
|
channel::Channel,
|
||||||
|
id::{GuildId, UserId},
|
||||||
|
interactions::InteractionResponseType,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
check_subscription_on_message,
|
check_subscription_on_message,
|
||||||
@ -17,7 +25,7 @@ use crate::{
|
|||||||
},
|
},
|
||||||
consts::{
|
consts::{
|
||||||
EMBED_DESCRIPTION_MAX_LENGTH, REGEX_CHANNEL_USER, REGEX_NATURAL_COMMAND_1,
|
EMBED_DESCRIPTION_MAX_LENGTH, REGEX_CHANNEL_USER, REGEX_NATURAL_COMMAND_1,
|
||||||
REGEX_NATURAL_COMMAND_2, REGEX_REMIND_COMMAND, THEME_COLOR,
|
REGEX_NATURAL_COMMAND_2, THEME_COLOR,
|
||||||
},
|
},
|
||||||
framework::{CommandInvoke, CommandOptions, CreateGenericResponse, OptionValue},
|
framework::{CommandInvoke, CommandOptions, CreateGenericResponse, OptionValue},
|
||||||
models::{
|
models::{
|
||||||
@ -26,6 +34,7 @@ use crate::{
|
|||||||
reminder::{
|
reminder::{
|
||||||
builder::{MultiReminderBuilder, ReminderScope},
|
builder::{MultiReminderBuilder, ReminderScope},
|
||||||
content::Content,
|
content::Content,
|
||||||
|
errors::ReminderError,
|
||||||
look_flags::{LookFlags, TimeDisplayType},
|
look_flags::{LookFlags, TimeDisplayType},
|
||||||
Reminder,
|
Reminder,
|
||||||
},
|
},
|
||||||
@ -33,7 +42,7 @@ use crate::{
|
|||||||
user_data::UserData,
|
user_data::UserData,
|
||||||
CtxData,
|
CtxData,
|
||||||
},
|
},
|
||||||
time_parser::{natural_parser, TimeParser},
|
time_parser::natural_parser,
|
||||||
SQLPool,
|
SQLPool,
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -444,6 +453,15 @@ async fn delete(ctx: &Context, invoke: &(dyn CommandInvoke + Send + Sync)) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn show_delete_page(
|
||||||
|
ctx: &Context,
|
||||||
|
guild_id: Option<GuildId>,
|
||||||
|
user_id: UserId,
|
||||||
|
page: usize,
|
||||||
|
timezone: Tz,
|
||||||
|
) {
|
||||||
|
}
|
||||||
|
|
||||||
#[command("timer")]
|
#[command("timer")]
|
||||||
#[description("Manage timers")]
|
#[description("Manage timers")]
|
||||||
#[subcommand("list")]
|
#[subcommand("list")]
|
||||||
@ -582,23 +600,168 @@ DELETE FROM timers WHERE owner = ? AND name = ?
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
|
||||||
#[derive(PartialEq)]
|
|
||||||
enum RemindCommand {
|
|
||||||
Remind,
|
|
||||||
Interval,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[command("remind")]
|
#[command("remind")]
|
||||||
#[permission_level(Managed)]
|
#[description("Create a new reminder")]
|
||||||
async fn remind(ctx: &Context, msg: &Message, args: String) {
|
#[arg(
|
||||||
remind_command(ctx, msg, args, RemindCommand::Remind).await;
|
name = "time",
|
||||||
|
description = "A description of the time to set the reminder for",
|
||||||
|
kind = "String",
|
||||||
|
required = true
|
||||||
|
)]
|
||||||
|
#[arg(
|
||||||
|
name = "content",
|
||||||
|
description = "The message content to send",
|
||||||
|
kind = "String",
|
||||||
|
required = true
|
||||||
|
)]
|
||||||
|
#[arg(
|
||||||
|
name = "channels",
|
||||||
|
description = "Channel or user mentions to set the reminder for",
|
||||||
|
kind = "String",
|
||||||
|
required = false
|
||||||
|
)]
|
||||||
|
#[arg(
|
||||||
|
name = "repeat",
|
||||||
|
description = "(Patreon only) Time to wait before repeating the reminder. Leave blank for one-shot reminder",
|
||||||
|
kind = "String",
|
||||||
|
required = false
|
||||||
|
)]
|
||||||
|
#[arg(
|
||||||
|
name = "expires",
|
||||||
|
description = "(Patreon only) For repeating reminders, the time at which the reminder will stop sending",
|
||||||
|
kind = "String",
|
||||||
|
required = false
|
||||||
|
)]
|
||||||
|
#[arg(
|
||||||
|
name = "tts",
|
||||||
|
description = "Set the TTS flag on the reminder message (like the /tts command)",
|
||||||
|
kind = "Boolean",
|
||||||
|
required = false
|
||||||
|
)]
|
||||||
|
#[required_permissions(Managed)]
|
||||||
|
async fn remind(ctx: &Context, invoke: &(dyn CommandInvoke + Send + Sync), args: CommandOptions) {
|
||||||
|
let interaction = invoke.interaction().unwrap();
|
||||||
|
|
||||||
|
// defer response since processing times can take some time
|
||||||
|
interaction
|
||||||
|
.create_interaction_response(&ctx, |r| {
|
||||||
|
r.kind(InteractionResponseType::DeferredChannelMessageWithSource)
|
||||||
|
})
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let user_data = ctx.user_data(invoke.author_id()).await.unwrap();
|
||||||
|
let timezone = user_data.timezone();
|
||||||
|
|
||||||
|
let time = {
|
||||||
|
let time_str = args.get("time").unwrap().to_string();
|
||||||
|
|
||||||
|
natural_parser(&time_str, &timezone.to_string()).await
|
||||||
|
};
|
||||||
|
|
||||||
|
match time {
|
||||||
|
Some(time) => {
|
||||||
|
let content = {
|
||||||
|
let content = args.get("content").unwrap().to_string();
|
||||||
|
let tts = args.get("tts").map_or(false, |arg| arg.as_bool().unwrap_or(false));
|
||||||
|
|
||||||
|
Content { content, tts, attachment: None, attachment_name: None }
|
||||||
|
};
|
||||||
|
|
||||||
|
let scopes = {
|
||||||
|
let list = args
|
||||||
|
.get("channels")
|
||||||
|
.map(|arg| parse_mention_list(&arg.to_string()))
|
||||||
|
.unwrap_or(vec![]);
|
||||||
|
|
||||||
|
if list.is_empty() {
|
||||||
|
vec![ReminderScope::Channel(invoke.channel_id().0)]
|
||||||
|
} else {
|
||||||
|
list
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let interval = args
|
||||||
|
.get("repeat")
|
||||||
|
.map(|arg| {
|
||||||
|
humantime::parse_duration(&arg.to_string())
|
||||||
|
.or_else(|_| humantime::parse_duration(&format!("1 {}", arg.to_string())))
|
||||||
|
.map(|duration| duration.as_secs() as i64)
|
||||||
|
.ok()
|
||||||
|
})
|
||||||
|
.flatten();
|
||||||
|
let expires = {
|
||||||
|
if let Some(arg) = args.get("expires") {
|
||||||
|
natural_parser(&arg.to_string(), &timezone.to_string()).await
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut builder = MultiReminderBuilder::new(ctx, invoke.guild_id())
|
||||||
|
.author(user_data)
|
||||||
|
.content(content)
|
||||||
|
.time(time)
|
||||||
|
.expires(expires)
|
||||||
|
.interval(interval);
|
||||||
|
|
||||||
|
builder.set_scopes(scopes);
|
||||||
|
|
||||||
|
let (errors, successes) = builder.build().await;
|
||||||
|
|
||||||
|
let embed = create_response(successes, errors, time);
|
||||||
|
|
||||||
|
interaction
|
||||||
|
.edit_original_interaction_response(&ctx, |r| r.add_embed(embed))
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
}
|
||||||
|
None => {
|
||||||
|
let _ = interaction
|
||||||
|
.edit_original_interaction_response(&ctx, |r| {
|
||||||
|
r.content("Time could not be processed.")
|
||||||
|
})
|
||||||
|
.await;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[command("interval")]
|
fn create_response(
|
||||||
#[permission_level(Managed)]
|
successes: HashSet<ReminderScope>,
|
||||||
async fn interval(ctx: &Context, msg: &Message, args: String) {
|
errors: HashSet<ReminderError>,
|
||||||
remind_command(ctx, msg, args, RemindCommand::Interval).await;
|
time: i64,
|
||||||
|
) -> CreateEmbed {
|
||||||
|
let success_part = match successes.len() {
|
||||||
|
0 => "".to_string(),
|
||||||
|
n => format!(
|
||||||
|
"Reminder{s} for {locations} set for <t:{offset}:R>",
|
||||||
|
s = if n > 1 { "s" } else { "" },
|
||||||
|
locations = successes.iter().map(|l| l.mention()).collect::<Vec<String>>().join(", "),
|
||||||
|
offset = time
|
||||||
|
),
|
||||||
|
};
|
||||||
|
|
||||||
|
let error_part = match errors.len() {
|
||||||
|
0 => "".to_string(),
|
||||||
|
n => format!(
|
||||||
|
"{n} reminder{s} failed to set:\n{errors}",
|
||||||
|
s = if n > 1 { "s" } else { "" },
|
||||||
|
n = n,
|
||||||
|
errors = errors.iter().map(|e| e.to_string()).collect::<Vec<String>>().join("\n")
|
||||||
|
),
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut embed = CreateEmbed::default();
|
||||||
|
embed
|
||||||
|
.title(format!(
|
||||||
|
"{n} Reminder{s} Set",
|
||||||
|
n = successes.len(),
|
||||||
|
s = if successes.len() > 1 { "s" } else { "" }
|
||||||
|
))
|
||||||
|
.description(format!("{}\n\n{}", success_part, error_part))
|
||||||
|
.color(*THEME_COLOR);
|
||||||
|
|
||||||
|
embed
|
||||||
}
|
}
|
||||||
|
|
||||||
fn parse_mention_list(mentions: &str) -> Vec<ReminderScope> {
|
fn parse_mention_list(mentions: &str) -> Vec<ReminderScope> {
|
||||||
@ -617,158 +780,7 @@ fn parse_mention_list(mentions: &str) -> Vec<ReminderScope> {
|
|||||||
.collect::<Vec<ReminderScope>>()
|
.collect::<Vec<ReminderScope>>()
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn remind_command(ctx: &Context, msg: &Message, args: String, command: RemindCommand) {
|
/*
|
||||||
let (pool, lm) = get_ctx_data(&ctx).await;
|
|
||||||
|
|
||||||
let timezone = UserData::timezone_of(&msg.author, &pool).await;
|
|
||||||
let language = UserData::language_of(&msg.author, &pool).await;
|
|
||||||
|
|
||||||
match REGEX_REMIND_COMMAND.captures(&args) {
|
|
||||||
Some(captures) => {
|
|
||||||
let parsed = parse_mention_list(captures.name("mentions").unwrap().as_str());
|
|
||||||
|
|
||||||
let scopes = if parsed.is_empty() {
|
|
||||||
vec![ReminderScope::Channel(msg.channel_id.into())]
|
|
||||||
} else {
|
|
||||||
parsed
|
|
||||||
};
|
|
||||||
|
|
||||||
let time_parser = TimeParser::new(captures.name("time").unwrap().as_str(), timezone);
|
|
||||||
|
|
||||||
let expires_parser =
|
|
||||||
captures.name("expires").map(|mat| TimeParser::new(mat.as_str(), timezone));
|
|
||||||
|
|
||||||
let interval_parser = captures
|
|
||||||
.name("interval")
|
|
||||||
.map(|mat| TimeParser::new(mat.as_str(), timezone))
|
|
||||||
.map(|parser| parser.displacement())
|
|
||||||
.transpose();
|
|
||||||
|
|
||||||
if let Ok(interval) = interval_parser {
|
|
||||||
if interval.is_some() && !check_subscription_on_message(&ctx, msg).await {
|
|
||||||
// no patreon
|
|
||||||
let _ = msg
|
|
||||||
.channel_id
|
|
||||||
.say(
|
|
||||||
&ctx,
|
|
||||||
lm.get(&language, "interval/donor")
|
|
||||||
.replace("{prefix}", &ctx.prefix(msg.guild_id).await),
|
|
||||||
)
|
|
||||||
.await;
|
|
||||||
} else {
|
|
||||||
let content_res = Content::build(
|
|
||||||
captures.name("content").map(|mat| mat.as_str()).unwrap(),
|
|
||||||
msg,
|
|
||||||
)
|
|
||||||
.await;
|
|
||||||
|
|
||||||
match content_res {
|
|
||||||
Ok(mut content) => {
|
|
||||||
if let Some(guild) = msg.guild(&ctx) {
|
|
||||||
content.substitute(guild);
|
|
||||||
}
|
|
||||||
|
|
||||||
let user_data = ctx.user_data(&msg.author).await.unwrap();
|
|
||||||
|
|
||||||
let mut builder = MultiReminderBuilder::new(ctx, msg.guild_id)
|
|
||||||
.author(user_data)
|
|
||||||
.content(content)
|
|
||||||
.interval(interval)
|
|
||||||
.expires_parser(expires_parser)
|
|
||||||
.time_parser(time_parser.clone());
|
|
||||||
|
|
||||||
builder.set_scopes(scopes);
|
|
||||||
|
|
||||||
let (errors, successes) = builder.build().await;
|
|
||||||
|
|
||||||
let success_part = match successes.len() {
|
|
||||||
0 => "".to_string(),
|
|
||||||
n => format!(
|
|
||||||
"Reminder{s} for {locations} set for <t:{offset}:R>",
|
|
||||||
s = if n > 1 { "s" } else { "" },
|
|
||||||
locations = successes
|
|
||||||
.iter()
|
|
||||||
.map(|l| l.mention())
|
|
||||||
.collect::<Vec<String>>()
|
|
||||||
.join(", "),
|
|
||||||
offset = time_parser.timestamp().unwrap()
|
|
||||||
),
|
|
||||||
};
|
|
||||||
|
|
||||||
let error_part = match errors.len() {
|
|
||||||
0 => "".to_string(),
|
|
||||||
n => format!(
|
|
||||||
"{n} reminder{s} failed to set:\n{errors}",
|
|
||||||
s = if n > 1 { "s" } else { "" },
|
|
||||||
n = n,
|
|
||||||
errors = errors
|
|
||||||
.iter()
|
|
||||||
.map(|e| e.display(false))
|
|
||||||
.collect::<Vec<String>>()
|
|
||||||
.join("\n")
|
|
||||||
),
|
|
||||||
};
|
|
||||||
|
|
||||||
let _ = msg
|
|
||||||
.channel_id
|
|
||||||
.send_message(&ctx, |m| {
|
|
||||||
m.embed(|e| {
|
|
||||||
e.title(format!(
|
|
||||||
"{n} Reminder{s} Set",
|
|
||||||
n = successes.len(),
|
|
||||||
s = if successes.len() > 1 { "s" } else { "" }
|
|
||||||
))
|
|
||||||
.description(format!("{}\n\n{}", success_part, error_part))
|
|
||||||
.color(*THEME_COLOR)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
.await;
|
|
||||||
}
|
|
||||||
|
|
||||||
Err(content_error) => {
|
|
||||||
let _ = msg
|
|
||||||
.channel_id
|
|
||||||
.send_message(ctx, |m| {
|
|
||||||
m.embed(move |e| {
|
|
||||||
e.title("0 Reminders Set")
|
|
||||||
.description(content_error.to_string())
|
|
||||||
.color(*THEME_COLOR)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
.await;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
let _ = msg
|
|
||||||
.channel_id
|
|
||||||
.send_message(ctx, |m| {
|
|
||||||
m.embed(move |e| {
|
|
||||||
e.title(lm.get(&language, "remind/title").replace("{number}", "0"))
|
|
||||||
.description(lm.get(&language, "interval/invalid_interval"))
|
|
||||||
.color(*THEME_COLOR)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
.await;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
None => {
|
|
||||||
let prefix = ctx.prefix(msg.guild_id).await;
|
|
||||||
|
|
||||||
match command {
|
|
||||||
RemindCommand::Remind => {
|
|
||||||
command_help(ctx, msg, lm, &prefix, &language, "remind").await
|
|
||||||
}
|
|
||||||
|
|
||||||
RemindCommand::Interval => {
|
|
||||||
command_help(ctx, msg, lm, &prefix, &language, "interval").await
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[command("natural")]
|
#[command("natural")]
|
||||||
#[permission_level(Managed)]
|
#[permission_level(Managed)]
|
||||||
async fn natural(ctx: &Context, msg: &Message, args: String) {
|
async fn natural(ctx: &Context, msg: &Message, args: String) {
|
||||||
|
@ -272,6 +272,34 @@ INSERT IGNORE INTO roles (role, name, guild_id) VALUES (?, \"Role\", (SELECT id
|
|||||||
let reminders =
|
let reminders =
|
||||||
Reminder::from_guild(ctx, component.guild_id, component.user.id).await;
|
Reminder::from_guild(ctx, component.guild_id, component.user.id).await;
|
||||||
|
|
||||||
|
if reminders.is_empty() {
|
||||||
|
let mut embed = CreateEmbed::default();
|
||||||
|
embed.title("Delete Reminders").description("No Reminders").color(*THEME_COLOR);
|
||||||
|
|
||||||
|
component
|
||||||
|
.create_interaction_response(&ctx, |r| {
|
||||||
|
r.kind(InteractionResponseType::UpdateMessage)
|
||||||
|
.interaction_response_data(|response| {
|
||||||
|
response.embeds(vec![embed]).components(|comp| comp)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.await;
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let pages = reminders
|
||||||
|
.iter()
|
||||||
|
.enumerate()
|
||||||
|
.map(|(count, reminder)| reminder.display_del(count, &selector.timezone))
|
||||||
|
.fold(0, |t, r| t + r.len())
|
||||||
|
.div_ceil(EMBED_DESCRIPTION_MAX_LENGTH);
|
||||||
|
|
||||||
|
let mut page = selector.page;
|
||||||
|
if page >= pages {
|
||||||
|
page = pages - 1;
|
||||||
|
}
|
||||||
|
|
||||||
let mut char_count = 0;
|
let mut char_count = 0;
|
||||||
let mut skip_char_count = 0;
|
let mut skip_char_count = 0;
|
||||||
let mut first_num = 0;
|
let mut first_num = 0;
|
||||||
@ -286,7 +314,7 @@ INSERT IGNORE INTO roles (role, name, guild_id) VALUES (?, \"Role\", (SELECT id
|
|||||||
first_num += 1;
|
first_num += 1;
|
||||||
skip_char_count += p.len();
|
skip_char_count += p.len();
|
||||||
|
|
||||||
skip_char_count < EMBED_DESCRIPTION_MAX_LENGTH * selector.page
|
skip_char_count < EMBED_DESCRIPTION_MAX_LENGTH * page
|
||||||
})
|
})
|
||||||
.take_while(|(_, p)| {
|
.take_while(|(_, p)| {
|
||||||
char_count += p.len();
|
char_count += p.len();
|
||||||
@ -297,17 +325,10 @@ INSERT IGNORE INTO roles (role, name, guild_id) VALUES (?, \"Role\", (SELECT id
|
|||||||
|
|
||||||
let display = display_vec.join("\n");
|
let display = display_vec.join("\n");
|
||||||
|
|
||||||
let pages = reminders
|
|
||||||
.iter()
|
|
||||||
.enumerate()
|
|
||||||
.map(|(count, reminder)| reminder.display_del(count, &selector.timezone))
|
|
||||||
.fold(0, |t, r| t + r.len())
|
|
||||||
.div_ceil(EMBED_DESCRIPTION_MAX_LENGTH);
|
|
||||||
|
|
||||||
let pager = DelPager::new(selector.timezone);
|
let pager = DelPager::new(selector.timezone);
|
||||||
|
|
||||||
let del_selector = ComponentDataModel::DelSelector(DelSelector {
|
let del_selector = ComponentDataModel::DelSelector(DelSelector {
|
||||||
page: selector.page,
|
page,
|
||||||
timezone: selector.timezone,
|
timezone: selector.timezone,
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -315,7 +336,7 @@ INSERT IGNORE INTO roles (role, name, guild_id) VALUES (?, \"Role\", (SELECT id
|
|||||||
embed
|
embed
|
||||||
.title("Delete Reminders")
|
.title("Delete Reminders")
|
||||||
.description(display)
|
.description(display)
|
||||||
.footer(|f| f.text(format!("Page {} of {}", selector.page + 1, pages)))
|
.footer(|f| f.text(format!("Page {} of {}", page + 1, pages)))
|
||||||
.color(*THEME_COLOR);
|
.color(*THEME_COLOR);
|
||||||
|
|
||||||
component
|
component
|
||||||
@ -333,7 +354,7 @@ INSERT IGNORE INTO roles (role, name, guild_id) VALUES (?, \"Role\", (SELECT id
|
|||||||
shown_reminders.iter().enumerate()
|
shown_reminders.iter().enumerate()
|
||||||
{
|
{
|
||||||
opt.create_option(|o| {
|
opt.create_option(|o| {
|
||||||
o.label(count + 1)
|
o.label(count + first_num)
|
||||||
.value(reminder.id)
|
.value(reminder.id)
|
||||||
.description({
|
.description({
|
||||||
let c =
|
let c =
|
||||||
|
@ -16,8 +16,9 @@ pub trait Pager {
|
|||||||
enum PageAction {
|
enum PageAction {
|
||||||
First = 0,
|
First = 0,
|
||||||
Previous = 1,
|
Previous = 1,
|
||||||
Next = 2,
|
Refresh = 2,
|
||||||
Last = 3,
|
Next = 3,
|
||||||
|
Last = 4,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize)]
|
#[derive(Serialize, Deserialize)]
|
||||||
@ -33,6 +34,7 @@ impl Pager for LookPager {
|
|||||||
match self.action {
|
match self.action {
|
||||||
PageAction::First => 0,
|
PageAction::First => 0,
|
||||||
PageAction::Previous => 0.max(self.page - 1),
|
PageAction::Previous => 0.max(self.page - 1),
|
||||||
|
PageAction::Refresh => self.page,
|
||||||
PageAction::Next => (max_pages - 1).min(self.page + 1),
|
PageAction::Next => (max_pages - 1).min(self.page + 1),
|
||||||
PageAction::Last => max_pages - 1,
|
PageAction::Last => max_pages - 1,
|
||||||
}
|
}
|
||||||
@ -41,7 +43,7 @@ impl Pager for LookPager {
|
|||||||
fn create_button_row(&self, max_pages: usize, comp: &mut CreateComponents) {
|
fn create_button_row(&self, max_pages: usize, comp: &mut CreateComponents) {
|
||||||
let next_page = self.next_page(max_pages);
|
let next_page = self.next_page(max_pages);
|
||||||
|
|
||||||
let (page_first, page_prev, page_next, page_last) =
|
let (page_first, page_prev, page_refresh, page_next, page_last) =
|
||||||
LookPager::buttons(self.flags, next_page, self.timezone);
|
LookPager::buttons(self.flags, next_page, self.timezone);
|
||||||
|
|
||||||
comp.create_action_row(|row| {
|
comp.create_action_row(|row| {
|
||||||
@ -57,6 +59,9 @@ impl Pager for LookPager {
|
|||||||
.custom_id(page_prev.to_custom_id())
|
.custom_id(page_prev.to_custom_id())
|
||||||
.disabled(next_page == 0)
|
.disabled(next_page == 0)
|
||||||
})
|
})
|
||||||
|
.create_button(|b| {
|
||||||
|
b.label("🔁").style(ButtonStyle::Secondary).custom_id(page_refresh.to_custom_id())
|
||||||
|
})
|
||||||
.create_button(|b| {
|
.create_button(|b| {
|
||||||
b.label("▶️")
|
b.label("▶️")
|
||||||
.style(ButtonStyle::Secondary)
|
.style(ButtonStyle::Secondary)
|
||||||
@ -82,7 +87,13 @@ impl LookPager {
|
|||||||
flags: LookFlags,
|
flags: LookFlags,
|
||||||
page: usize,
|
page: usize,
|
||||||
timezone: Tz,
|
timezone: Tz,
|
||||||
) -> (ComponentDataModel, ComponentDataModel, ComponentDataModel, ComponentDataModel) {
|
) -> (
|
||||||
|
ComponentDataModel,
|
||||||
|
ComponentDataModel,
|
||||||
|
ComponentDataModel,
|
||||||
|
ComponentDataModel,
|
||||||
|
ComponentDataModel,
|
||||||
|
) {
|
||||||
(
|
(
|
||||||
ComponentDataModel::LookPager(LookPager {
|
ComponentDataModel::LookPager(LookPager {
|
||||||
flags,
|
flags,
|
||||||
@ -96,6 +107,12 @@ impl LookPager {
|
|||||||
action: PageAction::Previous,
|
action: PageAction::Previous,
|
||||||
timezone,
|
timezone,
|
||||||
}),
|
}),
|
||||||
|
ComponentDataModel::LookPager(LookPager {
|
||||||
|
flags,
|
||||||
|
page,
|
||||||
|
action: PageAction::Refresh,
|
||||||
|
timezone,
|
||||||
|
}),
|
||||||
ComponentDataModel::LookPager(LookPager {
|
ComponentDataModel::LookPager(LookPager {
|
||||||
flags,
|
flags,
|
||||||
page,
|
page,
|
||||||
@ -124,6 +141,7 @@ impl Pager for DelPager {
|
|||||||
match self.action {
|
match self.action {
|
||||||
PageAction::First => 0,
|
PageAction::First => 0,
|
||||||
PageAction::Previous => 0.max(self.page - 1),
|
PageAction::Previous => 0.max(self.page - 1),
|
||||||
|
PageAction::Refresh => self.page,
|
||||||
PageAction::Next => (max_pages - 1).min(self.page + 1),
|
PageAction::Next => (max_pages - 1).min(self.page + 1),
|
||||||
PageAction::Last => max_pages - 1,
|
PageAction::Last => max_pages - 1,
|
||||||
}
|
}
|
||||||
@ -132,7 +150,7 @@ impl Pager for DelPager {
|
|||||||
fn create_button_row(&self, max_pages: usize, comp: &mut CreateComponents) {
|
fn create_button_row(&self, max_pages: usize, comp: &mut CreateComponents) {
|
||||||
let next_page = self.next_page(max_pages);
|
let next_page = self.next_page(max_pages);
|
||||||
|
|
||||||
let (page_first, page_prev, page_next, page_last) =
|
let (page_first, page_prev, page_refresh, page_next, page_last) =
|
||||||
DelPager::buttons(next_page, self.timezone);
|
DelPager::buttons(next_page, self.timezone);
|
||||||
|
|
||||||
comp.create_action_row(|row| {
|
comp.create_action_row(|row| {
|
||||||
@ -148,6 +166,9 @@ impl Pager for DelPager {
|
|||||||
.custom_id(page_prev.to_custom_id())
|
.custom_id(page_prev.to_custom_id())
|
||||||
.disabled(next_page == 0)
|
.disabled(next_page == 0)
|
||||||
})
|
})
|
||||||
|
.create_button(|b| {
|
||||||
|
b.label("🔁").style(ButtonStyle::Secondary).custom_id(page_refresh.to_custom_id())
|
||||||
|
})
|
||||||
.create_button(|b| {
|
.create_button(|b| {
|
||||||
b.label("▶️")
|
b.label("▶️")
|
||||||
.style(ButtonStyle::Secondary)
|
.style(ButtonStyle::Secondary)
|
||||||
@ -172,10 +193,17 @@ impl DelPager {
|
|||||||
pub fn buttons(
|
pub fn buttons(
|
||||||
page: usize,
|
page: usize,
|
||||||
timezone: Tz,
|
timezone: Tz,
|
||||||
) -> (ComponentDataModel, ComponentDataModel, ComponentDataModel, ComponentDataModel) {
|
) -> (
|
||||||
|
ComponentDataModel,
|
||||||
|
ComponentDataModel,
|
||||||
|
ComponentDataModel,
|
||||||
|
ComponentDataModel,
|
||||||
|
ComponentDataModel,
|
||||||
|
) {
|
||||||
(
|
(
|
||||||
ComponentDataModel::DelPager(DelPager { page, action: PageAction::First, timezone }),
|
ComponentDataModel::DelPager(DelPager { page, action: PageAction::First, timezone }),
|
||||||
ComponentDataModel::DelPager(DelPager { page, action: PageAction::Previous, timezone }),
|
ComponentDataModel::DelPager(DelPager { page, action: PageAction::Previous, timezone }),
|
||||||
|
ComponentDataModel::DelPager(DelPager { page, action: PageAction::Refresh, timezone }),
|
||||||
ComponentDataModel::DelPager(DelPager { page, action: PageAction::Next, timezone }),
|
ComponentDataModel::DelPager(DelPager { page, action: PageAction::Next, timezone }),
|
||||||
ComponentDataModel::DelPager(DelPager { page, action: PageAction::Last, timezone }),
|
ComponentDataModel::DelPager(DelPager { page, action: PageAction::Last, timezone }),
|
||||||
)
|
)
|
||||||
|
@ -296,8 +296,8 @@ async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
|
|||||||
.add_command(&info_cmds::CLOCK_COMMAND)
|
.add_command(&info_cmds::CLOCK_COMMAND)
|
||||||
// reminder commands
|
// reminder commands
|
||||||
.add_command(&reminder_cmds::TIMER_COMMAND)
|
.add_command(&reminder_cmds::TIMER_COMMAND)
|
||||||
|
.add_command(&reminder_cmds::REMIND_COMMAND)
|
||||||
/*
|
/*
|
||||||
.add_command("remind", &reminder_cmds::REMIND_COMMAND)
|
|
||||||
.add_command("r", &reminder_cmds::REMIND_COMMAND)
|
.add_command("r", &reminder_cmds::REMIND_COMMAND)
|
||||||
.add_command("interval", &reminder_cmds::INTERVAL_COMMAND)
|
.add_command("interval", &reminder_cmds::INTERVAL_COMMAND)
|
||||||
.add_command("i", &reminder_cmds::INTERVAL_COMMAND)
|
.add_command("i", &reminder_cmds::INTERVAL_COMMAND)
|
||||||
|
@ -6,6 +6,7 @@ pub mod user_data;
|
|||||||
|
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
use chrono_tz::Tz;
|
||||||
use serenity::{
|
use serenity::{
|
||||||
async_trait,
|
async_trait,
|
||||||
model::id::{ChannelId, GuildId, UserId},
|
model::id::{ChannelId, GuildId, UserId},
|
||||||
@ -33,6 +34,8 @@ pub trait CtxData {
|
|||||||
user_id: U,
|
user_id: U,
|
||||||
) -> Result<UserData, Box<dyn std::error::Error + Sync + Send>>;
|
) -> Result<UserData, Box<dyn std::error::Error + Sync + Send>>;
|
||||||
|
|
||||||
|
async fn timezone<U: Into<UserId> + Send + Sync>(&self, user_id: U) -> Tz;
|
||||||
|
|
||||||
async fn channel_data<C: Into<ChannelId> + Send + Sync>(
|
async fn channel_data<C: Into<ChannelId> + Send + Sync>(
|
||||||
&self,
|
&self,
|
||||||
channel_id: C,
|
channel_id: C,
|
||||||
@ -92,6 +95,13 @@ impl CtxData for Context {
|
|||||||
UserData::from_user(&user, &self, &pool).await
|
UserData::from_user(&user, &self, &pool).await
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn timezone<U: Into<UserId> + Send + Sync>(&self, user_id: U) -> Tz {
|
||||||
|
let user_id = user_id.into();
|
||||||
|
let pool = self.data.read().await.get::<SQLPool>().cloned().unwrap();
|
||||||
|
|
||||||
|
UserData::timezone_of(user_id, &pool).await
|
||||||
|
}
|
||||||
|
|
||||||
async fn channel_data<C: Into<ChannelId> + Send + Sync>(
|
async fn channel_data<C: Into<ChannelId> + Send + Sync>(
|
||||||
&self,
|
&self,
|
||||||
channel_id: C,
|
channel_id: C,
|
||||||
|
@ -21,7 +21,6 @@ use crate::{
|
|||||||
reminder::{content::Content, errors::ReminderError, helper::generate_uid, Reminder},
|
reminder::{content::Content, errors::ReminderError, helper::generate_uid, Reminder},
|
||||||
user_data::UserData,
|
user_data::UserData,
|
||||||
},
|
},
|
||||||
time_parser::TimeParser,
|
|
||||||
SQLPool,
|
SQLPool,
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -133,7 +132,8 @@ INSERT INTO reminders (
|
|||||||
self.set_by
|
self.set_by
|
||||||
)
|
)
|
||||||
.execute(&self.pool)
|
.execute(&self.pool)
|
||||||
.await;
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
Ok(Reminder::from_uid(&self.pool, self.uid).await.unwrap())
|
Ok(Reminder::from_uid(&self.pool, self.uid).await.unwrap())
|
||||||
}
|
}
|
||||||
@ -147,11 +147,9 @@ INSERT INTO reminders (
|
|||||||
pub struct MultiReminderBuilder<'a> {
|
pub struct MultiReminderBuilder<'a> {
|
||||||
scopes: Vec<ReminderScope>,
|
scopes: Vec<ReminderScope>,
|
||||||
utc_time: NaiveDateTime,
|
utc_time: NaiveDateTime,
|
||||||
utc_time_parser: Option<TimeParser>,
|
|
||||||
timezone: Tz,
|
timezone: Tz,
|
||||||
interval: Option<i64>,
|
interval: Option<i64>,
|
||||||
expires: Option<NaiveDateTime>,
|
expires: Option<NaiveDateTime>,
|
||||||
expires_parser: Option<TimeParser>,
|
|
||||||
content: Content,
|
content: Content,
|
||||||
set_by: Option<u32>,
|
set_by: Option<u32>,
|
||||||
ctx: &'a Context,
|
ctx: &'a Context,
|
||||||
@ -163,11 +161,9 @@ impl<'a> MultiReminderBuilder<'a> {
|
|||||||
MultiReminderBuilder {
|
MultiReminderBuilder {
|
||||||
scopes: vec![],
|
scopes: vec![],
|
||||||
utc_time: Utc::now().naive_utc(),
|
utc_time: Utc::now().naive_utc(),
|
||||||
utc_time_parser: None,
|
|
||||||
timezone: Tz::UTC,
|
timezone: Tz::UTC,
|
||||||
interval: None,
|
interval: None,
|
||||||
expires: None,
|
expires: None,
|
||||||
expires_parser: None,
|
|
||||||
content: Content::new(),
|
content: Content::new(),
|
||||||
set_by: None,
|
set_by: None,
|
||||||
ctx,
|
ctx,
|
||||||
@ -187,12 +183,6 @@ impl<'a> MultiReminderBuilder<'a> {
|
|||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn time_parser(mut self, parser: TimeParser) -> Self {
|
|
||||||
self.utc_time_parser = Some(parser);
|
|
||||||
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn expires<T: Into<i64>>(mut self, time: Option<T>) -> Self {
|
pub fn expires<T: Into<i64>>(mut self, time: Option<T>) -> Self {
|
||||||
if let Some(t) = time {
|
if let Some(t) = time {
|
||||||
self.expires = Some(NaiveDateTime::from_timestamp(t.into(), 0));
|
self.expires = Some(NaiveDateTime::from_timestamp(t.into(), 0));
|
||||||
@ -203,12 +193,6 @@ impl<'a> MultiReminderBuilder<'a> {
|
|||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn expires_parser(mut self, parser: Option<TimeParser>) -> Self {
|
|
||||||
self.expires_parser = parser;
|
|
||||||
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn author(mut self, user: UserData) -> Self {
|
pub fn author(mut self, user: UserData) -> Self {
|
||||||
self.set_by = Some(user.id);
|
self.set_by = Some(user.id);
|
||||||
self.timezone = user.timezone();
|
self.timezone = user.timezone();
|
||||||
@ -226,33 +210,13 @@ impl<'a> MultiReminderBuilder<'a> {
|
|||||||
self.scopes = scopes;
|
self.scopes = scopes;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn build(mut self) -> (HashSet<ReminderError>, HashSet<ReminderScope>) {
|
pub async fn build(self) -> (HashSet<ReminderError>, HashSet<ReminderScope>) {
|
||||||
let pool = self.ctx.data.read().await.get::<SQLPool>().cloned().unwrap();
|
let pool = self.ctx.data.read().await.get::<SQLPool>().cloned().unwrap();
|
||||||
|
|
||||||
let mut errors = HashSet::new();
|
let mut errors = HashSet::new();
|
||||||
|
|
||||||
let mut ok_locs = HashSet::new();
|
let mut ok_locs = HashSet::new();
|
||||||
|
|
||||||
if let Some(expire_parser) = self.expires_parser {
|
|
||||||
if let Ok(expires) = expire_parser.timestamp() {
|
|
||||||
self.expires = Some(NaiveDateTime::from_timestamp(expires, 0));
|
|
||||||
} else {
|
|
||||||
errors.insert(ReminderError::InvalidExpiration);
|
|
||||||
|
|
||||||
return (errors, ok_locs);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(time_parser) = self.utc_time_parser {
|
|
||||||
if let Ok(time) = time_parser.timestamp() {
|
|
||||||
self.utc_time = NaiveDateTime::from_timestamp(time, 0);
|
|
||||||
} else {
|
|
||||||
errors.insert(ReminderError::InvalidTime);
|
|
||||||
|
|
||||||
return (errors, ok_locs);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if self.interval.map_or(false, |i| (i as i64) < *MIN_INTERVAL) {
|
if self.interval.map_or(false, |i| (i as i64) < *MIN_INTERVAL) {
|
||||||
errors.insert(ReminderError::ShortInterval);
|
errors.insert(ReminderError::ShortInterval);
|
||||||
} else if self.interval.map_or(false, |i| (i as i64) > *MAX_TIME) {
|
} else if self.interval.map_or(false, |i| (i as i64) > *MAX_TIME) {
|
||||||
|
@ -39,8 +39,8 @@ pub enum ReminderError {
|
|||||||
DiscordError(String),
|
DiscordError(String),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ReminderError {
|
impl ToString for ReminderError {
|
||||||
pub fn display(&self, is_natural: bool) -> String {
|
fn to_string(&self) -> String {
|
||||||
match self {
|
match self {
|
||||||
ReminderError::LongTime => {
|
ReminderError::LongTime => {
|
||||||
"That time is too far in the future. Please specify a shorter time.".to_string()
|
"That time is too far in the future. Please specify a shorter time.".to_string()
|
||||||
@ -50,40 +50,20 @@ impl ReminderError {
|
|||||||
max_time = *MAX_TIME / 86_400
|
max_time = *MAX_TIME / 86_400
|
||||||
),
|
),
|
||||||
ReminderError::PastTime => {
|
ReminderError::PastTime => {
|
||||||
"Please ensure the time provided is in the future. If the time should be in \
|
"Please ensure the time provided is in the future. If the time should be in the future, please be more specific with the definition.".to_string()
|
||||||
the future, please be more specific with the definition."
|
|
||||||
.to_string()
|
|
||||||
}
|
}
|
||||||
ReminderError::ShortInterval => format!(
|
ReminderError::ShortInterval => format!(
|
||||||
"Please ensure the interval provided is longer than {min_interval} seconds",
|
"Please ensure the interval provided is longer than {min_interval} seconds",
|
||||||
min_interval = *MIN_INTERVAL
|
min_interval = *MIN_INTERVAL
|
||||||
),
|
),
|
||||||
ReminderError::InvalidTag => {
|
ReminderError::InvalidTag => {
|
||||||
"Couldn't find a location by your tag. Your tag must be either a channel or \
|
"Couldn't find a location by your tag. Your tag must be either a channel or a user (not a role)".to_string()
|
||||||
a user (not a role)"
|
|
||||||
.to_string()
|
|
||||||
}
|
}
|
||||||
ReminderError::InvalidTime => {
|
ReminderError::InvalidTime => {
|
||||||
if is_natural {
|
"Your time failed to process. Please make it as clear as possible, for example `\"16th of july\"` or `\"in 20 minutes\"`".to_string()
|
||||||
"Your time failed to process. Please make it as clear as possible, for example `\"16th of july\"` \
|
|
||||||
or `\"in 20 minutes\"`"
|
|
||||||
.to_string()
|
|
||||||
} else {
|
|
||||||
"Make sure the time you have provided is in the format of [num][s/m/h/d][num][s/m/h/d] etc. or \
|
|
||||||
`day/month/year-hour:minute:second`"
|
|
||||||
.to_string()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
ReminderError::InvalidExpiration => {
|
ReminderError::InvalidExpiration => {
|
||||||
if is_natural {
|
"Your expiration time failed to process. Please make it as clear as possible, for example `\"16th of july\"` or `\"in 20 minutes\"`".to_string()
|
||||||
"Your expiration time failed to process. Please make it as clear as possible, for example `\"16th \
|
|
||||||
of july\"` or `\"in 20 minutes\"`"
|
|
||||||
.to_string()
|
|
||||||
} else {
|
|
||||||
"Make sure the expiration time you have provided is in the format of [num][s/m/h/d][num][s/m/h/d] \
|
|
||||||
etc. or `day/month/year-hour:minute:second`"
|
|
||||||
.to_string()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
ReminderError::DiscordError(s) => format!("A Discord error occurred: **{}**", s),
|
ReminderError::DiscordError(s) => format!("A Discord error occurred: **{}**", s),
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user