Move Bot Logic to Class Methods
This commit is contained in:
parent
5e95d2b62c
commit
877c7ef324
2 changed files with 177 additions and 151 deletions
|
@ -1,7 +1,11 @@
|
|||
use std::{fs::{self, OpenOptions}, path::Path, io::Write};
|
||||
use std::{fs::{self, OpenOptions}, path::Path, io::Write, thread::sleep, time};
|
||||
|
||||
use lemmy_api_common::sensitive::Sensitive;
|
||||
use lemmy_api_common::{sensitive::Sensitive, post::CreatePost, community::{self, ListCommunities, ListCommunitiesResponse}};
|
||||
use lemmy_db_schema::newtypes::{LanguageId, CommunityId};
|
||||
use serde_derive::{Deserialize, Serialize};
|
||||
use url::Url;
|
||||
|
||||
use crate::{CLIENT};
|
||||
|
||||
macro_rules! pub_struct {
|
||||
($name:ident {$($field:ident: $t:ty,)*}) => {
|
||||
|
@ -19,7 +23,7 @@ pub_struct!(Secrets {
|
|||
});
|
||||
|
||||
impl Secrets {
|
||||
pub(crate) fn load() -> 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),
|
||||
|
@ -65,7 +69,7 @@ pub_struct!(Config {
|
|||
});
|
||||
|
||||
impl Config {
|
||||
pub(crate) fn load() -> 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),
|
||||
|
@ -77,6 +81,80 @@ impl Config {
|
|||
|
||||
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) fn check_feeds(&mut self, post_history: &mut Vec<PrevPost>
|
||||
, community_ids: &CommunitiesVector, auth: &Sensitive<String>) -> Vec<CreatePost> {
|
||||
let mut post_queue: Vec<CreatePost> = vec![];
|
||||
|
||||
self.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;
|
||||
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)\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: auth.clone(),
|
||||
};
|
||||
post_queue.push(new_post);
|
||||
match prev_post_idx {
|
||||
Some(idx) => {
|
||||
post_history[idx].title = data.title;
|
||||
post_history[idx].last_post_url = item.url.clone();
|
||||
}
|
||||
None => 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(&post_history);
|
||||
return post_queue;
|
||||
}
|
||||
}
|
||||
|
||||
pub_struct!(RedditConfig {
|
||||
|
@ -177,3 +255,51 @@ pub_struct!(FeedEntry {
|
|||
date_published: String,
|
||||
});
|
||||
|
||||
// Bot Helper Structs
|
||||
pub_struct!(CommunitiesVector {
|
||||
ids: Vec<(CommunityId, String)>,
|
||||
});
|
||||
|
||||
impl CommunitiesVector {
|
||||
pub(crate) fn new() -> CommunitiesVector {
|
||||
CommunitiesVector{ids: vec![]}
|
||||
}
|
||||
|
||||
pub(crate) 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;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
|
190
src/main.rs
190
src/main.rs
|
@ -1,22 +1,19 @@
|
|||
use chrono::Utc;
|
||||
use config::{Config, PrevPost, Secrets, LemmyCommunities};
|
||||
use chrono::{Utc, DateTime, NaiveTime};
|
||||
use config::{Config, PrevPost, Secrets, CommunitiesVector};
|
||||
use lemmy_api_common::{
|
||||
person::{Login, LoginResponse},
|
||||
post::{CreatePost, GetPosts, GetPostsResponse},
|
||||
sensitive::Sensitive, community::{ListCommunities, ListCommunitiesResponse},
|
||||
sensitive::Sensitive,
|
||||
};
|
||||
use lemmy_db_schema::{
|
||||
newtypes::{CommunityId, LanguageId},
|
||||
ListingType, SortType,
|
||||
};
|
||||
use once_cell::sync::Lazy;
|
||||
use reqwest::{blocking::Client, StatusCode};
|
||||
use tui::{backend::{CrosstermBackend, Backend}, Terminal, widgets::{Widget, Block, Borders, Cell, Row, Table}, layout::{Layout, Constraint, Direction}, Frame, style::{Style, Modifier, Color}};
|
||||
use tui::{backend::{CrosstermBackend, Backend}, Terminal, widgets::{Block, Borders, Cell, Row, Table}, layout::{Layout, Constraint, Direction}, Frame, style::{Style, Modifier, Color}};
|
||||
use crossterm::{event::{self, DisableMouseCapture, EnableMouseCapture, Event, KeyCode}, execute, terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen},};
|
||||
use std::{thread::{sleep, self}, time::{self, Duration}, io};
|
||||
use url::Url;
|
||||
|
||||
use crate::config::FeedData;
|
||||
mod config;
|
||||
|
||||
pub static CLIENT: Lazy<Client> = Lazy::new(|| {
|
||||
|
@ -34,16 +31,18 @@ struct Bot {
|
|||
post_history: Vec<PrevPost>,
|
||||
community_ids: CommunitiesVector,
|
||||
auth: Sensitive<String>,
|
||||
start_time: DateTime<Utc>,
|
||||
}
|
||||
|
||||
impl Bot {
|
||||
pub(crate) fn new() -> Bot {
|
||||
Bot {
|
||||
secrets: Secrets::load(),
|
||||
config: Config::load(),
|
||||
secrets: Secrets::init(),
|
||||
config: Config::init(),
|
||||
post_history: PrevPost::load(),
|
||||
community_ids: CommunitiesVector::new(),
|
||||
auth: Sensitive::new("".to_string()),
|
||||
start_time: Utc::now(),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -77,6 +76,30 @@ impl Bot {
|
|||
.send()
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
pub(crate) fn run_once(&mut self, mut prev_time: NaiveTime) {
|
||||
self.start_time = Utc::now();
|
||||
|
||||
if self.start_time.time() - prev_time > chrono::Duration::seconds(6) { // Prod should use hours, add command line switch later and read duration from config
|
||||
prev_time = self.start_time.time();
|
||||
self.config.load();
|
||||
self.community_ids.load(&self.auth, &self.config.instance);
|
||||
}
|
||||
|
||||
// Start the polling process
|
||||
// Get all feed URLs (use cache)
|
||||
let post_queue: Vec<CreatePost> = self.config.check_feeds(&mut self.post_history, &self.community_ids, &self.auth);
|
||||
|
||||
post_queue.iter().for_each(|post| {
|
||||
println!("Posting: {}", post.name);
|
||||
self.post(post.clone());
|
||||
});
|
||||
|
||||
while Utc::now().time() - self.start_time.time() < chrono::Duration::seconds(60) {
|
||||
sleep(time::Duration::from_secs(10));
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
fn list_posts(auth: &Sensitive<String>, base: String) -> GetPostsResponse {
|
||||
|
@ -98,144 +121,20 @@ fn list_posts(auth: &Sensitive<String>, base: String) -> GetPostsResponse {
|
|||
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 run_bot() {
|
||||
// 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);
|
||||
// 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");
|
||||
// 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));
|
||||
}
|
||||
// Enter a loop (not for debugging)
|
||||
loop {
|
||||
this.run_once(old);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -304,6 +203,7 @@ fn main() -> Result<(), io::Error> {
|
|||
)?;
|
||||
terminal.show_cursor()?;
|
||||
|
||||
|
||||
run_bot();
|
||||
Ok(())
|
||||
//run_bot();
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue