Move jnovel module into fetchers module

This commit is contained in:
Neshura 2024-01-08 21:06:25 +01:00
parent ba3110da0e
commit 6bd7369ecc
Signed by: Neshura
GPG key ID: B6983AAA6B9A7A6C

View file

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