use std::path::PathBuf; use std::sync::{Arc, RwLock}; use chrono::{Timelike, Utc}; use crate::config::PostBody::Description; use lemmy_api_common::sensitive::Sensitive; use lemmy_db_schema::PostFeatureType; use serde_derive::{Deserialize, Serialize}; use crate::lemmy::{Lemmy, PartInfo, PostType}; use crate::post_history::{SeriesHistory}; use systemd_journal_logger::connected_to_journal; use crate::fetchers::{FetcherTrait, Fetcher}; use crate::fetchers::jnovel::{JNovelFetcher}; macro_rules! info { ($msg:tt) => { match connected_to_journal() { true => log::info!("[INFO] {}", $msg), false => println!("[INFO] {}", $msg), } }; } macro_rules! warn { ($msg:tt) => { match connected_to_journal() { true => log::warn!("[WARN] {}", $msg), false => println!("[WARN] {}", $msg), } }; } macro_rules! error { ($msg:tt) => { match connected_to_journal() { true => log::error!("[ERROR] {}", $msg), false => eprintln!("[ERROR] {}", $msg), } }; } #[derive(Serialize, Deserialize, Clone, Debug)] pub(crate) struct Config { pub(crate) instance: String, username: String, password: String, pub(crate) status_post_url: Option<String>, pub(crate) config_reload_seconds: u32, pub(crate) protected_communities: Vec<String>, pub(crate) series: Vec<SeriesConfig>, } impl Config { pub(crate) fn load() -> Self { let cfg: Self = match confy::load(env!("CARGO_PKG_NAME"), "config") { Ok(data) => data, Err(e) => panic!("config.toml not found: {e}"), }; if cfg.instance.is_empty() { panic!("bot instance not set!") } if cfg.username.is_empty() { panic!("bot username not set!") } if cfg.password.is_empty() { panic!("bot password not provided!") } cfg.series.iter().for_each(|series| { if series.prepub_community.post_body == Description { panic!("'Description' type Post Body only supported for Volumes!") } }); cfg } pub(crate) fn get_path() -> PathBuf { confy::get_configuration_file_path(env!("CARGO_PKG_NAME"), "config").expect("Application will not without confy") } pub(crate) fn get_username(&self) -> Sensitive<String> { Sensitive::new(self.username.clone()) } pub(crate) fn get_password(&self) -> Sensitive<String> { Sensitive::new(self.password.clone()) } } impl Default for Config { fn default() -> Self { Config { instance: "".to_owned(), username: "".to_owned(), password: "".to_owned(), status_post_url: None, config_reload_seconds: 21600, protected_communities: vec![], series: vec![], } } } #[derive(Debug, Serialize, Deserialize, Clone)] pub(crate) struct SeriesConfig { pub(crate) slug: String, pub(crate) parted: bool, pub(crate) prepub_community: PostConfig, pub(crate) volume_community: PostConfig, pub(crate) fetcher: Fetcher } impl SeriesConfig { pub(crate) async fn update(&self, history: &mut SeriesHistory, lemmy: &Lemmy, config: &Arc<RwLock<Config>>) { let info_msg = format!("Checking {} for Updates", self.slug); info!(info_msg); let mut fetcher: Fetcher = match &self.fetcher { Fetcher::Jnc(_) => { Fetcher::Jnc(JNovelFetcher::new()) }, /*default => { let err_msg = format!("Fetcher {default} not implemented"); error!(err_msg); return; }*/ }; match fetcher { Fetcher::Jnc(ref mut jnc) => { jnc.set_series(self.slug.clone()); jnc.set_part_option(self.parted); } } let post_list = match fetcher.check_feed().await { Ok(data) => data, Err(_) => { let err_msg = format!("While checking feed for {}", self.slug); error!(err_msg); return; } }; if post_list.is_empty() && Utc::now().minute() % 10 == 0 { let info_msg = "No Updates found"; info!(info_msg); } for post_info in post_list.iter() { if history.check_for_post( self.slug.as_str(), post_info.get_part_info().unwrap_or(PartInfo::NoParts).as_string().as_str(), post_info.get_info().title.as_str() ) { continue } let post_data = post_info.get_post_data(self, lemmy); let info = format!( "Posting '{}' to {}", post_info.get_info().title.as_str(), post_info.get_post_config(self).name.as_str() ); info!(info); let post_id = match lemmy.post(post_data).await { Ok(data) => data, Err(_) => { error!("Error posting chapter"); return; } }; let read_config = config.read().expect("Read Lock Failed").clone(); if post_info.get_post_config(self).pin_settings.pin_new_post_community && !read_config .protected_communities .contains(&post_info.get_post_config(self).name) { let info = format!( "Pinning '{}' to {}", post_info.get_info().title, post_info.get_post_config(self).name.as_str() ); info!(info); let pinned_posts = match lemmy.get_community_pinned(lemmy.get_community_id(&post_info.get_post_config(self).name)).await { Ok(data) => data, Err(_) => { error!("Pinning of Post to community failed"); continue; } }; if !pinned_posts.is_empty() { let community_pinned_post = &pinned_posts[0]; match lemmy .unpin(community_pinned_post.post.id, PostFeatureType::Community) .await { Ok(_) => {} Err(_) => { error!("Error un-pinning post"); return; } } } match lemmy.pin(post_id, PostFeatureType::Community).await { Ok(_) => {} Err(_) => { error!("Error pinning post"); return; } } } else if read_config .protected_communities .contains(&post_info.get_post_config(self).name) { let message = format!( "Community '{}' for Series '{}' is protected. Is this intended?", &post_info.get_post_config(self).name, self.slug ); warn!(message); } if post_info.get_post_config(self).pin_settings.pin_new_post_local { let info = format!("Pinning '{}' to Instance", post_info.get_info().title); info!(info); let pinned_posts = match lemmy.get_local_pinned().await { Ok(data) => {data} Err(_) => { error!("Error fetching pinned posts"); return; } }; if !pinned_posts.is_empty() { for pinned_post in pinned_posts { if read_config .protected_communities .contains(&pinned_post.community.name) { continue; } else { let community_pinned_post = &pinned_post; match lemmy .unpin(community_pinned_post.post.id, PostFeatureType::Local) .await { Ok(_) => {} Err(_) => { error!("Error pinning post"); return; } } break; } } } match lemmy.pin(post_id, PostFeatureType::Local).await { Ok(_) => {} Err(_) => { error!("Error pinning post"); return; } }; } let mut series_history = history.get_series(self.slug.as_str()); let mut part_history = series_history.get_part(post_info.get_part_info().unwrap_or(PartInfo::NoParts).as_string().as_str()); match post_info.post_type { Some(post_type) => { match post_type { PostType::Chapter => part_history.chapter = post_info.get_info().title, PostType::Volume => part_history.volume = post_info.get_info().title, } } None => part_history.chapter = post_info.get_info().title, } series_history.set_part(post_info.get_part_info().unwrap_or(PartInfo::NoParts).as_string().as_str(), part_history); history .set_series(self.slug.as_str(), series_history); history.save_history(); } } } #[derive(Debug, Serialize, Deserialize, Clone)] pub(crate) struct PostConfig { pub(crate) name: String, pub(crate) pin_settings: PinConfig, pub(crate) post_body: PostBody, } #[derive(Debug, Serialize, Deserialize, Clone)] pub(crate) struct PinConfig { pub(crate) pin_new_post_local: bool, pub(crate) pin_new_post_community: bool, } #[derive(Debug, Serialize, Deserialize, Clone, PartialEq)] #[serde(tag = "body_type", content = "body_content")] pub(crate) enum PostBody { None, Description, Custom(String), }