From 47e6cc59c034ea6bc035bff0e3acdd3345f8a4f1 Mon Sep 17 00:00:00 2001 From: Neshura Date: Sun, 17 Dec 2023 22:29:18 +0100 Subject: [PATCH] Working Error Logging --- src/bot/mod.rs | 174 ++++++++++++++++++++++++++-------------- src/config/mod.rs | 8 +- src/jnovel/mod.rs | 46 +++++++---- src/lemmy/mod.rs | 124 +++++++++++++++++++--------- src/main.rs | 10 +++ src/post_history/mod.rs | 10 ++- src/tui/mod.rs | 4 +- 7 files changed, 253 insertions(+), 123 deletions(-) diff --git a/src/bot/mod.rs b/src/bot/mod.rs index 5de25da..ff232a7 100644 --- a/src/bot/mod.rs +++ b/src/bot/mod.rs @@ -1,10 +1,9 @@ use std::collections::HashMap; -use std::error::Error; 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, RwLockWriteGuard}; +use tokio::sync::{RwLock}; use crate::{jnovel, lemmy, Message, SharedData}; use crate::config::{Config, PostBody, SeriesConfig}; use crate::jnovel::PostInfo; @@ -12,7 +11,7 @@ use crate::lemmy::{CustomCreatePost, Lemmy}; use crate::post_history::SeriesHistory; use tokio::time::sleep; -pub(crate) async fn run(data: Arc>){ +pub(crate) async fn run(data: Arc>) { let credentials = match lemmy::Credentials::set_credentials() { Ok(creds) => creds, Err(e) => panic!("{}", e.to_string()), @@ -23,17 +22,20 @@ pub(crate) async fn run(data: Arc>){ let mut login_error: bool; let mut communities; { - let mut shared_data = data.write().await; + 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 - shared_data.config = match Config::load() { + write.config = match Config::load() { Ok(data) => data, Err(e) => panic!("{}", e), }; last_reload = Utc::now(); + } - lemmy = match lemmy::login(&credentials, shared_data.config.instance.as_str()).await { + { + let read = data.read().await; + lemmy = match lemmy::login(&credentials, read.config.instance.as_str()).await { Ok(data) => data, Err(e) => panic!("{}", e), }; @@ -49,64 +51,101 @@ pub(crate) async fn run(data: Arc>){ sleep(Duration::milliseconds(100).to_std().unwrap()).await; } + { + let mut write = data.write().await; + write.start = Utc::now(); + } + loop { idle(&data).await; - let mut shared_data = data.write().await; + { + let mut write = data.write().await; - shared_data.start = Utc::now(); + write.start = Utc::now(); - if shared_data.start - last_reload > Duration::seconds(shared_data.config.config_reload_seconds as i64) { - shared_data.config = match Config::load() { - Ok(data) => data, - Err(e) => panic!("{}", e), - }; - } - - if login_error { - lemmy = match lemmy::login(&credentials, shared_data.config.instance.as_str()).await { - Ok(data) => data, - Err(e) => { - shared_data.messages.push(Message::Error(format!("{}", e))); - continue - } - }; - login_error = false; - } - - if shared_data.start - last_reload > Duration::seconds(shared_data.config.config_reload_seconds as i64) { - communities = match lemmy.get_communities().await { - Ok(data) => data, - Err(e) => { - login_error = true; - shared_data.messages.push(Message::Error(format!("{}", e))); - continue - } - }; - last_reload = Utc::now(); - } - - shared_data.post_history = match SeriesHistory::load_history() { - Ok(data) => data, - Err(e) => { - login_error = true; - shared_data.messages.push(Message::Warning(format!("{}", e))); - continue + 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), + }; } - }; + } - for series in shared_data.config.series.clone() { - match handle_series(&series, &communities, &lemmy, &mut shared_data).await { + { + 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 + } + }; + last_reload = Utc::now(); + } + } + + { + let mut write = data.write().await; + write.post_history = match SeriesHistory::load_history() { Ok(data) => data, Err(e) => { - login_error = true; - shared_data.messages.push(Message::Warning(format!("{}", e))); + write.messages.push(Message::Warning(e.to_string())); continue } }; } - let _ = shared_data.downgrade(); + { + 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; } @@ -124,7 +163,7 @@ async fn idle(data: &Arc>) { Ok(_) => {} Err(e) => { let mut write = data.write().await; - write.messages.push(Message::Error(format!("{}", e))) + write.messages.push(Message::Error(e.to_string())) }, } }; @@ -134,26 +173,35 @@ async fn idle(data: &Arc>) { } } -async fn handle_series<'a>( +async fn handle_series( series: &SeriesConfig, communities: &HashMap, lemmy: &Lemmy, - data: &mut RwLockWriteGuard<'a, SharedData>, -) -> Result<(), Box> { + data: &Arc>, +) -> Result<(), String> { + let mut post_list = match jnovel::check_feed(series.slug.as_str(), series.parted).await { Ok(data) => data, - Err(e) => panic!("{:#?}", e), + 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(); - if data.post_history.check_for_post(series.slug.as_str(), post_part_info.as_string().as_str(), post_lemmy_info.title.as_str()) { - data.messages.push(Message::Info(format!("Skipping '{}' since already posted", post_lemmy_info.title))); + + { + 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}, @@ -200,8 +248,10 @@ async fn handle_series<'a>( lemmy.pin(post_id, PostFeatureType::Local).await?; } - let mut series_history = data.post_history.get_series(series.slug.as_str()); + 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 {..} => { @@ -213,8 +263,12 @@ async fn handle_series<'a>( } series_history.set_part(post_part_info.as_string().as_str(), part_history); - data.post_history.set_series(series.slug.as_str(), series_history); - let _ = data.post_history.save_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(()) } diff --git a/src/config/mod.rs b/src/config/mod.rs index e0fc5eb..f960166 100644 --- a/src/config/mod.rs +++ b/src/config/mod.rs @@ -1,4 +1,3 @@ -use std::{error::Error}; use serde_derive::{Deserialize, Serialize}; use crate::config::PostBody::Description; @@ -11,8 +10,11 @@ pub(crate) struct Config { } impl Config { - pub(crate) fn load() -> Result> { - let cfg: Self = confy::load_path("./config.toml")?; + pub(crate) fn load() -> Result { + let cfg: Self = match confy::load_path("./config.toml") { + Ok(data) => data, + Err(_) => panic!("config.toml not found!"), + }; if cfg.instance.is_empty() { panic!("config.toml not found!") } diff --git a/src/jnovel/mod.rs b/src/jnovel/mod.rs index e872023..f120b38 100644 --- a/src/jnovel/mod.rs +++ b/src/jnovel/mod.rs @@ -1,6 +1,5 @@ use std::cmp::Ordering; use std::collections::HashMap; -use std::error::Error; use std::ops::Sub; use chrono::{DateTime, Duration, Utc}; use serde_derive::{Deserialize, Serialize}; @@ -9,7 +8,7 @@ use crate::{HTTP_CLIENT}; use crate::jnovel::PartInfo::{NoParts, Part}; use crate::jnovel::PostInfo::{Chapter, Volume}; -static PAST_DAYS_ELIGIBLE: u8 = 4; +static PAST_DAYS_ELIGIBLE: u8 = 8; macro_rules! api_url { () => { @@ -223,15 +222,24 @@ impl PartialOrd for PostInfo { } } -pub(crate) async fn check_feed(series_slug: &str, series_has_parts: bool) -> Result, Box> { - let response = HTTP_CLIENT +pub(crate) async fn check_feed(series_slug: &str, series_has_parts: bool) -> Result, String> { + let response = match HTTP_CLIENT .get(api_url!() + "/series/" + series_slug + "/volumes?format=json") .send() - .await? - .text() - .await?; + .await { + Ok(data) => { + match data.text().await { + Ok(data) => data, + Err(e) => return Err(format!("{}", e)) + } + }, + Err(e) => return Err(format!("{}", e)) + }; - let mut volume_brief_data: VolumesWrapper = serde_json::from_str(&response)?; + let mut volume_brief_data: VolumesWrapper = match serde_json::from_str(&response) { + Ok(data) => data, + Err(e) => return Err(format!("{}", e)) + }; 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 @@ -322,15 +330,25 @@ pub(crate) async fn check_feed(series_slug: &str, series_has_parts: bool) -> Res Ok(result_vec) } -async fn get_latest_prepub(volume_slug: &str) -> Result, Box> { - let response = HTTP_CLIENT +async fn get_latest_prepub(volume_slug: &str) -> Result, String> { + let response = match HTTP_CLIENT .get(api_url!() + "/volumes/" + volume_slug + "/parts?format=json") .send() - .await? - .text() - .await?; + .await { + Ok(data) => { + match data.text().await { + Ok(data) => data, + Err(e) => return Err(e.to_string()) + } + }, + Err(e) => return Err(format!("{}", e)) + }; - let mut volume_prepub_parts_data: ChapterWrapper = serde_json::from_str(&response)?; + + let mut volume_prepub_parts_data: ChapterWrapper = match serde_json::from_str(&response) { + Ok(data) => data, + Err(e) => return Err(format!("{}", e)) + }; volume_prepub_parts_data.parts.reverse(); // Makes breaking out of the parts loop easier let mut post_details: Option = None; diff --git a/src/lemmy/mod.rs b/src/lemmy/mod.rs index ce39711..8026195 100644 --- a/src/lemmy/mod.rs +++ b/src/lemmy/mod.rs @@ -1,7 +1,6 @@ use std::collections::HashMap; use std::env; use std::env::VarError; -use std::error::Error; use lemmy_api_common::community::{ListCommunities}; use lemmy_api_common::person::{Login, LoginResponse}; use lemmy_api_common::post::{GetPosts}; @@ -42,18 +41,21 @@ pub(crate) struct Lemmy { instance: String, } -pub(crate) async fn login(credentials: &Credentials, instance: &str) -> Result> { +pub(crate) async fn login(credentials: &Credentials, instance: &str) -> Result { let login_params = Login { username_or_email: credentials.get_username(), password: credentials.get_password(), totp_2fa_token: None, }; - let response = HTTP_CLIENT + let response = match HTTP_CLIENT .post(instance.to_string() + "/api/v3/user/login") .json(&login_params) .send() - .await?; + .await { + Ok(data) => data, + Err(e) => return Err(format!("{}", e)) + }; match response.status() { StatusCode::OK => { @@ -71,40 +73,55 @@ pub(crate) async fn login(credentials: &Credentials, instance: &str) -> Result Result> { - let response = HTTP_CLIENT + pub(crate) async fn post(&self, post: CustomCreatePost) -> Result { + let response = match HTTP_CLIENT .post(format!("{}/api/v3/post", &self.instance)) .bearer_auth(&self.jwt_token.to_string()) .json(&post) .send() - .await? - .text() - .await?; + .await { + Ok(data) => { + match data.text().await { + Ok(data) => data, + Err(e) => return Err(format!("{}", e)) + } + }, + Err(e) => return Err(format!("{}", e)) + }; - let json_data = serde_json::from_str::>(&response)? - .remove("post_view") - .expect("Element should be present"); + let json_data = match serde_json::from_str::>(&response) { + Ok(mut data) => data.remove("post_view").expect("Element should be present"), + Err(e) => return Err(format!("{}", e)) + }; Ok(json_data.post.id) } - async fn feature(&self, params: CustomFeaturePost) -> Result> { - let response = HTTP_CLIENT + async fn feature(&self, params: CustomFeaturePost) -> Result { + let response = match HTTP_CLIENT .post(format!("{}/api/v3/post/feature", &self.instance)) .bearer_auth(&self.jwt_token.to_string()) .json(¶ms) .send() - .await? - .text() - .await?; + .await { + Ok(data) => { + match data.text().await { + Ok(data) => data, + Err(e) => return Err(format!("{}", e)) + } + }, + Err(e) => return Err(format!("{}", e)) + }; + + let json_data = match serde_json::from_str::>(&response) { + Ok(mut data) => data.remove("post_view").expect("Element should be present"), + Err(e) => return Err(format!("{}", e)) + }; - let json_data = serde_json::from_str::>(&response)? - .remove("post_view") - .expect("Element should be present"); Ok(json_data) } - pub(crate) async fn unpin(&self, post_id: PostId, location: PostFeatureType) -> Result> { + pub(crate) async fn unpin(&self, post_id: PostId, location: PostFeatureType) -> Result { let pin_params = CustomFeaturePost { post_id, featured: false, @@ -113,7 +130,7 @@ impl Lemmy { self.feature(pin_params).await } - pub(crate) async fn pin(&self, post_id: PostId, location: PostFeatureType) -> Result> { + pub(crate) async fn pin(&self, post_id: PostId, location: PostFeatureType) -> Result { let pin_params = CustomFeaturePost { post_id, featured: true, @@ -122,23 +139,32 @@ impl Lemmy { self.feature(pin_params).await } - pub(crate) async fn get_community_pinned(&self, community: CommunityId) -> Result, Box> { + pub(crate) async fn get_community_pinned(&self, community: CommunityId) -> Result, String> { let list_params = GetPosts { community_id: Some(community), type_: Some(ListingType::Local), ..Default::default() }; - let response = HTTP_CLIENT + let response = match HTTP_CLIENT .get(format!("{}/api/v3/post/list", &self.instance)) .bearer_auth(&self.jwt_token.to_string()) .query(&list_params) .send() - .await? - .text() - .await?; + .await { + Ok(data) => { + match data.text().await { + Ok(data) => data, + Err(e) => return Err(format!("{}", e)) + } + }, + Err(e) => return Err(format!("{}", e)) + }; - let json_data: CustomGetPostsResponse = serde_json::from_str(&response)?; + let json_data: CustomGetPostsResponse = match serde_json::from_str(&response) { + Ok(data) => data, + Err(e) => return Err(format!("{}", e)) + }; Ok(json_data.posts.iter().filter(|post| { post.post.featured_community @@ -148,22 +174,31 @@ impl Lemmy { ) } - pub(crate) async fn get_local_pinned(&self) -> Result, Box> { + pub(crate) async fn get_local_pinned(&self) -> Result, String> { let list_params = GetPosts { type_: Some(ListingType::Local), ..Default::default() }; - let response = HTTP_CLIENT + let response = match HTTP_CLIENT .get(format!("{}/api/v3/post/list", &self.instance)) .bearer_auth(&self.jwt_token.to_string()) .query(&list_params) .send() - .await? - .text() - .await?; + .await { + Ok(data) => { + match data.text().await { + Ok(data) => data, + Err(e) => return Err(format!("{}", e)) + } + }, + Err(e) => return Err(format!("{}", e)) + }; - let json_data: CustomGetPostsResponse = serde_json::from_str(&response)?; + let json_data: CustomGetPostsResponse = match serde_json::from_str(&response) { + Ok(data) => data, + Err(e) => return Err(format!("{}", e)) + }; Ok(json_data.posts.iter().filter(|post| { post.post.featured_local @@ -173,22 +208,31 @@ impl Lemmy { ) } - pub(crate) async fn get_communities(&self) -> Result, Box> { + pub(crate) async fn get_communities(&self) -> Result, String> { let list_params = ListCommunities { type_: Some(ListingType::Local), ..Default::default() }; - let response = HTTP_CLIENT + let response = match HTTP_CLIENT .get(format!("{}/api/v3/community/list", &self.instance)) .bearer_auth(&self.jwt_token.to_string()) .query(&list_params) .send() - .await? - .text() - .await?; + .await { + Ok(data) => { + match data.text().await { + Ok(data) => data, + Err(e) => return Err(format!("{}", e)) + } + }, + Err(e) => return Err(format!("{}", e)) + }; - let json_data: CustomListCommunitiesResponse = serde_json::from_str(&response)?; + let json_data: CustomListCommunitiesResponse = match serde_json::from_str(&response) { + Ok(data) => data, + Err(e) => return Err(format!("{}", e)) + }; let mut communities: HashMap = HashMap::new(); for community_view in json_data.communities { diff --git a/src/main.rs b/src/main.rs index 76e5ddf..552bc0f 100644 --- a/src/main.rs +++ b/src/main.rs @@ -69,6 +69,16 @@ pub(crate) enum Message { Error(String), } +impl Message { + pub(crate) fn content(&self) -> String { + match self { + Message::Info(msg) => msg.clone(), + Message::Warning(msg) => msg.clone(), + Message::Error(msg) => msg.clone(), + } + } +} + #[tokio::main] async fn main() { dotenv().ok(); diff --git a/src/post_history/mod.rs b/src/post_history/mod.rs index fe27e52..180c71c 100644 --- a/src/post_history/mod.rs +++ b/src/post_history/mod.rs @@ -1,5 +1,4 @@ use std::collections::HashMap; -use std::error::Error; use std::fs; use std::fs::OpenOptions; use std::io::Write; @@ -12,10 +11,13 @@ pub(crate) struct SeriesHistory { } impl SeriesHistory { - pub(crate) fn load_history() -> Result> { + pub(crate) fn load_history() -> Result { match Path::new("history.toml").exists() { true => { - let file_contents: String = fs::read_to_string("history.toml")?; + let file_contents: String = match fs::read_to_string("history.toml") { + Ok(data) => data, + Err(e) => return Err(format!("{}", e)), + }; let history: Result = match file_contents.len() { 0 => return Ok(SeriesHistory { @@ -26,7 +28,7 @@ impl SeriesHistory { match history { Ok(data) => Ok(data), - Err(e) => Err(Box::new(e)) + Err(e) => Err(format!("{}", e)) } }, false => { diff --git a/src/tui/mod.rs b/src/tui/mod.rs index 8bb41c3..35245fc 100644 --- a/src/tui/mod.rs +++ b/src/tui/mod.rs @@ -79,10 +79,10 @@ async fn print_info<'a>(data: RwLockReadGuard<'a, SharedData>, min_len_series: & }); println!("{:#<1$}", "", separator_width); for error in data.get_messages(true, true, false).iter() { - println!("{}", error); + println!("{}", error.content()); } println!("{:#<1$}", "", separator_width); for message in data.get_messages(false, false, false).iter() { - println!("{}", message); + println!("{}", message.content()); } } \ No newline at end of file