diff --git a/.gitignore b/.gitignore index b6956cc..f10e125 100644 --- a/.gitignore +++ b/.gitignore @@ -21,3 +21,4 @@ schema.json docker/.data test-locally.sh +run-locally.sh diff --git a/src/agenda_cultural/api.rs b/src/agenda_cultural/api.rs index 63a92f7..2172d6f 100644 --- a/src/agenda_cultural/api.rs +++ b/src/agenda_cultural/api.rs @@ -2,6 +2,7 @@ use super::{dto::EventResponse, model::Event}; use crate::agenda_cultural::dto::SingleEventResponse; use crate::agenda_cultural::model::Category; use chrono::{Datelike, NaiveDate, TimeDelta, Utc}; +use futures::TryFutureExt; use lazy_static::lazy_static; use reqwest::{Client, Response}; use reqwest_middleware::{ClientBuilder, ClientWithMiddleware}; @@ -11,8 +12,8 @@ use scraper::{Html, Selector}; 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 std::{cmp::Ordering}; +use tracing::{debug, info, instrument, trace, warn}; use voca_rs::strip::strip_tags; const AGENDA_EVENTS_URL: &str = "https://www.agendalx.pt/wp-json/agendalx/v1/events"; @@ -59,21 +60,13 @@ impl AgendaCulturalAPI { } let category: &'static str = category.into(); - let parsed_response = Self::get_events_by_category(amount_per_page, category).await; + let parsed_response = Self::get_events_by_category(amount_per_page, category).await?; - match parsed_response { - Ok(parsed_response) => { - info!("Fetched {} events", parsed_response.len()); + info!("Fetched {} events", parsed_response.len()); - let events = Self::parse_events_by_date(parsed_response).await; + let events = Self::parse_events_by_date(parsed_response).await; - Ok(events) - } - Err(e) => { - error!("Response parse failed: {:?}", e); - Err(APIError::InvalidResponse) - } - } + Ok(events) } #[instrument(skip_all)] @@ -137,7 +130,7 @@ impl AgendaCulturalAPI { min_date = min_date .add(TimeDelta::days(31)) .with_day(1) - .expect("Huh failed adding up months"); + .unwrap(); } } } @@ -169,30 +162,18 @@ 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") + .map_err(APIError::ErrorSending)? .error_for_status() - .expect("Request failed") + .map_err(APIError::ResponseError)? .text() - .await - .expect("Received invalid response"); + .map_err(APIError::InvalidResponse) + .await?; trace!("Json response: {json_response}"); - let parsed_response = serde_json::from_str::(&json_response); + let parsed_response = serde_json::from_str::(&json_response) + .map_err(APIError::ParseError)?; - match parsed_response { - Ok(dto) => Ok(Self::convert_response_to_model(&dto.event).await), - Err(e) => { - error!("Response parse failed: {:?}", e); - Err(APIError::InvalidResponse) - } - } + Ok(Self::convert_response_to_model(&parsed_response.event).await) } /** @@ -203,21 +184,27 @@ impl AgendaCulturalAPI { let full_page: String = REST_CLIENT .get(url) .send() - .await - .expect("Error getting full page") + .map_err(APIError::ErrorSending) + .await? + .error_for_status() + .map_err(APIError::ResponseError)? .text() - .await - .expect("Error getting text of page"); + .map_err(APIError::InvalidResponse) + .await?; let page_html = Html::parse_fragment(&full_page); let id_element = page_html .select(&EVENT_ID_SELECTOR) .next() - .expect("Could not find ID element in page!") + .ok_or_else(|| { + APIError::FailedParsingHtml("Could not find ID element in page!".to_string()) + })? .attr("href") .and_then(|href| href.strip_prefix(AGENDA_PAGE_BY_ID_PATH)) - .expect("Could not find ID in element!") + .ok_or_else(|| { + APIError::FailedParsingHtml("Could not find ID in the element!".to_string()) + })? .parse() - .expect("Fetched ID is not valid!"); + .map_err(|_| APIError::FailedParsingHtml("Fetched ID is not valid!".to_string()))?; Self::get_event_by_id(id_element).await } @@ -226,7 +213,7 @@ impl AgendaCulturalAPI { async fn get_events_by_category( amount_per_page: Option, category: &str, - ) -> serde_json::Result> { + ) -> Result, APIError> { let json_response = REST_CLIENT .get(format!( "{}?per_page={}&categories={}&type={}", @@ -236,15 +223,16 @@ impl AgendaCulturalAPI { EVENT_TYPE )) .send() - .await - .expect("Error sending request") + .map_err(APIError::ErrorSending) + .await? .error_for_status() - .expect("Request failed") + .map_err(APIError::ResponseError)? .text() - .await - .expect("Received invalid response"); + .map_err(APIError::InvalidResponse) + .await?; serde_json::from_str::>(&json_response) + .map_err(APIError::ParseError) } async fn get_full_description(link: &str) -> Option { @@ -396,5 +384,9 @@ mod tests { #[derive(Debug)] pub enum APIError { - InvalidResponse, + ErrorSending(reqwest_middleware::Error), + ResponseError(reqwest::Error), + InvalidResponse(reqwest::Error), + ParseError(serde_json::Error), + FailedParsingHtml(String), } diff --git a/src/config/env_loader.rs b/src/config/env_loader.rs index cef52f2..7cf6b35 100644 --- a/src/config/env_loader.rs +++ b/src/config/env_loader.rs @@ -6,6 +6,7 @@ pub fn load_config() -> Config { let teatro_channel_id: ChannelId = load_channel_id_config("DISCORD_TEATRO_CHANNEL_ID"); let artes_channel_id: ChannelId = load_channel_id_config("DISCORD_ARTES_CHANNEL_ID"); let voting_emojis: [EmojiConfig; 5] = load_voting_emojis_config("VOTING_EMOJIS"); + let gather_new_events: bool = load_bool_config("GATHER_NEW_EVENTS", true); let debug_config = DebugConfig { clear_channel: load_bool_config("DEBUG_CLEAR_CHANNEL", false), @@ -21,6 +22,7 @@ pub fn load_config() -> Config { teatro_channel_id, artes_channel_id, voting_emojis, + gather_new_events, } } diff --git a/src/config/model.rs b/src/config/model.rs index 2fab86b..1fd19b2 100644 --- a/src/config/model.rs +++ b/src/config/model.rs @@ -7,6 +7,7 @@ pub struct Config { pub teatro_channel_id: ChannelId, pub artes_channel_id: ChannelId, pub voting_emojis: [EmojiConfig; 5], + pub gather_new_events: bool, } #[derive(Debug)] diff --git a/src/main.rs b/src/main.rs index 5255c81..7d41c56 100644 --- a/src/main.rs +++ b/src/main.rs @@ -9,7 +9,7 @@ use lazy_static::lazy_static; use serenity::all::{ChannelId, GuildChannel}; use std::collections::BTreeMap; use std::process::exit; -use tracing::{debug, info, instrument, warn}; +use tracing::{debug, error, info, instrument, warn}; lazy_static! { pub static ref SAVE_FOR_LATER_EMOJI: char = '🔖'; @@ -56,6 +56,9 @@ async fn main() { } } +// No way will I match all variants of APIError +#[allow(clippy::unnecessary_unwrap)] +#[instrument(skip_all, fields(category = %category))] async fn run(config: &Config, discord: &DiscordAPI, category: Category, channel_id: ChannelId) { let guild = discord.get_guild(channel_id).await; let threads = discord.get_channel_threads(&guild, channel_id).await; @@ -66,12 +69,25 @@ async fn run(config: &Config, discord: &DiscordAPI, category: Category, channel_ info!("Handled reaction features"); + if !config.gather_new_events { + info!("Set to not gather new events"); + return; + } + let events = AgendaCulturalAPI::get_events_by_month(&category, config.debug_config.event_limit) - .await - .unwrap(); + .await; + + if events.is_err() { + let err = events.unwrap_err(); + error!("Failed getting events. Reason: {:?}", err); + return; + } + + let events = events.unwrap(); if events.is_empty() { - panic!("No events found"); + error!("No events found"); + return; } let new_events = filter_new_events_by_thread(discord, &guild, events, channel_id).await;