html terminal
voting
| -rw-r--r-- | Cargo.lock | 166 | ||||
| -rw-r--r-- | Cargo.toml | 2 | ||||
| -rw-r--r-- | src/bot/js.rs | 2 | ||||
| -rw-r--r-- | src/bot/maps.rs | 2 | ||||
| -rw-r--r-- | src/bot/mod.rs | 33 | ||||
| -rw-r--r-- | src/bot/player.rs | 2 | ||||
| -rw-r--r-- | src/bot/status.rs | 26 | ||||
| -rw-r--r-- | src/bot/voting.rs | 219 | ||||
| -rw-r--r-- | src/main.rs | 14 | ||||
| -rw-r--r-- | src/process.rs | 18 | ||||
| -rw-r--r-- | src/server.rs | 49 |
11 files changed, 447 insertions, 86 deletions
@@ -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", @@ -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"); } } |