use std::collections::HashMap; use std::sync::{Arc}; use chrono::{DateTime, Duration, Timelike, Utc}; use lemmy_db_schema::newtypes::{CommunityId, LanguageId}; use lemmy_db_schema::PostFeatureType; use tokio::sync::{RwLock}; use crate::{jnovel, lemmy, Message, SharedData}; use crate::config::{Config, PostBody, SeriesConfig}; use crate::jnovel::PostInfo; use crate::lemmy::{CustomCreatePost, Lemmy}; use crate::post_history::SeriesHistory; use tokio::time::sleep; pub(crate) async fn run(data: Arc>) { let credentials = match lemmy::Credentials::set_credentials() { Ok(creds) => creds, Err(e) => panic!("{}", e.to_string()), }; let mut last_reload: DateTime; let mut lemmy: Lemmy; let mut login_error: bool; let mut communities; { let mut write = data.write().await; // Errors during bot init are likely unrecoverable and therefore should panic the bot // Does not really matter since the bot will get restarted anyway but this way the uptime url logs a downtime write.config = match Config::load() { Ok(data) => data, Err(e) => panic!("{}", e), }; last_reload = Utc::now(); } { let read = data.read().await; lemmy = match lemmy::login(&credentials, read.config.instance.as_str()).await { Ok(data) => data, Err(e) => panic!("{}", e), }; login_error = false; communities = match lemmy.get_communities().await { Ok(data) => data, Err(e) => panic!("{}", e), }; } while Utc::now().naive_local().second() != 30 { sleep(Duration::milliseconds(100).to_std().unwrap()).await; } { let mut write = data.write().await; write.start = Utc::now(); } loop { idle(&data).await; { let mut write = data.write().await; write.start = Utc::now(); if write.start - last_reload > Duration::seconds(write.config.config_reload_seconds as i64) { write.config = match Config::load() { Ok(data) => data, Err(e) => panic!("{}", e), }; write.messages.push(Message::Info("Config reloaded".to_string())); } } { let read = data.read().await; if login_error { lemmy = match lemmy::login(&credentials, read.config.instance.as_str()).await { Ok(data) => data, Err(e) => { drop(read); let mut write = data.write().await; write.messages.push(Message::Error(e.to_string())); continue } }; login_error = false; } } { let read = data.read().await; if read.start - last_reload > Duration::seconds(read.config.config_reload_seconds as i64) { communities = match lemmy.get_communities().await { Ok(data) => data, Err(e) => { login_error = true; drop(read); let mut write = data.write().await; write.messages.push(Message::Error(e.to_string())); continue } }; let mut write = data.write().await; write.messages.push(Message::Info("Communities reloaded".to_string())); last_reload = Utc::now(); } } { let mut write = data.write().await; write.post_history = match SeriesHistory::load_history() { Ok(data) => data, Err(e) => { write.messages.push(Message::Warning(e.to_string())); continue } }; } { let read = data.read().await; let series = read.config.series.clone(); drop(read); for series in series { match handle_series(&series, &communities, &lemmy, &data).await { Ok(data) => data, Err(e) => { login_error = true; let mut write = data.write().await; write.messages.push(Message::Warning(e.to_string())); continue } }; } } let read = data.read().await; if read.messages.len() > 15 { let mut list = read.messages.clone(); drop(read); list.reverse(); while list.len() > 15 { list.pop(); } list.reverse(); let mut write = data.write().await; write.messages = list } idle(&data).await; } } async fn idle(data: &Arc>) { let read = data.read().await; let mut sleep_duration = Duration::seconds(30); if Utc::now() - read.start > sleep_duration { sleep_duration = Duration::seconds(60); } if let Some(status_url) = read.config.status_post_url.clone() { match reqwest::get(status_url).await { Ok(_) => {} Err(e) => { let mut write = data.write().await; write.messages.push(Message::Error(e.to_string())) }, } }; while Utc::now() - read.start < sleep_duration { sleep(Duration::milliseconds(100).to_std().unwrap()).await; } } async fn handle_series( series: &SeriesConfig, communities: &HashMap, lemmy: &Lemmy, data: &Arc>, ) -> Result<(), String> { let mut post_list = match jnovel::check_feed(series.slug.as_str(), series.parted).await { Ok(data) => data, Err(e) => { return Err(e.to_string()); }, }; for (index, post_info) in post_list.clone().iter().enumerate() { // todo .clone() likely not needed let post_part_info = post_info.get_part_info(); let post_lemmy_info = post_info.get_lemmy_info(); { let read = data.read().await; if read.post_history.check_for_post(series.slug.as_str(), post_part_info.as_string().as_str(), post_lemmy_info.title.as_str()) { drop(read); let mut write = data.write().await; write.messages.push(Message::Info(format!("Skipping '{}' since already posted", post_lemmy_info.title))); post_list.remove(index); continue } } let post_series_config = match post_info { PostInfo::Chapter {..} => {&series.prepub_community}, PostInfo::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 = CustomCreatePost { name: post_lemmy_info.title, 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 post_id = lemmy.post(post_data).await?; if post_series_config.pin_settings.pin_new_post_community { 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?; } if post_series_config.pin_settings.pin_new_post_local { let pinned_posts = lemmy.get_local_pinned().await?; if !pinned_posts.is_empty() { let community_pinned_post = &pinned_posts[0]; lemmy.unpin(community_pinned_post.post.id, PostFeatureType::Local).await?; } lemmy.pin(post_id, PostFeatureType::Local).await?; } let read = data.read().await; let mut series_history = read.post_history.get_series(series.slug.as_str()); let mut part_history = series_history.get_part(post_part_info.as_string().as_str()); drop(read); match post_info { PostInfo::Chapter {..} => { part_history.chapter = post_info.get_lemmy_info().title }, PostInfo::Volume {..} => { part_history.volume = post_info.get_lemmy_info().title } } series_history.set_part(post_part_info.as_string().as_str(), part_history); let mut write = data.write().await; write.post_history.set_series(series.slug.as_str(), series_history); let _ = match write.post_history.save_history() { Ok(data) => data, Err(e) => return Err(format!("{}", e)) }; } Ok(()) }