aob-lemmy-bot/src/bot.rs

306 lines
9.8 KiB
Rust
Raw Normal View History

2023-12-30 00:27:11 +00:00
use crate::config::{Config, PostBody, SeriesConfig};
use crate::fetchers::jnovel::JPostInfo;
use crate::lemmy::{Lemmy, PostInfo};
2023-12-30 00:27:11 +00:00
use crate::post_history::SeriesHistory;
use crate::{fetchers::{jnovel}, lemmy, write_error, write_info, write_warn, SharedData};
2023-12-30 00:22:04 +00:00
use chrono::{DateTime, Duration, Utc};
use lemmy_api_common::post::CreatePost;
2023-12-17 19:17:14 +00:00
use lemmy_db_schema::newtypes::{CommunityId, LanguageId};
use lemmy_db_schema::PostFeatureType;
2023-12-30 00:27:11 +00:00
use std::collections::HashMap;
use std::sync::Arc;
use tokio::sync::RwLock;
2023-12-17 19:17:14 +00:00
use tokio::time::sleep;
use crate::fetchers::Fetcher;
use systemd_journal_logger::connected_to_journal;
2023-12-17 19:17:14 +00:00
macro_rules! info {
($msg:tt) => {
match connected_to_journal() {
true => log::info!("[INFO] {}", $msg),
false => println!("[INFO] {}", $msg),
}
};
}
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() {
true => log::error!("[ERROR] {}", $msg),
false => eprintln!("[ERROR] {}", $msg),
}
};
}
2023-12-17 19:17:14 +00:00
let mut last_reload: DateTime<Utc>;
let mut lemmy: Lemmy;
let mut login_error: bool;
2023-12-17 19:57:03 +00:00
let mut communities;
2023-12-17 19:17:14 +00:00
{
2023-12-17 21:29:18 +00:00
let mut write = data.write().await;
2023-12-17 19:17:14 +00:00
// Errors during bot init are likely unrecoverable and therefore should panic the bot
// Does not really matter since the bot will get restarted anyway but this way the uptime url logs a downtime
write.config = Config::load();
2023-12-17 19:17:14 +00:00
last_reload = Utc::now();
2023-12-17 21:29:18 +00:00
}
2023-12-17 19:17:14 +00:00
{
let read = data.read().await;
lemmy = match lemmy::login(&read.config).await {
2023-12-17 19:17:14 +00:00
Ok(data) => data,
Err(_) => panic!(),
2023-12-17 19:17:14 +00:00
};
login_error = false;
2023-12-17 19:57:03 +00:00
communities = match lemmy.get_communities().await {
Ok(data) => data,
Err(_) => panic!(),
2023-12-17 19:57:03 +00:00
};
2023-12-17 19:17:14 +00:00
}
2023-12-17 21:29:18 +00:00
{
let mut write = data.write().await;
write.start = Utc::now();
}
2023-12-29 23:53:00 +00:00
let info_msg = "Bot init successful, starting normal operations".to_owned();
info!(info_msg);
2023-12-29 23:53:00 +00:00
2023-12-17 19:17:14 +00:00
loop {
idle(&data).await;
2023-12-17 21:29:18 +00:00
{
let mut write = data.write().await;
2023-12-17 19:17:14 +00:00
2023-12-17 21:29:18 +00:00
write.start = Utc::now();
let message = "Config reloaded".to_owned();
info!(message);
write.config = Config::load();
let message = "Config reloaded".to_owned();
write_info(message);
}
}
2023-12-17 21:29:18 +00:00
{
let info_msg = "Login invalid, refreshing session";
info!(info_msg);
lemmy = match lemmy::login(&config).await {
2023-12-17 21:29:18 +00:00
Ok(data) => data,
Err(_) => continue,
2023-12-17 21:29:18 +00:00
};
login_error = false;
}
}
{
let read = data.read().await;
2023-12-17 22:00:37 +00:00
if read.start - last_reload >= Duration::seconds(read.config.config_reload_seconds as i64) {
2023-12-17 21:29:18 +00:00
communities = match lemmy.get_communities().await {
Ok(data) => data,
Err(_) => {
2023-12-17 21:29:18 +00:00
login_error = true;
2023-12-30 00:27:11 +00:00
continue;
let message = "Communities reloaded".to_owned();
info!(message);
last_reload = Utc::now();
write_info(message);
2023-12-17 21:29:18 +00:00
last_reload = Utc::now();
}
2023-12-17 19:17:14 +00:00
}
2023-12-17 21:29:18 +00:00
{
let mut write = data.write().await;
write.post_history = SeriesHistory::load_history();
2023-12-17 19:17:14 +00:00
}
2023-12-17 21:29:18 +00:00
{
let read = data.read().await;
let series = read.config.series.clone();
drop(read);
for series in series {
2023-12-30 00:27:11 +00:00
if handle_series(&series, &communities, &lemmy, &data)
.await
.is_err()
{
login_error = true;
2023-12-30 00:27:11 +00:00
continue;
2023-12-17 21:29:18 +00:00
};
2023-12-17 19:17:14 +00:00
}
2023-12-17 21:29:18 +00:00
}
2023-12-17 19:17:14 +00:00
idle(&data).await;
}
}
async fn idle(data: &Arc<RwLock<SharedData>>) {
let read = data.read().await;
let mut sleep_duration = Duration::seconds(30);
if Utc::now() - read.start > sleep_duration {
sleep_duration = Duration::seconds(60);
}
if let Some(status_url) = read.config.status_post_url.clone() {
match reqwest::get(status_url).await {
Ok(_) => {}
Err(e) => {
let err_msg = format!("{e}");
error!(err_msg);
2023-12-30 00:27:11 +00:00
}
2023-12-17 19:17:14 +00:00
}
};
while Utc::now() - read.start < sleep_duration {
sleep(Duration::milliseconds(100).to_std().unwrap()).await;
}
}
2023-12-30 00:27:11 +00:00
async fn handle_series(series: &SeriesConfig, communities: &HashMap<String, CommunityId>, lemmy: &Lemmy, data: &Arc<RwLock<SharedData>>) -> Result<(), ()> {
let jnc = jnovel::JFetcherOptions::new(series.slug.clone(), series.parted);
let post_list = match jnc.check_feed().await {
2023-12-17 19:17:14 +00:00
Ok(data) => data,
Err(_) => return Err(()),
2023-12-17 19:17:14 +00:00
};
if post_list.is_empty() && Utc::now().minute() % 10 == 0 {
let info_msg = "No Updates found";
info!(info_msg);
}
2023-12-17 19:17:14 +00:00
for post_info in post_list.clone().iter() {
2023-12-30 00:27:11 +00:00
// todo .clone() likely not needed
2023-12-17 19:17:14 +00:00
let post_part_info = post_info.get_part_info();
let post_lemmy_info = post_info.get_info();
2023-12-17 19:17:14 +00:00
2023-12-17 21:29:18 +00:00
{
let read = data.read().await;
2023-12-30 00:27:11 +00:00
if read.post_history.check_for_post(
series.slug.as_str(),
post_part_info.as_string().as_str(),
post_lemmy_info.title.as_str(),
) {
continue;
2023-12-17 19:17:14 +00:00
}
2023-12-17 21:29:18 +00:00
}
2023-12-17 19:17:14 +00:00
let post_series_config = match post_info {
JPostInfo::Chapter { .. } => &series.prepub_community,
JPostInfo::Volume { .. } => &series.volume_community,
2023-12-17 19:17:14 +00:00
};
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 {
2023-12-30 00:22:13 +00:00
name: post_lemmy_info.title.clone(),
2023-12-17 19:35:37 +00:00
community_id,
2023-12-17 19:17:14 +00:00
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
};
2023-12-30 00:27:11 +00:00
let info = format!(
"Posting '{}' to {}",
post_lemmy_info.title.as_str(),
post_series_config.name.as_str()
);
info!(info);
2023-12-17 19:17:14 +00:00
let post_id = lemmy.post(post_data).await?;
{
let read = data.read().await;
2023-12-30 00:27:11 +00:00
if post_series_config.pin_settings.pin_new_post_community
&& !read
.config
.protected_communities
info!(info);
2023-12-30 00:27:11 +00:00
{
let info = format!(
"Pinning '{}' to {}",
post_lemmy_info.title,
post_series_config.name.as_str()
);
2023-12-30 00:22:13 +00:00
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];
2023-12-30 00:27:11 +00:00
lemmy
.unpin(community_pinned_post.post.id, PostFeatureType::Community)
.await?;
}
lemmy.pin(post_id, PostFeatureType::Community).await?;
2023-12-30 00:27:11 +00:00
} 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);
2023-12-17 19:17:14 +00:00
}
warn!(message);
2023-12-18 10:23:47 +00:00
}
2023-12-17 19:17:14 +00:00
2023-12-18 10:23:47 +00:00
let read = data.read().await;
2023-12-17 19:17:14 +00:00
if post_series_config.pin_settings.pin_new_post_local {
2023-12-30 00:22:13 +00:00
let info = format!("Pinning '{}' to Instance", post_lemmy_info.title);
info!(info);
2023-12-17 19:17:14 +00:00
let pinned_posts = lemmy.get_local_pinned().await?;
2023-12-17 19:35:37 +00:00
if !pinned_posts.is_empty() {
2023-12-18 10:23:47 +00:00
for pinned_post in pinned_posts {
2023-12-30 00:27:11 +00:00
if read
.config
.protected_communities
.contains(&pinned_post.community.name)
{
continue;
} else {
2023-12-18 10:23:47 +00:00
let community_pinned_post = &pinned_post;
2023-12-30 00:27:11 +00:00
lemmy
.unpin(community_pinned_post.post.id, PostFeatureType::Local)
.await?;
break;
2023-12-18 10:23:47 +00:00
}
}
2023-12-17 19:17:14 +00:00
}
lemmy.pin(post_id, PostFeatureType::Local).await?;
}
2023-12-17 21:29:18 +00:00
let mut series_history = read.post_history.get_series(series.slug.as_str());
2023-12-17 19:17:14 +00:00
let mut part_history = series_history.get_part(post_part_info.as_string().as_str());
2023-12-17 21:29:18 +00:00
drop(read);
2023-12-17 19:17:14 +00:00
match post_info {
JPostInfo::Chapter { .. } => part_history.chapter = post_info.get_info().title,
JPostInfo::Volume { .. } => part_history.volume = post_info.get_info().title,
2023-12-17 19:17:14 +00:00
}
series_history.set_part(post_part_info.as_string().as_str(), part_history);
2023-12-17 21:29:18 +00:00
let mut write = data.write().await;
2023-12-30 00:27:11 +00:00
write
.post_history
.set_series(series.slug.as_str(), series_history);
write.post_history.save_history();
2023-12-17 19:17:14 +00:00
}
Ok(())
}