mirror of
https://github.com/natsukagami/youmubot.git
synced 2025-04-20 17:28:54 +00:00
Overhaul reaction handler to a thread spawning model
This commit is contained in:
parent
03c3281029
commit
9e83501259
6 changed files with 164 additions and 125 deletions
|
@ -145,7 +145,7 @@ pub fn ranks(ctx: &mut Context, m: &Message) -> CommandResult {
|
|||
ctx.data.get_cloned::<ReactionWatcher>().paginate_fn(
|
||||
ctx.clone(),
|
||||
m.channel_id,
|
||||
|page, e| {
|
||||
move |page, e| {
|
||||
let page = page as usize;
|
||||
let start = ITEMS_PER_PAGE * page;
|
||||
let end = ranks.len().min(start + ITEMS_PER_PAGE);
|
||||
|
@ -236,12 +236,17 @@ pub fn contestranks(ctx: &mut Context, m: &Message, mut args: Args) -> CommandRe
|
|||
|
||||
// Table me
|
||||
let ranks = ranks
|
||||
.iter()
|
||||
.into_iter()
|
||||
.flat_map(|v| {
|
||||
v.party
|
||||
.members
|
||||
.iter()
|
||||
.filter_map(|m| members.get(&m.handle).map(|mem| (mem, m.handle.clone(), v)))
|
||||
.filter_map(|m| {
|
||||
members
|
||||
.get(&m.handle)
|
||||
.cloned()
|
||||
.map(|mem| (mem, m.handle.clone(), v.clone()))
|
||||
})
|
||||
.collect::<Vec<_>>()
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
|
|
@ -15,7 +15,7 @@ fn list(ctx: &mut Context, m: &Message, _: Args) -> CommandResult {
|
|||
|
||||
let db = DB::open(&*ctx.data.read());
|
||||
let db = db.borrow()?;
|
||||
let roles = db.get(&guild_id).filter(|v| !v.is_empty());
|
||||
let roles = db.get(&guild_id).filter(|v| !v.is_empty()).cloned();
|
||||
match roles {
|
||||
None => {
|
||||
m.reply(&ctx, "No roles available for assigning.")?;
|
||||
|
@ -23,8 +23,8 @@ fn list(ctx: &mut Context, m: &Message, _: Args) -> CommandResult {
|
|||
Some(v) => {
|
||||
let roles = guild_id.to_partial_guild(&ctx)?.roles;
|
||||
let roles: Vec<_> = v
|
||||
.iter()
|
||||
.filter_map(|(_, role)| roles.get(&role.id).map(|r| (r, &role.description)))
|
||||
.into_iter()
|
||||
.filter_map(|(_, role)| roles.get(&role.id).cloned().map(|r| (r, role.description)))
|
||||
.collect();
|
||||
const ROLES_PER_PAGE: usize = 8;
|
||||
let pages = (roles.len() + ROLES_PER_PAGE - 1) / ROLES_PER_PAGE;
|
||||
|
@ -33,7 +33,7 @@ fn list(ctx: &mut Context, m: &Message, _: Args) -> CommandResult {
|
|||
watcher.paginate_fn(
|
||||
ctx.clone(),
|
||||
m.channel_id,
|
||||
|page, e| {
|
||||
move |page, e| {
|
||||
let page = page as usize;
|
||||
let start = page * ROLES_PER_PAGE;
|
||||
let end = roles.len().min(start + ROLES_PER_PAGE);
|
||||
|
|
|
@ -7,7 +7,7 @@ use serenity::{
|
|||
},
|
||||
utils::MessageBuilder,
|
||||
};
|
||||
use std::collections::HashMap as Map;
|
||||
use std::collections::{HashMap as Map, HashSet as Set};
|
||||
use std::time::Duration;
|
||||
use youmubot_prelude::{Duration as ParseDuration, *};
|
||||
|
||||
|
@ -29,7 +29,10 @@ pub fn vote(ctx: &mut Context, msg: &Message, mut args: Args) -> CommandResult {
|
|||
}
|
||||
let question = args.single::<String>()?;
|
||||
let choices = if args.is_empty() {
|
||||
vec![("😍", "Yes! 😍".to_owned()), ("🤢", "No! 🤢".to_owned())]
|
||||
vec![
|
||||
("😍".to_owned(), "Yes! 😍".to_owned()),
|
||||
("🤢".to_owned(), "No! 🤢".to_owned()),
|
||||
]
|
||||
} else {
|
||||
let choices: Vec<_> = args.iter().map(|v| v.unwrap()).collect();
|
||||
if choices.len() < 2 {
|
||||
|
@ -73,7 +76,7 @@ pub fn vote(ctx: &mut Context, msg: &Message, mut args: Args) -> CommandResult {
|
|||
|
||||
// Ok... now we post up a nice voting panel.
|
||||
let channel = msg.channel_id;
|
||||
let author = &msg.author;
|
||||
let author = msg.author.clone();
|
||||
let panel = channel.send_message(&ctx, |c| {
|
||||
c.content("@here").embed(|e| {
|
||||
e.author(|au| {
|
||||
|
@ -90,100 +93,122 @@ pub fn vote(ctx: &mut Context, msg: &Message, mut args: Args) -> CommandResult {
|
|||
// React on all the choices
|
||||
choices
|
||||
.iter()
|
||||
.try_for_each(|(v, _)| panel.react(&ctx, *v))?;
|
||||
.try_for_each(|(v, _)| panel.react(&ctx, v.clone()))?;
|
||||
|
||||
let reaction_to_choice: Map<_, _> = choices.iter().map(|r| (r.0, &r.1)).collect();
|
||||
let mut user_reactions: Map<UserId, Vec<&str>> = Map::new();
|
||||
// A handler for votes.
|
||||
struct VoteHandler {
|
||||
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()
|
||||
.map(|(v, _)| (v.clone(), Set::new()))
|
||||
.collect(),
|
||||
panel,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ReactionHandler for VoteHandler {
|
||||
fn handle_reaction(&mut self, reaction: &Reaction, is_add: bool) -> CommandResult {
|
||||
if reaction.message_id != self.panel.id {
|
||||
return Ok(());
|
||||
}
|
||||
if reaction.user(&self.ctx)?.bot {
|
||||
return Ok(());
|
||||
}
|
||||
let users = if let ReactionType::Unicode(ref s) = reaction.emoji {
|
||||
if let Some(users) = self.user_reactions.get_mut(s.as_str()) {
|
||||
users
|
||||
} else {
|
||||
return Ok(());
|
||||
}
|
||||
} else {
|
||||
return Ok(());
|
||||
};
|
||||
if is_add {
|
||||
users.insert(reaction.user_id);
|
||||
} else {
|
||||
users.remove(&reaction.user_id);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
ctx.data
|
||||
.get_cloned::<ReactionWatcher>()
|
||||
.handle_reactions_timed(
|
||||
|reaction: &Reaction, is_add| {
|
||||
if reaction.message_id != panel.id {
|
||||
return Ok(());
|
||||
}
|
||||
if reaction.user(&ctx)?.bot {
|
||||
return Ok(());
|
||||
}
|
||||
let choice = if let ReactionType::Unicode(ref s) = reaction.emoji {
|
||||
if let Some(choice) = reaction_to_choice.get(s.as_str()) {
|
||||
choice
|
||||
} else {
|
||||
return Ok(());
|
||||
}
|
||||
} else {
|
||||
return Ok(());
|
||||
};
|
||||
if is_add {
|
||||
user_reactions
|
||||
.entry(reaction.user_id)
|
||||
.or_default()
|
||||
.push(choice);
|
||||
} else {
|
||||
user_reactions.entry(reaction.user_id).and_modify(|v| {
|
||||
v.retain(|f| &f != choice);
|
||||
});
|
||||
}
|
||||
Ok(())
|
||||
},
|
||||
VoteHandler::new(ctx.clone(), msg.clone(), panel, &choices),
|
||||
*duration,
|
||||
)?;
|
||||
let result: Vec<(&str, Vec<UserId>)> = {
|
||||
let mut res: Map<&str, Vec<UserId>> = Map::new();
|
||||
for (u, r) in user_reactions {
|
||||
for t in r {
|
||||
res.entry(t).or_default().push(u);
|
||||
}
|
||||
}
|
||||
res.into_iter().collect()
|
||||
};
|
||||
move |vh| {
|
||||
let (ctx, msg, user_reactions, panel) =
|
||||
(vh.ctx, vh.msg, vh.user_reactions, vh.panel);
|
||||
let result: Vec<(String, Vec<UserId>)> = user_reactions
|
||||
.into_iter()
|
||||
.filter(|(_, users)| !users.is_empty())
|
||||
.map(|(choice, users)| (choice, users.into_iter().collect()))
|
||||
.collect();
|
||||
|
||||
if result.len() == 0 {
|
||||
msg.reply(
|
||||
&ctx,
|
||||
MessageBuilder::new()
|
||||
.push("no one answer your question ")
|
||||
.push_bold_safe(&question)
|
||||
.push(", sorry 😭")
|
||||
.build(),
|
||||
)?;
|
||||
} else {
|
||||
channel.send_message(&ctx, |c| {
|
||||
c.content({
|
||||
let mut content = MessageBuilder::new();
|
||||
content
|
||||
.push("@here, ")
|
||||
.push(author.mention())
|
||||
.push(" previously asked ")
|
||||
.push_bold_safe(&question)
|
||||
.push(", and here are the results!");
|
||||
result.iter().for_each(|(choice, votes)| {
|
||||
content
|
||||
.push("\n - ")
|
||||
.push_bold(format!("{}", votes.len()))
|
||||
.push(" voted for ")
|
||||
.push_bold_safe(choice)
|
||||
.push(": ")
|
||||
.push(
|
||||
votes
|
||||
.iter()
|
||||
.map(|v| v.mention())
|
||||
.collect::<Vec<_>>()
|
||||
.join(", "),
|
||||
);
|
||||
});
|
||||
content.build()
|
||||
})
|
||||
})?;
|
||||
}
|
||||
panel.delete(&ctx)?;
|
||||
if result.len() == 0 {
|
||||
msg.reply(
|
||||
&ctx,
|
||||
MessageBuilder::new()
|
||||
.push("no one answer your question ")
|
||||
.push_bold_safe(&question)
|
||||
.push(", sorry 😭")
|
||||
.build(),
|
||||
)
|
||||
.ok();
|
||||
} else {
|
||||
channel
|
||||
.send_message(&ctx, |c| {
|
||||
c.content({
|
||||
let mut content = MessageBuilder::new();
|
||||
content
|
||||
.push("@here, ")
|
||||
.push(author.mention())
|
||||
.push(" previously asked ")
|
||||
.push_bold_safe(&question)
|
||||
.push(", and here are the results!");
|
||||
result.iter().for_each(|(choice, votes)| {
|
||||
content
|
||||
.push("\n - ")
|
||||
.push_bold(format!("{}", votes.len()))
|
||||
.push(" voted for ")
|
||||
.push_bold_safe(choice)
|
||||
.push(": ")
|
||||
.push(
|
||||
votes
|
||||
.iter()
|
||||
.map(|v| v.mention())
|
||||
.collect::<Vec<_>>()
|
||||
.join(", "),
|
||||
);
|
||||
});
|
||||
content.build()
|
||||
})
|
||||
})
|
||||
.ok();
|
||||
}
|
||||
panel.delete(&ctx).ok();
|
||||
},
|
||||
);
|
||||
|
||||
Ok(())
|
||||
// unimplemented!();
|
||||
}
|
||||
|
||||
// Pick a set of random n reactions!
|
||||
fn pick_n_reactions(n: usize) -> Result<Vec<&'static str>, Error> {
|
||||
fn pick_n_reactions(n: usize) -> Result<Vec<String>, Error> {
|
||||
use rand::seq::SliceRandom;
|
||||
if n > MAX_CHOICES {
|
||||
Err(Error::from("Too many options"))
|
||||
|
@ -191,7 +216,7 @@ fn pick_n_reactions(n: usize) -> Result<Vec<&'static str>, Error> {
|
|||
let mut rand = rand::thread_rng();
|
||||
Ok(REACTIONS
|
||||
.choose_multiple(&mut rand, n)
|
||||
.map(|v| *v)
|
||||
.map(|v| (*v).to_owned())
|
||||
.collect())
|
||||
}
|
||||
}
|
||||
|
|
|
@ -228,7 +228,7 @@ impl FromStr for Nth {
|
|||
}
|
||||
}
|
||||
|
||||
fn list_plays(plays: &[Score], mode: Mode, ctx: Context, m: &Message) -> CommandResult {
|
||||
fn list_plays(plays: Vec<Score>, mode: Mode, ctx: Context, m: &Message) -> CommandResult {
|
||||
let watcher = ctx.data.get_cloned::<ReactionWatcher>();
|
||||
let osu = ctx.data.get_cloned::<BeatmapMetaCache>();
|
||||
let beatmap_cache = ctx.data.get_cloned::<BeatmapCache>();
|
||||
|
@ -245,7 +245,7 @@ fn list_plays(plays: &[Score], mode: Mode, ctx: Context, m: &Message) -> Command
|
|||
watcher.paginate_fn(
|
||||
ctx,
|
||||
m.channel_id,
|
||||
|page, e| {
|
||||
move |page, e| {
|
||||
let page = page as usize;
|
||||
let start = page * ITEMS_PER_PAGE;
|
||||
let end = plays.len().min(start + ITEMS_PER_PAGE);
|
||||
|
@ -417,7 +417,7 @@ pub fn recent(ctx: &mut Context, msg: &Message, mut args: Args) -> CommandResult
|
|||
}
|
||||
Nth::All => {
|
||||
let plays = osu.user_recent(UserID::ID(user.id), |f| f.mode(mode).limit(50))?;
|
||||
list_plays(&plays, mode, ctx.clone(), msg)?;
|
||||
list_plays(plays, mode, ctx.clone(), msg)?;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
|
@ -549,7 +549,7 @@ pub fn top(ctx: &mut Context, msg: &Message, mut args: Args) -> CommandResult {
|
|||
}
|
||||
Nth::All => {
|
||||
let plays = osu.user_best(UserID::ID(user.id), |f| f.mode(mode).limit(100))?;
|
||||
list_plays(&plays, mode, ctx.clone(), msg)?;
|
||||
list_plays(plays, mode, ctx.clone(), msg)?;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
|
|
|
@ -17,7 +17,7 @@ impl ReactionWatcher {
|
|||
/// Takes a copy of Context (which you can `clone`), a pager (see "Pagination") and a target channel id.
|
||||
/// Pagination will handle all events on adding/removing an "arrow" emoji (⬅️ and ➡️).
|
||||
/// This is a blocking call - it will block the thread until duration is over.
|
||||
pub fn paginate<T: Pagination>(
|
||||
pub fn paginate<T: Pagination + Send + 'static>(
|
||||
&self,
|
||||
ctx: Context,
|
||||
channel: ChannelId,
|
||||
|
@ -25,7 +25,8 @@ impl ReactionWatcher {
|
|||
duration: std::time::Duration,
|
||||
) -> CommandResult {
|
||||
let handler = PaginationHandler::new(pager, ctx, channel)?;
|
||||
self.handle_reactions(handler, duration)
|
||||
self.handle_reactions(handler, duration, |_| {});
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// A version of `paginate` that compiles for closures.
|
||||
|
@ -39,7 +40,9 @@ impl ReactionWatcher {
|
|||
duration: std::time::Duration,
|
||||
) -> CommandResult
|
||||
where
|
||||
T: for<'a> FnMut(u8, &'a mut EditMessage) -> (&'a mut EditMessage, CommandResult),
|
||||
T: for<'a> FnMut(u8, &'a mut EditMessage) -> (&'a mut EditMessage, CommandResult)
|
||||
+ Send
|
||||
+ 'static,
|
||||
{
|
||||
self.paginate(ctx, channel, pager, duration)
|
||||
}
|
||||
|
|
|
@ -51,49 +51,55 @@ impl ReactionWatcher {
|
|||
/// React! to a series of reaction
|
||||
///
|
||||
/// The reactions stop after `duration` of idle.
|
||||
pub fn handle_reactions(
|
||||
pub fn handle_reactions<H: ReactionHandler + Send + 'static>(
|
||||
&self,
|
||||
mut h: impl ReactionHandler,
|
||||
mut h: H,
|
||||
duration: std::time::Duration,
|
||||
) -> CommandResult {
|
||||
callback: impl FnOnce(H) -> () + Send + 'static,
|
||||
) {
|
||||
let (send, reactions) = bounded(0);
|
||||
{
|
||||
self.channels.lock().expect("Poisoned!").push(send);
|
||||
}
|
||||
loop {
|
||||
let timeout = after(duration);
|
||||
let r = select! {
|
||||
recv(reactions) -> r => { let (r, is_added) = r.unwrap(); h.handle_reaction(&*r, is_added) },
|
||||
recv(timeout) -> _ => break,
|
||||
};
|
||||
if let Err(v) = r {
|
||||
dbg!(v);
|
||||
std::thread::spawn(move || {
|
||||
loop {
|
||||
let timeout = after(duration);
|
||||
let r = select! {
|
||||
recv(reactions) -> r => { let (r, is_added) = r.unwrap(); h.handle_reaction(&*r, is_added) },
|
||||
recv(timeout) -> _ => break,
|
||||
};
|
||||
if let Err(v) = r {
|
||||
dbg!(v);
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
callback(h)
|
||||
});
|
||||
}
|
||||
/// React! to a series of reaction
|
||||
///
|
||||
/// The handler will stop after `duration` no matter what.
|
||||
pub fn handle_reactions_timed(
|
||||
pub fn handle_reactions_timed<H: ReactionHandler + Send + 'static>(
|
||||
&self,
|
||||
mut h: impl ReactionHandler,
|
||||
mut h: H,
|
||||
duration: std::time::Duration,
|
||||
) -> CommandResult {
|
||||
callback: impl FnOnce(H) -> () + Send + 'static,
|
||||
) {
|
||||
let (send, reactions) = bounded(0);
|
||||
{
|
||||
self.channels.lock().expect("Poisoned!").push(send);
|
||||
}
|
||||
let timeout = after(duration);
|
||||
loop {
|
||||
let r = select! {
|
||||
recv(reactions) -> r => { let (r, is_added) = r.unwrap(); h.handle_reaction(&*r, is_added) },
|
||||
recv(timeout) -> _ => break,
|
||||
};
|
||||
if let Err(v) = r {
|
||||
dbg!(v);
|
||||
std::thread::spawn(move || {
|
||||
let timeout = after(duration);
|
||||
loop {
|
||||
let r = select! {
|
||||
recv(reactions) -> r => { let (r, is_added) = r.unwrap(); h.handle_reaction(&*r, is_added) },
|
||||
recv(timeout) -> _ => break,
|
||||
};
|
||||
if let Err(v) = r {
|
||||
dbg!(v);
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
callback(h);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue