Removes the TUI in favor of event based logging #15

Merged
Neshura merged 17 commits from remove_tui into main 2023-12-30 00:28:47 +00:00
6 changed files with 267 additions and 220 deletions
Showing only changes of commit 8c1da63e0c - Show all commits

View file

@ -1,15 +1,15 @@
use std::collections::HashMap;
use std::sync::{Arc};
use crate::config::{Config, PostBody, SeriesConfig};
use crate::jnovel::PostInfo;
use crate::lemmy::Lemmy;
use crate::post_history::SeriesHistory;
use crate::{jnovel, lemmy, write_error, write_info, write_warn, SharedData};
use chrono::{DateTime, Duration, Utc};
use lemmy_api_common::post::CreatePost;
use lemmy_db_schema::newtypes::{CommunityId, LanguageId};
use lemmy_db_schema::PostFeatureType;
use tokio::sync::{RwLock};
use crate::{jnovel, lemmy, SharedData, write_error, write_info, write_warn};
use crate::config::{Config, PostBody, SeriesConfig};
use crate::jnovel::PostInfo;
use crate::lemmy::{Lemmy};
use crate::post_history::SeriesHistory;
use std::collections::HashMap;
use std::sync::Arc;
use tokio::sync::RwLock;
use tokio::time::sleep;
pub(crate) async fn run(data: Arc<RwLock<SharedData>>) {
@ -81,7 +81,7 @@ pub(crate) async fn run(data: Arc<RwLock<SharedData>>) {
Ok(data) => data,
Err(_) => {
login_error = true;
continue
continue;
}
};
let message = "Communities reloaded".to_owned();
@ -100,9 +100,12 @@ pub(crate) async fn run(data: Arc<RwLock<SharedData>>) {
let series = read.config.series.clone();
drop(read);
for series in series {
if handle_series(&series, &communities, &lemmy, &data).await.is_err() {
if handle_series(&series, &communities, &lemmy, &data)
.await
.is_err()
{
login_error = true;
continue
continue;
};
}
}
@ -124,7 +127,7 @@ async fn idle(data: &Arc<RwLock<SharedData>>) {
Err(e) => {
let err_msg = format!("{e}");
write_error(err_msg);
},
}
}
};
@ -133,36 +136,34 @@ async fn idle(data: &Arc<RwLock<SharedData>>) {
}
}
async fn handle_series(
series: &SeriesConfig,
communities: &HashMap<String, CommunityId>,
lemmy: &Lemmy,
data: &Arc<RwLock<SharedData>>,
) -> Result<(), ()> {
async fn handle_series(series: &SeriesConfig, communities: &HashMap<String, CommunityId>, lemmy: &Lemmy, data: &Arc<RwLock<SharedData>>) -> Result<(), ()> {
let mut post_list = match jnovel::check_feed(series.slug.as_str(), series.parted).await {
Ok(data) => data,
Err(_) => return Err(()),
};
for (index, post_info) in post_list.clone().iter().enumerate() { // todo .clone() likely not needed
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()) {
if read.post_history.check_for_post(
series.slug.as_str(),
post_part_info.as_string().as_str(),
post_lemmy_info.title.as_str(),
) {
let message = format!("Skipping '{}' since already posted", post_lemmy_info.title);
write_info(message);
post_list.remove(index);
continue
continue;
}
}
let post_series_config = match post_info {
PostInfo::Chapter {..} => {&series.prepub_community},
PostInfo::Volume {..} => {&series.volume_community}
PostInfo::Chapter { .. } => &series.prepub_community,
PostInfo::Volume { .. } => &series.volume_community,
};
let community_id = *communities
@ -185,23 +186,45 @@ async fn handle_series(
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());
let info = format!(
"Posting '{}' to {}",
post_lemmy_info.title.as_str(),
post_series_config.name.as_str()
);
write_info(info);
let post_id = lemmy.post(post_data).await?;
{
let read = data.read().await;
if post_series_config.pin_settings.pin_new_post_community && !read.config.protected_communities.contains(&post_series_config.name) {
let info = format!("Pinning '{}' to {}", post_lemmy_info.title, post_series_config.name.as_str());
if post_series_config.pin_settings.pin_new_post_community
&& !read
.config
.protected_communities
.contains(&post_series_config.name)
{
let info = format!(
"Pinning '{}' to {}",
post_lemmy_info.title,
post_series_config.name.as_str()
);
write_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
.unpin(community_pinned_post.post.id, PostFeatureType::Community)
.await?;
}
lemmy.pin(post_id, PostFeatureType::Community).await?;
} else if read.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);
} else if read
.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
);
write_warn(message);
}
}
@ -213,13 +236,18 @@ async fn handle_series(
let pinned_posts = lemmy.get_local_pinned().await?;
if !pinned_posts.is_empty() {
for pinned_post in pinned_posts {
if read.config.protected_communities.contains(&pinned_post.community.name) {
continue
}
else {
if read
.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
.unpin(community_pinned_post.post.id, PostFeatureType::Local)
.await?;
break;
}
}
}
@ -231,21 +259,16 @@ async fn handle_series(
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
}
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);
write
.post_history
.set_series(series.slug.as_str(), series_history);
write.post_history.save_history();
}
Ok(())
}

