html terminal
Diffstat (limited to 'src/webhook.rs')
| -rw-r--r-- | src/webhook.rs | 310 |
1 files changed, 172 insertions, 138 deletions
diff --git a/src/webhook.rs b/src/webhook.rs index 275cdf9..6412a8f 100644 --- a/src/webhook.rs +++ b/src/webhook.rs @@ -1,84 +1,147 @@ -use std::{ - sync::Arc, - time::{Duration, Instant}, -}; +use poise::serenity_prelude::Webhook as RealHook; +use serenity::{builder::ExecuteWebhook, http::Http, json}; +use std::convert::AsRef; +use std::sync::{Arc, Mutex}; +use std::time::{Duration, Instant}; +use tokio::sync::broadcast::{self, error::TryRecvError}; -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<'a> { + pub skipped: broadcast::Sender<String>, + pub skip: Arc<Mutex<u8>>, + inner: RealHook, + http: &'a Http, } -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!(); - } +impl<'a> Webhook<'a> { + pub async fn new(http: &'a impl AsRef<Http>, url: &str) -> Webhook<'a> { + Self { + inner: RealHook::from_url(http, url).await.unwrap(), + http: http.as_ref(), + skip: Arc::new(Mutex::new(0)), + skipped: broadcast::channel(16).0, + } + } + + async fn send<F>(&self, block: F) + where + for<'b> F: FnOnce(&'b mut ExecuteWebhook<'a>) -> &'b mut ExecuteWebhook<'a>, + { + let mut execute_webhook = ExecuteWebhook::default(); + block(&mut execute_webhook); + + let map = json::hashmap_to_json_map(execute_webhook.0); + if let Err(e) = self + .http + .as_ref() + .execute_webhook( + self.inner.id.0, + self.inner.token.as_ref().unwrap(), + false, + &map, + ) + .await + { + println!("sending {map:#?} got error {e}."); + }; + } + + async fn send_message(&self, username: &str, content: &str) { + self.send(|m| m.username(username).content(content)).await; + } + + pub async fn link(&mut self, mut stdout: broadcast::Receiver<String>) { + 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 > 1 || feed.len() > 15 { + last.take(); + self.flush::<MindustryStyle>(feed).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; + continue; + } + }, + Ok(m) => { + let mut lock = self.skip.lock().unwrap(); + if *lock > 0 { + *lock -= 1; + input!("{m} < skipped"); + self.skipped.send(m).unwrap(); + continue; } + drop(lock); + for line in m.lines() { + let line = line.to_string(); + input!("{line}"); + feed.push(line); + } + last = Some(now); } - async_std::task::sleep(Duration::from_millis(20)).await; } - })) + async_std::task::sleep(Duration::from_millis(20)).await; + } } - pub fn running(&self) -> bool { - !self.0.is_finished() + pub async fn flush<Style: OutputStyle>(&self, feed: Vec<String>) { + 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() { + let line: String = Style::fix(line); + if let Some((name, msg)) = Style::split(&line) { + if let Some(n) = current.as_ref() { + if n == &name { + message.madd_panic(&msg); + continue; + } else { + let message = message.take().unwrap(); + self.send_message(n, &message).await; + current.take(); + } + } + current = Some(name.to_owned()); + message = Some(msg.to_owned()); + // interrupt + if let Some(msg) = unnamed.take() { + self.send_message("server", &msg).await; + } + continue; + } + unnamed.madd(line); + } + // finish + if let Some(n) = current.as_ref() { + let message = message.take().unwrap(); + self.send_message(n, &message).await; + } + if let Some(msg) = unnamed.as_ref() { + self.send_message("server", msg).await; + } } } /// 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 fix(raw_line: String) -> String { + raw_line + } + /// get the user and the content (none for no user) fn split(line: &str) -> Option<(String, String)>; } @@ -92,92 +155,34 @@ macro_rules! s { } 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 s!(line, [' ', '\t']) || s!(line, "at") { + return None; + } + if line.chars().filter(|x| x == &':').count() == 1 { + if let Some((u, c)) = line.split_once(':') { + let u = unify(u).trim_start_matches('<').trim().to_owned(); + let c = unify(c).trim_end_matches('>').trim().to_owned(); + if !(u.is_empty() || c.is_empty()) { + return Some((u, c)); } } } 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())); + let prefix = if line.contains("disconnected") { + "left" + } else { + "joined" + }; + return Some((unify(player).trim().to_owned(), prefix.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() @@ -205,3 +210,32 @@ impl Madd for Option<String> { } } } + +#[test] +fn style() { + macro_rules! test_line { + ($line:expr) => { + let line = $line.to_string(); // no fixing done! + let got = MindustryStyle::split(&line); + assert!(got == None, "got {got:?}, expected None"); + }; + ($line:expr, $name: expr, $content: expr) => { + let line = $line.to_string(); + let got = MindustryStyle::split(&line); + assert!( + got == Some(($name.into(), $content.into())), + "got {got:?}, expected ({}, {})", + $name, + $content + ); + }; + } + //unnamed + test_line!("undefined"); + test_line!("Lost command socket connection: localhost/127.0.0.1:6859"); + //named + test_line!("abc: hi", "abc", "hi"); + test_line!("<a: /help>", "a", "/help"); + test_line!("a has connected. [abc==]", "a", "joined"); + test_line!("a has disconnected. [abc==] (closed)", "a", "left"); +} |