Implement ReactionHandler

This commit is contained in:
Natsu Kagami 2020-02-06 15:07:20 -05:00
parent ac4e60c596
commit 486bd6b88d
Signed by: nki
GPG key ID: 73376E117CD20735
5 changed files with 89 additions and 0 deletions

View file

@ -9,4 +9,5 @@ edition = "2018"
[dependencies]
serenity = "0.8"
youmubot-db = { path = "../youmubot-db" }
crossbeam-channel = "0.4"
reqwest = "0.10"

View file

@ -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<RwLock<ShareMap>>;

View file

@ -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<T> 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<Mutex<Vec<Sender<Arc<Reaction>>>>>,
}
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(())
}
}

View file

@ -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::<crate::HTTPClient>(reqwest::blocking::Client::new());
// Set up the reaction watcher.
data.insert::<crate::ReactionWatcher>(crate::ReactionWatcher::new());
}