226 lines
7.9 KiB
Rust
226 lines
7.9 KiB
Rust
|
use std::collections::HashMap;
|
||
|
use std::error::Error;
|
||
|
use std::fmt::format;
|
||
|
use std::ops::Deref;
|
||
|
use std::sync::{Arc};
|
||
|
use chrono::{DateTime, Duration, NaiveDateTime, Timelike, Utc};
|
||
|
use lemmy_db_schema::newtypes::{CommunityId, LanguageId};
|
||
|
use lemmy_db_schema::PostFeatureType;
|
||
|
use tokio::sync::{RwLock, RwLockWriteGuard, RwLockReadGuard};
|
||
|
use crate::{jnovel, lemmy, Message, SharedData};
|
||
|
use crate::config::{Config, PostBody, SeriesConfig};
|
||
|
use crate::jnovel::PostInfo;
|
||
|
use crate::lemmy::{CustomCreatePost, Lemmy};
|
||
|
use crate::post_history::SeriesHistory;
|
||
|
use tokio::time::sleep;
|
||
|
|
||
|
pub(crate) async fn run(data: Arc<RwLock<SharedData>>){
|
||
|
let credentials = match lemmy::Credentials::set_credentials() {
|
||
|
Ok(creds) => creds,
|
||
|
Err(e) => panic!("{}", e.to_string()),
|
||
|
};
|
||
|
|
||
|
let mut last_reload: DateTime<Utc>;
|
||
|
let mut lemmy: Lemmy;
|
||
|
let mut login_error: bool;
|
||
|
let communities: HashMap<String, CommunityId>;
|
||
|
{
|
||
|
let mut shared_data = data.write().await;
|
||
|
|
||
|
// 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
|
||
|
shared_data.config = match Config::load() {
|
||
|
Ok(data) => data,
|
||
|
Err(e) => panic!("{}", e),
|
||
|
};
|
||
|
last_reload = Utc::now();
|
||
|
|
||
|
lemmy = match lemmy::login(&credentials, shared_data.config.instance.as_str()).await {
|
||
|
Ok(data) => data,
|
||
|
Err(e) => panic!("{}", e),
|
||
|
};
|
||
|
login_error = false;
|
||
|
|
||
|
communities = match lemmy.get_communities().await {
|
||
|
Ok(data) => data,
|
||
|
Err(e) => panic!("{}", e),
|
||
|
};
|
||
|
}
|
||
|
|
||
|
while (Utc::now().naive_local().second() != 30) {
|
||
|
sleep(Duration::milliseconds(100).to_std().unwrap()).await;
|
||
|
}
|
||
|
|
||
|
loop {
|
||
|
idle(&data).await;
|
||
|
|
||
|
let mut shared_data = data.write().await;
|
||
|
|
||
|
shared_data.start = Utc::now();
|
||
|
|
||
|
if shared_data.start - last_reload > Duration::seconds(shared_data.config.config_reload_seconds as i64) {
|
||
|
shared_data.config = match Config::load() {
|
||
|
Ok(data) => data,
|
||
|
Err(e) => panic!("{}", e),
|
||
|
};
|
||
|
}
|
||
|
|
||
|
if login_error {
|
||
|
lemmy = match lemmy::login(&credentials, shared_data.config.instance.as_str()).await {
|
||
|
Ok(data) => data,
|
||
|
Err(e) => {
|
||
|
shared_data.messages.push(Message::Error(format!("{}", e)));
|
||
|
continue
|
||
|
}
|
||
|
};
|
||
|
login_error = false;
|
||
|
}
|
||
|
|
||
|
if shared_data.start - last_reload > Duration::seconds(shared_data.config.config_reload_seconds as i64) {
|
||
|
let communities = match lemmy.get_communities().await {
|
||
|
Ok(data) => data,
|
||
|
Err(e) => {
|
||
|
login_error = true;
|
||
|
shared_data.messages.push(Message::Error(format!("{}", e)));
|
||
|
continue
|
||
|
}
|
||
|
};
|
||
|
last_reload = Utc::now();
|
||
|
}
|
||
|
|
||
|
shared_data.post_history = match SeriesHistory::load_history() {
|
||
|
Ok(data) => data,
|
||
|
Err(e) => {
|
||
|
login_error = true;
|
||
|
shared_data.messages.push(Message::Warning(format!("{}", e)));
|
||
|
continue
|
||
|
}
|
||
|
};
|
||
|
|
||
|
for series in shared_data.config.series.clone() {
|
||
|
match handle_series(&series, &communities, &lemmy, &mut shared_data).await {
|
||
|
Ok(data) => data,
|
||
|
Err(e) => {
|
||
|
login_error = true;
|
||
|
shared_data.messages.push(Message::Warning(format!("{}", e)));
|
||
|
continue
|
||
|
}
|
||
|
};
|
||
|
}
|
||
|
|
||
|
let down = shared_data.downgrade();
|
||
|
|
||
|
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 mut write = data.write().await;
|
||
|
write.messages.push(Message::Error(format!("{}", e)))
|
||
|
},
|
||
|
}
|
||
|
};
|
||
|
|
||
|
while Utc::now() - read.start < sleep_duration {
|
||
|
sleep(Duration::milliseconds(100).to_std().unwrap()).await;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
async fn handle_series<'a>(
|
||
|
series: &SeriesConfig,
|
||
|
communities: &HashMap<String, CommunityId>,
|
||
|
lemmy: &Lemmy,
|
||
|
data: &mut RwLockWriteGuard<'a, SharedData>,
|
||
|
) -> Result<(), Box<dyn Error>> {
|
||
|
let mut post_list = match jnovel::check_feed(series.slug.as_str(), series.parted).await {
|
||
|
Ok(data) => data,
|
||
|
Err(e) => panic!("{:#?}", e),
|
||
|
};
|
||
|
|
||
|
for (index, post_info) in post_list.clone().iter().enumerate() { // todo .clone() likely not needed
|
||
|
let post_part_info = post_info.get_part_info();
|
||
|
let post_lemmy_info = post_info.get_lemmy_info();
|
||
|
|
||
|
if data.post_history.check_for_post(series.slug.as_str(), post_part_info.as_string().as_str(), post_lemmy_info.title.as_str()) {
|
||
|
data.messages.push(Message::Info(format!("Skipping '{}' since already posted", post_lemmy_info.title)));
|
||
|
post_list.remove(index);
|
||
|
continue
|
||
|
}
|
||
|
|
||
|
let post_series_config = match post_info {
|
||
|
PostInfo::Chapter {..} => {&series.prepub_community},
|
||
|
PostInfo::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 = CustomCreatePost {
|
||
|
name: post_lemmy_info.title,
|
||
|
community_id: 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 post_id = lemmy.post(post_data).await?;
|
||
|
|
||
|
if post_series_config.pin_settings.pin_new_post_community {
|
||
|
let pinned_posts = lemmy.get_community_pinned(community_id).await?;
|
||
|
if pinned_posts.len() > 0 {
|
||
|
let community_pinned_post = &pinned_posts[0];
|
||
|
lemmy.unpin(community_pinned_post.post.id, PostFeatureType::Community).await?;
|
||
|
}
|
||
|
lemmy.pin(post_id, PostFeatureType::Community).await?;
|
||
|
}
|
||
|
|
||
|
if post_series_config.pin_settings.pin_new_post_local {
|
||
|
let pinned_posts = lemmy.get_local_pinned().await?;
|
||
|
if pinned_posts.len() > 0 {
|
||
|
let community_pinned_post = &pinned_posts[0];
|
||
|
lemmy.unpin(community_pinned_post.post.id, PostFeatureType::Local).await?;
|
||
|
}
|
||
|
lemmy.pin(post_id, PostFeatureType::Local).await?;
|
||
|
}
|
||
|
|
||
|
let mut series_history = data.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 {
|
||
|
PostInfo::Chapter {..} => {
|
||
|
part_history.chapter = post_info.get_lemmy_info().title
|
||
|
},
|
||
|
PostInfo::Volume {..} => {
|
||
|
part_history.volume = post_info.get_lemmy_info().title
|
||
|
}
|
||
|
}
|
||
|
|
||
|
series_history.set_part(post_part_info.as_string().as_str(), part_history);
|
||
|
data.post_history.set_series(series.slug.as_str(), series_history);
|
||
|
data.post_history.save_history();
|
||
|
}
|
||
|
Ok(())
|
||
|
}
|
||
|
|
||
|
|
||
|
|