html terminal
-rw-r--r--.gitignore2
-rw-r--r--Cargo.lock305
-rw-r--r--Cargo.toml5
-rw-r--r--src/bot/config.rs2
-rw-r--r--src/bot/js.rs2
-rw-r--r--src/bot/maps.rs2
-rw-r--r--src/bot/mod.rs58
-rw-r--r--src/bot/player.rs2
-rw-r--r--src/bot/status.rs12
-rw-r--r--src/bot/voting.rs690
-rw-r--r--src/main.rs1
-rw-r--r--src/process.rs21
-rw-r--r--src/server.rs17
-rw-r--r--src/webhook.rs30
14 files changed, 618 insertions, 531 deletions
diff --git a/.gitignore b/.gitignore
index 0065b4b..a0dd2a9 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,3 +1,5 @@
/target
*token
html/
+# vote data
+**.vd
diff --git a/Cargo.lock b/Cargo.lock
index 4b72581..d912d5a 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -65,107 +65,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "23b62fc65de8e4e7f52534fb52b0f3ed04746ae267519eef2a83941e8085068b"
[[package]]
-name = "async-channel"
-version = "1.8.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "cf46fee83e5ccffc220104713af3292ff9bc7c64c7de289f66dae8e38d826833"
-dependencies = [
- "concurrent-queue",
- "event-listener",
- "futures-core",
-]
-
-[[package]]
-name = "async-executor"
-version = "1.5.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6fa3dc5f2a8564f07759c008b9109dc0d39de92a88d5588b8a5036d286383afb"
-dependencies = [
- "async-lock",
- "async-task",
- "concurrent-queue",
- "fastrand",
- "futures-lite",
- "slab",
-]
-
-[[package]]
-name = "async-global-executor"
-version = "2.3.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f1b6f5d7df27bd294849f8eec66ecfc63d11814df7a4f5d74168a2394467b776"
-dependencies = [
- "async-channel",
- "async-executor",
- "async-io",
- "async-lock",
- "blocking",
- "futures-lite",
- "once_cell",
-]
-
-[[package]]
-name = "async-io"
-version = "1.13.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0fc5b45d93ef0529756f812ca52e44c221b35341892d3dcc34132ac02f3dd2af"
-dependencies = [
- "async-lock",
- "autocfg",
- "cfg-if",
- "concurrent-queue",
- "futures-lite",
- "log",
- "parking",
- "polling",
- "rustix",
- "slab",
- "socket2",
- "waker-fn",
-]
-
-[[package]]
-name = "async-lock"
-version = "2.7.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "fa24f727524730b077666307f2734b4a1a1c57acb79193127dcc8914d5242dd7"
-dependencies = [
- "event-listener",
-]
-
-[[package]]
-name = "async-std"
-version = "1.12.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "62565bb4402e926b29953c785397c6dc0391b7b446e45008b0049eb43cec6f5d"
-dependencies = [
- "async-channel",
- "async-global-executor",
- "async-io",
- "async-lock",
- "crossbeam-utils",
- "futures-channel",
- "futures-core",
- "futures-io",
- "futures-lite",
- "gloo-timers",
- "kv-log-macro",
- "log",
- "memchr",
- "once_cell",
- "pin-project-lite",
- "pin-utils",
- "slab",
- "wasm-bindgen-futures",
-]
-
-[[package]]
-name = "async-task"
-version = "4.4.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ecc7ab41815b3c653ccd2978ec3255c81349336702dfdf62ee6f7069b12a3aae"
-
-[[package]]
name = "async-trait"
version = "0.1.68"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -193,12 +92,6 @@ dependencies = [
]
[[package]]
-name = "atomic-waker"
-version = "1.1.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1181e1e0d1fce796a03db1ae795d67167da795f9cf4a39c37589e85ef57f26d3"
-
-[[package]]
name = "autocfg"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -278,19 +171,10 @@ dependencies = [
]
[[package]]
-name = "blocking"
-version = "1.3.1"
+name = "btparse"
+version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "77231a1c8f801696fc0123ec6150ce92cffb8e164a02afb9c8ddee0e9b65ad65"
-dependencies = [
- "async-channel",
- "async-lock",
- "async-task",
- "atomic-waker",
- "fastrand",
- "futures-lite",
- "log",
-]
+checksum = "ca8fb3f04d0ef6cb7d39262d6c2bad0eec0c00071083ceddeb8bbcf693ff28a6"
[[package]]
name = "bumpalo"
@@ -336,15 +220,6 @@ dependencies = [
]
[[package]]
-name = "concurrent-queue"
-version = "2.2.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "62ec6771ecfa0762d24683ee5a32ad78487a3d3afdc0fb8cae19d2c5deb50b7c"
-dependencies = [
- "crossbeam-utils",
-]
-
-[[package]]
name = "convert_case"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -384,15 +259,6 @@ dependencies = [
]
[[package]]
-name = "crossbeam-utils"
-version = "0.8.16"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5a22b2d63d4d1dc0b7f1b6b2747dd0088008a9be28b6ddf0b1e7d335e3037294"
-dependencies = [
- "cfg-if",
-]
-
-[[package]]
name = "crypto-common"
version = "0.1.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -512,42 +378,6 @@ dependencies = [
]
[[package]]
-name = "errno"
-version = "0.3.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4bcfec3a70f97c962c307b2d2c56e358cf1d00b558d74262b5f929ee8cc7e73a"
-dependencies = [
- "errno-dragonfly",
- "libc",
- "windows-sys",
-]
-
-[[package]]
-name = "errno-dragonfly"
-version = "0.1.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "aa68f1b12764fab894d2755d2518754e71b4fd80ecfb822714a1206c2aab39bf"
-dependencies = [
- "cc",
- "libc",
-]
-
-[[package]]
-name = "event-listener"
-version = "2.5.3"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0"
-
-[[package]]
-name = "fastrand"
-version = "1.9.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e51093e27b0797c359783294ca4f0a911c270184cb10f85783b118614a1501be"
-dependencies = [
- "instant",
-]
-
-[[package]]
name = "flate2"
version = "1.0.26"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -621,21 +451,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4fff74096e71ed47f8e023204cfd0aa1289cd54ae5430a9523be060cdb849964"
[[package]]
-name = "futures-lite"
-version = "1.13.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "49a9d51ce47660b1e808d3c990b4709f2f415d928835a17dfd16991515c46bce"
-dependencies = [
- "fastrand",
- "futures-core",
- "futures-io",
- "memchr",
- "parking",
- "pin-project-lite",
- "waker-fn",
-]
-
-[[package]]
name = "futures-macro"
version = "0.3.28"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -698,18 +513,6 @@ dependencies = [
]
[[package]]
-name = "gloo-timers"
-version = "0.2.6"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9b995a66bb87bebce9a0f4a95aed01daca4872c050bfcb21653361c03bc35e5c"
-dependencies = [
- "futures-channel",
- "futures-core",
- "js-sys",
- "wasm-bindgen",
-]
-
-[[package]]
name = "h2"
version = "0.3.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -754,12 +557,6 @@ dependencies = [
]
[[package]]
-name = "hermit-abi"
-version = "0.3.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "fed44880c466736ef9a5c5b5facefb5ed0785676d0c02d612db14e54f0d84286"
-
-[[package]]
name = "http"
version = "0.2.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -880,26 +677,6 @@ dependencies = [
]
[[package]]
-name = "instant"
-version = "0.1.12"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c"
-dependencies = [
- "cfg-if",
-]
-
-[[package]]
-name = "io-lifetimes"
-version = "1.0.11"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "eae7b9aee968036d54dce06cebaefd919e4472e753296daccd6d344e3e2df0c2"
-dependencies = [
- "hermit-abi 0.3.1",
- "libc",
- "windows-sys",
-]
-
-[[package]]
name = "ipnet"
version = "2.7.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -930,15 +707,6 @@ dependencies = [
]
[[package]]
-name = "kv-log-macro"
-version = "1.0.7"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0de8b303297635ad57c9f5059fd9cee7a47f8e8daa09df0fcd07dd39fb22977f"
-dependencies = [
- "log",
-]
-
-[[package]]
name = "lazy_static"
version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -951,12 +719,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f92be4933c13fd498862a9e02a3055f8a8d9c039ce33db97306fd5a6caa7f29b"
[[package]]
-name = "linux-raw-sys"
-version = "0.3.8"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ef53942eb7bf7ff43a617b3e2c1c4a5ecf5944a7c1bc12d7ee39bbb15e5c1519"
-
-[[package]]
name = "lock_api"
version = "0.4.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -971,9 +733,6 @@ name = "log"
version = "0.4.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b06a4cde4c0f271a446782e3eff8de789548ce57dbc8eca9292c27f4a42004b4"
-dependencies = [
- "value-bag",
-]
[[package]]
name = "matchit"
@@ -1156,7 +915,7 @@ version = "1.15.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0fac9e2da13b5eb447a6ce3d392f23a29d8694bff781bf03a16cd9ac8697593b"
dependencies = [
- "hermit-abi 0.2.6",
+ "hermit-abi",
"libc",
]
@@ -1180,8 +939,8 @@ name = "panel"
version = "0.1.0"
dependencies = [
"anyhow",
- "async-std",
"axum",
+ "btparse",
"convert_case 0.6.0",
"futures",
"futures-util",
@@ -1192,6 +951,8 @@ dependencies = [
"paste",
"poise",
"regex",
+ "serde",
+ "serde_json",
"serenity",
"strip-ansi-escapes",
"tokio",
@@ -1199,12 +960,6 @@ dependencies = [
]
[[package]]
-name = "parking"
-version = "2.1.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "14f2252c834a40ed9bb5422029649578e63aa341ac401f74e719dd1afda8394e"
-
-[[package]]
name = "parking_lot"
version = "0.12.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1338,22 +1093,6 @@ dependencies = [
]
[[package]]
-name = "polling"
-version = "2.8.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4b2d323e8ca7996b3e23126511a523f7e62924d93ecd5ae73b333815b0eb3dce"
-dependencies = [
- "autocfg",
- "bitflags",
- "cfg-if",
- "concurrent-queue",
- "libc",
- "log",
- "pin-project-lite",
- "windows-sys",
-]
-
-[[package]]
name = "ppv-lite86"
version = "0.2.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1506,20 +1245,6 @@ dependencies = [
]
[[package]]
-name = "rustix"
-version = "0.37.20"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b96e891d04aa506a6d1f318d2771bcb1c7dfda84e126660ace067c9b474bb2c0"
-dependencies = [
- "bitflags",
- "errno",
- "io-lifetimes",
- "libc",
- "linux-raw-sys",
- "windows-sys",
-]
-
-[[package]]
name = "rustls"
version = "0.20.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1945,9 +1670,9 @@ dependencies = [
[[package]]
name = "tracing-attributes"
-version = "0.1.24"
+version = "0.1.25"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0f57e3ca2a01450b1a921183a9c9cbfda207fd822cef4ccb00a65402cbba7a74"
+checksum = "8803eee176538f94ae9a14b55b2804eb7e1441f8210b1c31290b3bccdccff73b"
dependencies = [
"proc-macro2",
"quote",
@@ -2069,12 +1794,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a"
[[package]]
-name = "value-bag"
-version = "1.4.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a4d330786735ea358f3bc09eea4caa098569c1c93f342d9aca0514915022fe7e"
-
-[[package]]
name = "version_check"
version = "0.9.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -2102,12 +1821,6 @@ dependencies = [
]
[[package]]
-name = "waker-fn"
-version = "1.1.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9d5b2c62b4012a3e1eca5a7e077d13b3bf498c4073e33ccd58626607748ceeca"
-
-[[package]]
name = "want"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
diff --git a/Cargo.toml b/Cargo.toml
index 5561887..de8ad26 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -6,7 +6,6 @@ edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
-async-std = "1.12.0"
axum = { version = "0.6.18", features = [
"tokio",
"http1",
@@ -19,6 +18,7 @@ tokio = { version = "1.28.2", features = [
"sync",
"rt-multi-thread",
"parking_lot",
+ "time",
], default-features = false }
tokio-stream = "0.1.14"
futures-util = "0.3.28"
@@ -37,6 +37,9 @@ minify-js = "0.5.6"
itertools = "0.10.5"
convert_case = "0.6.0"
parse_duration = "2.1.1"
+serde = "1.0"
+serde_json = "1.0"
+btparse = "0.1.1"
[profile.release]
lto = true
diff --git a/src/bot/config.rs b/src/bot/config.rs
index a68a905..78a0da3 100644
--- a/src/bot/config.rs
+++ b/src/bot/config.rs
@@ -3,7 +3,7 @@ use crate::{return_next, send_ctx};
use convert_case::{Case, Casing};
use futures_util::StreamExt;
-const ITEMS: &'static [&'static str] = &[
+const ITEMS: &[&str] = &[
"autoUpdate",
"showConnectMessages",
"enableVotekick",
diff --git a/src/bot/js.rs b/src/bot/js.rs
index 1486f11..0b67e33 100644
--- a/src/bot/js.rs
+++ b/src/bot/js.rs
@@ -8,7 +8,7 @@ fn parse_js(from: &str) -> Result<String> {
static RE: LazyLock<Regex> =
LazyLock::new(|| Regex::new(r#"```(js|javascript)?([^`]+)```"#).unwrap());
let mat = RE
- .captures(&from)
+ .captures(from)
.ok_or(anyhow::anyhow!(r#"no code found (use \`\`\`js...\`\`\`."#))?;
let script = mat.get(2).unwrap().as_str();
let mut out = vec![];
diff --git a/src/bot/maps.rs b/src/bot/maps.rs
index 9c62168..758cae0 100644
--- a/src/bot/maps.rs
+++ b/src/bot/maps.rs
@@ -37,7 +37,7 @@ pub async fn autocomplete<'a>(
) -> impl futures::Stream<Item = String> + 'a {
futures::stream::iter(Maps::get_all(&ctx.data().stdin).await)
.filter(move |name| futures::future::ready(name.starts_with(partial)))
- .map(|name| name.to_string())
+ .map(ToString::to_string)
}
#[poise::command(
diff --git a/src/bot/mod.rs b/src/bot/mod.rs
index 9c3f6d8..a559dee 100644
--- a/src/bot/mod.rs
+++ b/src/bot/mod.rs
@@ -17,6 +17,7 @@ use std::fs::read_to_string;
use std::sync::{Arc, Mutex, OnceLock};
use tokio::sync::broadcast;
+#[derive(Debug)]
pub struct Data {
stdin: broadcast::Sender<String>,
vote_data: voting::Votes,
@@ -41,7 +42,7 @@ macro_rules! send_ctx {
#[cfg(not(debug_assertions))]
const PFX: &'static str = ">";
#[cfg(debug_assertions)]
-const PFX: &'static str = "-";
+const PFX: &str = "-";
const SUCCESS: (u8, u8, u8) = (34, 139, 34);
const FAIL: (u8, u8, u8) = (255, 69, 0);
@@ -66,24 +67,13 @@ impl Bot {
status::command(),
config::set(),
voting::create(),
+ voting::fixall(),
+ voting::list(),
start(),
end(),
help(),
],
- on_error: |e| {
- Box::pin(async move {
- e.ctx()
- .unwrap()
- .send(|b| {
- b.embed(|e| {
- e.color(FAIL)
- .description("oy <@696196765564534825> i broke")
- })
- })
- .await
- .unwrap();
- })
- },
+ on_error: |e| Box::pin(on_error(e)),
prefix_options: poise::PrefixFrameworkOptions {
edit_tracker: Some(poise::EditTracker::for_timespan(
std::time::Duration::from_secs(2 * 60),
@@ -103,6 +93,7 @@ impl Bot {
stdin,
vote_data: voting::Votes::new(vec![]),
})
+ // todo: voting::fixall() auto
})
});
@@ -112,12 +103,45 @@ impl Bot {
SKIPPING.get_or_init(|| (wh.skip.clone(), wh.skipped.clone()));
wh.link(stdout).await;
});
- f.run().await.unwrap()
+ f.run().await.unwrap();
}
}
type Context<'a> = poise::Context<'a, Data, anyhow::Error>;
+async fn on_error(error: poise::FrameworkError<'_, Data, anyhow::Error>) {
+ use poise::FrameworkError::Command;
+ match error {
+ Command { error, ctx } => {
+ ctx.say(format!("e: `{error}`")).await.unwrap();
+ if let Ok(n) = std::env::var("RUST_LIB_BACKTRACE") {
+ use std::str::FromStr;
+ if let Ok(n) = u8::from_str(&n) {
+ if n == 1 {
+ let mut parsed = btparse::deserialize(dbg!(error.backtrace())).unwrap();
+ let mut s = vec![];
+ for frame in &mut parsed.frames {
+ if let Some(line) = frame.line.take() {
+ if frame.function.contains("panel")
+ || frame.function.contains("poise")
+ || frame.function.contains("serenity")
+ {
+ s.push(format!("l{}@{}", line, frame.function));
+ }
+ }
+ }
+ s.truncate(15);
+ ctx.say(format!("trace: ```rs\n{}\n```", s.join("\n")))
+ .await
+ .unwrap();
+ }
+ }
+ }
+ }
+ err => poise::builtins::on_error(err).await.unwrap(),
+ }
+}
+
#[poise::command(
prefix_command,
required_permissions = "ADMINISTRATOR",
@@ -138,7 +162,7 @@ async fn raw(
#[macro_export]
macro_rules! return_next {
($ctx:expr) => {{
- let line = crate::bot::get_nextblock().await;
+ let line = $crate::bot::get_nextblock().await;
$ctx.send(|m| m.content(line)).await?;
return Ok(());
}};
diff --git a/src/bot/player.rs b/src/bot/player.rs
index 26b2dd5..9039485 100644
--- a/src/bot/player.rs
+++ b/src/bot/player.rs
@@ -67,7 +67,7 @@ async fn get_players(stdin: &broadcast::Sender<String>) -> Result<Vec<Player>> {
name: strip_colors(name),
uuid: uuid.to_owned(),
ip: Ipv4Addr::from_str(ip).unwrap(),
- })
+ });
}
}
}
diff --git a/src/bot/status.rs b/src/bot/status.rs
index 0863e57..ce6d7e4 100644
--- a/src/bot/status.rs
+++ b/src/bot/status.rs
@@ -3,6 +3,7 @@ use crate::send_ctx;
use anyhow::Result;
use itertools::Itertools;
use std::str::FromStr;
+use tokio::time::{sleep, Duration};
fn parse(line: &str) -> Option<(u32, u32, u32)> {
let mut v = vec![];
@@ -63,12 +64,10 @@ pub async fn command(ctx: Context<'_>) -> Result<()> {
}
let block = tokio::select! {
block = get_nextblock() => block,
- _ = async_std::task::sleep(std::time::Duration::from_secs(5)) => fail!(ctx, FAIL),
+ _ = sleep(Duration::from_secs(5)) => fail!(ctx, FAIL),
};
- let (tps, mem, pcount) = if let Some(t) = parse(&block) {
- t
- } else {
- fail!(ctx, FAIL)
+ let Some((tps,mem,pcount)) = parse(&block) else {
+ fail!(ctx, FAIL);
};
poise::send_reply(ctx, |m| {
m.embed(|e| {
@@ -77,7 +76,7 @@ pub async fn command(ctx: Context<'_>) -> Result<()> {
}
e.title("server online")
.field("tps", tps, true)
- .field("memory use", humanize_bytes(Size::Mb(mem as f64)), true)
+ .field("memory use", humanize_bytes(Size::Mb(f64::from(mem))), true)
.field("players", pcount, true)
.color(SUCCESS)
})
@@ -97,5 +96,6 @@ fn test_bytes() {
assert!(humanize_bytes(Size::B(550.0)) == "550 B");
assert!(humanize_bytes(Size::Kb(550.0)) == "550 KB");
assert!(humanize_bytes(Size::Mb(650.0)) == "650 MB");
+ assert!(humanize_bytes(Size::Mb(2000.0)) == "2 GB");
assert!(humanize_bytes(Size::Gb(15.3)) == "15.3 GB");
}
diff --git a/src/bot/voting.rs b/src/bot/voting.rs
index 090ac73..9fa57a6 100644
--- a/src/bot/voting.rs
+++ b/src/bot/voting.rs
@@ -1,68 +1,399 @@
use super::{Context, DISABLED, SUCCESS};
use ::serenity::builder::CreateActionRow;
use ::serenity::builder::CreateEmbed;
+use anyhow::anyhow;
use anyhow::Result;
use itertools::Itertools;
-use poise::serenity_prelude::CollectComponentInteraction as Interaction;
use poise::serenity_prelude::*;
+use serde::{Deserialize, Serialize};
use std::collections::HashMap;
+use std::fs::{read_dir, File};
+use std::io::BufReader;
+use std::path::Path;
use std::str::FromStr;
use std::sync::Mutex;
use std::time::SystemTime;
+use tokio::time::*;
pub type Vote = usize;
+
+#[derive(Default, Clone, Serialize, Deserialize, Debug)]
+struct VoteOptions {
+ options: Vec<String>,
+ styles: Vec<ButtonStyle>,
+ title: String,
+ fields: HashMap<String, String>,
+ image: Option<String>,
+}
+
+#[derive(Serialize, Deserialize, Debug, Clone, Default)]
+pub struct BeforePushVoteData {
+ votes: HashMap<UserId, Vote>,
+ // you can parse this from the message but its easier to do this
+ deadline: Duration,
+ #[serde(skip)]
+ deadline_changed: Duration,
+ options: VoteOptions,
+ #[serde(skip)]
+ reply: Option<Box<Message>>,
+ id: u64,
+ mid: MessageId,
+ cid: ChannelId,
+ endat: u64,
+}
+
+#[derive(Clone, Debug)]
+pub struct AfterPushVoteData {
+ index: usize,
+ deadline_changed: Duration,
+ options: VoteOptions,
+ id: u64,
+}
+
+macro_rules! read {
+ ($self:expr,$ctx:expr,$v:expr) => {{
+ $v = $ctx.data().vote_data.lock().unwrap();
+ match $v.get_mut($self.index).unwrap() {
+ VoteData::Before(x) => x,
+ VoteData::After(_) => unreachable!(),
+ }
+ }};
+}
+
+#[derive(Debug)]
pub enum VoteData {
- Running(HashMap<UserId, Vote>),
- Finished(Vec<usize>),
+ Before(BeforePushVoteData),
+ After(AfterPushVoteData),
+}
+
+pub type Votes = Mutex<Vec<VoteData>>;
+
+trait EmbedUtil {
+ fn imageor<S: ToString>(&mut self, img: Option<S>) -> &mut Self;
+ fn set_fields<M: IntoIterator<Item = (S, S)>, S: ToString>(&mut self, fields: M) -> &mut Self;
+}
+
+impl EmbedUtil for CreateEmbed {
+ fn imageor<S: ToString>(&mut self, img: Option<S>) -> &mut Self {
+ if let Some(iuri) = img {
+ self.image(iuri)
+ } else {
+ self
+ }
+ }
+
+ fn set_fields<M: IntoIterator<Item = (S, S)>, S: ToString>(&mut self, fields: M) -> &mut Self {
+ for (k, v) in fields {
+ self.field(k, v, false);
+ }
+ self
+ }
+}
+
+macro_rules! votes {
+ ($self:expr, $ctx:expr, $v:expr) => {
+ match &mut $self {
+ VoteData::After(a) => &mut read!(a, $ctx, $v).votes,
+ VoteData::Before(b) => &mut b.votes,
+ }
+ };
}
impl VoteData {
- pub fn get(&mut self) -> &mut HashMap<UserId, Vote> {
+ pub fn summarize(&mut self, ctx: &Context<'_>, optcount: usize) -> Vec<usize> {
+ let mut ret = vec![];
+ ret.resize(optcount, 0);
+ let mut v;
+ for v in votes!(*self, ctx, v).values() {
+ ret[*v] += 1;
+ }
+ ret
+ }
+
+ fn deadline(&self) -> Duration {
match self {
- Self::Running(x) => x,
- Self::Finished(_) => unreachable!(),
+ Self::After(a) => a.deadline_changed,
+ Self::Before(b) => b.deadline_changed,
}
}
- pub fn summarize_running(&self, optcount: usize) -> Vec<usize> {
+ fn id(&self) -> u64 {
match self {
- Self::Running(s) => {
- let mut ret = vec![];
- ret.resize(optcount, 0);
- for (_, v) in s {
- ret[*v] += 1
- }
- ret
+ Self::After(a) => a.id,
+ Self::Before(b) => b.id,
+ }
+ }
+
+ fn options(&self) -> &VoteOptions {
+ match self {
+ Self::After(a) => &a.options,
+ Self::Before(b) => &b.options,
+ }
+ }
+
+ fn set_reply(&mut self, ctx: &Context<'_>, reply: Message) {
+ let mut v;
+ let mid = reply.id;
+ let cid = reply.channel_id;
+ let reply = Some(Box::new(reply));
+ match self {
+ Self::After(a) => {
+ let read = read!(a, ctx, v);
+ read.reply = reply;
+ read.mid = mid;
+ read.cid = cid;
+ }
+ Self::Before(b) => {
+ b.reply = reply;
+ b.mid = mid;
+ b.cid = cid;
}
- Self::Finished(_) => unreachable!(),
}
}
- pub fn get_summarized(&self) -> &Vec<usize> {
+ fn get_reply(&mut self, ctx: &Context<'_>) -> Box<Message> {
+ let mut v;
match self {
- Self::Finished(ret) => ret,
- Self::Running(_) => unreachable!(),
+ Self::After(a) => read!(a, ctx, v).reply.take().unwrap(),
+ Self::Before(b) => b.reply.take().unwrap(),
}
}
- pub fn finish(self, optcount: usize) -> Self {
- Self::Finished(self.summarize_running(optcount))
+ fn set_end(&mut self) {
+ let end = self.dead_secs();
+ match self {
+ Self::Before(x) => x.endat = end,
+ _ => unreachable!(),
+ }
}
-}
-pub type Votes = Mutex<Vec<VoteData>>;
+ fn dead_secs(&self) -> u64 {
+ (SystemTime::now() + self.deadline())
+ .duration_since(SystemTime::UNIX_EPOCH)
+ .unwrap()
+ .as_secs()
+ }
-trait Imgor {
- fn imageor<S: ToString>(&mut self, img: Option<S>) -> &mut Self;
+ fn end_stamp(&self) -> String {
+ format!("<t:{}:R>", self.dead_secs())
+ }
+
+ pub async fn begin(mut self, ctx: Context<'_>) -> Result<Self> {
+ self.set_end();
+ let o = self.options();
+ let handle = poise::send_reply(ctx, |m| {
+ m.embed(|e| {
+ e.imageor(o.image.as_ref())
+ .color(SUCCESS)
+ .title(&o.title)
+ .description(format!("vote ends {}", self.end_stamp()))
+ .set_fields(&o.fields)
+ })
+ .components(|c| {
+ c.create_action_row(|r| {
+ for (n, option) in o.options.iter().enumerate() {
+ r.create_button(|b| {
+ b.custom_id(format!("{}{n}", self.id()))
+ .label(option)
+ .style(o.styles[n])
+ });
+ }
+ r
+ })
+ })
+ })
+ .await?;
+ let msg = handle.into_message().await?;
+ self.set_reply(&ctx, msg);
+ self.push(&ctx).save(&ctx)
+ }
+
+ fn save_ref(&self, ctx: &Context<'_>) -> Result<()> {
+ let t = self.options().title.clone() + ".vd";
+ let mut re;
+ let thing = match &self {
+ Self::Before(x) => x,
+ Self::After(y) => {
+ re = ctx.data().vote_data.lock().unwrap();
+ match re.get_mut(y.index).unwrap() {
+ VoteData::Before(x) => x,
+ VoteData::After(_) => unreachable!(),
+ }
+ }
+ };
+ std::fs::write(t, serde_json::to_string(thing)?)?;
+ Ok(())
+ }
+
+ pub fn save(self, ctx: &Context<'_>) -> Result<Self> {
+ self.save_ref(ctx)?;
+ Ok(self)
+ }
+
+ pub fn push(self, ctx: &Context<'_>) -> Self {
+ let mut data = ctx.data().vote_data.lock().unwrap();
+ let n = data.len();
+ let v = Self::After(AfterPushVoteData {
+ index: n,
+ id: self.id(),
+ deadline_changed: self.deadline(),
+ options: self.options().clone(),
+ });
+ data.push(self);
+ v
+ }
+
+ fn remove(self, ctx: &Context<'_>) -> Self {
+ match self {
+ Self::After(x) => ctx.data().vote_data.lock().unwrap().remove(x.index),
+ Self::Before(_) => unreachable!(),
+ }
+ }
+
+ pub async fn input(mut self, ctx: &Context<'_>) -> Result<Self> {
+ let dead = self.deadline();
+ let ctx_id = self.id();
+ let ctx_id_len = ctx_id.to_string().len();
+ let o = self.options().clone();
+ let timestamp = self.end_stamp();
+ while let Some(press) = CollectComponentInteraction::new(ctx)
+ .filter(move |press| press.data.custom_id.starts_with(&ctx_id.to_string()))
+ .timeout(dead)
+ .await
+ {
+ let s = {
+ let mut v;
+ if votes!(self, ctx, v)
+ .insert(
+ press.user.id,
+ Vote::from_str(&press.data.custom_id[ctx_id_len..]).unwrap(),
+ )
+ .is_some()
+ {
+ "updated"
+ } else {
+ "voted"
+ }
+ };
+ self.save_ref(ctx)?;
+ let (_m, _) = tokio::join!(
+ press.create_followup_message(ctx, |m| { m.ephemeral(true).content(s) }),
+ press.create_interaction_response(ctx, |c| {
+ c.kind(InteractionResponseType::UpdateMessage)
+ .interaction_response_data(|m| {
+ m.embed(|e| {
+ for (option, votes) in
+ self.summarize(ctx, o.options.len()).iter().enumerate()
+ {
+ e.field(&o.options[option], votes, true);
+ }
+ e.imageor(o.image.as_ref())
+ .color(SUCCESS)
+ .title(&o.title)
+ .set_fields(&o.fields)
+ .description(format!("vote ends {timestamp}"))
+ })
+ })
+ })
+ );
+ // let m = m?;
+ // let http = ctx.serenity_context().http.clone();
+ // tokio::spawn(async move {
+ // sleep(Duration::from_secs(10)).await;
+ // m.delete(http).await.unwrap();
+ // });
+ }
+ Ok(self)
+ }
+
+ pub async fn finish(mut self, ctx: &Context<'_>) -> Result<()> {
+ let o = self.options().clone();
+ let filename = format!("{}.vd", o.title);
+ let p = std::path::Path::new(&filename);
+ if p.exists() {
+ let _ = std::fs::remove_file(p);
+ }
+ self.get_reply(ctx)
+ .edit(ctx, |m| {
+ m.embed(|e| {
+ for (option, votes) in self
+ .remove(ctx)
+ .summarize(ctx, o.options.len())
+ .iter()
+ .enumerate()
+ {
+ e.field(&o.options[option], votes, true);
+ }
+ e.color(DISABLED)
+ .title(&o.title)
+ .imageor(o.image.as_ref())
+ .set_fields(o.fields)
+ .description(format!("vote ended!"))
+ })
+ .components(|c| {
+ c.set_action_row({
+ let mut r = CreateActionRow::default();
+ for (n, option) in o.options.iter().enumerate() {
+ r.create_button(|b| {
+ b.label(option)
+ .disabled(true)
+ .style(o.styles[n])
+ .custom_id("_")
+ });
+ }
+ r
+ })
+ })
+ })
+ .await?;
+ Ok(())
+ }
}
-impl Imgor for CreateEmbed {
- fn imageor<S: ToString>(&mut self, img: Option<S>) -> &mut Self {
- if let Some(iuri) = img {
- self.image(iuri)
- } else {
- self
+trait Parsing {
+ fn parse_pscsp(self) -> HashMap<String, String>;
+ fn parse_psv(self) -> Vec<String>;
+ fn parse_psv_to_styles(self) -> Vec<ButtonStyle>;
+}
+
+impl<S> Parsing for S
+where
+ S: AsRef<str>,
+{
+ fn parse_pscsp(self) -> HashMap<String, String> {
+ let pair = self.as_ref().split('|');
+ let pairs = pair.map(|s| {
+ s.split(':')
+ .map(|s| s.trim().to_owned())
+ .collect_tuple()
+ .unwrap()
+ });
+ let mut map = HashMap::new();
+ for (k, v) in pairs {
+ map.insert(k, v);
}
+ map
+ }
+ fn parse_psv(self) -> Vec<String> {
+ self.as_ref()
+ .split('|')
+ .map(|s| s.trim().to_owned())
+ .collect()
+ }
+ fn parse_psv_to_styles(self) -> Vec<ButtonStyle> {
+ self.as_ref()
+ .split('|')
+ .map(|s| {
+ use ButtonStyle::*;
+ match s.trim().to_lowercase().as_str() {
+ // "blue" => Primary,
+ "gray" => Secondary,
+ "green" => Success,
+ "red" => Danger,
+ _ => Primary,
+ }
+ })
+ .collect()
}
}
@@ -72,148 +403,185 @@ pub async fn create(
ctx: Context<'_>,
#[description = "picture url"] image: Option<String>,
#[description = "pressables (psv)"] options: String,
- #[description = "option styles (psv)"] styles: String,
+ #[description = "option styles (psv) {blue|gray|green|red}"] styles: Option<String>,
#[description = "how long the vote will be up"] length: String,
+ #[description = "fields (pipe separated colon seperated pairs) (a:b|c:d)"] fields: Option<
+ String,
+ >,
title: String,
) -> Result<()> {
- let ctx_id = ctx.id();
- let options = options.split('|').map(|s| s.trim()).collect_vec();
- let styles = styles
- .split('|')
- .map(|s| s.trim().to_lowercase())
- .map(|s| {
- use ButtonStyle::*;
- match s.as_str() {
- "blue" => Primary,
- "gray" => Secondary,
- "green" => Success,
- "red" => Danger,
- _ => Primary,
- }
- })
- .collect_vec();
- let dur = if let Ok(t) = parse_duration::parse(&length) {
- t
- } else {
+ let options = options.parse_psv();
+ let styles = styles.map_or(
+ vec![ButtonStyle::Primary; options.len()],
+ Parsing::parse_psv_to_styles,
+ );
+ let fields = fields.as_ref().map_or(HashMap::new(), Parsing::parse_pscsp);
+ let Ok(dur) = parse_duration::parse(&length) else {
ctx.say(format!("`{length}` is not time")).await?;
return Ok(());
};
- let end = format!(
- "<t:{}:R>",
- (SystemTime::now() + dur)
- .duration_since(SystemTime::UNIX_EPOCH)?
- .as_secs()
- );
- macro_rules! update_msg {
- // give me unhygenic macros
- ($m:expr, $ctx:expr, $image:expr, $title:expr, $options:expr, $ctx_id:expr, $n:expr) => {
- $m.embed(|e| {
- for (option, votes) in $ctx.data().vote_data.lock().unwrap()[$n]
- .summarize_running($options.len())
- .iter()
- .enumerate()
- {
- e.field(&$options[option], votes, true);
- }
- e.imageor($image.as_ref())
- .color(SUCCESS)
- .title(&$title)
- .description(format!("vote ends {end}"))
- })
- };
+
+ VoteData::Before({
+ BeforePushVoteData {
+ votes: HashMap::new(),
+ deadline: dur,
+ deadline_changed: dur,
+ options: VoteOptions {
+ options,
+ styles,
+ title,
+ fields,
+ image,
+ },
+ reply: None,
+ id: ctx.id(),
+ ..Default::default()
+ }
+ })
+ .begin(ctx)
+ .await?
+ .input(&ctx)
+ .await?
+ .finish(&ctx)
+ .await
+}
+
+async fn fix(ctx: &Context<'_>, data: BufReader<std::fs::File>) -> Result<()> {
+ let mut v: BeforePushVoteData = serde_json::from_reader(data)?;
+ let m = ctx.http().get_message(v.cid.0, v.mid.0).await?;
+ let end = dbg!(m.timestamp.unix_timestamp()) as u64;
+ v.reply = Some(Box::new(m));
+ let now = SystemTime::now()
+ .duration_since(SystemTime::UNIX_EPOCH)
+ .unwrap()
+ .as_secs();
+ let end = end + dbg!(v.deadline.as_secs());
+ println!("@{now} | :{end}");
+ // cant use abs() because unsigned
+ v.deadline_changed = if now < end {
+ Duration::from_secs(end - now)
+ } else {
+ Duration::from_secs(now - end)
+ };
+ let v = VoteData::Before(v);
+ if end < now {
+ v.push(&ctx).finish(&ctx).await
+ } else {
+ v.push(&ctx).input(&ctx).await?.finish(&ctx).await
}
- let n = {
- let mut data = ctx.data().vote_data.lock().unwrap();
- let n = data.len();
- data.push(VoteData::Running(HashMap::new()));
- n
+}
+
+#[poise::command(
+ slash_command,
+ category = "Discord",
+ required_permissions = "ADMINISTRATOR"
+)]
+pub async fn fixall(ctx: Context<'_>) -> Result<()> {
+ use futures::future;
+ let mut futs = vec![];
+ for e in read_dir(".")? {
+ let e = e?;
+ let fname = e.file_name();
+ let p = Path::new(&fname);
+ if let Some(ext) = p.extension() {
+ if ext == "vd" {
+ futs.push(fix(&ctx, BufReader::new(File::open(p).unwrap())));
+ }
+ }
+ }
+ let msg = format!("fixed {}", futs.len());
+ poise::send_reply(ctx, |m| m.content(msg).ephemeral(true)).await?;
+ future::join_all(futs).await;
+ Ok(())
+}
+
+// #[poise::command(
+// context_menu_command = "fix vote",
+// slash_command,
+// category = "Discord",
+// rename = "fix_vote",
+// required_permissions = "ADMINISTRATOR"
+// )]
+// /// restart vote (use ctx menu)
+// pub async fn reinstate(
+// ctx: Context<'_>,
+// #[description = "previous vote, id or link"] m: Message,
+// ) -> Result<()> {
+// static RE: LazyLock<Regex> = LazyLock::new(|| Regex::new(r#"<t:([0-9]+):R>"#).unwrap());
+// let e = m.embeds.get(0).ok_or(anyhow!("no embed?"))?;
+// let end = u64::from_str(
+// RE.captures(
+// m.embeds
+// .get(0)
+// .ok_or(anyhow!("no embed?"))?
+// .description
+// .as_ref()
+// .ok_or(anyhow!("no desc?"))?,
+// )
+// .ok_or(anyhow!("no timestamp?"))?
+// .get(1)
+// .unwrap()
+// .as_str(),
+// )
+// .unwrap();
+// let now = SystemTime::now()
+// .duration_since(SystemTime::UNIX_EPOCH)
+// .unwrap()
+// .as_secs();
+// let f = BufReader::new(std::fs::File::open(
+// e.title.to_owned().ok_or(anyhow!("no title?"))? + ".vd",
+// )?);
+// poise::send_reply(ctx, |m| m.content("yes").ephemeral(true)).await?;
+// let mut v: BeforePushVoteData = serde_json::from_reader(f)?;
+// v.reply = Some(Box::new(m));
+// // cant use abs() because unsigned
+// v.deadline = if now < end {
+// Duration::from_secs(end - now)
+// } else {
+// Duration::from_secs(now - end)
+// };
+// let v = VoteData::Before(v);
+// if end < now {
+// v.push(&ctx).finish(&ctx).await
+// } else {
+// v.push(&ctx).input(&ctx).await?.finish(&ctx).await
+// }
+// }
+
+// voters
+
+#[poise::command(prefix_command, slash_command, category = "Discord", rename = "votes")]
+pub async fn list(ctx: Context<'_>, #[description = "the vote title"] vote: String) -> Result<()> {
+ let vd = {
+ let buf = ctx.data().vote_data.lock().unwrap();
+ match &buf[buf
+ .iter()
+ .position(|x| x.options().title == vote)
+ .ok_or(anyhow!("vote doesnt exist"))?]
+ {
+ VoteData::Before(x) => x.clone(),
+ VoteData::After(_) => unreachable!(),
+ }
};
- let handle = poise::send_reply(ctx, |m| {
- update_msg!(m, ctx, image, title, options, ctx_id, n).components(|c| {
- c.create_action_row(|r| {
- for (n, option) in options.iter().enumerate() {
- r.create_button(|b| {
- b.custom_id(format!("{}{n}", ctx_id))
- .label(option)
- .style(styles[n])
- });
+ poise::send_reply(ctx, |m| {
+ m.allowed_mentions(|x| x.empty_parse()).embed(|e| {
+ let mut votes: HashMap<usize, Vec<u64>> = HashMap::new();
+ for (user, vote) in vd.votes {
+ votes.entry(vote).or_default().push(user.0);
+ }
+ for (vote, voters) in votes {
+ let mut s = vec![];
+ s.reserve(voters.len());
+ for person in voters {
+ s.push(format!("<@{person}>"));
}
- r
- })
+ e.field(&vd.options.options[vote], s.join("\n"), false);
+ }
+ e.color(SUCCESS)
+ .title(format!("voter list for {vote}"))
+ .footer(|f| f.text("privacy is a illusion"))
})
})
.await?;
- let ctx_id_len = ctx_id.to_string().len();
- while let Some(press) = Interaction::new(ctx)
- .filter(move |press| press.data.custom_id.starts_with(&ctx_id.to_string()))
- .timeout(dur)
- .await
- {
- let s = {
- if ctx.data().vote_data.lock().unwrap()[n]
- .get()
- .insert(
- press.user.id,
- Vote::from_str(&press.data.custom_id[ctx_id_len..]).unwrap(),
- )
- .is_some()
- {
- "updated"
- } else {
- "voted"
- }
- };
- println!("got vote!");
- tokio::join!(
- press.create_followup_message(ctx, |m| {
- m.ephemeral(true).embed(|e| e.title(s).color(SUCCESS))
- }),
- press.create_interaction_response(ctx, |c| {
- c.kind(InteractionResponseType::UpdateMessage)
- .interaction_response_data(|m| {
- update_msg!(m, ctx, image, title, options, ctx_id, n)
- })
- })
- )
- .0?;
- }
- println!("vote ended!");
- handle
- .edit(ctx, |m| {
- m.embed(|e| {
- for (option, votes) in ctx
- .data()
- .vote_data
- .lock()
- .unwrap()
- .remove(n)
- .finish(options.len())
- .get_summarized()
- .into_iter()
- .enumerate()
- {
- e.field(&options[option], votes, true);
- }
- e.color(DISABLED)
- .title(&title)
- .imageor(image.as_ref())
- .description(format!("vote ended!"))
- })
- .components(|c| {
- c.set_action_row({
- let mut r = CreateActionRow::default();
- for (n, option) in options.iter().enumerate() {
- r.create_button(|b| {
- b.custom_id(format!("{}{n}", ctx_id))
- .label(option)
- .disabled(true)
- .style(styles[n])
- });
- }
- r
- })
- })
- })
- .await?;
Ok(())
}
diff --git a/src/main.rs b/src/main.rs
index ab28cfd..1a7a9dd 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -1,5 +1,4 @@
#![feature(lazy_cell)]
-
use std::str::FromStr;
#[macro_use]
mod logging;
diff --git a/src/process.rs b/src/process.rs
index 1a5c9fb..bfa284e 100644
--- a/src/process.rs
+++ b/src/process.rs
@@ -1,9 +1,9 @@
use std::time::Duration;
use tokio::io::{AsyncReadExt, AsyncWriteExt};
use tokio::net::TcpStream;
-use tokio::sync::broadcast;
-use tokio::sync::broadcast::error::TryRecvError;
+use tokio::sync::broadcast::{self, error::TryRecvError};
use tokio::task::JoinHandle;
+use tokio::time::sleep;
pub struct Process {
inner: TcpStream,
@@ -13,7 +13,6 @@ pub struct Process {
impl Process {
/// spawns the server
- #[must_use]
pub async fn spawn() -> anyhow::Result<Self> {
let stream = TcpStream::connect("localhost:6859").await?;
Ok(Self {
@@ -41,7 +40,7 @@ impl Process {
let mut stdout = [0; 4096];
loop {
if output.receiver_count() == 0 {
- async_std::task::sleep(Duration::from_millis(500)).await;
+ sleep(Duration::from_millis(500)).await;
continue;
}
match input.try_recv() {
@@ -54,16 +53,6 @@ impl Process {
input!("{s}");
s += "\n";
self.inner.write_all(s.as_bytes()).await.unwrap();
- // let mut last = 250;
- // while let Err(e) = self.inner.write_all(s.as_bytes()).await {
- // last *= last;
- // if e.kind() == std::io::ErrorKind::BrokenPipe {
- // println!("failed write, waiting {last}ms to retry.");
- // async_std::task::sleep(Duration::from_millis(last)).await;
- // continue;
- // }
- // panic!("{e:?}");
- // }
self.inner.flush().await.unwrap();
}
}
@@ -71,7 +60,7 @@ impl Process {
let string = {
let n = tokio::select! {
n = {self.inner.read(&mut stdout)} => n.unwrap(),
- _ = async_std::task::sleep(Duration::from_millis(500)) => continue,
+ _ = sleep(Duration::from_millis(500)) => continue,
};
String::from_utf8_lossy(&stdout[..n]).into_owned()
};
@@ -82,7 +71,7 @@ impl Process {
String::from_utf8_lossy(&strip_ansi_escapes::strip(&string).unwrap())
.into_owned();
output.send(stripped).unwrap();
- async_std::task::sleep(Duration::from_millis(500)).await;
+ sleep(Duration::from_millis(500)).await;
}
})
}
diff --git a/src/server.rs b/src/server.rs
index a5529cf..14f080c 100644
--- a/src/server.rs
+++ b/src/server.rs
@@ -7,8 +7,8 @@ use axum::{
Router, Server as AxumServer,
};
-use std::{net::SocketAddr, sync::Arc, time::Duration};
-use tokio::{sync::broadcast, task::JoinHandle};
+use std::{net::SocketAddr, sync::Arc};
+use tokio::{sync::broadcast, task::JoinHandle, time::sleep, time::Duration};
// its a arced arcs
pub struct State {
@@ -21,7 +21,7 @@ pub struct State {
impl State {
fn new(stdin: broadcast::Sender<String>) -> Self {
let (stdout, _) = broadcast::channel(2);
- Self { stdin, stdout }
+ Self { stdout, stdin }
}
}
@@ -65,15 +65,15 @@ impl Server {
AxumServer::bind(&addr)
.serve(router.into_make_service())
.await
- .unwrap()
+ .unwrap();
});
let stdout = state.stdout.clone();
tokio::spawn(async move {
macro_rules! backoff {
($backoff:expr) => {
- $backoff *= $backoff;
+ $backoff <<= 1;
println!("process died; waiting {}s", $backoff);
- async_std::task::sleep(Duration::from_secs($backoff)).await;
+ sleep(Duration::from_secs($backoff)).await;
continue;
};
}
@@ -84,10 +84,7 @@ impl Server {
let _ = h.await;
process_handle = None;
}
-
- let spawn = if let Ok(s) = Process::spawn().await {
- s
- } else {
+ let Ok(spawn) = Process::spawn().await else {
backoff!(backoff);
};
process_handle = Some(
diff --git a/src/webhook.rs b/src/webhook.rs
index c83e64c..f018917 100644
--- a/src/webhook.rs
+++ b/src/webhook.rs
@@ -4,8 +4,8 @@ use regex::Regex;
use serenity::{builder::ExecuteWebhook, http::Http, json};
use std::convert::AsRef;
use std::sync::{Arc, LazyLock, Mutex};
-use std::time::{Duration, Instant};
use tokio::sync::broadcast::{self, error::TryRecvError};
+use tokio::time::{sleep, Duration, Instant};
pub struct Webhook<'a> {
pub skipped: broadcast::Sender<String>,
@@ -73,7 +73,7 @@ impl<'a> Webhook<'a> {
flush!();
}
}
- async_std::task::sleep(Duration::from_millis(20)).await;
+ sleep(Duration::from_millis(20)).await;
continue;
}
},
@@ -94,7 +94,7 @@ impl<'a> Webhook<'a> {
last = Some(now);
}
}
- async_std::task::sleep(Duration::from_millis(20)).await;
+ sleep(Duration::from_millis(20)).await;
}
}
@@ -104,18 +104,17 @@ impl<'a> Webhook<'a> {
let mut unnamed: Option<String> = None;
// this code is very game dependent
- for line in feed.into_iter() {
+ for line in feed {
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();
}
+ let message = message.take().unwrap();
+ self.send_message(n, &message).await;
+ current.take();
}
current = Some(name.to_owned());
message = Some(msg.to_owned());
@@ -125,7 +124,7 @@ impl<'a> Webhook<'a> {
}
continue;
}
- unnamed.madd(line);
+ unnamed.madd(unify(&line));
}
// finish
if let Some(n) = current.as_ref() {
@@ -173,7 +172,7 @@ impl OutputStyle for MindustryStyle {
return None;
}
- if let Some((u, c)) = line.split(": ").map(|s| unify(s)).collect_tuple() {
+ if let Some((u, c)) = line.split(": ").map(unify).collect_tuple() {
let u = u.trim_start_matches('<');
let c = c.trim_end_matches('>');
if !(u.is_empty() || c.is_empty()) {
@@ -194,15 +193,8 @@ impl OutputStyle for MindustryStyle {
}
}
-fn unify(s: &str) -> String {
- s.chars()
- .filter_map(|c| {
- if c > 'џ' {
- return None;
- }
- Some(c)
- })
- .collect()
+pub fn unify(s: &str) -> String {
+ s.chars().filter(|&c| c < 'џ').collect()
}
trait Madd {