diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index 9786447..ced3ae5 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: @@ -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 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/agenda_cultural/api.rs b/src/agenda_cultural/api.rs index fb84e28..63a92f7 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_or_default(), + err.source().unwrap() + ) + }) .expect("Error sending request") .error_for_status() .expect("Request failed") diff --git a/src/discord/api.rs b/src/discord/api.rs index 571961c..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; @@ -243,11 +257,14 @@ 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(&self.client.http, edit_message) .await .expect("Failed to edit message!"); } diff --git a/src/main.rs b/src/main.rs index b6000b0..5255c81 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,10 +103,19 @@ 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 { + 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={})", @@ -159,7 +168,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 3851ff5..d0df7ba 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,18 @@ 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_add_remove_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_add_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; @@ -163,7 +164,6 @@ mod discord { let message = tester_api .client .http - .clone() .get_message(thread_id, message.id) .await .unwrap(); @@ -172,6 +172,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!(!message.pinned); } #[test_log::test(tokio::test)]