diff options
| author | Mishio595 <[email protected]> | 2018-11-13 22:23:39 -0700 |
|---|---|---|
| committer | Mishio595 <[email protected]> | 2018-11-13 22:23:39 -0700 |
| commit | 1dad6996d8e9c983dc9e1689a93dbef5b0696c22 (patch) | |
| tree | 1ee7226178cae76b20e6adcdc7e7cfcc3c477c32 /src/framework | |
| parent | Finish rebase (diff) | |
| parent | Make `Region`s `Japan`-variant lowercase. (diff) | |
| download | serenity-1dad6996d8e9c983dc9e1689a93dbef5b0696c22.tar.xz serenity-1dad6996d8e9c983dc9e1689a93dbef5b0696c22.zip | |
Merge branch 'upstream'
Diffstat (limited to 'src/framework')
| -rw-r--r-- | src/framework/standard/args.rs | 45 | ||||
| -rw-r--r-- | src/framework/standard/command.rs | 15 | ||||
| -rw-r--r-- | src/framework/standard/configuration.rs | 20 | ||||
| -rw-r--r-- | src/framework/standard/create_command.rs | 16 | ||||
| -rw-r--r-- | src/framework/standard/create_group.rs | 19 | ||||
| -rw-r--r-- | src/framework/standard/create_help_command.rs | 6 | ||||
| -rw-r--r-- | src/framework/standard/help_commands.rs | 13 | ||||
| -rw-r--r-- | src/framework/standard/mod.rs | 130 |
8 files changed, 228 insertions, 36 deletions
diff --git a/src/framework/standard/args.rs b/src/framework/standard/args.rs index 2700bfb..6841bd7 100644 --- a/src/framework/standard/args.rs +++ b/src/framework/standard/args.rs @@ -348,6 +348,51 @@ impl Args { self.args.get(self.offset).map(|t| t.lit.as_str()) } + /// Trims the current argument off leading and trailing whitespace. + /// + /// # Examples + /// + /// ```rust + /// use serenity::framework::standard::Args; + /// + /// let mut args = Args::new(" 42 ", &[]); + /// + /// assert_eq!(args.trim().current(), Some("42")); + /// ``` + pub fn trim(&mut self) -> &mut Self { + if self.is_empty() { + return self; + } + + self.args[self.offset].lit = self.args[self.offset].lit.trim().to_string(); + + self + } + + /// Trims all of the arguments after the offset off leading and trailing whitespace. + /// + /// # Examples + /// + /// ```rust + /// use serenity::framework::standard::Args; + /// + /// let mut args = Args::new(" 42 , 84 ,\t168\t", &[",".to_string()]); + /// + /// args.trim_all(); + /// assert_eq!(args.single::<String>().unwrap(), "42"); + /// assert_eq!(args.single::<String>().unwrap(), "84"); + /// assert_eq!(args.single::<String>().unwrap(), "168"); + /// ``` + pub fn trim_all(&mut self) { + if self.is_empty() { + return; + } + + for token in &mut self.args[self.offset..] { + token.lit = token.lit.trim().to_string(); + } + } + /// Parses the current argument and advances. /// /// # Examples diff --git a/src/framework/standard/command.rs b/src/framework/standard/command.rs index 2b1149e..5345f4b 100644 --- a/src/framework/standard/command.rs +++ b/src/framework/standard/command.rs @@ -60,6 +60,7 @@ impl HelpCommand for Help { pub type BeforeHook = Fn(&mut Context, &Message, &str) -> bool + Send + Sync + 'static; pub type AfterHook = Fn(&mut Context, &Message, &str, Result<(), Error>) + Send + Sync + 'static; pub type UnrecognisedCommandHook = Fn(&mut Context, &Message, &str) + Send + Sync + 'static; +pub type MessageWithoutCommandHook = Fn(&mut Context, &Message) + Send + Sync + 'static; pub(crate) type InternalCommand = Arc<Command>; pub type PrefixCheck = Fn(&mut Context, &Message) -> Option<String> + Send + Sync + 'static; @@ -81,7 +82,7 @@ impl fmt::Debug for CommandOrAlias { #[derive(Clone, Debug)] pub struct Error(pub String); -// TODO: Have seperate `From<(&)String>` and `From<&str>` impls via specialization +// TODO: Have separate `From<(&)String>` and `From<&str>` impls via specialization impl<D: fmt::Display> From<D> for Error { fn from(d: D) -> Self { Error(d.to_string()) @@ -99,6 +100,7 @@ pub struct CommandGroup { pub help_available: bool, pub dm_only: bool, pub guild_only: bool, + pub owner_privileges: bool, pub owners_only: bool, pub help: Option<Arc<Help>>, /// A set of checks to be called prior to executing the command-group. The checks @@ -117,6 +119,7 @@ impl Default for CommandGroup { required_permissions: Permissions::empty(), dm_only: false, guild_only: false, + owner_privileges: true, help_available: true, owners_only: false, allowed_roles: Vec::new(), @@ -141,7 +144,7 @@ pub struct CommandOptions { pub example: Option<String>, /// Command usage schema, used by other commands. pub usage: Option<String>, - /// Minumum amount of arguments that should be passed. + /// Minimum amount of arguments that should be passed. pub min_args: Option<i32>, /// Maximum amount of arguments that can be passed. pub max_args: Option<i32>, @@ -155,6 +158,8 @@ pub struct CommandOptions { pub dm_only: bool, /// Whether command can be used only in guilds or not. pub guild_only: bool, + /// Whether the command treats owners as normal users. + pub owner_privileges: bool, /// Whether command can only be used by owners or not. pub owners_only: bool, /// Other names that can be used to call this command instead. @@ -216,7 +221,7 @@ pub struct HelpOptions { pub wrong_channel: HelpBehaviour, /// Colour help-embed will use upon encountering an error. pub embed_error_colour: Colour, - /// Colour help-embed will use if no error occured. + /// Colour help-embed will use if no error occurred. pub embed_success_colour: Colour, /// If not 0, help will check whether a command is similar to searched named. pub max_levenshtein_distance: usize, @@ -335,6 +340,7 @@ impl Default for CommandOptions { required_permissions: Permissions::empty(), dm_only: false, guild_only: false, + owner_privileges: true, help_available: true, owners_only: false, allowed_roles: Vec::new(), @@ -374,7 +380,8 @@ pub fn positions(ctx: &mut Context, msg: &Message, conf: &Configuration) -> Opti // If the above do not fill `positions`, then that means no kind of prefix was present. // Check if a no-prefix-execution is applicable. - if conf.no_dm_prefix && private && positions.is_empty() { + if conf.no_dm_prefix && private && positions.is_empty() && + !(conf.ignore_bots && msg.author.bot) { positions.push(0); } } diff --git a/src/framework/standard/configuration.rs b/src/framework/standard/configuration.rs index 795493d..a8ef075 100644 --- a/src/framework/standard/configuration.rs +++ b/src/framework/standard/configuration.rs @@ -6,9 +6,10 @@ use model::{ }; use std::{ collections::HashSet, - default::Default + default::Default, + sync::Arc, }; -use super::command::PrefixCheck; +use super::command::{Command, InternalCommand, PrefixCheck}; /// The configuration to use for a [`StandardFramework`] associated with a [`Client`] /// instance. @@ -59,6 +60,7 @@ pub struct Configuration { #[doc(hidden)] pub no_dm_prefix: bool, #[doc(hidden)] pub delimiters: Vec<String>, #[doc(hidden)] pub case_insensitive: bool, + #[doc(hidden)] pub prefix_only_cmd: Option<InternalCommand>, } impl Configuration { @@ -448,7 +450,7 @@ impl Configuration { /// /// # Examples /// - /// Have the args be seperated by a comma and a space: + /// Have the args be separated by a comma and a space: /// /// ```rust,no_run /// # use serenity::prelude::*; @@ -475,7 +477,7 @@ impl Configuration { /// /// # Examples /// - /// Have the args be seperated by a comma and a space; and a regular space: + /// Have the args be separated by a comma and a space; and a regular space: /// /// ```rust,no_run /// # use serenity::prelude::*; @@ -511,6 +513,15 @@ impl Configuration { self } + + /// Sets a command to dispatch if user's input is a prefix only. + /// + /// **Note**: Defaults to no command and ignores prefix only. + pub fn prefix_only_cmd<C: Command + 'static>(mut self, c: C) -> Self { + self.prefix_only_cmd = Some(Arc::new(c)); + + self + } } impl Default for Configuration { @@ -550,6 +561,7 @@ impl Default for Configuration { on_mention: None, owners: HashSet::default(), prefixes: vec![], + prefix_only_cmd: None, } } } diff --git a/src/framework/standard/create_command.rs b/src/framework/standard/create_command.rs index a769d4c..3976f6d 100644 --- a/src/framework/standard/create_command.rs +++ b/src/framework/standard/create_command.rs @@ -168,6 +168,14 @@ impl CreateCommand { self } + /// Whether owners shall bypass buckets, missing permissions, + /// wrong channels, missing roles, and checks. + pub fn owner_privileges(mut self, owner_privileges: bool) -> Self { + self.0.owner_privileges = owner_privileges; + + self + } + /// Whether command should be displayed in help list or not, used by other commands. pub fn help_available(mut self, help_available: bool) -> Self { self.0.help_available = help_available; @@ -189,7 +197,7 @@ impl CreateCommand { self } - /// Minumum amount of arguments that should be passed. + /// Minimum amount of arguments that should be passed. pub fn min_args(mut self, min_args: i32) -> Self { self.0.min_args = Some(min_args); @@ -236,7 +244,7 @@ impl CreateCommand { /// Sets an initialise middleware to be called upon the command's actual registration. /// - /// This is similiar to implementing the `init` function on `Command`. + /// This is similar to implementing the `init` function on `Command`. pub fn init<F: Fn() + Send + Sync + 'static>(mut self, f: F) -> Self { self.2.init = Some(Arc::new(f)); @@ -245,7 +253,7 @@ impl CreateCommand { /// Sets a before middleware to be called before the command's execution. /// - /// This is similiar to implementing the `before` function on `Command`. + /// This is similar to implementing the `before` function on `Command`. pub fn before<F: Send + Sync + 'static>(mut self, f: F) -> Self where F: Fn(&mut Context, &Message) -> bool { self.2.before = Some(Arc::new(f)); @@ -255,7 +263,7 @@ impl CreateCommand { /// Sets an after middleware to be called after the command's execution. /// - /// This is similiar to implementing the `after` function on `Command`. + /// This is similar to implementing the `after` function on `Command`. pub fn after<F: Send + Sync + 'static>(mut self, f: F) -> Self where F: Fn(&mut Context, &Message, &Result<(), CommandError>) { self.2.after = Some(Arc::new(f)); diff --git a/src/framework/standard/create_group.rs b/src/framework/standard/create_group.rs index 3cd4047..0049cf3 100644 --- a/src/framework/standard/create_group.rs +++ b/src/framework/standard/create_group.rs @@ -43,7 +43,8 @@ impl CreateGroup { .dm_only(self.0.dm_only) .guild_only(self.0.guild_only) .help_available(self.0.help_available) - .owners_only(self.0.owners_only); + .owners_only(self.0.owners_only) + .owner_privileges(self.0.owner_privileges); if let Some(ref bucket) = self.0.bucket { cmd = cmd.bucket(bucket); @@ -151,6 +152,14 @@ impl CreateGroup { self } + /// Whether owners shall bypass buckets, missing permissions, + /// wrong channels, missing roles, and checks. + pub fn owner_privileges(mut self, owner_privileges: bool) -> Self { + self.0.owner_privileges = owner_privileges; + + self + } + /// Whether command should be displayed in help list or not, used by other commands. pub fn help_available(mut self, help_available: bool) -> Self { self.0.help_available = help_available; @@ -198,11 +207,11 @@ impl CreateGroup { /// Adds a command for a group that will be executed if no command-name /// has been passed. pub fn default_cmd<C: Command + 'static>(mut self, c: C) -> Self { - let cmd: Arc<Command> = Arc::new(c); - - self.0.default_command = Some(CommandOrAlias::Command(Arc::clone(&cmd))); + c.init(); - cmd.init(); + let cmd_with_group_options = self.build_command().cmd(c).finish(); + let cmd_finished = CommandOrAlias::Command(cmd_with_group_options); + self.0.default_command = Some(cmd_finished); self } diff --git a/src/framework/standard/create_help_command.rs b/src/framework/standard/create_help_command.rs index b8eb57a..c05cb32 100644 --- a/src/framework/standard/create_help_command.rs +++ b/src/framework/standard/create_help_command.rs @@ -126,7 +126,7 @@ impl CreateHelpCommand { self } - /// Sets how the group-prexix shall be labeled. + /// Sets how the group-prefix shall be labeled. pub fn group_prefix(mut self, text: &str) -> Self { self.0.group_prefix = text.to_string(); @@ -204,14 +204,14 @@ impl CreateHelpCommand { self } - /// Sets the colour for the embed if no error occured. + /// Sets the colour for the embed if no error occurred. pub fn embed_success_colour(mut self, colour: Colour) -> Self { self.0.embed_success_colour = colour; self } - /// Sets the colour for the embed if an error occured. + /// Sets the colour for the embed if an error occurred. pub fn embed_error_colour(mut self, colour: Colour) -> Self { self.0.embed_error_colour = colour; diff --git a/src/framework/standard/help_commands.rs b/src/framework/standard/help_commands.rs index ca8fdc6..94c9127 100644 --- a/src/framework/standard/help_commands.rs +++ b/src/framework/standard/help_commands.rs @@ -112,8 +112,8 @@ impl Suggestions { &self.0 } - /// Concats names of suggestions with a given `seperator`. - fn join(&self, seperator: &str) -> String { + /// Concats names of suggestions with a given `separator`. + fn join(&self, separator: &str) -> String { let mut iter = self.as_vec().iter(); let first_iter_element = match iter.next() { @@ -122,12 +122,12 @@ impl Suggestions { }; let size = self.as_vec().iter().fold(0, |total_size, size| total_size + size.name.len()); - let byte_len_of_sep = self.as_vec().len().checked_sub(1).unwrap_or(0) * seperator.len(); + let byte_len_of_sep = self.as_vec().len().checked_sub(1).unwrap_or(0) * separator.len(); let mut result = String::with_capacity(size + byte_len_of_sep); result.push_str(first_iter_element.name.borrow()); for element in iter { - result.push_str(&*seperator); + result.push_str(&*separator); result.push_str(element.name.borrow()); } @@ -303,6 +303,7 @@ pub fn is_command_visible(command_options: &Arc<CommandOptions>, msg: &Message, /// Tries to extract a single command matching searched command name otherwise /// returns similar commands. +#[cfg(feature = "cache")] fn fetch_single_command<'a, H: BuildHasher>( groups: &'a HashMap<String, Arc<CommandGroup>, H>, name: &str, @@ -423,6 +424,7 @@ fn fetch_single_command<'a, H: BuildHasher>( } /// Tries to extract a single command matching searched command name. +#[cfg(feature = "cache")] fn fetch_all_eligible_commands_in_group<'a>( commands: &HashMap<&String, &InternalCommand>, command_names: &[&&String], @@ -471,6 +473,7 @@ fn fetch_all_eligible_commands_in_group<'a>( } /// Fetch groups with their commands. +#[cfg(feature = "cache")] fn create_command_group_commands_pair_from_groups<'a, H: BuildHasher>( groups: &'a HashMap<String, Arc<CommandGroup>, H>, group_names: &[&'a String], @@ -498,6 +501,7 @@ fn create_command_group_commands_pair_from_groups<'a, H: BuildHasher>( } /// Fetches a single group with its commands. +#[cfg(feature = "cache")] fn create_single_group<'a>( group: &CommandGroup, group_name: &'a str, @@ -527,6 +531,7 @@ let commands = remove_aliases(&group.commands); /// Iterates over all commands and forges them into a `CustomisedHelpData` /// taking `HelpOptions` into consideration when deciding on whether a command /// shall be picked and in what textual format. +#[cfg(feature = "cache")] pub fn create_customised_help_data<'a, H: BuildHasher>( groups: &'a HashMap<String, Arc<CommandGroup>, H>, args: &'a Args, diff --git a/src/framework/standard/mod.rs b/src/framework/standard/mod.rs index aa32d11..162871d 100644 --- a/src/framework/standard/mod.rs +++ b/src/framework/standard/mod.rs @@ -39,7 +39,7 @@ use model::{ id::{ChannelId, GuildId, UserId}, Permissions }; -use self::command::{AfterHook, BeforeHook, UnrecognisedCommandHook}; +use self::command::{AfterHook, BeforeHook, MessageWithoutCommandHook, UnrecognisedCommandHook}; use std::{ collections::HashMap, default::Default, @@ -217,6 +217,7 @@ pub struct StandardFramework { buckets: HashMap<String, Bucket>, after: Option<Arc<AfterHook>>, unrecognised_command: Option<Arc<UnrecognisedCommandHook>>, + message_without_command: Option<Arc<MessageWithoutCommandHook>>, /// Whether the framework has been "initialized". /// /// The framework is initialized once one of the following occurs: @@ -532,7 +533,7 @@ impl StandardFramework { } } - if self.configuration.owners.contains(&message.author.id) { + if command.owner_privileges && self.configuration.owners.contains(&message.author.id) { return None; } @@ -949,9 +950,34 @@ impl StandardFramework { self } + /// Specify the function to be called if a message contains no command. + /// + /// # Examples + /// + /// Using `message_without_command`: + /// + /// ```rust,no_run + /// # use serenity::prelude::*; + /// # struct Handler; + /// # + /// # impl EventHandler for Handler {} + /// # let mut client = Client::new("token", Handler).unwrap(); + /// # + /// use serenity::framework::StandardFramework; + /// + /// client.with_framework(StandardFramework::new() + /// .message_without_command(|ctx, msg| { })); + /// ``` + pub fn message_without_command<F>(mut self, f: F) -> Self + where F: Fn(&mut Context, &Message) + Send + Sync + 'static { + self.message_without_command = Some(Arc::new(f)); + + self + } + /// Sets what code should be executed when a user sends `(prefix)help`. /// - /// If a command named `help` was set with [`command`], then this takes precendence first. + /// If a command named `help` was set with [`command`], then this takes precedence first. /// /// [`command`]: #method.command pub fn help(mut self, f: HelpFunction) -> Self { @@ -1004,12 +1030,54 @@ impl Framework for StandardFramework { // Ensure that there is _at least one_ position remaining. There // is no point in continuing if there is not. if positions.is_empty() { + + if let Some(ref prefix_only_cmd) = + self.configuration.prefix_only_cmd { + let prefix_only_cmd = Arc::clone(prefix_only_cmd); + let before = self.before.clone(); + let after = self.after.clone(); + + threadpool.execute(move || { + if let Some(before) = before { + if !(before)(&mut context, &message, "") { + return; + } + } + + if !prefix_only_cmd.before(&mut context, &message) { + return; + } + + let result = prefix_only_cmd.execute(&mut context, + &message, Args::new("", &Vec::new())); + + prefix_only_cmd.after(&mut context, &message, + &result); + + if let Some(after) = after { + (after)(&mut context, &message, "", result); + } + }); + } + return; } positions }, - None => return, + None => { + if let &Some(ref message_without_command) = &self.message_without_command { + + if !(self.configuration.ignore_bots && message.author.bot) { + let message_without_command = message_without_command.clone(); + threadpool.execute(move || { + (message_without_command)(&mut context, &message); + }); + } + } + + return; + }, }; 'outer: for position in positions { @@ -1160,6 +1228,21 @@ impl Framework for StandardFramework { Args::new(&orginal_round[longest_matching_prefix_len..], &self.configuration.delimiters) }; + if let Some(error) = self.should_fail( + &mut context, + &message, + &command.options(), + &group, + &mut args, + &to_check, + &built, + ) { + if let Some(ref handler) = self.dispatch_error_handler { + handler(context, message, error); + } + return; + } + threadpool.execute(move || { if let Some(before) = before { if !(before)(&mut context, &message, &args.full()) { @@ -1190,9 +1273,33 @@ impl Framework for StandardFramework { if !(self.configuration.ignore_bots && message.author.bot) { if let &Some(ref unrecognised_command) = &self.unrecognised_command { - let unrecognised_command = unrecognised_command.clone(); - threadpool.execute(move || { - (unrecognised_command)(&mut context, &message, &unrecognised_command_name); + + // If both functions are set, we need to clone `Context` and + // `Message`, else we can avoid it. + if let &Some(ref message_without_command) = &self.message_without_command { + let mut context_unrecognised = context.clone(); + let message_unrecognised = message.clone(); + + let unrecognised_command = unrecognised_command.clone(); + threadpool.execute(move || { + (unrecognised_command)(&mut context_unrecognised, &message_unrecognised, + &unrecognised_command_name); + }); + + let message_without_command = message_without_command.clone(); + threadpool.execute(move || { + (message_without_command)(&mut context, &message); + }); + } else { + let unrecognised_command = unrecognised_command.clone(); + threadpool.execute(move || { + (unrecognised_command)(&mut context, &message, &unrecognised_command_name); + }); + } + } else if let &Some(ref message_without_command) = &self.message_without_command { + let message_without_command = message_without_command.clone(); + threadpool.execute(move || { + (message_without_command)(&mut context, &message); }); } } @@ -1205,8 +1312,9 @@ impl Framework for StandardFramework { #[cfg(feature = "cache")] pub fn has_correct_permissions(command: &Arc<CommandOptions>, message: &Message) -> bool { - if !command.required_permissions.is_empty() { - + if command.required_permissions.is_empty() { + true + } else { if let Some(guild) = message.guild() { let perms = guild .with(|g| g.permissions_in(message.channel_id, message.author.id)); @@ -1215,8 +1323,6 @@ pub fn has_correct_permissions(command: &Arc<CommandOptions>, message: &Message) } else { false } - } else { - true } } @@ -1238,7 +1344,7 @@ pub fn has_correct_roles(cmd: &Arc<CommandOptions>, guild: &Guild, member: &Memb /// The command can't be used in the current channel (as in `DM only` or `guild only`). #[derive(PartialEq, Debug)] pub enum HelpBehaviour { - /// Strikes a command by applying `~~{comand_name}~~`. + /// Strikes a command by applying `~~{command_name}~~`. Strike, /// Does not list a command in the help-menu. Hide, |