View file

@ -1,6 +1,6 @@
use crate::config::PostBody::Description;
use lemmy_api_common::sensitive::Sensitive;
use serde_derive::{Deserialize, Serialize};
use crate::config::PostBody::Description;
#[derive(Serialize, Deserialize, Clone, Debug)]
pub(crate) struct Config {
@ -58,7 +58,7 @@ impl Default for Config {
status_post_url: None,
config_reload_seconds: 21600,
protected_communities: vec![],
series: vec![]
series: vec![],
}
}
}

View file

@ -1,12 +1,12 @@
use crate::jnovel::PartInfo::{NoParts, Part};
use crate::jnovel::PostInfo::{Chapter, Volume};
use crate::{write_error, HTTP_CLIENT};
use chrono::{DateTime, Duration, Utc};
use serde_derive::{Deserialize, Serialize};
use std::cmp::Ordering;
use std::collections::HashMap;
use std::ops::Sub;
use chrono::{DateTime, Duration, Utc};
use serde_derive::{Deserialize, Serialize};
use url::Url;
use crate::{HTTP_CLIENT, write_error};
use crate::jnovel::PartInfo::{NoParts, Part};
use crate::jnovel::PostInfo::{Chapter, Volume};
static PAST_DAYS_ELIGIBLE: u8 = 4;
@ -61,7 +61,6 @@ pub(crate) struct VolumeDetail {
pub(crate) cover: Cover,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
pub(crate) struct ChapterDetail {
pub(crate) title: String,
@ -105,9 +104,13 @@ impl PartialEq for PartInfo {
impl PartialOrd for PartInfo {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
if self.gt(other) { Some(Ordering::Greater) }
else if self.eq(other) { Some(Ordering::Equal) }
else { Some(Ordering::Less) }
if self.gt(other) {
Some(Ordering::Greater)
} else if self.eq(other) {
Some(Ordering::Equal)
} else {
Some(Ordering::Less)
}
}
fn lt(&self, other: &Self) -> bool {
@ -135,22 +138,33 @@ impl PartialOrd for PartInfo {
#[derive(Debug, Clone)]
pub(crate) enum PostInfo {
Chapter { part: PartInfo, lemmy_info: LemmyPostInfo },
Volume { part: PartInfo, description: String, lemmy_info: LemmyPostInfo },
Chapter {
part: PartInfo,
lemmy_info: LemmyPostInfo,
},
Volume {
part: PartInfo,
description: String,
lemmy_info: LemmyPostInfo,
},
}
impl PostInfo {
pub(crate) fn get_part_info(&self) -> PartInfo {
match self {
Chapter {part: part_info, ..} => *part_info,
Volume {part: part_info, ..} => *part_info
Chapter {
part: part_info, ..
} => *part_info,
Volume {
part: part_info, ..
} => *part_info,
}
}
pub(crate) fn get_lemmy_info(&self) -> LemmyPostInfo {
match self {
Chapter { lemmy_info, .. } => lemmy_info.clone(),
Volume {lemmy_info, ..} => lemmy_info.clone()
Volume { lemmy_info, .. } => lemmy_info.clone(),
}
}
@ -180,9 +194,13 @@ impl PartialEq for PostInfo {
impl PartialOrd for PostInfo {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
if self.gt(other) { Some(Ordering::Greater) }
else if self.eq(other) { Some(Ordering::Equal) }
else { Some(Ordering::Less) }
if self.gt(other) {
Some(Ordering::Greater)
} else if self.eq(other) {
Some(Ordering::Equal)
} else {
Some(Ordering::Less)
}
}
fn lt(&self, other: &Self) -> bool {
@ -226,21 +244,20 @@ pub(crate) async fn check_feed(series_slug: &str, series_has_parts: bool) -> Res
let response = match HTTP_CLIENT
.get(api_url!() + "/series/" + series_slug + "/volumes?format=json")
.send()
.await {
Ok(data) => {
match data.text().await {
.await
{
Ok(data) => match data.text().await {
Ok(data) => data,
Err(e) => {
let err_msg = format!("{e}");
write_error(err_msg);
return Err(())
}
return Err(());
}
},
Err(e) => {
let err_msg = format!("{e}");
write_error(err_msg);
return Err(())
return Err(());
}
};
@ -249,7 +266,7 @@ pub(crate) async fn check_feed(series_slug: &str, series_has_parts: bool) -> Res
Err(e) => {
let err_msg = format!("{e}");
write_error(err_msg);
return Err(())
return Err(());
}
};
volume_brief_data.volumes.reverse(); // Makes breaking out of the volume loop easier
@ -272,11 +289,12 @@ pub(crate) async fn check_feed(series_slug: &str, series_has_parts: bool) -> Res
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]
part_number = Some(
splits[index + 1]
.parse::<u8>()
.expect("Split Element after 'Part' should always be a number"));
.expect("Split Element after 'Part' should always be a number"),
);
break;
}
}
@ -286,14 +304,17 @@ pub(crate) async fn check_feed(series_slug: &str, series_has_parts: bool) -> Res
None => {
println!("No Part found, assuming 1");
new_part_info = Part(1);
},
}
}
else {
} else {
new_part_info = NoParts;
}
let post_url = format!("{}/series/{series_slug}#volume-{}", jnc_base_url!(), volume.number);
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(),
@ -321,7 +342,7 @@ pub(crate) async fn check_feed(series_slug: &str, series_has_parts: bool) -> Res
if let Some(prepub_info) = get_latest_prepub(&volume.slug).await? {
let prepub_post_info = Chapter {
part: new_part_info,
lemmy_info: prepub_info
lemmy_info: prepub_info,
};
prepub_map
@ -346,31 +367,29 @@ async fn get_latest_prepub(volume_slug: &str) -> Result<Option<LemmyPostInfo>, (
let response = match HTTP_CLIENT
.get(api_url!() + "/volumes/" + volume_slug + "/parts?format=json")
.send()
.await {
Ok(data) => {
match data.text().await {
.await
{
Ok(data) => match data.text().await {
Ok(data) => data,
Err(e) => {
let err_msg = format!("{e}");
write_error(err_msg);
return Err(())
}
return Err(());
}
},
Err(e) => {
let err_msg = format!("{e}");
write_error(err_msg);
return Err(())
return Err(());
}
};
let mut volume_prepub_parts_data: ChapterWrapper = match serde_json::from_str(&response) {
Ok(data) => data,
Err(e) => {
let err_msg = format!("{e}");
write_error(err_msg);
return Err(())
return Err(());
}
};
volume_prepub_parts_data.parts.reverse(); // Makes breaking out of the parts loop easier
@ -380,10 +399,9 @@ async fn get_latest_prepub(volume_slug: &str) -> Result<Option<LemmyPostInfo>, (
for prepub_part in volume_prepub_parts_data.parts.iter() {
let publishing_date = DateTime::parse_from_rfc3339(&prepub_part.launch).unwrap();
if publishing_date > Utc::now() {
break
}
else if publishing_date < Utc::now().sub(Duration::days(PAST_DAYS_ELIGIBLE as i64)) {
continue
break;
} else if publishing_date < Utc::now().sub(Duration::days(PAST_DAYS_ELIGIBLE as i64)) {
continue;
}
let post_url = format!("{}/read/{}", jnc_base_url!(), prepub_part.slug);
@ -391,7 +409,6 @@ async fn get_latest_prepub(volume_slug: &str) -> Result<Option<LemmyPostInfo>, (
title: prepub_part.title.clone(),
url: Url::parse(&post_url).unwrap(),
});
}
Ok(post_details)

View file

@ -1,4 +1,5 @@
use std::collections::HashMap;
use crate::config::Config;
use crate::{write_error, HTTP_CLIENT};
use lemmy_api_common::community::{ListCommunities, ListCommunitiesResponse};
use lemmy_api_common::lemmy_db_views::structs::PostView;
use lemmy_api_common::person::{Login, LoginResponse};
@ -7,8 +8,7 @@ use lemmy_api_common::sensitive::Sensitive;
use lemmy_db_schema::newtypes::{CommunityId, PostId};
use lemmy_db_schema::{ListingType, PostFeatureType};
use reqwest::StatusCode;
use crate::config::Config;
use crate::{HTTP_CLIENT, write_error};
use std::collections::HashMap;
pub(crate) struct Lemmy {
jwt_token: Sensitive<String>,
@ -26,18 +26,22 @@ pub(crate) async fn login(config: &Config) -> Result<Lemmy, ()> {
.post(config.instance.to_owned() + "/api/v3/user/login")
.json(&login_params)
.send()
.await {
.await
{
Ok(data) => data,
Err(e) => {
let err_msg = format!("{e}");
write_error(err_msg);
return Err(())
return Err(());
}
};
match response.status() {
StatusCode::OK => {
let data: LoginResponse = response.json().await.expect("Successful Login Request should return JSON");
let data: LoginResponse = response
.json()
.await
.expect("Successful Login Request should return JSON");
match data.jwt {
Some(token) => Ok(Lemmy {
jwt_token: token.clone(),
@ -49,7 +53,7 @@ pub(crate) async fn login(config: &Config) -> Result<Lemmy, ()> {
Err(())
}
}
},
}
status => {
let err_msg = format!("Unexpected HTTP Status '{}' during Login", status);
write_error(err_msg);
@ -65,21 +69,20 @@ impl Lemmy {
.bearer_auth(&self.jwt_token.to_string())
.json(&post)
.send()
.await {
Ok(data) => {
match data.text().await {
.await
{
Ok(data) => match data.text().await {
Ok(data) => data,
Err(e) => {
let err_msg = format!("{e}");
write_error(err_msg);
return Err(())
}
return Err(());
}
},
Err(e) => {
let err_msg = format!("{e}");
write_error(err_msg);
return Err(())
return Err(());
}
};
@ -88,7 +91,7 @@ impl Lemmy {
Err(e) => {
let err_msg = format!("{e}");
write_error(err_msg);
return Err(())
return Err(());
}
};
@ -101,21 +104,20 @@ impl Lemmy {
.bearer_auth(&self.jwt_token.to_string())
.json(&params)
.send()
.await {
Ok(data) => {
match data.text().await {
.await
{
Ok(data) => match data.text().await {
Ok(data) => data,
Err(e) => {
let err_msg = format!("{e}");
write_error(err_msg);
return Err(())
}
return Err(());
}
},
Err(e) => {
let err_msg = format!("{e}");
write_error(err_msg);
return Err(())
return Err(());
}
};
@ -124,7 +126,7 @@ impl Lemmy {
Err(e) => {
let err_msg = format!("{e}");
write_error(err_msg);
return Err(())
return Err(());
}
};
@ -161,21 +163,20 @@ impl Lemmy {
.bearer_auth(&self.jwt_token.to_string())
.query(&list_params)
.send()
.await {
Ok(data) => {
match data.text().await {
.await
{
Ok(data) => match data.text().await {
Ok(data) => data,
Err(e) => {
let err_msg = format!("{e}");
write_error(err_msg);
return Err(())
}
return Err(());
}
},
Err(e) => {
let err_msg = format!("{e}");
write_error(err_msg);
return Err(())
return Err(());
}
};
@ -184,16 +185,16 @@ impl Lemmy {
Err(e) => {
let err_msg = format!("{e}");
write_error(err_msg);
return Err(())
return Err(());
}
};
Ok(json_data.posts.iter().filter(|post| {
post.post.featured_community
})
Ok(json_data
.posts
.iter()
.filter(|post| post.post.featured_community)
.cloned()
.collect()
)
.collect())
}
pub(crate) async fn get_local_pinned(&self) -> Result<Vec<PostView>, ()> {
@ -207,21 +208,20 @@ impl Lemmy {
.bearer_auth(&self.jwt_token.to_string())
.query(&list_params)
.send()
.await {
Ok(data) => {
match data.text().await {
.await
{
Ok(data) => match data.text().await {
Ok(data) => data,
Err(e) => {
let err_msg = format!("{e}");
write_error(err_msg);
return Err(())
}
return Err(());
}
},
Err(e) => {
let err_msg = format!("{e}");
write_error(err_msg);
return Err(())
return Err(());
}
};
@ -230,16 +230,16 @@ impl Lemmy {
Err(e) => {
let err_msg = format!("{e}");
write_error(err_msg);
return Err(())
return Err(());
}
};
Ok(json_data.posts.iter().filter(|post| {
post.post.featured_local
})
Ok(json_data
.posts
.iter()
.filter(|post| post.post.featured_local)
.cloned()
.collect()
)
.collect())
}
pub(crate) async fn get_communities(&self) -> Result<HashMap<String, CommunityId>, ()> {
@ -253,21 +253,20 @@ impl Lemmy {
.bearer_auth(&self.jwt_token.to_string())
.query(&list_params)
.send()
.await {
Ok(data) => {
match data.text().await {
.await
{
Ok(data) => match data.text().await {
Ok(data) => data,
Err(e) => {
let err_msg = format!("{e}");
write_error(err_msg);
return Err(())
}
return Err(());
}
},
Err(e) => {
let err_msg = format!("{e}");
write_error(err_msg);
return Err(())
return Err(());
}
};
@ -276,7 +275,7 @@ impl Lemmy {
Err(e) => {
let err_msg = format!("{e}");
write_error(err_msg);
return Err(())
return Err(());
}
};

View file

@ -1,19 +1,19 @@
use chrono::{DateTime, Duration, Utc};
use once_cell::sync::Lazy;
use reqwest::{Client};
use std::{collections::HashMap};
use std::fmt::Debug;
use std::sync::{Arc};
use log::{error, warn, info, LevelFilter};
use tokio::sync::{RwLock};
use systemd_journal_logger::{connected_to_journal, JournalLog};
use tokio::time::sleep;
use crate::config::Config;
use crate::post_history::{SeriesHistory};
use crate::post_history::SeriesHistory;
use chrono::{DateTime, Duration, Utc};
use log::{error, info, warn, LevelFilter};
use once_cell::sync::Lazy;
use reqwest::Client;
use std::collections::HashMap;
use std::fmt::Debug;
use std::sync::Arc;
use systemd_journal_logger::{connected_to_journal, JournalLog};
use tokio::sync::RwLock;
use tokio::time::sleep;
mod bot;
mod config;
mod jnovel;
mod bot;
mod lemmy;
mod post_history;
@ -67,7 +67,10 @@ impl SharedData {
#[tokio::main]
async fn main() {
JournalLog::new().expect("Systemd-Logger crate error").install().expect("Systemd-Logger crate error");
JournalLog::new()
.expect("Systemd-Logger crate error")
.install()
.expect("Systemd-Logger crate error");
log::set_max_level(LevelFilter::Info);
let mut data = SharedData::new();
@ -87,10 +90,15 @@ async fn main() {
let err_msg = "Bot crashed due to unknown Error, restarting thread after wait...";
match connected_to_journal() {
true => error!("[ERROR] {err_msg}"),
false => println!("[ERROR] {err_msg}")
false => println!("[ERROR] {err_msg}"),
}
}
sleep(Duration::seconds(5).to_std().expect("Conversion should always work since static")).await;
sleep(
Duration::seconds(5)
.to_std()
.expect("Conversion should always work since static"),
)
.await;
}
}

View file

@ -1,6 +1,6 @@
use std::collections::HashMap;
use serde_derive::{Deserialize, Serialize};
use crate::write_error;
use serde_derive::{Deserialize, Serialize};
use std::collections::HashMap;
#[derive(Serialize, Deserialize, Default, Clone, Debug)]
pub(crate) struct SeriesHistory {
@ -25,7 +25,7 @@ impl SeriesHistory {
pub(crate) fn check_for_post(&self, series: &str, part: &str, title: &str) -> bool {
if let Some(series_map) = self.series.get(series) {
if let Some(part_info) = series_map.parts.get(part) {
return part_info.volume == title || part_info.chapter == title
return part_info.volume == title || part_info.chapter == title;
}
}
false
@ -35,15 +35,15 @@ impl SeriesHistory {
match self.series.get(series) {
Some(history) => history.clone(),
None => PostHistory {
parts: HashMap::new()
}
parts: HashMap::new(),
},
}
}
pub(crate) fn set_series(&mut self, series: &str, data: PostHistory) {
self.series.entry(series.to_owned()).and_modify(|val| {
*val = data.clone()
})
self.series
.entry(series.to_owned())
.and_modify(|val| *val = data.clone())
.or_insert(data);
}
}
@ -60,14 +60,14 @@ impl PostHistory {
None => PostHistoryInner {
volume: "".to_owned(),
chapter: "".to_owned(),
}
},
}
}
pub(crate) fn set_part(&mut self, part: &str, data: PostHistoryInner) {
self.parts.entry(part.to_owned()).and_modify(|val| {
*val = data.clone()
})
self.parts
.entry(part.to_owned())
.and_modify(|val| *val = data.clone())
.or_insert(data);
}
}