Static Example TUI

This commit is contained in:
Neshura 2023-06-21 21:29:14 +02:00
parent 4e9a2075f5
commit 5e95d2b62c
Signed by: Neshura
GPG key ID: B6983AAA6B9A7A6C
3 changed files with 261 additions and 92 deletions

96
Cargo.lock generated
View file

@ -93,6 +93,7 @@ name = "ascendance-of-a-bookworm-bot"
version = "1.1.0"
dependencies = [
"chrono",
"crossterm 0.26.1",
"lemmy_api_common",
"lemmy_db_schema",
"once_cell",
@ -101,6 +102,7 @@ dependencies = [
"serde_derive",
"serde_json",
"strum_macros 0.25.0",
"tui",
"url",
]
@ -203,6 +205,12 @@ version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "89b2fd2a0dcf38d7971e2194b6b6eebab45ae01067456a7fd93d5547a61b70be"
[[package]]
name = "cassowary"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "df8670b8c7b9dae1793364eafadf7239c40d669904660c5960d74cfd80b46a53"
[[package]]
name = "cc"
version = "1.0.79"
@ -266,6 +274,47 @@ dependencies = [
"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]]
name = "encoding_rs"
version = "0.8.32"
@ -772,6 +821,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "927a765cd3fc26206e66b296465fa9d3e5ab003e651c1b3c060e7956d96b19d2"
dependencies = [
"libc",
"log",
"wasi 0.11.0+wasi-snapshot-preview1",
"windows-sys 0.48.0",
]
@ -1164,6 +1214,27 @@ dependencies = [
"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]]
name = "signal-hook-registry"
version = "1.4.1"
@ -1478,6 +1549,19 @@ version = "0.2.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
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]]
name = "typed-builder"
version = "0.10.0"
@ -1510,6 +1594,18 @@ dependencies = [
"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]]
name = "url"
version = "2.4.0"

View file

@ -7,6 +7,7 @@ edition = "2021"
[dependencies]
chrono = "0.4.26"
crossterm = "0.26.1"
lemmy_api_common = "0.17.4"
lemmy_db_schema = "0.17.4"
once_cell = "1.18.0"
@ -15,4 +16,5 @@ serde = "1.0.164"
serde_derive = "1.0.164"
serde_json = "1.0.97"
strum_macros = "0.25.0"
tui = "0.19.0"
url = "2.4.0"

View file

@ -11,7 +11,9 @@ use lemmy_db_schema::{
};
use once_cell::sync::Lazy;
use reqwest::{blocking::Client, StatusCode};
use std::{thread::sleep, time};
use tui::{backend::{CrosstermBackend, Backend}, Terminal, widgets::{Widget, 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;
@ -144,95 +146,164 @@ impl CommunitiesVector {
}
}
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);
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);
// 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()
);
// 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);
}
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();
// 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);
}
});
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
});
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());
});
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));
}
}
while Utc::now().time() - start.time() < chrono::Duration::seconds(60) {
sleep(time::Duration::from_secs(10));
}
}
}
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());
let block = Block::default()
.title("Block")
.borders(Borders::ALL);
f.render_widget(block, chunks[0]);
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()?;
Ok(())
//run_bot();
}