Merge branch 'tui' into main
This commit is contained in:
commit
06b90cb5bf
4 changed files with 357 additions and 141 deletions
96
Cargo.lock
generated
96
Cargo.lock
generated
|
@ -93,6 +93,7 @@ name = "ascendance-of-a-bookworm-bot"
|
||||||
version = "1.1.0"
|
version = "1.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"chrono",
|
"chrono",
|
||||||
|
"crossterm 0.26.1",
|
||||||
"lemmy_api_common",
|
"lemmy_api_common",
|
||||||
"lemmy_db_schema",
|
"lemmy_db_schema",
|
||||||
"once_cell",
|
"once_cell",
|
||||||
|
@ -101,6 +102,7 @@ dependencies = [
|
||||||
"serde_derive",
|
"serde_derive",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
"strum_macros 0.25.0",
|
"strum_macros 0.25.0",
|
||||||
|
"tui",
|
||||||
"url",
|
"url",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@ -203,6 +205,12 @@ version = "1.4.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "89b2fd2a0dcf38d7971e2194b6b6eebab45ae01067456a7fd93d5547a61b70be"
|
checksum = "89b2fd2a0dcf38d7971e2194b6b6eebab45ae01067456a7fd93d5547a61b70be"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "cassowary"
|
||||||
|
version = "0.3.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "df8670b8c7b9dae1793364eafadf7239c40d669904660c5960d74cfd80b46a53"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "cc"
|
name = "cc"
|
||||||
version = "1.0.79"
|
version = "1.0.79"
|
||||||
|
@ -266,6 +274,47 @@ dependencies = [
|
||||||
"cfg-if",
|
"cfg-if",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "crossterm"
|
||||||
|
version = "0.25.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e64e6c0fbe2c17357405f7c758c1ef960fce08bdfb2c03d88d2a18d7e09c4b67"
|
||||||
|
dependencies = [
|
||||||
|
"bitflags",
|
||||||
|
"crossterm_winapi",
|
||||||
|
"libc",
|
||||||
|
"mio",
|
||||||
|
"parking_lot",
|
||||||
|
"signal-hook",
|
||||||
|
"signal-hook-mio",
|
||||||
|
"winapi",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "crossterm"
|
||||||
|
version = "0.26.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a84cda67535339806297f1b331d6dd6320470d2a0fe65381e79ee9e156dd3d13"
|
||||||
|
dependencies = [
|
||||||
|
"bitflags",
|
||||||
|
"crossterm_winapi",
|
||||||
|
"libc",
|
||||||
|
"mio",
|
||||||
|
"parking_lot",
|
||||||
|
"signal-hook",
|
||||||
|
"signal-hook-mio",
|
||||||
|
"winapi",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "crossterm_winapi"
|
||||||
|
version = "0.9.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "acdd7c62a3665c7f6830a51635d9ac9b23ed385797f70a83bb8bafe9c572ab2b"
|
||||||
|
dependencies = [
|
||||||
|
"winapi",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "encoding_rs"
|
name = "encoding_rs"
|
||||||
version = "0.8.32"
|
version = "0.8.32"
|
||||||
|
@ -772,6 +821,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "927a765cd3fc26206e66b296465fa9d3e5ab003e651c1b3c060e7956d96b19d2"
|
checksum = "927a765cd3fc26206e66b296465fa9d3e5ab003e651c1b3c060e7956d96b19d2"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"libc",
|
"libc",
|
||||||
|
"log",
|
||||||
"wasi 0.11.0+wasi-snapshot-preview1",
|
"wasi 0.11.0+wasi-snapshot-preview1",
|
||||||
"windows-sys 0.48.0",
|
"windows-sys 0.48.0",
|
||||||
]
|
]
|
||||||
|
@ -1164,6 +1214,27 @@ dependencies = [
|
||||||
"lazy_static",
|
"lazy_static",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "signal-hook"
|
||||||
|
version = "0.3.15"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "732768f1176d21d09e076c23a93123d40bba92d50c4058da34d45c8de8e682b9"
|
||||||
|
dependencies = [
|
||||||
|
"libc",
|
||||||
|
"signal-hook-registry",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "signal-hook-mio"
|
||||||
|
version = "0.2.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "29ad2e15f37ec9a6cc544097b78a1ec90001e9f71b81338ca39f430adaca99af"
|
||||||
|
dependencies = [
|
||||||
|
"libc",
|
||||||
|
"mio",
|
||||||
|
"signal-hook",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "signal-hook-registry"
|
name = "signal-hook-registry"
|
||||||
version = "1.4.1"
|
version = "1.4.1"
|
||||||
|
@ -1478,6 +1549,19 @@ version = "0.2.4"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "3528ecfd12c466c6f163363caf2d02a71161dd5e1cc6ae7b34207ea2d42d81ed"
|
checksum = "3528ecfd12c466c6f163363caf2d02a71161dd5e1cc6ae7b34207ea2d42d81ed"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tui"
|
||||||
|
version = "0.19.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ccdd26cbd674007e649a272da4475fb666d3aa0ad0531da7136db6fab0e5bad1"
|
||||||
|
dependencies = [
|
||||||
|
"bitflags",
|
||||||
|
"cassowary",
|
||||||
|
"crossterm 0.25.0",
|
||||||
|
"unicode-segmentation",
|
||||||
|
"unicode-width",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "typed-builder"
|
name = "typed-builder"
|
||||||
version = "0.10.0"
|
version = "0.10.0"
|
||||||
|
@ -1510,6 +1594,18 @@ dependencies = [
|
||||||
"tinyvec",
|
"tinyvec",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "unicode-segmentation"
|
||||||
|
version = "1.10.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "1dd624098567895118886609431a7c3b8f516e41d30e0643f03d94592a147e36"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "unicode-width"
|
||||||
|
version = "0.1.10"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "c0edd1e5b14653f783770bce4a4dabb4a5108a5370a5f5d8cfe8710c361f6c8b"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "url"
|
name = "url"
|
||||||
version = "2.4.0"
|
version = "2.4.0"
|
||||||
|
|
|
@ -7,6 +7,7 @@ edition = "2021"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
chrono = "0.4.26"
|
chrono = "0.4.26"
|
||||||
|
crossterm = "0.26.1"
|
||||||
lemmy_api_common = "0.17.4"
|
lemmy_api_common = "0.17.4"
|
||||||
lemmy_db_schema = "0.17.4"
|
lemmy_db_schema = "0.17.4"
|
||||||
once_cell = "1.18.0"
|
once_cell = "1.18.0"
|
||||||
|
@ -15,4 +16,5 @@ serde = "1.0.164"
|
||||||
serde_derive = "1.0.164"
|
serde_derive = "1.0.164"
|
||||||
serde_json = "1.0.97"
|
serde_json = "1.0.97"
|
||||||
strum_macros = "0.25.0"
|
strum_macros = "0.25.0"
|
||||||
|
tui = "0.19.0"
|
||||||
url = "2.4.0"
|
url = "2.4.0"
|
||||||
|
|
|
@ -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 serde_derive::{Deserialize, Serialize};
|
||||||
|
use url::Url;
|
||||||
|
|
||||||
|
use crate::{CLIENT};
|
||||||
|
|
||||||
macro_rules! pub_struct {
|
macro_rules! pub_struct {
|
||||||
($name:ident {$($field:ident: $t:ty,)*}) => {
|
($name:ident {$($field:ident: $t:ty,)*}) => {
|
||||||
|
@ -19,7 +23,7 @@ pub_struct!(Secrets {
|
||||||
});
|
});
|
||||||
|
|
||||||
impl Secrets {
|
impl Secrets {
|
||||||
pub(crate) fn load() -> Secrets {
|
pub(crate) fn init() -> Secrets {
|
||||||
let file_contents = match fs::read_to_string("secrets.json") {
|
let file_contents = match fs::read_to_string("secrets.json") {
|
||||||
Ok(data) => data,
|
Ok(data) => data,
|
||||||
Err(e) => panic!("ERROR: secrets.json could not be read:\n\n{:#?}", e),
|
Err(e) => panic!("ERROR: secrets.json could not be read:\n\n{:#?}", e),
|
||||||
|
@ -65,7 +69,7 @@ pub_struct!(Config {
|
||||||
});
|
});
|
||||||
|
|
||||||
impl Config {
|
impl Config {
|
||||||
pub(crate) fn load() -> Config {
|
pub(crate) fn init() -> Config {
|
||||||
let file_contents = match fs::read_to_string("config.json") {
|
let file_contents = match fs::read_to_string("config.json") {
|
||||||
Ok(data) => data,
|
Ok(data) => data,
|
||||||
Err(e) => panic!("ERROR: config.json could not be read:\n\n{:#?}", e),
|
Err(e) => panic!("ERROR: config.json could not be read:\n\n{:#?}", e),
|
||||||
|
@ -77,6 +81,81 @@ impl Config {
|
||||||
|
|
||||||
return config_parse;
|
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 {
|
||||||
|
id: feed.id,
|
||||||
|
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 {
|
pub_struct!(RedditConfig {
|
||||||
|
@ -85,6 +164,7 @@ pub_struct!(RedditConfig {
|
||||||
});
|
});
|
||||||
|
|
||||||
pub_struct!(FeedSetting {
|
pub_struct!(FeedSetting {
|
||||||
|
id: usize,
|
||||||
feed_url: String,
|
feed_url: String,
|
||||||
communities: FeedCommunities,
|
communities: FeedCommunities,
|
||||||
reddit: FeedRedditSettings,
|
reddit: FeedRedditSettings,
|
||||||
|
@ -112,6 +192,7 @@ pub_struct!(FeedRedditSettings {
|
||||||
|
|
||||||
// Posts structs
|
// Posts structs
|
||||||
pub_struct!(PrevPost {
|
pub_struct!(PrevPost {
|
||||||
|
id: usize,
|
||||||
title: String,
|
title: String,
|
||||||
last_post_url: String,
|
last_post_url: String,
|
||||||
});
|
});
|
||||||
|
@ -177,3 +258,51 @@ pub_struct!(FeedEntry {
|
||||||
date_published: String,
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
249
src/main.rs
249
src/main.rs
|
@ -1,20 +1,19 @@
|
||||||
use chrono::Utc;
|
use chrono::{Utc, DateTime, NaiveTime};
|
||||||
use config::{Config, PrevPost, Secrets, LemmyCommunities};
|
use config::{Config, PrevPost, Secrets, CommunitiesVector};
|
||||||
use lemmy_api_common::{
|
use lemmy_api_common::{
|
||||||
person::{Login, LoginResponse},
|
person::{Login, LoginResponse},
|
||||||
post::{CreatePost, GetPosts, GetPostsResponse},
|
post::{CreatePost, GetPosts, GetPostsResponse},
|
||||||
sensitive::Sensitive, community::{ListCommunities, ListCommunitiesResponse},
|
sensitive::Sensitive,
|
||||||
};
|
};
|
||||||
use lemmy_db_schema::{
|
use lemmy_db_schema::{
|
||||||
newtypes::{CommunityId, LanguageId},
|
|
||||||
ListingType, SortType,
|
ListingType, SortType,
|
||||||
};
|
};
|
||||||
use once_cell::sync::Lazy;
|
use once_cell::sync::Lazy;
|
||||||
use reqwest::{blocking::Client, StatusCode};
|
use reqwest::{blocking::Client, StatusCode};
|
||||||
use std::{thread::sleep, time};
|
use tui::{backend::{CrosstermBackend, Backend}, Terminal, widgets::{Block, Borders, Cell, Row, Table, Paragraph, Wrap}, layout::{Layout, Constraint, Direction, Alignment}, Frame, style::{Style, Modifier, Color}, text::{Span, Spans}};
|
||||||
use url::Url;
|
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, vec};
|
||||||
|
|
||||||
use crate::config::FeedData;
|
|
||||||
mod config;
|
mod config;
|
||||||
|
|
||||||
pub static CLIENT: Lazy<Client> = Lazy::new(|| {
|
pub static CLIENT: Lazy<Client> = Lazy::new(|| {
|
||||||
|
@ -32,16 +31,18 @@ struct Bot {
|
||||||
post_history: Vec<PrevPost>,
|
post_history: Vec<PrevPost>,
|
||||||
community_ids: CommunitiesVector,
|
community_ids: CommunitiesVector,
|
||||||
auth: Sensitive<String>,
|
auth: Sensitive<String>,
|
||||||
|
start_time: DateTime<Utc>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Bot {
|
impl Bot {
|
||||||
pub(crate) fn new() -> Bot {
|
pub(crate) fn new() -> Bot {
|
||||||
Bot {
|
Bot {
|
||||||
secrets: Secrets::load(),
|
secrets: Secrets::init(),
|
||||||
config: Config::load(),
|
config: Config::init(),
|
||||||
post_history: PrevPost::load(),
|
post_history: PrevPost::load(),
|
||||||
community_ids: CommunitiesVector::new(),
|
community_ids: CommunitiesVector::new(),
|
||||||
auth: Sensitive::new("".to_string()),
|
auth: Sensitive::new("".to_string()),
|
||||||
|
start_time: Utc::now(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -75,6 +76,31 @@ impl Bot {
|
||||||
.send()
|
.send()
|
||||||
.unwrap();
|
.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());
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn idle(&self) {
|
||||||
|
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 {
|
fn list_posts(auth: &Sensitive<String>, base: String) -> GetPostsResponse {
|
||||||
|
@ -96,143 +122,106 @@ fn list_posts(auth: &Sensitive<String>, base: String) -> GetPostsResponse {
|
||||||
return serde_json::from_str(&res).unwrap();
|
return serde_json::from_str(&res).unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
struct CommunitiesVector {
|
fn run_bot(mut terminal: Terminal<CrosstermBackend<&io::Stdout>>) {
|
||||||
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
|
// Get all needed auth tokens at the start
|
||||||
let mut old = Utc::now().time();
|
let mut old = Utc::now().time();
|
||||||
let mut this = Bot::new();
|
let mut this = Bot::new();
|
||||||
println!("{}", this.secrets.lemmy.username);
|
|
||||||
this.login();
|
this.login();
|
||||||
this.community_ids.load(&this.auth, &this.config.instance);
|
this.community_ids.load(&this.auth, &this.config.instance);
|
||||||
|
|
||||||
// Create empty eTag list
|
|
||||||
println!("TODO: Etag list");
|
|
||||||
|
|
||||||
// Enter a loop (not for debugging)
|
// Enter a loop (not for debugging)
|
||||||
loop {
|
loop {
|
||||||
let start = Utc::now();
|
this.run_once(old);
|
||||||
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) {
|
// Update UI
|
||||||
old = start.time();
|
terminal.draw(|f| {
|
||||||
this.config = Config::load();
|
ui(f, &this);
|
||||||
this.community_ids.load(&this.auth, &this.config.instance);
|
}).unwrap();
|
||||||
|
|
||||||
|
thread::sleep(Duration::from_secs(5));
|
||||||
|
|
||||||
|
disable_raw_mode().unwrap();
|
||||||
|
execute!(
|
||||||
|
terminal.backend_mut(),
|
||||||
|
LeaveAlternateScreen,
|
||||||
|
DisableMouseCapture
|
||||||
|
).unwrap();
|
||||||
|
terminal.show_cursor().unwrap();
|
||||||
|
|
||||||
|
this.idle();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Start the polling process
|
fn ui<B: Backend>(f: &mut Frame<B>, state: &Bot) {
|
||||||
// Get all feed URLs (use cache)
|
let chunks = Layout::default()
|
||||||
let mut post_queue: Vec<CreatePost> = vec![];
|
.direction(Direction::Vertical)
|
||||||
this.config.feeds.iter().for_each(|feed| {
|
.margin(1)
|
||||||
let res = CLIENT
|
.constraints(
|
||||||
.get(feed.feed_url.clone())
|
[
|
||||||
.send()
|
Constraint::Percentage(15),
|
||||||
.unwrap()
|
Constraint::Percentage(90),
|
||||||
.text()
|
].as_ref()
|
||||||
.unwrap();
|
)
|
||||||
let data: FeedData = serde_json::from_str(&res).unwrap();
|
.split(f.size());
|
||||||
|
|
||||||
let mut prev_post_idx: Option<usize> = None;
|
// Account Infos
|
||||||
let mut do_post = true;
|
|
||||||
this.post_history
|
let account_info = vec![
|
||||||
|
Row::new(vec![
|
||||||
|
Cell::from(format!("Lemmy Username: {} |", state.secrets.lemmy.username)),
|
||||||
|
Cell::from(format!("Lemmy Instance: {} |", state.config.instance))
|
||||||
|
])
|
||||||
|
];
|
||||||
|
|
||||||
|
|
||||||
|
let block = Table::new(account_info)
|
||||||
|
.block(Block::default().title("Account info").borders(Borders::ALL))
|
||||||
|
.widths(&[
|
||||||
|
Constraint::Percentage(15),
|
||||||
|
Constraint::Percentage(20),
|
||||||
|
Constraint::Percentage(20),
|
||||||
|
Constraint::Percentage(20),
|
||||||
|
Constraint::Percentage(20),
|
||||||
|
]);
|
||||||
|
f.render_widget(block, chunks[0]);
|
||||||
|
|
||||||
|
|
||||||
|
// Post Table
|
||||||
|
|
||||||
|
let selected_style = Style::default().add_modifier(Modifier::REVERSED);
|
||||||
|
let normal_style = Style::default().bg(Color::Blue);
|
||||||
|
let header_cells = ["Series", "Community", "Reddit", "Discord", "Last Post"]
|
||||||
.iter()
|
.iter()
|
||||||
.enumerate()
|
.map(|h| Cell::from(*h).style(Style::default().fg(Color::Red)));
|
||||||
.for_each(|(idx, post)| {
|
let header = Row::new(header_cells)
|
||||||
if &post.last_post_url == &data.items[0].url {
|
.style(normal_style)
|
||||||
do_post = false;
|
.height(1)
|
||||||
} else if &post.title == &data.title {
|
.bottom_margin(1);
|
||||||
prev_post_idx = Some(idx);
|
let rows = state.post_history.iter().map(|post| {
|
||||||
}
|
let config = &state.config.feeds[post.id];
|
||||||
|
Row::new([Cell::from(post.title.clone()), Cell::from(format!("{}", config.communities.chapter)), Cell::from(format!("{}", config.reddit.enabled as u8)), Cell::from("0"), Cell::from(post.last_post_url.clone())]).height(1 as u16).bottom_margin(1)
|
||||||
});
|
});
|
||||||
|
|
||||||
if do_post {
|
let t = Table::new(rows)
|
||||||
let item = &data.items[0];
|
.header(header)
|
||||||
let new_post = CreatePost {
|
.block(Block::default().borders(Borders::ALL).title("Table"))
|
||||||
name: item.title.clone(),
|
.highlight_style(selected_style)
|
||||||
community_id: this.community_ids.find(&feed.communities.chapter),
|
.highlight_symbol(">> ")
|
||||||
url: Some(Url::parse(&item.url).unwrap()),
|
.widths(&[
|
||||||
body: Some(
|
Constraint::Percentage(30),
|
||||||
"[Reddit](https://reddit.com)\n\n[Discord](https://discord.com)".into(),
|
Constraint::Percentage(10),
|
||||||
),
|
Constraint::Percentage(5),
|
||||||
honeypot: None,
|
Constraint::Percentage(5),
|
||||||
nsfw: Some(false),
|
Constraint::Percentage(50),
|
||||||
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(),
|
f.render_widget(t, chunks[1]);
|
||||||
};
|
|
||||||
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);
|
fn main() -> Result<(), io::Error> {
|
||||||
post_queue.iter().for_each(|post| {
|
let stdout = io::stdout();
|
||||||
println!("Posting: {}", post.name);
|
let backend = CrosstermBackend::new(&stdout);
|
||||||
this.post(post.clone());
|
let terminal = Terminal::new(backend)?;
|
||||||
});
|
|
||||||
|
|
||||||
while Utc::now().time() - start.time() < chrono::Duration::seconds(60) {
|
run_bot(terminal);
|
||||||
sleep(time::Duration::from_secs(10));
|
Ok(())
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue