239 lines
7.1 KiB
Rust
239 lines
7.1 KiB
Rust
use chrono::Utc;
|
|
use config::{Config, PrevPost, Secrets, LemmyCommunities};
|
|
use lemmy_api_common::{
|
|
person::{Login, LoginResponse},
|
|
post::{CreatePost, GetPosts, GetPostsResponse},
|
|
sensitive::Sensitive, community::{ListCommunities, ListCommunitiesResponse},
|
|
};
|
|
use lemmy_db_schema::{
|
|
newtypes::{CommunityId, LanguageId},
|
|
ListingType, SortType,
|
|
};
|
|
use once_cell::sync::Lazy;
|
|
use reqwest::{blocking::Client, StatusCode};
|
|
use std::{thread::sleep, time};
|
|
use url::Url;
|
|
|
|
use crate::config::FeedData;
|
|
mod config;
|
|
|
|
pub static CLIENT: Lazy<Client> = Lazy::new(|| {
|
|
let client = Client::builder()
|
|
.timeout(time::Duration::from_secs(30))
|
|
.connect_timeout(time::Duration::from_secs(30))
|
|
.build()
|
|
.expect("build client");
|
|
client
|
|
});
|
|
|
|
struct Bot {
|
|
secrets: Secrets,
|
|
config: Config,
|
|
post_history: Vec<PrevPost>,
|
|
community_ids: CommunitiesVector,
|
|
auth: Sensitive<String>,
|
|
}
|
|
|
|
impl Bot {
|
|
pub(crate) fn new() -> Bot {
|
|
Bot {
|
|
secrets: Secrets::load(),
|
|
config: Config::load(),
|
|
post_history: PrevPost::load(),
|
|
community_ids: CommunitiesVector::new(),
|
|
auth: Sensitive::new("".to_string()),
|
|
}
|
|
}
|
|
|
|
pub(crate) fn login(&mut self) {
|
|
let login_params = Login {
|
|
username_or_email: self.secrets.lemmy.get_username(),
|
|
password: self.secrets.lemmy.get_password(),
|
|
};
|
|
|
|
let res = CLIENT
|
|
.post(self.config.instance.clone() + "/api/v3/user/login")
|
|
.json(&login_params)
|
|
.send()
|
|
.unwrap();
|
|
|
|
if res.status() == StatusCode::OK {
|
|
let data: &LoginResponse = &res.json().unwrap();
|
|
|
|
let jwt = data.jwt.clone().expect("JWT Token could not be acquired");
|
|
self.auth = jwt;
|
|
} else {
|
|
println!("Error Code: {:?}", res.status());
|
|
panic!("JWT Token could not be acquired");
|
|
}
|
|
}
|
|
|
|
pub(crate) fn post(&mut self, post_data: CreatePost) {
|
|
let res = CLIENT
|
|
.post(self.config.instance.clone() + "/api/v3/post")
|
|
.json(&post_data)
|
|
.send()
|
|
.unwrap();
|
|
}
|
|
}
|
|
|
|
fn list_posts(auth: &Sensitive<String>, base: String) -> GetPostsResponse {
|
|
let params = GetPosts {
|
|
type_: Some(ListingType::Local),
|
|
sort: Some(SortType::New),
|
|
auth: Some(auth.clone()),
|
|
..Default::default()
|
|
};
|
|
|
|
let res = CLIENT
|
|
.get(base + "/api/v3/post/list")
|
|
.query(¶ms)
|
|
.send()
|
|
.unwrap()
|
|
.text()
|
|
.unwrap();
|
|
|
|
return serde_json::from_str(&res).unwrap();
|
|
}
|
|
|
|
struct CommunitiesVector {
|
|
ids: Vec<(CommunityId, String)>,
|
|
}
|
|
|
|
impl CommunitiesVector {
|
|
fn new() -> CommunitiesVector {
|
|
CommunitiesVector{ids: vec![]}
|
|
}
|
|
|
|
fn load(&mut self, auth: &Sensitive<String>, base: &String) {
|
|
let params = ListCommunities {
|
|
auth: Some(auth.clone()),
|
|
..Default::default()
|
|
};
|
|
|
|
let res = CLIENT
|
|
.get(base.clone() + "/api/v3/community/list")
|
|
.query(¶ms)
|
|
.send()
|
|
.unwrap()
|
|
.text()
|
|
.unwrap();
|
|
|
|
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;
|
|
}
|
|
|
|
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;
|
|
}
|
|
}
|
|
|
|
fn main() {
|
|
// Get all needed auth tokens at the start
|
|
let mut old = Utc::now().time();
|
|
let mut this = Bot::new();
|
|
println!("{}", this.secrets.lemmy.username);
|
|
this.login();
|
|
this.community_ids.load(&this.auth, &this.config.instance);
|
|
|
|
// Create empty eTag list
|
|
println!("TODO: Etag list");
|
|
|
|
// Enter a loop (not for debugging)
|
|
loop {
|
|
let start = Utc::now();
|
|
print!("\x1B[2J\x1B[1;1H");
|
|
println!(
|
|
"Started loop at {} {}",
|
|
start.format("%H:%M:%S"),
|
|
start.timezone()
|
|
);
|
|
|
|
if start.time() - old > chrono::Duration::seconds(6) {
|
|
old = start.time();
|
|
this.config = Config::load();
|
|
this.community_ids.load(&this.auth, &this.config.instance);
|
|
}
|
|
|
|
// Start the polling process
|
|
// Get all feed URLs (use cache)
|
|
let mut post_queue: Vec<CreatePost> = vec![];
|
|
this.config.feeds.iter().for_each(|feed| {
|
|
let res = CLIENT
|
|
.get(feed.feed_url.clone())
|
|
.send()
|
|
.unwrap()
|
|
.text()
|
|
.unwrap();
|
|
let data: FeedData = serde_json::from_str(&res).unwrap();
|
|
|
|
let mut prev_post_idx: Option<usize> = None;
|
|
let mut do_post = true;
|
|
this.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: this.community_ids.find(&feed.communities.chapter),
|
|
url: Some(Url::parse(&item.url).unwrap()),
|
|
body: Some(
|
|
"[Reddit](https://reddit.com)\n\n[Discord](https://discord.com)".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: this.auth.clone(),
|
|
};
|
|
post_queue.push(new_post);
|
|
match prev_post_idx {
|
|
Some(idx) => {
|
|
this.post_history[idx].title = data.title;
|
|
this.post_history[idx].last_post_url = item.url.clone();
|
|
}
|
|
None => this.post_history.push(PrevPost {
|
|
title: data.title,
|
|
last_post_url: item.url.clone(),
|
|
}),
|
|
}
|
|
}
|
|
sleep(time::Duration::from_millis(100)); // Should prevent dos-ing J-Novel servers
|
|
});
|
|
|
|
PrevPost::save(&this.post_history);
|
|
post_queue.iter().for_each(|post| {
|
|
println!("Posting: {}", post.name);
|
|
this.post(post.clone());
|
|
});
|
|
|
|
while Utc::now().time() - start.time() < chrono::Duration::seconds(60) {
|
|
sleep(time::Duration::from_secs(10));
|
|
}
|
|
}
|
|
}
|