soundboard cmd

This commit is contained in:
jellywx 2021-06-25 14:51:52 +01:00
parent f0590328b0
commit b4b8d16bcc
3 changed files with 154 additions and 92 deletions

View File

@ -1,7 +1,7 @@
use crate::{ use crate::{
framework::RegexFramework, framework::RegexFramework,
guild_data::CtxGuildData, guild_data::CtxGuildData,
join_channel, play_audio, join_channel, play_audio, play_cmd,
sound::{JoinSoundCtx, Sound}, sound::{JoinSoundCtx, Sound},
MySQL, ReqwestClient, MySQL, ReqwestClient,
}; };
@ -22,6 +22,9 @@ use serenity::{
use songbird::{Event, EventContext, EventHandler as SongbirdEventHandler}; 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}; use std::{collections::HashMap, env};
pub struct RestartTrack; 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) { async fn interaction_create(&self, ctx: Context, interaction: Interaction) {
let framework = ctx if interaction.guild_id.is_none() {
.data return;
.read() }
.await
.get::<RegexFramework>()
.cloned()
.expect("RegexFramework not found in context");
framework.execute(ctx, interaction).await; match interaction.kind {
InteractionType::ApplicationCommand => {
let framework = ctx
.data
.read()
.await
.get::<RegexFramework>()
.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();
}
}
_ => {}
}
} }
} }

View File

