From 3e14455a973c06a203349b113d992e2e42f75693 Mon Sep 17 00:00:00 2001 From: CircuitSacul Date: Tue, 22 Nov 2022 01:09:23 -0500 Subject: [PATCH 1/2] working confirmation, confirm for starboard delete --- .../commands/chat/starboard/delete.rs | 17 ++- src/interactions/context.rs | 14 +++ src/utils/mod.rs | 1 + src/utils/views/confirm.rs | 107 ++++++++++++++++++ src/utils/views/mod.rs | 2 + src/utils/views/wait_for.rs | 63 +++++++++++ 6 files changed, 201 insertions(+), 3 deletions(-) create mode 100644 src/utils/views/confirm.rs create mode 100644 src/utils/views/mod.rs create mode 100644 src/utils/views/wait_for.rs diff --git a/src/interactions/commands/chat/starboard/delete.rs b/src/interactions/commands/chat/starboard/delete.rs index 3c5b02f9..234bb6f8 100644 --- a/src/interactions/commands/chat/starboard/delete.rs +++ b/src/interactions/commands/chat/starboard/delete.rs @@ -1,6 +1,9 @@ use twilight_interactions::command::{CommandModel, CreateCommand}; -use crate::{database::Starboard, get_guild_id, interactions::context::CommandCtx, unwrap_id}; +use crate::{ + database::Starboard, get_guild_id, interactions::context::CommandCtx, unwrap_id, + utils::views::confirm, +}; #[derive(CreateCommand, CommandModel)] #[command(name = "delete", desc = "Delete a starboard.")] @@ -13,9 +16,16 @@ pub struct DeleteStarboard { impl DeleteStarboard { pub async fn callback(self, mut ctx: CommandCtx) -> anyhow::Result<()> { let guild_id = get_guild_id!(ctx); + + let mut int_ctx = match confirm::simple(&mut ctx, "Are you sure?", true).await? { + None => return Ok(()), + Some(int_ctx) => int_ctx, + }; + let ret = Starboard::delete(&ctx.bot.pool, &self.name, unwrap_id!(guild_id)).await?; if ret.is_none() { - ctx.respond_str("No starboard with that name was found.", true) + int_ctx + .edit_str("No starboard with that name was found.", true) .await?; } else { ctx.bot @@ -27,7 +37,8 @@ impl DeleteStarboard { .cache .guild_vote_emojis .remove(&unwrap_id!(guild_id)); - ctx.respond_str(&format!("Deleted starboard '{}'.", self.name), false) + int_ctx + .edit_str(&format!("Deleted starboard '{}'.", self.name), true) .await?; } Ok(()) diff --git a/src/interactions/context.rs b/src/interactions/context.rs index 6b9b1126..19ad8ce0 100644 --- a/src/interactions/context.rs +++ b/src/interactions/context.rs @@ -126,4 +126,18 @@ impl Ctx { ) .await } + + pub async fn edit(&mut self, data: InteractionResponseData) -> TwResult { + self.raw_respond(Some(data), InteractionResponseType::UpdateMessage) + .await + } + + pub async fn edit_str(&mut self, response: &str, clear_comps: bool) -> TwResult { + let mut data = self.build_resp().content(response); + if clear_comps { + data = data.components([]); + } + + self.edit(data.build()).await + } } diff --git a/src/utils/mod.rs b/src/utils/mod.rs index 8f6d15aa..898a554e 100644 --- a/src/utils/mod.rs +++ b/src/utils/mod.rs @@ -8,3 +8,4 @@ pub mod gifv; pub mod into_id; pub mod notify; pub mod system_content; +pub mod views; diff --git a/src/utils/views/confirm.rs b/src/utils/views/confirm.rs new file mode 100644 index 00000000..f4eca967 --- /dev/null +++ b/src/utils/views/confirm.rs @@ -0,0 +1,107 @@ +use std::sync::Arc; + +use twilight_model::{ + channel::message::{ + component::{ActionRow, Button, ButtonStyle}, + Component, + }, + id::{ + marker::{MessageMarker, UserMarker}, + Id, + }, +}; + +use crate::{ + client::bot::StarboardBot, + errors::StarboardResult, + interactions::context::{CommandCtx, ComponentCtx}, +}; + +use super::wait_for::wait_for_button; + +pub fn components(danger: bool) -> Vec { + vec![Component::ActionRow(ActionRow { + components: vec![ + Component::Button(Button { + custom_id: Some("stateless::confirm_no".to_string()), + disabled: false, + emoji: None, + label: Some("Cancel".to_string()), + style: ButtonStyle::Secondary, + url: None, + }), + Component::Button(Button { + custom_id: Some("stateless::confirm_yes".to_string()), + disabled: false, + emoji: None, + label: Some("Confirm".to_string()), + style: if danger { + ButtonStyle::Danger + } else { + ButtonStyle::Primary + }, + url: None, + }), + ], + })] +} + +pub async fn wait_for_result( + bot: Arc, + message_id: Id, + user_id: Id, +) -> Option<(ComponentCtx, bool)> { + let int_ctx = wait_for_button( + bot, + &["stateless::confirm_yes", "stateless::confirm_no"], + message_id, + user_id, + ) + .await?; + + let conf = match &*int_ctx.data.custom_id { + "stateless::confirm_yes" => true, + "stateless::confirm_no" => false, + _ => unreachable!(), + }; + + Some((int_ctx, conf)) +} + +pub async fn simple( + ctx: &mut CommandCtx, + prompt: &str, + danger: bool, +) -> StarboardResult> { + let cmd = ctx + .build_resp() + .content(prompt) + .components(components(danger)) + .build(); + let msg = ctx.respond(cmd).await?.model().await.unwrap(); + let ret = wait_for_result( + ctx.bot.clone(), + msg.id, + ctx.interaction.author_id().unwrap(), + ) + .await; + + if let Some((mut int_ctx, conf)) = ret { + if conf { + return Ok(Some(int_ctx)); + } else { + int_ctx.edit_str("Canceled.", true).await?; + } + } else { + ctx.bot + .http + .update_message(msg.channel_id, msg.id) + .content(Some("Canceled.")) + .unwrap() + .components(Some(&[])) + .unwrap() + .await?; + } + + Ok(None) +} diff --git a/src/utils/views/mod.rs b/src/utils/views/mod.rs new file mode 100644 index 00000000..49bcba30 --- /dev/null +++ b/src/utils/views/mod.rs @@ -0,0 +1,2 @@ +pub mod confirm; +mod wait_for; diff --git a/src/utils/views/wait_for.rs b/src/utils/views/wait_for.rs new file mode 100644 index 00000000..be11c305 --- /dev/null +++ b/src/utils/views/wait_for.rs @@ -0,0 +1,63 @@ +use std::{sync::Arc, time::Duration}; + +use twilight_model::{ + application::interaction::{Interaction, InteractionData}, + id::{ + marker::{MessageMarker, UserMarker}, + Id, + }, +}; + +use crate::{client::bot::StarboardBot, interactions::context::ComponentCtx}; + +pub async fn wait_for_button( + bot: Arc, + button_ids: &'static [&'static str], + message_id: Id, + user_id: Id, +) -> Option { + let check = move |int: &Interaction| { + let data = match &int.data { + None => return false, + Some(data) => data, + }; + + let data = match data { + InteractionData::MessageComponent(data) => data, + _ => return false, + }; + + let msg = match &int.message { + None => return false, + Some(msg) => msg, + }; + + let int_user_id = match int.author_id() { + None => return false, + Some(user_id) => user_id, + }; + + { + msg.id == message_id && int_user_id == user_id && button_ids.contains(&&*data.custom_id) + } + }; + + let event = tokio::time::timeout( + Duration::from_secs(30), + bot.standby.wait_for_component(message_id, check), + ) + .await + .ok()? + .ok()?; + + let data = { + let data = &event.data.as_ref().unwrap(); + let data = match data { + InteractionData::MessageComponent(data) => data, + _ => unreachable!(), + }; + data.clone() + }; + + Some(ComponentCtx::new(0, bot, event, data)) +} From 8cbaf2574e0beb02f86d74fe6e814a1ecb30750d Mon Sep 17 00:00:00 2001 From: CircuitSacul Date: Tue, 22 Nov 2022 01:20:14 -0500 Subject: [PATCH 2/2] confirmation for autostar/override, rename var --- .../commands/chat/autostar/delete.rs | 22 ++++++++++++-- .../commands/chat/overrides/delete.rs | 29 +++++++++++++++---- .../commands/chat/starboard/delete.rs | 17 ++++++++--- src/utils/views/confirm.rs | 12 ++++---- 4 files changed, 62 insertions(+), 18 deletions(-) diff --git a/src/interactions/commands/chat/autostar/delete.rs b/src/interactions/commands/chat/autostar/delete.rs index 48480e30..e91a3bd2 100644 --- a/src/interactions/commands/chat/autostar/delete.rs +++ b/src/interactions/commands/chat/autostar/delete.rs @@ -2,6 +2,7 @@ use twilight_interactions::command::{CommandModel, CreateCommand}; use crate::{ database::AutoStarChannel, get_guild_id, interactions::context::CommandCtx, unwrap_id, + utils::views::confirm, }; #[derive(CreateCommand, CommandModel)] @@ -15,9 +16,25 @@ pub struct DeleteAutoStarChannel { impl DeleteAutoStarChannel { pub async fn callback(self, mut ctx: CommandCtx) -> anyhow::Result<()> { let guild_id = get_guild_id!(ctx); + + let mut btn_ctx = match confirm::simple( + &mut ctx, + &format!( + "Are you sure you want to delete the autostar channel '{}'?", + self.name + ), + true, + ) + .await? + { + None => return Ok(()), + Some(btn_ctx) => btn_ctx, + }; + let ret = AutoStarChannel::delete(&ctx.bot.pool, &self.name, unwrap_id!(guild_id)).await?; if ret.is_none() { - ctx.respond_str("No autostar channel with that name was found.", true) + btn_ctx + .edit_str("No autostar channel with that name was found.", true) .await?; } else { ctx.bot @@ -25,7 +42,8 @@ impl DeleteAutoStarChannel { .guild_autostar_channel_names .remove(&guild_id) .await; - ctx.respond_str(&format!("Deleted autostar channel '{}'.", self.name), false) + btn_ctx + .edit_str(&format!("Deleted autostar channel '{}'.", self.name), true) .await?; } Ok(()) diff --git a/src/interactions/commands/chat/overrides/delete.rs b/src/interactions/commands/chat/overrides/delete.rs index 4071285e..8bfa794a 100644 --- a/src/interactions/commands/chat/overrides/delete.rs +++ b/src/interactions/commands/chat/overrides/delete.rs @@ -2,6 +2,7 @@ use twilight_interactions::command::{CommandModel, CreateCommand}; use crate::{ database::StarboardOverride, get_guild_id, interactions::context::CommandCtx, unwrap_id, + utils::views::confirm, }; #[derive(CommandModel, CreateCommand)] @@ -16,15 +17,31 @@ impl DeleteOverride { pub async fn callback(self, mut ctx: CommandCtx) -> anyhow::Result<()> { let guild_id = unwrap_id!(get_guild_id!(ctx)); + let btn_ctx = confirm::simple( + &mut ctx, + &format!( + "Are you sure you want to delete the override '{}'?", + self.name + ), + true, + ) + .await?; + let mut btn_ctx = match btn_ctx { + None => return Ok(()), + Some(btn_ctx) => btn_ctx, + }; + let ov = StarboardOverride::delete(&ctx.bot.pool, guild_id, &self.name).await?; if ov.is_none() { - ctx.respond_str( - &format!("No override with the name '{}' exists.", self.name), - true, - ) - .await?; + btn_ctx + .edit_str( + &format!("No override with the name '{}' exists.", self.name), + true, + ) + .await?; } else { - ctx.respond_str(&format!("Deleted override '{}'.", self.name), false) + btn_ctx + .edit_str(&format!("Deleted override '{}'.", self.name), true) .await?; } diff --git a/src/interactions/commands/chat/starboard/delete.rs b/src/interactions/commands/chat/starboard/delete.rs index 234bb6f8..4bcc6db6 100644 --- a/src/interactions/commands/chat/starboard/delete.rs +++ b/src/interactions/commands/chat/starboard/delete.rs @@ -17,14 +17,23 @@ impl DeleteStarboard { pub async fn callback(self, mut ctx: CommandCtx) -> anyhow::Result<()> { let guild_id = get_guild_id!(ctx); - let mut int_ctx = match confirm::simple(&mut ctx, "Are you sure?", true).await? { + let mut btn_ctx = match confirm::simple( + &mut ctx, + &format!( + "Are you sure you want to delete the starboard '{}'?", + self.name + ), + true, + ) + .await? + { None => return Ok(()), - Some(int_ctx) => int_ctx, + Some(btn_ctx) => btn_ctx, }; let ret = Starboard::delete(&ctx.bot.pool, &self.name, unwrap_id!(guild_id)).await?; if ret.is_none() { - int_ctx + btn_ctx .edit_str("No starboard with that name was found.", true) .await?; } else { @@ -37,7 +46,7 @@ impl DeleteStarboard { .cache .guild_vote_emojis .remove(&unwrap_id!(guild_id)); - int_ctx + btn_ctx .edit_str(&format!("Deleted starboard '{}'.", self.name), true) .await?; } diff --git a/src/utils/views/confirm.rs b/src/utils/views/confirm.rs index f4eca967..fe4c3c38 100644 --- a/src/utils/views/confirm.rs +++ b/src/utils/views/confirm.rs @@ -51,7 +51,7 @@ pub async fn wait_for_result( message_id: Id, user_id: Id, ) -> Option<(ComponentCtx, bool)> { - let int_ctx = wait_for_button( + let btn_ctx = wait_for_button( bot, &["stateless::confirm_yes", "stateless::confirm_no"], message_id, @@ -59,13 +59,13 @@ pub async fn wait_for_result( ) .await?; - let conf = match &*int_ctx.data.custom_id { + let conf = match &*btn_ctx.data.custom_id { "stateless::confirm_yes" => true, "stateless::confirm_no" => false, _ => unreachable!(), }; - Some((int_ctx, conf)) + Some((btn_ctx, conf)) } pub async fn simple( @@ -86,11 +86,11 @@ pub async fn simple( ) .await; - if let Some((mut int_ctx, conf)) = ret { + if let Some((mut btn_ctx, conf)) = ret { if conf { - return Ok(Some(int_ctx)); + return Ok(Some(btn_ctx)); } else { - int_ctx.edit_str("Canceled.", true).await?; + btn_ctx.edit_str("Canceled.", true).await?; } } else { ctx.bot