html terminal
voting
bendn 2023-06-18
parent 58d5917 · commit dc32a97
-rw-r--r--Cargo.lock166
-rw-r--r--Cargo.toml2
-rw-r--r--src/bot/js.rs2
-rw-r--r--src/bot/maps.rs2
-rw-r--r--src/bot/mod.rs33
-rw-r--r--src/bot/player.rs2
-rw-r--r--src/bot/status.rs26
-rw-r--r--src/bot/voting.rs219
-rw-r--r--src/main.rs14
-rw-r--r--src/process.rs18
-rw-r--r--src/server.rs49
11 files changed, 447 insertions, 86 deletions
diff --git a/Cargo.lock b/Cargo.lock
index 3c3c414..4b72581 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -29,6 +29,15 @@ dependencies = [
]
[[package]]
+name = "aho-corasick"
+version = "1.0.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "43f6cb1bf222025340178f382c426f13757b2960e89779dfcb319c32542a5a41"
+dependencies = [
+ "memchr",
+]
+
+[[package]]
name = "android-tzdata"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -358,9 +367,9 @@ checksum = "e496a50fda8aacccc86d7529e2c1e0892dbd0f898a6b5645b5561b89c3210efa"
[[package]]
name = "cpufeatures"
-version = "0.2.7"
+version = "0.2.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3e4c1eaa2012c47becbbad2ab175484c2a84d1185b566fb2cc5b8707343dfe58"
+checksum = "03e69e28e9f7f77debdedbaafa2866e1de9ba56df55a8bd7cfc724c25a09987c"
dependencies = [
"libc",
]
@@ -376,9 +385,9 @@ dependencies = [
[[package]]
name = "crossbeam-utils"
-version = "0.8.15"
+version = "0.8.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3c063cd8cc95f5c377ed0d4b49a4b21f632396ff690e8470c29b3359b346984b"
+checksum = "5a22b2d63d4d1dc0b7f1b6b2747dd0088008a9be28b6ddf0b1e7d335e3037294"
dependencies = [
"cfg-if",
]
@@ -816,7 +825,7 @@ checksum = "0646026eb1b3eea4cd9ba47912ea5ce9cc07713d105b1a14698f4e6433d348b7"
dependencies = [
"http",
"hyper",
- "rustls 0.21.1",
+ "rustls 0.21.2",
"tokio",
"tokio-rustls 0.24.1",
]
@@ -913,9 +922,9 @@ checksum = "453ad9f582a441959e5f0d088b02ce04cfe8d51a8eaf077f12ac6d3e94164ca6"
[[package]]
name = "js-sys"
-version = "0.3.63"
+version = "0.3.64"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2f37a4a5928311ac501dee68b3c7613a1037d0edb30c8e5427bd832d55d1b790"
+checksum = "c5f195fe497f702db0f318b07fdd68edb16955aed830df8363d837542f8f935a"
dependencies = [
"wasm-bindgen",
]
@@ -1000,7 +1009,7 @@ version = "0.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fc4d9147754a49e80557df835eb59e743eab1bf75410a134f55dc4b9dbb692ad"
dependencies = [
- "aho-corasick",
+ "aho-corasick 0.7.20",
"css-minify",
"lazy_static",
"memchr",
@@ -1065,6 +1074,74 @@ dependencies = [
]
[[package]]
+name = "num"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b8536030f9fea7127f841b45bb6243b27255787fb4eb83958aa1ef9d2fdc0c36"
+dependencies = [
+ "num-bigint",
+ "num-complex",
+ "num-integer",
+ "num-iter",
+ "num-rational",
+ "num-traits",
+]
+
+[[package]]
+name = "num-bigint"
+version = "0.2.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "090c7f9998ee0ff65aa5b723e4009f7b217707f1fb5ea551329cc4d6231fb304"
+dependencies = [
+ "autocfg",
+ "num-integer",
+ "num-traits",
+]
+
+[[package]]
+name = "num-complex"
+version = "0.2.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b6b19411a9719e753aff12e5187b74d60d3dc449ec3f4dc21e3989c3f554bc95"
+dependencies = [
+ "autocfg",
+ "num-traits",
+]
+
+[[package]]
+name = "num-integer"
+version = "0.1.45"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "225d3389fb3509a24c93f5c29eb6bde2586b98d9f016636dff58d7c6f7569cd9"
+dependencies = [
+ "autocfg",
+ "num-traits",
+]
+
+[[package]]
+name = "num-iter"
+version = "0.1.43"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7d03e6c028c5dc5cac6e2dec0efda81fc887605bb3d884578bb6d6bf7514e252"
+dependencies = [
+ "autocfg",
+ "num-integer",
+ "num-traits",
+]
+
+[[package]]
+name = "num-rational"
+version = "0.2.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5c000134b5dbf44adc5cb772486d335293351644b801551abe8f75c84cfa4aef"
+dependencies = [
+ "autocfg",
+ "num-bigint",
+ "num-integer",
+ "num-traits",
+]
+
+[[package]]
name = "num-traits"
version = "0.2.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1111,6 +1188,7 @@ dependencies = [
"itertools",
"minify-html",
"minify-js 0.5.6",
+ "parse_duration",
"paste",
"poise",
"regex",
@@ -1155,7 +1233,7 @@ version = "0.10.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "30534759e6ad87aa144c396544747e1c25b1020bd133356fd758c8facec764e5"
dependencies = [
- "aho-corasick",
+ "aho-corasick 0.7.20",
"lazy_static",
"memchr",
]
@@ -1166,7 +1244,7 @@ version = "0.17.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9ec3b11d443640ec35165ee8f6f0559f1c6f41878d70330fe9187012b5935f02"
dependencies = [
- "aho-corasick",
+ "aho-corasick 0.7.20",
"bumpalo",
"hashbrown 0.13.2",
"lazy_static",
@@ -1174,6 +1252,17 @@ dependencies = [
]
[[package]]
+name = "parse_duration"
+version = "2.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7037e5e93e0172a5a96874380bf73bc6ecef022e26fa25f2be26864d6b3ba95d"
+dependencies = [
+ "lazy_static",
+ "num",
+ "regex",
+]
+
+[[package]]
name = "paste"
version = "1.0.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1333,6 +1422,8 @@ version = "1.8.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d0ab3ca65655bb1e41f2a8c8cd662eb4fb035e67c3f78da1d61dffe89d07300f"
dependencies = [
+ "aho-corasick 1.0.2",
+ "memchr",
"regex-syntax",
]
@@ -1366,7 +1457,7 @@ dependencies = [
"once_cell",
"percent-encoding",
"pin-project-lite",
- "rustls 0.21.1",
+ "rustls 0.21.2",
"rustls-pemfile",
"serde",
"serde_json",
@@ -1442,9 +1533,9 @@ dependencies = [
[[package]]
name = "rustls"
-version = "0.21.1"
+version = "0.21.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c911ba11bc8433e811ce56fde130ccf32f5127cab0e0194e9c68c5a5b671791e"
+checksum = "e32ca28af694bc1bbf399c33a516dbdf1c90090b8ab23c2bc24f834aa2247f5f"
dependencies = [
"log",
"ring",
@@ -1537,9 +1628,9 @@ dependencies = [
[[package]]
name = "serde_json"
-version = "1.0.96"
+version = "1.0.97"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "057d394a50403bcac12672b2b18fb387ab6d289d957dab67dd201875391e52f1"
+checksum = "bdf3bf93142acad5821c99197022e170842cdbc1c30482b98750c688c640842a"
dependencies = [
"itoa",
"ryu",
@@ -1602,15 +1693,6 @@ dependencies = [
]
[[package]]
-name = "signal-hook-registry"
-version = "1.4.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d8229b473baa5980ac72ef434c4415e70c4b5e71b423043adb4ba059f89c99a1"
-dependencies = [
- "libc",
-]
-
-[[package]]
name = "slab"
version = "0.4.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1759,7 +1841,6 @@ dependencies = [
"num_cpus",
"parking_lot",
"pin-project-lite",
- "signal-hook-registry",
"socket2",
"tokio-macros",
"windows-sys",
@@ -1793,7 +1874,7 @@ version = "0.24.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c28327cf380ac148141087fbfb9de9d7bd4e84ab5d2c28fbc911d753de8a7081"
dependencies = [
- "rustls 0.21.1",
+ "rustls 0.21.2",
"tokio",
]
@@ -2028,11 +2109,10 @@ checksum = "9d5b2c62b4012a3e1eca5a7e077d13b3bf498c4073e33ccd58626607748ceeca"
[[package]]
name = "want"
-version = "0.3.0"
+version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1ce8a968cb1cd110d136ff8b819a556d6fb6d919363c61534f6860c7eb172ba0"
+checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e"
dependencies = [
- "log",
"try-lock",
]
@@ -2044,9 +2124,9 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
[[package]]
name = "wasm-bindgen"
-version = "0.2.86"
+version = "0.2.87"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5bba0e8cb82ba49ff4e229459ff22a191bbe9a1cb3a341610c9c33efc27ddf73"
+checksum = "7706a72ab36d8cb1f80ffbf0e071533974a60d0a308d01a5d0375bf60499a342"
dependencies = [
"cfg-if",
"wasm-bindgen-macro",
@@ -2054,9 +2134,9 @@ dependencies = [
[[package]]
name = "wasm-bindgen-backend"
-version = "0.2.86"
+version = "0.2.87"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "19b04bc93f9d6bdee709f6bd2118f57dd6679cf1176a1af464fca3ab0d66d8fb"
+checksum = "5ef2b6d3c510e9625e5fe6f509ab07d66a760f0885d858736483c32ed7809abd"
dependencies = [
"bumpalo",
"log",
@@ -2069,9 +2149,9 @@ dependencies = [
[[package]]
name = "wasm-bindgen-futures"
-version = "0.4.36"
+version = "0.4.37"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2d1985d03709c53167ce907ff394f5316aa22cb4e12761295c5dc57dacb6297e"
+checksum = "c02dbc21516f9f1f04f187958890d7e6026df8d16540b7ad9492bc34a67cea03"
dependencies = [
"cfg-if",
"js-sys",
@@ -2081,9 +2161,9 @@ dependencies = [
[[package]]
name = "wasm-bindgen-macro"
-version = "0.2.86"
+version = "0.2.87"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "14d6b024f1a526bb0234f52840389927257beb670610081360e5a03c5df9c258"
+checksum = "dee495e55982a3bd48105a7b947fd2a9b4a8ae3010041b9e0faab3f9cd028f1d"
dependencies = [
"quote",
"wasm-bindgen-macro-support",
@@ -2091,9 +2171,9 @@ dependencies = [
[[package]]
name = "wasm-bindgen-macro-support"
-version = "0.2.86"
+version = "0.2.87"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e128beba882dd1eb6200e1dc92ae6c5dbaa4311aa7bb211ca035779e5efc39f8"
+checksum = "54681b18a46765f095758388f2d0cf16eb8d4169b639ab575a8f5693af210c7b"
dependencies = [
"proc-macro2",
"quote",
@@ -2104,9 +2184,9 @@ dependencies = [
[[package]]
name = "wasm-bindgen-shared"
-version = "0.2.86"
+version = "0.2.87"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ed9d5b4305409d1fc9482fee2d7f9bcbf24b3972bf59817ef757e23982242a93"
+checksum = "ca6ad05a4870b2bf5fe995117d3728437bd27d7cd5f06f13c17443ef369775a1"
[[package]]
name = "wasm-streams"
@@ -2123,9 +2203,9 @@ dependencies = [
[[package]]
name = "web-sys"
-version = "0.3.63"
+version = "0.3.64"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3bdd9ef4e984da1187bf8110c5cf5b845fbc87a23602cdf912386a76fcd3a7c2"
+checksum = "9b85cbef8c220a6abc02aefd892dfc0fc23afb1c6a426316ec33253a3877249b"
dependencies = [
"js-sys",
"wasm-bindgen",
diff --git a/Cargo.toml b/Cargo.toml
index 60eff10..5561887 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -18,7 +18,6 @@ tokio = { version = "1.28.2", features = [
"net",
"sync",
"rt-multi-thread",
- "process",
"parking_lot",
], default-features = false }
tokio-stream = "0.1.14"
@@ -37,6 +36,7 @@ regex = { version = "1.8.4", features = ["std"], default-features = false }
minify-js = "0.5.6"
itertools = "0.10.5"
convert_case = "0.6.0"
+parse_duration = "2.1.1"
[profile.release]
lto = true
diff --git a/src/bot/js.rs b/src/bot/js.rs
index b4940ff..1486f11 100644
--- a/src/bot/js.rs
+++ b/src/bot/js.rs
@@ -30,7 +30,7 @@ fn parse_js(from: &str) -> Result<String> {
#[poise::command(
prefix_command,
- required_permissions = "USE_SLASH_COMMANDS",
+ required_permissions = "ADMINISTRATOR",
category = "Control",
track_edits,
rename = "js"
diff --git a/src/bot/maps.rs b/src/bot/maps.rs
index 807f8df..9c62168 100644
--- a/src/bot/maps.rs
+++ b/src/bot/maps.rs
@@ -43,7 +43,7 @@ pub async fn autocomplete<'a>(
#[poise::command(
slash_command,
prefix_command,
- required_permissions = "USE_SLASH_COMMANDS",
+ required_permissions = "ADMINISTRATOR",
category = "Info",
rename = "maps"
)]
diff --git a/src/bot/mod.rs b/src/bot/mod.rs
index a6d56e7..9c3f6d8 100644
--- a/src/bot/mod.rs
+++ b/src/bot/mod.rs
@@ -5,6 +5,7 @@ mod js;
mod maps;
mod player;
mod status;
+mod voting;
use crate::webhook::Webhook;
use anyhow::Result;
@@ -18,6 +19,7 @@ use tokio::sync::broadcast;
pub struct Data {
stdin: broadcast::Sender<String>,
+ vote_data: voting::Votes,
}
static SKIPPING: OnceLock<(Arc<Mutex<u8>>, broadcast::Sender<String>)> = OnceLock::new();
@@ -43,6 +45,7 @@ const PFX: &'static str = "-";
const SUCCESS: (u8, u8, u8) = (34, 139, 34);
const FAIL: (u8, u8, u8) = (255, 69, 0);
+const DISABLED: (u8, u8, u8) = (112, 128, 144);
pub struct Bot;
impl Bot {
@@ -62,10 +65,25 @@ impl Bot {
player::list(),
status::command(),
config::set(),
+ voting::create(),
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();
+ })
+ },
prefix_options: poise::PrefixFrameworkOptions {
edit_tracker: Some(poise::EditTracker::for_timespan(
std::time::Duration::from_secs(2 * 60),
@@ -81,7 +99,10 @@ impl Bot {
Box::pin(async move {
poise::builtins::register_globally(ctx, &framework.options().commands).await?;
println!("registered");
- Ok(Data { stdin })
+ Ok(Data {
+ stdin,
+ vote_data: voting::Votes::new(vec![]),
+ })
})
});
@@ -91,9 +112,7 @@ impl Bot {
SKIPPING.get_or_init(|| (wh.skip.clone(), wh.skipped.clone()));
wh.link(stdout).await;
});
- tokio::spawn(async move {
- f.run().await.unwrap();
- });
+ f.run().await.unwrap()
}
}
@@ -101,7 +120,7 @@ type Context<'a> = poise::Context<'a, Data, anyhow::Error>;
#[poise::command(
prefix_command,
- required_permissions = "USE_SLASH_COMMANDS",
+ required_permissions = "ADMINISTRATOR",
category = "Control",
track_edits
)]
@@ -163,7 +182,7 @@ fn strip_colors(from: &str) -> String {
#[poise::command(
slash_command,
- required_permissions = "USE_SLASH_COMMANDS",
+ required_permissions = "ADMINISTRATOR",
category = "Control"
)]
/// start the game.
@@ -181,7 +200,7 @@ pub async fn start(
#[poise::command(
slash_command,
category = "Control",
- required_permissions = "USE_SLASH_COMMANDS"
+ required_permissions = "ADMINISTRATOR"
)]
/// end the game.
pub async fn end(
diff --git a/src/bot/player.rs b/src/bot/player.rs
index f0d6a0f..26b2dd5 100644
--- a/src/bot/player.rs
+++ b/src/bot/player.rs
@@ -104,7 +104,7 @@ pub async fn list(ctx: Context<'_>) -> Result<()> {
let admins = if p.admin { " [A]" } else { "" };
(
p.name,
- if perms.use_slash_commands() {
+ if perms.administrator() {
format!("{id}, {ip}", id = p.uuid, ip = p.ip) + admins
} else {
admins.to_string()
diff --git a/src/bot/status.rs b/src/bot/status.rs
index 6926ab0..0863e57 100644
--- a/src/bot/status.rs
+++ b/src/bot/status.rs
@@ -5,9 +5,11 @@ use itertools::Itertools;
use std::str::FromStr;
fn parse(line: &str) -> Option<(u32, u32, u32)> {
- line.split('/')
- .map(|s| u32::from_str(s.trim().split_once(' ').unwrap().0).unwrap())
- .collect_tuple()
+ let mut v = vec![];
+ for piece in line.split('/') {
+ v.push(u32::from_str(piece.trim().split_once(' ')?.0).ok()?);
+ }
+ v.into_iter().collect_tuple()
}
#[allow(dead_code)]
@@ -32,7 +34,7 @@ impl Size {
// https://git.sr.ht/~f9/human_bytes
pub fn humanize_bytes<T: Into<Size>>(bytes: T) -> String {
const SUFFIX: [&str; 4] = ["B", "KB", "MB", "GB"];
- let size = dbg!(bytes.into().bytes());
+ let size = bytes.into().bytes();
if size <= 0.0 {
return "0 B".to_string();
@@ -53,13 +55,21 @@ pub fn humanize_bytes<T: Into<Size>>(bytes: T) -> String {
pub async fn command(ctx: Context<'_>) -> Result<()> {
let _ = ctx.defer_or_broadcast().await;
send_ctx!(ctx, "status")?;
+ macro_rules! fail {
+ ($ctx:expr,$fail:expr) => {{
+ poise::send_reply(ctx, |m| m.embed(|e| e.title("server down").color($fail))).await?;
+ return Ok(());
+ }};
+ }
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(()) },
+ _ = async_std::task::sleep(std::time::Duration::from_secs(5)) => fail!(ctx, FAIL),
+ };
+ let (tps, mem, pcount) = if let Some(t) = parse(&block) {
+ t
+ } else {
+ fail!(ctx, FAIL)
};
- let (tps, mem, pcount) =
- parse(&block).ok_or(anyhow::anyhow!("couldnt split block {block}."))?;
poise::send_reply(ctx, |m| {
m.embed(|e| {
if pcount > 0 {
diff --git a/src/bot/voting.rs b/src/bot/voting.rs
new file mode 100644
index 0000000..090ac73
--- /dev/null
+++ b/src/bot/voting.rs
@@ -0,0 +1,219 @@
+use super::{Context, DISABLED, SUCCESS};
+use ::serenity::builder::CreateActionRow;
+use ::serenity::builder::CreateEmbed;
+use anyhow::Result;
+use itertools::Itertools;
+use poise::serenity_prelude::CollectComponentInteraction as Interaction;
+use poise::serenity_prelude::*;
+use std::collections::HashMap;
+use std::str::FromStr;
+use std::sync::Mutex;
+use std::time::SystemTime;
+
+pub type Vote = usize;
+pub enum VoteData {
+ Running(HashMap<UserId, Vote>),
+ Finished(Vec<usize>),
+}
+
+impl VoteData {
+ pub fn get(&mut self) -> &mut HashMap<UserId, Vote> {
+ match self {
+ Self::Running(x) => x,
+ Self::Finished(_) => unreachable!(),
+ }
+ }
+
+ pub fn summarize_running(&self, optcount: usize) -> Vec<usize> {
+ match self {
+ Self::Running(s) => {
+ let mut ret = vec![];
+ ret.resize(optcount, 0);
+ for (_, v) in s {
+ ret[*v] += 1
+ }
+ ret
+ }
+ Self::Finished(_) => unreachable!(),
+ }
+ }
+
+ pub fn get_summarized(&self) -> &Vec<usize> {
+ match self {
+ Self::Finished(ret) => ret,
+ Self::Running(_) => unreachable!(),
+ }
+ }
+
+ pub fn finish(self, optcount: usize) -> Self {
+ Self::Finished(self.summarize_running(optcount))
+ }
+}
+
+pub type Votes = Mutex<Vec<VoteData>>;
+
+trait Imgor {
+ fn imageor<S: ToString>(&mut self, img: Option<S>) -> &mut Self;
+}
+
+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
+ }
+ }
+}
+
+#[poise::command(slash_command, category = "Discord", rename = "create_vote")]
+/// make a vote
+pub async fn create(
+ ctx: Context<'_>,
+ #[description = "picture url"] image: Option<String>,
+ #[description = "pressables (psv)"] options: String,
+ #[description = "option styles (psv)"] styles: String,
+ #[description = "how long the vote will be up"] length: 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 {
+ 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}"))
+ })
+ };
+ }
+ let n = {
+ let mut data = ctx.data().vote_data.lock().unwrap();
+ let n = data.len();
+ data.push(VoteData::Running(HashMap::new()));
+ n
+ };
+ 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])
+ });
+ }
+ r
+ })
+ })
+ })
+ .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 aa78c35..ab28cfd 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -1,7 +1,6 @@
#![feature(lazy_cell)]
use std::str::FromStr;
-
#[macro_use]
mod logging;
#[macro_use]
@@ -10,19 +9,14 @@ mod process;
mod server;
mod webhook;
-use process::*;
use server::*;
use std::net::SocketAddr;
#[tokio::main]
async fn main() {
- let process = Process::spawn().await;
- Server::spawn(
- SocketAddr::from((
- [0, 0, 0, 0],
- std::env::var("PORT").map_or(4001, |x| u16::from_str(&x).unwrap()),
- )),
- process,
- )
+ Server::spawn(SocketAddr::from((
+ [0, 0, 0, 0],
+ std::env::var("PORT").map_or(4001, |x| u16::from_str(&x).unwrap()),
+ )))
.await;
}
diff --git a/src/process.rs b/src/process.rs
index 302a173..1a5c9fb 100644
--- a/src/process.rs
+++ b/src/process.rs
@@ -14,13 +14,13 @@ pub struct Process {
impl Process {
/// spawns the server
#[must_use]
- pub async fn spawn() -> Self {
- let stream = TcpStream::connect("localhost:6859").await.unwrap();
- Self {
+ pub async fn spawn() -> anyhow::Result<Self> {
+ let stream = TcpStream::connect("localhost:6859").await?;
+ Ok(Self {
inner: stream,
input: None,
output: None,
- }
+ })
}
pub fn input(mut self, input: broadcast::Receiver<String>) -> Self {
@@ -54,6 +54,16 @@ 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();
}
}
diff --git a/src/server.rs b/src/server.rs
index 50b5cbd..a5529cf 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};
-use tokio::sync::broadcast;
+use std::{net::SocketAddr, sync::Arc, time::Duration};
+use tokio::{sync::broadcast, task::JoinHandle};
// its a arced arcs
pub struct State {
@@ -53,7 +53,7 @@ macro_rules! png {
pub struct Server;
impl Server {
- pub async fn spawn(addr: SocketAddr, proc: Process) {
+ pub async fn spawn(addr: SocketAddr) {
let (stdin_tx, stdin) = broadcast::channel(2);
let state = Arc::new(State::new(stdin_tx));
let router = Router::new()
@@ -61,18 +61,47 @@ impl Server {
.route("/plaguess.png", png!(plaguess))
.route("/favicon.ico", png!(logo32))
.with_state(state.clone());
- let mut server_handle = tokio::spawn(async move {
+ tokio::spawn(async move {
AxumServer::bind(&addr)
.serve(router.into_make_service())
.await
.unwrap()
});
- let mut process_handle = proc.input(stdin).output(state.stdout.clone()).link();
+ let stdout = state.stdout.clone();
+ tokio::spawn(async move {
+ macro_rules! backoff {
+ ($backoff:expr) => {
+ $backoff *= $backoff;
+ println!("process died; waiting {}s", $backoff);
+ async_std::task::sleep(Duration::from_secs($backoff)).await;
+ continue;
+ };
+ }
+ let mut process_handle: Option<JoinHandle<()>> = None;
+ let mut backoff = 1u64;
+ loop {
+ if let Some(h) = process_handle {
+ let _ = h.await;
+ process_handle = None;
+ }
+
+ let spawn = if let Ok(s) = Process::spawn().await {
+ s
+ } else {
+ backoff!(backoff);
+ };
+ process_handle = Some(
+ spawn
+ .input(stdin.resubscribe())
+ .output(stdout.clone())
+ .link(),
+ );
+ if backoff == 1 {
+ continue;
+ }
+ backoff!(backoff);
+ }
+ });
Bot::spawn(state.stdout.subscribe(), state.stdin.clone()).await;
- tokio::select! {
- _ = (&mut server_handle) => process_handle.abort(),
- _ = (&mut process_handle) => server_handle.abort(),
- }
- panic!("oh no");
}
}