From b4b8d16bcc3c208bda1c69255993e724d18ff2e5 Mon Sep 17 00:00:00 2001 From: jellywx Date: Fri, 25 Jun 2021 14:51:52 +0100 Subject: [PATCH] soundboard cmd --- src/event_handlers.rs | 56 +++++++++++++--- src/framework.rs | 147 +++++++++++++++++++++++++----------------- src/main.rs | 43 ++++++------ 3 files changed, 154 insertions(+), 92 deletions(-) diff --git a/src/event_handlers.rs b/src/event_handlers.rs index e0f39cf..6b3be41 100644 --- a/src/event_handlers.rs +++ b/src/event_handlers.rs @@ -1,7 +1,7 @@ use crate::{ framework::RegexFramework, guild_data::CtxGuildData, - join_channel, play_audio, + join_channel, play_audio, play_cmd, sound::{JoinSoundCtx, Sound}, MySQL, ReqwestClient, }; @@ -22,6 +22,9 @@ use serenity::{ use songbird::{Event, EventContext, EventHandler as SongbirdEventHandler}; +use crate::framework::{Args, CommandInvoke}; +use serenity::model::interactions::{InteractionData, InteractionType}; +use serenity::model::prelude::InteractionResponseType; use std::{collections::HashMap, env}; pub struct RestartTrack; @@ -180,14 +183,49 @@ SELECT name, id, plays, public, server_id, uploader_id } async fn interaction_create(&self, ctx: Context, interaction: Interaction) { - let framework = ctx - .data - .read() - .await - .get::() - .cloned() - .expect("RegexFramework not found in context"); + if interaction.guild_id.is_none() { + return; + } - framework.execute(ctx, interaction).await; + match interaction.kind { + InteractionType::ApplicationCommand => { + let framework = ctx + .data + .read() + .await + .get::() + .cloned() + .expect("RegexFramework not found in context"); + + framework.execute(ctx, interaction).await; + } + InteractionType::MessageComponent => { + if let (Some(InteractionData::MessageComponent(data)), Some(member)) = + (interaction.clone().data, interaction.clone().member) + { + let mut args = Args { + args: Default::default(), + }; + args.args.insert("query".to_string(), data.custom_id); + + play_cmd( + &ctx, + interaction.guild(ctx.cache.clone()).await.unwrap(), + member.user.id, + args, + false, + ) + .await; + + interaction + .create_interaction_response(ctx, |r| { + r.kind(InteractionResponseType::DeferredUpdateMessage) + }) + .await + .unwrap(); + } + } + _ => {} + } } } diff --git a/src/framework.rs b/src/framework.rs index 11388a2..a177b7c 100644 --- a/src/framework.rs +++ b/src/framework.rs @@ -10,14 +10,14 @@ use serenity::{ channel::{Channel, GuildChannel, Message}, guild::{Guild, Member}, id::{ChannelId, GuildId, UserId}, - interactions::{ApplicationCommand, Interaction, InteractionData, InteractionType}, + interactions::{ApplicationCommand, Interaction, InteractionData}, prelude::{ApplicationCommandOptionType, InteractionResponseType}, }, prelude::TypeMapKey, Result as SerenityResult, }; -use log::{error, info, warn}; +use log::{debug, error, info, warn}; use regex::{Match, Regex, RegexBuilder}; @@ -30,6 +30,7 @@ use std::{ use crate::{guild_data::CtxGuildData, MySQL}; use serde_json::Value; +use serenity::builder::CreateComponents; type CommandFn = for<'fut> fn( &'fut Context, @@ -38,7 +39,7 @@ type CommandFn = for<'fut> fn( ) -> BoxFuture<'fut, CommandResult>; pub struct Args { - args: HashMap, + pub args: HashMap, } impl Args { @@ -87,6 +88,7 @@ impl Args { pub struct CreateGenericResponse { content: String, embed: Option, + components: Option, } impl CreateGenericResponse { @@ -94,6 +96,7 @@ impl CreateGenericResponse { Self { content: "".to_string(), embed: None, + components: None, } } @@ -112,6 +115,19 @@ impl CreateGenericResponse { self } + + pub fn components &mut CreateComponents>( + mut self, + f: F, + ) -> Self { + let mut components = CreateComponents::default(); + + f(&mut components); + + self.components = Some(components); + + self + } } #[async_trait] @@ -178,6 +194,10 @@ impl CommandInvoke for Message { m.set_embed(embed.clone()); } + if let Some(components) = generic_response.components { + m.set_components(components.clone()); + } + m }) .await @@ -197,6 +217,10 @@ impl CommandInvoke for Message { m.set_embed(embed.clone()); } + if let Some(components) = generic_response.components { + m.set_components(components.clone()); + } + m }) .await @@ -252,6 +276,10 @@ impl CommandInvoke for Interaction { d.add_embed(embed.clone()); } + if let Some(components) = generic_response.components { + d.set_components(components.clone()); + } + d }) }) @@ -271,6 +299,10 @@ impl CommandInvoke for Interaction { d.add_embed(embed.clone()); } + if let Some(components) = generic_response.components { + d.set_components(components.clone()); + } + d }) .await @@ -453,8 +485,6 @@ impl RegexFramework { } pub fn add_command(mut self, command: &'static Command) -> Self { - info!("{:?}", command); - self.commands_.insert(command); for name in command.names { @@ -475,7 +505,7 @@ impl RegexFramework { command_names = command_names_vec.join("|"); } - info!("Command names: {}", command_names); + debug!("Command names: {}", command_names); { let match_string = r#"^(?:(?:<@ID>\s*)|(?:<@!ID>\s*)|(?P\S{1,5}?))(?PCOMMANDS)(?:$|\s+(?P.*))$"# @@ -534,7 +564,7 @@ impl RegexFramework { .await .expect("Failed to fetch existing commands"); - info!("Existing commands: {:?}", current_commands); + debug!("Existing commands: {:?}", current_commands); // delete commands not in use for command in ¤t_commands { @@ -613,64 +643,61 @@ impl RegexFramework { } pub async fn execute(&self, ctx: Context, interaction: Interaction) { - if interaction.kind == InteractionType::ApplicationCommand && interaction.guild_id.is_some() - { - if let Some(InteractionData::ApplicationCommand(data)) = interaction.data.clone() { - let command = { - let name = data.name; + if let Some(InteractionData::ApplicationCommand(data)) = interaction.data.clone() { + let command = { + let name = data.name; - self.commands - .get(&name) - .expect(&format!("Received invalid command: {}", name)) - }; + self.commands + .get(&name) + .expect(&format!("Received invalid command: {}", name)) + }; - if command - .check_permissions( - &ctx, - &interaction.guild(ctx.cache.clone()).await.unwrap(), - &interaction.member(&ctx).await.unwrap(), - ) - .await - { - let mut args = HashMap::new(); + if command + .check_permissions( + &ctx, + &interaction.guild(ctx.cache.clone()).await.unwrap(), + &interaction.member(&ctx).await.unwrap(), + ) + .await + { + let mut args = HashMap::new(); - for arg in data.options.iter().filter(|o| o.value.is_some()) { - args.insert( - arg.name.clone(), - match arg.value.clone().unwrap() { - Value::Bool(b) => { - if b { - arg.name.clone() - } else { - String::new() - } + for arg in data.options.iter().filter(|o| o.value.is_some()) { + args.insert( + arg.name.clone(), + match arg.value.clone().unwrap() { + Value::Bool(b) => { + if b { + arg.name.clone() + } else { + String::new() } - Value::Number(n) => n.to_string(), - Value::String(s) => s, - _ => String::new(), - }, - ); - } - - (command.fun)(&ctx, &interaction, Args { args }) - .await - .unwrap(); - } else if command.required_permissions == PermissionLevel::Managed { - let _ = interaction - .respond( - ctx.http.clone(), - CreateGenericResponse::new().content("You must either be an Admin or have a role specified in `?roles` to do this command") - ) - .await; - } else if command.required_permissions == PermissionLevel::Restricted { - let _ = interaction - .respond( - ctx.http.clone(), - CreateGenericResponse::new() - .content("You must be an Admin to do this command"), - ) - .await; + } + Value::Number(n) => n.to_string(), + Value::String(s) => s, + _ => String::new(), + }, + ); } + + (command.fun)(&ctx, &interaction, Args { args }) + .await + .unwrap(); + } else if command.required_permissions == PermissionLevel::Managed { + let _ = interaction + .respond( + ctx.http.clone(), + CreateGenericResponse::new().content("You must either be an Admin or have a role specified in `?roles` to do this command") + ) + .await; + } else if command.required_permissions == PermissionLevel::Restricted { + let _ = interaction + .respond( + ctx.http.clone(), + CreateGenericResponse::new() + .content("You must be an Admin to do this command"), + ) + .await; } } } diff --git a/src/main.rs b/src/main.rs index d56e2d8..0929b2e 100644 --- a/src/main.rs +++ b/src/main.rs @@ -48,7 +48,6 @@ use dashmap::DashMap; use std::{collections::HashMap, convert::TryFrom, env, sync::Arc, time::Duration}; -use serenity::model::prelude::InteractionResponseType; use tokio::sync::{MutexGuard, RwLock}; struct MySQL; @@ -1154,30 +1153,28 @@ async fn soundboard( let sounds = Sound::get_guild_sounds(invoke.guild_id().unwrap(), pool).await?; - if let Some(interaction) = invoke.interaction() { - interaction - .create_interaction_response(&ctx, |r| r.kind(InteractionResponseType::Pong)) - .await?; - } - invoke - .channel_id() - .send_message(&ctx, |m| { - m.components(|c| { - for row in sounds.as_slice().chunks(5) { - let mut action_row: CreateActionRow = Default::default(); - for sound in row { - action_row.create_button(|b| { - b.style(ButtonStyle::Primary) - .label(&sound.name) - .custom_id(sound.id) - }); - } - } + .respond( + ctx.http.clone(), + CreateGenericResponse::new() + .content("Select a sound from below:") + .components(|c| { + for row in sounds.as_slice().chunks(5) { + let mut action_row: CreateActionRow = Default::default(); + for sound in row { + action_row.create_button(|b| { + b.style(ButtonStyle::Primary) + .label(&sound.name) + .custom_id(sound.id) + }); + } - c - }) - }) + c.add_action_row(action_row); + } + + c + }), + ) .await?; Ok(())