cleared up all unwraps from the reminder sender. cleared up clippy lints. added undo button
This commit is contained in:
parent
8bad95510d
commit
7d43aa5918
@ -58,10 +58,10 @@ fn fmt_displacement(format: &str, seconds: u64) -> String {
|
||||
|
||||
pub fn substitute(string: &str) -> String {
|
||||
let new = TIMEFROM_REGEX.replace(string, |caps: &Captures| {
|
||||
let final_time = caps.name("time").unwrap().as_str();
|
||||
let format = caps.name("format").unwrap().as_str();
|
||||
let final_time = caps.name("time").map(|m| m.as_str().parse::<i64>().ok()).flatten();
|
||||
let format = caps.name("format").map(|m| m.as_str());
|
||||
|
||||
if let Ok(final_time) = final_time.parse::<i64>() {
|
||||
if let (Some(final_time), Some(format)) = (final_time, format) {
|
||||
let dt = NaiveDateTime::from_timestamp(final_time, 0);
|
||||
let now = Utc::now().naive_utc();
|
||||
|
||||
@ -81,13 +81,11 @@ pub fn substitute(string: &str) -> String {
|
||||
|
||||
TIMENOW_REGEX
|
||||
.replace(&new, |caps: &Captures| {
|
||||
let timezone = caps.name("timezone").unwrap().as_str();
|
||||
let timezone = caps.name("timezone").map(|m| m.as_str().parse::<Tz>().ok()).flatten();
|
||||
let format = caps.name("format").map(|m| m.as_str());
|
||||
|
||||
println!("{}", timezone);
|
||||
|
||||
if let Ok(tz) = timezone.parse::<Tz>() {
|
||||
let format = caps.name("format").unwrap().as_str();
|
||||
let now = Utc::now().with_timezone(&tz);
|
||||
if let (Some(timezone), Some(format)) = (timezone, format) {
|
||||
let now = Utc::now().with_timezone(&timezone);
|
||||
|
||||
now.format(format).to_string()
|
||||
} else {
|
||||
@ -122,7 +120,7 @@ impl Embed {
|
||||
pool: impl Executor<'_, Database = Database> + Copy,
|
||||
id: u32,
|
||||
) -> Option<Self> {
|
||||
let mut embed = sqlx::query_as!(
|
||||
match sqlx::query_as!(
|
||||
Self,
|
||||
r#"
|
||||
SELECT
|
||||
@ -142,8 +140,8 @@ impl Embed {
|
||||
)
|
||||
.fetch_one(pool)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
{
|
||||
Ok(mut embed) => {
|
||||
embed.title = substitute(&embed.title);
|
||||
embed.description = substitute(&embed.description);
|
||||
embed.footer = substitute(&embed.footer);
|
||||
@ -160,6 +158,14 @@ impl Embed {
|
||||
}
|
||||
}
|
||||
|
||||
Err(e) => {
|
||||
warn!("Error loading embed from reminder: {:?}", e);
|
||||
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn has_content(&self) -> bool {
|
||||
if self.title.is_empty()
|
||||
&& self.description.is_empty()
|
||||
@ -251,9 +257,9 @@ pub struct Reminder {
|
||||
|
||||
impl Reminder {
|
||||
pub async fn fetch_reminders(pool: impl Executor<'_, Database = Database> + Copy) -> Vec<Self> {
|
||||
sqlx::query_as_unchecked!(
|
||||
match sqlx::query_as!(
|
||||
Reminder,
|
||||
"
|
||||
r#"
|
||||
SELECT
|
||||
reminders.`id` AS id,
|
||||
|
||||
@ -261,20 +267,20 @@ SELECT
|
||||
channels.`webhook_id` AS webhook_id,
|
||||
channels.`webhook_token` AS webhook_token,
|
||||
|
||||
channels.`paused` AS channel_paused,
|
||||
channels.`paused_until` AS channel_paused_until,
|
||||
reminders.`enabled` AS enabled,
|
||||
channels.`paused` AS "channel_paused:_",
|
||||
channels.`paused_until` AS "channel_paused_until:_",
|
||||
reminders.`enabled` AS "enabled:_",
|
||||
|
||||
reminders.`tts` AS tts,
|
||||
reminders.`pin` AS pin,
|
||||
reminders.`tts` AS "tts:_",
|
||||
reminders.`pin` AS "pin:_",
|
||||
reminders.`content` AS content,
|
||||
reminders.`attachment` AS attachment,
|
||||
reminders.`attachment_name` AS attachment_name,
|
||||
|
||||
reminders.`utc_time` AS 'utc_time',
|
||||
reminders.`utc_time` AS "utc_time:_",
|
||||
reminders.`timezone` AS timezone,
|
||||
reminders.`restartable` AS restartable,
|
||||
reminders.`expires` AS expires,
|
||||
reminders.`restartable` AS "restartable:_",
|
||||
reminders.`expires` AS "expires:_",
|
||||
reminders.`interval_seconds` AS 'interval_seconds',
|
||||
reminders.`interval_months` AS 'interval_months',
|
||||
|
||||
@ -288,18 +294,26 @@ ON
|
||||
reminders.channel_id = channels.id
|
||||
WHERE
|
||||
reminders.`utc_time` < NOW()
|
||||
",
|
||||
"#,
|
||||
)
|
||||
.fetch_all(pool)
|
||||
.await
|
||||
.unwrap()
|
||||
{
|
||||
Ok(reminders) => reminders
|
||||
.into_iter()
|
||||
.map(|mut rem| {
|
||||
rem.content = substitute(&rem.content);
|
||||
|
||||
rem
|
||||
})
|
||||
.collect::<Vec<Self>>()
|
||||
.collect::<Vec<Self>>(),
|
||||
|
||||
Err(e) => {
|
||||
warn!("Could not fetch reminders: {:?}", e);
|
||||
|
||||
vec![]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async fn reset_webhook(&self, pool: impl Executor<'_, Database = Database> + Copy) {
|
||||
@ -319,7 +333,7 @@ UPDATE channels SET webhook_id = NULL, webhook_token = NULL WHERE channel = ?
|
||||
let mut updated_reminder_time = self.utc_time;
|
||||
|
||||
if let Some(interval) = self.interval_months {
|
||||
let row = sqlx::query!(
|
||||
match sqlx::query!(
|
||||
// use the second date_add to force return value to datetime
|
||||
"SELECT DATE_ADD(DATE_ADD(?, INTERVAL ? MONTH), INTERVAL 0 SECOND) AS new_time",
|
||||
updated_reminder_time,
|
||||
@ -327,9 +341,25 @@ UPDATE channels SET webhook_id = NULL, webhook_token = NULL WHERE channel = ?
|
||||
)
|
||||
.fetch_one(pool)
|
||||
.await
|
||||
.unwrap();
|
||||
{
|
||||
Ok(row) => match row.new_time {
|
||||
Some(datetime) => {
|
||||
updated_reminder_time = datetime;
|
||||
}
|
||||
None => {
|
||||
warn!("Could not update interval by months: got NULL");
|
||||
|
||||
updated_reminder_time = row.new_time.unwrap();
|
||||
updated_reminder_time += Duration::days(30);
|
||||
}
|
||||
},
|
||||
|
||||
Err(e) => {
|
||||
warn!("Could not update interval by months: {:?}", e);
|
||||
|
||||
// naively fallback to adding 30 days
|
||||
updated_reminder_time += Duration::days(30);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(interval) = self.interval_seconds {
|
||||
@ -538,7 +568,7 @@ UPDATE `channels` SET paused = 0, paused_until = NULL WHERE `channel` = ?
|
||||
error!("Error sending {:?}: {:?}", self, e);
|
||||
|
||||
if let Error::Http(error) = e {
|
||||
if error.status_code() == Some(StatusCode::from_u16(404).unwrap()) {
|
||||
if error.status_code() == Some(StatusCode::NOT_FOUND) {
|
||||
error!("Seeing channel is deleted. Removing reminder");
|
||||
self.force_delete(pool).await;
|
||||
} else {
|
||||
|
@ -71,7 +71,7 @@ pub async fn info(ctx: Context<'_>) -> Result<(), Error> {
|
||||
.send(|m| {
|
||||
m.ephemeral(true).embed(|e| {
|
||||
e.title("Info")
|
||||
.description(format!(
|
||||
.description(
|
||||
"Help: `/help`
|
||||
|
||||
**Welcome to Reminder Bot!**
|
||||
@ -81,7 +81,7 @@ Find me on https://discord.jellywx.com and on https://github.com/JellyWX :)
|
||||
|
||||
Invite the bot: https://invite.reminder-bot.com/
|
||||
Use our dashboard: https://reminder-bot.com/",
|
||||
))
|
||||
)
|
||||
.footer(footer)
|
||||
.color(*THEME_COLOR)
|
||||
})
|
||||
|
@ -1,3 +1,5 @@
|
||||
use std::collections::hash_map::Entry;
|
||||
|
||||
use chrono::offset::Utc;
|
||||
use chrono_tz::{Tz, TZ_VARIANTS};
|
||||
use levenshtein::levenshtein;
|
||||
@ -52,7 +54,7 @@ pub async fn timezone(
|
||||
.description(format!(
|
||||
"Timezone has been set to **{}**. Your current time should be `{}`",
|
||||
timezone,
|
||||
now.format("%H:%M").to_string()
|
||||
now.format("%H:%M")
|
||||
))
|
||||
.color(*THEME_COLOR)
|
||||
})
|
||||
@ -75,10 +77,7 @@ pub async fn timezone(
|
||||
let fields = filtered_tz.iter().map(|tz| {
|
||||
(
|
||||
tz.to_string(),
|
||||
format!(
|
||||
"🕗 `{}`",
|
||||
Utc::now().with_timezone(tz).format("%H:%M").to_string()
|
||||
),
|
||||
format!("🕗 `{}`", Utc::now().with_timezone(tz).format("%H:%M")),
|
||||
true,
|
||||
)
|
||||
});
|
||||
@ -98,11 +97,7 @@ pub async fn timezone(
|
||||
}
|
||||
} else {
|
||||
let popular_timezones_iter = ctx.data().popular_timezones.iter().map(|t| {
|
||||
(
|
||||
t.to_string(),
|
||||
format!("🕗 `{}`", Utc::now().with_timezone(t).format("%H:%M").to_string()),
|
||||
true,
|
||||
)
|
||||
(t.to_string(), format!("🕗 `{}`", Utc::now().with_timezone(t).format("%H:%M")), true)
|
||||
});
|
||||
|
||||
ctx.send(|m| {
|
||||
@ -142,7 +137,7 @@ WHERE
|
||||
)
|
||||
.fetch_all(&ctx.data().database)
|
||||
.await
|
||||
.unwrap_or(vec![])
|
||||
.unwrap_or_default()
|
||||
.iter()
|
||||
.map(|s| s.name.clone())
|
||||
.collect()
|
||||
@ -200,14 +195,11 @@ Please select a unique name for your macro.",
|
||||
let okay = {
|
||||
let mut lock = ctx.data().recording_macros.write().await;
|
||||
|
||||
if lock.contains_key(&(guild_id, ctx.author().id)) {
|
||||
false
|
||||
} else {
|
||||
lock.insert(
|
||||
(guild_id, ctx.author().id),
|
||||
CommandMacro { guild_id, name, description, commands: vec![] },
|
||||
);
|
||||
if let Entry::Vacant(e) = lock.entry((guild_id, ctx.author().id)) {
|
||||
e.insert(CommandMacro { guild_id, name, description, commands: vec![] });
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -9,13 +9,14 @@ use chrono_tz::Tz;
|
||||
use num_integer::Integer;
|
||||
use poise::{
|
||||
serenity::{builder::CreateEmbed, model::channel::Channel},
|
||||
serenity_prelude::{ButtonStyle, ReactionType},
|
||||
CreateReply,
|
||||
};
|
||||
|
||||
use crate::{
|
||||
component_models::{
|
||||
pager::{DelPager, LookPager, Pager},
|
||||
ComponentDataModel, DelSelector,
|
||||
ComponentDataModel, DelSelector, UndoReminder,
|
||||
},
|
||||
consts::{
|
||||
EMBED_DESCRIPTION_MAX_LENGTH, HOUR, MINUTE, REGEX_CHANNEL_USER, SELECT_MAX_ENTRIES,
|
||||
@ -500,8 +501,7 @@ pub async fn start_timer(
|
||||
if count >= 25 {
|
||||
ctx.say("You already have 25 timers. Please delete some timers before creating a new one")
|
||||
.await?;
|
||||
} else {
|
||||
if name.len() <= 32 {
|
||||
} else if name.len() <= 32 {
|
||||
Timer::create(&name, owner, &ctx.data().database).await;
|
||||
|
||||
ctx.say("Created a new timer").await?;
|
||||
@ -512,7 +512,6 @@ pub async fn start_timer(
|
||||
))
|
||||
.await?;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@ -589,8 +588,7 @@ pub async fn remind(
|
||||
};
|
||||
|
||||
let scopes = {
|
||||
let list =
|
||||
channels.map(|arg| parse_mention_list(&arg.to_string())).unwrap_or_default();
|
||||
let list = channels.map(|arg| parse_mention_list(&arg)).unwrap_or_default();
|
||||
|
||||
if list.is_empty() {
|
||||
if ctx.guild_id().is_some() {
|
||||
@ -610,7 +608,7 @@ pub async fn remind(
|
||||
{
|
||||
(
|
||||
parse_duration(repeat)
|
||||
.or_else(|_| parse_duration(&format!("1 {}", repeat.to_string())))
|
||||
.or_else(|_| parse_duration(&format!("1 {}", repeat)))
|
||||
.ok(),
|
||||
{
|
||||
if let Some(arg) = &expires {
|
||||
@ -653,8 +651,33 @@ pub async fn remind(
|
||||
|
||||
let (errors, successes) = builder.build().await;
|
||||
|
||||
let embed = create_response(successes, errors, time);
|
||||
let embed = create_response(&successes, &errors, time);
|
||||
|
||||
if successes.len() == 1 {
|
||||
let reminder = successes.iter().next().map(|(r, _)| r.id).unwrap();
|
||||
let undo_button = ComponentDataModel::UndoReminder(UndoReminder {
|
||||
user_id: ctx.author().id,
|
||||
reminder_id: reminder,
|
||||
});
|
||||
|
||||
ctx.send(|m| {
|
||||
m.embed(|c| {
|
||||
*c = embed;
|
||||
c
|
||||
})
|
||||
.components(|c| {
|
||||
c.create_action_row(|r| {
|
||||
r.create_button(|b| {
|
||||
b.emoji(ReactionType::Unicode("🔕".to_string()))
|
||||
.label("Cancel")
|
||||
.style(ButtonStyle::Danger)
|
||||
.custom_id(undo_button.to_custom_id())
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
.await?;
|
||||
} else {
|
||||
ctx.send(|m| {
|
||||
m.embed(|c| {
|
||||
*c = embed;
|
||||
@ -664,6 +687,7 @@ pub async fn remind(
|
||||
.await?;
|
||||
}
|
||||
}
|
||||
}
|
||||
None => {
|
||||
ctx.say("Time could not be processed").await?;
|
||||
}
|
||||
@ -673,8 +697,8 @@ pub async fn remind(
|
||||
}
|
||||
|
||||
fn create_response(
|
||||
successes: HashSet<ReminderScope>,
|
||||
errors: HashSet<ReminderError>,
|
||||
successes: &HashSet<(Reminder, ReminderScope)>,
|
||||
errors: &HashSet<ReminderError>,
|
||||
time: i64,
|
||||
) -> CreateEmbed {
|
||||
let success_part = match successes.len() {
|
||||
@ -682,7 +706,8 @@ fn create_response(
|
||||
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(", "),
|
||||
locations =
|
||||
successes.iter().map(|(_, l)| l.mention()).collect::<Vec<String>>().join(", "),
|
||||
offset = time
|
||||
),
|
||||
};
|
||||
|
@ -336,7 +336,7 @@ pub fn show_todo_page(
|
||||
opt.create_option(|o| {
|
||||
o.label(format!("Mark {} complete", count + first_num))
|
||||
.value(id)
|
||||
.description(disp.split_once(" ").unwrap_or(("", "")).1)
|
||||
.description(disp.split_once(' ').unwrap_or(("", "")).1)
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -3,14 +3,20 @@ pub(crate) mod pager;
|
||||
use std::io::Cursor;
|
||||
|
||||
use chrono_tz::Tz;
|
||||
use poise::serenity::{
|
||||
use log::warn;
|
||||
use poise::{
|
||||
serenity::{
|
||||
builder::CreateEmbed,
|
||||
client::Context,
|
||||
model::{
|
||||
channel::Channel,
|
||||
interactions::{message_component::MessageComponentInteraction, InteractionResponseType},
|
||||
interactions::{
|
||||
message_component::MessageComponentInteraction, InteractionResponseType,
|
||||
},
|
||||
prelude::InteractionApplicationCommandCallbackDataFlags,
|
||||
},
|
||||
},
|
||||
serenity_prelude as serenity,
|
||||
};
|
||||
use rmp_serde::Serializer;
|
||||
use serde::{Deserialize, Serialize};
|
||||
@ -38,6 +44,7 @@ pub enum ComponentDataModel {
|
||||
DelSelector(DelSelector),
|
||||
TodoSelector(TodoSelector),
|
||||
MacroPager(MacroPager),
|
||||
UndoReminder(UndoReminder),
|
||||
}
|
||||
|
||||
impl ComponentDataModel {
|
||||
@ -334,6 +341,70 @@ WHERE guilds.guild = ?",
|
||||
})
|
||||
.await;
|
||||
}
|
||||
ComponentDataModel::UndoReminder(undo_reminder) => {
|
||||
if component.user.id == undo_reminder.user_id {
|
||||
let reminder =
|
||||
Reminder::from_id(&data.database, undo_reminder.reminder_id).await;
|
||||
|
||||
if let Some(reminder) = reminder {
|
||||
match reminder.delete(&data.database).await {
|
||||
Ok(()) => {
|
||||
let _ = component
|
||||
.create_interaction_response(&ctx, |f| {
|
||||
f.kind(InteractionResponseType::UpdateMessage)
|
||||
.interaction_response_data(|d| {
|
||||
d.embed(|e| {
|
||||
e.title("Reminder Canceled")
|
||||
.description(
|
||||
"This reminder has been canceled.",
|
||||
)
|
||||
.color(*THEME_COLOR)
|
||||
})
|
||||
.components(|c| c)
|
||||
})
|
||||
})
|
||||
.await;
|
||||
}
|
||||
Err(e) => {
|
||||
warn!("Error canceling reminder: {:?}", e);
|
||||
|
||||
let _ = component
|
||||
.create_interaction_response(&ctx, |f| {
|
||||
f.kind(InteractionResponseType::ChannelMessageWithSource)
|
||||
.interaction_response_data(|d| {
|
||||
d.content(
|
||||
"The reminder could not be canceled: it may have already been deleted. Check `/del`!")
|
||||
.ephemeral(true)
|
||||
})
|
||||
})
|
||||
.await;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
let _ = component
|
||||
.create_interaction_response(&ctx, |f| {
|
||||
f.kind(InteractionResponseType::ChannelMessageWithSource)
|
||||
.interaction_response_data(|d| {
|
||||
d.content(
|
||||
"The reminder could not be canceled: it may have already been deleted. Check `/del`!")
|
||||
.ephemeral(true)
|
||||
})
|
||||
})
|
||||
.await;
|
||||
}
|
||||
} else {
|
||||
let _ = component
|
||||
.create_interaction_response(&ctx, |f| {
|
||||
f.kind(InteractionResponseType::ChannelMessageWithSource)
|
||||
.interaction_response_data(|d| {
|
||||
d.content(
|
||||
"Only the user who performed the command can use this button.")
|
||||
.ephemeral(true)
|
||||
})
|
||||
})
|
||||
.await;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -351,3 +422,9 @@ pub struct TodoSelector {
|
||||
pub channel_id: Option<u64>,
|
||||
pub guild_id: Option<u64>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
pub struct UndoReminder {
|
||||
pub user_id: serenity::UserId,
|
||||
pub reminder_id: u32,
|
||||
}
|
||||
|
@ -36,15 +36,11 @@ lazy_static! {
|
||||
);
|
||||
pub static ref CNC_GUILD: Option<u64> =
|
||||
env::var("CNC_GUILD").map(|var| var.parse::<u64>().ok()).ok().flatten();
|
||||
pub static ref MIN_INTERVAL: i64 = env::var("MIN_INTERVAL")
|
||||
.ok()
|
||||
.map(|inner| inner.parse::<i64>().ok())
|
||||
.flatten()
|
||||
.unwrap_or(600);
|
||||
pub static ref MIN_INTERVAL: i64 =
|
||||
env::var("MIN_INTERVAL").ok().and_then(|inner| inner.parse::<i64>().ok()).unwrap_or(600);
|
||||
pub static ref MAX_TIME: i64 = env::var("MAX_TIME")
|
||||
.ok()
|
||||
.map(|inner| inner.parse::<i64>().ok())
|
||||
.flatten()
|
||||
.and_then(|inner| inner.parse::<i64>().ok())
|
||||
.unwrap_or(60 * 60 * 24 * 365 * 50);
|
||||
pub static ref LOCAL_TIMEZONE: String =
|
||||
env::var("LOCAL_TIMEZONE").unwrap_or_else(|_| "UTC".to_string());
|
||||
|
@ -42,7 +42,7 @@ pub async fn listener(
|
||||
};
|
||||
});
|
||||
} else {
|
||||
warn!("Not running postman")
|
||||
warn!("Not running postman");
|
||||
}
|
||||
|
||||
if !run_settings.contains("web") {
|
||||
@ -50,7 +50,7 @@ pub async fn listener(
|
||||
reminder_web::initialize(kill_tx, ctx2, pool2).await.unwrap();
|
||||
});
|
||||
} else {
|
||||
warn!("Not running web")
|
||||
warn!("Not running web");
|
||||
}
|
||||
|
||||
data.is_loop_running.swap(true, Ordering::Relaxed);
|
||||
@ -114,14 +114,13 @@ pub async fn listener(
|
||||
.execute(&data.database)
|
||||
.await;
|
||||
}
|
||||
poise::Event::InteractionCreate { interaction } => match interaction {
|
||||
Interaction::MessageComponent(component) => {
|
||||
poise::Event::InteractionCreate { interaction } => {
|
||||
if let Interaction::MessageComponent(component) = interaction {
|
||||
let component_model = ComponentDataModel::from_custom_id(&component.data.custom_id);
|
||||
|
||||
component_model.act(ctx, data, component).await;
|
||||
}
|
||||
_ => {}
|
||||
},
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
||||
|
15
src/hooks.rs
15
src/hooks.rs
@ -30,19 +30,13 @@ async fn macro_check(ctx: Context<'_>) -> bool {
|
||||
.await;
|
||||
}
|
||||
|
||||
false
|
||||
} else {
|
||||
true
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
true
|
||||
}
|
||||
} else {
|
||||
true
|
||||
}
|
||||
} else {
|
||||
true
|
||||
}
|
||||
|
||||
true
|
||||
}
|
||||
|
||||
async fn check_self_permissions(ctx: Context<'_>) -> bool {
|
||||
@ -56,14 +50,13 @@ async fn check_self_permissions(ctx: Context<'_>) -> bool {
|
||||
let (view_channel, send_messages, embed_links) = ctx
|
||||
.channel_id()
|
||||
.to_channel_cached(&ctx.discord())
|
||||
.map(|c| {
|
||||
.and_then(|c| {
|
||||
if let Channel::Guild(channel) = c {
|
||||
channel.permissions_for_user(&ctx.discord(), user_id).ok()
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.flatten()
|
||||
.map_or((false, false, false), |p| {
|
||||
(p.view_channel(), p.send_messages(), p.embed_links())
|
||||
});
|
||||
|
@ -75,7 +75,7 @@ impl fmt::Display for Error {
|
||||
match self {
|
||||
Error::InvalidCharacter(offset) => write!(f, "invalid character at {}", offset),
|
||||
Error::NumberExpected(offset) => write!(f, "expected number at {}", offset),
|
||||
Error::UnknownUnit { unit, value, .. } if &unit == &"" => {
|
||||
Error::UnknownUnit { unit, value, .. } if unit.is_empty() => {
|
||||
write!(f, "time unit needed, for example {0}sec or {0}ms", value,)
|
||||
}
|
||||
Error::UnknownUnit { unit, .. } => {
|
||||
@ -162,11 +162,11 @@ impl<'a> Parser<'a> {
|
||||
};
|
||||
let mut nsec = self.current.2 + nsec;
|
||||
if nsec > 1_000_000_000 {
|
||||
sec = sec + nsec / 1_000_000_000;
|
||||
sec += nsec / 1_000_000_000;
|
||||
nsec %= 1_000_000_000;
|
||||
}
|
||||
sec = self.current.1 + sec;
|
||||
month = self.current.0 + month;
|
||||
sec += self.current.1;
|
||||
month += self.current.0;
|
||||
|
||||
self.current = (month, sec, nsec);
|
||||
|
||||
|
@ -1,4 +1,5 @@
|
||||
#![feature(int_roundings)]
|
||||
|
||||
#[macro_use]
|
||||
extern crate lazy_static;
|
||||
|
||||
@ -23,7 +24,7 @@ use std::{
|
||||
use chrono_tz::Tz;
|
||||
use dotenv::dotenv;
|
||||
use poise::serenity::model::{
|
||||
gateway::{Activity, GatewayIntents},
|
||||
gateway::GatewayIntents,
|
||||
id::{GuildId, UserId},
|
||||
};
|
||||
use sqlx::{MySql, Pool};
|
||||
@ -52,7 +53,7 @@ pub struct Data {
|
||||
broadcast: Sender<()>,
|
||||
}
|
||||
|
||||
impl std::fmt::Debug for Data {
|
||||
impl Debug for Data {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "Data {{ .. }}")
|
||||
}
|
||||
|
@ -5,11 +5,11 @@ use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::{Context, Data, Error};
|
||||
|
||||
fn default_none<U, E>() -> Option<
|
||||
for<'a> fn(
|
||||
type Func<U, E> = for<'a> fn(
|
||||
poise::ApplicationContext<'a, U, E>,
|
||||
) -> poise::BoxFuture<'a, Result<(), poise::FrameworkError<'a, U, E>>>,
|
||||
> {
|
||||
) -> poise::BoxFuture<'a, Result<(), poise::FrameworkError<'a, U, E>>>;
|
||||
|
||||
fn default_none<U, E>() -> Option<Func<U, E>> {
|
||||
None
|
||||
}
|
||||
|
||||
@ -17,11 +17,7 @@ fn default_none<U, E>() -> Option<
|
||||
pub struct RecordedCommand<U, E> {
|
||||
#[serde(skip)]
|
||||
#[serde(default = "default_none::<U, E>")]
|
||||
pub action: Option<
|
||||
for<'a> fn(
|
||||
poise::ApplicationContext<'a, U, E>,
|
||||
) -> poise::BoxFuture<'a, Result<(), poise::FrameworkError<'a, U, E>>>,
|
||||
>,
|
||||
pub action: Option<Func<U, E>>,
|
||||
pub command_name: String,
|
||||
pub options: Vec<ApplicationCommandInteractionDataOption>,
|
||||
}
|
||||
@ -59,7 +55,7 @@ SELECT * FROM macro WHERE guild_id = (SELECT id FROM guilds WHERE guild = ?) AND
|
||||
.iter()
|
||||
.find(|c| c.identifying_name == recorded_command.command_name);
|
||||
|
||||
recorded_command.action = command.map(|c| c.slash_action).flatten().clone();
|
||||
recorded_command.action = command.map(|c| c.slash_action).flatten();
|
||||
}
|
||||
|
||||
let command_macro = CommandMacro {
|
||||
|
@ -126,7 +126,7 @@ INSERT INTO reminders (
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
Ok(Reminder::from_uid(&self.pool, self.uid).await.unwrap())
|
||||
Ok(Reminder::from_uid(&self.pool, &self.uid).await.unwrap())
|
||||
}
|
||||
}
|
||||
|
||||
@ -207,7 +207,7 @@ impl<'a> MultiReminderBuilder<'a> {
|
||||
self.scopes = scopes;
|
||||
}
|
||||
|
||||
pub async fn build(self) -> (HashSet<ReminderError>, HashSet<ReminderScope>) {
|
||||
pub async fn build(self) -> (HashSet<ReminderError>, HashSet<(Reminder, ReminderScope)>) {
|
||||
let mut errors = HashSet::new();
|
||||
|
||||
let mut ok_locs = HashSet::new();
|
||||
@ -309,8 +309,8 @@ impl<'a> MultiReminderBuilder<'a> {
|
||||
};
|
||||
|
||||
match builder.build().await {
|
||||
Ok(_) => {
|
||||
ok_locs.insert(scope);
|
||||
Ok(r) => {
|
||||
ok_locs.insert((r, scope));
|
||||
}
|
||||
Err(e) => {
|
||||
errors.insert(e);
|
||||
|
@ -4,6 +4,8 @@ pub mod errors;
|
||||
mod helper;
|
||||
pub mod look_flags;
|
||||
|
||||
use std::hash::{Hash, Hasher};
|
||||
|
||||
use chrono::{NaiveDateTime, TimeZone};
|
||||
use chrono_tz::Tz;
|
||||
use poise::{
|
||||
@ -32,11 +34,22 @@ pub struct Reminder {
|
||||
pub set_by: Option<u64>,
|
||||
}
|
||||
|
||||
impl Hash for Reminder {
|
||||
fn hash<H: Hasher>(&self, state: &mut H) {
|
||||
self.uid.hash(state);
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq<Self> for Reminder {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
self.uid == other.uid
|
||||
}
|
||||
}
|
||||
|
||||
impl Eq for Reminder {}
|
||||
|
||||
impl Reminder {
|
||||
pub async fn from_uid(
|
||||
pool: impl Executor<'_, Database = Database>,
|
||||
uid: String,
|
||||
) -> Option<Self> {
|
||||
pub async fn from_uid(pool: impl Executor<'_, Database = Database>, uid: &str) -> Option<Self> {
|
||||
sqlx::query_as_unchecked!(
|
||||
Self,
|
||||
"
|
||||
@ -72,6 +85,42 @@ WHERE
|
||||
.ok()
|
||||
}
|
||||
|
||||
pub async fn from_id(pool: impl Executor<'_, Database = Database>, id: u32) -> Option<Self> {
|
||||
sqlx::query_as_unchecked!(
|
||||
Self,
|
||||
"
|
||||
SELECT
|
||||
reminders.id,
|
||||
reminders.uid,
|
||||
channels.channel,
|
||||
reminders.utc_time,
|
||||
reminders.interval_seconds,
|
||||
reminders.interval_months,
|
||||
reminders.expires,
|
||||
reminders.enabled,
|
||||
reminders.content,
|
||||
reminders.embed_description,
|
||||
users.user AS set_by
|
||||
FROM
|
||||
reminders
|
||||
INNER JOIN
|
||||
channels
|
||||
ON
|
||||
reminders.channel_id = channels.id
|
||||
LEFT JOIN
|
||||
users
|
||||
ON
|
||||
reminders.set_by = users.id
|
||||
WHERE
|
||||
reminders.id = ?
|
||||
",
|
||||
id
|
||||
)
|
||||
.fetch_one(pool)
|
||||
.await
|
||||
.ok()
|
||||
}
|
||||
|
||||
pub async fn from_channel<C: Into<ChannelId>>(
|
||||
pool: impl Executor<'_, Database = Database>,
|
||||
channel_id: C,
|
||||
@ -240,6 +289,13 @@ WHERE
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
pub async fn delete(
|
||||
&self,
|
||||
db: impl Executor<'_, Database = Database>,
|
||||
) -> Result<(), sqlx::Error> {
|
||||
sqlx::query!("DELETE FROM reminders WHERE uid = ?", self.uid).execute(db).await.map(|_| ())
|
||||
}
|
||||
|
||||
pub fn display_content(&self) -> &str {
|
||||
if self.content.is_empty() {
|
||||
&self.embed_description
|
||||
@ -254,10 +310,7 @@ WHERE
|
||||
count + 1,
|
||||
self.display_content(),
|
||||
self.channel,
|
||||
timezone
|
||||
.timestamp(self.utc_time.timestamp(), 0)
|
||||
.format("%Y-%m-%d %H:%M:%S")
|
||||
.to_string()
|
||||
timezone.timestamp(self.utc_time.timestamp(), 0).format("%Y-%m-%d %H:%M:%S")
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -211,14 +211,12 @@ pub async fn natural_parser(time: &str, timezone: &str) -> Option<i64> {
|
||||
.output()
|
||||
.await
|
||||
.ok()
|
||||
.map(|inner| {
|
||||
.and_then(|inner| {
|
||||
if inner.status.success() {
|
||||
Some(from_utf8(&*inner.stdout).unwrap().parse::<i64>().unwrap())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.flatten()
|
||||
.map(|inner| if inner < 0 { None } else { Some(inner) })
|
||||
.flatten()
|
||||
.and_then(|inner| if inner < 0 { None } else { Some(inner) })
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user