html terminal
Diffstat (limited to 'src/webhook.rs')
| -rw-r--r-- | src/webhook.rs | 207 |
1 files changed, 207 insertions, 0 deletions
diff --git a/src/webhook.rs b/src/webhook.rs new file mode 100644 index 0000000..275cdf9 --- /dev/null +++ b/src/webhook.rs @@ -0,0 +1,207 @@ +use std::{ + sync::Arc, + time::{Duration, Instant}, +}; + +use tokio::{ + sync::broadcast::{self, error::TryRecvError}, + task::JoinHandle, +}; +use webhook::{client::WebhookClient, models::Message}; + +macro_rules! send { + ($client:ident, $block:expr) => {{ + let client = $client.clone(); + let mut m = Message::new(); + let mut bloc: Box<dyn FnMut(&mut Message) -> &mut Message> = Box::new($block); + bloc(&mut m); + m.allow_mentions(None, None, None, false); + tokio::spawn(async move { + client.send_message(&m).await.unwrap(); + }); + // handle goes out of scope, detatched + }}; +} + +pub struct Webhook(JoinHandle<()>); +impl Webhook { + pub async fn new(mut stdout: broadcast::Receiver<String>, url: &str) -> Self { + let client = Arc::new(WebhookClient::new(url)); + Self(tokio::spawn(async move { + define_print!("webhook"); + let mut last: Option<Instant> = None; + let mut feed: Vec<String> = vec![]; + loop { + let out = stdout.try_recv(); + let now = Instant::now(); + match out { + Err(e) => match e { + TryRecvError::Closed => fail!("closed"), + TryRecvError::Lagged(_) => continue, + TryRecvError::Empty => { + if let Some(earlier) = last { + let since = now.duration_since(earlier).as_secs(); + if since > 2 || feed.len() > 15 { + last.take(); + flush::<MindustryStyle>(feed, &client).await; + feed = vec![]; + flush!(); + } + } + async_std::task::sleep(Duration::from_millis(20)).await; + continue; + } + }, + Ok(m) => { + for line in m.lines() { + feed.push(line.to_string()); + input!("{line}"); + } + last = Some(now); + } + } + async_std::task::sleep(Duration::from_millis(20)).await; + } + })) + } + + pub fn running(&self) -> bool { + !self.0.is_finished() + } +} + +/// functions ordered by call order +pub trait OutputStyle { + /// first step + fn fix(raw_line: String) -> String; + /// ignore completely? + fn ignore(line: &str) -> bool; + /// is there no user + fn unnamed(line: &str) -> bool; + /// get the user and the content + fn split(line: &str) -> Option<(String, String)>; +} + +macro_rules! s { + ($line:expr, $e:ident) => { + $line.starts_with(stringify!($e)) + }; + ($line:expr, $e:expr) => { + $line.starts_with($e) + }; +} +pub struct MindustryStyle; +impl OutputStyle for MindustryStyle { + fn fix(raw_line: String) -> String { + raw_line.split(' ').skip(3).collect::<Vec<&str>>().join(" ") + } + fn ignore(line: &str) -> bool { + line.trim().is_empty() + || line.contains("requested trace info") + || s!(line, Server) + || s!(line, Connected) + || s!(line, PLAGUE) + || s!(line, "1 mods loaded") + } + + fn unnamed(line: &str) -> bool { + s!(line, [' ', '\t']) + } + + fn split(line: &str) -> Option<(String, String)> { + if let Some(n) = line.find(':') { + if n < 12 { + if let Some((u, c)) = line.split_once(':') { + return Some(( + unify(u).trim_start_matches('<').to_owned(), + unify(c).trim_end_matches('>').to_owned(), + )); + } + } + } + if let Some(index) = line.find("has") { + if line.contains("connected") { + let player = &line[..index]; + return Some((unify(player).trim().to_owned(), "joined".to_owned())); + } + } + None + } +} + +async fn flush<Style: OutputStyle>(feed: Vec<String>, client: &Arc<WebhookClient>) { + let mut current: Option<String> = None; + let mut message: Option<String> = None; + let mut unnamed: Option<String> = None; + + // this code is very game dependent + for line in feed.into_iter() { + // [0-0-0-0 0-0-0-0] [i] ... -> ... + let line: String = Style::fix(line); + if Style::ignore(&line) { + continue; + } + if Style::unnamed(&line) { + unnamed.madd(line); + continue; + } + + if let Some((name, msg)) = Style::split(&line) { + if !(msg.is_empty() || name.is_empty()) { + if let Some(n) = current.as_ref() { + if n == &name { + message.madd_panic(&msg); + continue; + } else { + let message = message.take().unwrap(); + send!(client, |m| m.content(&message).username(n)); + current.take(); + } + } + current = Some(name.to_owned()); + message = Some(msg.to_owned()); + // interrupt + if let Some(msg) = unnamed.take() { + send!(client, |m| m.username("server").content(&msg)); + } + continue; + } + } + unnamed.madd(line); + } + // leftovers + if let Some(n) = current.as_ref() { + let message = message.take().unwrap(); + send!(client, |m| m.content(&message).username(n)); + } + if let Some(msg) = unnamed.as_ref() { + send!(client, |m| m.username("server").content(msg)); + } +} +/// latin > extended a > kill +fn unify(s: &str) -> String { + s.chars() + .map(|c| if (c as u32) < 384 { c } else { ' ' }) + .collect() +} + +trait Madd { + fn madd(&mut self, line: String); + fn madd_panic(&mut self, line: &str); +} + +// cant impl addassign because no impl for other types because +impl Madd for Option<String> { + fn madd(&mut self, line: String) { + match self.take() { + Some(x) => *self = Some(x + "\n" + &line), + None => *self = Some(line), + } + } + fn madd_panic(&mut self, line: &str) { + match self.take() { + Some(x) => *self = Some(x + "\n" + line), + None => unreachable!(), + } + } +} |