Core: asyncify Votes

This commit is contained in:
Natsu Kagami 2020-09-06 22:33:48 -04:00
parent 890cb21770
commit 9975aa5880
Signed by: nki
GPG key ID: 73376E117CD20735

View file

@ -1,14 +1,18 @@
use serenity::framework::standard::CommandError as Error; use serenity::framework::standard::CommandError as Error;
use serenity::{ use serenity::{
collector::ReactionAction,
framework::standard::{macros::command, Args, CommandResult}, framework::standard::{macros::command, Args, CommandResult},
model::{ model::{
channel::{Message, Reaction, ReactionType}, channel::{Message, ReactionType},
id::UserId, id::UserId,
}, },
utils::MessageBuilder, utils::MessageBuilder,
}; };
use std::collections::{HashMap as Map, HashSet as Set};
use std::time::Duration; use std::time::Duration;
use std::{
collections::{HashMap as Map, HashSet as Set},
convert::TryFrom,
};
use youmubot_prelude::{Duration as ParseDuration, *}; use youmubot_prelude::{Duration as ParseDuration, *};
#[command] #[command]
@ -19,13 +23,13 @@ use youmubot_prelude::{Duration as ParseDuration, *};
#[only_in(guilds)] #[only_in(guilds)]
#[min_args(2)] #[min_args(2)]
#[owner_privilege] #[owner_privilege]
pub fn vote(ctx: &mut Context, msg: &Message, mut args: Args) -> CommandResult { pub async fn vote(ctx: &Context, msg: &Message, mut args: Args) -> CommandResult {
// Parse stuff first // Parse stuff first
let args = args.quoted(); let args = args.quoted();
let _duration = args.single::<ParseDuration>()?; let _duration = args.single::<ParseDuration>()?;
let duration = &_duration.0; let duration = &_duration.0;
if *duration < Duration::from_secs(2 * 60) || *duration > Duration::from_secs(60 * 60 * 24) { if *duration < Duration::from_secs(2 * 60) || *duration > Duration::from_secs(60 * 60 * 24) {
msg.reply(ctx, format!("😒 Invalid duration ({}). The voting time should be between **2 minutes** and **1 day**.", _duration))?; msg.reply(ctx, format!("😒 Invalid duration ({}). The voting time should be between **2 minutes** and **1 day**.", _duration)).await?;
return Ok(()); return Ok(());
} }
let question = args.single::<String>()?; let question = args.single::<String>()?;
@ -41,7 +45,8 @@ pub fn vote(ctx: &mut Context, msg: &Message, mut args: Args) -> CommandResult {
msg.reply( msg.reply(
ctx, ctx,
"😒 Can't have a nice voting session if you only have one choice.", "😒 Can't have a nice voting session if you only have one choice.",
)?; )
.await?;
return Ok(()); return Ok(());
} }
if choices.len() > MAX_CHOICES { if choices.len() > MAX_CHOICES {
@ -52,7 +57,8 @@ pub fn vote(ctx: &mut Context, msg: &Message, mut args: Args) -> CommandResult {
"😵 Too many choices... We only support {} choices at the moment!", "😵 Too many choices... We only support {} choices at the moment!",
MAX_CHOICES MAX_CHOICES
), ),
)?; )
.await?;
return Ok(()); return Ok(());
} }
@ -89,70 +95,59 @@ pub fn vote(ctx: &mut Context, msg: &Message, mut args: Args) -> CommandResult {
.description(MessageBuilder::new().push_bold_line_safe(&question).push("\nThis question was asked by ").push(author.mention())) .description(MessageBuilder::new().push_bold_line_safe(&question).push("\nThis question was asked by ").push(author.mention()))
.fields(fields.into_iter()) .fields(fields.into_iter())
}) })
})?; }).await?;
msg.delete(&ctx)?; msg.delete(&ctx).await?;
// React on all the choices // React on all the choices
choices choices
.iter() .iter()
.try_for_each(|(emote, _)| panel.react(&ctx, &emote[..]))?; .map(|(emote, _)| {
panel
.react(&ctx, ReactionType::try_from(&emote[..]).unwrap())
.map_ok(|_| ())
})
.collect::<stream::FuturesUnordered<_>>()
.try_collect::<()>()
.await?;
// A handler for votes. // A handler for votes.
struct VoteHandler { let mut user_reactions: Map<String, Set<UserId>> = choices
pub ctx: Context,
pub msg: Message,
pub user_reactions: Map<String, Set<UserId>>,
pub panel: Message,
}
impl VoteHandler {
fn new(ctx: Context, msg: Message, panel: Message, choices: &[(String, String)]) -> Self {
VoteHandler {
ctx,
msg,
user_reactions: choices
.iter() .iter()
.map(|(emote, _)| (emote.clone(), Set::new())) .map(|(emote, _)| (emote.clone(), Set::new()))
.collect(), .collect();
panel,
}
}
}
impl ReactionHandler for VoteHandler { // Collect reactions...
fn handle_reaction(&mut self, reaction: &Reaction, is_add: bool) -> CommandResult { msg.await_reactions(&ctx)
if reaction.message_id != self.panel.id { .timeout(*duration)
return Ok(()); .await
} .scan(user_reactions, |set, reaction| async move {
if reaction.user(&self.ctx)?.bot { let (reaction, is_add) = match &*reaction {
return Ok(()); ReactionAction::Added(r) => (r, true),
} ReactionAction::Removed(r) => (r, false),
};
let users = if let ReactionType::Unicode(ref s) = reaction.emoji { let users = if let ReactionType::Unicode(ref s) = reaction.emoji {
if let Some(users) = self.user_reactions.get_mut(s.as_str()) { if let Some(users) = set.get_mut(s.as_str()) {
users users
} else { } else {
return Ok(()); return None;
} }
} else { } else {
return Ok(()); return None;
};
let user_id = match reaction.user_id {
Some(v) => v,
None => return None,
}; };
if is_add { if is_add {
users.insert(reaction.user_id); users.insert(user_id);
} else { } else {
users.remove(&reaction.user_id); users.remove(&user_id);
}
Ok(())
}
} }
Some(())
})
.collect::<()>()
.await;
ctx.data // Handle choices
.get_cloned::<ReactionWatcher>()
.handle_reactions_timed(
VoteHandler::new(ctx.clone(), msg.clone(), panel, &choices),
*duration,
move |vh| {
let (ctx, msg, user_reactions, panel) =
(vh.ctx, vh.msg, vh.user_reactions, vh.panel);
let choice_map = choices.into_iter().collect::<Map<_, _>>(); let choice_map = choices.into_iter().collect::<Map<_, _>>();
let result: Vec<(String, Vec<UserId>)> = user_reactions let result: Vec<(String, Vec<UserId>)> = user_reactions
.into_iter() .into_iter()
@ -169,8 +164,10 @@ pub fn vote(ctx: &mut Context, msg: &Message, mut args: Args) -> CommandResult {
.push(", sorry 😭") .push(", sorry 😭")
.build(), .build(),
) )
.ok(); .await?;
} else { return Ok(());
}
channel channel
.send_message(&ctx, |c| { .send_message(&ctx, |c| {
c.content({ c.content({
@ -201,11 +198,8 @@ pub fn vote(ctx: &mut Context, msg: &Message, mut args: Args) -> CommandResult {
content.build() content.build()
}) })
}) })
.ok(); .await?;
} panel.delete(&ctx).await?;
panel.delete(&ctx).ok();
},
);
Ok(()) Ok(())
// unimplemented!(); // unimplemented!();