diff --git a/Cargo.lock b/Cargo.lock index 3210d13..6f9104b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -183,6 +183,14 @@ dependencies = [ "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "crossbeam-channel" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "crossbeam-utils 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "crossbeam-deque" version = "0.7.2" @@ -1731,6 +1739,7 @@ dependencies = [ name = "youmubot-prelude" version = "0.1.0" dependencies = [ + "crossbeam-channel 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", "reqwest 0.10.1 (registry+https://github.com/rust-lang/crates.io-index)", "serenity 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)", "youmubot-db 0.1.0", @@ -1763,6 +1772,7 @@ dependencies = [ "checksum core-foundation 0.6.4 (registry+https://github.com/rust-lang/crates.io-index)" = "25b9e03f145fd4f2bf705e07b900cd41fc636598fe5dc452fd0db1441c3f496d" "checksum core-foundation-sys 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)" = "e7ca8a5221364ef15ce201e8ed2f609fc312682a8f4e0e3d4aa5879764e0fa3b" "checksum crc32fast 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ba125de2af0df55319f41944744ad91c71113bf74a4646efff39afe1f6842db1" +"checksum crossbeam-channel 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "acec9a3b0b3559f15aee4f90746c4e5e293b701c0f7d3925d24e01645267b68c" "checksum crossbeam-deque 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)" = "c3aa945d63861bfe624b55d153a39684da1e8c0bc8fba932f7ee3a3c16cea3ca" "checksum crossbeam-epoch 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "5064ebdbf05ce3cb95e45c8b086f72263f4166b29b97f6baff7ef7fe047b55ac" "checksum crossbeam-queue 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "c695eeca1e7173472a32221542ae469b3e9aac3a4fc81f7696bcad82029493db" diff --git a/youmubot-prelude/Cargo.toml b/youmubot-prelude/Cargo.toml index 5e8e18b..c2eb702 100644 --- a/youmubot-prelude/Cargo.toml +++ b/youmubot-prelude/Cargo.toml @@ -9,4 +9,5 @@ edition = "2018" [dependencies] serenity = "0.8" youmubot-db = { path = "../youmubot-db" } +crossbeam-channel = "0.4" reqwest = "0.10" diff --git a/youmubot-prelude/src/lib.rs b/youmubot-prelude/src/lib.rs index 1b73b8c..c2c3d6b 100644 --- a/youmubot-prelude/src/lib.rs +++ b/youmubot-prelude/src/lib.rs @@ -3,10 +3,12 @@ use std::sync::Arc; pub mod announcer; pub mod args; +pub mod reaction_watch; pub mod setup; pub use announcer::Announcer; pub use args::Duration; +pub use reaction_watch::{ReactionHandler, ReactionWatcher}; /// The global app data. pub type AppData = Arc>; diff --git a/youmubot-prelude/src/reaction_watch.rs b/youmubot-prelude/src/reaction_watch.rs new file mode 100644 index 0000000..e522435 --- /dev/null +++ b/youmubot-prelude/src/reaction_watch.rs @@ -0,0 +1,72 @@ +use crossbeam_channel::{after, bounded, select, Sender}; +use serenity::{framework::standard::CommandResult, model::channel::Reaction, prelude::*}; +use std::sync::{Arc, Mutex}; + +/// Handles a reaction. +/// +/// Every handler needs an expire time too. +pub trait ReactionHandler { + /// Handle a reaction. This is fired on EVERY reaction. + /// You do the filtering yourself. + fn handle_reaction(&mut self, reaction: &Reaction) -> CommandResult; +} + +impl ReactionHandler for T +where + T: FnMut(&Reaction) -> CommandResult, +{ + fn handle_reaction(&mut self, reaction: &Reaction) -> CommandResult { + self(reaction) + } +} + +/// The store for a set of dynamic reaction handlers. +#[derive(Debug, Clone)] +pub struct ReactionWatcher { + channels: Arc>>>>, +} + +impl TypeMapKey for ReactionWatcher { + type Value = ReactionWatcher; +} + +impl ReactionWatcher { + /// Create a new ReactionWatcher. + pub fn new() -> Self { + Self { + channels: Arc::new(Mutex::new(vec![])), + } + } + /// Send a reaction. + pub fn send(&self, r: Reaction) { + let r = Arc::new(r); + self.channels + .lock() + .expect("Poisoned!") + .retain(|e| e.send(r.clone()).is_ok()); + } + /// React! to a series of reaction + /// + /// The reactions stop after `duration`. + pub fn handle_reactions( + &self, + mut h: impl ReactionHandler, + duration: std::time::Duration, + ) -> CommandResult { + let (send, reactions) = bounded(0); + { + self.channels.lock().expect("Poisoned!").push(send); + } + let timeout = after(duration); + loop { + let r = select! { + recv(reactions) -> r => h.handle_reaction(&*r.unwrap()), + recv(timeout) -> _ => break, + }; + if let Err(v) = r { + return Err(v); + } + } + Ok(()) + } +} diff --git a/youmubot-prelude/src/setup.rs b/youmubot-prelude/src/setup.rs index f503922..ab523cc 100644 --- a/youmubot-prelude/src/setup.rs +++ b/youmubot-prelude/src/setup.rs @@ -9,5 +9,9 @@ pub fn setup_prelude(db_path: &Path, data: &mut ShareMap, _: &mut StandardFramew crate::announcer::AnnouncerChannels::insert_into(data, db_path.join("announcers.yaml")) .expect("Announcers DB set up"); + // Set up the HTTP client. data.insert::(reqwest::blocking::Client::new()); + + // Set up the reaction watcher. + data.insert::(crate::ReactionWatcher::new()); }