use crate::{config::{Config, PostBody, SeriesConfig}, fetchers::{jnovel}, lemmy}; use crate::fetchers::jnovel::JPostInfo; use crate::lemmy::{Lemmy, PostInfo}; use crate::post_history::SeriesHistory; use chrono::{DateTime, Duration, Timelike, Utc}; use lemmy_api_common::post::CreatePost; use lemmy_db_schema::newtypes::{CommunityId, LanguageId}; use lemmy_db_schema::PostFeatureType; use std::collections::HashMap; use tokio::time::sleep; use crate::fetchers::Fetcher; use systemd_journal_logger::connected_to_journal; 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), } }; } pub(crate) async fn run() { let mut last_reload: DateTime<Utc>; let mut lemmy: Lemmy; let mut login_error: bool; let mut communities: HashMap<String, CommunityId>; let mut post_history: SeriesHistory; let mut start: DateTime<Utc>; let mut config: Config = Config::load(); last_reload = Utc::now(); lemmy = match lemmy::login(&config).await { Ok(data) => data, Err(_) => panic!(), }; login_error = false; communities = match lemmy.get_communities().await { Ok(data) => data, Err(_) => panic!(), }; start = Utc::now(); let info_msg = "Bot init successful, starting normal operations".to_owned(); info!(info_msg); loop { idle(&start, &config).await; start = Utc::now(); // replace with watcher if start - last_reload >= Duration::seconds(config.config_reload_seconds as i64) { config = Config::load(); let message = "Config reloaded".to_owned(); info!(message); } if login_error { let info_msg = "Login invalid, refreshing session"; info!(info_msg); lemmy = match lemmy::login(&config).await { Ok(data) => data, Err(_) => continue, }; login_error = false; } if start - last_reload >= Duration::seconds(config.config_reload_seconds as i64) { communities = match lemmy.get_communities().await { Ok(data) => data, Err(_) => { login_error = true; continue; } }; let message = "Communities reloaded".to_owned(); info!(message); last_reload = Utc::now(); } post_history = SeriesHistory::load_history(); let series = config.series.clone(); for series in series { sleep(Duration::seconds(1).to_std().unwrap()).await; let info_msg = format!("Handling Series {}", series.slug); info!(info_msg); if handle_series(&series, &communities, &lemmy, &config, &mut post_history) .await .is_err() { login_error = true; continue; }; } idle(&start, &config).await; } } async fn idle(start: &DateTime<Utc>, config: &Config) { let mut sleep_duration = Duration::seconds(30); let info_msg = format!("Idling for {} seconds", sleep_duration.num_seconds()); info!(info_msg); if Utc::now() - start > sleep_duration { sleep_duration = Duration::seconds(60); } if let Some(status_url) = config.status_post_url.clone() { match reqwest::get(status_url).await { Ok(_) => {} Err(e) => { let err_msg = format!("{e}"); error!(err_msg); } } }; while Utc::now() - start < sleep_duration { sleep(Duration::milliseconds(100).to_std().unwrap()).await; } } async fn handle_series(series: &SeriesConfig, communities: &HashMap<String, CommunityId>, lemmy: &Lemmy, config: &Config, post_history: &mut SeriesHistory ) -> Result<(), ()> { let jnc = jnovel::JFetcherOptions::new(series.slug.clone(), series.parted); let post_list = match jnc.check_feed().await { Ok(data) => data, Err(_) => return Err(()), }; 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.clone().iter() { let post_part_info = post_info.get_part_info(); let post_lemmy_info = post_info.get_info(); if post_history.check_for_post( series.slug.as_str(), post_part_info.as_string().as_str(), post_lemmy_info.title.as_str(), ) { continue; } let post_series_config = match post_info { JPostInfo::Chapter { .. } => &series.prepub_community, JPostInfo::Volume { .. } => &series.volume_community, }; let community_id = *communities .get(post_series_config.name.as_str()) .expect("Given community is invalid"); let post_body = match &post_series_config.post_body { PostBody::None => None, PostBody::Description => post_info.get_description(), PostBody::Custom(text) => Some(text.clone()), }; let post_data = CreatePost { name: post_lemmy_info.title.clone(), community_id, url: Some(post_lemmy_info.url), body: post_body, honeypot: None, nsfw: None, language_id: Some(LanguageId(37)), // TODO get this id once every few hours per API request, the ordering of IDs suggests that the EN Id might change in the future }; let info = format!( "Posting '{}' to {}", post_lemmy_info.title.as_str(), post_series_config.name.as_str() ); info!(info); let post_id = lemmy.post(post_data).await?; if post_series_config.pin_settings.pin_new_post_community && config .protected_communities .contains(&post_series_config.name) { let info = format!( "Pinning '{}' to {}", post_lemmy_info.title, post_series_config.name.as_str() ); info!(info); let pinned_posts = lemmy.get_community_pinned(community_id).await?; if !pinned_posts.is_empty() { let community_pinned_post = &pinned_posts[0]; lemmy .unpin(community_pinned_post.post.id, PostFeatureType::Community) .await?; } lemmy.pin(post_id, PostFeatureType::Community).await?; } else if config .protected_communities .contains(&post_series_config.name) { let message = format!( "Community '{}' for Series '{}' is protected. Is this intended?", &post_series_config.name, series.slug ); warn!(message); } if post_series_config.pin_settings.pin_new_post_local { let info = format!("Pinning '{}' to Instance", post_lemmy_info.title); info!(info); let pinned_posts = lemmy.get_local_pinned().await?; if !pinned_posts.is_empty() { for pinned_post in pinned_posts { if config .protected_communities .contains(&pinned_post.community.name) { continue; } else { let community_pinned_post = &pinned_post; lemmy .unpin(community_pinned_post.post.id, PostFeatureType::Local) .await?; break; } } } lemmy.pin(post_id, PostFeatureType::Local).await?; } let mut series_history = post_history.get_series(series.slug.as_str()); let mut part_history = series_history.get_part(post_part_info.as_string().as_str()); match post_info { JPostInfo::Chapter { .. } => part_history.chapter = post_info.get_info().title, JPostInfo::Volume { .. } => part_history.volume = post_info.get_info().title, } series_history.set_part(post_part_info.as_string().as_str(), part_history); post_history .set_series(series.slug.as_str(), series_history); post_history.save_history(); } Ok(()) }