aob-lemmy-bot/src/main.rs

210 lines
6.3 KiB
Rust
Raw Normal View History

2023-06-22 20:08:10 +00:00
use chrono::{Utc, DateTime, NaiveTime};
use config::{Config, PrevPost, Secrets, CommunitiesVector};
use lemmy_api_common::{
person::{Login, LoginResponse},
post::{CreatePost, GetPosts, GetPostsResponse},
2023-06-22 20:08:10 +00:00
sensitive::Sensitive,
};
use lemmy_db_schema::{
ListingType, SortType,
};
use once_cell::sync::Lazy;
use reqwest::{blocking::Client, StatusCode};
2023-06-22 20:08:10 +00:00
use tui::{backend::{CrosstermBackend, Backend}, Terminal, widgets::{Block, Borders, Cell, Row, Table}, layout::{Layout, Constraint, Direction}, Frame, style::{Style, Modifier, Color}};
2023-06-21 19:29:14 +00:00
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};
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>,
2023-06-19 17:21:28 +00:00
community_ids: CommunitiesVector,
auth: Sensitive<String>,
2023-06-22 20:08:10 +00:00
start_time: DateTime<Utc>,
}
impl Bot {
pub(crate) fn new() -> Bot {
Bot {
2023-06-22 20:08:10 +00:00
secrets: Secrets::init(),
config: Config::init(),
post_history: PrevPost::load(),
2023-06-19 17:21:28 +00:00
community_ids: CommunitiesVector::new(),
auth: Sensitive::new("".to_string()),
2023-06-22 20:08:10 +00:00
start_time: Utc::now(),
}
}
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
2023-06-19 20:10:28 +00:00
.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
2023-06-19 20:10:28 +00:00
.post(self.config.instance.clone() + "/api/v3/post")
.json(&post_data)
.send()
.unwrap();
}
2023-06-22 20:08:10 +00:00
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));
}
}
}
2023-06-19 20:10:28 +00:00
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
2023-06-19 20:10:28 +00:00
.get(base + "/api/v3/post/list")
.query(&params)
.send()
.unwrap()
.text()
.unwrap();
return serde_json::from_str(&res).unwrap();
}
2023-06-21 19:29:14 +00:00
fn run_bot() {
2023-06-22 20:08:10 +00:00
// 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 {
this.run_once(old);
2023-06-21 19:29:14 +00:00
}
}
2023-06-21 19:29:14 +00:00
fn ui<B: Backend>(f: &mut Frame<B>) {
let chunks = Layout::default()
.direction(Direction::Vertical)
.margin(1)
.constraints(
[
Constraint::Percentage(10),
Constraint::Percentage(90),
].as_ref()
)
.split(f.size());
2023-06-21 19:29:14 +00:00
let block = Block::default()
.title("Block")
.borders(Borders::ALL);
f.render_widget(block, chunks[0]);
2023-06-21 19:29:14 +00:00
let selected_style = Style::default().add_modifier(Modifier::REVERSED);
let normal_style = Style::default().bg(Color::Blue);
let header_cells = ["Series", "Community", "Last Post"]
.iter()
.map(|h| Cell::from(*h).style(Style::default().fg(Color::Red)));
let header = Row::new(header_cells)
.style(normal_style)
.height(1)
.bottom_margin(1);
let rows = [
Row::new([Cell::from("a1"), Cell::from("a2"), Cell::from("a3")]).height(1 as u16).bottom_margin(1),
Row::new([Cell::from("b1"), Cell::from("b2"), Cell::from("b3")]).height(1 as u16).bottom_margin(1),
Row::new([Cell::from("unicorn"), Cell::from("c2"), Cell::from("c3")]).height(2 as u16).bottom_margin(1)
];
let t = Table::new(rows)
.header(header)
.block(Block::default().borders(Borders::ALL).title("Table"))
.highlight_style(selected_style)
.highlight_symbol(">> ")
.widths(&[
Constraint::Percentage(50),
Constraint::Length(30),
Constraint::Min(10),
]);
f.render_widget(t, chunks[1]);
}
fn main() -> Result<(), io::Error> {
let stdout = io::stdout();
let backend = CrosstermBackend::new(&stdout);
let mut terminal = Terminal::new(backend)?;
terminal.draw(|f| {
ui(f);
})?;
thread::sleep(Duration::from_secs(5));
disable_raw_mode()?;
execute!(
terminal.backend_mut(),
LeaveAlternateScreen,
DisableMouseCapture
)?;
terminal.show_cursor()?;
2023-06-22 20:08:10 +00:00
run_bot();
2023-06-21 19:29:14 +00:00
Ok(())
}