diff options
Diffstat (limited to 'src')
| -rw-r--r-- | src/api/guzzle.rs | 10 | ||||
| -rw-r--r-- | src/api/shiggy.rs | 10 | ||||
| -rw-r--r-- | src/commands/moderation/config.rs | 5 | ||||
| -rw-r--r-- | src/commands/optional/copypasta.rs | 8 | ||||
| -rw-r--r-- | src/commands/optional/teawiespam.rs | 2 | ||||
| -rw-r--r-- | src/handlers/error.rs | 4 | ||||
| -rw-r--r-- | src/handlers/event/guild.rs | 14 | ||||
| -rw-r--r-- | src/handlers/event/mod.rs | 11 | ||||
| -rw-r--r-- | src/handlers/event/pinboard.rs | 6 | ||||
| -rw-r--r-- | src/handlers/event/reactboard.rs | 45 | ||||
| -rw-r--r-- | src/main.rs | 3 | ||||
| -rw-r--r-- | src/storage/mod.rs | 126 | ||||
| -rw-r--r-- | src/storage/reactboard.rs | 14 |
13 files changed, 135 insertions, 123 deletions
diff --git a/src/api/guzzle.rs b/src/api/guzzle.rs index 83c159e..0533c8b 100644 --- a/src/api/guzzle.rs +++ b/src/api/guzzle.rs @@ -18,7 +18,7 @@ pub async fn get_random_teawie() -> Result<String> { .get(format!("{GUZZLE}{RANDOM_TEAWIE}")) .build()?; - info!("making request to {}", req.url()); + debug!("making request to {}", req.url()); let resp = REQWEST_CLIENT.execute(req).await?; let status = resp.status(); @@ -26,12 +26,6 @@ pub async fn get_random_teawie() -> Result<String> { let data = resp.json::<GuzzleResponse>().await?; Ok(data.url) } else { - error!( - "couldn't fetch random teawie from {}! {}", - resp.url(), - status - ); - - Err(eyre!("failed to get random teawie with {status}")) + Err(eyre!("Failed to get random Teawie with {status}")) } } diff --git a/src/api/shiggy.rs b/src/api/shiggy.rs index 7a582ee..1fe4b49 100644 --- a/src/api/shiggy.rs +++ b/src/api/shiggy.rs @@ -18,7 +18,7 @@ pub async fn get_random_shiggy() -> Result<String> { .get(format!("{SHIGGY}{RANDOM_SHIGGY}")) .build()?; - info!("making request to {}", req.url()); + debug!("Making request to {}", req.url()); let resp = REQWEST_CLIENT.execute(req).await?; let status = resp.status(); @@ -26,12 +26,6 @@ pub async fn get_random_shiggy() -> Result<String> { let data = resp.json::<SafebooruResponse>().await?; Ok(data.file_url) } else { - error!( - "couldn't fetch random teawie from {}! {}", - resp.url(), - status - ); - - Err(eyre!("failed to get random teawie with {status}")) + Err(eyre!("Failed to get random shiggy with {status}")) } } diff --git a/src/commands/moderation/config.rs b/src/commands/moderation/config.rs index 967e585..64cdb83 100644 --- a/src/commands/moderation/config.rs +++ b/src/commands/moderation/config.rs @@ -1,8 +1,7 @@ use std::str::FromStr; use crate::{storage, Context}; -use settings::{Settings, SettingsProperties}; -use storage::settings; +use storage::{Settings, SettingsProperties}; use color_eyre::eyre::{eyre, Result}; use log::*; @@ -125,7 +124,7 @@ pub async fn set( if previous_settings != settings { debug!("Updating settings key for {gid}"); - storage.create_settings_key(settings).await?; + storage.create_guild_settings(settings).await?; ctx.reply("Configuration updated!").await?; } else { debug!("Not updating settings key for {gid} since no changes were made"); diff --git a/src/commands/optional/copypasta.rs b/src/commands/optional/copypasta.rs index 289a936..54b6194 100644 --- a/src/commands/optional/copypasta.rs +++ b/src/commands/optional/copypasta.rs @@ -41,13 +41,13 @@ fn get_copypasta(name: Copypastas) -> Result<String> { let name = file .path() .file_stem() - .ok_or_else(|| eyre!("couldn't get file stem from {file:#?}"))? + .ok_or_else(|| eyre!("Couldn't get file stem from {file:#?}"))? .to_str() - .ok_or_else(|| eyre!("couldn't convert file stem to str!"))?; + .ok_or_else(|| eyre!("Couldn't convert file stem to str!"))?; let contents = file .contents_utf8() - .ok_or_else(|| eyre!("couldnt get contents from copypasta!"))?; + .ok_or_else(|| eyre!("Couldnt get contents from copypasta!"))?; // refer to files by their name w/o extension files.insert(name, contents); @@ -56,7 +56,7 @@ fn get_copypasta(name: Copypastas) -> Result<String> { if files.contains_key(name.as_str()) { Ok(files[name.as_str()].to_string()) } else { - Err(eyre!("couldnt find copypasta {name}!")) + Err(eyre!("Couldnt find copypasta {name}!")) } } diff --git a/src/commands/optional/teawiespam.rs b/src/commands/optional/teawiespam.rs index bb8f32d..a876d09 100644 --- a/src/commands/optional/teawiespam.rs +++ b/src/commands/optional/teawiespam.rs @@ -6,8 +6,6 @@ use log::*; /// teawie will spam you. #[poise::command(slash_command, prefix_command)] pub async fn teawiespam(ctx: Context<'_>) -> Result<()> { - debug!("Running teawiespam command"); - let gid = ctx.guild_id().unwrap_or_default(); let settings = ctx.data().storage.get_guild_settings(&gid).await?; diff --git a/src/handlers/error.rs b/src/handlers/error.rs index c6cefa2..bddd4f4 100644 --- a/src/handlers/error.rs +++ b/src/handlers/error.rs @@ -18,7 +18,7 @@ pub async fn handle(error: poise::FrameworkError<'_, Data, Report>) { } FrameworkError::Command { error, ctx } => { - error!("Error in command {}:\n{error:?}", ctx.command().name); + error!("Error in command {}:\n{error}", ctx.command().name); ctx.send(|c| { c.embed(|e| { e.title("Something went wrong!") @@ -37,7 +37,7 @@ pub async fn handle(error: poise::FrameworkError<'_, Data, Report>) { event, framework: _, } => { - error!("Error while handling event {}:\n{error:?}", event.name()); + error!("Error while handling event {}:\n{error}", event.name()); } error => { diff --git a/src/handlers/event/guild.rs b/src/handlers/event/guild.rs index 3473276..4583688 100644 --- a/src/handlers/event/guild.rs +++ b/src/handlers/event/guild.rs @@ -3,14 +3,12 @@ use log::*; use poise::serenity_prelude::{Guild, UnavailableGuild}; use crate::{storage, Data}; -use storage::settings::Settings; -use storage::Storage; +use storage::Settings; pub async fn handle_create(guild: &Guild, _is_new: &bool, data: &Data) -> Result<()> { let storage = &data.storage; - let key = Storage::format_settings_key(guild.id); - if storage.key_exists(&key).await? { + if storage.guild_settings_exist(&guild.id).await? { debug!("Not recreating settings key for {}", guild.id); return Ok(()); } @@ -21,14 +19,14 @@ pub async fn handle_create(guild: &Guild, _is_new: &bool, data: &Data) -> Result ..Default::default() }; - warn!("Creating new settings key {key}:\n{settings:#?}"); - storage.create_settings_key(settings).await?; + warn!("Creating new settings key for {}:\n{settings:#?}", guild.id); + storage.create_guild_settings(settings).await?; Ok(()) } pub async fn handle_delete(guild: &UnavailableGuild, data: &Data) -> Result<()> { - let key = Storage::format_settings_key(guild.id); - data.storage.delete_key(&key).await?; + data.storage.delete_guild_settings(&guild.id).await?; + Ok(()) } diff --git a/src/handlers/event/mod.rs b/src/handlers/event/mod.rs index 6b1fc9b..5abbfb4 100644 --- a/src/handlers/event/mod.rs +++ b/src/handlers/event/mod.rs @@ -1,4 +1,3 @@ -use crate::storage::{ReactBoardInfo, REACT_BOARD_KEY}; use crate::Data; use color_eyre::eyre::{Report, Result}; @@ -20,16 +19,6 @@ pub async fn handle( match event { Event::Ready { data_about_bot } => { info!("Logged in as {}!", data_about_bot.user.name); - - // make sure react board is setup - let storage = &data.storage; - if !storage.key_exists(REACT_BOARD_KEY).await? { - warn!("Creating new ReactBoardInfo key {REACT_BOARD_KEY}"); - - storage - .create_reactboard_info_key(ReactBoardInfo::default()) - .await?; - } } Event::Message { new_message } => { diff --git a/src/handlers/event/pinboard.rs b/src/handlers/event/pinboard.rs index 7a13b88..4a1dad7 100644 --- a/src/handlers/event/pinboard.rs +++ b/src/handlers/event/pinboard.rs @@ -1,6 +1,6 @@ use crate::{utils, Data}; -use color_eyre::eyre::{eyre, Context as _, Result}; +use color_eyre::eyre::{Context as _, Result}; use log::*; use poise::serenity_prelude::model::prelude::*; use poise::serenity_prelude::Context; @@ -37,7 +37,7 @@ pub async fn handle(ctx: &Context, pin: &ChannelPinsUpdateEvent, data: &Data) -> .channel_id .pins(&ctx.http) .await - .expect("Couldn't get a list of pins!?"); + .wrap_err_with(|| "Couldn't get a list of pins!?")?; for pin in pins { // We call `take` because it's supposed to be just for the latest message. @@ -64,7 +64,7 @@ async fn redirect( .set_embed(embed) }) .await - .wrap_err_with(|| eyre!("couldn't redirect message"))?; + .wrap_err_with(|| "Couldn't redirect message")?; Ok(()) } diff --git a/src/handlers/event/reactboard.rs b/src/handlers/event/reactboard.rs index fa546c0..7550c6a 100644 --- a/src/handlers/event/reactboard.rs +++ b/src/handlers/event/reactboard.rs @@ -1,5 +1,5 @@ use crate::{storage, utils, Data}; -use storage::{ReactBoardEntry, REACT_BOARD_KEY}; +use storage::ReactBoardEntry; use color_eyre::eyre::{eyre, Context as _, Result}; use log::*; @@ -72,19 +72,11 @@ async fn send_to_reactboard( return Ok(()); } - let mut reactboard = storage.get_reactboard_info().await?; - - // try to find previous reactboard entry by the id of the original message - let old_index = reactboard - .reactions - .iter() - .position(|r| r.original_id == msg.id); - let content = format!("{} **#{}**", reaction.reaction_type, reaction.count); // bump reaction count if previous entry exists - if let Some(old_index) = old_index { - let old_entry = reactboard.reactions[old_index].clone(); + if storage.reactboard_entry_exists(guild_id, &msg.id).await? { + let old_entry = storage.get_reactboard_entry(guild_id, &msg.id).await?; // bail if we don't need to edit anything if old_entry.reaction_count >= reaction.count { @@ -99,14 +91,14 @@ async fn send_to_reactboard( ctx.http .get_message( - *old_entry.channel_id.as_u64(), - *old_entry.message_id.as_u64(), + *old_entry.posted_channel_id.as_u64(), + *old_entry.posted_message_id.as_u64(), ) .await .wrap_err_with(|| { format!( "Couldn't get previous message from ReactBoardEntry {} in Redis DB!", - old_entry.original_id + old_entry.original_message_id ) })? .edit(ctx, |m| m.content(content)) @@ -116,14 +108,8 @@ async fn send_to_reactboard( let mut new_entry = old_entry.clone(); new_entry.reaction_count = reaction.count; - reactboard.reactions.remove(old_index); - reactboard.reactions.push(new_entry.clone()); - - debug!( - "Updating ReactBoard entry {}\nOld entry:\n{old_entry:#?}\n\nNew:\n{new_entry:#?}\n", - msg.id - ); - storage.create_reactboard_info_key(reactboard).await?; + debug!("Updating ReactBoard entry\nOld entry:\n{old_entry:#?}\n\nNew:\n{new_entry:#?}\n",); + storage.create_reactboard_entry(guild_id, new_entry).await?; // make new message and add entry to redis otherwise } else { let embed = utils::resolve_message_to_embed(ctx, msg).await; @@ -137,19 +123,14 @@ async fn send_to_reactboard( .await?; let entry = ReactBoardEntry { - original_id: msg.id, + original_message_id: msg.id, reaction_count: reaction.count, - channel_id: resp.channel_id, - message_id: resp.id, + posted_channel_id: resp.channel_id, + posted_message_id: resp.id, }; - reactboard.reactions.push(entry.clone()); - - debug!( - "Creating new ReactBoard entry {} in {REACT_BOARD_KEY}:\n{:#?}", - msg.id, entry - ); - storage.create_reactboard_info_key(reactboard).await?; + debug!("Creating new ReactBoard entry:\n{entry:#?}"); + storage.create_reactboard_entry(guild_id, entry).await?; } Ok(()) diff --git a/src/main.rs b/src/main.rs index 4fb268a..42d7c78 100644 --- a/src/main.rs +++ b/src/main.rs @@ -80,8 +80,7 @@ async fn main() -> Result<()> { color_eyre::install()?; env_logger::init(); - let token = - std::env::var("TOKEN").wrap_err_with(|| eyre!("Couldn't find token in environment!"))?; + let token = std::env::var("TOKEN").wrap_err_with(|| "Couldn't find token in environment!")?; let intents = serenity::GatewayIntents::non_privileged() | serenity::GatewayIntents::MESSAGE_CONTENT; diff --git a/src/storage/mod.rs b/src/storage/mod.rs index 64a3856..2ab54f6 100644 --- a/src/storage/mod.rs +++ b/src/storage/mod.rs @@ -1,15 +1,18 @@ -use std::fmt::{Debug, Display}; +use std::fmt::Debug; use color_eyre::eyre::Result; use log::*; -use poise::serenity_prelude::GuildId; +use poise::serenity_prelude::{GuildId, MessageId}; use redis::{AsyncCommands, Client, FromRedisValue, ToRedisArgs}; -pub mod reactboard; -pub mod settings; +mod reactboard; +mod settings; +// these are purposefully private. see the comment below +use reactboard::REACTBOARD_KEY; +use settings::SETTINGS_KEY; -pub use reactboard::*; -pub use settings::*; +pub use reactboard::ReactBoardEntry; +pub use settings::{Settings, SettingsProperties}; #[derive(Clone, Debug)] pub struct Storage { @@ -23,6 +26,12 @@ impl Storage { Ok(Self { client }) } + /* + these are mainly light abstractions to avoid the `let mut con` + boilerplate, as well as not require the caller to format the + strings for keys + */ + pub async fn get_key<T>(&self, key: &str) -> Result<T> where T: FromRedisValue, @@ -66,6 +75,15 @@ impl Storage { Ok(()) } + pub async fn expire_key(&self, key: &str, expire_seconds: usize) -> Result<()> { + debug!("Expiring key {key} in {expire_seconds}"); + + let mut con = self.client.get_async_connection().await?; + con.expire(key, expire_seconds).await?; + + Ok(()) + } + pub async fn add_to_index<'a>( &self, key: &str, @@ -80,19 +98,60 @@ impl Storage { Ok(()) } - pub fn format_settings_key(subkey: impl Display) -> String { - format!("{}:{subkey}", SETTINGS_KEY) + pub async fn get_from_index<T>(&self, key: &str) -> Result<Vec<T>> + where + T: FromRedisValue, + { + let index = format!("{key}:index"); + debug!("Fetching index {index}"); + + let mut con = self.client.get_async_connection().await?; + let mems = con.smembers(key).await?; + + Ok(mems) } - pub async fn create_settings_key(&self, settings: Settings) -> Result<()> { - let key = Self::format_settings_key(settings.guild_id); + // guild settings + + pub async fn create_guild_settings(&self, settings: Settings) -> Result<()> { + let key = format!("{SETTINGS_KEY}:{}", settings.guild_id); self.set_key(&key, &settings).await?; + // adding to index since we need to look all of these up sometimes self.add_to_index(SETTINGS_KEY, settings).await?; Ok(()) } + pub async fn get_guild_settings(&self, guild_id: &GuildId) -> Result<Settings> { + debug!("Fetching guild settings for {guild_id}"); + + let key = format!("{SETTINGS_KEY}:{guild_id}"); + let settings: Settings = self.get_key(&key).await?; + + Ok(settings) + } + + pub async fn delete_guild_settings(&self, guild_id: &GuildId) -> Result<()> { + let key = format!("{SETTINGS_KEY}:{guild_id}"); + self.delete_key(&key).await?; + + Ok(()) + } + + pub async fn guild_settings_exist(&self, guild_id: &GuildId) -> Result<bool> { + let key = format!("{SETTINGS_KEY}:{guild_id}"); + self.key_exists(&key).await + } + + pub async fn get_all_guild_settings(&self) -> Result<Vec<Settings>> { + debug!("Fetching all guild settings"); + + let guilds: Vec<Settings> = self.get_from_index(SETTINGS_KEY).await?; + + Ok(guilds) + } + /// get guilds that have enabled optional commands pub async fn get_opted_guilds(&self) -> Result<Vec<GuildId>> { debug!("Fetching opted-in guilds"); @@ -112,35 +171,40 @@ impl Storage { Ok(opted) } - pub async fn get_all_guild_settings(&self) -> Result<Vec<Settings>> { - debug!("Fetching all guild settings"); + // reactboard - let mut con = self.client.get_async_connection().await?; - let key = Self::format_settings_key("index"); + pub async fn create_reactboard_entry( + &self, + guild_id: &GuildId, + entry: ReactBoardEntry, + ) -> Result<()> { + let key = format!("{REACTBOARD_KEY}:{guild_id}:{}", entry.original_message_id); - let guilds: Vec<Settings> = con.smembers(key).await?; + self.set_key(&key, &entry).await?; + self.expire_key(&key, 30 * 24 * 60 * 60).await?; // 30 days - Ok(guilds) + Ok(()) } - pub async fn get_guild_settings(&self, guild_id: &GuildId) -> Result<Settings> { - debug!("Fetching guild settings for {guild_id}"); - - let key = Self::format_settings_key(guild_id); - let settings: Settings = self.get_key(&key).await?; + pub async fn get_reactboard_entry( + &self, + guild_id: &GuildId, + message_id: &MessageId, + ) -> Result<ReactBoardEntry> { + debug!("Fetching reactboard entry in {guild_id}"); - Ok(settings) - } + let key = format!("{REACTBOARD_KEY}:{guild_id}:{message_id}"); + let entry: ReactBoardEntry = self.get_key(&key).await?; - pub async fn create_reactboard_info_key(&self, reactboard: ReactBoardInfo) -> Result<()> { - self.set_key(REACT_BOARD_KEY, reactboard).await?; - Ok(()) + Ok(entry) } - pub async fn get_reactboard_info(&self) -> Result<ReactBoardInfo> { - debug!("Fetching reactboard info"); - let reactboard: ReactBoardInfo = self.get_key(REACT_BOARD_KEY).await?; - - Ok(reactboard) + pub async fn reactboard_entry_exists( + &self, + guild_id: &GuildId, + message_id: &MessageId, + ) -> Result<bool> { + let key = format!("{REACTBOARD_KEY}:{guild_id}:{message_id}"); + self.key_exists(&key).await } } diff --git a/src/storage/reactboard.rs b/src/storage/reactboard.rs index e08aa54..19453df 100644 --- a/src/storage/reactboard.rs +++ b/src/storage/reactboard.rs @@ -2,17 +2,13 @@ use poise::serenity_prelude::{ChannelId, MessageId}; use redis_macros::{FromRedisValue, ToRedisArgs}; use serde::{Deserialize, Serialize}; -pub const REACT_BOARD_KEY: &str = "reactboard-v1"; +pub const REACTBOARD_KEY: &str = "reactboard-v2"; #[derive(Clone, Debug, Serialize, Deserialize, FromRedisValue, ToRedisArgs)] pub struct ReactBoardEntry { - pub original_id: MessageId, + pub original_message_id: MessageId, pub reaction_count: u64, - pub channel_id: ChannelId, - pub message_id: MessageId, -} - -#[derive(Clone, Debug, Default, Serialize, Deserialize, FromRedisValue, ToRedisArgs)] -pub struct ReactBoardInfo { - pub reactions: Vec<ReactBoardEntry>, + // we need these to update our message with new interactions + pub posted_channel_id: ChannelId, + pub posted_message_id: MessageId, } |