@ -10,14 +10,14 @@ use serenity::{
channel::{Channel, GuildChannel, Message}, channel::{Channel, GuildChannel, Message},
guild::{Guild, Member}, guild::{Guild, Member},
id::{ChannelId, GuildId, UserId}, id::{ChannelId, GuildId, UserId},
interactions::{ApplicationCommand, Interaction, InteractionData, InteractionType}, interactions::{ApplicationCommand, Interaction, InteractionData},
prelude::{ApplicationCommandOptionType, InteractionResponseType}, prelude::{ApplicationCommandOptionType, InteractionResponseType},
}, },
prelude::TypeMapKey, prelude::TypeMapKey,
Result as SerenityResult, Result as SerenityResult,
}; };
use log::{error, info, warn}; use log::{debug, error, info, warn};
use regex::{Match, Regex, RegexBuilder}; use regex::{Match, Regex, RegexBuilder};
@ -30,6 +30,7 @@ use std::{
use crate::{guild_data::CtxGuildData, MySQL}; use crate::{guild_data::CtxGuildData, MySQL};
use serde_json::Value; use serde_json::Value;
use serenity::builder::CreateComponents;
type CommandFn = for<'fut> fn( type CommandFn = for<'fut> fn(
&'fut Context, &'fut Context,
@ -38,7 +39,7 @@ type CommandFn = for<'fut> fn(
) -> BoxFuture<'fut, CommandResult>; ) -> BoxFuture<'fut, CommandResult>;
pub struct Args { pub struct Args {
args: HashMap<String, String>, pub args: HashMap<String, String>,
} }
impl Args { impl Args {
@ -87,6 +88,7 @@ impl Args {
pub struct CreateGenericResponse { pub struct CreateGenericResponse {
content: String, content: String,
embed: Option<CreateEmbed>, embed: Option<CreateEmbed>,
components: Option<CreateComponents>,
} }
impl CreateGenericResponse { impl CreateGenericResponse {
@ -94,6 +96,7 @@ impl CreateGenericResponse {
Self { Self {
content: "".to_string(), content: "".to_string(),
embed: None, embed: None,
components: None,
} }
} }
@ -112,6 +115,19 @@ impl CreateGenericResponse {
self self
} }
pub fn components<F: FnOnce(&mut CreateComponents) -> &mut CreateComponents>(
mut self,
f: F,
) -> Self {
let mut components = CreateComponents::default();
f(&mut components);
self.components = Some(components);
self
}
} }
#[async_trait] #[async_trait]
@ -178,6 +194,10 @@ impl CommandInvoke for Message {
m.set_embed(embed.clone()); m.set_embed(embed.clone());
} }
if let Some(components) = generic_response.components {
m.set_components(components.clone());
}
m m
}) })
.await .await
@ -197,6 +217,10 @@ impl CommandInvoke for Message {
m.set_embed(embed.clone()); m.set_embed(embed.clone());
} }
if let Some(components) = generic_response.components {
m.set_components(components.clone());
}
m m
}) })
.await .await
@ -252,6 +276,10 @@ impl CommandInvoke for Interaction {
d.add_embed(embed.clone()); d.add_embed(embed.clone());
} }
if let Some(components) = generic_response.components {
d.set_components(components.clone());
}
d d
}) })
}) })
@ -271,6 +299,10 @@ impl CommandInvoke for Interaction {
d.add_embed(embed.clone()); d.add_embed(embed.clone());
} }
if let Some(components) = generic_response.components {
d.set_components(components.clone());
}
d d
}) })
.await .await
@ -453,8 +485,6 @@ impl RegexFramework {
} }
pub fn add_command(mut self, command: &'static Command) -> Self { pub fn add_command(mut self, command: &'static Command) -> Self {
info!("{:?}", command);
self.commands_.insert(command); self.commands_.insert(command);
for name in command.names { for name in command.names {
@ -475,7 +505,7 @@ impl RegexFramework {
command_names = command_names_vec.join("|"); 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<prefix>\S{1,5}?))(?P<cmd>COMMANDS)(?:$|\s+(?P<args>.*))$"# let match_string = r#"^(?:(?:<@ID>\s*)|(?:<@!ID>\s*)|(?P<prefix>\S{1,5}?))(?P<cmd>COMMANDS)(?:$|\s+(?P<args>.*))$"#
@ -534,7 +564,7 @@ impl RegexFramework {
.await .await
.expect("Failed to fetch existing commands"); .expect("Failed to fetch existing commands");
info!("Existing commands: {:?}", current_commands); debug!("Existing commands: {:?}", current_commands);
// delete commands not in use // delete commands not in use
for command in &current_commands { for command in &current_commands {
@ -613,64 +643,61 @@ impl RegexFramework {
} }
pub async fn execute(&self, ctx: Context, interaction: Interaction) { 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 = {
if let Some(InteractionData::ApplicationCommand(data)) = interaction.data.clone() { let name = data.name;
let command = {
let name = data.name;
self.commands self.commands
.get(&name) .get(&name)
.expect(&format!("Received invalid command: {}", name)) .expect(&format!("Received invalid command: {}", name))
}; };
if command if command
.check_permissions( .check_permissions(
&ctx, &ctx,
&interaction.guild(ctx.cache.clone()).await.unwrap(), &interaction.guild(ctx.cache.clone()).await.unwrap(),
&interaction.member(&ctx).await.unwrap(), &interaction.member(&ctx).await.unwrap(),
) )
.await .await
{ {
let mut args = HashMap::new(); let mut args = HashMap::new();
for arg in data.options.iter().filter(|o| o.value.is_some()) { for arg in data.options.iter().filter(|o| o.value.is_some()) {
args.insert( args.insert(
arg.name.clone(), arg.name.clone(),
match arg.value.clone().unwrap() { match arg.value.clone().unwrap() {
Value::Bool(b) => { Value::Bool(b) => {
if b { if b {
arg.name.clone() arg.name.clone()
} else { } else {
String::new() String::new()
}
} }
Value::Number(n) => n.to_string(), }
Value::String(s) => s, Value::Number(n) => n.to_string(),
_ => String::new(), 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;
} }
(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;
} }
} }
} }

View File

@ -48,7 +48,6 @@ use dashmap::DashMap;
use std::{collections::HashMap, convert::TryFrom, env, sync::Arc, time::Duration}; use std::{collections::HashMap, convert::TryFrom, env, sync::Arc, time::Duration};
use serenity::model::prelude::InteractionResponseType;
use tokio::sync::{MutexGuard, RwLock}; use tokio::sync::{MutexGuard, RwLock};
struct MySQL; struct MySQL;
@ -1154,30 +1153,28 @@ async fn soundboard(
let sounds = Sound::get_guild_sounds(invoke.guild_id().unwrap(), pool).await?; 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 invoke
.channel_id() .respond(
.send_message(&ctx, |m| { ctx.http.clone(),
m.components(|c| { CreateGenericResponse::new()
for row in sounds.as_slice().chunks(5) { .content("Select a sound from below:")
let mut action_row: CreateActionRow = Default::default(); .components(|c| {
for sound in row { for row in sounds.as_slice().chunks(5) {
action_row.create_button(|b| { let mut action_row: CreateActionRow = Default::default();
b.style(ButtonStyle::Primary) for sound in row {
.label(&sound.name) action_row.create_button(|b| {
.custom_id(sound.id) b.style(ButtonStyle::Primary)
}); .label(&sound.name)
} .custom_id(sound.id)
} });
}
c c.add_action_row(action_row);
}) }
})
c
}),
)
.await?; .await?;
Ok(()) Ok(())