Bot Rewrite for jnovel labs
This commit is contained in:
parent
ba052a29b5
commit
a0d414a091
2 changed files with 320 additions and 193 deletions
|
@ -158,7 +158,6 @@ impl Config {
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
let data: FeedSeriesData = serde_json::from_str(&res).unwrap();
|
let data: FeedSeriesData = serde_json::from_str(&res).unwrap();
|
||||||
println!("{:#?}", data);
|
|
||||||
}
|
}
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
|
@ -210,10 +209,6 @@ impl Config {
|
||||||
// Get First Volume that has valid Release Data
|
// Get First Volume that has valid Release Data
|
||||||
if now >= published {
|
if now >= published {
|
||||||
if Some(volume.slug.clone()) != history_data.last_volume_slug {
|
if Some(volume.slug.clone()) != history_data.last_volume_slug {
|
||||||
println!(
|
|
||||||
"Should Post for {} Volume {}",
|
|
||||||
feed.series_slug, volume.slug
|
|
||||||
);
|
|
||||||
if let Some(volume_community) = &feed.communities.volume {
|
if let Some(volume_community) = &feed.communities.volume {
|
||||||
let mut post_url = Url::parse(&(volume_url_base!() + &feed.series_slug))?;
|
let mut post_url = Url::parse(&(volume_url_base!() + &feed.series_slug))?;
|
||||||
post_url.set_fragment(Some(&("volume-".to_string() + &volume.number.to_string())));
|
post_url.set_fragment(Some(&("volume-".to_string() + &volume.number.to_string())));
|
||||||
|
@ -235,7 +230,6 @@ 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
|
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(),
|
||||||
};
|
};
|
||||||
println!("{:?}", new_post.url);
|
|
||||||
post_queue.push((
|
post_queue.push((
|
||||||
new_post,
|
new_post,
|
||||||
PostQueueMetadata {
|
PostQueueMetadata {
|
||||||
|
@ -269,8 +263,6 @@ impl Config {
|
||||||
data.parts.reverse();
|
data.parts.reverse();
|
||||||
for part in data.parts {
|
for part in data.parts {
|
||||||
if Some(part.slug.clone()) != history_data.last_part_slug {
|
if Some(part.slug.clone()) != history_data.last_part_slug {
|
||||||
println!("Should Post for {} Part {}", feed.series_slug, part.slug);
|
|
||||||
|
|
||||||
let new_post = CreatePost {
|
let new_post = CreatePost {
|
||||||
name: part.title.clone(),
|
name: part.title.clone(),
|
||||||
community_id: community_ids.find(&chapter_community),
|
community_id: community_ids.find(&chapter_community),
|
||||||
|
|
505
src/main.rs
505
src/main.rs
|
@ -1,4 +1,4 @@
|
||||||
use chrono::{NaiveDateTime, Utc};
|
use chrono::{DateTime, Duration, NaiveDateTime, Utc};
|
||||||
use config::{CommunitiesVector, Config, LemmyCommunities, PrevPost, Secrets};
|
use config::{CommunitiesVector, Config, LemmyCommunities, PrevPost, Secrets};
|
||||||
use lemmy_api_common::{
|
use lemmy_api_common::{
|
||||||
lemmy_db_views::structs::PostView,
|
lemmy_db_views::structs::PostView,
|
||||||
|
@ -6,6 +6,7 @@ use lemmy_api_common::{
|
||||||
post::{CreatePost, FeaturePost, GetPosts, GetPostsResponse},
|
post::{CreatePost, FeaturePost, GetPosts, GetPostsResponse},
|
||||||
sensitive::Sensitive,
|
sensitive::Sensitive,
|
||||||
};
|
};
|
||||||
|
use lemmy_db_schema::newtypes::PostId;
|
||||||
use lemmy_db_schema::{newtypes::CommunityId, ListingType, PostFeatureType, SortType};
|
use lemmy_db_schema::{newtypes::CommunityId, ListingType, PostFeatureType, SortType};
|
||||||
use once_cell::sync::Lazy;
|
use once_cell::sync::Lazy;
|
||||||
use reqwest::{Client, StatusCode};
|
use reqwest::{Client, StatusCode};
|
||||||
|
@ -16,23 +17,32 @@ use std::{
|
||||||
atomic::{AtomicBool, Ordering},
|
atomic::{AtomicBool, Ordering},
|
||||||
Arc,
|
Arc,
|
||||||
},
|
},
|
||||||
thread::{self},
|
vec,
|
||||||
time::{self, Duration}, vec,
|
|
||||||
};
|
};
|
||||||
|
use std::str::FromStr;
|
||||||
|
use std::time::UNIX_EPOCH;
|
||||||
use tokio::sync::Mutex;
|
use tokio::sync::Mutex;
|
||||||
use tokio::time::sleep;
|
use tokio::time::sleep;
|
||||||
|
|
||||||
mod config;
|
mod config;
|
||||||
|
mod feeds;
|
||||||
|
|
||||||
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(Duration::seconds(30).to_std().unwrap())
|
||||||
.connect_timeout(time::Duration::from_secs(30))
|
.connect_timeout(Duration::seconds(30).to_std().unwrap())
|
||||||
.build()
|
.build()
|
||||||
.expect("build client");
|
.expect("build client");
|
||||||
client
|
client
|
||||||
});
|
});
|
||||||
|
|
||||||
|
struct PostQueueMetadata {
|
||||||
|
id: usize,
|
||||||
|
series: String,
|
||||||
|
part: Option<String>,
|
||||||
|
volume: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
struct Bot {
|
struct Bot {
|
||||||
secrets: Secrets,
|
secrets: Secrets,
|
||||||
|
@ -40,8 +50,10 @@ struct Bot {
|
||||||
post_history: Vec<PrevPost>,
|
post_history: Vec<PrevPost>,
|
||||||
community_ids: CommunitiesVector,
|
community_ids: CommunitiesVector,
|
||||||
auth: Sensitive<String>,
|
auth: Sensitive<String>,
|
||||||
start_time: NaiveDateTime,
|
login_error: bool,
|
||||||
|
start_time: DateTime<Utc>,
|
||||||
message_queue: Vec<String>,
|
message_queue: Vec<String>,
|
||||||
|
error_queue: Vec<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Bot {
|
impl Bot {
|
||||||
|
@ -52,14 +64,16 @@ impl Bot {
|
||||||
post_history: PrevPost::load(),
|
post_history: PrevPost::load(),
|
||||||
community_ids: CommunitiesVector::new(),
|
community_ids: CommunitiesVector::new(),
|
||||||
auth: Sensitive::new("".to_string()),
|
auth: Sensitive::new("".to_string()),
|
||||||
start_time: Utc::now().naive_local(),
|
login_error: true,
|
||||||
|
start_time: Utc::now(),
|
||||||
message_queue: vec![],
|
message_queue: vec![],
|
||||||
|
error_queue: vec![],
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get JWT Token
|
/// Get JWT Token
|
||||||
///
|
///
|
||||||
/// * `return` : Returns true if token was succesfully retrieved, false otherwise
|
/// * `return` : Returns true if token was successfully retrieved, false otherwise
|
||||||
#[warn(unused_results)]
|
#[warn(unused_results)]
|
||||||
pub(crate) async fn login(&mut self) -> Result<Sensitive<String>, Box<dyn Error>> {
|
pub(crate) async fn login(&mut self) -> Result<Sensitive<String>, Box<dyn Error>> {
|
||||||
let login_params = Login {
|
let login_params = Login {
|
||||||
|
@ -71,111 +85,101 @@ 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().await?;
|
.send()
|
||||||
|
.await?;
|
||||||
|
|
||||||
if res.status() == StatusCode::OK {
|
return if res.status() == StatusCode::OK {
|
||||||
let data: &LoginResponse = &res.json().await.unwrap();
|
let data: &LoginResponse = &res.json().await.unwrap();
|
||||||
|
|
||||||
let jwt = data.jwt.clone().expect("JWT Token could not be acquired");
|
let jwt = match data.jwt.clone() {
|
||||||
|
Some(data) => data,
|
||||||
|
None => {
|
||||||
|
self.error_queue.push(format!("Error: Missing JWT Token"));
|
||||||
|
return Err(Box::try_from(format!("Error: Missing JWT Token")).unwrap());
|
||||||
|
}
|
||||||
|
};
|
||||||
self.auth = jwt.clone();
|
self.auth = jwt.clone();
|
||||||
return Ok(jwt);
|
self.login_error = false;
|
||||||
|
Ok(jwt)
|
||||||
} else {
|
} else {
|
||||||
self.message_queue.push(format!("Error Code: {:?}", res.status()));
|
self.error_queue
|
||||||
return Err(Box::new(res.error_for_status().unwrap_err()));
|
.push(format!("Error Code: {:?}", res.status()));
|
||||||
}
|
Err(Box::new(res.error_for_status().unwrap_err()))
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 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 successful, false otherwise
|
||||||
#[warn(unused_results)]
|
#[warn(unused_results)]
|
||||||
pub(crate) async 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().await?;
|
.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 successfully (mostly if jwt token was valid)
|
||||||
let ret: PostView = serde_json::from_str::<HashMap<&str, PostView>>(res.text().await?.as_str())
|
let ret: PostView = serde_json::from_str::<HashMap<&str, PostView>>(res.text().await?.as_str())?
|
||||||
.unwrap()
|
|
||||||
.remove("post_view")
|
.remove("post_view")
|
||||||
.unwrap();
|
.unwrap();
|
||||||
return Ok(ret);
|
return Ok(ret);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[warn(unused_results)]
|
#[warn(unused_results)]
|
||||||
pub(crate) async fn pin_new(
|
pub(crate) async fn pin_post(&mut self, new_post_id: &PostId, new_post_community: &CommunityId) -> Result<(bool, bool), Box<dyn Error>> {
|
||||||
&mut self,
|
let mut local_pins = true;
|
||||||
old_post: &Option<usize>,
|
let mut community_pins = true;
|
||||||
new_post: &PostView,
|
// Unpin Old Posts
|
||||||
) -> Result<(), Box<dyn 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 => {
|
|
||||||
self.message_queue.push(format!("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().await?
|
|
||||||
.text().await?;
|
|
||||||
|
|
||||||
let post_list: GetPostsResponse = serde_json::from_str(post_list_json.as_str()).unwrap();
|
|
||||||
|
|
||||||
|
// Get Local Posts & Unpin The Other Post
|
||||||
let mut meta_community: CommunityId = CommunityId(15);
|
let mut meta_community: CommunityId = CommunityId(15);
|
||||||
|
|
||||||
self.community_ids.ids.iter().for_each(|(id, name)| {
|
self.community_ids.ids.iter().for_each(|(id, name)| {
|
||||||
if name == &LemmyCommunities::metadiscussions.to_string() {
|
if name == &LemmyCommunities::metadiscussions.to_string() {
|
||||||
meta_community = id.clone();
|
meta_community = id.clone();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
for post_view in post_list.posts {
|
let get_params = GetPosts {
|
||||||
if post_view.community.id != meta_community && post_view.post.featured_local {
|
auth: Some(self.auth.clone()),
|
||||||
let remove_local_pin = FeaturePost {
|
..Default::default()
|
||||||
post_id: post_view.post.id,
|
};
|
||||||
featured: false,
|
|
||||||
feature_type: PostFeatureType::Local,
|
|
||||||
auth: self.auth.clone(),
|
|
||||||
};
|
|
||||||
|
|
||||||
match self.pin(remove_local_pin).await {
|
local_pins = self
|
||||||
Ok(_) => {}
|
.unpin_old_posts(get_params, PostFeatureType::Local, &meta_community)
|
||||||
Err(e) => self.message_queue.push(format!("Error Unpinning Post: {:#?}", e)),
|
.await?;
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
// Get Community Posts & Unpin The Other Post
|
||||||
|
let get_params = GetPosts {
|
||||||
|
auth: Some(self.auth.clone()),
|
||||||
|
community_id: Some(new_post_community.clone()),
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
|
||||||
|
community_pins = self
|
||||||
|
.unpin_old_posts(get_params, PostFeatureType::Community, &meta_community)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
// Pin New Post
|
||||||
let pin_new_community = FeaturePost {
|
let pin_new_community = FeaturePost {
|
||||||
post_id: new_post.post.id,
|
post_id: new_post_id.clone(),
|
||||||
featured: true,
|
featured: true,
|
||||||
feature_type: PostFeatureType::Community,
|
feature_type: PostFeatureType::Community,
|
||||||
auth: self.auth.clone(),
|
auth: self.auth.clone(),
|
||||||
};
|
};
|
||||||
|
|
||||||
let _ = self.pin(pin_new_community);
|
match self.pin(pin_new_community).await {
|
||||||
|
Ok(_) => {}
|
||||||
|
Err(e) => {
|
||||||
|
self.message_queue
|
||||||
|
.push(format!("Error Unpinning Post: {:#?}", e));
|
||||||
|
community_pins = false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
let pin_new_local = FeaturePost {
|
let pin_new_local = FeaturePost {
|
||||||
post_id: new_post.post.id,
|
post_id: new_post_id.clone(),
|
||||||
featured: true,
|
featured: true,
|
||||||
feature_type: PostFeatureType::Local,
|
feature_type: PostFeatureType::Local,
|
||||||
auth: self.auth.clone(),
|
auth: self.auth.clone(),
|
||||||
|
@ -183,98 +187,83 @@ impl Bot {
|
||||||
|
|
||||||
match self.pin(pin_new_local).await {
|
match self.pin(pin_new_local).await {
|
||||||
Ok(_) => {}
|
Ok(_) => {}
|
||||||
Err(e) => self.message_queue.push(format!("Error Pinning Post: {:#?}", e)),
|
Err(e) => {
|
||||||
|
self.message_queue
|
||||||
|
.push(format!("Error Unpinning Post: {:#?}", e));
|
||||||
|
local_pins = false;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
return Ok(());
|
return Ok((community_pins, local_pins));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[warn(unused_results)]
|
||||||
|
pub(crate) async fn unpin_old_posts(
|
||||||
|
&mut self,
|
||||||
|
get_params: GetPosts,
|
||||||
|
pin_scope: PostFeatureType,
|
||||||
|
meta_community: &CommunityId,
|
||||||
|
) -> Result<bool, Box<dyn Error>> {
|
||||||
|
let post_list_json = CLIENT
|
||||||
|
.get(self.config.instance.clone() + "/api/v3/post/list")
|
||||||
|
.query(&get_params)
|
||||||
|
.send()
|
||||||
|
.await?
|
||||||
|
.text()
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
let post_list: GetPostsResponse = serde_json::from_str(post_list_json.as_str()).unwrap();
|
||||||
|
|
||||||
|
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: pin_scope,
|
||||||
|
auth: self.auth.clone(),
|
||||||
|
};
|
||||||
|
|
||||||
|
match self.pin(remove_local_pin).await {
|
||||||
|
Ok(_) => {}
|
||||||
|
Err(e) => {
|
||||||
|
self.message_queue
|
||||||
|
.push(format!("Error Unpinning Post: {:#?}", e));
|
||||||
|
return Err(Box::from(format!("{}", e)));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) async 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().await?;
|
.send()
|
||||||
|
.await?;
|
||||||
|
|
||||||
let ret: PostView = serde_json::from_str::<HashMap<&str, PostView>>(res.text().await?.as_str())
|
let ret: PostView = serde_json::from_str::<HashMap<&str, PostView>>(res.text().await?.as_str())?
|
||||||
.unwrap()
|
|
||||||
.remove("post_view")
|
.remove("post_view")
|
||||||
.unwrap();
|
.unwrap();
|
||||||
return Ok(ret.post.featured_local);
|
return Ok(ret.post.featured_local);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[warn(unused_results)]
|
|
||||||
pub(crate) async fn run_once(&mut self, prev_time: &mut NaiveDateTime) -> Result<(), Box<dyn Error>> {
|
|
||||||
self.message_queue.push(format!("{:#<1$}", "", 30));
|
|
||||||
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
|
|
||||||
self.message_queue.push(format!("Reloading Config"));
|
|
||||||
*prev_time = self.start_time;
|
|
||||||
self.config.load();
|
|
||||||
self.community_ids.load(&self.auth, &self.config.instance).await?;
|
|
||||||
self.message_queue.push(format!("Done!"));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Start the polling process
|
|
||||||
// Get all feed URLs (use cache)
|
|
||||||
self.message_queue.push(format!("Checking Feeds"));
|
|
||||||
let post_queue: Vec<(CreatePost, (Option<usize>, usize, String))> = self
|
|
||||||
.config
|
|
||||||
.check_feeds(&mut self.post_history, &self.community_ids, &self.auth).await?;
|
|
||||||
self.message_queue.push(format!("Done!"));
|
|
||||||
|
|
||||||
|
|
||||||
let mut i = 0;
|
|
||||||
while i < post_queue.len() {
|
|
||||||
let (post, (prev_idx, feed_id, feed_title)) = &post_queue[i];
|
|
||||||
|
|
||||||
self.message_queue.push(format!("Posting: {}", post.name));
|
|
||||||
|
|
||||||
let post_data = self.post(post.clone()).await?;
|
|
||||||
|
|
||||||
self.pin_new(&prev_idx, &post_data).await?;
|
|
||||||
|
|
||||||
// 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(),
|
|
||||||
}),
|
|
||||||
}
|
|
||||||
|
|
||||||
i += 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
PrevPost::save(&self.post_history);
|
|
||||||
|
|
||||||
return Ok(());
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) async fn idle(&mut self) {
|
pub(crate) async fn idle(&mut self) {
|
||||||
let mut sleep_duration = chrono::Duration::seconds(30);
|
let mut sleep_duration = Duration::seconds(30);
|
||||||
if Utc::now().naive_local() - self.start_time > sleep_duration {
|
if Utc::now() - self.start_time > sleep_duration {
|
||||||
sleep_duration = chrono::Duration::seconds(60);
|
sleep_duration = Duration::seconds(60);
|
||||||
}
|
}
|
||||||
|
|
||||||
while Utc::now().naive_local() - self.start_time < sleep_duration {
|
match reqwest::get("https://status.neshweb.net/api/push/7s1CjPPzrV?status=up&msg=OK&ping=").await {
|
||||||
sleep(time::Duration::from_millis(100)).await;
|
|
||||||
}
|
|
||||||
|
|
||||||
match reqwest::get(
|
|
||||||
"https://status.neshweb.net/api/push/7s1CjPPzrV?status=up&msg=OK&ping=",
|
|
||||||
).await {
|
|
||||||
Ok(_) => {}
|
Ok(_) => {}
|
||||||
Err(err) => self.message_queue.push(format!("{}", err)),
|
Err(err) => self.error_queue.push(format!("{}", err)),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
while Utc::now() - self.start_time < sleep_duration {
|
||||||
|
sleep(Duration::milliseconds(100).to_std().unwrap()).await;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -289,65 +278,213 @@ async 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().await
|
.send()
|
||||||
|
.await
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.text().await
|
.text()
|
||||||
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
return serde_json::from_str(&res).unwrap();
|
return serde_json::from_str(&res).unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn run_bot(bot: Arc<Mutex<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
|
|
||||||
let mut old = Utc::now().naive_local();
|
|
||||||
|
|
||||||
let mut this = bot.lock().await.clone();
|
let mut this = bot.lock().await.clone();
|
||||||
match this.login().await {
|
let now = Utc::now;
|
||||||
Ok(_) => {
|
|
||||||
println!("Login successful");
|
let mut config_reload_duration = this.config.config_reload.unwrap_or(360);
|
||||||
},
|
let mut config_reload_time = now() - Duration::minutes(config_reload_duration as i64 + 1); // Setting this to be in the future by default prevents unneeded code duplication
|
||||||
Err(e) => {
|
|
||||||
println!("Unable to get initial login:\n {:#?}", e);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
let _ = this.community_ids.load(&this.auth, &this.config.instance).await;
|
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
this.idle().await;
|
|
||||||
this.message_queue = vec![];
|
|
||||||
match this.run_once(&mut old).await {
|
|
||||||
Ok(_) => {}
|
|
||||||
Err(e) => panic!("Crashed due to Error: {:#?}", e),
|
|
||||||
};
|
|
||||||
*bot.lock().await = this.clone();
|
*bot.lock().await = this.clone();
|
||||||
|
|
||||||
|
this.start_time = now();
|
||||||
|
|
||||||
|
this.idle().await;
|
||||||
|
|
||||||
|
// After configured time passed reload config
|
||||||
|
if now() - config_reload_time >= Duration::minutes(config_reload_duration as i64) {
|
||||||
|
this.config.load();
|
||||||
|
this.secrets.load();
|
||||||
|
|
||||||
|
config_reload_duration = this.config.config_reload.unwrap_or(360);
|
||||||
|
|
||||||
|
let _ = this
|
||||||
|
.community_ids
|
||||||
|
.load(&this.auth, &this.config.instance)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
this.message_queue
|
||||||
|
.push(format!("Config Reloaded at {}", now().naive_local()));
|
||||||
|
config_reload_time = now();
|
||||||
|
}
|
||||||
|
// Check if Login token is valid, if it is not get a new one
|
||||||
|
while this.login_error {
|
||||||
|
let _ = this.login().await;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Perform Run
|
||||||
|
// Start the polling process
|
||||||
|
// Get all feed URLs (use cache)
|
||||||
|
let queue_data = match this
|
||||||
|
.config
|
||||||
|
.check_feeds(&this.post_history, &this.community_ids, &this.auth)
|
||||||
|
.await
|
||||||
|
{
|
||||||
|
Ok(data) => data,
|
||||||
|
Err(e) => {
|
||||||
|
this.error_queue.push(format!("{}", e));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
this.message_queue
|
||||||
|
.push(format!("Checked Feeds at {}", now().naive_local()));
|
||||||
|
|
||||||
|
for queued_post in queue_data {
|
||||||
|
let (post, post_metadata) = queued_post;
|
||||||
|
this.message_queue.push(format!("Posting: {}", post.name));
|
||||||
|
|
||||||
|
// Perform Post and Pins
|
||||||
|
let post_data = match this.post(&post).await {
|
||||||
|
Ok(data) => data,
|
||||||
|
Err(e) => {
|
||||||
|
this.error_queue.push(format!("{}", e));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
let _ = this
|
||||||
|
.pin_post(&post_data.post.id, &post_data.community.id)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
// Update Post History
|
||||||
|
let mut index_exists = false;
|
||||||
|
if this.post_history.len() > post_metadata.id {
|
||||||
|
index_exists = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(part_slug) = post_metadata.part {
|
||||||
|
match index_exists {
|
||||||
|
true => {
|
||||||
|
this.post_history[post_metadata.id].last_part_slug = Some(part_slug);
|
||||||
|
this.post_history[post_metadata.id].last_part_time = Some(now().to_string());
|
||||||
|
}
|
||||||
|
false => {
|
||||||
|
let new_history = PrevPost {
|
||||||
|
id: post_metadata.id,
|
||||||
|
last_volume_slug: None,
|
||||||
|
last_volume_time: None,
|
||||||
|
last_part_slug: Some(part_slug),
|
||||||
|
last_part_time: Some(now().to_string()),
|
||||||
|
};
|
||||||
|
this.post_history.push(new_history);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(volume_slug) = post_metadata.volume {
|
||||||
|
match index_exists {
|
||||||
|
true => {
|
||||||
|
this.post_history[post_metadata.id].last_volume_slug = Some(volume_slug);
|
||||||
|
this.post_history[post_metadata.id].last_volume_time = Some(now().to_string());
|
||||||
|
}
|
||||||
|
false => {
|
||||||
|
let new_history = PrevPost {
|
||||||
|
id: post_metadata.id,
|
||||||
|
last_volume_slug: Some(volume_slug),
|
||||||
|
last_volume_time: Some(now().to_string()),
|
||||||
|
last_part_slug: None,
|
||||||
|
last_part_time: None,
|
||||||
|
};
|
||||||
|
this.post_history.push(new_history);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
PrevPost::save(&this.post_history);
|
||||||
|
|
||||||
|
// Fix Queue Lengths and Update Mutex Data
|
||||||
|
while this.message_queue.len() > 5 {
|
||||||
|
this.message_queue.remove(0);
|
||||||
|
}
|
||||||
|
while this.error_queue.len() > 10 {
|
||||||
|
this.error_queue.remove(0);
|
||||||
|
}
|
||||||
|
|
||||||
this.idle().await;
|
this.idle().await;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn print_info(shutdown: Arc<AtomicBool>, bot: Arc<Mutex<Bot>>) {
|
async fn print_info(shutdown: Arc<AtomicBool>, bot: Arc<Mutex<Bot>>) {
|
||||||
while !shutdown.load(Ordering::Relaxed) {
|
while !shutdown.load(Ordering::Relaxed) {
|
||||||
let bot: tokio::sync::MutexGuard<'_, Bot> = bot.lock().await;
|
let snapshot: tokio::sync::MutexGuard<'_, Bot> = bot.lock().await;
|
||||||
sleep(Duration::from_millis(500)).await;
|
sleep(Duration::milliseconds(200).to_std().unwrap()).await;
|
||||||
|
|
||||||
print!("\x1B[2J\x1B[1;1H");
|
print!("\x1B[2J\x1B[1;1H");
|
||||||
println!(
|
println!(
|
||||||
"##[Ascendance of a Bookworm Bot]## | Time: {}",
|
"##[Ascendance of a Bookworm Bot]## | Time: {}",
|
||||||
Utc::now().naive_local().format("%H:%M:%S")
|
Utc::now().naive_local().format("%H:%M:%S")
|
||||||
);
|
);
|
||||||
println!("Instance: {}", bot.config.instance);
|
println!("Instance: {}", snapshot.config.instance);
|
||||||
println!("Ran Last: {}", bot.start_time.format("%d/%m/%Y %H:%M:%S"));
|
println!(
|
||||||
println!("{:#<1$}", "", 30);
|
"Ran Last: {}",
|
||||||
bot.post_history.iter().for_each(|post| {
|
snapshot
|
||||||
print!("| -- |");
|
.start_time
|
||||||
print!("{} ", post.title);
|
.naive_local()
|
||||||
print!("{:<1$}| ", "", 60 - post.title.len());
|
.format("%d/%m/%Y %H:%M:%S")
|
||||||
println!("{}", post.last_post_url);
|
);
|
||||||
|
println!("{:#<1$}", "", 175);
|
||||||
|
snapshot.post_history.iter().for_each(|post| {
|
||||||
|
if post.last_part_time.is_some() && post.last_volume_time.is_some() {
|
||||||
|
let part_time = post.last_part_time.clone().unwrap();
|
||||||
|
let volume_time = post.last_volume_time.clone().unwrap();
|
||||||
|
|
||||||
|
let parsed_part_time = DateTime::<Utc>::from_str(&part_time).unwrap_or(DateTime::<Utc>::from(UNIX_EPOCH)).naive_local();
|
||||||
|
|
||||||
|
let parsed_volume_time = DateTime::<Utc>::from_str(&volume_time).unwrap_or(DateTime::<Utc>::from(UNIX_EPOCH)).naive_local();
|
||||||
|
|
||||||
|
let formatted_time;
|
||||||
|
if parsed_part_time > parsed_volume_time {
|
||||||
|
formatted_time = parsed_part_time;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
formatted_time = parsed_volume_time;
|
||||||
|
}
|
||||||
|
|
||||||
|
print!("| {} |", formatted_time.format("%d/%m/%Y %H:%M:%S"));
|
||||||
|
}
|
||||||
|
else if post.last_part_time.is_some() {
|
||||||
|
let part_time = post.last_part_time.clone().unwrap();
|
||||||
|
let formatted_time: NaiveDateTime = DateTime::<Utc>::from_str(&part_time).unwrap_or(DateTime::<Utc>::from(UNIX_EPOCH)).naive_local();
|
||||||
|
|
||||||
|
print!("| {} |", formatted_time.format("%d/%m/%Y %H:%M:%S"));
|
||||||
|
}
|
||||||
|
else if post.last_volume_time.is_some() {
|
||||||
|
let volume_time = post.last_volume_time.clone().unwrap();
|
||||||
|
let formatted_time = DateTime::<Utc>::from_str(&volume_time).unwrap_or(DateTime::<Utc>::from(UNIX_EPOCH)).naive_local();
|
||||||
|
|
||||||
|
print!("| {} |", formatted_time.format("%d/%m/%Y %H:%M:%S"));
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
print!("| {:<1$} |", "", 19);
|
||||||
|
}
|
||||||
|
|
||||||
|
print!("{:<1$}", "", 2 - post.id.to_string().len());
|
||||||
|
print!("{}| ", post.id);
|
||||||
|
print!("{}", post.last_part_slug.clone().unwrap_or("N/A".to_string()));
|
||||||
|
print!("{:<1$}| ", "", 75 - post.last_part_slug.clone().unwrap_or("N/A".to_string()).len());
|
||||||
|
print!("{}", post.last_volume_slug.clone().unwrap_or("N/A".to_string()));
|
||||||
|
println!("{:<1$}| ", "", 70 - post.last_volume_slug.clone().unwrap_or("N/A".to_string()).len());
|
||||||
});
|
});
|
||||||
bot.message_queue.iter().for_each(|message| {
|
println!("{:#<1$}", "", 175);
|
||||||
|
for error in snapshot.error_queue.iter() {
|
||||||
|
println!("{}", error);
|
||||||
|
}
|
||||||
|
println!("{:#<1$}", "", 175);
|
||||||
|
for message in snapshot.message_queue.iter() {
|
||||||
println!("{}", message);
|
println!("{}", message);
|
||||||
})
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -357,7 +494,7 @@ async fn main() {
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
println!("Starting AoB Bot...");
|
println!("Starting AoB Bot...");
|
||||||
|
|
||||||
let shutdown_clone = shutdown.clone();
|
let shutdown_clone = shutdown.clone();
|
||||||
|
|
||||||
let bot = Arc::new(Mutex::new(Bot::new()));
|
let bot = Arc::new(Mutex::new(Bot::new()));
|
||||||
|
@ -373,8 +510,6 @@ async fn main() {
|
||||||
|
|
||||||
println!("Bot crashed due to unknown Error, restarting thread after wait...");
|
println!("Bot crashed due to unknown Error, restarting thread after wait...");
|
||||||
|
|
||||||
sleep(Duration::from_secs(30)).await;
|
sleep(Duration::seconds(10).to_std().unwrap()).await;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue