328 lines
8.8 KiB
Rust
328 lines
8.8 KiB
Rust
use std::{
|
|
error::Error,
|
|
fs::{self, OpenOptions},
|
|
io::Write,
|
|
path::Path,
|
|
time,
|
|
};
|
|
|
|
use lemmy_api_common::{
|
|
community::{ListCommunities, ListCommunitiesResponse},
|
|
post::CreatePost,
|
|
sensitive::Sensitive,
|
|
};
|
|
use lemmy_db_schema::{
|
|
newtypes::{CommunityId, LanguageId, PostId},
|
|
ListingType,
|
|
};
|
|
use serde_derive::{Deserialize, Serialize};
|
|
use tokio::time::sleep;
|
|
use url::Url;
|
|
|
|
use crate::CLIENT;
|
|
|
|
macro_rules! pub_struct {
|
|
($name:ident {$($field:ident: $t:ty,)*}) => {
|
|
#[derive(Serialize, Deserialize, Clone, PartialEq)]
|
|
pub(crate) struct $name {
|
|
$(pub(crate) $field: $t), *
|
|
}
|
|
}
|
|
}
|
|
|
|
// Secrets structs
|
|
pub_struct!(Secrets {
|
|
lemmy: LemmyLogin,
|
|
reddit: RedditLogin,
|
|
});
|
|
|
|
impl Secrets {
|
|
pub(crate) fn init() -> Secrets {
|
|
let file_contents = match fs::read_to_string("secrets.json") {
|
|
Ok(data) => data,
|
|
Err(e) => panic!("ERROR: secrets.json could not be read:\n\n{:#?}", e),
|
|
};
|
|
let config_parse: Secrets = match serde_json::from_str(&file_contents) {
|
|
Ok(data) => data,
|
|
Err(e) => panic!("ERROR: secrets.json could not be parsed:\n\n{:#?}", e),
|
|
};
|
|
|
|
return config_parse;
|
|
}
|
|
}
|
|
|
|
#[derive(Serialize, Deserialize, Clone, PartialEq)]
|
|
pub(crate) struct LemmyLogin {
|
|
pub(crate) username: String,
|
|
password: String,
|
|
}
|
|
|
|
impl LemmyLogin {
|
|
pub(crate) fn get_username(&self) -> Sensitive<String> {
|
|
return Sensitive::new(self.username.clone());
|
|
}
|
|
|
|
pub(crate) fn get_password(&self) -> Sensitive<String> {
|
|
return Sensitive::new(self.password.clone());
|
|
}
|
|
}
|
|
|
|
#[derive(Serialize, Deserialize, Clone, PartialEq)]
|
|
pub(crate) struct RedditLogin {
|
|
pub(crate) app_id: String,
|
|
app_secret: String,
|
|
refresh_token: String,
|
|
redirect_uri: String,
|
|
}
|
|
|
|
// Config structs
|
|
pub_struct!(Config {
|
|
instance: String,
|
|
reddit_config: RedditConfig,
|
|
feeds: Vec<FeedSetting>,
|
|
});
|
|
|
|
impl Config {
|
|
pub(crate) fn init() -> Config {
|
|
let file_contents = match fs::read_to_string("config.json") {
|
|
Ok(data) => data,
|
|
Err(e) => panic!("ERROR: config.json could not be read:\n\n{:#?}", e),
|
|
};
|
|
let config_parse: Config = match serde_json::from_str(&file_contents) {
|
|
Ok(data) => data,
|
|
Err(e) => panic!("ERROR: config.json could not be parsed:\n\n{:#?}", e),
|
|
};
|
|
|
|
return config_parse;
|
|
}
|
|
|
|
pub(crate) fn load(&mut self) {
|
|
let file_contents = match fs::read_to_string("config.json") {
|
|
Ok(data) => data,
|
|
Err(e) => panic!("ERROR: config.json could not be read:\n\n{:#?}", e),
|
|
};
|
|
let config_parse: Config = match serde_json::from_str(&file_contents) {
|
|
Ok(data) => data,
|
|
Err(e) => panic!("ERROR: config.json could not be parsed:\n\n{:#?}", e),
|
|
};
|
|
|
|
self.feeds = config_parse.feeds;
|
|
self.instance = config_parse.instance;
|
|
self.reddit_config = config_parse.reddit_config;
|
|
}
|
|
|
|
pub(crate) async fn check_feeds(
|
|
&mut self,
|
|
post_history: &mut Vec<PrevPost>,
|
|
community_ids: &CommunitiesVector,
|
|
auth: &Sensitive<String>,
|
|
) -> Result<Vec<(CreatePost, (Option<usize>, usize, String))>, Box<dyn Error>> {
|
|
let mut post_queue: Vec<(CreatePost, (Option<usize>, usize, String))> = vec![];
|
|
|
|
let mut i = 0;
|
|
while i < self.feeds.len() {
|
|
let feed = &self.feeds[i];
|
|
|
|
let res = CLIENT
|
|
.get(feed.feed_url.clone())
|
|
.send()
|
|
.await?
|
|
.text()
|
|
.await?;
|
|
|
|
let data: FeedData = serde_json::from_str(&res).unwrap();
|
|
|
|
let mut prev_post_idx: Option<usize> = None;
|
|
let mut do_post = true;
|
|
post_history.iter().enumerate().for_each(|(idx, post)| {
|
|
if &post.last_post_url == &data.items[0].url {
|
|
do_post = false;
|
|
} else if &post.title == &data.title {
|
|
prev_post_idx = Some(idx);
|
|
}
|
|
});
|
|
|
|
if do_post {
|
|
let item = &data.items[0];
|
|
let new_post = CreatePost {
|
|
name: item.title.clone(),
|
|
community_id: community_ids.find(&feed.communities.chapter),
|
|
url: Some(Url::parse(&item.url).unwrap()),
|
|
body: Some(
|
|
"[Reddit](https://reddit.com/r/HonzukinoGekokujou)\n\n[Discord](https://discord.com/invite/fGefmzu)".into(),
|
|
),
|
|
honeypot: None,
|
|
nsfw: Some(false),
|
|
language_id: Some(LanguageId(37)), // TODO get this id once every few hours per API request, the ordering of IDs suggests that the EN Id might change in the future
|
|
auth: auth.clone(),
|
|
};
|
|
|
|
let prev_data = (prev_post_idx, feed.id, data.title);
|
|
|
|
post_queue.push((new_post, prev_data));
|
|
}
|
|
sleep(time::Duration::from_millis(100)).await; // Should prevent dos-ing J-Novel servers
|
|
i += 1;
|
|
}
|
|
|
|
return Ok(post_queue);
|
|
}
|
|
}
|
|
|
|
pub_struct!(RedditConfig {
|
|
user_agent: String,
|
|
subreddit: String,
|
|
});
|
|
|
|
pub_struct!(FeedSetting {
|
|
id: usize,
|
|
feed_url: String,
|
|
communities: FeedCommunities,
|
|
reddit: FeedRedditSettings,
|
|
});
|
|
|
|
pub_struct!(FeedCommunities {
|
|
chapter: LemmyCommunities,
|
|
volume: LemmyCommunities,
|
|
});
|
|
|
|
#[derive(Serialize, Deserialize, Clone, PartialEq, strum_macros::Display)]
|
|
#[allow(non_camel_case_types)]
|
|
pub(crate) enum LemmyCommunities {
|
|
aobwebnovel,
|
|
aobprepub,
|
|
aoblightnovel,
|
|
aobmanga,
|
|
metadiscussions,
|
|
}
|
|
|
|
pub_struct!(FeedRedditSettings {
|
|
enabled: bool,
|
|
flair: String,
|
|
});
|
|
|
|
// Posts structs
|
|
pub_struct!(PrevPost {
|
|
id: usize,
|
|
post_id: PostId,
|
|
title: String,
|
|
last_post_url: String,
|
|
});
|
|
|
|
impl PrevPost {
|
|
pub(crate) fn load() -> Vec<PrevPost> {
|
|
let history;
|
|
|
|
if Path::new("posts.json").exists() {
|
|
let file_contents = match fs::read_to_string("posts.json") {
|
|
Ok(data) => data,
|
|
Err(e) => panic!("ERROR: posts.json could not be read:\n\n{:#?}", e),
|
|
};
|
|
|
|
if file_contents.len() > 0 {
|
|
let history_parse: Vec<PrevPost> = match serde_json::from_str(&file_contents) {
|
|
Ok(data) => data,
|
|
Err(e) => panic!("ERROR: posts.json could not be parsed:\n\n{:#?}", e),
|
|
};
|
|
history = history_parse;
|
|
} else {
|
|
history = [].to_vec()
|
|
}
|
|
} else {
|
|
let _ = fs::File::create("posts.json");
|
|
history = [].to_vec()
|
|
}
|
|
|
|
return history;
|
|
}
|
|
|
|
pub(crate) fn save(data: &Vec<PrevPost>) {
|
|
let mut file = OpenOptions::new()
|
|
.read(true)
|
|
.write(true)
|
|
.create(true)
|
|
.open("posts.json")
|
|
.unwrap();
|
|
|
|
let json_data = serde_json::to_string_pretty(&data).unwrap();
|
|
|
|
write!(&mut file, "{}", json_data).unwrap();
|
|
}
|
|
}
|
|
|
|
// RSS Feed Structs
|
|
pub_struct!(FeedData {
|
|
version: String,
|
|
title: String,
|
|
home_page_url: String,
|
|
description: String,
|
|
author: FeedAuthor,
|
|
items: Vec<FeedEntry>,
|
|
});
|
|
|
|
pub_struct!(FeedAuthor { name: String, });
|
|
|
|
pub_struct!(FeedEntry {
|
|
id: String,
|
|
url: String,
|
|
title: String,
|
|
summary: String,
|
|
image: Option<String>,
|
|
date_published: String,
|
|
});
|
|
|
|
// Bot Helper Structs
|
|
pub_struct!(CommunitiesVector {
|
|
ids: Vec<(CommunityId, String)>,
|
|
});
|
|
|
|
impl CommunitiesVector {
|
|
pub(crate) fn new() -> CommunitiesVector {
|
|
CommunitiesVector { ids: vec![] }
|
|
}
|
|
|
|
#[warn(unused_results)]
|
|
pub(crate) async fn load(
|
|
&mut self,
|
|
auth: &Sensitive<String>,
|
|
base: &String,
|
|
) -> Result<(), Box<dyn Error>> {
|
|
let params = ListCommunities {
|
|
auth: Some(auth.clone()),
|
|
type_: Some(ListingType::Local),
|
|
..Default::default()
|
|
};
|
|
|
|
let res = CLIENT
|
|
.get(base.clone() + "/api/v3/community/list")
|
|
.query(¶ms)
|
|
.send()
|
|
.await?
|
|
.text()
|
|
.await?;
|
|
|
|
let site_data: ListCommunitiesResponse = serde_json::from_str(&res).unwrap();
|
|
|
|
let mut ids = [].to_vec();
|
|
|
|
site_data.communities.iter().for_each(|entry| {
|
|
let new_id = (entry.community.id, entry.community.name.clone());
|
|
ids.push(new_id);
|
|
});
|
|
|
|
self.ids = ids;
|
|
return Ok(());
|
|
}
|
|
|
|
pub(crate) fn find(&self, name: &LemmyCommunities) -> CommunityId {
|
|
let mut ret_id = CommunityId(0);
|
|
|
|
self.ids.iter().for_each(|id| {
|
|
let id_name = &id.1;
|
|
if &name.to_string() == id_name {
|
|
ret_id = id.0;
|
|
}
|
|
});
|
|
return ret_id;
|
|
}
|
|
}
|