parent
aefceda628
commit
6520cc65a3
8 changed files with 715 additions and 481 deletions
src
307
src/bot.rs
307
src/bot.rs
|
@ -1,14 +1,10 @@
|
|||
use crate::{config::{Config, PostBody, SeriesConfig}, fetchers::{jnovel}, lemmy};
|
||||
use crate::fetchers::jnovel::JPostInfo;
|
||||
use crate::lemmy::{Lemmy, PostInfo};
|
||||
use crate::post_history::SeriesHistory;
|
||||
use chrono::{DateTime, Duration, Timelike, Utc};
|
||||
use lemmy_api_common::post::CreatePost;
|
||||
use lemmy_db_schema::newtypes::{CommunityId, LanguageId};
|
||||
use lemmy_db_schema::PostFeatureType;
|
||||
use std::collections::HashMap;
|
||||
use crate::{config::{Config}};
|
||||
use crate::lemmy::{Lemmy};
|
||||
use crate::post_history::{SeriesHistory};
|
||||
use chrono::{DateTime, Duration, Utc};
|
||||
use std::sync::{Arc, RwLock};
|
||||
use notify::{Event, EventKind, event::{AccessKind, AccessMode}, RecursiveMode, Watcher};
|
||||
use tokio::time::sleep;
|
||||
use crate::fetchers::Fetcher;
|
||||
use systemd_journal_logger::connected_to_journal;
|
||||
|
||||
macro_rules! info {
|
||||
|
@ -20,15 +16,6 @@ macro_rules! info {
|
|||
};
|
||||
}
|
||||
|
||||
macro_rules! warn {
|
||||
($msg:tt) => {
|
||||
match connected_to_journal() {
|
||||
true => log::warn!("[WARN] {}", $msg),
|
||||
false => println!("[WARN] {}", $msg),
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
macro_rules! error {
|
||||
($msg:tt) => {
|
||||
match connected_to_journal() {
|
||||
|
@ -38,231 +25,97 @@ macro_rules! error {
|
|||
};
|
||||
}
|
||||
|
||||
pub(crate) async fn run() {
|
||||
let mut last_reload: DateTime<Utc>;
|
||||
let mut lemmy: Lemmy;
|
||||
let mut login_error: bool;
|
||||
let mut communities: HashMap<String, CommunityId>;
|
||||
let mut post_history: SeriesHistory;
|
||||
let mut start: DateTime<Utc>;
|
||||
let mut config: Config = Config::load();
|
||||
last_reload = Utc::now();
|
||||
pub(crate) struct Bot {
|
||||
shared_config: Arc<RwLock<Config>>,
|
||||
history: SeriesHistory,
|
||||
run_start_time: DateTime<Utc>
|
||||
}
|
||||
|
||||
lemmy = match lemmy::login(&config).await {
|
||||
Ok(data) => data,
|
||||
Err(_) => panic!(),
|
||||
};
|
||||
login_error = false;
|
||||
enum Wait {
|
||||
Absolute,
|
||||
Buffer
|
||||
}
|
||||
|
||||
communities = match lemmy.get_communities().await {
|
||||
Ok(data) => data,
|
||||
Err(_) => panic!(),
|
||||
};
|
||||
impl Bot {
|
||||
pub(crate) fn new() -> Self {
|
||||
let config = Config::load();
|
||||
let shared_config: Arc<RwLock<Config>> = Arc::new(RwLock::new(config));
|
||||
|
||||
start = Utc::now();
|
||||
let shared_config_copy = shared_config.clone();
|
||||
let mut watcher = notify::recommended_watcher(move |res: Result<Event, notify::Error>| {
|
||||
match res {
|
||||
Ok(event) => {
|
||||
if event.kind == EventKind::Access(AccessKind::Close(AccessMode::Write)) {
|
||||
let mut write = shared_config_copy.write().expect("Write Lock Failed");
|
||||
let new_config = Config::load();
|
||||
write.series = new_config.series;
|
||||
write.instance = new_config.instance;
|
||||
write.protected_communities = new_config.protected_communities;
|
||||
write.status_post_url = new_config.status_post_url;
|
||||
info!("Reloaded Configuration");
|
||||
}
|
||||
},
|
||||
Err(e) => {
|
||||
let msg = format!("Error watching files: {e}");
|
||||
error!(msg);
|
||||
}
|
||||
}
|
||||
}).expect("Watcher Error");
|
||||
|
||||
let info_msg = "Bot init successful, starting normal operations".to_owned();
|
||||
info!(info_msg);
|
||||
watcher.watch(&Config::get_path(), RecursiveMode::NonRecursive).expect("Error in watcher");
|
||||
|
||||
loop {
|
||||
idle(&start, &config).await;
|
||||
start = Utc::now();
|
||||
let history: SeriesHistory = SeriesHistory::load_history();
|
||||
|
||||
// replace with watcher
|
||||
if start - last_reload >= Duration::seconds(config.config_reload_seconds as i64) {
|
||||
config = Config::load();
|
||||
let message = "Config reloaded".to_owned();
|
||||
info!(message);
|
||||
}
|
||||
|
||||
if login_error {
|
||||
let info_msg = "Login invalid, refreshing session";
|
||||
info!(info_msg);
|
||||
lemmy = match lemmy::login(&config).await {
|
||||
Bot { shared_config, history, run_start_time: Utc::now() }
|
||||
}
|
||||
pub(crate) async fn run(&mut self) {
|
||||
loop {
|
||||
let mut lemmy = match Lemmy::new(&self.shared_config).await {
|
||||
Ok(data) => data,
|
||||
Err(_) => continue,
|
||||
};
|
||||
login_error = false;
|
||||
}
|
||||
|
||||
if start - last_reload >= Duration::seconds(config.config_reload_seconds as i64) {
|
||||
communities = match lemmy.get_communities().await {
|
||||
Ok(data) => data,
|
||||
Err(_) => {
|
||||
login_error = true;
|
||||
continue;
|
||||
lemmy.get_communities().await;
|
||||
|
||||
let start: DateTime<Utc> = Utc::now();
|
||||
while Utc::now() - start <= Duration::minutes(60) {
|
||||
self.run_start_time = Utc::now();
|
||||
self.ping_status().await;
|
||||
let read_copy = self.shared_config.read().expect("Read Lock Failed").clone();
|
||||
for series in read_copy.series {
|
||||
series.update(&mut self.history, &lemmy, &self.shared_config).await;
|
||||
self.wait(1, Wait::Absolute).await;
|
||||
}
|
||||
};
|
||||
let message = "Communities reloaded".to_owned();
|
||||
info!(message);
|
||||
last_reload = Utc::now();
|
||||
}
|
||||
|
||||
post_history = SeriesHistory::load_history();
|
||||
|
||||
let series = config.series.clone();
|
||||
for series in series {
|
||||
sleep(Duration::seconds(1).to_std().unwrap()).await;
|
||||
let info_msg = format!("Handling Series {}", series.slug);
|
||||
info!(info_msg);
|
||||
if handle_series(&series, &communities, &lemmy, &config, &mut post_history)
|
||||
.await
|
||||
.is_err()
|
||||
{
|
||||
login_error = true;
|
||||
continue;
|
||||
};
|
||||
}
|
||||
|
||||
idle(&start, &config).await;
|
||||
}
|
||||
}
|
||||
|
||||
async fn idle(start: &DateTime<Utc>, config: &Config) {
|
||||
let mut sleep_duration = Duration::seconds(30);
|
||||
let info_msg = format!("Idling for {} seconds", sleep_duration.num_seconds());
|
||||
info!(info_msg);
|
||||
if Utc::now() - start > sleep_duration {
|
||||
sleep_duration = Duration::seconds(60);
|
||||
}
|
||||
|
||||
if let Some(status_url) = config.status_post_url.clone() {
|
||||
match reqwest::get(status_url).await {
|
||||
Ok(_) => {}
|
||||
Err(e) => {
|
||||
let err_msg = format!("{e}");
|
||||
error!(err_msg);
|
||||
self.wait(30, Wait::Buffer).await;
|
||||
self.ping_status().await;
|
||||
self.wait(30, Wait::Absolute).await;
|
||||
}
|
||||
|
||||
lemmy.logout().await;
|
||||
}
|
||||
};
|
||||
|
||||
while Utc::now() - start < sleep_duration {
|
||||
sleep(Duration::milliseconds(100).to_std().unwrap()).await;
|
||||
}
|
||||
}
|
||||
|
||||
async fn handle_series(series: &SeriesConfig, communities: &HashMap<String, CommunityId>, lemmy: &Lemmy, config: &Config, post_history: &mut SeriesHistory ) -> Result<(), ()> {
|
||||
let jnc = jnovel::JFetcherOptions::new(series.slug.clone(), series.parted);
|
||||
let post_list = match jnc.check_feed().await {
|
||||
Ok(data) => data,
|
||||
Err(_) => return Err(()),
|
||||
};
|
||||
|
||||
if post_list.is_empty() && Utc::now().minute() % 10 == 0 {
|
||||
let info_msg = "No Updates found";
|
||||
info!(info_msg);
|
||||
}
|
||||
|
||||
for post_info in post_list.clone().iter() {
|
||||
let post_part_info = post_info.get_part_info();
|
||||
let post_lemmy_info = post_info.get_info();
|
||||
|
||||
if post_history.check_for_post(
|
||||
series.slug.as_str(),
|
||||
post_part_info.as_string().as_str(),
|
||||
post_lemmy_info.title.as_str(),
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
|
||||
let post_series_config = match post_info {
|
||||
JPostInfo::Chapter { .. } => &series.prepub_community,
|
||||
JPostInfo::Volume { .. } => &series.volume_community,
|
||||
};
|
||||
|
||||
let community_id = *communities
|
||||
.get(post_series_config.name.as_str())
|
||||
.expect("Given community is invalid");
|
||||
|
||||
let post_body = match &post_series_config.post_body {
|
||||
PostBody::None => None,
|
||||
PostBody::Description => post_info.get_description(),
|
||||
PostBody::Custom(text) => Some(text.clone()),
|
||||
};
|
||||
|
||||
let post_data = CreatePost {
|
||||
name: post_lemmy_info.title.clone(),
|
||||
community_id,
|
||||
url: Some(post_lemmy_info.url),
|
||||
body: post_body,
|
||||
honeypot: None,
|
||||
nsfw: None,
|
||||
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()
|
||||
);
|
||||
info!(info);
|
||||
let post_id = lemmy.post(post_data).await?;
|
||||
|
||||
if post_series_config.pin_settings.pin_new_post_community
|
||||
&& config
|
||||
.protected_communities
|
||||
.contains(&post_series_config.name)
|
||||
{
|
||||
let info = format!(
|
||||
"Pinning '{}' to {}",
|
||||
post_lemmy_info.title,
|
||||
post_series_config.name.as_str()
|
||||
);
|
||||
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.pin(post_id, PostFeatureType::Community).await?;
|
||||
} else if 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
|
||||
);
|
||||
warn!(message);
|
||||
}
|
||||
|
||||
if post_series_config.pin_settings.pin_new_post_local {
|
||||
let info = format!("Pinning '{}' to Instance", post_lemmy_info.title);
|
||||
info!(info);
|
||||
let pinned_posts = lemmy.get_local_pinned().await?;
|
||||
if !pinned_posts.is_empty() {
|
||||
for pinned_post in pinned_posts {
|
||||
if 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;
|
||||
}
|
||||
async fn ping_status(&self) {
|
||||
let read_config = &self.shared_config.read().expect("Read Lock Failed").clone();
|
||||
if let Some(status_url) = &read_config.status_post_url {
|
||||
match reqwest::get(status_url).await {
|
||||
Ok(_) => {},
|
||||
Err(e) => {
|
||||
let err_msg = format!("While pinging status URL: {e}");
|
||||
error!(err_msg);
|
||||
}
|
||||
}
|
||||
lemmy.pin(post_id, PostFeatureType::Local).await?;
|
||||
}
|
||||
|
||||
let mut series_history = post_history.get_series(series.slug.as_str());
|
||||
let mut part_history = series_history.get_part(post_part_info.as_string().as_str());
|
||||
|
||||
match post_info {
|
||||
JPostInfo::Chapter { .. } => part_history.chapter = post_info.get_info().title,
|
||||
JPostInfo::Volume { .. } => part_history.volume = post_info.get_info().title,
|
||||
}
|
||||
|
||||
series_history.set_part(post_part_info.as_string().as_str(), part_history);
|
||||
post_history
|
||||
.set_series(series.slug.as_str(), series_history);
|
||||
post_history.save_history();
|
||||
}
|
||||
Ok(())
|
||||
|
||||
async fn wait(&self, seconds: i64, start_time: Wait) {
|
||||
let duration: Duration = Duration::seconds(seconds);
|
||||
let start_time: DateTime<Utc> = match start_time {
|
||||
Wait::Absolute => Utc::now(),
|
||||
Wait::Buffer => self.run_start_time,
|
||||
};
|
||||
while Utc::now() - start_time < duration {
|
||||
sleep(Duration::milliseconds(100).to_std().unwrap()).await
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue