diff --git a/regex_command_attr/src/attributes.rs b/regex_command_attr/src/attributes.rs index 856eefd..f9522b3 100644 --- a/regex_command_attr/src/attributes.rs +++ b/regex_command_attr/src/attributes.rs @@ -5,7 +5,7 @@ use syn::parse::{Error, Result}; use syn::spanned::Spanned; 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}; #[derive(Debug, Clone, Copy, PartialEq)] @@ -321,6 +321,18 @@ impl AttributeOption for PermissionLevel { } } +impl AttributeOption for CommandKind { + fn parse(values: Values) -> Result { + validate(&values, &[ValueKind::SingleList])?; + + Ok(values + .literals + .get(0) + .map(|(_, l)| CommandKind::from_str(&*l.to_str()).unwrap()) + .unwrap()) + } +} + impl AttributeOption for Arg { fn parse(values: Values) -> Result { validate(&values, &[ValueKind::EqualsList])?; diff --git a/regex_command_attr/src/lib.rs b/regex_command_attr/src/lib.rs index be33c48..2ae6b0f 100644 --- a/regex_command_attr/src/lib.rs +++ b/regex_command_attr/src/lib.rs @@ -70,7 +70,7 @@ pub fn command(attr: TokenStream, input: TokenStream) -> TokenStream { aliases; group; required_permissions; - allow_slash + kind ]); } } @@ -82,7 +82,7 @@ pub fn command(attr: TokenStream, input: TokenStream) -> TokenStream { group, examples, required_permissions, - allow_slash, + kind, mut cmd_args, } = options; @@ -152,7 +152,7 @@ pub fn command(attr: TokenStream, input: TokenStream) -> TokenStream { group: #group, examples: &[#(#examples),*], required_permissions: #required_permissions, - allow_slash: #allow_slash, + kind: #kind, args: &[#(&#arg_idents),*], }; diff --git a/regex_command_attr/src/structures.rs b/regex_command_attr/src/structures.rs index 36e9331..302a0b0 100644 --- a/regex_command_attr/src/structures.rs +++ b/regex_command_attr/src/structures.rs @@ -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 { + 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)] pub(crate) enum ApplicationCommandOptionType { SubCommand, @@ -293,7 +342,7 @@ pub(crate) struct Options { pub group: String, pub examples: Vec, pub required_permissions: PermissionLevel, - pub allow_slash: bool, + pub kind: CommandKind, pub cmd_args: Vec, } @@ -301,7 +350,6 @@ impl Options { #[inline] pub fn new() -> Self { Self { - allow_slash: true, group: "Other".to_string(), ..Default::default() } diff --git a/src/cmds/info.rs b/src/cmds/info.rs index 7b82717..b5843c6 100644 --- a/src/cmds/info.rs +++ b/src/cmds/info.rs @@ -7,6 +7,7 @@ use crate::{ THEME_COLOR, }; +use crate::framework::CommandKind; use std::{collections::HashMap, sync::Arc}; #[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::>() + .join("\n") + ) + }; + invoke .respond( ctx.http.clone(), @@ -69,15 +92,28 @@ pub async fn help( e.title(format!("{} Help", command_name)) .color(THEME_COLOR) .description(format!( - "**Aliases** + "**Available In** +`Slash Commands` {} +` Text Commands` {} + +**Aliases** {} **Overview** • {} -**Arguments** {} {}", + if command.kind != CommandKind::Text { + "✅" + } else { + "❎" + }, + if command.kind != CommandKind::Slash { + "✅" + } else { + "❎" + }, command .names .iter() @@ -85,17 +121,7 @@ pub async fn help( .collect::>() .join(" "), command.desc, - command - .args - .iter() - .map(|a| format!( - " • `{}` {} - {}", - a.name, - if a.required { "" } else { "[optional]" }, - a.description - )) - .collect::>() - .join("\n"), + args, examples )) }), diff --git a/src/cmds/manage.rs b/src/cmds/manage.rs index d22eb24..594b4b6 100644 --- a/src/cmds/manage.rs +++ b/src/cmds/manage.rs @@ -186,6 +186,7 @@ pub async fn upload_new_sound( kind = "String", required = true )] +#[example("`/delete beep` - delete the sound with the name \"beep\"")] pub async fn delete_sound( ctx: &Context, 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", 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( ctx: &Context, invoke: &(dyn CommandInvoke + Sync + Send), diff --git a/src/cmds/play.rs b/src/cmds/play.rs index 2151145..d3e6b37 100644 --- a/src/cmds/play.rs +++ b/src/cmds/play.rs @@ -184,14 +184,14 @@ __Available ambience sounds:__ } #[command("soundboard")] -#[aliases("board")] #[group("Play")] +#[kind(Slash)] #[description("Get a menu of sounds with buttons to play them")] #[arg( name = "1", description = "Query for sound button 1", kind = "String", - required = false + required = true )] #[arg( name = "2", diff --git a/src/cmds/search.rs b/src/cmds/search.rs index 2335cb5..9a77985 100644 --- a/src/cmds/search.rs +++ b/src/cmds/search.rs @@ -40,6 +40,8 @@ fn format_search_results(search_results: Vec) -> CreateGenericResponse { kind = "Boolean", 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( ctx: &Context, invoke: &(dyn CommandInvoke + Sync + Send), diff --git a/src/cmds/settings.rs b/src/cmds/settings.rs index 6d41f7f..ddfb97e 100644 --- a/src/cmds/settings.rs +++ b/src/cmds/settings.rs @@ -20,6 +20,9 @@ use crate::{ kind = "Integer", 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( ctx: &Context, invoke: &(dyn CommandInvoke + Sync + Send), @@ -66,7 +69,7 @@ pub async fn change_volume( #[command("prefix")] #[required_permissions(Restricted)] -#[allow_slash(false)] +#[kind(Text)] #[group("Settings")] #[description("Change the prefix of the bot for using non-slash commands")] #[arg( @@ -97,7 +100,7 @@ pub async fn change_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); { @@ -142,7 +145,7 @@ pub async fn change_prefix( #[command("roles")] #[required_permissions(Restricted)] -#[allow_slash(false)] +#[kind(Text)] #[group("Settings")] #[description("Change the roles allowed to use the bot")] 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", 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( ctx: &Context, invoke: &(dyn CommandInvoke + Sync + Send), @@ -312,6 +317,8 @@ pub async fn set_greet_sound( #[group("Settings")] #[description("Configure whether users should be able to use join sounds")] #[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( ctx: &Context, invoke: &(dyn CommandInvoke + Sync + Send), diff --git a/src/framework.rs b/src/framework.rs index 9bb93f2..13beec0 100644 --- a/src/framework.rs +++ b/src/framework.rs @@ -309,6 +309,13 @@ pub enum PermissionLevel { Restricted, } +#[derive(Debug, PartialEq)] +pub enum CommandKind { + Slash, + Both, + Text, +} + #[derive(Debug)] pub struct Arg { pub name: &'static str, @@ -320,7 +327,7 @@ pub struct Arg { impl Arg { pub fn to_regex(&self) -> String { 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::Boolean => format!(r#"(?P<{0}>{0})?"#, self.name), ApplicationCommandOptionType::User => format!(r#"<(@|@!)(?P<{}>\d+)>"#, self.name), @@ -343,7 +350,7 @@ pub struct Command { pub examples: &'static [&'static str], pub group: &'static str, - pub allow_slash: bool, + pub kind: CommandKind, pub required_permissions: PermissionLevel, pub args: &'static [&'static Arg], } @@ -528,7 +535,11 @@ impl RegexFramework { .flatten() .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 .create_application_command(&http, |a| { 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 .iter() .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()) .unwrap(); - let args = full_match - .name("args") - .map(|m| m.as_str()) - .unwrap_or("") - .to_string(); + if command.kind != CommandKind::Slash { + let args = full_match + .name("args") + .map(|m| m.as_str()) + .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 { - (command.fun)(&ctx, &msg, Args::from(&args, command.args)) - .await - .unwrap(); - } else if command.required_permissions == PermissionLevel::Managed { - 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 - == PermissionLevel::Restricted - { - let _ = msg - .channel_id - .say(&ctx, "You must be an Admin to do this command") - .await; + if command.check_permissions(&ctx, &guild, &member).await { + (command.fun)(&ctx, &msg, Args::from(&args, command.args)) + .await + .unwrap(); + } else if command.required_permissions + == PermissionLevel::Managed + { + 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 + == PermissionLevel::Restricted + { + let _ = msg + .channel_id + .say(&ctx, "You must be an Admin to do this command") + .await; + } } }