summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorseth <[email protected]>2023-12-06 03:57:33 -0500
committerseth <[email protected]>2023-12-15 16:41:13 -0500
commit904259063831738d357a8092bee7c0e30988b0f6 (patch)
tree9290284e1ec3f639167e03b862ab776c74eddfc6 /src
parent14c417db576bc45e768143832750e6ed47d2d776 (diff)
refactor: use reactboard-v2
yay! we don't need to fetch every single reaction from every guild whenever a new one is triggered
Diffstat (limited to 'src')
-rw-r--r--src/api/guzzle.rs10
-rw-r--r--src/api/shiggy.rs10
-rw-r--r--src/commands/moderation/config.rs5
-rw-r--r--src/commands/optional/copypasta.rs8
-rw-r--r--src/commands/optional/teawiespam.rs2
-rw-r--r--src/handlers/error.rs4
-rw-r--r--src/handlers/event/guild.rs14
-rw-r--r--src/handlers/event/mod.rs11
-rw-r--r--src/handlers/event/pinboard.rs6
-rw-r--r--src/handlers/event/reactboard.rs45
-rw-r--r--src/main.rs3
-rw-r--r--src/storage/mod.rs126
-rw-r--r--src/storage/reactboard.rs14
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,
}