diff --git a/youmubot-osu/src/discord/interaction.rs b/youmubot-osu/src/discord/interaction.rs index 67c8f13..1b8a533 100644 --- a/youmubot-osu/src/discord/interaction.rs +++ b/youmubot-osu/src/discord/interaction.rs @@ -2,8 +2,9 @@ use std::pin::Pin; use future::Future; use serenity::all::{ - ComponentInteractionDataKind, CreateActionRow, CreateButton, CreateInteractionResponse, - CreateInteractionResponseFollowup, CreateInteractionResponseMessage, GuildId, Interaction, + ComponentInteraction, ComponentInteractionDataKind, CreateActionRow, CreateButton, + CreateInteractionResponse, CreateInteractionResponseFollowup, CreateInteractionResponseMessage, + GuildId, Interaction, }; use youmubot_prelude::*; @@ -52,14 +53,9 @@ pub fn handle_check_button<'a>( interaction: &'a Interaction, ) -> Pin> + Send + 'a>> { Box::pin(async move { - let comp = match interaction.as_message_component() { - Some(comp) - if comp.data.custom_id == BTN_CHECK - && matches!(comp.data.kind, ComponentInteractionDataKind::Button) => - { - comp - } - _ => return Ok(()), + let comp = match expect_and_defer_button(ctx, interaction, BTN_CHECK).await? { + Some(comp) => comp, + None => return Ok(()), }; let msg = &*comp.message; @@ -70,46 +66,46 @@ pub fn handle_check_button<'a>( let user = match env.saved_users.by_user_id(comp.user.id).await? { Some(u) => u, None => { - comp.create_response(&ctx, CreateInteractionResponse::Message(CreateInteractionResponseMessage::new().content("You don't have a saved account yet! Save your osu! account by `y2!osu save `.").ephemeral(true))).await?; + comp.create_followup(&ctx, CreateInteractionResponseFollowup::new().content("You don't have a saved account yet! Save your osu! account by `y2!osu save `.").ephemeral(true)).await?; return Ok(()); } }; let scores = super::do_check(&env, &bm, Mods::NOMOD, &crate::UserID::ID(user.id)).await?; if scores.is_empty() { - comp.create_response( + comp.create_followup( &ctx, - CreateInteractionResponse::Message( - CreateInteractionResponseMessage::new().content(format!( - "No plays found for [`{}`]() on `{}`.", - user.username, - user.id, - bm.short_link(Mods::NOMOD) - )), - ), + CreateInteractionResponseFollowup::new().content(format!( + "No plays found for [`{}`]() on `{}`.", + user.username, + user.id, + bm.short_link(Mods::NOMOD) + )), ) .await?; return Ok(()); } - let reply = { - comp.create_response( + let reply = comp + .create_followup( &ctx, - serenity::all::CreateInteractionResponse::Message( - CreateInteractionResponseMessage::new().content(format!( - "Here are the scores by [`{}`]() on `{}`!", - user.username, - user.id, - bm.short_link(Mods::NOMOD) - )), - ), + CreateInteractionResponseFollowup::new().content(format!( + "Here are the scores by [`{}`]() on `{}`!", + user.username, + user.id, + bm.short_link(Mods::NOMOD) + )), ) .await?; - comp.get_response(&ctx).await? - }; - ScoreListStyle::Grid - .display_scores(scores, bm.1, ctx, comp.guild_id, reply) - .await?; + + let ctx = ctx.clone(); + let guild_id = comp.guild_id; + spawn_future(async move { + ScoreListStyle::Grid + .display_scores(scores, bm.1, &ctx, guild_id, reply) + .await + .pls_ok(); + }); Ok(()) }) @@ -129,14 +125,9 @@ pub fn handle_last_button<'a>( interaction: &'a Interaction, ) -> Pin> + Send + 'a>> { Box::pin(async move { - let comp = match interaction.as_message_component() { - Some(comp) - if comp.data.custom_id == BTN_LAST - && matches!(comp.data.kind, ComponentInteractionDataKind::Button) => - { - comp - } - _ => return Ok(()), + let comp = match expect_and_defer_button(ctx, interaction, BTN_LAST).await? { + Some(comp) => comp, + None => return Ok(()), }; let msg = &*comp.message; @@ -153,17 +144,15 @@ pub fn handle_last_button<'a>( .get_beatmap(b.beatmap_id) .await? .get_possible_pp_with(*m, &mods)?; - comp.create_response( + comp.create_followup( &ctx, - serenity::all::CreateInteractionResponse::Message( - CreateInteractionResponseMessage::new() - .content(format!( - "Information for beatmap `{}`", - bm.short_link(&mods) - )) - .embed(beatmap_embed(b, *m, &mods, info)) - .components(vec![beatmap_components(comp.guild_id)]), - ), + serenity::all::CreateInteractionResponseFollowup::new() + .content(format!( + "Information for beatmap `{}`", + bm.short_link(&mods) + )) + .embed(beatmap_embed(b, *m, &mods, info)) + .components(vec![beatmap_components(comp.guild_id)]), ) .await?; // Save the beatmap... @@ -187,14 +176,9 @@ pub fn handle_lb_button<'a>( interaction: &'a Interaction, ) -> Pin> + Send + 'a>> { Box::pin(async move { - let comp = match interaction.as_message_component() { - Some(comp) - if comp.data.custom_id == BTN_LB - && matches!(comp.data.kind, ComponentInteractionDataKind::Button) => - { - comp - } - _ => return Ok(()), + let comp = match expect_and_defer_button(ctx, interaction, BTN_LB).await? { + Some(comp) => comp, + None => return Ok(()), }; let msg = &*comp.message; @@ -206,11 +190,6 @@ pub fn handle_lb_button<'a>( let order = OrderBy::default(); let guild = comp.guild_id.expect("Guild-only command"); - comp.create_response( - &ctx, - CreateInteractionResponse::Defer(CreateInteractionResponseMessage::new()), - ) - .await?; let scores = get_leaderboard(ctx, &env, &bm, order, guild).await?; if scores.is_empty() { @@ -237,3 +216,25 @@ pub fn handle_lb_button<'a>( Ok(()) }) } + +async fn expect_and_defer_button<'a>( + ctx: &'_ Context, + interaction: &'a Interaction, + expect_id: &'static str, +) -> Result> { + let comp = match interaction.as_message_component() { + Some(comp) + if comp.data.custom_id == expect_id + && matches!(comp.data.kind, ComponentInteractionDataKind::Button) => + { + comp + } + _ => return Ok(None), + }; + comp.create_response( + &ctx, + CreateInteractionResponse::Defer(CreateInteractionResponseMessage::new()), + ) + .await?; + Ok(Some(comp)) +} diff --git a/youmubot-prelude/src/hook.rs b/youmubot-prelude/src/hook.rs index 96f4783..abe9589 100644 --- a/youmubot-prelude/src/hook.rs +++ b/youmubot-prelude/src/hook.rs @@ -26,13 +26,13 @@ where /// InteractionHook represents the asynchronous hook that is run on every interaction. #[async_trait] pub trait InteractionHook: Send + Sync { - async fn call(&mut self, ctx: &Context, interaction: &Interaction) -> Result<()>; + async fn call(&self, ctx: &Context, interaction: &Interaction) -> Result<()>; } #[async_trait] impl InteractionHook for T where - T: for<'a> FnMut( + T: for<'a> Fn( &'a Context, &'a Interaction, ) @@ -40,7 +40,7 @@ where + Send + Sync, { - async fn call(&mut self, ctx: &Context, interaction: &Interaction) -> Result<()> { + async fn call(&self, ctx: &Context, interaction: &Interaction) -> Result<()> { self(ctx, interaction).await } } diff --git a/youmubot/src/main.rs b/youmubot/src/main.rs index d2f2da6..85c6be2 100644 --- a/youmubot/src/main.rs +++ b/youmubot/src/main.rs @@ -21,7 +21,7 @@ mod compose_framework; struct Handler { hooks: Vec>>, - interaction_hooks: Vec>>, + interaction_hooks: Vec>, ready_hooks: Vec CommandResult>, } @@ -43,7 +43,7 @@ impl Handler { } fn push_interaction_hook(&mut self, f: T) { - self.interaction_hooks.push(RwLock::new(Box::new(f))); + self.interaction_hooks.push(Box::new(f)); } } @@ -113,10 +113,7 @@ impl EventHandler for Handler { let interaction = &interaction; self.interaction_hooks .iter() - .map(|hook| { - hook.write() - .then(|mut h| async move { h.call(ctx, interaction).await }) - }) + .map(|hook| hook.call(ctx, interaction)) .collect::>() .for_each(|v| async move { if let Err(e) = v {