From 231e47dad2b8d64d31a65b1ef2c3bc99c8ff965e Mon Sep 17 00:00:00 2001 From: David Gomes <10091092+davidgomesdev@users.noreply.github.com> Date: Sun, 7 Sep 2025 13:41:42 +0100 Subject: [PATCH 1/3] ci(release): remove cache not supporting multi-arch --- .github/workflows/release.yaml | 2 -- 1 file changed, 2 deletions(-) diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index 1956f8f..d693447 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -47,8 +47,6 @@ jobs: rustup default stable rustup override set stable - - uses: Swatinem/rust-cache@v2 - - name: 🌱 - Install dependencies run: cargo install cross --git https://github.com/cross-rs/cross From 4a728ec7eb34674f5b5e1be09161a48ff7d0ca55 Mon Sep 17 00:00:00 2001 From: David Gomes <10091092+davidgomesdev@users.noreply.github.com> Date: Sun, 7 Sep 2025 15:01:04 +0100 Subject: [PATCH 2/3] feat: add reply to edit review --- Cargo.toml | 2 + src/discord/api.rs | 102 +++++++++++++++++++++++++++++++++---- tests/discord_api_tests.rs | 6 +-- 3 files changed, 98 insertions(+), 12 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 62e67f4..c04ddd7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -35,3 +35,5 @@ serenity = "0.12.4" uuid = { version = "1.11.1", features = ["v4"] } chrono = "0.4.40" itertools = "0.14.0" +mockall = "0.13.1" +mockall_double = "0.3.1" diff --git a/src/discord/api.rs b/src/discord/api.rs index 208c02c..7ceb363 100644 --- a/src/discord/api.rs +++ b/src/discord/api.rs @@ -19,6 +19,7 @@ use serenity::prelude::SerenityError; use serenity::Client; use std::env; use std::fmt::Debug; +use mockall::automock; use tracing::field::debug; use tracing::{debug, error, info, instrument, trace, warn}; @@ -40,10 +41,13 @@ const PORTUGUESE_MONTHS: [&str; 12] = [ ]; const CHILDREN_LABEL: &str = "🧸 para crianças"; +const COMMENT_APPLIED_REACTION: char = '✅'; lazy_static! { static ref USER_MENTION_REGEX: Regex = Regex::new("<@(\\d+)>").expect("Failed to create mention regex"); + + static ref COMMENT_IN_USER_REVIEW_REGEX: Regex = Regex::new(r"\*\*Comentários:\*\*.*").unwrap(); } pub struct DiscordAPI { @@ -51,16 +55,17 @@ pub struct DiscordAPI { pub own_user: CurrentUser, } +#[automock] impl DiscordAPI { pub async fn default() -> Self { - DiscordAPI::new( + DiscordAPI::new_with_token( &env::var("DISCORD_TOKEN").expect("DISCORD_TOKEN not set"), true, ) .await } - pub async fn new(token: &str, cache_flag: bool) -> Self { + pub async fn new_with_token(token: &str, cache_flag: bool) -> Self { let intents = GatewayIntents::GUILD_MESSAGES | GatewayIntents::MESSAGE_CONTENT | GatewayIntents::GUILD_MESSAGE_REACTIONS; @@ -188,12 +193,8 @@ impl DiscordAPI { .react(&self.client.http, ReactionType::from(emoji_char)) .await; - if let Err(e) = react_result { - let msg = &format!("Failed to add reaction {} to message", emoji_char); - error!( - msg, - error = %e - ); + if let Err(error) = react_result { + error!("Failed to add reaction {} to message. Error: {:?}", emoji_char, error); } } @@ -285,6 +286,66 @@ impl DiscordAPI { } } + #[instrument(skip_all)] + pub async fn add_reply_comments_on_dms(&self) { + let dms = self + .client + .http + .get_user_dm_channels() + .await + .expect("Failed to get DM channels"); + + for dm in dms { + let messages: Vec = dm + .messages(&self.client.http, GetMessages::new()) + .await + .expect("Failed to get messages") + .into_iter() + .filter(|msg| self.is_message_reply_to_own(msg)) + .collect(); + + debug!("Found {} review edit messages", messages.len()); + + for msg in messages { + let mut referenced_msg = msg.referenced_message.clone().unwrap(); + + if referenced_msg.embeds.is_empty() { + warn!("Referenced message is not a review (does not have an embed)"); + continue + } + + self.edit_user_review_comment(&msg, &mut referenced_msg).await; + + info!("Edited user review with new comment"); + } + } + } + + #[instrument(skip_all)] + async fn edit_user_review_comment(&self, msg: &Message, referenced_msg: &mut Message) { + let new_comment = msg.content.as_str(); + let embed = referenced_msg.embeds.first().unwrap().to_owned(); + + debug!(event = embed.url, "Editing user review comment"); + + let embed_edit = Self::edit_event_embed_comment(embed, &new_comment); + + let edit_embed_result = referenced_msg + .edit(&self.client.http, EditMessage::new().embed(embed_edit)) + .await; + + if let Err(error) = edit_embed_result + { + error!("Failed editing review to new comment. Error: {:?}", error); + } + + self.add_reaction_to_message(&msg, COMMENT_APPLIED_REACTION) + .await; + + info!(comment = new_comment, "Edited user review with new comment"); + } + + // TODO: instrument async fn send_user_review( &self, user: &User, @@ -312,6 +373,13 @@ impl DiscordAPI { } } + // TODO: unit test this + fn is_message_reply_to_own(&self, msg: &Message) -> bool { + msg.author != *self.own_user + && msg.referenced_message.is_some() + && msg.referenced_message.clone().unwrap().author == *self.own_user + } + async fn get_user_votes( &self, event_message: &Message, @@ -422,7 +490,8 @@ impl DiscordAPI { .expect("Failed to send message"); if let Some(comment) = comment { - self.add_reaction_to_message(&comment, '✅').await; + self.add_reaction_to_message(&comment, COMMENT_APPLIED_REACTION) + .await; } } @@ -448,6 +517,21 @@ impl DiscordAPI { } } + #[instrument(skip(event_embed), fields(event_name = %event_embed.title.clone().unwrap_or_default()))] + fn edit_event_embed_comment(event_embed: Embed, comment: &str) -> CreateEmbed { + if event_embed.description == None { + warn!("Event review does not have a description!"); + } + + let original_description = event_embed.description.clone().unwrap(); + let new_description = COMMENT_IN_USER_REVIEW_REGEX.replace( + &original_description, + format!("**Comentários: {}**", comment), + ); + + CreateEmbed::from(event_embed).description(new_description) + } + #[instrument(skip(self, dm), fields(user_name = %dm.recipient.name.to_string()))] async fn get_user_last_comment(&self, dm: &PrivateChannel) -> Option { match dm.last_message_id { diff --git a/tests/discord_api_tests.rs b/tests/discord_api_tests.rs index 3851ff5..a8e22c8 100644 --- a/tests/discord_api_tests.rs +++ b/tests/discord_api_tests.rs @@ -406,7 +406,7 @@ mod discord { } pub async fn build_api() -> DiscordAPI { - let api = DiscordAPI::new(&token, false).await; + let api = DiscordAPI::new_with_token(&token, false).await; let _ = INIT .get_or_init(|| async { @@ -419,7 +419,7 @@ mod discord { } pub async fn build_api_without_cache() -> DiscordAPI { - let api = DiscordAPI::new(&token, false).await; + let api = DiscordAPI::new_with_token(&token, false).await; let _ = INIT .get_or_init(|| async { @@ -445,7 +445,7 @@ mod discord { } pub async fn build_tester_api() -> DiscordAPI { - DiscordAPI::new(&tester_token, false).await + DiscordAPI::new_with_token(&tester_token, false).await } } } From 7b67ef3ef06b061b9bdbe4f624e145395fbbd885 Mon Sep 17 00:00:00 2001 From: David Gomes <10091092+davidgomesdev@users.noreply.github.com> Date: Sat, 4 Oct 2025 21:13:35 +0100 Subject: [PATCH 3/3] feat: add logs --- src/agenda_cultural/api.rs | 2 ++ src/api.rs | 7 ++++++- src/main.rs | 4 +++- 3 files changed, 11 insertions(+), 2 deletions(-) diff --git a/src/agenda_cultural/api.rs b/src/agenda_cultural/api.rs index 4d13d78..fb84e28 100644 --- a/src/agenda_cultural/api.rs +++ b/src/agenda_cultural/api.rs @@ -107,6 +107,8 @@ impl AgendaCulturalAPI { } } + info!("Parsed events"); + events_by_date } diff --git a/src/api.rs b/src/api.rs index e3afd85..c3566ae 100644 --- a/src/api.rs +++ b/src/api.rs @@ -4,18 +4,23 @@ use crate::discord::api::{DiscordAPI, EventsThread}; use chrono::NaiveDate; use serenity::all::{ChannelId, GuildChannel, Message, PartialGuild}; use std::collections::BTreeMap; -use tracing::info; +use tracing::{info, instrument, trace}; +#[instrument(skip_all)] pub async fn filter_new_events_by_thread( discord: &DiscordAPI, guild: &PartialGuild, events_by_month: BTreeMap>, channel_id: ChannelId, ) -> BTreeMap> { + trace!("Getting threads"); let threads = discord.get_channel_threads(guild, channel_id).await; + trace!("Sorting threads by month"); let threads_by_month = get_threads_by_month(discord, channel_id, &events_by_month, &threads).await; + + trace!("Getting sent events"); let sent_events = get_sent_events(discord, &threads).await; threads_by_month diff --git a/src/main.rs b/src/main.rs index 639ace5..dfcefa9 100644 --- a/src/main.rs +++ b/src/main.rs @@ -67,6 +67,8 @@ async fn run(config: &Config, discord: &DiscordAPI, category: Category, channel_ let new_events = filter_new_events_by_thread(discord, &guild, events, channel_id).await; + info!("Filtered new events"); + send_new_events( discord, new_events, @@ -75,7 +77,7 @@ async fn run(config: &Config, discord: &DiscordAPI, category: Category, channel_ ) .await; - info!("Finished for {}", category); + info!("Finished sending new events for {}", category); } #[instrument(skip(discord, threads, vote_emojis))]