From 27a7550ab5daf27a0ded0e8f123fb8f791ef4ab7 Mon Sep 17 00:00:00 2001 From: David Gomes <10091092+davidgomesdev@users.noreply.github.com> Date: Fri, 19 Dec 2025 12:13:58 +0000 Subject: [PATCH 1/7] feat: remove msg when no saved for later --- src/discord/api.rs | 8 +++++++- tests/discord_api_tests.rs | 5 +++-- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/src/discord/api.rs b/src/discord/api.rs index 571961c..0082b9a 100644 --- a/src/discord/api.rs +++ b/src/discord/api.rs @@ -243,10 +243,16 @@ impl DiscordAPI { info!("Saved for later changed to '{}'", mentions); + let mut edit_message = EditMessage::new().content(message_content); + + if saved_for_later_user_ids.is_empty() { + edit_message = edit_message.content(""); + } + message .edit( &self.client.http, - EditMessage::new().content(message_content), + edit_message, ) .await .expect("Failed to edit message!"); diff --git a/tests/discord_api_tests.rs b/tests/discord_api_tests.rs index 3851ff5..4eac1a2 100644 --- a/tests/discord_api_tests.rs +++ b/tests/discord_api_tests.rs @@ -121,13 +121,13 @@ mod discord { } #[test_log::test(tokio::test)] - async fn when_someone_removes_save_for_later_react_should_add_remove_that_person_from_the_message( + async fn when_someone_removes_save_for_later_react_should_emove_that_person_from_the_message( ) { let api = build_api().await; let (thread_id, _, mut message) = send_random_event( &api, - "when_someone_removes_save_for_later_react_should_add_remove_that_person_from_the_message" + "when_someone_removes_save_for_later_react_should_remove_that_person_from_the_message" ).await; api.add_reaction_to_message(&message, *SAVE_FOR_LATER_EMOJI) @@ -172,6 +172,7 @@ mod discord { assert!(!saved_later.contains(tester_api.own_user.id.to_string().as_str())); assert!(!saved_later.contains(api.own_user.id.to_string().as_str())); + assert!(saved_later.is_empty()) } #[test_log::test(tokio::test)] From 066d397cd693f9916b80a6f17ee288f7053cfc0e Mon Sep 17 00:00:00 2001 From: David Gomes <10091092+davidgomesdev@users.noreply.github.com> Date: Fri, 19 Dec 2025 12:26:16 +0000 Subject: [PATCH 2/7] ci: set build on pr instead of push --- .github/workflows/build.yaml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index 9786447..6be21b2 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -1,9 +1,9 @@ name: Build on: - push: - # On main, it's the build and release that will run - branches-ignore: [ "main" ] + pull_request: + branches: + - main jobs: detect-changes: From 5d18ca49a0a92b0463bf3d7c47d1f95cfdce4b9a Mon Sep 17 00:00:00 2001 From: David Gomes <10091092+davidgomesdev@users.noreply.github.com> Date: Fri, 19 Dec 2025 13:09:24 +0000 Subject: [PATCH 3/7] fix: ignore non-bot messages on channel --- .gitignore | 2 ++ src/main.rs | 5 +++++ tests/discord_api_tests.rs | 1 - 3 files changed, 7 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 896508b..b6956cc 100644 --- a/.gitignore +++ b/.gitignore @@ -19,3 +19,5 @@ example.json schema.json docker/.data + +test-locally.sh diff --git a/src/main.rs b/src/main.rs index b6000b0..5f1f179 100644 --- a/src/main.rs +++ b/src/main.rs @@ -107,6 +107,11 @@ async fn handle_reaction_features( ); for mut message in messages { + if message.author != *discord.own_user { + debug!("Ignoring message from a different user {}", message.author.id); + continue; + } + if message.embeds.is_empty() { warn!( "Found message without embed (id={}; content={})", diff --git a/tests/discord_api_tests.rs b/tests/discord_api_tests.rs index 4eac1a2..e39546b 100644 --- a/tests/discord_api_tests.rs +++ b/tests/discord_api_tests.rs @@ -163,7 +163,6 @@ mod discord { let message = tester_api .client .http - .clone() .get_message(thread_id, message.id) .await .unwrap(); From ac0160a56079084c2ed6a90fbad415c965d0c046 Mon Sep 17 00:00:00 2001 From: David Gomes <10091092+davidgomesdev@users.noreply.github.com> Date: Fri, 19 Dec 2025 12:21:57 +0000 Subject: [PATCH 4/7] feat: pin message on saved for later --- src/discord/api.rs | 19 +++++++++++++++---- src/main.rs | 7 ++++--- tests/agenda_cultural_api_tests.rs | 27 ++++++++++----------------- tests/discord_api_tests.rs | 19 ++++++++++--------- 4 files changed, 39 insertions(+), 33 deletions(-) diff --git a/src/discord/api.rs b/src/discord/api.rs index 0082b9a..5845c91 100644 --- a/src/discord/api.rs +++ b/src/discord/api.rs @@ -236,6 +236,20 @@ impl DiscordAPI { .join(" "); let message_content = format!("Interessados: {}", mentions); + if saved_for_later_user_ids.is_empty() && message.pinned { + message + .unpin(&self.client.http) + .await + .expect("Failed to unpin no longer saved for later message!"); + } + + if !saved_for_later_user_ids.is_empty() && !message.pinned { + message + .pin(&self.client.http) + .await + .expect("Failed to pin saved for later message!"); + } + if message_content.trim() == message.content.trim() { trace!("No new users saved for later"); return; @@ -250,10 +264,7 @@ impl DiscordAPI { } message - .edit( - &self.client.http, - edit_message, - ) + .edit(&self.client.http, edit_message) .await .expect("Failed to edit message!"); } diff --git a/src/main.rs b/src/main.rs index 5f1f179..b5ffa28 100644 --- a/src/main.rs +++ b/src/main.rs @@ -47,7 +47,7 @@ async fn main() { Category::Teatro, config.teatro_channel_id, ) - .await; + .await; } if let Some((controller, join_handle)) = loki_controller { @@ -103,7 +103,8 @@ async fn handle_reaction_features( info!( "Tagging save for later and sending votes in DM (on thread '{}' with {} messages)", - thread.name, messages.len() + thread.name, + messages.len() ); for mut message in messages { @@ -164,7 +165,7 @@ async fn send_new_events( if debug_config.skip_feature_reactions { info!("Skipping feature reactions"); - continue + continue; } add_feature_reactions(discord, &message, emojis, *SAVE_FOR_LATER_EMOJI).await; diff --git a/tests/agenda_cultural_api_tests.rs b/tests/agenda_cultural_api_tests.rs index 8d1eaee..ddbe52a 100644 --- a/tests/agenda_cultural_api_tests.rs +++ b/tests/agenda_cultural_api_tests.rs @@ -59,7 +59,9 @@ mod agenda_cultural { let event: Event = AgendaCulturalAPI::get_event_by_id(208058).await.unwrap(); assert_eq!(event.title, "Nora Helmer"); - assert!(event.details.description.starts_with("A história de Nora Helmer, protagonista de Casa de Bonecas, peça de Henrik Ibsen")); + assert!(event.details.description.starts_with( + "A história de Nora Helmer, protagonista de Casa de Bonecas, peça de Henrik Ibsen" + )); assert_eq!(event.details.image_url, "https://www.agendalx.pt/content/uploads/2025/02/Nora-Helmer_ensaios2©Filipe_Figueiredo.jpg"); assert_eq!( event.details.subtitle, @@ -84,8 +86,8 @@ mod agenda_cultural { let event: Event = AgendaCulturalAPI::get_event_by_public_url( "https://www.agendalx.pt/events/event/o-monte/", ) - .await - .unwrap(); + .await + .unwrap(); assert_eq!(event.title, "O Monte"); assert!(event.details.description.starts_with("A partir de um texto de João Ascenso, O Monte é inspirado no relato da atriz Luísa Ortigoso sobre um ex-preso político que reencontra o seu torturador anos após a ditadura.")); @@ -94,10 +96,7 @@ mod agenda_cultural { "https://www.agendalx.pt/content/uploads/2025/03/omonte.jpg" ); assert_eq!(event.details.subtitle, "Teatro Livre"); - assert_eq!( - event.link, - "https://www.agendalx.pt/events/event/o-monte/" - ); + assert_eq!(event.link, "https://www.agendalx.pt/events/event/o-monte/"); assert_eq!(event.occurring_at.dates, "24 abril a 4 maio"); assert_eq!( event.occurring_at.times, @@ -117,8 +116,8 @@ mod agenda_cultural { let event: Event = AgendaCulturalAPI::get_event_by_public_url( "https://www.agendalx.pt/events/event/um-sapato-especial/", ) - .await - .unwrap(); + .await + .unwrap(); assert_eq!(event.title, "Um sapato especial"); assert!(event.details.description.starts_with("O Ursinho José gosta muito de ir brincar para o jardim. Joga à bola, às corridas, anda de bicicleta e nos baloiços. E ele é o campeão dos saltos! Mas um dia acontece algo inesperado e começa um")); @@ -132,16 +131,10 @@ mod agenda_cultural { "https://www.agendalx.pt/events/event/um-sapato-especial/" ); assert_eq!(event.occurring_at.dates, "14 junho"); - assert_eq!( - event.occurring_at.times, - "16h00" - ); + assert_eq!(event.occurring_at.times, "16h00"); assert_eq!(event.venue, "Fábrica Braço de Prata"); assert_eq!(event.tags.len(), 2); - assert_eq!( - event.tags, - ["crianças", "famílias"] - ); + assert_eq!(event.tags, ["crianças", "famílias"]); assert!(event.is_for_children); } } diff --git a/tests/discord_api_tests.rs b/tests/discord_api_tests.rs index e39546b..ae99485 100644 --- a/tests/discord_api_tests.rs +++ b/tests/discord_api_tests.rs @@ -77,7 +77,7 @@ mod discord { } #[test_log::test(tokio::test)] - async fn when_someone_reacts_with_save_later_should_add_that_person_to_message() { + async fn when_someone_reacts_with_save_later_should_add_that_person_to_message_and_pin_it() { let api = build_api().await; let (thread_id, link, mut message) = send_random_event( &api, @@ -118,17 +118,17 @@ mod discord { assert!(saved_later.contains(tester_api.own_user.id.to_string().as_str())); assert!(!saved_later.contains(api.own_user.id.to_string().as_str())); + assert!(message.pinned); } #[test_log::test(tokio::test)] - async fn when_someone_removes_save_for_later_react_should_emove_that_person_from_the_message( - ) { + async fn when_someone_removes_save_for_later_react_should_emove_that_person_from_the_message_and_unpin_it() { let api = build_api().await; - let (thread_id, _, mut message) = - send_random_event( - &api, - "when_someone_removes_save_for_later_react_should_remove_that_person_from_the_message" - ).await; + let (thread_id, _, mut message) = send_random_event( + &api, + "when_someone_removes_save_for_later_react_should_remove_that_person_from_the_message", + ) + .await; api.add_reaction_to_message(&message, *SAVE_FOR_LATER_EMOJI) .await; @@ -171,7 +171,8 @@ mod discord { assert!(!saved_later.contains(tester_api.own_user.id.to_string().as_str())); assert!(!saved_later.contains(api.own_user.id.to_string().as_str())); - assert!(saved_later.is_empty()) + assert!(saved_later.is_empty()); + assert!(!message.pinned); } #[test_log::test(tokio::test)] From eb8f9e9ce8ad2223cb7574063509c6ce0f318739 Mon Sep 17 00:00:00 2001 From: David Gomes <10091092+davidgomesdev@users.noreply.github.com> Date: Fri, 19 Dec 2025 14:20:20 +0000 Subject: [PATCH 5/7] chore: print reqwest error --- src/agenda_cultural/api.rs | 9 ++++++++- src/main.rs | 5 ++++- tests/discord_api_tests.rs | 3 ++- 3 files changed, 14 insertions(+), 3 deletions(-) diff --git a/src/agenda_cultural/api.rs b/src/agenda_cultural/api.rs index fb84e28..c42b4d5 100644 --- a/src/agenda_cultural/api.rs +++ b/src/agenda_cultural/api.rs @@ -8,10 +8,10 @@ use reqwest_middleware::{ClientBuilder, ClientWithMiddleware}; use reqwest_retry::policies::ExponentialBackoff; use reqwest_retry::RetryTransientMiddleware; use scraper::{Html, Selector}; -use std::cmp::Ordering; use std::collections::BTreeMap; use std::ops::Add; use std::time::Duration; +use std::{cmp::Ordering, error::Error}; use tracing::{debug, error, info, instrument, trace, warn}; use voca_rs::strip::strip_tags; @@ -169,6 +169,13 @@ impl AgendaCulturalAPI { .get(format!("{}/{}", AGENDA_EVENTS_URL, event_id)) .send() .await + .inspect_err(|err| { + error!( + "Failed with status {} source {}", + err.status().unwrap(), + err.source().unwrap() + ) + }) .expect("Error sending request") .error_for_status() .expect("Request failed") diff --git a/src/main.rs b/src/main.rs index b5ffa28..5255c81 100644 --- a/src/main.rs +++ b/src/main.rs @@ -109,7 +109,10 @@ async fn handle_reaction_features( for mut message in messages { if message.author != *discord.own_user { - debug!("Ignoring message from a different user {}", message.author.id); + debug!( + "Ignoring message from a different user {}", + message.author.id + ); continue; } diff --git a/tests/discord_api_tests.rs b/tests/discord_api_tests.rs index ae99485..d0df7ba 100644 --- a/tests/discord_api_tests.rs +++ b/tests/discord_api_tests.rs @@ -122,7 +122,8 @@ mod discord { } #[test_log::test(tokio::test)] - async fn when_someone_removes_save_for_later_react_should_emove_that_person_from_the_message_and_unpin_it() { + async fn when_someone_removes_save_for_later_react_should_emove_that_person_from_the_message_and_unpin_it( + ) { let api = build_api().await; let (thread_id, _, mut message) = send_random_event( &api, From 2a7761561a82cdb974a05a699819253d6744ef3c Mon Sep 17 00:00:00 2001 From: David Gomes <10091092+davidgomesdev@users.noreply.github.com> Date: Fri, 19 Dec 2025 14:30:06 +0000 Subject: [PATCH 6/7] ci: cache on failure --- .github/workflows/build.yaml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index 6be21b2..ced3ae5 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -42,6 +42,8 @@ jobs: - uses: actions/checkout@v3 - uses: Swatinem/rust-cache@v2 + with: + cache-on-failure: true - name: 🧯 - Check for compile errors run: cargo check From 4d4a213d7354b3868b94f33dafa2575c3ab5b4f0 Mon Sep 17 00:00:00 2001 From: David Gomes <10091092+davidgomesdev@users.noreply.github.com> Date: Fri, 19 Dec 2025 14:32:30 +0000 Subject: [PATCH 7/7] test: print --- src/agenda_cultural/api.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/agenda_cultural/api.rs b/src/agenda_cultural/api.rs index c42b4d5..63a92f7 100644 --- a/src/agenda_cultural/api.rs +++ b/src/agenda_cultural/api.rs @@ -172,7 +172,7 @@ impl AgendaCulturalAPI { .inspect_err(|err| { error!( "Failed with status {} source {}", - err.status().unwrap(), + err.status().unwrap_or_default(), err.source().unwrap() ) })