Compare commits
4 commits
Author | SHA1 | Date | |
---|---|---|---|
041590a559 | |||
d6f883f890 | |||
b07420e0bd | |||
0fc71f0a7d |
16 changed files with 332 additions and 709 deletions
|
@ -137,7 +137,7 @@ jobs:
|
||||||
run: rm release_blobs/build.env
|
run: rm release_blobs/build.env
|
||||||
-
|
-
|
||||||
name: Release New Version
|
name: Release New Version
|
||||||
uses: actions/forgejo-release@v1
|
uses: actions/forgejo-release@v2
|
||||||
with:
|
with:
|
||||||
direction: upload
|
direction: upload
|
||||||
url: https://forgejo.neshweb.net
|
url: https://forgejo.neshweb.net
|
||||||
|
|
41
Cargo.lock
generated
41
Cargo.lock
generated
|
@ -52,9 +52,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "aob-lemmy-bot"
|
name = "aob-lemmy-bot"
|
||||||
version = "3.1.0"
|
version = "3.2.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"atomic_enum",
|
"async-trait",
|
||||||
"chrono",
|
"chrono",
|
||||||
"confy",
|
"confy",
|
||||||
"lemmy_api_common",
|
"lemmy_api_common",
|
||||||
|
@ -62,13 +62,13 @@ dependencies = [
|
||||||
"log",
|
"log",
|
||||||
"notify",
|
"notify",
|
||||||
"once_cell",
|
"once_cell",
|
||||||
"parking_lot",
|
|
||||||
"reqwest",
|
"reqwest",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_derive",
|
"serde_derive",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
"strum_macros",
|
"strum_macros",
|
||||||
"systemd-journal-logger",
|
"systemd-journal-logger",
|
||||||
|
"tokio",
|
||||||
"toml",
|
"toml",
|
||||||
"url",
|
"url",
|
||||||
]
|
]
|
||||||
|
@ -95,17 +95,6 @@ dependencies = [
|
||||||
"syn",
|
"syn",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "atomic_enum"
|
|
||||||
version = "0.3.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "99e1aca718ea7b89985790c94aad72d77533063fe00bc497bb79a7c2dae6a661"
|
|
||||||
dependencies = [
|
|
||||||
"proc-macro2",
|
|
||||||
"quote",
|
|
||||||
"syn",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "autocfg"
|
name = "autocfg"
|
||||||
version = "1.1.0"
|
version = "1.1.0"
|
||||||
|
@ -456,9 +445,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "futures-channel"
|
name = "futures-channel"
|
||||||
version = "0.3.30"
|
version = "0.3.29"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "eac8f7d7865dcb88bd4373ab671c8cf4508703796caa2b1985a9ca867b3fcb78"
|
checksum = "ff4dd66668b557604244583e3e1e1eada8c5c2e96a6d0d6653ede395b78bbacb"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"futures-core",
|
"futures-core",
|
||||||
"futures-sink",
|
"futures-sink",
|
||||||
|
@ -1210,9 +1199,9 @@ checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "proc-macro2"
|
name = "proc-macro2"
|
||||||
version = "1.0.86"
|
version = "1.0.76"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "5e719e8df665df0d1c8fbfd238015744736151d4445ec0836b8e628aae103b77"
|
checksum = "95fc56cda0b5c3325f5fbbd7ff9fda9e02bb00bb3dac51252d2f1bfa1cb8cc8c"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"unicode-ident",
|
"unicode-ident",
|
||||||
]
|
]
|
||||||
|
@ -1608,9 +1597,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "syn"
|
name = "syn"
|
||||||
version = "2.0.71"
|
version = "2.0.48"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "b146dcf730474b4bcd16c311627b31ede9ab149045db4d6088b3becaea046462"
|
checksum = "0f3531638e407dfc0814761abb7c00a5b54992b849452a0646b7f65c9f770f3f"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
|
@ -1758,9 +1747,21 @@ dependencies = [
|
||||||
"num_cpus",
|
"num_cpus",
|
||||||
"pin-project-lite",
|
"pin-project-lite",
|
||||||
"socket2",
|
"socket2",
|
||||||
|
"tokio-macros",
|
||||||
"windows-sys 0.48.0",
|
"windows-sys 0.48.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tokio-macros"
|
||||||
|
version = "2.2.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "5b8a1e28f2deaa14e508979454cb3a223b10b938b45af148bc0986de36f1923b"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tokio-native-tls"
|
name = "tokio-native-tls"
|
||||||
version = "0.3.1"
|
version = "0.3.1"
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
[package]
|
[package]
|
||||||
authors = ["Neshura"]
|
authors = ["Neshura"]
|
||||||
name = "aob-lemmy-bot"
|
name = "aob-lemmy-bot"
|
||||||
version = "3.1.0"
|
version = "3.2.0"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
description = "Bot for automatically posting new chapters of 'Ascendance of a Bookworm' released by J-Novel Club"
|
description = "Bot for automatically posting new chapters of 'Ascendance of a Bookworm' released by J-Novel Club"
|
||||||
license = "GPL-3.0-or-later"
|
license = "GPL-3.0-or-later"
|
||||||
|
@ -25,11 +25,11 @@ serde = "^1.0"
|
||||||
serde_derive = "^1.0"
|
serde_derive = "^1.0"
|
||||||
serde_json = "^1.0"
|
serde_json = "^1.0"
|
||||||
strum_macros = "^0.26"
|
strum_macros = "^0.26"
|
||||||
|
tokio = { version = "^1.37", features = ["rt", "rt-multi-thread", "macros"] }
|
||||||
url = "^2.5"
|
url = "^2.5"
|
||||||
confy = "^0.6"
|
confy = "^0.6"
|
||||||
toml = "^0.8"
|
toml = "^0.8"
|
||||||
systemd-journal-logger = "^2.1.1"
|
systemd-journal-logger = "^2.1.1"
|
||||||
log = "^0.4"
|
log = "^0.4"
|
||||||
notify = "6.1.1"
|
async-trait = "^0.1"
|
||||||
parking_lot = "0.12.3"
|
notify = "6.1.1"
|
||||||
atomic_enum = "0.3.0"
|
|
|
@ -6,6 +6,7 @@ instance = "https://lemmy.example.org"
|
||||||
username = "BotUserName"
|
username = "BotUserName"
|
||||||
password = "BotPassword"
|
password = "BotPassword"
|
||||||
status_post_url = "PostUrlForStatusMonitoring"
|
status_post_url = "PostUrlForStatusMonitoring"
|
||||||
|
config_reload_seconds = 10800
|
||||||
|
|
||||||
protected_communities = [ "community_where_pins_should_stay" ]
|
protected_communities = [ "community_where_pins_should_stay" ]
|
||||||
|
|
||||||
|
|
190
src/bot.rs
190
src/bot.rs
|
@ -1,134 +1,136 @@
|
||||||
use std::collections::HashMap;
|
use crate::{config::{Config}, HTTP_CLIENT};
|
||||||
use crate::{AtomicHealthSignal, HealthSignal, HTTP_CLIENT};
|
|
||||||
use crate::lemmy::{Lemmy};
|
use crate::lemmy::{Lemmy};
|
||||||
use crate::post_history::{SeriesHistory};
|
use crate::post_history::{SeriesHistory};
|
||||||
use chrono::{DateTime, Duration, Utc};
|
use chrono::{DateTime, Duration, Utc};
|
||||||
use std::sync::{Arc};
|
use std::sync::{Arc, RwLock};
|
||||||
use std::sync::atomic::{AtomicBool, Ordering};
|
use notify::{Event, EventKind, event::{AccessKind, AccessMode}, RecursiveMode, Watcher};
|
||||||
use std::thread::sleep;
|
use tokio::time::sleep;
|
||||||
use parking_lot::RwLock;
|
use systemd_journal_logger::connected_to_journal;
|
||||||
use crate::logging::Logging;
|
|
||||||
use crate::settings::{BotSettings, SeriesSettings};
|
macro_rules! debug {
|
||||||
|
($msg:tt) => {
|
||||||
|
match connected_to_journal() {
|
||||||
|
true => log::debug!("[DEBUG] {}", $msg),
|
||||||
|
false => println!("[DEBUG] {}", $msg),
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
macro_rules! info {
|
||||||
|
($msg:tt) => {
|
||||||
|
match connected_to_journal() {
|
||||||
|
true => log::info!("[INFO] {}", $msg),
|
||||||
|
false => println!("[INFO] {}", $msg),
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
macro_rules! error {
|
||||||
|
($msg:tt) => {
|
||||||
|
match connected_to_journal() {
|
||||||
|
true => log::error!("[ERROR] {}", $msg),
|
||||||
|
false => eprintln!("[ERROR] {}", $msg),
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
pub(crate) struct Bot {
|
pub(crate) struct Bot {
|
||||||
settings: BotSettings,
|
shared_config: Arc<RwLock<Config>>,
|
||||||
settings_id: u16,
|
|
||||||
health_signal: Arc<AtomicHealthSignal>,
|
|
||||||
history: SeriesHistory,
|
history: SeriesHistory,
|
||||||
login_time: Option<DateTime<Utc>>,
|
run_start_time: DateTime<Utc>
|
||||||
}
|
}
|
||||||
|
|
||||||
enum Wait {
|
enum Wait {
|
||||||
Absolute,
|
Absolute,
|
||||||
Buffer(DateTime<Utc>)
|
Buffer
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Bot {
|
impl Bot {
|
||||||
pub(crate) fn new(health_signal: Arc<AtomicHealthSignal>, settings_id: u16, settings: BotSettings) -> Self {
|
pub(crate) fn new() -> Self {
|
||||||
|
let config = Config::load();
|
||||||
|
let shared_config: Arc<RwLock<Config>> = Arc::new(RwLock::new(config));
|
||||||
|
|
||||||
|
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");
|
||||||
|
|
||||||
|
watcher.watch(&Config::get_path(), RecursiveMode::NonRecursive).expect("Error in watcher");
|
||||||
|
|
||||||
let history: SeriesHistory = SeriesHistory::load_history();
|
let history: SeriesHistory = SeriesHistory::load_history();
|
||||||
|
|
||||||
|
Bot { shared_config, history, run_start_time: Utc::now() }
|
||||||
Bot { health_signal, settings_id, settings, history, login_time: None }
|
|
||||||
}
|
}
|
||||||
pub(crate) fn run(
|
pub(crate) async fn run(&mut self) {
|
||||||
&mut self,
|
|
||||||
series_settings: Arc<RwLock<HashMap<u16, SeriesSettings>>>,
|
|
||||||
bot_settings: Arc<RwLock<HashMap<u16, BotSettings>>>,
|
|
||||||
update_pending: Arc<AtomicBool>
|
|
||||||
) {
|
|
||||||
let mut last_ping = Utc::now();
|
|
||||||
if let Some(interval) = self.settings.status_post_interval() {
|
|
||||||
last_ping -= interval
|
|
||||||
}
|
|
||||||
loop {
|
loop {
|
||||||
match self.health_signal.load(Ordering::SeqCst) {
|
let mut lemmy = match Lemmy::new(&self.shared_config).await {
|
||||||
HealthSignal::RequestAlive => {
|
Ok(data) => data,
|
||||||
self.health_signal.store(HealthSignal::ConfirmAlive, Ordering::SeqCst);
|
Err(_) => continue,
|
||||||
}
|
};
|
||||||
HealthSignal::RequestStop => {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
_ => {}
|
|
||||||
}
|
|
||||||
|
|
||||||
if update_pending.load(Ordering::SeqCst) {
|
lemmy.get_communities().await;
|
||||||
Logging::debug("Awaiting Config Update");
|
|
||||||
sleep(Duration::milliseconds(100).to_std().unwrap());
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
self.settings = bot_settings.read().get(&self.settings_id).unwrap().clone();
|
|
||||||
|
|
||||||
// perform status ping
|
|
||||||
if let Some(interval) = self.settings.status_post_interval() {
|
|
||||||
if Utc::now() + Duration::seconds(1) >= last_ping + interval {
|
|
||||||
if self.ping_status() {
|
|
||||||
last_ping = Utc::now();
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(login_time) = self.login_time {
|
|
||||||
if Utc::now() - login_time >= Duration::minutes(60) {
|
|
||||||
//self.logout();
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
//self.login();
|
|
||||||
}
|
|
||||||
|
|
||||||
self.history = SeriesHistory::load_history();
|
self.history = SeriesHistory::load_history();
|
||||||
|
|
||||||
series_settings.read().iter().for_each(|(id, series)| {
|
let start: DateTime<Utc> = Utc::now();
|
||||||
//let new_entries = series.check();
|
while Utc::now() - start <= Duration::minutes(60) {
|
||||||
});
|
self.run_start_time = Utc::now();
|
||||||
for series in series_settings.read().iter() {
|
self.ping_status().await;
|
||||||
//series.update(&mut self.history, &lemmy, &self.settings).await;
|
let read_copy = self.shared_config.read().expect("Read Lock Failed").clone();
|
||||||
Logging::debug("Done Updating Series");
|
for series in read_copy.series {
|
||||||
self.wait(1, Wait::Absolute);
|
series.update(&mut self.history, &lemmy, &self.shared_config).await;
|
||||||
|
debug!("Done Updating Series");
|
||||||
|
self.wait(1, Wait::Absolute).await;
|
||||||
|
}
|
||||||
|
debug!("Awaiting Timeout");
|
||||||
|
self.wait(30, Wait::Buffer).await;
|
||||||
|
debug!("Pinging Server");
|
||||||
|
self.ping_status().await;
|
||||||
|
debug!("Awaiting Timeout 2");
|
||||||
|
self.wait(30, Wait::Absolute).await;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
lemmy.logout().await;
|
||||||
}
|
}
|
||||||
self.health_signal.store(HealthSignal::ConfirmStop, Ordering::SeqCst);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn ping_status(&self) -> bool {
|
async fn ping_status(&self) {
|
||||||
if let Some(status_url) = &self.settings.status_post_url() {
|
let read_config = &self.shared_config.read().expect("Read Lock Failed").clone();
|
||||||
return match HTTP_CLIENT.get(status_url).send() {
|
if let Some(status_url) = &read_config.status_post_url {
|
||||||
Ok(_) => {
|
match HTTP_CLIENT.get(status_url).send().await {
|
||||||
true
|
Ok(_) => {},
|
||||||
},
|
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
let err_msg = format!("While pinging status URL: {e}");
|
let err_msg = format!("While pinging status URL: {e}");
|
||||||
Logging::error(err_msg.as_str());
|
error!(err_msg);
|
||||||
false
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
false
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn wait(&self, seconds: i64, start_time: Wait) {
|
async fn wait(&self, seconds: i64, start_time: Wait) {
|
||||||
let duration: Duration = Duration::seconds(seconds);
|
let duration: Duration = Duration::seconds(seconds);
|
||||||
let start_time: DateTime<Utc> = match start_time {
|
let start_time: DateTime<Utc> = match start_time {
|
||||||
Wait::Absolute => Utc::now(),
|
Wait::Absolute => Utc::now(),
|
||||||
Wait::Buffer(time) => time,
|
Wait::Buffer => self.run_start_time,
|
||||||
};
|
};
|
||||||
while Utc::now() - start_time < duration {
|
while Utc::now() - start_time < duration {
|
||||||
sleep(Duration::milliseconds(100).to_std().unwrap());
|
sleep(Duration::milliseconds(100).to_std().unwrap()).await
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn login(&mut self) {
|
|
||||||
todo!();
|
|
||||||
self.login_time = Some(Utc::now());
|
|
||||||
}
|
|
||||||
|
|
||||||
fn logout(&mut self) {
|
|
||||||
self.login_time = None;
|
|
||||||
todo!();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
124
src/config.rs
124
src/config.rs
|
@ -1,47 +1,88 @@
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
use std::sync::{Arc, RwLock};
|
use std::sync::{Arc, RwLock};
|
||||||
use chrono::{Timelike, Utc};
|
use chrono::{Timelike, Utc};
|
||||||
use crate::settings::PostBody::Description;
|
use crate::config::PostBody::Description;
|
||||||
use lemmy_db_schema::PostFeatureType;
|
use lemmy_db_schema::PostFeatureType;
|
||||||
use lemmy_db_schema::sensitive::SensitiveString;
|
use lemmy_db_schema::sensitive::SensitiveString;
|
||||||
use serde_derive::{Deserialize, Serialize};
|
use serde_derive::{Deserialize, Serialize};
|
||||||
use crate::lemmy::{Lemmy, PartInfo, PostType};
|
use crate::lemmy::{Lemmy, PartInfo, PostType};
|
||||||
use crate::post_history::{SeriesHistory};
|
use crate::post_history::{SeriesHistory};
|
||||||
|
use systemd_journal_logger::connected_to_journal;
|
||||||
use crate::fetchers::{FetcherTrait, Fetcher};
|
use crate::fetchers::{FetcherTrait, Fetcher};
|
||||||
use crate::fetchers::jnovel::{JNovelFetcher};
|
use crate::fetchers::jnovel::{JNovelFetcher};
|
||||||
use crate::logging::Logging;
|
|
||||||
use crate::settings::PostBody;
|
macro_rules! debug {
|
||||||
|
($msg:tt) => {
|
||||||
|
match connected_to_journal() {
|
||||||
|
true => log::debug!("[DEBUG] {}", $msg),
|
||||||
|
false => println!("[DEBUG] {}", $msg),
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
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),
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Clone, Debug)]
|
#[derive(Serialize, Deserialize, Clone, Debug)]
|
||||||
pub(crate) struct SingleBotConfig {
|
pub(crate) struct Config {
|
||||||
pub(crate) instance: String,
|
pub(crate) instance: String,
|
||||||
username: SensitiveString,
|
username: SensitiveString,
|
||||||
password: SensitiveString,
|
password: SensitiveString,
|
||||||
pub(crate) status_post_url: Option<String>,
|
pub(crate) status_post_url: Option<String>,
|
||||||
|
pub(crate) config_reload_seconds: u32,
|
||||||
pub(crate) protected_communities: Vec<String>,
|
pub(crate) protected_communities: Vec<String>,
|
||||||
pub(crate) series: Vec<SeriesConfig>,
|
pub(crate) series: Vec<SeriesConfig>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl SingleBotConfig {
|
impl Config {
|
||||||
pub(crate) fn load(&self) -> Self {
|
pub(crate) fn load() -> Self {
|
||||||
if self.instance.is_empty() {
|
let cfg: Self = match confy::load(env!("CARGO_PKG_NAME"), "config") {
|
||||||
|
Ok(data) => data,
|
||||||
|
Err(e) => panic!("config.toml not found: {e}"),
|
||||||
|
};
|
||||||
|
|
||||||
|
if cfg.instance.is_empty() {
|
||||||
panic!("bot instance not set!")
|
panic!("bot instance not set!")
|
||||||
}
|
}
|
||||||
|
|
||||||
if self.username.is_empty() {
|
if cfg.username.is_empty() {
|
||||||
panic!("bot username not set!")
|
panic!("bot username not set!")
|
||||||
}
|
}
|
||||||
|
|
||||||
if self.password.is_empty() {
|
if cfg.password.is_empty() {
|
||||||
panic!("bot password not provided!")
|
panic!("bot password not provided!")
|
||||||
}
|
}
|
||||||
|
|
||||||
self.series.iter().for_each(|series| {
|
cfg.series.iter().for_each(|series| {
|
||||||
if series.prepub_community.post_body == Description {
|
if series.prepub_community.post_body == Description {
|
||||||
panic!("'Description' type Post Body only supported for Volumes!")
|
panic!("'Description' type Post Body only supported for Volumes!")
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
self.clone()
|
cfg
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn get_path() -> PathBuf {
|
pub(crate) fn get_path() -> PathBuf {
|
||||||
|
@ -57,13 +98,14 @@ impl SingleBotConfig {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for SingleBotConfig {
|
impl Default for Config {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
SingleBotConfig {
|
Config {
|
||||||
instance: "".to_owned(),
|
instance: "".to_owned(),
|
||||||
username: SensitiveString::from("".to_owned()),
|
username: SensitiveString::from("".to_owned()),
|
||||||
password: SensitiveString::from("".to_owned()),
|
password: SensitiveString::from("".to_owned()),
|
||||||
status_post_url: None,
|
status_post_url: None,
|
||||||
|
config_reload_seconds: 21600,
|
||||||
protected_communities: vec![],
|
protected_communities: vec![],
|
||||||
series: vec![],
|
series: vec![],
|
||||||
}
|
}
|
||||||
|
@ -80,9 +122,9 @@ pub(crate) struct SeriesConfig {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl SeriesConfig {
|
impl SeriesConfig {
|
||||||
pub(crate) fn update(&self, history: &mut SeriesHistory, lemmy: &Lemmy, config: &Arc<RwLock<SingleBotConfig>>) {
|
pub(crate) async fn update(&self, history: &mut SeriesHistory, lemmy: &Lemmy, config: &Arc<RwLock<Config>>) {
|
||||||
let info_msg = format!("Checking {} for Updates", self.slug);
|
let info_msg = format!("Checking {} for Updates", self.slug);
|
||||||
Logging::info(info_msg.as_str());
|
info!(info_msg);
|
||||||
|
|
||||||
let mut fetcher: Fetcher = match &self.fetcher {
|
let mut fetcher: Fetcher = match &self.fetcher {
|
||||||
Fetcher::Jnc(_) => {
|
Fetcher::Jnc(_) => {
|
||||||
|
@ -102,18 +144,18 @@ impl SeriesConfig {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let post_list = match fetcher.check_feed() {
|
let post_list = match fetcher.check_feed().await {
|
||||||
Ok(data) => data,
|
Ok(data) => data,
|
||||||
Err(_) => {
|
Err(_) => {
|
||||||
let err_msg = format!("While checking feed for {}", self.slug);
|
let err_msg = format!("While checking feed for {}", self.slug);
|
||||||
Logging::error(err_msg.as_str());
|
error!(err_msg);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
if post_list.is_empty() && Utc::now().minute() % 10 == 0 {
|
if post_list.is_empty() && Utc::now().minute() % 10 == 0 {
|
||||||
let info_msg = "No Updates found";
|
let info_msg = "No Updates found";
|
||||||
Logging::info(info_msg);
|
info!(info_msg);
|
||||||
}
|
}
|
||||||
|
|
||||||
for post_info in post_list.iter() {
|
for post_info in post_list.iter() {
|
||||||
|
@ -132,12 +174,12 @@ impl SeriesConfig {
|
||||||
post_info.get_info().title.as_str(),
|
post_info.get_info().title.as_str(),
|
||||||
post_info.get_post_config(self).name.as_str()
|
post_info.get_post_config(self).name.as_str()
|
||||||
);
|
);
|
||||||
Logging::info(info.as_str());
|
info!(info);
|
||||||
|
|
||||||
let post_id = match lemmy.post(post_data) {
|
let post_id = match lemmy.post(post_data).await {
|
||||||
Some(data) => data,
|
Some(data) => data,
|
||||||
None=> {
|
None=> {
|
||||||
Logging::error("Error posting chapter");
|
error!("Error posting chapter");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -154,19 +196,19 @@ impl SeriesConfig {
|
||||||
post_info.get_info().title,
|
post_info.get_info().title,
|
||||||
post_info.get_post_config(self).name.as_str()
|
post_info.get_post_config(self).name.as_str()
|
||||||
);
|
);
|
||||||
Logging::info(info.as_str());
|
info!(info);
|
||||||
let pinned_posts = lemmy.get_community_pinned(lemmy.get_community_id(&post_info.get_post_config(self).name)).unwrap_or_else(|| {
|
let pinned_posts = lemmy.get_community_pinned(lemmy.get_community_id(&post_info.get_post_config(self).name)).await.unwrap_or_else(|| {
|
||||||
Logging::error("Pinning of Post to community failed");
|
error!("Pinning of Post to community failed");
|
||||||
vec![]
|
vec![]
|
||||||
});
|
});
|
||||||
if !pinned_posts.is_empty() {
|
if !pinned_posts.is_empty() {
|
||||||
let community_pinned_post = &pinned_posts[0];
|
let community_pinned_post = &pinned_posts[0];
|
||||||
if lemmy.unpin(community_pinned_post.post.id, PostFeatureType::Community).is_none() {
|
if lemmy.unpin(community_pinned_post.post.id, PostFeatureType::Community).await.is_none() {
|
||||||
Logging::error("Error un-pinning post");
|
error!("Error un-pinning post");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if lemmy.pin(post_id, PostFeatureType::Community).is_none() {
|
if lemmy.pin(post_id, PostFeatureType::Community).await.is_none() {
|
||||||
Logging::error("Error pinning post");
|
error!("Error pinning post");
|
||||||
}
|
}
|
||||||
} else if read_config
|
} else if read_config
|
||||||
.protected_communities
|
.protected_communities
|
||||||
|
@ -176,16 +218,16 @@ impl SeriesConfig {
|
||||||
"Community '{}' for Series '{}' is protected. Is this intended?",
|
"Community '{}' for Series '{}' is protected. Is this intended?",
|
||||||
&post_info.get_post_config(self).name, self.slug
|
&post_info.get_post_config(self).name, self.slug
|
||||||
);
|
);
|
||||||
Logging::warn(message.as_str());
|
warn!(message);
|
||||||
}
|
}
|
||||||
|
|
||||||
if post_info.get_post_config(self).pin_settings.pin_new_post_local {
|
if post_info.get_post_config(self).pin_settings.pin_new_post_local {
|
||||||
let info = format!("Pinning '{}' to Instance", post_info.get_info().title);
|
let info = format!("Pinning '{}' to Instance", post_info.get_info().title);
|
||||||
Logging::info(info.as_str());
|
info!(info);
|
||||||
let pinned_posts = match lemmy.get_local_pinned() {
|
let pinned_posts = match lemmy.get_local_pinned().await {
|
||||||
Some(data) => {data}
|
Some(data) => {data}
|
||||||
None => {
|
None => {
|
||||||
Logging::error("Error fetching pinned posts");
|
error!("Error fetching pinned posts");
|
||||||
vec![]
|
vec![]
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -199,16 +241,16 @@ impl SeriesConfig {
|
||||||
continue;
|
continue;
|
||||||
} else {
|
} else {
|
||||||
let community_pinned_post = &pinned_post;
|
let community_pinned_post = &pinned_post;
|
||||||
if lemmy.unpin(community_pinned_post.post.id, PostFeatureType::Local).is_none() {
|
if lemmy.unpin(community_pinned_post.post.id, PostFeatureType::Local).await.is_none() {
|
||||||
Logging::error("Error pinning post");
|
error!("Error pinning post");
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if lemmy.pin(post_id, PostFeatureType::Local).is_none() {
|
if lemmy.pin(post_id, PostFeatureType::Local).await.is_none() {
|
||||||
Logging::error("Error pinning post");
|
error!("Error pinning post");
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -228,7 +270,7 @@ impl SeriesConfig {
|
||||||
series_history.set_part(post_info.get_part_info().unwrap_or(PartInfo::NoParts).as_string().as_str(), part_history);
|
series_history.set_part(post_info.get_part_info().unwrap_or(PartInfo::NoParts).as_string().as_str(), part_history);
|
||||||
history
|
history
|
||||||
.set_series(self.slug.as_str(), series_history);
|
.set_series(self.slug.as_str(), series_history);
|
||||||
Logging::debug("Saving History");
|
debug!("Saving History");
|
||||||
history.save_history();
|
history.save_history();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -246,3 +288,11 @@ pub(crate) struct PinConfig {
|
||||||
pub(crate) pin_new_post_local: bool,
|
pub(crate) pin_new_post_local: bool,
|
||||||
pub(crate) pin_new_post_community: bool,
|
pub(crate) pin_new_post_community: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
|
||||||
|
#[serde(tag = "body_type", content = "body_content")]
|
||||||
|
pub(crate) enum PostBody {
|
||||||
|
None,
|
||||||
|
Description,
|
||||||
|
Custom(String),
|
||||||
|
}
|
||||||
|
|
|
@ -3,16 +3,35 @@ use chrono::{DateTime, Duration, Utc};
|
||||||
use serde_derive::{Deserialize, Serialize};
|
use serde_derive::{Deserialize, Serialize};
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::ops::Sub;
|
use std::ops::Sub;
|
||||||
|
use async_trait::async_trait;
|
||||||
use crate::fetchers::{FetcherTrait};
|
use crate::fetchers::{FetcherTrait};
|
||||||
use crate::lemmy::{PartInfo, PostInfo, PostInfoInner, PostType};
|
use crate::lemmy::{PartInfo, PostInfo, PostInfoInner, PostType};
|
||||||
|
use systemd_journal_logger::connected_to_journal;
|
||||||
use crate::lemmy::PartInfo::{NoParts, Part};
|
use crate::lemmy::PartInfo::{NoParts, Part};
|
||||||
use crate::logging::Logging;
|
|
||||||
|
macro_rules! error {
|
||||||
|
($msg:tt) => {
|
||||||
|
match connected_to_journal() {
|
||||||
|
true => log::error!("[ERROR] {}", $msg),
|
||||||
|
false => eprintln!("[ERROR] {}", $msg),
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
macro_rules! info {
|
||||||
|
($msg:tt) => {
|
||||||
|
match connected_to_journal() {
|
||||||
|
true => log::info!("[INFO] {}", $msg),
|
||||||
|
false => println!("[INFO] {}", $msg),
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
static PAST_DAYS_ELIGIBLE: u8 = 4;
|
static PAST_DAYS_ELIGIBLE: u8 = 4;
|
||||||
|
|
||||||
macro_rules! api_url {
|
macro_rules! api_url {
|
||||||
() => {
|
() => {
|
||||||
"https://labs.j-novel.club/app/v1".to_owned()
|
"https://labs.j-novel.club/app/v2".to_owned()
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -94,6 +113,7 @@ impl JNovelFetcher {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[async_trait]
|
||||||
impl FetcherTrait for JNovelFetcher {
|
impl FetcherTrait for JNovelFetcher {
|
||||||
fn new() -> Self {
|
fn new() -> Self {
|
||||||
JNovelFetcher {
|
JNovelFetcher {
|
||||||
|
@ -102,22 +122,23 @@ impl FetcherTrait for JNovelFetcher {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn check_feed(&self) -> Result<Vec<PostInfo>, ()> {
|
async fn check_feed(&self) -> Result<Vec<PostInfo>, ()> {
|
||||||
let response = match HTTP_CLIENT
|
let response = match HTTP_CLIENT
|
||||||
.get(api_url!() + "/series/" + self.series_slug.as_str() + "/volumes?format=json")
|
.get(api_url!() + "/series/" + self.series_slug.as_str() + "/volumes?format=json")
|
||||||
.send()
|
.send()
|
||||||
|
.await
|
||||||
{
|
{
|
||||||
Ok(data) => match data.text() {
|
Ok(data) => match data.text().await {
|
||||||
Ok(data) => data,
|
Ok(data) => data,
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
let err_msg = format!("While checking feed: {e}");
|
let err_msg = format!("While checking feed: {e}");
|
||||||
Logging::error(err_msg.as_str());
|
error!(err_msg);
|
||||||
return Err(());
|
return Err(());
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
let err_msg = format!("{e}");
|
let err_msg = format!("{e}");
|
||||||
Logging::error(err_msg.as_str());
|
error!(err_msg);
|
||||||
return Err(());
|
return Err(());
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -126,7 +147,7 @@ impl FetcherTrait for JNovelFetcher {
|
||||||
Ok(data) => data,
|
Ok(data) => data,
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
let err_msg = format!("{e}");
|
let err_msg = format!("{e}");
|
||||||
Logging::error(err_msg.as_str());
|
error!(err_msg);
|
||||||
return Err(());
|
return Err(());
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -163,7 +184,7 @@ impl FetcherTrait for JNovelFetcher {
|
||||||
match part_number {
|
match part_number {
|
||||||
Some(number) => new_part_info = Part(number),
|
Some(number) => new_part_info = Part(number),
|
||||||
None => {
|
None => {
|
||||||
Logging::info("No Part found, assuming 1");
|
info!("No Part found, assuming 1");
|
||||||
new_part_info = Part(1);
|
new_part_info = Part(1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -203,7 +224,7 @@ impl FetcherTrait for JNovelFetcher {
|
||||||
.or_insert(new_post_info);
|
.or_insert(new_post_info);
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(prepub_info) = get_latest_prepub(&volume.slug) {
|
if let Some(prepub_info) = get_latest_prepub(&volume.slug).await {
|
||||||
let prepub_post_info = PostInfo {
|
let prepub_post_info = PostInfo {
|
||||||
post_type: Some(PostType::Chapter),
|
post_type: Some(PostType::Chapter),
|
||||||
part: Some(new_part_info),
|
part: Some(new_part_info),
|
||||||
|
@ -231,22 +252,23 @@ impl FetcherTrait for JNovelFetcher {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
fn get_latest_prepub(volume_slug: &str) -> Option<PostInfoInner> {
|
async fn get_latest_prepub(volume_slug: &str) -> Option<PostInfoInner> {
|
||||||
let response = match HTTP_CLIENT
|
let response = match HTTP_CLIENT
|
||||||
.get(api_url!() + "/volumes/" + volume_slug + "/parts?format=json")
|
.get(api_url!() + "/volumes/" + volume_slug + "/parts?format=json")
|
||||||
.send()
|
.send()
|
||||||
|
.await
|
||||||
{
|
{
|
||||||
Ok(data) => match data.text() {
|
Ok(data) => match data.text().await {
|
||||||
Ok(data) => data,
|
Ok(data) => data,
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
let err_msg = format!("While getting latest PrePub: {e}");
|
let err_msg = format!("While getting latest PrePub: {e}");
|
||||||
Logging::error(err_msg.as_str());
|
error!(err_msg);
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
let err_msg = format!("{e}");
|
let err_msg = format!("{e}");
|
||||||
Logging::error(err_msg.as_str());
|
error!(err_msg);
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -255,7 +277,7 @@ fn get_latest_prepub(volume_slug: &str) -> Option<PostInfoInner> {
|
||||||
Ok(data) => data,
|
Ok(data) => data,
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
let err_msg = format!("{e}");
|
let err_msg = format!("{e}");
|
||||||
Logging::error(err_msg.as_str());
|
error!(err_msg);
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
use async_trait::async_trait;
|
||||||
use serde_derive::{Deserialize, Serialize};
|
use serde_derive::{Deserialize, Serialize};
|
||||||
use strum_macros::Display;
|
use strum_macros::Display;
|
||||||
use crate::fetchers::Fetcher::Jnc;
|
use crate::fetchers::Fetcher::Jnc;
|
||||||
|
@ -6,15 +7,16 @@ use crate::lemmy::{PostInfo};
|
||||||
|
|
||||||
pub mod jnovel;
|
pub mod jnovel;
|
||||||
|
|
||||||
|
#[async_trait]
|
||||||
pub(crate) trait FetcherTrait {
|
pub(crate) trait FetcherTrait {
|
||||||
fn new() -> Self where Self: Sized;
|
fn new() -> Self where Self: Sized;
|
||||||
fn check_feed(&self) -> Result<Vec<PostInfo>, ()>;
|
async fn check_feed(&self) -> Result<Vec<PostInfo>, ()>;
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Fetcher {
|
impl Fetcher {
|
||||||
pub(crate) fn check_feed(&self) -> Result<Vec<PostInfo>, ()> {
|
pub(crate) async fn check_feed(&self) -> Result<Vec<PostInfo>, ()> {
|
||||||
match self {
|
match self {
|
||||||
Jnc(fetcher) => fetcher.check_feed(),
|
Jnc(fetcher) => fetcher.check_feed().await,
|
||||||
/*default => {
|
/*default => {
|
||||||
let err_msg = format!("Fetcher {default} is not implemented");
|
let err_msg = format!("Fetcher {default} is not implemented");
|
||||||
error!(err_msg);
|
error!(err_msg);
|
||||||
|
|
131
src/lemmy.rs
131
src/lemmy.rs
|
@ -1,5 +1,5 @@
|
||||||
use std::cmp::Ordering;
|
use std::cmp::Ordering;
|
||||||
use crate::config::{PostConfig, SeriesConfig};
|
use crate::config::{Config, PostBody, PostConfig, SeriesConfig};
|
||||||
use crate::{HTTP_CLIENT};
|
use crate::{HTTP_CLIENT};
|
||||||
use lemmy_api_common::community::{ListCommunities, ListCommunitiesResponse};
|
use lemmy_api_common::community::{ListCommunities, ListCommunitiesResponse};
|
||||||
use lemmy_api_common::lemmy_db_views::structs::PostView;
|
use lemmy_api_common::lemmy_db_views::structs::PostView;
|
||||||
|
@ -9,10 +9,28 @@ use lemmy_db_schema::newtypes::{CommunityId, LanguageId, PostId};
|
||||||
use lemmy_db_schema::{ListingType, PostFeatureType};
|
use lemmy_db_schema::{ListingType, PostFeatureType};
|
||||||
use reqwest::StatusCode;
|
use reqwest::StatusCode;
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
use std::sync::{RwLock};
|
||||||
use lemmy_db_schema::sensitive::SensitiveString;
|
use lemmy_db_schema::sensitive::SensitiveString;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use crate::logging::Logging;
|
use systemd_journal_logger::connected_to_journal;
|
||||||
use crate::settings::{BotSettings, PostBody};
|
|
||||||
|
macro_rules! debug {
|
||||||
|
($msg:tt) => {
|
||||||
|
match connected_to_journal() {
|
||||||
|
true => log::debug!("[DEBUG] {}", $msg),
|
||||||
|
false => println!("[DEBUG] {}", $msg),
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
macro_rules! error {
|
||||||
|
($msg:tt) => {
|
||||||
|
match connected_to_journal() {
|
||||||
|
true => log::error!("[ERROR] {}", $msg),
|
||||||
|
false => eprintln!("[ERROR] {}", $msg),
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
pub(crate) struct Lemmy {
|
pub(crate) struct Lemmy {
|
||||||
jwt_token: SensitiveString,
|
jwt_token: SensitiveString,
|
||||||
|
@ -89,7 +107,7 @@ impl PartialOrd for PartInfo {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
#[derive(Debug, Clone, Copy)]
|
||||||
pub(crate) enum PostType {
|
pub(crate) enum PostType {
|
||||||
Chapter,
|
Chapter,
|
||||||
Volume
|
Volume
|
||||||
|
@ -189,24 +207,26 @@ impl PartialOrd for PostInfo {
|
||||||
|
|
||||||
impl Lemmy {
|
impl Lemmy {
|
||||||
pub(crate) fn get_community_id(&self, name: &str) -> CommunityId {
|
pub(crate) fn get_community_id(&self, name: &str) -> CommunityId {
|
||||||
*self.communities.get(name).expect(format!("Community '{name}' is invalid").as_str())
|
*self.communities.get(name).expect("Given community is invalid")
|
||||||
}
|
}
|
||||||
pub(crate) fn new(config: &BotSettings) -> Result<Self, ()> {
|
pub(crate) async fn new(config: &RwLock<Config>) -> Result<Self, ()> {
|
||||||
|
let read_config = config.read().expect("Read Lock Failed").clone();
|
||||||
let login_params = Login {
|
let login_params = Login {
|
||||||
username_or_email: config.username(),
|
username_or_email: read_config.get_username(),
|
||||||
password: config.password(),
|
password: read_config.get_password(),
|
||||||
totp_2fa_token: None,
|
totp_2fa_token: None,
|
||||||
};
|
};
|
||||||
|
|
||||||
let response = match HTTP_CLIENT
|
let response = match HTTP_CLIENT
|
||||||
.post(config.instance().to_owned() + "/api/v3/user/login")
|
.post(read_config.instance.to_owned() + "/api/v3/user/login")
|
||||||
.json(&login_params)
|
.json(&login_params)
|
||||||
.send()
|
.send()
|
||||||
|
.await
|
||||||
{
|
{
|
||||||
Ok(data) => data,
|
Ok(data) => data,
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
let err_msg = format!("{e}");
|
let err_msg = format!("{e}");
|
||||||
Logging::error(err_msg.as_str());
|
error!(err_msg);
|
||||||
return Err(());
|
return Err(());
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -215,41 +235,40 @@ impl Lemmy {
|
||||||
StatusCode::OK => {
|
StatusCode::OK => {
|
||||||
let data: LoginResponse = response
|
let data: LoginResponse = response
|
||||||
.json()
|
.json()
|
||||||
|
.await
|
||||||
.expect("Successful Login Request should return JSON");
|
.expect("Successful Login Request should return JSON");
|
||||||
match data.jwt {
|
match data.jwt {
|
||||||
Some(token) => Ok(Lemmy {
|
Some(token) => Ok(Lemmy {
|
||||||
jwt_token: token.clone(),
|
jwt_token: token.clone(),
|
||||||
instance: config.instance().to_owned(),
|
instance: read_config.instance.to_owned(),
|
||||||
communities: HashMap::new(),
|
communities: HashMap::new(),
|
||||||
}),
|
}),
|
||||||
None => {
|
None => {
|
||||||
Logging::error("Login did not return JWT token. Are the credentials valid?");
|
let err_msg = "Login did not return JWT token. Are the credentials valid?".to_owned();
|
||||||
|
error!(err_msg);
|
||||||
Err(())
|
Err(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
status => {
|
status => {
|
||||||
let err_msg = format!("Unexpected HTTP Status '{}' during Login", status);
|
let err_msg = format!("Unexpected HTTP Status '{}' during Login", status);
|
||||||
Logging::error(err_msg.as_str());
|
error!(err_msg);
|
||||||
Err(())
|
Err(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn logout(&self) {
|
pub(crate) async fn logout(&self) {
|
||||||
let _ = self.post_data_json("/api/v3/user/logout", &"");
|
let _ = self.post_data_json("/api/v3/user/logout", &"").await;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn login(&self) {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn post(&self, post: CreatePost) -> Option<PostId> {
|
pub(crate) async fn post(&self, post: CreatePost) -> Option<PostId> {
|
||||||
let response: String = match self.post_data_json("/api/v3/post", &post) {
|
let response: String = match self.post_data_json("/api/v3/post", &post).await {
|
||||||
Some(data) => data,
|
Some(data) => data,
|
||||||
None => return None,
|
None => return None,
|
||||||
};
|
};
|
||||||
let json_data: PostView = match self.parse_json_map(&response) {
|
let json_data: PostView = match self.parse_json_map(&response).await {
|
||||||
Some(data) => data,
|
Some(data) => data,
|
||||||
None => return None,
|
None => return None,
|
||||||
};
|
};
|
||||||
|
@ -257,12 +276,12 @@ impl Lemmy {
|
||||||
Some(json_data.post.id)
|
Some(json_data.post.id)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn feature(&self, params: FeaturePost) -> Option<PostView> {
|
async fn feature(&self, params: FeaturePost) -> Option<PostView> {
|
||||||
let response: String = match self.post_data_json("/api/v3/post/feature", ¶ms) {
|
let response: String = match self.post_data_json("/api/v3/post/feature", ¶ms).await {
|
||||||
Some(data) => data,
|
Some(data) => data,
|
||||||
None => return None,
|
None => return None,
|
||||||
};
|
};
|
||||||
let json_data: PostView = match self.parse_json_map(&response) {
|
let json_data: PostView = match self.parse_json_map(&response).await {
|
||||||
Some(data) => data,
|
Some(data) => data,
|
||||||
None => return None,
|
None => return None,
|
||||||
};
|
};
|
||||||
|
@ -270,36 +289,36 @@ impl Lemmy {
|
||||||
Some(json_data)
|
Some(json_data)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn unpin(&self, post_id: PostId, location: PostFeatureType) -> Option<PostView> {
|
pub(crate) async fn unpin(&self, post_id: PostId, location: PostFeatureType) -> Option<PostView> {
|
||||||
let pin_params = FeaturePost {
|
let pin_params = FeaturePost {
|
||||||
post_id,
|
post_id,
|
||||||
featured: false,
|
featured: false,
|
||||||
feature_type: location,
|
feature_type: location,
|
||||||
};
|
};
|
||||||
self.feature(pin_params)
|
self.feature(pin_params).await
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn pin(&self, post_id: PostId, location: PostFeatureType) -> Option<PostView> {
|
pub(crate) async fn pin(&self, post_id: PostId, location: PostFeatureType) -> Option<PostView> {
|
||||||
let pin_params = FeaturePost {
|
let pin_params = FeaturePost {
|
||||||
post_id,
|
post_id,
|
||||||
featured: true,
|
featured: true,
|
||||||
feature_type: location,
|
feature_type: location,
|
||||||
};
|
};
|
||||||
self.feature(pin_params)
|
self.feature(pin_params).await
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn get_community_pinned(&self, community: CommunityId) -> Option<Vec<PostView>> {
|
pub(crate) async fn get_community_pinned(&self, community: CommunityId) -> Option<Vec<PostView>> {
|
||||||
let list_params = GetPosts {
|
let list_params = GetPosts {
|
||||||
community_id: Some(community),
|
community_id: Some(community),
|
||||||
type_: Some(ListingType::Local),
|
type_: Some(ListingType::Local),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
};
|
};
|
||||||
|
|
||||||
let response: String = match self.get_data_query("/api/v3/post/list", &list_params) {
|
let response: String = match self.get_data_query("/api/v3/post/list", &list_params).await {
|
||||||
Some(data) => data,
|
Some(data) => data,
|
||||||
None => return None,
|
None => return None,
|
||||||
};
|
};
|
||||||
let json_data: GetPostsResponse = match self.parse_json(&response) {
|
let json_data: GetPostsResponse = match self.parse_json(&response).await {
|
||||||
Some(data) => data,
|
Some(data) => data,
|
||||||
None => return None,
|
None => return None,
|
||||||
};
|
};
|
||||||
|
@ -312,17 +331,17 @@ impl Lemmy {
|
||||||
.collect())
|
.collect())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn get_local_pinned(&self) -> Option<Vec<PostView>> {
|
pub(crate) async fn get_local_pinned(&self) -> Option<Vec<PostView>> {
|
||||||
let list_params = GetPosts {
|
let list_params = GetPosts {
|
||||||
type_: Some(ListingType::Local),
|
type_: Some(ListingType::Local),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
};
|
};
|
||||||
|
|
||||||
let response: String = match self.get_data_query("/api/v3/post/list", &list_params) {
|
let response: String = match self.get_data_query("/api/v3/post/list", &list_params).await {
|
||||||
Some(data) => data,
|
Some(data) => data,
|
||||||
None => return None,
|
None => return None,
|
||||||
};
|
};
|
||||||
let json_data: GetPostsResponse = match self.parse_json(&response) {
|
let json_data: GetPostsResponse = match self.parse_json(&response).await {
|
||||||
Some(data) => data,
|
Some(data) => data,
|
||||||
None => return None,
|
None => return None,
|
||||||
};
|
};
|
||||||
|
@ -335,17 +354,17 @@ impl Lemmy {
|
||||||
.collect())
|
.collect())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn get_communities(&mut self) {
|
pub(crate) async fn get_communities(&mut self) {
|
||||||
let list_params = ListCommunities {
|
let list_params = ListCommunities {
|
||||||
type_: Some(ListingType::Local),
|
type_: Some(ListingType::Local),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
};
|
};
|
||||||
|
|
||||||
let response: String = match self.get_data_query("/api/v3/community/list", &list_params) {
|
let response: String = match self.get_data_query("/api/v3/community/list", &list_params).await {
|
||||||
Some(data) => data,
|
Some(data) => data,
|
||||||
None => return,
|
None => return,
|
||||||
};
|
};
|
||||||
let json_data: ListCommunitiesResponse = match self.parse_json::<ListCommunitiesResponse>(&response) {
|
let json_data: ListCommunitiesResponse = match self.parse_json::<ListCommunitiesResponse>(&response).await {
|
||||||
Some(data) => data,
|
Some(data) => data,
|
||||||
None => return,
|
None => return,
|
||||||
};
|
};
|
||||||
|
@ -359,71 +378,71 @@ impl Lemmy {
|
||||||
self.communities = communities;
|
self.communities = communities;
|
||||||
}
|
}
|
||||||
|
|
||||||
fn post_data_json<T: Serialize>(&self, route: &str, json: &T ) -> Option<String> {
|
async fn post_data_json<T: Serialize>(&self, route: &str, json: &T ) -> Option<String> {
|
||||||
let res = HTTP_CLIENT
|
let res = HTTP_CLIENT
|
||||||
.post(format!("{}{route}", &self.instance))
|
.post(format!("{}{route}", &self.instance))
|
||||||
.bearer_auth(&self.jwt_token.to_string())
|
.bearer_auth(&self.jwt_token.to_string())
|
||||||
.json(&json)
|
.json(&json)
|
||||||
.send();
|
.send()
|
||||||
self.extract_data(res)
|
.await;
|
||||||
|
self.extract_data(res).await
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_data_query<T: Serialize>(&self, route: &str, param: &T ) -> Option<String> {
|
async fn get_data_query<T: Serialize>(&self, route: &str, param: &T ) -> Option<String> {
|
||||||
let res = HTTP_CLIENT
|
let res = HTTP_CLIENT
|
||||||
.get(format!("{}{route}", &self.instance))
|
.get(format!("{}{route}", &self.instance))
|
||||||
.bearer_auth(&self.jwt_token.to_string())
|
.bearer_auth(&self.jwt_token.to_string())
|
||||||
.query(¶m)
|
.query(¶m)
|
||||||
.send();
|
.send()
|
||||||
self.extract_data(res)
|
.await;
|
||||||
|
self.extract_data(res).await
|
||||||
}
|
}
|
||||||
|
|
||||||
fn extract_data(&self, response: Result<reqwest::blocking::Response, reqwest::Error>) -> Option<String> {
|
async fn extract_data(&self, response: Result<reqwest::Response, reqwest::Error>) -> Option<String> {
|
||||||
match response {
|
match response {
|
||||||
Ok(data) => {
|
Ok(data) => {
|
||||||
let msg = format!("Status Code: '{}'", data.status().clone());
|
|
||||||
Logging::debug(msg.as_str());
|
|
||||||
if data.status().is_success() {
|
if data.status().is_success() {
|
||||||
match data.text() {
|
match data.text().await {
|
||||||
Ok(data) => Some(data),
|
Ok(data) => Some(data),
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
let err_msg = format!("{e}");
|
let err_msg = format!("{e}");
|
||||||
Logging::error(err_msg.as_str());
|
error!(err_msg);
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
let err_msg = format!("HTTP Request failed: {}", data.text().unwrap());
|
let err_msg = format!("HTTP Request failed: {}", data.text().await.unwrap());
|
||||||
Logging::error(err_msg.as_str());
|
error!(err_msg);
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
let err_msg = format!("{e}");
|
let err_msg = format!("{e}");
|
||||||
Logging::error(err_msg.as_str());
|
error!(err_msg);
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn parse_json<'a, T: Deserialize<'a>>(&self, response: &'a str) -> Option<T> {
|
async fn parse_json<'a, T: Deserialize<'a>>(&self, response: &'a str) -> Option<T> {
|
||||||
match serde_json::from_str::<T>(response) {
|
match serde_json::from_str::<T>(response) {
|
||||||
Ok(data) => Some(data),
|
Ok(data) => Some(data),
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
let err_msg = format!("while parsing JSON: {e} ");
|
let err_msg = format!("while parsing JSON: {e} ");
|
||||||
Logging::error(err_msg.as_str());
|
error!(err_msg);
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn parse_json_map<'a, T: Deserialize<'a>>(&self, response: &'a str) -> Option<T> {
|
async fn parse_json_map<'a, T: Deserialize<'a>>(&self, response: &'a str) -> Option<T> {
|
||||||
Logging::debug(response);
|
debug!(response);
|
||||||
match serde_json::from_str::<HashMap<&str, T>>(response) {
|
match serde_json::from_str::<HashMap<&str, T>>(response) {
|
||||||
Ok(mut data) => Some(data.remove("post_view").expect("Element should be present")),
|
Ok(mut data) => Some(data.remove("post_view").expect("Element should be present")),
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
let err_msg = format!("while parsing JSON HashMap: {e}");
|
let err_msg = format!("while parsing JSON HashMap: {e}");
|
||||||
Logging::error(err_msg.as_str());
|
error!(err_msg);
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
110
src/logging.rs
110
src/logging.rs
|
@ -1,110 +0,0 @@
|
||||||
use std::collections::VecDeque;
|
|
||||||
use chrono::{DateTime, Utc};
|
|
||||||
use log::Level;
|
|
||||||
use parking_lot::Mutex;
|
|
||||||
use systemd_journal_logger::connected_to_journal;
|
|
||||||
|
|
||||||
fn mem_log(level: Level, msg: Option<String>) -> VecDeque<LogEvent> {
|
|
||||||
static MEM_LOG_DEBUG: Mutex<VecDeque<LogEvent>> = Mutex::new(VecDeque::new());
|
|
||||||
static MEM_LOG_INFO: Mutex<VecDeque<LogEvent>> = Mutex::new(VecDeque::new());
|
|
||||||
static MEM_LOG_WARN: Mutex<VecDeque<LogEvent>> = Mutex::new(VecDeque::new());
|
|
||||||
static MEM_LOG_ERROR: Mutex<VecDeque<LogEvent>> = Mutex::new(VecDeque::new());
|
|
||||||
|
|
||||||
let max_len: i8;
|
|
||||||
|
|
||||||
let mut list = match level {
|
|
||||||
Level::Debug => {
|
|
||||||
max_len = 10;
|
|
||||||
MEM_LOG_DEBUG.lock()
|
|
||||||
},
|
|
||||||
Level::Info => {
|
|
||||||
max_len = 20;
|
|
||||||
MEM_LOG_INFO.lock()
|
|
||||||
},
|
|
||||||
Level::Warn => {
|
|
||||||
max_len = 40;
|
|
||||||
MEM_LOG_WARN.lock()
|
|
||||||
},
|
|
||||||
Level::Error => {
|
|
||||||
max_len = -1;
|
|
||||||
MEM_LOG_ERROR.lock()
|
|
||||||
},
|
|
||||||
Level::Trace => {
|
|
||||||
max_len = 10;
|
|
||||||
MEM_LOG_DEBUG.lock()
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
if let Some(msg) = msg {
|
|
||||||
let now = Utc::now();
|
|
||||||
let log_event = LogEvent::new(now, msg);
|
|
||||||
list.push_back(log_event);
|
|
||||||
if max_len != -1 {
|
|
||||||
while list.len() > max_len as usize {
|
|
||||||
list.pop_front();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
list.clone()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct Logging {
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Logging {
|
|
||||||
pub fn debug(msg: &str) {
|
|
||||||
let msg = format!("[DEBUG] {msg}");
|
|
||||||
match connected_to_journal() {
|
|
||||||
true => log::debug!("{msg}"),
|
|
||||||
false => println!("{msg}"),
|
|
||||||
}
|
|
||||||
mem_log(Level::Debug, Some(msg));
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn info(msg: &str) {
|
|
||||||
let msg = format!("[INFO] {msg}");
|
|
||||||
match connected_to_journal() {
|
|
||||||
true => log::info!("{msg}"),
|
|
||||||
false => println!("{msg}"),
|
|
||||||
}
|
|
||||||
mem_log(Level::Info, Some(msg));
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn warn(msg: &str) {
|
|
||||||
let msg = format!("[WARN] {msg}");
|
|
||||||
match connected_to_journal() {
|
|
||||||
true => log::warn!("{msg}"),
|
|
||||||
false => println!("{msg}"),
|
|
||||||
}
|
|
||||||
mem_log(Level::Warn, Some(msg));
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn error(msg: &str) {
|
|
||||||
let msg = format!("[ERROR] {msg}");
|
|
||||||
match connected_to_journal() {
|
|
||||||
true => log::error!("{msg}"),
|
|
||||||
false => eprintln!("{msg}"),
|
|
||||||
}
|
|
||||||
mem_log(Level::Error, Some(msg));
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get_mem_log(level: Level) -> VecDeque<LogEvent> {
|
|
||||||
mem_log(level, None)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone)]
|
|
||||||
pub(crate) struct LogEvent {
|
|
||||||
pub date: DateTime<Utc>,
|
|
||||||
pub text: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl LogEvent {
|
|
||||||
pub fn new(time: DateTime<Utc>, message: String) -> Self {
|
|
||||||
Self {
|
|
||||||
date: time,
|
|
||||||
text: message
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
88
src/main.rs
88
src/main.rs
|
@ -1,26 +1,15 @@
|
||||||
use std::env;
|
|
||||||
use std::sync::Arc;
|
|
||||||
use std::sync::atomic::{AtomicBool, Ordering};
|
|
||||||
use std::thread::{sleep, spawn};
|
|
||||||
use atomic_enum::atomic_enum;
|
|
||||||
use chrono::{Duration};
|
use chrono::{Duration};
|
||||||
use log::{LevelFilter};
|
use log::{LevelFilter};
|
||||||
use once_cell::sync::Lazy;
|
use once_cell::sync::Lazy;
|
||||||
use parking_lot::{RwLock};
|
use reqwest::Client;
|
||||||
use reqwest::blocking::Client;
|
|
||||||
use systemd_journal_logger::{JournalLog};
|
use systemd_journal_logger::{JournalLog};
|
||||||
use crate::bot::Bot;
|
use crate::bot::Bot;
|
||||||
use crate::HealthSignal::RequestAlive;
|
|
||||||
use crate::logging::Logging;
|
|
||||||
use crate::settings::ApplicationSettings;
|
|
||||||
|
|
||||||
mod bot;
|
mod bot;
|
||||||
mod config;
|
mod config;
|
||||||
mod lemmy;
|
mod lemmy;
|
||||||
mod post_history;
|
mod post_history;
|
||||||
mod fetchers;
|
mod fetchers;
|
||||||
mod logging;
|
|
||||||
mod settings;
|
|
||||||
|
|
||||||
pub static HTTP_CLIENT: Lazy<Client> = Lazy::new(|| {
|
pub static HTTP_CLIENT: Lazy<Client> = Lazy::new(|| {
|
||||||
Client::builder()
|
Client::builder()
|
||||||
|
@ -30,13 +19,13 @@ pub static HTTP_CLIENT: Lazy<Client> = Lazy::new(|| {
|
||||||
.expect("build client")
|
.expect("build client")
|
||||||
});
|
});
|
||||||
|
|
||||||
fn main() {
|
#[tokio::main]
|
||||||
|
async fn main() {
|
||||||
JournalLog::new()
|
JournalLog::new()
|
||||||
.expect("Systemd-Logger crate error")
|
.expect("Systemd-Logger crate error")
|
||||||
.install()
|
.install()
|
||||||
.expect("Systemd-Logger crate error");
|
.expect("Systemd-Logger crate error");
|
||||||
|
match std::env::var("LOG_LEVEL") {
|
||||||
match env::var("LOG_LEVEL") {
|
|
||||||
Ok(level) => {
|
Ok(level) => {
|
||||||
match level.as_str() {
|
match level.as_str() {
|
||||||
"debug" => log::set_max_level(LevelFilter::Debug),
|
"debug" => log::set_max_level(LevelFilter::Debug),
|
||||||
|
@ -47,71 +36,6 @@ fn main() {
|
||||||
_ => log::set_max_level(LevelFilter::Info),
|
_ => log::set_max_level(LevelFilter::Info),
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut settings = Arc::new(RwLock::new(ApplicationSettings::new()));
|
let mut bot = Bot::new();
|
||||||
|
bot.run().await;
|
||||||
env::set_var("USING_DB", settings.read().is_settings_source_database().to_string());
|
|
||||||
if settings.read().is_settings_source_database() {
|
|
||||||
Logging::info("Database Usage enabled");
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
Logging::info("Operating in Single Bot Mode")
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut health_signals: Vec<Arc<AtomicHealthSignal>> = vec![];
|
|
||||||
let mut update_pending: Arc<AtomicBool> = Arc::new(AtomicBool::new(false));
|
|
||||||
|
|
||||||
settings.read().bot_settings().read().iter().for_each(|(id, bot_settings)| {
|
|
||||||
let health_signal = Arc::new(AtomicHealthSignal::new(HealthSignal::RequestAlive));
|
|
||||||
health_signals.push(health_signal.clone());
|
|
||||||
|
|
||||||
let bot_settings = bot_settings.clone();
|
|
||||||
let bot_id = *id;
|
|
||||||
let series_settings = settings.read().series_settings().clone();
|
|
||||||
let bot_settings_map = settings.read().bot_settings().clone();
|
|
||||||
let update_pending = update_pending.clone();
|
|
||||||
spawn(move || {
|
|
||||||
let mut bot = Bot::new(health_signal, bot_id, bot_settings);
|
|
||||||
bot.run(series_settings, bot_settings_map, update_pending);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
loop {
|
|
||||||
update_pending.store(true, Ordering::SeqCst);
|
|
||||||
settings.write().update();
|
|
||||||
Logging::info("Configuration Updated");
|
|
||||||
update_pending.store(false, Ordering::SeqCst);
|
|
||||||
|
|
||||||
health_signals.iter().for_each(|signal| {
|
|
||||||
if signal.load(Ordering::SeqCst) == RequestAlive {
|
|
||||||
// at least one thread died, close all other threads
|
|
||||||
Logging::warn("A Bot died, restarting application.");
|
|
||||||
health_signals.iter().for_each(|signal| {
|
|
||||||
signal.store(HealthSignal::RequestStop, Ordering::SeqCst);
|
|
||||||
});
|
|
||||||
|
|
||||||
loop {
|
|
||||||
if health_signals.iter().all(|signal| {
|
|
||||||
signal.load(Ordering::SeqCst) == HealthSignal::ConfirmStop
|
|
||||||
}) {
|
|
||||||
Logging::info("All Threads successfully stopped");
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
sleep(Duration::milliseconds(100).to_std().unwrap());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
sleep(Duration::seconds(10).to_std().unwrap());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
#[atomic_enum]
|
|
||||||
#[derive(PartialEq)]
|
|
||||||
enum HealthSignal {
|
|
||||||
RequestAlive,
|
|
||||||
ConfirmAlive,
|
|
||||||
RequestStop,
|
|
||||||
ConfirmStop,
|
|
||||||
}
|
|
|
@ -1,142 +0,0 @@
|
||||||
use std::any::{Any, TypeId};
|
|
||||||
use std::collections::HashMap;
|
|
||||||
use std::sync::Arc;
|
|
||||||
use std::time::Duration;
|
|
||||||
use chrono::{DateTime, Utc};
|
|
||||||
use lemmy_db_schema::sensitive::SensitiveString;
|
|
||||||
use parking_lot::RwLock;
|
|
||||||
use serde_derive::{Deserialize, Serialize};
|
|
||||||
use crate::fetchers::Fetcher;
|
|
||||||
use crate::lemmy::PostType;
|
|
||||||
use crate::settings::toml::database::DatabaseTomlSettings;
|
|
||||||
use crate::settings::toml::TomlSettings;
|
|
||||||
|
|
||||||
pub mod database;
|
|
||||||
pub mod toml;
|
|
||||||
|
|
||||||
pub trait SettingsSource {
|
|
||||||
fn update(&mut self);
|
|
||||||
fn bot_settings(&self) -> HashMap<u16, BotSettings>;
|
|
||||||
fn series_settings(&self) -> HashMap<u16, SeriesSettings>;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct ApplicationSettings {
|
|
||||||
settings_source: Box<dyn SettingsSource>,
|
|
||||||
bot_settings: Arc<RwLock<HashMap<u16, BotSettings>>>,
|
|
||||||
series_settings: Arc<RwLock<HashMap<u16, SeriesSettings>>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ApplicationSettings {
|
|
||||||
pub fn new() -> Self {
|
|
||||||
let toml_settings = TomlSettings::load();
|
|
||||||
let settings_source: Box<dyn SettingsSource>;
|
|
||||||
|
|
||||||
if let Some(database) = toml_settings.database {
|
|
||||||
settings_source = Box::new(database) as Box<dyn SettingsSource>;
|
|
||||||
}
|
|
||||||
else if let Some(single_bot) = toml_settings.single_bot {
|
|
||||||
settings_source = Box::new(single_bot) as Box<dyn SettingsSource>;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
panic!("Invalid TOML Configuration: No valid Settings Source provided!");
|
|
||||||
}
|
|
||||||
|
|
||||||
let bot_settings = Arc::new(RwLock::new(settings_source.bot_settings()));
|
|
||||||
let series_settings = Arc::new(RwLock::new(settings_source.series_settings()));
|
|
||||||
ApplicationSettings {
|
|
||||||
settings_source,
|
|
||||||
bot_settings,
|
|
||||||
series_settings,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn is_settings_source_database(&self) -> bool {
|
|
||||||
self.settings_source.type_id() == TypeId::of::<DatabaseTomlSettings>()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn update(&mut self) {
|
|
||||||
self.settings_source.update();
|
|
||||||
*self.bot_settings.write() = self.settings_source.bot_settings();
|
|
||||||
*self.series_settings.write() = self.settings_source.series_settings();
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn bot_settings(&self) -> Arc<RwLock<HashMap<u16, BotSettings>>> {
|
|
||||||
self.bot_settings.clone()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn series_settings(&self) -> Arc<RwLock<HashMap<u16, SeriesSettings>>> {
|
|
||||||
self.series_settings.clone()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone)]
|
|
||||||
pub struct StatusPostSettings {
|
|
||||||
url: String,
|
|
||||||
interval: Duration,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone)]
|
|
||||||
pub struct BotSettings {
|
|
||||||
id: u16,
|
|
||||||
series_ids: Vec<u16>,
|
|
||||||
instance: String,
|
|
||||||
username: SensitiveString,
|
|
||||||
password: SensitiveString,
|
|
||||||
status_post_settings: Option<StatusPostSettings>,
|
|
||||||
}
|
|
||||||
impl BotSettings {
|
|
||||||
pub fn instance(&self) -> String {
|
|
||||||
self.instance.clone()
|
|
||||||
}
|
|
||||||
pub fn username(&self) -> SensitiveString {
|
|
||||||
self.username.clone()
|
|
||||||
}
|
|
||||||
pub fn password(&self) -> SensitiveString {
|
|
||||||
self.password.clone()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn status_post_url(&self) -> Option<String> {
|
|
||||||
match &self.status_post_settings {
|
|
||||||
Some(settings) => Some(settings.url.clone()),
|
|
||||||
None => None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn status_post_interval(&self) -> Option<Duration> {
|
|
||||||
match &self.status_post_settings {
|
|
||||||
Some(settings) => Some(settings.interval),
|
|
||||||
None => None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct SeriesSettings {
|
|
||||||
id: u16,
|
|
||||||
post_settings: HashMap<PostType, PostSettings>,
|
|
||||||
fetcher: Fetcher,
|
|
||||||
fetch_interval: Duration,
|
|
||||||
last_fetched: DateTime<Utc>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl SeriesSettings {
|
|
||||||
pub fn check(&self) {
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct PostSettings {
|
|
||||||
r#type: PostType,
|
|
||||||
community: String,
|
|
||||||
body_type: PostBody,
|
|
||||||
pin_community: bool,
|
|
||||||
pin_instance: bool
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
|
|
||||||
#[serde(tag = "body_type", content = "body_content")]
|
|
||||||
pub(crate) enum PostBody {
|
|
||||||
None,
|
|
||||||
Description,
|
|
||||||
Custom(String),
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,87 +0,0 @@
|
||||||
use std::collections::HashMap;
|
|
||||||
use std::time::Duration;
|
|
||||||
use chrono::Utc;
|
|
||||||
use lemmy_db_schema::sensitive::SensitiveString;
|
|
||||||
use serde_derive::{Deserialize, Serialize};
|
|
||||||
use crate::config::SeriesConfig;
|
|
||||||
use crate::fetchers::{Fetcher, FetcherTrait};
|
|
||||||
use crate::fetchers::jnovel::JNovelFetcher;
|
|
||||||
use crate::lemmy::PostType;
|
|
||||||
use crate::settings::{BotSettings, PostSettings, SeriesSettings, SettingsSource, StatusPostSettings};
|
|
||||||
use crate::settings::toml::TomlSettings;
|
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Clone, Debug)]
|
|
||||||
pub struct SingleBotTomlSettings {
|
|
||||||
instance: String,
|
|
||||||
username: SensitiveString,
|
|
||||||
password: SensitiveString,
|
|
||||||
status_post_url: Option<String>,
|
|
||||||
status_post_interval: Option<u32>,
|
|
||||||
pub protected_communities: Vec<String>,
|
|
||||||
series: Vec<SeriesConfig>,
|
|
||||||
fetch_interval: u32,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl SettingsSource for SingleBotTomlSettings {
|
|
||||||
fn update(&mut self) {
|
|
||||||
let toml_settings = TomlSettings::load();
|
|
||||||
|
|
||||||
if let Some(single_bot) = toml_settings.single_bot {
|
|
||||||
*self = single_bot;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
panic!("No valid 'single_bot' configuration found. Hot-switching between database and single_bot mode is not currently supported.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn bot_settings(&self) -> HashMap<u16, BotSettings> {
|
|
||||||
let mut map = HashMap::new();
|
|
||||||
map.insert(0, BotSettings {
|
|
||||||
id: 0,
|
|
||||||
series_ids: vec![0],
|
|
||||||
instance: self.instance.clone(),
|
|
||||||
username: self.username.clone(),
|
|
||||||
password: self.password.clone(),
|
|
||||||
status_post_settings: Some(StatusPostSettings {
|
|
||||||
url: self.status_post_url.clone().unwrap(),
|
|
||||||
interval: Duration::from_secs(self.status_post_interval.clone().unwrap() as u64)
|
|
||||||
}),
|
|
||||||
});
|
|
||||||
map
|
|
||||||
}
|
|
||||||
|
|
||||||
fn series_settings(&self) -> HashMap<u16, SeriesSettings> {
|
|
||||||
let mut map = HashMap::new();
|
|
||||||
self.series.iter().enumerate().for_each(|(id, series)| {
|
|
||||||
let mut fetcher = JNovelFetcher::new();
|
|
||||||
fetcher.set_series(series.slug.clone());
|
|
||||||
fetcher.set_part_option(series.parted);
|
|
||||||
|
|
||||||
let mut post_settings = HashMap::new();
|
|
||||||
post_settings.insert(PostType::Chapter, PostSettings {
|
|
||||||
r#type: PostType::Chapter,
|
|
||||||
community: series.prepub_community.name.clone(),
|
|
||||||
body_type: series.prepub_community.post_body.clone(),
|
|
||||||
pin_community: series.prepub_community.pin_settings.pin_new_post_community,
|
|
||||||
pin_instance: series.prepub_community.pin_settings.pin_new_post_local,
|
|
||||||
});
|
|
||||||
|
|
||||||
post_settings.insert(PostType::Volume, PostSettings {
|
|
||||||
r#type: PostType::Volume,
|
|
||||||
community: series.volume_community.name.clone(),
|
|
||||||
body_type: series.volume_community.post_body.clone(),
|
|
||||||
pin_community: series.volume_community.pin_settings.pin_new_post_community,
|
|
||||||
pin_instance: series.volume_community.pin_settings.pin_new_post_local,
|
|
||||||
});
|
|
||||||
|
|
||||||
map.insert(id as u16, SeriesSettings {
|
|
||||||
id: id as u16,
|
|
||||||
post_settings,
|
|
||||||
fetcher: Fetcher::Jnc(fetcher),
|
|
||||||
fetch_interval: Duration::from_secs(self.fetch_interval as u64),
|
|
||||||
last_fetched: Utc::now() - Duration::from_secs(self.fetch_interval as u64),
|
|
||||||
});
|
|
||||||
});
|
|
||||||
map
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,22 +0,0 @@
|
||||||
use std::collections::HashMap;
|
|
||||||
use serde_derive::{Deserialize, Serialize};
|
|
||||||
use crate::settings::{BotSettings, SeriesSettings, SettingsSource};
|
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Clone, Debug)]
|
|
||||||
pub struct DatabaseTomlSettings {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
impl SettingsSource for DatabaseTomlSettings {
|
|
||||||
fn update(&mut self) {
|
|
||||||
todo!()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn bot_settings(&self) -> HashMap<u16, BotSettings> {
|
|
||||||
todo!()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn series_settings(&self) -> HashMap<u16, SeriesSettings> {
|
|
||||||
todo!()
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,37 +0,0 @@
|
||||||
pub mod database;
|
|
||||||
pub mod bot;
|
|
||||||
|
|
||||||
use std::time::Duration;
|
|
||||||
use lemmy_db_schema::sensitive::SensitiveString;
|
|
||||||
use serde_derive::{Deserialize, Serialize};
|
|
||||||
use crate::config::SeriesConfig;
|
|
||||||
use crate::settings::BotSettings;
|
|
||||||
use crate::settings::toml::bot::SingleBotTomlSettings;
|
|
||||||
use crate::settings::toml::database::DatabaseTomlSettings;
|
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Clone, Debug)]
|
|
||||||
pub struct TomlSettings {
|
|
||||||
pub database: Option<DatabaseTomlSettings>,
|
|
||||||
pub single_bot: Option<SingleBotTomlSettings>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl TomlSettings {
|
|
||||||
pub(crate) fn load() -> Self {
|
|
||||||
let mut cfg: Self = match confy::load(env!("CARGO_PKG_NAME"), "config") {
|
|
||||||
Ok(data) => data,
|
|
||||||
Err(e) => panic!("config.toml not found: {e}"),
|
|
||||||
};
|
|
||||||
|
|
||||||
cfg
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Default for TomlSettings {
|
|
||||||
fn default() -> Self {
|
|
||||||
TomlSettings {
|
|
||||||
database: None,
|
|
||||||
single_bot: None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
Loading…
Reference in a new issue