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