html terminal
Diffstat (limited to 'src/webhook.rs')
-rw-r--r--src/webhook.rs310
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");
+}