html terminal
admin add commands
bendn 2023-06-17
parent bd452ad · commit d63233b
-rw-r--r--.gitignore1
-rw-r--r--Cargo.lock29
-rw-r--r--Cargo.toml11
-rw-r--r--build.rs57
-rw-r--r--html-src/index.html (renamed from html/index.html)0
-rw-r--r--html-src/panel.html (renamed from html/panel.html)0
-rw-r--r--src/bot/mod.rs130
-rw-r--r--src/bot/player.rs21
-rw-r--r--src/main.rs2
-rw-r--r--src/server.rs47
-rw-r--r--src/webhook.rs68
-rw-r--r--src/websocket.rs108
12 files changed, 306 insertions, 168 deletions
diff --git a/.gitignore b/.gitignore
index df2c3c5..0065b4b 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,2 +1,3 @@
/target
*token
+html/
diff --git a/Cargo.lock b/Cargo.lock
index 53f5511..b1bdf0c 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -227,9 +227,6 @@ dependencies = [
"pin-project-lite",
"rustversion",
"serde",
- "serde_json",
- "serde_path_to_error",
- "serde_urlencoded",
"sha1",
"sync_wrapper",
"tokio",
@@ -494,6 +491,12 @@ dependencies = [
]
[[package]]
+name = "either"
+version = "1.8.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7fcaabb2fef8c910e7f4c7ce9f67a1283a1715879a7c230ca9d6d1ae31f16d91"
+
+[[package]]
name = "encoding_rs"
version = "0.8.32"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -887,6 +890,15 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "12b6ee2129af8d4fb011108c73d99a1b83a85977f23b82460c0ae2e25bb4b57f"
[[package]]
+name = "itertools"
+version = "0.10.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473"
+dependencies = [
+ "either",
+]
+
+[[package]]
name = "itoa"
version = "1.0.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1079,6 +1091,7 @@ dependencies = [
"axum",
"futures",
"futures-util",
+ "itertools",
"minify-html",
"minify-js",
"paste",
@@ -1506,15 +1519,6 @@ dependencies = [
]
[[package]]
-name = "serde_path_to_error"
-version = "0.1.11"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f7f05c1d5476066defcdfacce1f52fc3cae3af1d3089727100c02ae92e5abbe0"
-dependencies = [
- "serde",
-]
-
-[[package]]
name = "serde_urlencoded"
version = "0.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1826,7 +1830,6 @@ dependencies = [
"tokio",
"tower-layer",
"tower-service",
- "tracing",
]
[[package]]
diff --git a/Cargo.toml b/Cargo.toml
index 78cba10..926f69a 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -13,9 +13,8 @@ axum = { version = "0.6.18", features = [
"tokio",
"http1",
"matched-path",
-] }
+], default-features = false }
futures = "0.3.28"
-minify-html = "0.11.1"
paste = "1.0.12"
tokio = { version = "1.28.2", features = [
"macros",
@@ -24,24 +23,26 @@ tokio = { version = "1.28.2", features = [
"rt-multi-thread",
"process",
"parking_lot",
-] }
+], default-features = false }
tokio-stream = "0.1.14"
futures-util = "0.3.28"
strip-ansi-escapes = "0.1.1"
serenity = { version = "0.11.5", features = [
"builder",
"client",
- # "framework",
"utils",
"rustls_backend",
"gateway",
- # "standard_framework",
], default-features = false }
poise = "0.5.5"
anyhow = "1.0.71"
regex = { version = "1.8.4", features = ["std"], default-features = false }
minify-js = "0.4.3"
+itertools = "0.10.5"
[profile.release]
lto = true
strip = true
+
+[build-dependencies]
+minify-html = "0.11.1"
diff --git a/build.rs b/build.rs
new file mode 100644
index 0000000..77d8311
--- /dev/null
+++ b/build.rs
@@ -0,0 +1,57 @@
+#![feature(utf8_chunks)]
+use std::fs;
+use std::io::prelude::*;
+use std::path::Path;
+
+use minify_html::{minify, Cfg};
+
+/// like [String::from_utf8_lossy] but instead of being lossy it panics
+pub fn from_utf8(v: &[u8]) -> &str {
+ let mut iter = std::str::Utf8Chunks::new(v);
+ if let Some(chunk) = iter.next() {
+ let valid = chunk.valid();
+ if chunk.invalid().is_empty() {
+ debug_assert_eq!(valid.len(), v.len());
+ return valid;
+ }
+ } else {
+ return "";
+ };
+ unreachable!("invalid utf8")
+}
+
+pub fn process(input: impl AsRef<Path>) -> std::io::Result<()> {
+ let mut f = fs::File::create(dbg!(Path::new("html").join(input.as_ref()))).unwrap();
+ let mut buf = vec![];
+ fs::File::open(Path::new("html-src").join(input.as_ref()))?.read_to_end(&mut buf)?;
+ let minified = minify(
+ &buf,
+ &Cfg {
+ minify_js: true,
+ minify_css: true,
+ ..Default::default()
+ },
+ );
+ let minified = from_utf8(&minified);
+ let minified = minified.replace(
+ "ws://localhost:4001/connect/",
+ &format!(
+ "{}",
+ std::env::var("URL").unwrap_or("ws://localhost:4001/connect/".to_string())
+ ),
+ );
+ f.write_all(minified.as_bytes())
+}
+
+fn main() -> std::io::Result<()> {
+ if !Path::new("html").exists() {
+ std::fs::create_dir("html")?;
+ }
+
+ for path in fs::read_dir("html-src")? {
+ process(path.unwrap().path().file_name().unwrap())?;
+ }
+ println!("cargo:rerun-if-changed=html-src/");
+ println!("cargo:rerun-if-changed=build.rs");
+ Ok(())
+}
diff --git a/html/index.html b/html-src/index.html
index bbf9f63..bbf9f63 100644
--- a/html/index.html
+++ b/html-src/index.html
diff --git a/html/panel.html b/html-src/panel.html
index 982b465..982b465 100644
--- a/html/panel.html
+++ b/html-src/panel.html
diff --git a/src/bot/mod.rs b/src/bot/mod.rs
index 760a290..7386f2b 100644
--- a/src/bot/mod.rs
+++ b/src/bot/mod.rs
@@ -2,15 +2,17 @@ mod maps;
mod player;
use crate::webhook::Webhook;
-use anyhow::Result;
+use anyhow::{anyhow, Result};
+use itertools::Itertools;
use maps::Maps;
use minify_js::TopLevelMode;
use player::Players;
use regex::Regex;
-use serenity::http::Http;
+use serenity::http::{CacheHttp, Http};
use serenity::prelude::*;
use std::fs::read_to_string;
-use std::sync::{Arc, Mutex, OnceLock};
+use std::str::FromStr;
+use std::sync::{Arc, LazyLock, Mutex, OnceLock};
use tokio::sync::broadcast;
pub struct Data {
@@ -32,6 +34,14 @@ macro_rules! send_ctx {
};
}
+#[cfg(not(debug_assertions))]
+const PFX: &'static str = ">";
+#[cfg(debug_assertions)]
+const PFX: &'static str = "-";
+
+const SUCCESS: (u8, u8, u8) = (34, 139, 34);
+const FAIL: (u8, u8, u8) = (255, 69, 0);
+
pub struct Bot;
impl Bot {
pub async fn spawn(stdout: broadcast::Receiver<String>, stdin: broadcast::Sender<String>) {
@@ -43,15 +53,21 @@ impl Bot {
say(),
ban(),
unban(),
+ add_admin(),
+ remove_admin(),
js(),
maps(),
players(),
+ status(),
start(),
end(),
help(),
],
prefix_options: poise::PrefixFrameworkOptions {
- prefix: Some(">".to_string()),
+ edit_tracker: Some(poise::EditTracker::for_timespan(
+ std::time::Duration::from_secs(2 * 60),
+ )),
+ prefix: Some(PFX.to_string()),
..Default::default()
},
..Default::default()
@@ -169,10 +185,10 @@ async fn js(
#[rest]
script: String,
) -> Result<()> {
- static RE: OnceLock<Regex> = OnceLock::new();
+ static RE: LazyLock<Regex> =
+ LazyLock::new(|| Regex::new(r#"```(js|javascript)?([^`]+)```"#).unwrap());
let _ = ctx.channel_id().start_typing(&ctx.serenity_context().http);
- let re = RE.get_or_init(|| Regex::new(r#"```(js|javascript)?([^`]+)```"#).unwrap());
- let mat = re
+ let mat = RE
.captures(&script)
.ok_or(anyhow::anyhow!(r#"no code found (use \`\`\`js...\`\`\`."#))?;
let script = mat.get(2).unwrap().as_str();
@@ -207,7 +223,7 @@ fn strip_colors(from: &str) -> String {
#[poise::command(
slash_command,
required_permissions = "USE_SLASH_COMMANDS",
- category = "Control"
+ category = "Info"
)]
/// lists the maps.
pub async fn maps(ctx: Context<'_>) -> Result<()> {
@@ -218,37 +234,78 @@ pub async fn maps(ctx: Context<'_>) -> Result<()> {
for (k, v) in maps.iter().enumerate() {
e.field((k + 1).to_string(), v, true);
}
- e.description("map list.").color((34, 139, 34))
+ e.description("map list.").color(SUCCESS)
})
})
.await?;
Ok(())
}
-#[poise::command(
- slash_command,
- required_permissions = "USE_SLASH_COMMANDS",
- category = "Control"
-)]
+#[poise::command(prefix_command, slash_command, category = "Info")]
+/// server status.
+pub async fn status(ctx: Context<'_>) -> Result<()> {
+ let _ = ctx.defer_or_broadcast().await;
+ send_ctx!(ctx, "status")?;
+ let block = tokio::select! {
+ block = get_nextblock() => block,
+ _ = async_std::task::sleep(std::time::Duration::from_secs(5)) =>
+ { poise::send_reply(ctx, |m| m.embed(|e| e.title("server down").color(FAIL))).await?; return Ok(()) },
+ };
+ let (tps, mem, pcount) = block
+ .split('/')
+ .map(|s| u32::from_str(s.trim().split_once(' ').unwrap().0).unwrap())
+ .collect_tuple()
+ .ok_or(anyhow!("couldnt split block"))?;
+ poise::send_reply(ctx, |m| {
+ m.embed(|e| {
+ if pcount > 0 {
+ e.footer(|f| f.text("see /players for player list"));
+ }
+ e.title("server online")
+ .field("tps", tps, true)
+ .field("memory use", mem, true)
+ .field("players", pcount, true)
+ .color(SUCCESS)
+ })
+ })
+ .await?;
+ Ok(())
+}
+
+#[poise::command(slash_command, prefix_command, category = "Info")]
/// lists the currently online players.
pub async fn players(ctx: Context<'_>) -> Result<()> {
let _ = ctx.defer().await;
let players = Players::get_all(&ctx.data().stdin).await.unwrap().clone();
+ let perms = ctx
+ .partial_guild()
+ .await
+ .unwrap()
+ .member_permissions(ctx.http(), ctx.author().id)
+ .await?;
+ // let perms = ctx
+ // .author_member()
+ // .await
+ // .ok_or(anyhow!("couldnt get perms"))?
+ // .permissions(ctx.cache().unwrap())?;
poise::send_reply(ctx, |m| {
m.embed(|e| {
if players.is_empty() {
- return e.title("no players online.").color((255, 69, 0));
+ return e.title("no players online.").color(FAIL);
}
e.fields(players.into_iter().map(|p| {
+ let admins = if p.admin { " [A]" } else { "" };
(
p.name,
- format!("{id}, {ip}", id = p.uuid, ip = p.ip)
- + if p.admin { " [A]" } else { "" },
+ if perms.use_slash_commands() {
+ format!("{id}, {ip}", id = p.uuid, ip = p.ip) + admins
+ } else {
+ admins.to_string()
+ },
true,
)
}));
- e.description("currently online players.")
- .color((255, 165, 0))
+ e.description("currently online players.").color(SUCCESS)
})
})
.await?;
@@ -293,11 +350,44 @@ pub async fn end(
return_next!(ctx)
}
+#[poise::command(slash_command, category = "Configuration")]
+/// make somebody a admin
+pub async fn add_admin(
+ ctx: Context<'_>,
+ #[description = "The player to make admin"]
+ #[autocomplete = "player::autocomplete"]
+ player: String,
+) -> Result<()> {
+ let player = Players::find(&ctx.data().stdin, player)
+ .await
+ .unwrap()
+ .unwrap();
+ send_ctx!(ctx, "admin add {}", player.uuid)?;
+ Ok(())
+}
+
+#[poise::command(slash_command, category = "Configuration")]
+/// remove the admin status
+pub async fn remove_admin(
+ ctx: Context<'_>,
+ #[description = "The player to remove admin status from"]
+ #[autocomplete = "player::autocomplete"]
+ player: String,
+) -> Result<()> {
+ let player = Players::find(&ctx.data().stdin, player)
+ .await
+ .unwrap()
+ .unwrap();
+ send_ctx!(ctx, "admin remove {}", player.uuid)?;
+ Ok(())
+}
+
#[poise::command(
prefix_command,
slash_command,
required_permissions = "USE_SLASH_COMMANDS",
- track_edits
+ track_edits,
+ category = "Info"
)]
/// show help and stuff
pub async fn help(
diff --git a/src/bot/player.rs b/src/bot/player.rs
index 590f9e4..29b534d 100644
--- a/src/bot/player.rs
+++ b/src/bot/player.rs
@@ -2,6 +2,7 @@ use super::{get_nextblock, strip_colors, Context};
use crate::send;
use anyhow::Result;
use futures_util::StreamExt;
+use itertools::Itertools;
use std::net::Ipv4Addr;
use std::str::FromStr;
use std::time::Instant;
@@ -58,17 +59,15 @@ async fn get_players(stdin: &broadcast::Sender<String>) -> Result<Vec<Player>> {
} else if line.is_empty() {
continue;
}
- let split = line.split('/').collect::<Vec<&str>>();
- if split.len() != 3 {
- continue;
- }
- if let Some((admin, name)) = split[0].split_once(' ') {
- players.push(Player {
- admin: admin == "[A]",
- name: strip_colors(name),
- uuid: split[1].to_owned(),
- ip: Ipv4Addr::from_str(split[2]).unwrap(),
- })
+ if let Some((first, uuid, ip)) = line.split('/').collect_tuple() {
+ if let Some((admin, name)) = first.split_once(' ') {
+ players.push(Player {
+ admin: admin == "[A]",
+ name: strip_colors(name),
+ uuid: uuid.to_owned(),
+ ip: Ipv4Addr::from_str(ip).unwrap(),
+ })
+ }
}
}
Ok(players)
diff --git a/src/main.rs b/src/main.rs
index 0510033..ee1fc78 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -1,4 +1,4 @@
-#![feature(utf8_chunks)]
+#![feature(lazy_cell)]
use std::str::FromStr;
diff --git a/src/server.rs b/src/server.rs
index 9f8dbd1..3426530 100644
--- a/src/server.rs
+++ b/src/server.rs
@@ -12,12 +12,7 @@ use axum::{
Router, Server as AxumServer,
};
use futures::sink::SinkExt;
-use minify_html::{minify, Cfg};
-use paste::paste;
-use std::{
- net::SocketAddr,
- sync::{Arc, OnceLock},
-};
+use std::{net::SocketAddr, sync::Arc};
use tokio::sync::broadcast;
pub struct State {
@@ -42,21 +37,14 @@ impl State {
macro_rules! html {
($file:expr) => {
- get(paste!(
- || async {
- static [<$file:upper>]: OnceLock<Vec<u8>> = OnceLock::new();
- Html(from_utf8([<$file:upper>].get_or_init(|| {
- minify(
- include_bytes!(concat!("../html/", stringify!($file), ".html")),
- &Cfg {
- minify_js: true,
- minify_css: true,
- ..Default::default()
- },
- )
- })).replace("ws://localhost:4001/connect/", &format!("{}", std::env::var("URL").unwrap_or("ws://localhost:4001/connect/".to_string()))))
- }
- ))
+ get(|| async {
+ let ret: Html<&'static [u8]> = Html(include_bytes!(concat!(
+ "../html/",
+ stringify!($file),
+ ".html"
+ )));
+ ret
+ })
};
}
@@ -101,21 +89,6 @@ impl Server {
}
}
-/// like [String::from_utf8_lossy] but instead of being lossy it panics
-pub fn from_utf8(v: &[u8]) -> &str {
- let mut iter = std::str::Utf8Chunks::new(v);
- if let Some(chunk) = iter.next() {
- let valid = chunk.valid();
- if chunk.invalid().is_empty() {
- debug_assert_eq!(valid.len(), v.len());
- return valid;
- }
- } else {
- return "";
- };
- unreachable!("invalid utf8")
-}
-
fn matches(id: &str) -> bool {
std::env::var("ID").as_deref().unwrap_or("4") == id
}
@@ -131,6 +104,6 @@ async fn connect_ws(
let _ = s.send(Message::Text("correct id".to_string())).await;
return;
}
- WebSocket::new(socket, state).await.wait().await;
+ tokio::spawn(WebSocket::spawn(socket, state));
})
}
diff --git a/src/webhook.rs b/src/webhook.rs
index 7ebc069..c83e64c 100644
--- a/src/webhook.rs
+++ b/src/webhook.rs
@@ -1,7 +1,9 @@
+use itertools::Itertools;
use poise::serenity_prelude::Webhook as RealHook;
+use regex::Regex;
use serenity::{builder::ExecuteWebhook, http::Http, json};
use std::convert::AsRef;
-use std::sync::{Arc, Mutex};
+use std::sync::{Arc, LazyLock, Mutex};
use std::time::{Duration, Instant};
use tokio::sync::broadcast::{self, error::TryRecvError};
@@ -154,40 +156,52 @@ macro_rules! s {
$line.starts_with($e)
};
}
+
+macro_rules! tern {
+ ($predicate:expr, $true: expr, $false: expr) => {{
+ if $predicate {
+ $true
+ } else {
+ $false
+ }
+ }};
+}
pub struct MindustryStyle;
impl OutputStyle for MindustryStyle {
fn split(line: &str) -> Option<(String, String)> {
- if s!(line, [' ', '\t']) || s!(line, "at") {
+ if s!(line, [' ', '\t']) || s!(line, "at") || s!(line, "Lost command socket connection") {
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((u, c)) = line.split(": ").map(|s| unify(s)).collect_tuple() {
+ let u = u.trim_start_matches('<');
+ let c = c.trim_end_matches('>');
+ if !(u.is_empty() || c.is_empty()) {
+ return Some((u.to_owned(), c.to_owned()));
}
}
- if let Some(index) = line.find("has") {
- if line.contains("connected") {
- let player = &line[..index];
- let prefix = if line.contains("disconnected") {
- "left"
- } else {
- "joined"
- };
- return Some((unify(player).trim().to_owned(), prefix.to_owned()));
- }
+
+ static REGEX: LazyLock<Regex> = LazyLock::new(|| {
+ Regex::new(r#"(.+) has (dis)?connected. \[([a-zA-Z0-9+/]+==)\]"#).unwrap()
+ });
+ if let Some(captures) = REGEX.captures(line) {
+ let player = unify(captures.get(1).unwrap().as_str());
+ let prefix = tern!(captures.get(2).is_some(), "left", "joined");
+ let uuid = captures.get(3).unwrap().as_str();
+ return Some((player, format!("{prefix} ({uuid})")));
}
None
}
}
-/// latin > extended a > kill
fn unify(s: &str) -> String {
s.chars()
- .map(|c| if (c as u32) < 384 { c } else { ' ' })
+ .filter_map(|c| {
+ if c > 'џ' {
+ return None;
+ }
+ Some(c)
+ })
.collect()
}
@@ -237,6 +251,14 @@ fn style() {
//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");
+ test_line!("a has connected. [abc==]", "a", "joined (abc==)");
+ test_line!("a has disconnected. [abc==] (closed)", "a", "left (abc==)");
+ test_line!("a: :o", "a", ":o");
+ test_line!("a:b: :o", "a:b", ":o");
+}
+
+#[test]
+fn test_unify() {
+ assert!(unify("grassྱྊၔ") == "grass");
+ assert!(unify("иди к черту") == "иди к черту")
}
diff --git a/src/websocket.rs b/src/websocket.rs
index 75591d4..df614f4 100644
--- a/src/websocket.rs
+++ b/src/websocket.rs
@@ -7,75 +7,67 @@ use std::{
};
use tokio::{sync::broadcast::error::TryRecvError, task::JoinHandle};
use tokio_stream::StreamExt;
-
pub struct WebSocket(JoinHandle<()>);
impl WebSocket {
- pub async fn new(stream: RealWebSocket, state: Arc<State>) -> Self {
+ pub async fn spawn(stream: RealWebSocket, state: Arc<State>) {
let (mut sender, mut reciever) = futures::stream::StreamExt::split(stream);
let mut stdout = state.stdout_html.subscribe();
- let ws_task = tokio::spawn(async move {
- dummy_print!("websocket");
- let mut last: Option<Instant> = None;
- let mut waiting: usize = 0;
- loop {
- let out = stdout.try_recv();
- let now = Instant::now();
- match out {
- Err(e) => match e {
- TryRecvError::Closed => fail!("closed"),
- TryRecvError::Lagged(_) => continue, // no delay
- _ => {
- if let Some(earlier) = last {
- let since = now.duration_since(earlier).as_millis();
- if since > 200 || waiting > 15 {
- last.take();
- sender.flush().await.unwrap();
- waiting = 0;
- flush!();
- }
+ dummy_print!("websocket");
+ let mut last: Option<Instant> = None;
+ let mut waiting: usize = 0;
+ loop {
+ let out = stdout.try_recv();
+ let now = Instant::now();
+ match out {
+ Err(e) => match e {
+ TryRecvError::Closed => fail!("closed"),
+ TryRecvError::Lagged(_) => continue, // no delay
+ _ => {
+ if let Some(earlier) = last {
+ let since = now.duration_since(earlier).as_millis();
+ if since > 200 || waiting > 15 {
+ last.take();
+ sender.flush().await.unwrap();
+ waiting = 0;
+ flush!();
}
}
- },
- Ok(m) => {
- #[allow(unused_variables)]
- for line in m.lines() {
- input!("{line}");
- if let Err(e) = sender.feed(Message::Text(line.to_owned())).await {
- fail!("{e}");
- };
- waiting += 1;
- }
- last = Some(now);
}
- }
- match tokio::select! {
- next = reciever.next() => next,
- _ = async_std::task::sleep(Duration::from_millis(20)) =>continue,
- } {
- Some(r) => match r {
- Ok(m) => {
- if let Message::Text(m) = m {
- output!("{m}");
- state.stdin.send(m).unwrap();
- }
- }
- #[allow(unused_variables)]
- Err(e) => {
+ },
+ Ok(m) => {
+ #[allow(unused_variables)]
+ for line in m.lines() {
+ input!("{line}");
+ if let Err(e) = sender.feed(Message::Text(line.to_owned())).await {
fail!("{e}");
+ };
+ waiting += 1;
+ }
+ last = Some(now);
+ }
+ }
+ match tokio::select! {
+ next = reciever.next() => next,
+ _ = async_std::task::sleep(Duration::from_millis(20)) => continue,
+ } {
+ Some(r) => match r {
+ Ok(m) => {
+ if let Message::Text(m) = m {
+ output!("{m}");
+ state.stdin.send(m).unwrap();
}
- },
- None => {
- nooutput!();
}
+ #[allow(unused_variables)]
+ Err(e) => {
+ fail!("{e}");
+ }
+ },
+ None => {
+ nooutput!();
}
- async_std::task::sleep(Duration::from_millis(20)).await;
- continue;
}
- });
- Self(ws_task)
- }
-
- pub async fn wait(self) {
- self.0.await.unwrap()
+ async_std::task::sleep(Duration::from_millis(20)).await;
+ continue;
+ }
}
}