diff --git a/src/commands/moderation_cmds.rs b/src/commands/moderation_cmds.rs index 207209b..b543ad9 100644 --- a/src/commands/moderation_cmds.rs +++ b/src/commands/moderation_cmds.rs @@ -74,18 +74,16 @@ async fn blacklist(ctx: &Context, msg: &Message, args: String) { .say(&ctx, lm.get(&language, "blacklist/added_from")) .await; } + } else if local { + let _ = msg + .channel_id + .say(&ctx, lm.get(&language, "blacklist/removed")) + .await; } else { - if local { - let _ = msg - .channel_id - .say(&ctx, lm.get(&language, "blacklist/removed")) - .await; - } else { - let _ = msg - .channel_id - .say(&ctx, lm.get(&language, "blacklist/removed_from")) - .await; - } + let _ = msg + .channel_id + .say(&ctx, lm.get(&language, "blacklist/removed_from")) + .await; } } diff --git a/src/commands/reminder_cmds.rs b/src/commands/reminder_cmds.rs index e3c1ec5..fd17838 100644 --- a/src/commands/reminder_cmds.rs +++ b/src/commands/reminder_cmds.rs @@ -18,14 +18,17 @@ use serenity::{ use crate::{ check_subscription_on_message, command_help, consts::{ - CHARACTERS, DAY, HOUR, MAX_TIME, MINUTE, MIN_INTERVAL, REGEX_CHANNEL, REGEX_CHANNEL_USER, - REGEX_CONTENT_SUBSTITUTION, REGEX_NATURAL_COMMAND_1, REGEX_NATURAL_COMMAND_2, - REGEX_REMIND_COMMAND, THEME_COLOR, + CHARACTERS, MAX_TIME, MIN_INTERVAL, REGEX_CHANNEL_USER, REGEX_CONTENT_SUBSTITUTION, + REGEX_NATURAL_COMMAND_1, REGEX_NATURAL_COMMAND_2, REGEX_REMIND_COMMAND, THEME_COLOR, }, framework::SendIterator, get_ctx_data, models::{ - channel_data::ChannelData, guild_data::GuildData, timer::Timer, user_data::UserData, + channel_data::ChannelData, + guild_data::GuildData, + reminder::{LookFlags, Reminder}, + timer::Timer, + user_data::UserData, CtxGuildData, }, time_parser::{natural_parser, TimeParser}, @@ -53,25 +56,6 @@ use regex::Captures; use ring::hmac; -fn longhand_displacement(seconds: u64) -> String { - let (days, seconds) = seconds.div_rem(&DAY); - let (hours, seconds) = seconds.div_rem(&HOUR); - let (minutes, seconds) = seconds.div_rem(&MINUTE); - - let mut sections = vec![]; - - for (var, name) in [days, hours, minutes, seconds] - .iter() - .zip(["days", "hours", "minutes", "seconds"].iter()) - { - if *var > 0 { - sections.push(format!("{} {}", var, name)); - } - } - - sections.join(", ") -} - fn generate_signed_payload(reminder_id: u32, member_id: u64) -> String { let s_key = hmac::Key::new( hmac::HMAC_SHA256, @@ -307,115 +291,6 @@ async fn nudge(ctx: &Context, msg: &Message, args: String) { } } -enum TimeDisplayType { - Absolute, - Relative, -} - -struct LookFlags { - pub limit: u16, - pub show_disabled: bool, - pub channel_id: Option, - time_display: TimeDisplayType, -} - -impl Default for LookFlags { - fn default() -> Self { - Self { - limit: u16::MAX, - show_disabled: true, - channel_id: None, - time_display: TimeDisplayType::Relative, - } - } -} - -impl LookFlags { - fn from_string(args: &str) -> Self { - let mut new_flags: Self = Default::default(); - - for arg in args.split(' ') { - match arg { - "enabled" => { - new_flags.show_disabled = false; - } - - "time" => { - new_flags.time_display = TimeDisplayType::Absolute; - } - - param => { - if let Ok(val) = param.parse::() { - new_flags.limit = val; - } else { - if let Some(channel) = REGEX_CHANNEL - .captures(&arg) - .map(|cap| cap.get(1)) - .flatten() - .map(|c| c.as_str().parse::().unwrap()) - { - new_flags.channel_id = Some(channel); - } - } - } - } - } - - new_flags - } -} - -struct LookReminder { - id: u32, - time: NaiveDateTime, - interval: Option, - channel: u64, - content: String, - description: String, - set_by: Option, -} - -impl LookReminder { - fn display_content(&self) -> String { - if self.content.len() > 0 { - self.content.clone() - } else { - self.description.clone() - } - } - - fn display(&self, flags: &LookFlags, inter: &str) -> String { - let time_display = match flags.time_display { - TimeDisplayType::Absolute => format!("", self.time.timestamp()), - - TimeDisplayType::Relative => format!("", self.time.timestamp()), - }; - - if let Some(interval) = self.interval { - format!( - "'{}' *{}* **{}**, repeating every **{}** (set by {})", - self.display_content(), - &inter, - time_display, - longhand_displacement(interval as u64), - self.set_by - .map(|i| format!("<@{}>", i)) - .unwrap_or_else(|| "unknown".to_string()) - ) - } else { - format!( - "'{}' *{}* **{}** (set by {})", - self.display_content(), - &inter, - time_display, - self.set_by - .map(|i| format!("<@{}>", i)) - .unwrap_or_else(|| "unknown".to_string()) - ) - } - } -} - #[command("look")] #[permission_level(Managed)] async fn look(ctx: &Context, msg: &Message, args: String) { @@ -425,58 +300,19 @@ async fn look(ctx: &Context, msg: &Message, args: String) { let flags = LookFlags::from_string(&args); - let enabled = if flags.show_disabled { "0,1" } else { "1" }; - let channel_opt = msg.channel_id.to_channel_cached(&ctx).await; let channel_id = if let Some(Channel::Guild(channel)) = channel_opt { if Some(channel.guild_id) == msg.guild_id { - flags - .channel_id - .unwrap_or_else(|| msg.channel_id.as_u64().to_owned()) + flags.channel_id.unwrap_or(msg.channel_id) } else { - msg.channel_id.as_u64().to_owned() + msg.channel_id } } else { - msg.channel_id.as_u64().to_owned() + msg.channel_id }; - let reminders = sqlx::query_as!( - LookReminder, - " -SELECT - reminders.id, - reminders.utc_time AS time, - reminders.interval, - channels.channel, - reminders.content, - reminders.embed_description AS 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 - channels.channel = ? AND - FIND_IN_SET(reminders.enabled, ?) -ORDER BY - reminders.utc_time -LIMIT - ? - ", - channel_id, - enabled, - flags.limit - ) - .fetch_all(&pool) - .await - .unwrap(); + let reminders = Reminder::from_channel(ctx, channel_id, &flags).await; if reminders.is_empty() { let _ = msg @@ -506,110 +342,10 @@ async fn delete(ctx: &Context, msg: &Message, _args: String) { .say(&ctx, lm.get(&user_data.language, "del/listing")) .await; - let reminders = if let Some(guild_id) = msg.guild_id { - let guild_opt = guild_id.to_guild_cached(&ctx).await; - - if let Some(guild) = guild_opt { - let channels = guild - .channels - .keys() - .into_iter() - .map(|k| k.as_u64().to_string()) - .collect::>() - .join(","); - - sqlx::query_as_unchecked!( - LookReminder, - " -SELECT - reminders.id, - reminders.utc_time AS time, - reminders.interval, - channels.channel, - reminders.content, - reminders.embed_description AS description, - users.user AS set_by -FROM - reminders -LEFT JOIN - channels -ON - channels.id = reminders.channel_id -LEFT JOIN - users -ON - reminders.set_by = users.id -WHERE - FIND_IN_SET(channels.channel, ?) - ", - channels - ) - .fetch_all(&pool) - .await - } else { - sqlx::query_as_unchecked!( - LookReminder, - " -SELECT - reminders.id, - reminders.utc_time AS time, - reminders.interval, - channels.channel, - reminders.content, - reminders.embed_description AS description, - users.user AS set_by -FROM - reminders -LEFT JOIN - channels -ON - channels.id = reminders.channel_id -LEFT JOIN - users -ON - reminders.set_by = users.id -WHERE - channels.guild_id = (SELECT id FROM guilds WHERE guild = ?) - ", - guild_id.as_u64() - ) - .fetch_all(&pool) - .await - } - } else { - sqlx::query_as!( - LookReminder, - " -SELECT - reminders.id, - reminders.utc_time AS time, - reminders.interval, - channels.channel, - reminders.content, - reminders.embed_description AS description, - users.user AS set_by -FROM - reminders -INNER JOIN - channels -ON - channels.id = reminders.channel_id -LEFT JOIN - users -ON - reminders.set_by = users.id -WHERE - channels.channel = ? - ", - msg.channel_id.as_u64() - ) - .fetch_all(&pool) - .await - } - .unwrap(); - let mut reminder_ids: Vec = vec![]; + let reminders = Reminder::from_guild(ctx, msg.guild_id, msg.author.id).await; + let enumerated_reminders = reminders.iter().enumerate().map(|(count, reminder)| { reminder_ids.push(reminder.id); @@ -618,7 +354,7 @@ WHERE count + 1, reminder.display_content(), reminder.channel, - reminder.time.timestamp() + reminder.utc_time.timestamp() ) }); @@ -983,7 +719,7 @@ impl Content { Ok(Self { content: content.to_string(), tts: false, - attachment: Some(attachment_bytes.clone()), + attachment: Some(attachment_bytes), attachment_name: Some(attachment.filename.clone()), }) } else { @@ -1186,7 +922,7 @@ async fn remind_command(ctx: &Context, msg: &Message, args: String, command: Rem Some(captures) => { let parsed = parse_mention_list(captures.name("mentions").unwrap().as_str()); - let scopes = if parsed.len() == 0 { + let scopes = if parsed.is_empty() { vec![ReminderScope::Channel(msg.channel_id.into())] } else { parsed @@ -1236,7 +972,7 @@ async fn remind_command(ctx: &Context, msg: &Message, args: String, command: Rem msg.guild_id, &scope, &time_parser, - expires_parser.as_ref().clone(), + expires_parser.as_ref(), interval, &mut content, ) @@ -1455,7 +1191,7 @@ async fn natural(ctx: &Context, msg: &Message, args: String) { &scope, timestamp, expires, - interval.clone(), + interval, &mut content, ) .await; diff --git a/src/consts.rs b/src/consts.rs index 6993032..926b560 100644 --- a/src/consts.rs +++ b/src/consts.rs @@ -51,7 +51,7 @@ lazy_static! { .split(',') .filter_map(|item| { item.parse::().ok() }) .collect::>()) - .unwrap_or_else(|_| vec![]) + .unwrap_or_else(|_| Vec::new()) ); pub static ref CNC_GUILD: Option = env::var("CNC_GUILD") diff --git a/src/framework.rs b/src/framework.rs index de0b2ed..b41f6ac 100644 --- a/src/framework.rs +++ b/src/framework.rs @@ -240,7 +240,7 @@ impl RegexFramework { let mut command_names_vec = self.commands.keys().map(|k| &k[..]).collect::>(); - command_names_vec.sort_unstable_by(|a, b| b.len().cmp(&a.len())); + command_names_vec.sort_unstable_by_key(|a| a.len()); command_names = command_names_vec.join("|"); } @@ -276,7 +276,7 @@ impl RegexFramework { }) .collect::>(); - command_names_vec.sort_unstable_by(|a, b| b.len().cmp(&a.len())); + command_names_vec.sort_unstable_by_key(|a| a.len()); dm_command_names = command_names_vec.join("|"); } @@ -399,12 +399,14 @@ impl Framework for RegexFramework { { let guild_id = guild.id.as_u64().to_owned(); - GuildData::from_guild(guild, &pool).await.expect( - &format!( + GuildData::from_guild(guild, &pool) + .await + .unwrap_or_else(|_| { + panic!( "Failed to create new guild object for {}", guild_id - ), - ); + ) + }); } if msg.id == MessageId(0) diff --git a/src/language_manager.rs b/src/language_manager.rs index 94c73bf..bd90d4a 100644 --- a/src/language_manager.rs +++ b/src/language_manager.rs @@ -14,7 +14,7 @@ pub struct LanguageManager { impl LanguageManager { pub fn from_compiled(content: &'static str) -> Result> { - let new: Self = from_str(content.as_ref())?; + let new: Self = from_str(content)?; Ok(new) } @@ -23,13 +23,13 @@ impl LanguageManager { self.strings .get(language) .map(|sm| sm.get(name)) - .expect(&format!(r#"Language does not exist: "{}""#, language)) + .unwrap_or_else(|| panic!(r#"Language does not exist: "{}""#, language)) .unwrap_or_else(|| { self.strings .get(&*LOCAL_LANGUAGE) .map(|sm| { sm.get(name) - .expect(&format!(r#"String does not exist: "{}""#, name)) + .unwrap_or_else(|| panic!(r#"String does not exist: "{}""#, name)) }) .expect("LOCAL_LANGUAGE is not available") }) diff --git a/src/main.rs b/src/main.rs index 2bd290a..0887754 100644 --- a/src/main.rs +++ b/src/main.rs @@ -178,10 +178,11 @@ DELETE FROM channels WHERE channel = ? .cloned() .expect("Could not get SQLPool from data"); - GuildData::from_guild(guild, &pool).await.expect(&format!( - "Failed to create new guild object for {}", - guild_id - )); + GuildData::from_guild(guild, &pool) + .await + .unwrap_or_else(|_| { + panic!("Failed to create new guild object for {}", guild_id) + }); } if let Ok(token) = env::var("DISCORDBOTS_TOKEN") { @@ -229,7 +230,12 @@ DELETE FROM channels WHERE channel = ? } } - async fn guild_delete(&self, ctx: Context, guild: GuildUnavailable, _guild: Option) { + async fn guild_delete( + &self, + ctx: Context, + deleted_guild: GuildUnavailable, + _guild: Option, + ) { let pool = ctx .data .read() @@ -245,13 +251,13 @@ DELETE FROM channels WHERE channel = ? .get::() .cloned() .unwrap(); - guild_data_cache.remove(&guild.id); + guild_data_cache.remove(&deleted_guild.id); sqlx::query!( " DELETE FROM guilds WHERE guild = ? ", - guild.id.as_u64() + deleted_guild.id.as_u64() ) .execute(&pool) .await diff --git a/src/models/mod.rs b/src/models/mod.rs index 384dd7f..e5decbf 100644 --- a/src/models/mod.rs +++ b/src/models/mod.rs @@ -1,5 +1,6 @@ pub mod channel_data; pub mod guild_data; +pub mod reminder; pub mod timer; pub mod user_data; diff --git a/src/models/reminder.rs b/src/models/reminder.rs new file mode 100644 index 0000000..4e3ab9f --- /dev/null +++ b/src/models/reminder.rs @@ -0,0 +1,308 @@ +use serenity::{ + client::Context, + model::id::{ChannelId, GuildId, UserId}, +}; + +use chrono::NaiveDateTime; + +use crate::{ + consts::{DAY, HOUR, MINUTE, REGEX_CHANNEL}, + SQLPool, +}; + +use num_integer::Integer; + +fn longhand_displacement(seconds: u64) -> String { + let (days, seconds) = seconds.div_rem(&DAY); + let (hours, seconds) = seconds.div_rem(&HOUR); + let (minutes, seconds) = seconds.div_rem(&MINUTE); + + let mut sections = vec![]; + + for (var, name) in [days, hours, minutes, seconds] + .iter() + .zip(["days", "hours", "minutes", "seconds"].iter()) + { + if *var > 0 { + sections.push(format!("{} {}", var, name)); + } + } + + sections.join(", ") +} + +pub struct Reminder { + pub id: u32, + pub uid: String, + pub channel: u64, + pub utc_time: NaiveDateTime, + pub interval: Option, + pub expires: NaiveDateTime, + pub enabled: bool, + pub content: String, + pub embed_description: String, + pub set_by: Option, +} + +impl Reminder { + pub async fn from_channel>( + ctx: &Context, + channel_id: C, + flags: &LookFlags, + ) -> Vec { + let pool = ctx.data.read().await.get::().cloned().unwrap(); + + let enabled = if flags.show_disabled { "0,1" } else { "1" }; + let channel_id = channel_id.into(); + + sqlx::query_as_unchecked!( + Self, + " +SELECT + reminders.id, + reminders.uid, + channels.channel, + reminders.utc_time, + reminders.interval, + 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 + channels.channel = ? AND + FIND_IN_SET(reminders.enabled, ?) +ORDER BY + reminders.utc_time +LIMIT + ? + ", + channel_id.as_u64(), + enabled, + flags.limit + ) + .fetch_all(&pool) + .await + .unwrap() + } + + pub async fn from_guild(ctx: &Context, guild_id: Option, user: UserId) -> Vec { + let pool = ctx.data.read().await.get::().cloned().unwrap(); + + if let Some(guild_id) = guild_id { + let guild_opt = guild_id.to_guild_cached(&ctx).await; + + if let Some(guild) = guild_opt { + let channels = guild + .channels + .keys() + .into_iter() + .map(|k| k.as_u64().to_string()) + .collect::>() + .join(","); + + sqlx::query_as_unchecked!( + Self, + " +SELECT + reminders.id, + reminders.uid, + channels.channel, + reminders.utc_time, + reminders.interval, + reminders.expires, + reminders.enabled, + reminders.content, + reminders.embed_description, + users.user AS set_by +FROM + reminders +LEFT JOIN + channels +ON + channels.id = reminders.channel_id +LEFT JOIN + users +ON + reminders.set_by = users.id +WHERE + FIND_IN_SET(channels.channel, ?) + ", + channels + ) + .fetch_all(&pool) + .await + } else { + sqlx::query_as_unchecked!( + Self, + " +SELECT + reminders.id, + reminders.uid, + channels.channel, + reminders.utc_time, + reminders.interval, + reminders.expires, + reminders.enabled, + reminders.content, + reminders.embed_description, + users.user AS set_by +FROM + reminders +LEFT JOIN + channels +ON + channels.id = reminders.channel_id +LEFT JOIN + users +ON + reminders.set_by = users.id +WHERE + channels.guild_id = (SELECT id FROM guilds WHERE guild = ?) + ", + guild_id.as_u64() + ) + .fetch_all(&pool) + .await + } + } else { + sqlx::query_as_unchecked!( + Self, + " +SELECT + reminders.id, + reminders.uid, + channels.channel, + reminders.utc_time, + reminders.interval, + reminders.expires, + reminders.enabled, + reminders.content, + reminders.embed_description, + users.user AS set_by +FROM + reminders +INNER JOIN + channels +ON + channels.id = reminders.channel_id +LEFT JOIN + users +ON + reminders.set_by = users.id +WHERE + channels.id = (SELECT dm_channel FROM users WHERE user = ?) + ", + user.as_u64() + ) + .fetch_all(&pool) + .await + } + .unwrap() + } + + pub fn display_content(&self) -> &str { + if self.content.is_empty() { + &self.embed_description + } else { + &self.content + } + } + + pub fn display(&self, flags: &LookFlags, inter: &str) -> String { + let time_display = match flags.time_display { + TimeDisplayType::Absolute => format!("", self.utc_time.timestamp()), + + TimeDisplayType::Relative => format!("", self.utc_time.timestamp()), + }; + + if let Some(interval) = self.interval { + format!( + "'{}' *{}* **{}**, repeating every **{}** (set by {})", + self.display_content(), + &inter, + time_display, + longhand_displacement(interval as u64), + self.set_by + .map(|i| format!("<@{}>", i)) + .unwrap_or_else(|| "unknown".to_string()) + ) + } else { + format!( + "'{}' *{}* **{}** (set by {})", + self.display_content(), + &inter, + time_display, + self.set_by + .map(|i| format!("<@{}>", i)) + .unwrap_or_else(|| "unknown".to_string()) + ) + } + } +} + +enum TimeDisplayType { + Absolute, + Relative, +} + +pub struct LookFlags { + pub limit: u16, + pub show_disabled: bool, + pub channel_id: Option, + time_display: TimeDisplayType, +} + +impl Default for LookFlags { + fn default() -> Self { + Self { + limit: u16::MAX, + show_disabled: true, + channel_id: None, + time_display: TimeDisplayType::Relative, + } + } +} + +impl LookFlags { + pub fn from_string(args: &str) -> Self { + let mut new_flags: Self = Default::default(); + + for arg in args.split(' ') { + match arg { + "enabled" => { + new_flags.show_disabled = false; + } + + "time" => { + new_flags.time_display = TimeDisplayType::Absolute; + } + + param => { + if let Ok(val) = param.parse::() { + new_flags.limit = val; + } else if let Some(channel) = REGEX_CHANNEL + .captures(&arg) + .map(|cap| cap.get(1)) + .flatten() + .map(|c| c.as_str().parse::().unwrap()) + { + new_flags.channel_id = Some(ChannelId(channel)); + } + } + } + } + + new_flags + } +} diff --git a/src/time_parser.rs b/src/time_parser.rs index 821022c..fabba8e 100644 --- a/src/time_parser.rs +++ b/src/time_parser.rs @@ -112,7 +112,7 @@ impl TimeParser { DateTime::with_second, ]) { time = setter(&time, t.parse().map_err(|_| InvalidTime::ParseErrorHMS)?) - .map_or_else(|| Err(InvalidTime::ParseErrorHMS), |inner| Ok(inner))?; + .map_or_else(|| Err(InvalidTime::ParseErrorHMS), Ok)?; } if let Some(dmy) = segments.next() { @@ -128,7 +128,7 @@ impl TimeParser { { if let Some(t) = t { time = setter(&time, t.parse().map_err(|_| InvalidTime::ParseErrorDMY)?) - .map_or_else(|| Err(InvalidTime::ParseErrorDMY), |inner| Ok(inner))?; + .map_or_else(|| Err(InvalidTime::ParseErrorDMY), Ok)?; } } @@ -136,7 +136,7 @@ impl TimeParser { if year.len() == 4 { time = time .with_year(year.parse().map_err(|_| InvalidTime::ParseErrorDMY)?) - .map_or_else(|| Err(InvalidTime::ParseErrorDMY), |inner| Ok(inner))?; + .map_or_else(|| Err(InvalidTime::ParseErrorDMY), Ok)?; } else if year.len() == 2 { time = time .with_year( @@ -144,9 +144,9 @@ impl TimeParser { .parse() .map_err(|_| InvalidTime::ParseErrorDMY)?, ) - .map_or_else(|| Err(InvalidTime::ParseErrorDMY), |inner| Ok(inner))?; + .map_or_else(|| Err(InvalidTime::ParseErrorDMY), Ok)?; } else { - Err(InvalidTime::ParseErrorDMY)?; + return Err(InvalidTime::ParseErrorDMY); } } } @@ -157,10 +157,10 @@ impl TimeParser { fn process_displacement(&self) -> Result { let mut current_buffer = "0".to_string(); - let mut seconds = 0 as i64; - let mut minutes = 0 as i64; - let mut hours = 0 as i64; - let mut days = 0 as i64; + let mut seconds = 0_i64; + let mut minutes = 0_i64; + let mut hours = 0_i64; + let mut days = 0_i64; for character in self.time_string.chars() { match character { @@ -205,7 +205,7 @@ impl TimeParser { } } -pub(crate) async fn natural_parser(time: &str, timezone: &str) -> Option { +pub async fn natural_parser(time: &str, timezone: &str) -> Option { Command::new(&*PYTHON_LOCATION) .arg("-c") .arg(include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/dp.py")))