From 94b12a1069d8eb53317b429fc3997a944f177f74 Mon Sep 17 00:00:00 2001 From: seth Date: Tue, 2 Apr 2024 19:23:15 -0400 Subject: [PATCH] remove reactions from messages older than n days --- .env.template | 9 +++++++ .gitignore | 4 +-- Cargo.lock | 9 ++++--- Cargo.toml | 1 + src/config/discord.rs | 29 ++++++++++++++------- src/config/mod.rs | 8 +++--- src/handlers/event/block_reaction.rs | 39 ++++++++++++++++++++++++++++ src/handlers/event/mod.rs | 3 +++ src/main.rs | 2 +- 9 files changed, 86 insertions(+), 18 deletions(-) create mode 100644 .env.template create mode 100644 src/handlers/event/block_reaction.rs diff --git a/.env.template b/.env.template new file mode 100644 index 0000000..024ce41 --- /dev/null +++ b/.env.template @@ -0,0 +1,9 @@ +DISCORD_BOT_TOKEN= +DISCORD_DAYS_TO_DELETE_REACTION= +DISCORD_LOG_CHANNEL_ID= +DISCORD_WELCOME_CHANNEL_ID= + +BOT_REDIS_URL="redis://localhost:6379" + +RUST_LOG="refraction=debug" +RUST_BACKTRACE=1 diff --git a/.gitignore b/.gitignore index f07ccc4..b0e79ce 100644 --- a/.gitignore +++ b/.gitignore @@ -17,7 +17,7 @@ Cargo.lock # direnv secrets .env .env.* -!.env.example +!.env.template # Nix .direnv/ @@ -29,4 +29,4 @@ repl-result-out* *.rdb # JetBrains -.idea/ \ No newline at end of file +.idea/ diff --git a/Cargo.lock b/Cargo.lock index f29d4e0..8588b30 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -241,15 +241,17 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "chrono" -version = "0.4.31" +version = "0.4.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f2c685bad3eb3d45a01354cedb7d5faa66194d1d58ba6e267a8de788f79db38" +checksum = "8a0d04d43504c61aa6c7531f1871dd0d418d91130162063b789da00fd7057a5e" dependencies = [ "android-tzdata", "iana-time-zone", + "js-sys", "num-traits", "serde", - "windows-targets 0.48.5", + "wasm-bindgen", + "windows-targets 0.52.0", ] [[package]] @@ -1445,6 +1447,7 @@ dependencies = [ name = "refraction" version = "2.0.0" dependencies = [ + "chrono", "color-eyre", "dotenvy", "enum_dispatch", diff --git a/Cargo.toml b/Cargo.toml index 71ddf90..37c72ee 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -15,6 +15,7 @@ serde = "1.0.196" serde_json = "1.0.112" [dependencies] +chrono = "0.4.37" color-eyre = "0.6.2" dotenvy = "0.15.7" enum_dispatch = "0.3.12" diff --git a/src/config/discord.rs b/src/config/discord.rs index 27782b1..e1ef7fd 100644 --- a/src/config/discord.rs +++ b/src/config/discord.rs @@ -1,5 +1,6 @@ use std::str::FromStr; +use eyre::{Context, Result}; use log::{info, warn}; use poise::serenity_prelude::ChannelId; @@ -12,6 +13,7 @@ pub struct RefractionChannels { #[derive(Clone, Debug, Default)] pub struct Config { pub channels: RefractionChannels, + pub days_to_delete_reaction: i64, } impl RefractionChannels { @@ -22,15 +24,15 @@ impl RefractionChannels { } } - pub fn new_from_env() -> Self { - let log_channel_id = Self::get_channel_from_env("DISCORD_LOG_CHANNEL_ID"); + pub fn from_env() -> Self { + let log_channel_id = Self::channel_from_env("DISCORD_LOG_CHANNEL_ID"); if let Some(channel_id) = log_channel_id { info!("Log channel is {channel_id}"); } else { warn!("DISCORD_LOG_CHANNEL_ID is empty; this will disable logging in your server."); } - let welcome_channel_id = Self::get_channel_from_env("DISCORD_WELCOME_CHANNEL_ID"); + let welcome_channel_id = Self::channel_from_env("DISCORD_WELCOME_CHANNEL_ID"); if let Some(channel_id) = welcome_channel_id { info!("Welcome channel is {channel_id}"); } else { @@ -40,7 +42,7 @@ impl RefractionChannels { Self::new(log_channel_id, welcome_channel_id) } - fn get_channel_from_env(var: &str) -> Option { + fn channel_from_env(var: &str) -> Option { std::env::var(var) .ok() .and_then(|env_var| ChannelId::from_str(&env_var).ok()) @@ -48,13 +50,22 @@ impl RefractionChannels { } impl Config { - pub fn new(channels: RefractionChannels) -> Self { - Self { channels } + pub fn new(channels: RefractionChannels, days_to_delete_reaction: i64) -> Self { + Self { + channels, + days_to_delete_reaction, + } } - pub fn from_env() -> Self { - let channels = RefractionChannels::new_from_env(); + pub fn from_env() -> Result { + let channels = RefractionChannels::from_env(); + let days_to_delete_reaction = std::env::var("DISCORD_DAYS_TO_DELETE_REACTION") + .wrap_err("DISCORD_DAYS_TO_DELETE_REACTION is empty! This variable is required.")? + .parse() + .wrap_err("DISCORD_DAYS_TO_DELETE_REACTION is not a number!")?; - Self::new(channels) + info!("Reactions will be deleted on messages older than {days_to_delete_reaction} days"); + + Ok(Self::new(channels, days_to_delete_reaction)) } } diff --git a/src/config/mod.rs b/src/config/mod.rs index 3507670..4ef4071 100644 --- a/src/config/mod.rs +++ b/src/config/mod.rs @@ -1,3 +1,5 @@ +use eyre::Result; + mod bot; mod discord; @@ -15,10 +17,10 @@ impl Config { } } - pub fn new_from_env() -> Self { + pub fn from_env() -> Result { let bot = bot::Config::from_env(); - let discord = discord::Config::from_env(); + let discord = discord::Config::from_env()?; - Self::new(bot, discord) + Ok(Self::new(bot, discord)) } } diff --git a/src/handlers/event/block_reaction.rs b/src/handlers/event/block_reaction.rs new file mode 100644 index 0000000..888733d --- /dev/null +++ b/src/handlers/event/block_reaction.rs @@ -0,0 +1,39 @@ +use crate::Data; + +use chrono::Duration; +use eyre::{Context as _, Result}; +use log::{debug, trace}; +use poise::serenity_prelude::{Context, Reaction, Timestamp}; + +pub async fn handle(ctx: &Context, reaction: &Reaction, data: &Data) -> Result<()> { + let reaction_type = reaction.emoji.clone(); + let reactor = reaction.user_id; + let message = reaction.message(ctx).await.wrap_err_with(|| { + format!( + "Couldn't get message {} from reaction! We won't be able to check if it's old", + reaction.message_id + ) + })?; + + let time_sent = message.timestamp.to_utc(); + let age = Timestamp::now().signed_duration_since(time_sent); + let max_days = Duration::days(data.config.discord.days_to_delete_reaction); + + if age >= max_days { + // NOTE: if we for some reason **didn't** get the user_id associated with the reaction, + // this will clear **all** reactions of this type. this is intentional as older reactions + // being removed > harmful reactions being kept + debug!( + "Removing reaction {reaction_type} from message {}", + message.id + ); + message.delete_reaction(ctx, reactor, reaction_type).await?; + } else { + trace!( + "Keeping reaction {reaction_type} for message {}", + message.id + ); + } + + Ok(()) +} diff --git a/src/handlers/event/mod.rs b/src/handlers/event/mod.rs index dcbfc4d..bf77748 100644 --- a/src/handlers/event/mod.rs +++ b/src/handlers/event/mod.rs @@ -5,6 +5,7 @@ use poise::serenity_prelude::{ActivityData, Context, FullEvent, OnlineStatus}; use poise::FrameworkContext; mod analyze_logs; +mod block_reaction; mod delete_on_reaction; mod eta; mod expand_link; @@ -71,6 +72,8 @@ pub async fn handle( add_reaction.message_id.to_string(), add_reaction.user_id.unwrap_or_default().to_string() ); + + block_reaction::handle(ctx, add_reaction, data).await?; delete_on_reaction::handle(ctx, add_reaction).await?; } diff --git a/src/main.rs b/src/main.rs index 09de4a0..4034840 100644 --- a/src/main.rs +++ b/src/main.rs @@ -37,7 +37,7 @@ async fn setup( _: &serenity::Ready, framework: &Framework, ) -> Result { - let config = Config::new_from_env(); + let config = Config::from_env()?; let storage = if let Some(url) = &config.bot.redis_url { Some(Storage::from_url(url)?)