This commit is contained in:
jellywx 2021-09-18 13:40:30 +01:00
parent a9c91bee93
commit a0974795e1
8 changed files with 276 additions and 263 deletions

View File

@ -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"))),

View File

@ -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) {

View File

@ -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 =

View File

@ -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 }),
) )

View File

@ -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)

View File

@ -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,

View File

@ -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) {

View File

@ -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),
} }