Threading Implementation for higher stability
This commit is contained in:
parent
8b3e6a8380
commit
211b44978a
5 changed files with 244 additions and 159 deletions
12
Cargo.lock
generated
12
Cargo.lock
generated
|
@ -301,6 +301,7 @@ dependencies = [
|
||||||
"serde_derive",
|
"serde_derive",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
"strum_macros",
|
"strum_macros",
|
||||||
|
"tokio",
|
||||||
"url",
|
"url",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@ -1283,9 +1284,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "pin-project-lite"
|
name = "pin-project-lite"
|
||||||
version = "0.2.10"
|
version = "0.2.12"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "4c40d25201921e5ff0c862a505c6557ea88568a4e3ace775ab55e93f2f4f9d57"
|
checksum = "12cc1b0bf1727a77a54b6654e7b5f1af8604923edc8b81885f8ec92f9e3f0a05"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "pin-utils"
|
name = "pin-utils"
|
||||||
|
@ -1880,11 +1881,10 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tokio"
|
name = "tokio"
|
||||||
version = "1.29.1"
|
version = "1.32.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "532826ff75199d5833b9d2c5fe410f29235e25704ee5f0ef599fb51c21f4a4da"
|
checksum = "17ed6077ed6cd6c74735e21f37eb16dc3935f96878b1fe961074089cc80893f9"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"autocfg",
|
|
||||||
"backtrace",
|
"backtrace",
|
||||||
"bytes",
|
"bytes",
|
||||||
"libc",
|
"libc",
|
||||||
|
@ -1893,7 +1893,7 @@ dependencies = [
|
||||||
"parking_lot",
|
"parking_lot",
|
||||||
"pin-project-lite",
|
"pin-project-lite",
|
||||||
"signal-hook-registry",
|
"signal-hook-registry",
|
||||||
"socket2 0.4.9",
|
"socket2 0.5.3",
|
||||||
"tokio-macros",
|
"tokio-macros",
|
||||||
"windows-sys",
|
"windows-sys",
|
||||||
]
|
]
|
||||||
|
|
|
@ -15,4 +15,5 @@ serde = "1.0.164"
|
||||||
serde_derive = "1.0.164"
|
serde_derive = "1.0.164"
|
||||||
serde_json = "1.0.97"
|
serde_json = "1.0.97"
|
||||||
strum_macros = "0.25.0"
|
strum_macros = "0.25.0"
|
||||||
|
tokio = "1.32.0"
|
||||||
url = "2.4.0"
|
url = "2.4.0"
|
||||||
|
|
6
deploy.sh
Normal file
6
deploy.sh
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
#!/bin/bash
|
||||||
|
## deploy to machine as automod.new
|
||||||
|
## stop automod service
|
||||||
|
## mv automod.new to automod
|
||||||
|
## restart automod service
|
||||||
|
## idea: websocket event?
|
|
@ -1,11 +1,25 @@
|
||||||
use std::{fs::{self, OpenOptions}, path::Path, io::Write, thread::sleep, time, error::Error};
|
use std::{
|
||||||
|
error::Error,
|
||||||
|
fs::{self, OpenOptions},
|
||||||
|
io::Write,
|
||||||
|
path::Path,
|
||||||
|
thread::sleep,
|
||||||
|
time,
|
||||||
|
};
|
||||||
|
|
||||||
use lemmy_api_common::{sensitive::Sensitive, post::CreatePost, community::{ListCommunities, ListCommunitiesResponse}};
|
use lemmy_api_common::{
|
||||||
use lemmy_db_schema::{newtypes::{LanguageId, CommunityId, PostId}, ListingType};
|
community::{ListCommunities, ListCommunitiesResponse},
|
||||||
|
post::CreatePost,
|
||||||
|
sensitive::Sensitive,
|
||||||
|
};
|
||||||
|
use lemmy_db_schema::{
|
||||||
|
newtypes::{CommunityId, LanguageId, PostId},
|
||||||
|
ListingType,
|
||||||
|
};
|
||||||
use serde_derive::{Deserialize, Serialize};
|
use serde_derive::{Deserialize, Serialize};
|
||||||
use url::Url;
|
use url::Url;
|
||||||
|
|
||||||
use crate::{CLIENT};
|
use crate::CLIENT;
|
||||||
|
|
||||||
macro_rules! pub_struct {
|
macro_rules! pub_struct {
|
||||||
($name:ident {$($field:ident: $t:ty,)*}) => {
|
($name:ident {$($field:ident: $t:ty,)*}) => {
|
||||||
|
@ -40,7 +54,7 @@ impl Secrets {
|
||||||
#[derive(Serialize, Deserialize, Clone, PartialEq)]
|
#[derive(Serialize, Deserialize, Clone, PartialEq)]
|
||||||
pub(crate) struct LemmyLogin {
|
pub(crate) struct LemmyLogin {
|
||||||
pub(crate) username: String,
|
pub(crate) username: String,
|
||||||
password: String
|
password: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl LemmyLogin {
|
impl LemmyLogin {
|
||||||
|
@ -49,7 +63,7 @@ impl LemmyLogin {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn get_password(&self) -> Sensitive<String> {
|
pub(crate) fn get_password(&self) -> Sensitive<String> {
|
||||||
return Sensitive::new(self.password.clone())
|
return Sensitive::new(self.password.clone());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -97,58 +111,58 @@ impl Config {
|
||||||
self.reddit_config = config_parse.reddit_config;
|
self.reddit_config = config_parse.reddit_config;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn check_feeds(&mut self, post_history: &mut Vec<PrevPost>
|
pub(crate) async fn check_feeds(
|
||||||
, community_ids: &CommunitiesVector, auth: &Sensitive<String>) -> Result<Vec<(CreatePost, (Option<usize>, usize, String))>, Box<dyn Error>> {
|
&mut self,
|
||||||
|
post_history: &mut Vec<PrevPost>,
|
||||||
|
community_ids: &CommunitiesVector,
|
||||||
|
auth: &Sensitive<String>,
|
||||||
|
) -> Result<Vec<(CreatePost, (Option<usize>, usize, String))>, Box<dyn Error>> {
|
||||||
let mut post_queue: Vec<(CreatePost, (Option<usize>, usize, String))> = vec![];
|
let mut post_queue: Vec<(CreatePost, (Option<usize>, usize, String))> = vec![];
|
||||||
|
|
||||||
match self.feeds.iter().map(|feed| {
|
let mut i = 0;
|
||||||
|
while i < self.feeds.len() {
|
||||||
|
let feed = &self.feeds[i];
|
||||||
|
|
||||||
let res = CLIENT
|
let res = CLIENT
|
||||||
.get(feed.feed_url.clone())
|
.get(feed.feed_url.clone())
|
||||||
.send()?.text()?;
|
.send()
|
||||||
|
.await?
|
||||||
|
.text()
|
||||||
|
.await?;
|
||||||
|
|
||||||
let data: FeedData = serde_json::from_str(&res).unwrap();
|
let data: FeedData = serde_json::from_str(&res).unwrap();
|
||||||
|
|
||||||
let mut prev_post_idx: Option<usize> = None;
|
let mut prev_post_idx: Option<usize> = None;
|
||||||
let mut do_post = true;
|
let mut do_post = true;
|
||||||
post_history
|
post_history.iter().enumerate().for_each(|(idx, post)| {
|
||||||
.iter()
|
if &post.last_post_url == &data.items[0].url {
|
||||||
.enumerate()
|
do_post = false;
|
||||||
.for_each(|(idx, post)| {
|
} else if &post.title == &data.title {
|
||||||
if &post.last_post_url == &data.items[0].url {
|
prev_post_idx = Some(idx);
|
||||||
do_post = false;
|
}
|
||||||
} else if &post.title == &data.title {
|
});
|
||||||
prev_post_idx = Some(idx);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
if do_post {
|
if do_post {
|
||||||
let item = &data.items[0];
|
let item = &data.items[0];
|
||||||
let new_post = CreatePost {
|
let new_post = CreatePost {
|
||||||
name: item.title.clone(),
|
name: item.title.clone(),
|
||||||
community_id: community_ids.find(&feed.communities.chapter),
|
community_id: community_ids.find(&feed.communities.chapter),
|
||||||
url: Some(Url::parse(&item.url).unwrap()),
|
url: Some(Url::parse(&item.url).unwrap()),
|
||||||
body: Some(
|
body: Some(
|
||||||
"[Reddit](https://reddit.com/r/HonzukinoGekokujou)\n\n[Discord](https://discord.com/invite/fGefmzu)".into(),
|
"[Reddit](https://reddit.com/r/HonzukinoGekokujou)\n\n[Discord](https://discord.com/invite/fGefmzu)".into(),
|
||||||
),
|
),
|
||||||
honeypot: None,
|
honeypot: None,
|
||||||
nsfw: Some(false),
|
nsfw: Some(false),
|
||||||
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
|
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(),
|
auth: auth.clone(),
|
||||||
};
|
};
|
||||||
|
|
||||||
let prev_data = (
|
let prev_data = (prev_post_idx, feed.id, data.title);
|
||||||
prev_post_idx,
|
|
||||||
feed.id,
|
|
||||||
data.title
|
|
||||||
);
|
|
||||||
|
|
||||||
post_queue.push((new_post, prev_data));
|
post_queue.push((new_post, prev_data));
|
||||||
}
|
}
|
||||||
sleep(time::Duration::from_millis(100)); // Should prevent dos-ing J-Novel servers
|
sleep(time::Duration::from_millis(100)); // Should prevent dos-ing J-Novel servers
|
||||||
return Ok(());
|
i += 1;
|
||||||
}).collect() {
|
|
||||||
Ok(()) => {}
|
|
||||||
Err(e) => return Err(e)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return Ok(post_queue);
|
return Ok(post_queue);
|
||||||
|
@ -179,7 +193,7 @@ pub(crate) enum LemmyCommunities {
|
||||||
aobprepub,
|
aobprepub,
|
||||||
aoblightnovel,
|
aoblightnovel,
|
||||||
aobmanga,
|
aobmanga,
|
||||||
metadiscussions
|
metadiscussions,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub_struct!(FeedRedditSettings {
|
pub_struct!(FeedRedditSettings {
|
||||||
|
@ -198,7 +212,7 @@ pub_struct!(PrevPost {
|
||||||
impl PrevPost {
|
impl PrevPost {
|
||||||
pub(crate) fn load() -> Vec<PrevPost> {
|
pub(crate) fn load() -> Vec<PrevPost> {
|
||||||
let history;
|
let history;
|
||||||
|
|
||||||
if Path::new("posts.json").exists() {
|
if Path::new("posts.json").exists() {
|
||||||
let file_contents = match fs::read_to_string("posts.json") {
|
let file_contents = match fs::read_to_string("posts.json") {
|
||||||
Ok(data) => data,
|
Ok(data) => data,
|
||||||
|
@ -211,12 +225,10 @@ impl PrevPost {
|
||||||
Err(e) => panic!("ERROR: posts.json could not be parsed:\n\n{:#?}", e),
|
Err(e) => panic!("ERROR: posts.json could not be parsed:\n\n{:#?}", e),
|
||||||
};
|
};
|
||||||
history = history_parse;
|
history = history_parse;
|
||||||
}
|
} else {
|
||||||
else {
|
|
||||||
history = [].to_vec()
|
history = [].to_vec()
|
||||||
}
|
}
|
||||||
}
|
} else {
|
||||||
else {
|
|
||||||
let _ = fs::File::create("posts.json");
|
let _ = fs::File::create("posts.json");
|
||||||
history = [].to_vec()
|
history = [].to_vec()
|
||||||
}
|
}
|
||||||
|
@ -225,7 +237,12 @@ impl PrevPost {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn save(data: &Vec<PrevPost>) {
|
pub(crate) fn save(data: &Vec<PrevPost>) {
|
||||||
let mut file = OpenOptions::new().read(true).write(true).create(true).open("posts.json").unwrap();
|
let mut file = OpenOptions::new()
|
||||||
|
.read(true)
|
||||||
|
.write(true)
|
||||||
|
.create(true)
|
||||||
|
.open("posts.json")
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
let json_data = serde_json::to_string_pretty(&data).unwrap();
|
let json_data = serde_json::to_string_pretty(&data).unwrap();
|
||||||
|
|
||||||
|
@ -243,9 +260,7 @@ pub_struct!(FeedData {
|
||||||
items: Vec<FeedEntry>,
|
items: Vec<FeedEntry>,
|
||||||
});
|
});
|
||||||
|
|
||||||
pub_struct!(FeedAuthor {
|
pub_struct!(FeedAuthor { name: String, });
|
||||||
name: String,
|
|
||||||
});
|
|
||||||
|
|
||||||
pub_struct!(FeedEntry {
|
pub_struct!(FeedEntry {
|
||||||
id: String,
|
id: String,
|
||||||
|
@ -255,7 +270,7 @@ pub_struct!(FeedEntry {
|
||||||
image: Option<String>,
|
image: Option<String>,
|
||||||
date_published: String,
|
date_published: String,
|
||||||
});
|
});
|
||||||
|
|
||||||
// Bot Helper Structs
|
// Bot Helper Structs
|
||||||
pub_struct!(CommunitiesVector {
|
pub_struct!(CommunitiesVector {
|
||||||
ids: Vec<(CommunityId, String)>,
|
ids: Vec<(CommunityId, String)>,
|
||||||
|
@ -263,11 +278,15 @@ pub_struct!(CommunitiesVector {
|
||||||
|
|
||||||
impl CommunitiesVector {
|
impl CommunitiesVector {
|
||||||
pub(crate) fn new() -> CommunitiesVector {
|
pub(crate) fn new() -> CommunitiesVector {
|
||||||
CommunitiesVector{ids: vec![]}
|
CommunitiesVector { ids: vec![] }
|
||||||
}
|
}
|
||||||
|
|
||||||
#[warn(unused_results)]
|
#[warn(unused_results)]
|
||||||
pub(crate) fn load(&mut self, auth: &Sensitive<String>, base: &String) -> Result<(), Box<dyn Error>> {
|
pub(crate) async fn load(
|
||||||
|
&mut self,
|
||||||
|
auth: &Sensitive<String>,
|
||||||
|
base: &String,
|
||||||
|
) -> Result<(), Box<dyn Error>> {
|
||||||
let params = ListCommunities {
|
let params = ListCommunities {
|
||||||
auth: Some(auth.clone()),
|
auth: Some(auth.clone()),
|
||||||
type_: Some(ListingType::Local),
|
type_: Some(ListingType::Local),
|
||||||
|
@ -277,7 +296,10 @@ impl CommunitiesVector {
|
||||||
let res = CLIENT
|
let res = CLIENT
|
||||||
.get(base.clone() + "/api/v3/community/list")
|
.get(base.clone() + "/api/v3/community/list")
|
||||||
.query(¶ms)
|
.query(¶ms)
|
||||||
.send()?.text()?;
|
.send()
|
||||||
|
.await?
|
||||||
|
.text()
|
||||||
|
.await?;
|
||||||
|
|
||||||
let site_data: ListCommunitiesResponse = serde_json::from_str(&res).unwrap();
|
let site_data: ListCommunitiesResponse = serde_json::from_str(&res).unwrap();
|
||||||
|
|
||||||
|
@ -301,6 +323,6 @@ impl CommunitiesVector {
|
||||||
ret_id = id.0;
|
ret_id = id.0;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
return ret_id;
|
return ret_id;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
252
src/main.rs
252
src/main.rs
|
@ -1,21 +1,28 @@
|
||||||
use chrono::{Utc, NaiveDateTime};
|
use chrono::{NaiveDateTime, Utc};
|
||||||
use config::{Config, PrevPost, Secrets, CommunitiesVector, LemmyCommunities};
|
use config::{CommunitiesVector, Config, LemmyCommunities, PrevPost, Secrets};
|
||||||
use lemmy_api_common::{
|
use lemmy_api_common::{
|
||||||
|
lemmy_db_views::structs::PostView,
|
||||||
person::{Login, LoginResponse},
|
person::{Login, LoginResponse},
|
||||||
post::{CreatePost, GetPosts, GetPostsResponse, FeaturePost},
|
post::{CreatePost, FeaturePost, GetPosts, GetPostsResponse},
|
||||||
sensitive::Sensitive, lemmy_db_views::structs::PostView,
|
sensitive::Sensitive,
|
||||||
};
|
|
||||||
use lemmy_db_schema::{
|
|
||||||
ListingType, SortType, PostFeatureType, newtypes::CommunityId,
|
|
||||||
};
|
};
|
||||||
|
use lemmy_db_schema::{newtypes::CommunityId, ListingType, PostFeatureType, SortType};
|
||||||
use once_cell::sync::Lazy;
|
use once_cell::sync::Lazy;
|
||||||
use reqwest::{blocking::Client, StatusCode};
|
use reqwest::{Client, StatusCode};
|
||||||
use std::{thread::sleep, time, collections::HashMap, error::Error};
|
use std::{
|
||||||
|
collections::HashMap,
|
||||||
|
error::Error,
|
||||||
|
sync::{
|
||||||
|
atomic::{AtomicBool, Ordering},
|
||||||
|
Arc,
|
||||||
|
},
|
||||||
|
thread::{self, sleep},
|
||||||
|
time::{self, Duration},
|
||||||
|
};
|
||||||
|
use tokio::sync::Mutex;
|
||||||
|
|
||||||
mod config;
|
mod config;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
pub static CLIENT: Lazy<Client> = Lazy::new(|| {
|
pub static CLIENT: Lazy<Client> = Lazy::new(|| {
|
||||||
let client = Client::builder()
|
let client = Client::builder()
|
||||||
.timeout(time::Duration::from_secs(30))
|
.timeout(time::Duration::from_secs(30))
|
||||||
|
@ -25,6 +32,7 @@ pub static CLIENT: Lazy<Client> = Lazy::new(|| {
|
||||||
client
|
client
|
||||||
});
|
});
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
struct Bot {
|
struct Bot {
|
||||||
secrets: Secrets,
|
secrets: Secrets,
|
||||||
config: Config,
|
config: Config,
|
||||||
|
@ -47,10 +55,10 @@ impl Bot {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get JWT Token
|
/// Get JWT Token
|
||||||
///
|
///
|
||||||
/// * `return` : Returns true if token was succesfully retrieved, false otherwise
|
/// * `return` : Returns true if token was succesfully retrieved, false otherwise
|
||||||
#[warn(unused_results)]
|
#[warn(unused_results)]
|
||||||
pub(crate) fn login(&mut self) -> Result<(), Box<dyn Error>> {
|
pub(crate) async fn login(&mut self) -> Result<Sensitive<String>, Box<dyn Error>> {
|
||||||
let login_params = Login {
|
let login_params = Login {
|
||||||
username_or_email: self.secrets.lemmy.get_username(),
|
username_or_email: self.secrets.lemmy.get_username(),
|
||||||
password: self.secrets.lemmy.get_password(),
|
password: self.secrets.lemmy.get_password(),
|
||||||
|
@ -60,15 +68,14 @@ impl Bot {
|
||||||
let res = CLIENT
|
let res = CLIENT
|
||||||
.post(self.config.instance.clone() + "/api/v3/user/login")
|
.post(self.config.instance.clone() + "/api/v3/user/login")
|
||||||
.json(&login_params)
|
.json(&login_params)
|
||||||
.send()?;
|
.send().await?;
|
||||||
|
|
||||||
|
|
||||||
if res.status() == StatusCode::OK {
|
if res.status() == StatusCode::OK {
|
||||||
let data: &LoginResponse = &res.json().unwrap();
|
let data: &LoginResponse = &res.json().await.unwrap();
|
||||||
|
|
||||||
let jwt = data.jwt.clone().expect("JWT Token could not be acquired");
|
let jwt = data.jwt.clone().expect("JWT Token could not be acquired");
|
||||||
self.auth = jwt;
|
self.auth = jwt.clone();
|
||||||
return Ok(());
|
return Ok(jwt);
|
||||||
} else {
|
} else {
|
||||||
println!("Error Code: {:?}", res.status());
|
println!("Error Code: {:?}", res.status());
|
||||||
return Err(Box::new(res.error_for_status().unwrap_err()));
|
return Err(Box::new(res.error_for_status().unwrap_err()));
|
||||||
|
@ -76,30 +83,37 @@ impl Bot {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Make Post to Lemmy Instance
|
/// Make Post to Lemmy Instance
|
||||||
///
|
///
|
||||||
/// * `post_data` : Object of type [CreatePost] containing post info
|
/// * `post_data` : Object of type [CreatePost] containing post info
|
||||||
/// * `return` : Returns true if Post was succesful, false otherwise
|
/// * `return` : Returns true if Post was succesful, false otherwise
|
||||||
#[warn(unused_results)]
|
#[warn(unused_results)]
|
||||||
pub(crate) fn post(&mut self, post_data: CreatePost) -> Result<PostView, Box<dyn Error>> {
|
pub(crate) async fn post(&mut self, post_data: CreatePost) -> Result<PostView, Box<dyn Error>> {
|
||||||
let res = CLIENT
|
let res = CLIENT
|
||||||
.post(self.config.instance.clone() + "/api/v3/post")
|
.post(self.config.instance.clone() + "/api/v3/post")
|
||||||
.json(&post_data)
|
.json(&post_data)
|
||||||
.send()?;
|
.send().await?;
|
||||||
|
|
||||||
// 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();
|
let ret: PostView = serde_json::from_str::<HashMap<&str, PostView>>(res.text().await?.as_str())
|
||||||
|
.unwrap()
|
||||||
|
.remove("post_view")
|
||||||
|
.unwrap();
|
||||||
return Ok(ret);
|
return Ok(ret);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[warn(unused_results)]
|
#[warn(unused_results)]
|
||||||
pub(crate) fn pin_new(&mut self, old_post: &Option<usize>, new_post: &PostView) -> Result<(), Box<dyn Error>> {
|
pub(crate) async fn pin_new(
|
||||||
|
&mut self,
|
||||||
|
old_post: &Option<usize>,
|
||||||
|
new_post: &PostView,
|
||||||
|
) -> Result<(), Box<dyn Error>> {
|
||||||
match old_post {
|
match old_post {
|
||||||
Some(id) => {
|
Some(id) => {
|
||||||
let remove_community_pin = FeaturePost {
|
let remove_community_pin = FeaturePost {
|
||||||
post_id: self.post_history[*id].post_id,
|
post_id: self.post_history[*id].post_id,
|
||||||
featured: false,
|
featured: false,
|
||||||
feature_type: PostFeatureType::Community,
|
feature_type: PostFeatureType::Community,
|
||||||
auth: self.auth.clone()
|
auth: self.auth.clone(),
|
||||||
};
|
};
|
||||||
|
|
||||||
let _ = self.pin(remove_community_pin);
|
let _ = self.pin(remove_community_pin);
|
||||||
|
@ -119,8 +133,8 @@ impl Bot {
|
||||||
let post_list_json = CLIENT
|
let post_list_json = CLIENT
|
||||||
.get(self.config.instance.clone() + "/api/v3/post/list")
|
.get(self.config.instance.clone() + "/api/v3/post/list")
|
||||||
.query(&get_params)
|
.query(&get_params)
|
||||||
.send()?
|
.send().await?
|
||||||
.text()?;
|
.text().await?;
|
||||||
|
|
||||||
let post_list: GetPostsResponse = serde_json::from_str(post_list_json.as_str()).unwrap();
|
let post_list: GetPostsResponse = serde_json::from_str(post_list_json.as_str()).unwrap();
|
||||||
|
|
||||||
|
@ -138,12 +152,12 @@ impl Bot {
|
||||||
post_id: post_view.post.id,
|
post_id: post_view.post.id,
|
||||||
featured: false,
|
featured: false,
|
||||||
feature_type: PostFeatureType::Local,
|
feature_type: PostFeatureType::Local,
|
||||||
auth: self.auth.clone()
|
auth: self.auth.clone(),
|
||||||
};
|
};
|
||||||
|
|
||||||
match self.pin(remove_local_pin) {
|
match self.pin(remove_local_pin).await {
|
||||||
Ok(_) => {},
|
Ok(_) => {}
|
||||||
Err(e) => println!("Error Unpinning Post: {:#?}", e)
|
Err(e) => println!("Error Unpinning Post: {:#?}", e),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -163,57 +177,68 @@ impl Bot {
|
||||||
feature_type: PostFeatureType::Local,
|
feature_type: PostFeatureType::Local,
|
||||||
auth: self.auth.clone(),
|
auth: self.auth.clone(),
|
||||||
};
|
};
|
||||||
|
|
||||||
match self.pin(pin_new_local) {
|
match self.pin(pin_new_local).await {
|
||||||
Ok(_) => {},
|
Ok(_) => {}
|
||||||
Err(e) => println!("Error Pinning Post: {:#?}", e)
|
Err(e) => println!("Error Pinning Post: {:#?}", e),
|
||||||
};;
|
};
|
||||||
|
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn pin (&mut self, pin_data: FeaturePost) -> Result<bool, Box<dyn Error>> {
|
pub(crate) async fn pin(&mut self, pin_data: FeaturePost) -> Result<bool, Box<dyn Error>> {
|
||||||
let res = CLIENT
|
let res = CLIENT
|
||||||
.post(self.config.instance.clone() + "/api/v3/post/feature")
|
.post(self.config.instance.clone() + "/api/v3/post/feature")
|
||||||
.json(&pin_data)
|
.json(&pin_data)
|
||||||
.send()?;
|
.send().await?;
|
||||||
|
|
||||||
let ret: PostView = serde_json::from_str::<HashMap<&str, PostView>>(res.text()?.as_str()).unwrap().remove("post_view").unwrap();
|
let ret: PostView = serde_json::from_str::<HashMap<&str, PostView>>(res.text().await?.as_str())
|
||||||
|
.unwrap()
|
||||||
|
.remove("post_view")
|
||||||
|
.unwrap();
|
||||||
return Ok(ret.post.featured_local);
|
return Ok(ret.post.featured_local);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[warn(unused_results)]
|
#[warn(unused_results)]
|
||||||
pub(crate) fn run_once(&mut self, prev_time: &mut NaiveDateTime) -> Result<(), Box<dyn Error>> {
|
pub(crate) async fn run_once(&mut self, prev_time: &mut NaiveDateTime) -> Result<(), Box<dyn Error>> {
|
||||||
println!("{:#<1$}", "", 30);
|
println!("{:#<1$}", "", 30);
|
||||||
self.start_time = Utc::now().naive_local();
|
self.start_time = Utc::now().naive_local();
|
||||||
|
|
||||||
if self.start_time - *prev_time > chrono::Duration::seconds(6) { // Prod should use hours, add command line switch later and read duration from config
|
if self.start_time - *prev_time > chrono::Duration::seconds(6) {
|
||||||
|
// Prod should use hours, add command line switch later and read duration from config
|
||||||
println!("Reloading Config");
|
println!("Reloading Config");
|
||||||
*prev_time = self.start_time;
|
*prev_time = self.start_time;
|
||||||
self.config.load();
|
self.config.load();
|
||||||
self.community_ids.load(&self.auth, &self.config.instance)?;
|
self.community_ids.load(&self.auth, &self.config.instance).await?;
|
||||||
println!("Done!");
|
println!("Done!");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Start the polling process
|
// Start the polling process
|
||||||
// Get all feed URLs (use cache)
|
// Get all feed URLs (use cache)
|
||||||
println!("Checking Feeds");
|
println!("Checking Feeds");
|
||||||
let post_queue: Vec<(CreatePost, (Option<usize>, usize, String))> = 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).await?;
|
||||||
println!("Done!");
|
println!("Done!");
|
||||||
|
|
||||||
let _ = post_queue.iter().map(|(post, (prev_idx, feed_id, feed_title))| -> Result<(), Box<dyn Error>> {
|
|
||||||
|
let mut i = 0;
|
||||||
|
while i < post_queue.len() {
|
||||||
|
let (post, (prev_idx, feed_id, feed_title)) = &post_queue[i];
|
||||||
|
|
||||||
println!("Posting: {}", post.name);
|
println!("Posting: {}", post.name);
|
||||||
|
|
||||||
let post_data = self.post(post.clone())?;
|
let post_data = self.post(post.clone()).await?;
|
||||||
|
|
||||||
self.pin_new(prev_idx, &post_data)?;
|
self.pin_new(&prev_idx, &post_data).await?;
|
||||||
|
|
||||||
// Move current post to old post list
|
// Move current post to old post list
|
||||||
match prev_idx {
|
match prev_idx {
|
||||||
Some(idx) => {
|
Some(idx) => {
|
||||||
self.post_history[*idx].title = feed_title.clone();
|
self.post_history[*idx].title = feed_title.clone();
|
||||||
self.post_history[*idx].post_id = post_data.post.id;
|
self.post_history[*idx].post_id = post_data.post.id;
|
||||||
self.post_history[*idx].last_post_url = post.url.clone().unwrap().to_string();
|
self.post_history[*idx].last_post_url =
|
||||||
|
post.url.clone().unwrap().to_string();
|
||||||
}
|
}
|
||||||
None => self.post_history.push(PrevPost {
|
None => self.post_history.push(PrevPost {
|
||||||
id: feed_id.clone(),
|
id: feed_id.clone(),
|
||||||
|
@ -222,8 +247,9 @@ impl Bot {
|
||||||
last_post_url: post.url.clone().unwrap().to_string(),
|
last_post_url: post.url.clone().unwrap().to_string(),
|
||||||
}),
|
}),
|
||||||
}
|
}
|
||||||
Ok(())
|
|
||||||
}).collect::<Vec<_>>();
|
i += 1;
|
||||||
|
}
|
||||||
|
|
||||||
PrevPost::save(&self.post_history);
|
PrevPost::save(&self.post_history);
|
||||||
|
|
||||||
|
@ -240,27 +266,16 @@ impl Bot {
|
||||||
sleep(time::Duration::from_secs(1));
|
sleep(time::Duration::from_secs(1));
|
||||||
}
|
}
|
||||||
|
|
||||||
match reqwest::blocking::get("https://status.neshweb.net/api/push/7s1CjPPzrV?status=up&msg=OK&ping=") {
|
match reqwest::blocking::get(
|
||||||
Ok(_) => {},
|
"https://status.neshweb.net/api/push/7s1CjPPzrV?status=up&msg=OK&ping=",
|
||||||
Err(err) => println!("{}", err)
|
) {
|
||||||
|
Ok(_) => {}
|
||||||
|
Err(err) => println!("{}", err),
|
||||||
};
|
};
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn print_info(&self) {
|
|
||||||
print!("\x1B[2J\x1B[1;1H");
|
|
||||||
println!("##[Ascendance of a Bookworm Bot]##");
|
|
||||||
println!("Instance: {}", &self.config.instance);
|
|
||||||
println!("Ran Last: {}", &self.start_time.format("%d/%m/%Y %H:%M:%S"));
|
|
||||||
println!("{:#<1$}", "", 30);
|
|
||||||
self.post_history.iter().for_each(|post| {
|
|
||||||
print!("{} ", post.title);
|
|
||||||
print!("{:<1$}: ", "", 60 - post.title.len());
|
|
||||||
println!("{}", post.last_post_url);
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn list_posts(auth: &Sensitive<String>, base: String) -> GetPostsResponse {
|
async fn list_posts(auth: &Sensitive<String>, base: String) -> GetPostsResponse {
|
||||||
let params = GetPosts {
|
let params = GetPosts {
|
||||||
type_: Some(ListingType::Local),
|
type_: Some(ListingType::Local),
|
||||||
sort: Some(SortType::New),
|
sort: Some(SortType::New),
|
||||||
|
@ -271,48 +286,89 @@ fn list_posts(auth: &Sensitive<String>, base: String) -> GetPostsResponse {
|
||||||
let res = CLIENT
|
let res = CLIENT
|
||||||
.get(base + "/api/v3/post/list")
|
.get(base + "/api/v3/post/list")
|
||||||
.query(¶ms)
|
.query(¶ms)
|
||||||
.send()
|
.send().await
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.text()
|
.text().await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
return serde_json::from_str(&res).unwrap();
|
return serde_json::from_str(&res).unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
fn run_bot() {
|
async fn run_bot(bot: Arc<Mutex<Bot>>) {
|
||||||
|
// TODO this currently does not update the bot Mutex when run
|
||||||
// Get all needed auth tokens at the start
|
// Get all needed auth tokens at the start
|
||||||
let mut old = Utc::now().naive_local();
|
let mut old = Utc::now().naive_local();
|
||||||
let mut this = Bot::new();
|
|
||||||
match this.login() {
|
|
||||||
Ok(_) => {
|
|
||||||
let _ = this.community_ids.load(&this.auth, &this.config.instance);
|
|
||||||
|
|
||||||
// Enter a loop (not for debugging)
|
let mut this = bot.lock().await.clone();
|
||||||
loop {
|
match this.login().await {
|
||||||
this.idle();
|
Ok(_) => {
|
||||||
// 3 retries in case of connection issues
|
println!("Login successful");
|
||||||
//let mut loop_breaker: u8 = 0; // DEBUG disabled for clearer crash finding
|
|
||||||
match this.run_once(&mut old) {
|
|
||||||
Ok(_) => {},
|
|
||||||
Err(e) => panic!("{:#?}", e)
|
|
||||||
};
|
|
||||||
/* 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() {
|
|
||||||
let _ = this.community_ids.load(&this.auth, &this.config.instance);
|
|
||||||
}
|
|
||||||
sleep(time::Duration::from_secs(10));
|
|
||||||
loop_breaker += 1;
|
|
||||||
}; */
|
|
||||||
this.print_info();
|
|
||||||
this.idle();
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
println!("Unable to get initial login:\n {:#?}", e);
|
println!("Unable to get initial login:\n {:#?}", e);
|
||||||
}
|
}
|
||||||
|
};
|
||||||
|
let _ = this.community_ids.load(&this.auth, &this.config.instance).await;
|
||||||
|
|
||||||
|
loop {
|
||||||
|
this.idle();
|
||||||
|
println!("Debug A"); // DEBUG
|
||||||
|
match this.run_once(&mut old).await {
|
||||||
|
Ok(_) => {}
|
||||||
|
Err(e) => panic!("Crashed due to Error: {:#?}", e),
|
||||||
|
};
|
||||||
|
*bot.lock().await = this.clone();
|
||||||
|
|
||||||
|
this.idle();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
fn main() {
|
|
||||||
run_bot();
|
async fn print_info(shutdown: Arc<AtomicBool>, bot: Arc<Mutex<Bot>>) {
|
||||||
|
while !shutdown.load(Ordering::Relaxed) {
|
||||||
|
let bot: tokio::sync::MutexGuard<'_, Bot> = bot.lock().await;
|
||||||
|
thread::sleep(Duration::from_millis(500));
|
||||||
|
|
||||||
|
//print!("\x1B[2J\x1B[1;1H");
|
||||||
|
println!(
|
||||||
|
"##[Ascendance of a Bookworm Bot]## | Time: {}",
|
||||||
|
Utc::now().naive_local().format("%H:%M:%S")
|
||||||
|
);
|
||||||
|
println!("Instance: {}", bot.config.instance);
|
||||||
|
println!("Ran Last: {}", bot.start_time.format("%d/%m/%Y %H:%M:%S"));
|
||||||
|
println!("{:#<1$}", "", 30);
|
||||||
|
bot.post_history.iter().for_each(|post| {
|
||||||
|
print!("| -- |");
|
||||||
|
print!("{} ", post.title);
|
||||||
|
print!("{:<1$}| ", "", 60 - post.title.len());
|
||||||
|
println!("{}", post.last_post_url);
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::main]
|
||||||
|
async fn main() {
|
||||||
|
let shutdown = Arc::new(AtomicBool::new(false));
|
||||||
|
|
||||||
|
loop {
|
||||||
|
println!("Starting AoB Bot...");
|
||||||
|
|
||||||
|
let shutdown_clone = shutdown.clone();
|
||||||
|
|
||||||
|
let bot = Arc::new(Mutex::new(Bot::new()));
|
||||||
|
let bot_clone = bot.clone();
|
||||||
|
|
||||||
|
let bot_thread = tokio::spawn(async move { run_bot(bot).await });
|
||||||
|
|
||||||
|
let tui_thread = tokio::spawn(async move { print_info(shutdown_clone, bot_clone).await });
|
||||||
|
|
||||||
|
let _ = bot_thread.await;
|
||||||
|
|
||||||
|
tui_thread.abort();
|
||||||
|
|
||||||
|
println!("Bot crashed due to unknown Error, restarting thread after wait...");
|
||||||
|
|
||||||
|
sleep(Duration::from_secs(30));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue