replaced allow_slash with a method to disallow text commands for soundboard. made string argument selector stricter

This commit is contained in:
jellywx 2021-06-25 23:13:11 +01:00
parent 14ef6385a0
commit a38e4c808e
9 changed files with 164 additions and 47 deletions

View File

@ -5,7 +5,7 @@ use syn::parse::{Error, Result};
use syn::spanned::Spanned; use syn::spanned::Spanned;
use syn::{Attribute, Ident, Lit, LitStr, Meta, NestedMeta, Path}; use syn::{Attribute, Ident, Lit, LitStr, Meta, NestedMeta, Path};
use crate::structures::{ApplicationCommandOptionType, Arg, PermissionLevel}; use crate::structures::{ApplicationCommandOptionType, Arg, CommandKind, PermissionLevel};
use crate::util::{AsOption, LitExt}; use crate::util::{AsOption, LitExt};
#[derive(Debug, Clone, Copy, PartialEq)] #[derive(Debug, Clone, Copy, PartialEq)]
@ -321,6 +321,18 @@ impl AttributeOption for PermissionLevel {
} }
} }
impl AttributeOption for CommandKind {
fn parse(values: Values) -> Result<Self> {
validate(&values, &[ValueKind::SingleList])?;
Ok(values
.literals
.get(0)
.map(|(_, l)| CommandKind::from_str(&*l.to_str()).unwrap())
.unwrap())
}
}
impl AttributeOption for Arg { impl AttributeOption for Arg {
fn parse(values: Values) -> Result<Self> { fn parse(values: Values) -> Result<Self> {
validate(&values, &[ValueKind::EqualsList])?; validate(&values, &[ValueKind::EqualsList])?;

View File

@ -70,7 +70,7 @@ pub fn command(attr: TokenStream, input: TokenStream) -> TokenStream {
aliases; aliases;
group; group;
required_permissions; required_permissions;
allow_slash kind
]); ]);
} }
} }
@ -82,7 +82,7 @@ pub fn command(attr: TokenStream, input: TokenStream) -> TokenStream {
group, group,
examples, examples,
required_permissions, required_permissions,
allow_slash, kind,
mut cmd_args, mut cmd_args,
} = options; } = options;
@ -152,7 +152,7 @@ pub fn command(attr: TokenStream, input: TokenStream) -> TokenStream {
group: #group, group: #group,
examples: &[#(#examples),*], examples: &[#(#examples),*],
required_permissions: #required_permissions, required_permissions: #required_permissions,
allow_slash: #allow_slash, kind: #kind,
args: &[#(&#arg_idents),*], args: &[#(&#arg_idents),*],
}; };

View File

@ -214,6 +214,55 @@ impl ToTokens for PermissionLevel {
} }
} }
#[derive(Debug)]
pub enum CommandKind {
Slash,
Both,
Text,
}
impl Default for CommandKind {
fn default() -> Self {
Self::Both
}
}
impl CommandKind {
pub fn from_str(s: &str) -> Option<Self> {
Some(match s.to_uppercase().as_str() {
"SLASH" => Self::Slash,
"BOTH" => Self::Both,
"TEXT" => Self::Text,
_ => return None,
})
}
}
impl ToTokens for CommandKind {
fn to_tokens(&self, stream: &mut TokenStream2) {
let path = quote!(crate::framework::CommandKind);
let variant;
match self {
Self::Slash => {
variant = quote!(Slash);
}
Self::Both => {
variant = quote!(Both);
}
Self::Text => {
variant = quote!(Text);
}
}
stream.extend(quote! {
#path::#variant
});
}
}
#[derive(Debug)] #[derive(Debug)]
pub(crate) enum ApplicationCommandOptionType { pub(crate) enum ApplicationCommandOptionType {
SubCommand, SubCommand,
@ -293,7 +342,7 @@ pub(crate) struct Options {
pub group: String, pub group: String,
pub examples: Vec<String>, pub examples: Vec<String>,
pub required_permissions: PermissionLevel, pub required_permissions: PermissionLevel,
pub allow_slash: bool, pub kind: CommandKind,
pub cmd_args: Vec<Arg>, pub cmd_args: Vec<Arg>,
} }
@ -301,7 +350,6 @@ impl Options {
#[inline] #[inline]
pub fn new() -> Self { pub fn new() -> Self {
Self { Self {
allow_slash: true,
group: "Other".to_string(), group: "Other".to_string(),
..Default::default() ..Default::default()
} }

View File

@ -7,6 +7,7 @@ use crate::{
THEME_COLOR, THEME_COLOR,
}; };
use crate::framework::CommandKind;
use std::{collections::HashMap, sync::Arc}; use std::{collections::HashMap, sync::Arc};
#[command] #[command]
@ -62,6 +63,28 @@ pub async fn help(
) )
}; };
let args = if command.args.is_empty() {
"**Arguments**
*This command has no arguments*"
.to_string()
} else {
format!(
"**Arguments**
{}",
command
.args
.iter()
.map(|a| format!(
" • `{}` {} - {}",
a.name,
if a.required { "" } else { "[optional]" },
a.description
))
.collect::<Vec<String>>()
.join("\n")
)
};
invoke invoke
.respond( .respond(
ctx.http.clone(), ctx.http.clone(),
@ -69,15 +92,28 @@ pub async fn help(
e.title(format!("{} Help", command_name)) e.title(format!("{} Help", command_name))
.color(THEME_COLOR) .color(THEME_COLOR)
.description(format!( .description(format!(
"**Aliases** "**Available In**
`Slash Commands` {}
` Text Commands` {}
**Aliases**
{} {}
**Overview** **Overview**
{} {}
**Arguments**
{} {}
{}", {}",
if command.kind != CommandKind::Text {
""
} else {
""
},
if command.kind != CommandKind::Slash {
""
} else {
""
},
command command
.names .names
.iter() .iter()
@ -85,17 +121,7 @@ pub async fn help(
.collect::<Vec<String>>() .collect::<Vec<String>>()
.join(" "), .join(" "),
command.desc, command.desc,
command args,
.args
.iter()
.map(|a| format!(
" • `{}` {} - {}",
a.name,
if a.required { "" } else { "[optional]" },
a.description
))
.collect::<Vec<String>>()
.join("\n"),
examples examples
)) ))
}), }),

View File

@ -186,6 +186,7 @@ pub async fn upload_new_sound(
kind = "String", kind = "String",
required = true required = true
)] )]
#[example("`/delete beep` - delete the sound with the name \"beep\"")]
pub async fn delete_sound( pub async fn delete_sound(
ctx: &Context, ctx: &Context,
invoke: &(dyn CommandInvoke + Sync + Send), invoke: &(dyn CommandInvoke + Sync + Send),
@ -278,6 +279,8 @@ pub async fn delete_sound(
description = "Sound name or ID to change the privacy setting of", description = "Sound name or ID to change the privacy setting of",
required = true required = true
)] )]
#[example("`/public 12` - change sound with ID 12 to private")]
#[example("`/public 12` - change sound with ID 12 back to public")]
pub async fn change_public( pub async fn change_public(
ctx: &Context, ctx: &Context,
invoke: &(dyn CommandInvoke + Sync + Send), invoke: &(dyn CommandInvoke + Sync + Send),

View File

@ -184,14 +184,14 @@ __Available ambience sounds:__
} }
#[command("soundboard")] #[command("soundboard")]
#[aliases("board")]
#[group("Play")] #[group("Play")]
#[kind(Slash)]
#[description("Get a menu of sounds with buttons to play them")] #[description("Get a menu of sounds with buttons to play them")]
#[arg( #[arg(
name = "1", name = "1",
description = "Query for sound button 1", description = "Query for sound button 1",
kind = "String", kind = "String",
required = false required = true
)] )]
#[arg( #[arg(
name = "2", name = "2",

View File

@ -40,6 +40,8 @@ fn format_search_results(search_results: Vec<Sound>) -> CreateGenericResponse {
kind = "Boolean", kind = "Boolean",
required = false required = false
)] )]
#[example("`/list` - list sounds uploaded to the server you're in")]
#[example("`/list [me: True]` - list sounds you have uploaded across all servers")]
pub async fn list_sounds( pub async fn list_sounds(
ctx: &Context, ctx: &Context,
invoke: &(dyn CommandInvoke + Sync + Send), invoke: &(dyn CommandInvoke + Sync + Send),

View File

@ -20,6 +20,9 @@ use crate::{
kind = "Integer", kind = "Integer",
required = false required = false
)] )]
#[example("`/volume` - check the volume on the current server")]
#[example("`/volume 100` - reset the volume on the current server")]
#[example("`/volume 10` - set the volume on the current server to 10%")]
pub async fn change_volume( pub async fn change_volume(
ctx: &Context, ctx: &Context,
invoke: &(dyn CommandInvoke + Sync + Send), invoke: &(dyn CommandInvoke + Sync + Send),
@ -66,7 +69,7 @@ pub async fn change_volume(
#[command("prefix")] #[command("prefix")]
#[required_permissions(Restricted)] #[required_permissions(Restricted)]
#[allow_slash(false)] #[kind(Text)]
#[group("Settings")] #[group("Settings")]
#[description("Change the prefix of the bot for using non-slash commands")] #[description("Change the prefix of the bot for using non-slash commands")]
#[arg( #[arg(
@ -97,7 +100,7 @@ pub async fn change_prefix(
} }
if let Some(prefix) = args.named("prefix") { if let Some(prefix) = args.named("prefix") {
if prefix.len() <= 5 { if prefix.len() <= 5 && !prefix.is_empty() {
let reply = format!("Prefix changed to `{}`", prefix); let reply = format!("Prefix changed to `{}`", prefix);
{ {
@ -142,7 +145,7 @@ pub async fn change_prefix(
#[command("roles")] #[command("roles")]
#[required_permissions(Restricted)] #[required_permissions(Restricted)]
#[allow_slash(false)] #[kind(Text)]
#[group("Settings")] #[group("Settings")]
#[description("Change the roles allowed to use the bot")] #[description("Change the roles allowed to use the bot")]
pub async fn set_allowed_roles( pub async fn set_allowed_roles(
@ -240,6 +243,8 @@ INSERT INTO roles (guild_id, role)
description = "Name or ID of sound to set as your greet sound", description = "Name or ID of sound to set as your greet sound",
required = false required = false
)] )]
#[example("`/greet` - remove your join sound")]
#[example("`/greet 1523` - set your join sound to sound with ID 1523")]
pub async fn set_greet_sound( pub async fn set_greet_sound(
ctx: &Context, ctx: &Context,
invoke: &(dyn CommandInvoke + Sync + Send), invoke: &(dyn CommandInvoke + Sync + Send),
@ -312,6 +317,8 @@ pub async fn set_greet_sound(
#[group("Settings")] #[group("Settings")]
#[description("Configure whether users should be able to use join sounds")] #[description("Configure whether users should be able to use join sounds")]
#[required_permissions(Restricted)] #[required_permissions(Restricted)]
#[example("`/allow_greet` - disable greet sounds in the server")]
#[example("`/allow_greet` - re-enable greet sounds in the server")]
pub async fn allow_greet_sounds( pub async fn allow_greet_sounds(
ctx: &Context, ctx: &Context,
invoke: &(dyn CommandInvoke + Sync + Send), invoke: &(dyn CommandInvoke + Sync + Send),

View File

@ -309,6 +309,13 @@ pub enum PermissionLevel {
Restricted, Restricted,
} }
#[derive(Debug, PartialEq)]
pub enum CommandKind {
Slash,
Both,
Text,
}
#[derive(Debug)] #[derive(Debug)]
pub struct Arg { pub struct Arg {
pub name: &'static str, pub name: &'static str,
@ -320,7 +327,7 @@ pub struct Arg {
impl Arg { impl Arg {
pub fn to_regex(&self) -> String { pub fn to_regex(&self) -> String {
match self.kind { match self.kind {
ApplicationCommandOptionType::String => format!(r#"(?P<{}>.*?)"#, self.name), ApplicationCommandOptionType::String => format!(r#"(?P<{}>.+?)"#, self.name),
ApplicationCommandOptionType::Integer => format!(r#"(?P<{}>\d+)"#, self.name), ApplicationCommandOptionType::Integer => format!(r#"(?P<{}>\d+)"#, self.name),
ApplicationCommandOptionType::Boolean => format!(r#"(?P<{0}>{0})?"#, self.name), ApplicationCommandOptionType::Boolean => format!(r#"(?P<{0}>{0})?"#, self.name),
ApplicationCommandOptionType::User => format!(r#"<(@|@!)(?P<{}>\d+)>"#, self.name), ApplicationCommandOptionType::User => format!(r#"<(@|@!)(?P<{}>\d+)>"#, self.name),
@ -343,7 +350,7 @@ pub struct Command {
pub examples: &'static [&'static str], pub examples: &'static [&'static str],
pub group: &'static str, pub group: &'static str,
pub allow_slash: bool, pub kind: CommandKind,
pub required_permissions: PermissionLevel, pub required_permissions: PermissionLevel,
pub args: &'static [&'static Arg], pub args: &'static [&'static Arg],
} }
@ -528,7 +535,11 @@ impl RegexFramework {
.flatten() .flatten()
.map(|v| GuildId(v)) .map(|v| GuildId(v))
{ {
for command in self.commands_.iter().filter(|c| c.allow_slash) { for command in self
.commands_
.iter()
.filter(|c| c.kind != CommandKind::Text)
{
guild_id guild_id
.create_application_command(&http, |a| { .create_application_command(&http, |a| {
a.name(command.names[0]).description(command.desc); a.name(command.names[0]).description(command.desc);
@ -577,7 +588,11 @@ impl RegexFramework {
} }
} }
for command in self.commands_.iter().filter(|c| c.allow_slash) { for command in self
.commands_
.iter()
.filter(|c| c.kind != CommandKind::Text)
{
let already_created = if let Some(current_command) = current_commands let already_created = if let Some(current_command) = current_commands
.iter() .iter()
.find(|curr| curr.name == command.names[0]) .find(|curr| curr.name == command.names[0])
@ -752,27 +767,31 @@ impl Framework for RegexFramework {
.get(&full_match.name("cmd").unwrap().as_str().to_lowercase()) .get(&full_match.name("cmd").unwrap().as_str().to_lowercase())
.unwrap(); .unwrap();
let args = full_match if command.kind != CommandKind::Slash {
.name("args") let args = full_match
.map(|m| m.as_str()) .name("args")
.unwrap_or("") .map(|m| m.as_str())
.to_string(); .unwrap_or("")
.to_string();
let member = guild.member(&ctx, &msg.author).await.unwrap(); let member = guild.member(&ctx, &msg.author).await.unwrap();
if command.check_permissions(&ctx, &guild, &member).await { if command.check_permissions(&ctx, &guild, &member).await {
(command.fun)(&ctx, &msg, Args::from(&args, command.args)) (command.fun)(&ctx, &msg, Args::from(&args, command.args))
.await .await
.unwrap(); .unwrap();
} else if command.required_permissions == PermissionLevel::Managed { } else if command.required_permissions
let _ = msg.channel_id.say(&ctx, "You must either be an Admin or have a role specified in `?roles` to do this command").await; == PermissionLevel::Managed
} else if command.required_permissions {
== PermissionLevel::Restricted let _ = msg.channel_id.say(&ctx, "You must either be an Admin or have a role specified in `?roles` to do this command").await;
{ } else if command.required_permissions
let _ = msg == PermissionLevel::Restricted
.channel_id {
.say(&ctx, "You must be an Admin to do this command") let _ = msg
.await; .channel_id
.say(&ctx, "You must be an Admin to do this command")
.await;
}
} }
} }