Automatic Post Pinning and Unpinning, Closes #2

This commit is contained in:
Neshura 2023-08-03 23:34:23 +02:00
parent 7f717d30b7
commit 8c58088321
Signed by: Neshura
GPG key ID: B6983AAA6B9A7A6C
2 changed files with 142 additions and 32 deletions

View file

@ -1,7 +1,7 @@
use std::{fs::{self, OpenOptions}, path::Path, io::Write, thread::sleep, time};
use lemmy_api_common::{sensitive::Sensitive, post::CreatePost, community::{ListCommunities, ListCommunitiesResponse}};
use lemmy_db_schema::{newtypes::{LanguageId, CommunityId}, ListingType};
use lemmy_db_schema::{newtypes::{LanguageId, CommunityId, PostId}, ListingType};
use serde_derive::{Deserialize, Serialize};
use url::Url;
@ -98,8 +98,8 @@ impl Config {
}
pub(crate) fn check_feeds(&mut self, post_history: &mut Vec<PrevPost>
, community_ids: &CommunitiesVector, auth: &Sensitive<String>) -> Result<Vec<CreatePost>, reqwest::Error> {
let mut post_queue: Vec<CreatePost> = vec![];
, community_ids: &CommunitiesVector, auth: &Sensitive<String>) -> Result<Vec<(CreatePost, (Option<usize>, usize, String))>, reqwest::Error> {
let mut post_queue: Vec<(CreatePost, (Option<usize>, usize, String))> = vec![];
match self.feeds.iter().map(|feed| {
let res = CLIENT
@ -135,18 +135,14 @@ impl Config {
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
auth: auth.clone(),
};
post_queue.push(new_post);
match prev_post_idx {
Some(idx) => {
post_history[idx].title = data.title;
post_history[idx].last_post_url = item.url.clone();
}
None => post_history.push(PrevPost {
id: feed.id,
title: data.title,
last_post_url: item.url.clone(),
}),
}
let prev_data = (
prev_post_idx,
feed.id,
data.title
);
post_queue.push((new_post, prev_data));
}
sleep(time::Duration::from_millis(100)); // Should prevent dos-ing J-Novel servers
return Ok(());
@ -155,7 +151,6 @@ impl Config {
Err(e) => return Err(e)
}
PrevPost::save(&post_history);
return Ok(post_queue);
}
}
@ -184,7 +179,7 @@ pub(crate) enum LemmyCommunities {
aobprepub,
aoblightnovel,
aobmanga,
aobanime
metadiscussions
}
pub_struct!(FeedRedditSettings {
@ -195,6 +190,7 @@ pub_struct!(FeedRedditSettings {
// Posts structs
pub_struct!(PrevPost {
id: usize,
post_id: PostId,
title: String,
last_post_url: String,
});
@ -206,13 +202,13 @@ impl PrevPost {
if Path::new("posts.json").exists() {
let file_contents = match fs::read_to_string("posts.json") {
Ok(data) => data,
Err(e) => panic!("ERROR: secrets.json could not be read:\n\n{:#?}", e),
Err(e) => panic!("ERROR: posts.json could not be read:\n\n{:#?}", e),
};
if file_contents.len() > 0 {
let history_parse: Vec<PrevPost> = match serde_json::from_str(&file_contents) {
Ok(data) => data,
Err(e) => panic!("ERROR: secrets.json could not be parsed:\n\n{:#?}", e),
Err(e) => panic!("ERROR: posts.json could not be parsed:\n\n{:#?}", e),
};
history = history_parse;
}

View file

@ -1,16 +1,16 @@
use chrono::{Utc, DateTime, NaiveTime, NaiveDateTime, Timelike, NaiveDate};
use config::{Config, PrevPost, Secrets, CommunitiesVector};
use chrono::{Utc, NaiveDateTime};
use config::{Config, PrevPost, Secrets, CommunitiesVector, LemmyCommunities};
use lemmy_api_common::{
person::{Login, LoginResponse},
post::{CreatePost, GetPosts, GetPostsResponse},
sensitive::Sensitive,
post::{CreatePost, GetPosts, GetPostsResponse, FeaturePost},
sensitive::Sensitive, lemmy_db_views::structs::PostView,
};
use lemmy_db_schema::{
ListingType, SortType,
ListingType, SortType, PostFeatureType, newtypes::CommunityId,
};
use once_cell::sync::Lazy;
use reqwest::{blocking::Client, StatusCode};
use std::{thread::sleep, time, io, error::Error};
use std::{thread::sleep, time, collections::HashMap};
mod config;
@ -80,17 +80,102 @@ impl Bot {
/// * `post_data` : Object of type [CreatePost] containing post info
/// * `return` : Returns true if Post was succesful, false otherwise
#[warn(unused_results)]
pub(crate) fn post(&mut self, post_data: CreatePost) -> Result<(), reqwest::Error> {
pub(crate) fn post(&mut self, post_data: CreatePost) -> Result<PostView, reqwest::Error> {
let res = CLIENT
.post(self.config.instance.clone() + "/api/v3/post")
.json(&post_data)
.send()?;
// TODO: process res to get info about if post was successfuly (mostly if jwt token was valid)
// TODO: process res to get info about if post was successfuly (mostly if jwt token was valid)
let ret: PostView = serde_json::from_str::<HashMap<&str, PostView>>(res.text()?.as_str()).unwrap().remove("post_view").unwrap();
return Ok(ret);
}
#[warn(unused_results)]
pub(crate) fn pin_new(&mut self, old_post: &Option<usize>, new_post: &PostView) -> Result<(), reqwest::Error> {
match old_post {
Some(id) => {
let remove_community_pin = FeaturePost {
post_id: self.post_history[*id].post_id,
featured: false,
feature_type: PostFeatureType::Community,
auth: self.auth.clone()
};
let _ = self.pin(remove_community_pin);
}
None => {
println!("Unable to unpin old post, please do so manually");
}
}
// Unpin the other chapter post on local
// Get all local pinned posts first
// Filter out any post made in the meta community (Community ID)
let get_params = GetPosts {
auth: Some(self.auth.clone()),
..Default::default()
};
let post_list_json = CLIENT
.get(self.config.instance.clone() + "/api/v3/post/list")
.query(&get_params)
.send()?
.text()?;
let post_list: GetPostsResponse = serde_json::from_str(post_list_json.as_str()).unwrap();
let mut meta_community: CommunityId = CommunityId(15);
self.community_ids.ids.iter().for_each(|(id, name)| {
if name == &LemmyCommunities::metadiscussions.to_string() {
meta_community = id.clone();
}
});
for post_view in post_list.posts {
if post_view.community.id != meta_community && post_view.post.featured_local {
let remove_local_pin = FeaturePost {
post_id: post_view.post.id,
featured: false,
feature_type: PostFeatureType::Local,
auth: self.auth.clone()
};
let _ = self.pin(remove_local_pin);
}
}
let pin_new_community = FeaturePost {
post_id: new_post.post.id,
featured: true,
feature_type: PostFeatureType::Community,
auth: self.auth.clone(),
};
let _ = self.pin(pin_new_community);
let pin_new_local = FeaturePost {
post_id: new_post.post.id,
featured: true,
feature_type: PostFeatureType::Local,
auth: self.auth.clone(),
};
let _ = self.pin(pin_new_local);
return Ok(());
}
pub(crate) fn pin (&mut self, pin_data: FeaturePost) -> Result<bool, reqwest::Error> {
let res = CLIENT
.post(self.config.instance.clone() + "/api/v3/post/feature")
.json(&pin_data)
.send()?;
let ret: PostView = serde_json::from_str::<HashMap<&str, PostView>>(res.text()?.as_str()).unwrap().remove("post_view").unwrap();
return Ok(ret.post.featured_local);
}
#[warn(unused_results)]
pub(crate) fn run_once(&mut self, prev_time: &mut NaiveDateTime) -> Result<(), reqwest::Error> {
println!("{:#<1$}", "", 30);
@ -107,17 +192,46 @@ impl Bot {
// Start the polling process
// Get all feed URLs (use cache)
println!("Checking Feeds");
let post_queue: Vec<CreatePost> = self.config.check_feeds(&mut self.post_history, &self.community_ids, &self.auth)?;
let post_queue: Vec<(CreatePost, (Option<usize>, usize, String))> = self.config.check_feeds(&mut self.post_history, &self.community_ids, &self.auth)?;
println!("Done!");
post_queue.iter().for_each(|post| {
post_queue.iter().for_each(|(post, (prev_idx, feed_id, feed_title))| {
println!("Posting: {}", post.name);
let post_data: PostView;
loop {
if self.post(post.clone()).is_ok() {break};
let post_res = self.post(post.clone());
if post_res.is_ok() {
post_data = post_res.unwrap();
break
};
println!("Post attempt failed, retrying");
}
loop {
if self.pin_new(prev_idx, &post_data).is_ok() {break};
println!("Pin attempt failed, retrying");
}
// Move current post to old post list
match prev_idx {
Some(idx) => {
self.post_history[*idx].title = feed_title.clone();
self.post_history[*idx].post_id = post_data.post.id;
self.post_history[*idx].last_post_url = post.url.clone().unwrap().to_string();
}
None => self.post_history.push(PrevPost {
id: feed_id.clone(),
post_id: post_data.post.id,
title: feed_title.clone(),
last_post_url: post.url.clone().unwrap().to_string(),
}),
}
});
PrevPost::save(&self.post_history);
return Ok(());
}
@ -176,7 +290,7 @@ fn run_bot() {
let mut this = Bot::new();
match this.login() {
Ok(_) => {
this.community_ids.load(&this.auth, &this.config.instance);
let _ = this.community_ids.load(&this.auth, &this.config.instance);
// Enter a loop (not for debugging)
loop {
@ -186,7 +300,7 @@ fn run_bot() {
while !this.run_once(&mut old).is_ok() && loop_breaker <= 3 {
println!("Unable to complete Bot cycle, retrying with fresh login credentials");
if this.login().is_ok() {
this.community_ids.load(&this.auth, &this.config.instance);
let _ = this.community_ids.load(&this.auth, &this.config.instance);
}
sleep(time::Duration::from_secs(10));
loop_breaker += 1;