diff --git a/src/jnovel.rs b/src/fetchers/jnovel.rs similarity index 56% rename from src/jnovel.rs rename to src/fetchers/jnovel.rs index b27c88c..6bebc7d 100644 --- a/src/jnovel.rs +++ b/src/fetchers/jnovel.rs @@ -1,12 +1,15 @@ -use crate::jnovel::PartInfo::{NoParts, Part}; -use crate::jnovel::PostInfo::{Chapter, Volume}; -use crate::{write_error, HTTP_CLIENT}; +use crate::{write_error, HTTP_CLIENT, lemmy}; use chrono::{DateTime, Duration, Utc}; use serde_derive::{Deserialize, Serialize}; use std::cmp::Ordering; use std::collections::HashMap; use std::ops::Sub; +use async_trait::async_trait; use url::Url; +use crate::fetchers::Fetcher; +use crate::fetchers::jnovel::JPostInfo::{Chapter, Volume}; +use crate::fetchers::jnovel::PartInfo::{NoParts, Part}; +use crate::lemmy::{PostInfo, PostInfoInner}; static PAST_DAYS_ELIGIBLE: u8 = 4; @@ -54,27 +57,21 @@ pub(crate) struct Cover { pub(crate) struct VolumeDetail { pub(crate) title: String, pub(crate) slug: String, - pub(crate) number: u8, - pub(crate) publishing: String, + number: u8, + publishing: String, #[serde(alias = "shortDescription")] - pub(crate) short_description: String, - pub(crate) cover: Cover, + short_description: String, + cover: Cover, } #[derive(Serialize, Deserialize, Debug, Clone, PartialEq)] pub(crate) struct ChapterDetail { pub(crate) title: String, pub(crate) slug: String, - pub(crate) launch: String, + launch: String, pub(crate) cover: Option, } -#[derive(Debug, Clone)] -pub(crate) struct LemmyPostInfo { - pub(crate) title: String, - pub(crate) url: Url, -} - #[derive(Debug, Copy, Clone)] pub(crate) enum PartInfo { NoParts, @@ -137,19 +134,19 @@ impl PartialOrd for PartInfo { } #[derive(Debug, Clone)] -pub(crate) enum PostInfo { +pub(crate) enum JPostInfo { Chapter { part: PartInfo, - lemmy_info: LemmyPostInfo, + lemmy_info: PostInfoInner, }, Volume { part: PartInfo, description: String, - lemmy_info: LemmyPostInfo, + lemmy_info: PostInfoInner, }, } -impl PostInfo { +impl JPostInfo { pub(crate) fn get_part_info(&self) -> PartInfo { match self { Chapter { @@ -160,15 +157,17 @@ impl PostInfo { } => *part_info, } } +} - pub(crate) fn get_lemmy_info(&self) -> LemmyPostInfo { +impl PostInfo for JPostInfo { + fn get_info(&self) -> PostInfoInner { match self { Chapter { lemmy_info, .. } => lemmy_info.clone(), Volume { lemmy_info, .. } => lemmy_info.clone(), } } - pub(crate) fn get_description(&self) -> Option { + fn get_description(&self) -> Option { match self { Chapter { .. } => None, Volume { description, .. } => Some(description.clone()), @@ -176,7 +175,7 @@ impl PostInfo { } } -impl PartialEq for PostInfo { +impl PartialEq for JPostInfo { fn eq(&self, other: &Self) -> bool { let self_part = match self { Chapter { part, .. } => part, @@ -192,7 +191,7 @@ impl PartialEq for PostInfo { } } -impl PartialOrd for PostInfo { +impl PartialOrd for JPostInfo { fn partial_cmp(&self, other: &Self) -> Option { if self.gt(other) { Some(Ordering::Greater) @@ -240,130 +239,150 @@ impl PartialOrd for PostInfo { } } -pub(crate) async fn check_feed(series_slug: &str, series_has_parts: bool) -> Result, ()> { - let response = match HTTP_CLIENT - .get(api_url!() + "/series/" + series_slug + "/volumes?format=json") - .send() - .await - { - Ok(data) => match data.text().await { +pub(crate) struct JFetcherOptions { + series_slug: String, + series_has_parts: bool +} + +impl JFetcherOptions { + pub(crate) fn new(series_slug: String, series_has_parts: bool) -> Self { + JFetcherOptions { + series_slug, + series_has_parts + } + } +} + +#[async_trait] +impl Fetcher for JFetcherOptions { + type Return = JPostInfo; + async fn check_feed(&self) -> Result, ()> { + let response = match HTTP_CLIENT + .get(api_url!() + "/series/" + self.series_slug.as_str() + "/volumes?format=json") + .send() + .await + { + Ok(data) => match data.text().await { + Ok(data) => data, + Err(e) => { + let err_msg = format!("{e}"); + write_error(err_msg); + return Err(()); + } + }, + Err(e) => { + let err_msg = format!("{e}"); + write_error(err_msg); + return Err(()); + } + }; + + let mut volume_brief_data: VolumesWrapper = match serde_json::from_str(&response) { Ok(data) => data, Err(e) => { let err_msg = format!("{e}"); write_error(err_msg); return Err(()); } - }, - Err(e) => { - let err_msg = format!("{e}"); - write_error(err_msg); - return Err(()); - } - }; + }; + volume_brief_data.volumes.reverse(); // Makes breaking out of the volume loop easier - let mut volume_brief_data: VolumesWrapper = match serde_json::from_str(&response) { - Ok(data) => data, - Err(e) => { - let err_msg = format!("{e}"); - write_error(err_msg); - return Err(()); - } - }; - volume_brief_data.volumes.reverse(); // Makes breaking out of the volume loop easier + // If no parts just use 0 as Part indicator as no Series with Parts has a Part 0 + let mut volume_map: HashMap = HashMap::new(); + let mut prepub_map: HashMap = HashMap::new(); - // If no parts just use 0 as Part indicator as no Series with Parts has a Part 0 - let mut volume_map: HashMap = HashMap::new(); - let mut prepub_map: HashMap = HashMap::new(); - - for volume in volume_brief_data.volumes.iter() { - let publishing_date = DateTime::parse_from_rfc3339(&volume.publishing).unwrap(); - if publishing_date < Utc::now().sub(Duration::days(PAST_DAYS_ELIGIBLE as i64)) { - match series_has_parts { - true => continue, - false => break, - } - } - let new_part_info: PartInfo; - - if series_has_parts { - let mut part_number: Option = None; - let splits: Vec<&str> = volume.slug.split('-').collect(); - for (index, split) in splits.clone().into_iter().enumerate() { - if split == "part" { - part_number = Some( - splits[index + 1] - .parse::() - .expect("Split Element after 'Part' should always be a number"), - ); - break; + for volume in volume_brief_data.volumes.iter() { + let publishing_date = DateTime::parse_from_rfc3339(&volume.publishing).unwrap(); + if publishing_date < Utc::now().sub(Duration::days(PAST_DAYS_ELIGIBLE as i64)) { + match self.series_has_parts { + true => continue, + false => break, } } + let new_part_info: PartInfo; - match part_number { - Some(number) => new_part_info = Part(number), - None => { - println!("No Part found, assuming 1"); - new_part_info = Part(1); - } - } - } else { - new_part_info = NoParts; - } - - let post_url = format!( - "{}/series/{series_slug}#volume-{}", - jnc_base_url!(), - volume.number - ); - let post_details = LemmyPostInfo { - title: volume.title.clone(), - url: Url::parse(&post_url).unwrap(), - }; - - let new_post_info = Volume { - part: new_part_info, - description: volume.short_description.clone(), - lemmy_info: post_details, - }; - - let part_id = new_part_info.as_u8(); - - if publishing_date <= Utc::now() { - volume_map - .entry(part_id) - .and_modify(|val| { - if *val < new_post_info { - *val = new_post_info.clone() + if self.series_has_parts { + let mut part_number: Option = None; + let splits: Vec<&str> = volume.slug.split('-').collect(); + for (index, split) in splits.clone().into_iter().enumerate() { + if split == "part" { + part_number = Some( + splits[index + 1] + .parse::() + .expect("Split Element after 'Part' should always be a number"), + ); + break; } - }) - .or_insert(new_post_info); - } + } - if let Some(prepub_info) = get_latest_prepub(&volume.slug).await? { - let prepub_post_info = Chapter { - part: new_part_info, - lemmy_info: prepub_info, + match part_number { + Some(number) => new_part_info = Part(number), + None => { + println!("No Part found, assuming 1"); + new_part_info = Part(1); + } + } + } else { + new_part_info = NoParts; + } + + let post_url = format!( + "{}/series/{}#volume-{}", + jnc_base_url!(), + self.series_slug.as_str(), + volume.number + ); + let post_details = lemmy::PostInfoInner { + title: volume.title.clone(), + url: Url::parse(&post_url).unwrap(), }; - prepub_map - .entry(part_id) - .and_modify(|val| { - if *val < prepub_post_info { - *val = prepub_post_info.clone() - } - }) - .or_insert(prepub_post_info); + let new_post_info = Volume { + part: new_part_info, + description: volume.short_description.clone(), + lemmy_info: post_details, + }; + + let part_id = new_part_info.as_u8(); + + if publishing_date <= Utc::now() { + volume_map + .entry(part_id) + .and_modify(|val| { + if *val < new_post_info { + *val = new_post_info.clone() + } + }) + .or_insert(new_post_info); + } + + if let Some(prepub_info) = get_latest_prepub(&volume.slug).await? { + let prepub_post_info = Chapter { + part: new_part_info, + lemmy_info: prepub_info, + }; + + prepub_map + .entry(part_id) + .and_modify(|val| { + if *val < prepub_post_info { + *val = prepub_post_info.clone() + } + }) + .or_insert(prepub_post_info); + } } + + let mut result_vec: Vec = volume_map.values().cloned().collect(); + let mut prepub_vec: Vec = prepub_map.values().cloned().collect(); + result_vec.append(&mut prepub_vec); + + Ok(result_vec) } - - let mut result_vec: Vec = volume_map.values().cloned().collect(); - let mut prepub_vec: Vec = prepub_map.values().cloned().collect(); - result_vec.append(&mut prepub_vec); - - Ok(result_vec) } -async fn get_latest_prepub(volume_slug: &str) -> Result, ()> { + +async fn get_latest_prepub(volume_slug: &str) -> Result, ()> { let response = match HTTP_CLIENT .get(api_url!() + "/volumes/" + volume_slug + "/parts?format=json") .send() @@ -394,7 +413,7 @@ async fn get_latest_prepub(volume_slug: &str) -> Result, ( }; volume_prepub_parts_data.parts.reverse(); // Makes breaking out of the parts loop easier - let mut post_details: Option = None; + let mut post_details: Option = None; for prepub_part in volume_prepub_parts_data.parts.iter() { let publishing_date = DateTime::parse_from_rfc3339(&prepub_part.launch).unwrap(); @@ -405,7 +424,7 @@ async fn get_latest_prepub(volume_slug: &str) -> Result, ( } let post_url = format!("{}/read/{}", jnc_base_url!(), prepub_part.slug); - post_details = Some(LemmyPostInfo { + post_details = Some(lemmy::PostInfoInner { title: prepub_part.title.clone(), url: Url::parse(&post_url).unwrap(), });