aboutsummaryrefslogtreecommitdiff
path: root/src/ext/framework
diff options
context:
space:
mode:
authorIllia <[email protected]>2017-04-20 00:51:34 +0300
committerZeyla Hellyer <[email protected]>2017-04-19 14:51:34 -0700
commit31aae7d12763f94a7a08ea9fd0102921e8402241 (patch)
tree76e0b54954ee02140ad6bd409a73e16943bda5f8 /src/ext/framework
parentFix example in README (diff)
downloadserenity-31aae7d12763f94a7a08ea9fd0102921e8402241.tar.xz
serenity-31aae7d12763f94a7a08ea9fd0102921e8402241.zip
Update the way errors are handled in dispatch
Diffstat (limited to 'src/ext/framework')
-rw-r--r--src/ext/framework/configuration.rs149
-rw-r--r--src/ext/framework/mod.rs424
2 files changed, 172 insertions, 401 deletions
diff --git a/src/ext/framework/configuration.rs b/src/ext/framework/configuration.rs
index b5c58c9..46174b4 100644
--- a/src/ext/framework/configuration.rs
+++ b/src/ext/framework/configuration.rs
@@ -4,29 +4,6 @@ use super::command::PrefixCheck;
use ::client::{Context, rest};
use ::model::{GuildId, UserId};
-/// The account type used when setting the framework [`Configuration`].
-///
-/// [`Configuration`]: struct.Configuration.html
-pub enum AccountType {
- /// The current user will only react to [`Message`]s sent by itself.
- ///
- /// [`Message`]: ../../model/struct.Message.html
- Selfbot,
- /// The current user will only react to [`Message`]s sent by [`User`]s which
- /// are _not_ [bot][`User::bot`]s.
- ///
- /// [`Message`]: ../../model/struct.Message.html
- /// [`User`]: ../../model/struct.User.html
- /// [`User::bot`]: ../../model/struct.User.html#structfield.bot
- Bot,
- /// The current user will react to all [`Message`]s.
- ///
- /// [`Message`]: ../../model/struct.Message.html
- Any,
- #[doc(hidden)]
- Automatic,
-}
-
/// The configuration to use for a [`Framework`] associated with a [`Client`]
/// instance.
///
@@ -61,27 +38,7 @@ pub struct Configuration {
#[doc(hidden)]
pub dynamic_prefix: Option<Box<PrefixCheck>>,
#[doc(hidden)]
- pub rate_limit_message: Option<String>,
- #[doc(hidden)]
- pub invalid_permission_message: Option<String>,
- #[doc(hidden)]
- pub invalid_check_message: Option<String>,
- #[doc(hidden)]
- pub no_dm_message: Option<String>,
- #[doc(hidden)]
- pub no_guild_message: Option<String>,
- #[doc(hidden)]
- pub blocked_user_message: Option<String>,
- #[doc(hidden)]
- pub blocked_guild_message: Option<String>,
- #[doc(hidden)]
- pub too_many_args_message: Option<String>,
- #[doc(hidden)]
- pub not_enough_args_message: Option<String>,
- #[doc(hidden)]
- pub command_disabled_message: Option<String>,
- #[doc(hidden)]
- pub account_type: AccountType,
+ pub ignore_bots: bool,
#[doc(hidden)]
pub blocked_users: HashSet<UserId>,
#[doc(hidden)]
@@ -97,9 +54,11 @@ pub struct Configuration {
}
impl Configuration {
- /// Allows you to change what accounts to ignore.
- pub fn account_type(mut self, account_type: AccountType) -> Self {
- self.account_type = account_type;
+ /// Whether the bot should respond to other bots.
+ ///
+ /// For example, if this is set to false, then the bot will respond to any other bots including itself.
+ pub fn ignore_bots(mut self, ignore_bots: bool) -> Self {
+ self.ignore_bots = ignore_bots;
self
}
@@ -194,79 +153,6 @@ impl Configuration {
self
}
- /// Message that's sent when one of a command's checks doesn't succeed.
- pub fn invalid_check_message(mut self, content: &str) -> Self {
- self.invalid_check_message = Some(content.to_owned());
-
- self
- }
-
- /// Message that's sent if a command is disabled.
- ///
- /// `%command%` will be replaced with command name.
- pub fn command_disabled_message(mut self, content: &str) -> Self {
- self.command_disabled_message = Some(content.to_owned());
-
- self
- }
-
- /// Message that's sent if a blocked user attempts to use a command.
- pub fn blocked_user_message(mut self, content: &str) -> Self {
- self.blocked_user_message = Some(content.to_owned());
-
- self
- }
-
- /// Message that's sent if a command issued within a guild owned by a
- /// blocked user.
- pub fn blocked_guild_message(mut self, content: &str) -> Self {
- self.blocked_guild_message = Some(content.to_owned());
-
- self
- }
-
- /// Message that's sent when a user with wrong permissions calls a command.
- pub fn invalid_permission_message(mut self, content: &str) -> Self {
- self.invalid_permission_message = Some(content.to_owned());
-
- self
- }
-
- /// Message that's sent when a command is on cooldown.
- /// See framework documentation to see where is this utilized.
- ///
- /// `%time%` will be replaced with waiting time in seconds.
- pub fn rate_limit_message(mut self, content: &str) -> Self {
- self.rate_limit_message = Some(content.to_owned());
-
- self
- }
-
- /// Message that's sent when a command isn't available in DM.
- pub fn no_dm_message(mut self, content: &str) -> Self {
- self.no_dm_message = Some(content.to_owned());
-
- self
- }
-
- /// Message that's sent when a command isn't available in guilds.
- pub fn no_guild_message(mut self, content: &str) -> Self {
- self.no_guild_message = Some(content.to_owned());
-
- self
- }
-
- /// Message that's sent when user sends too few arguments to a command.
- ///
- /// `%min%` will be replaced with minimum allowed amount of arguments.
- ///
- /// `%given%` will be replced with the given amount of arguments.
- pub fn not_enough_args_message(mut self, content: &str) -> Self {
- self.not_enough_args_message = Some(content.to_owned());
-
- self
- }
-
/// Whether or not to respond to commands initiated with a mention. Note
/// that this can be used in conjunction with [`prefix`].
///
@@ -325,17 +211,6 @@ impl Configuration {
self
}
-
- /// Message that's sent when user sends too many arguments to a command.
- ///
- /// `%max%` will be replaced with maximum allowed amount of arguments.
- ///
- /// `%given%` will be replced with the given amount of arguments.
- pub fn too_many_args_message(mut self, content: &str) -> Self {
- self.too_many_args_message = Some(content.to_owned());
-
- self
- }
}
impl Default for Configuration {
@@ -352,17 +227,7 @@ impl Default for Configuration {
dynamic_prefix: None,
allow_whitespace: false,
prefixes: vec![],
- no_dm_message: None,
- no_guild_message: None,
- rate_limit_message: None,
- blocked_user_message: None,
- too_many_args_message: None,
- invalid_check_message: None,
- blocked_guild_message: None,
- not_enough_args_message: None,
- command_disabled_message: None,
- invalid_permission_message: None,
- account_type: AccountType::Automatic,
+ ignore_bots: true,
owners: HashSet::default(),
blocked_users: HashSet::default(),
blocked_guilds: HashSet::default(),
diff --git a/src/ext/framework/mod.rs b/src/ext/framework/mod.rs
index 098f26a..5ff67ec 100644
--- a/src/ext/framework/mod.rs
+++ b/src/ext/framework/mod.rs
@@ -64,7 +64,7 @@ mod buckets;
pub use self::buckets::{Bucket, MemberRatelimit, Ratelimit};
pub use self::command::{Command, CommandType, CommandGroup, CommandOrAlias};
-pub use self::configuration::{AccountType, Configuration};
+pub use self::configuration::Configuration;
pub use self::create_command::CreateCommand;
pub use self::create_group::CreateGroup;
@@ -75,6 +75,7 @@ use std::sync::Arc;
use std::thread;
use ::client::Context;
use ::model::{Message, UserId};
+use ::model::permissions::Permissions;
use ::utils;
#[cfg(feature="cache")]
@@ -170,6 +171,43 @@ macro_rules! command {
};
}
+/// A enum representing all possible fail conditions under which a command won't
+/// be executed.
+pub enum DispatchError {
+ /// When a custom function check has failed.
+ CheckFailed,
+ /// When the requested command is disabled in bot configuration.
+ CommandDisabled(String),
+ /// When the user is blocked in bot configuration.
+ BlockedUser,
+ /// When the guild or its owner is blocked in bot configuration.
+ BlockedGuild,
+ /// When the command requester lacks specific required permissions.
+ LackOfPermissions(Permissions),
+ /// When the command requester has exceeded a ratelimit bucket. The attached
+ /// value is the time a requester has to wait to run the command again.
+ RateLimited(i64),
+ /// When the requested command can only be used in a direct message or group
+ /// channel.
+ OnlyForDM,
+ /// When the requested command can only be ran in guilds, or the bot doesn't
+ /// support DMs.
+ OnlyForGuilds,
+ /// When the requested command can only be used by bot owners.
+ OnlyForOwners,
+ /// When there are too few arguments.
+ NotEnoughArguments { min: i32, given: usize },
+ /// When there are too many arguments.
+ TooManyArguments { max: i32, given: usize },
+ /// When the command was requested by a bot user when they are set to be
+ /// ignored.
+ IgnoredBot,
+ /// When the bot ignores webhooks and a command was issued by one.
+ WebhookAuthor,
+}
+
+type DispatchErrorHook = Fn(Context, Message, DispatchError) + Send + Sync + 'static;
+
/// A utility for easily managing dispatches to commands.
///
/// Refer to the [module-level documentation] for more information.
@@ -181,6 +219,7 @@ pub struct Framework {
configuration: Configuration,
groups: HashMap<String, Arc<CommandGroup>>,
before: Option<Arc<BeforeHook>>,
+ dispatch_error_handler: Option<Arc<DispatchErrorHook>>,
buckets: HashMap<String, Bucket>,
after: Option<Arc<AfterHook>>,
/// Whether the framework has been "initialized".
@@ -251,7 +290,7 @@ impl Framework {
self
}
- /// Defines a bucket just with `delay` between each command.
+ /// Defines a bucket with only a `delay` between each command.
pub fn simple_bucket<S>(mut self, s: S, delay: i64) -> Self
where S: Into<String> {
self.buckets.insert(s.into(), Bucket {
@@ -265,29 +304,118 @@ impl Framework {
self
}
- #[allow(cyclomatic_complexity)]
- #[doc(hidden)]
- pub fn dispatch(&mut self, mut context: Context, message: Message) {
- match self.configuration.account_type {
- AccountType::Selfbot => {
- if message.author.id != self.user_info.0 {
- return;
+ #[cfg(feature="cache")]
+ fn is_blocked_guild(&self, message: &Message) -> bool {
+ if let Some(Channel::Guild(channel)) = CACHE.read().unwrap().get_channel(message.channel_id) {
+ let guild_id = channel.read().unwrap().guild_id;
+ if self.configuration.blocked_guilds.contains(&guild_id) {
+ return true;
+ }
+
+ if let Some(guild) = guild_id.find() {
+ return self.configuration.blocked_users.contains(&guild.read().unwrap().owner_id);
+ }
+ }
+
+ false
+ }
+
+ fn has_correct_permissions(&self, command: &Arc<Command>, message: &Message) -> bool {
+ if !command.required_permissions.is_empty() {
+ if let Some(guild) = message.guild() {
+ let perms = guild.read().unwrap().permissions_for(message.channel_id, message.author.id);
+
+ return perms.contains(command.required_permissions);
+ }
+ }
+
+ true
+ }
+
+ fn checks_passed(&self, command: &Arc<Command>, mut context: &mut Context, message: &Message) -> bool {
+ for check in &command.checks {
+ if !(check)(&mut context, &message) {
+ return false;
+ }
+ }
+
+ true
+ }
+
+ fn should_fail(&mut self,
+ mut context: &mut Context,
+ message: &Message,
+ command: &Arc<Command>,
+ args: usize,
+ to_check: &str,
+ built: &str) -> Option<DispatchError> {
+ if self.configuration.ignore_bots && message.author.bot {
+ Some(DispatchError::IgnoredBot)
+ } else if self.configuration.ignore_webhooks && message.webhook_id.is_some() {
+ Some(DispatchError::WebhookAuthor)
+ } else if !self.configuration.owners.contains(&message.author.id) {
+ let ref bucket = command.bucket;
+
+ if let Some(rate_limit) = bucket.clone().map(|x| self.ratelimit_time(x.as_str(), message.author.id.0)) {
+ if rate_limit > 0i64 {
+ return Some(DispatchError::RateLimited(rate_limit.clone()));
}
- },
- AccountType::Bot => if message.author.bot {
- return;
- },
- AccountType::Automatic => {
- if self.user_info.1 {
- if message.author.bot {
- return;
- }
- } else if message.author.id != self.user_info.0 {
- return;
+ }
+
+ if let Some(x) = command.min_args {
+ if args < x as usize {
+ return Some(DispatchError::NotEnoughArguments {
+ min: x,
+ given: args
+ });
}
- },
- AccountType::Any => {}
+ }
+
+ if let Some(x) = command.max_args {
+ if args > x as usize {
+ return Some(DispatchError::TooManyArguments {
+ max: x,
+ given: args
+ });
+ }
+ }
+
+ #[cfg(feature="cache")]
+ {
+ if self.is_blocked_guild(&message) {
+ return Some(DispatchError::BlockedGuild);
+ }
+ }
+
+ if command.owners_only {
+ Some(DispatchError::OnlyForOwners)
+ } else if !self.checks_passed(&command, &mut context, &message) {
+ Some(DispatchError::CheckFailed)
+ } else if !self.has_correct_permissions(&command, &message) {
+ Some(DispatchError::LackOfPermissions(command.required_permissions))
+ } else if self.configuration.blocked_users.contains(&message.author.id) {
+ Some(DispatchError::BlockedUser)
+ } else if !self.configuration.allow_dm && message.is_private() {
+ Some(DispatchError::OnlyForGuilds)
+ } else if self.configuration.disabled_commands.contains(to_check) {
+ Some(DispatchError::CommandDisabled(to_check.to_owned()))
+ } else if self.configuration.disabled_commands.contains(built) {
+ Some(DispatchError::CommandDisabled(built.to_owned()))
+ } else if message.is_private() && command.guild_only {
+ Some(DispatchError::OnlyForGuilds)
+ } else if !message.is_private() && command.dm_only {
+ Some(DispatchError::OnlyForDM)
+ } else {
+ None
+ }
+ } else {
+ None
}
+ }
+
+ #[allow(cyclomatic_complexity)]
+ #[doc(hidden)]
+ pub fn dispatch(&mut self, mut context: Context, message: Message) {
let res = command::positions(&mut context, &message.content, &self.configuration);
let positions = match res {
@@ -346,123 +474,6 @@ impl Framework {
};
if let Some(&CommandOrAlias::Command(ref command)) = group.commands.get(&to_check) {
- let is_owner = self.configuration.owners.contains(&message.author.id);
- // Most of the checks don't apply to owners.
- if !is_owner {
- if command.owners_only {
- if let Some(ref message) = self.configuration.invalid_permission_message {
- let _ = context.channel_id.unwrap().say(message);
- }
-
- return;
- }
-
- if self.configuration.ignore_webhooks && message.webhook_id.is_some() {
- return;
- }
-
- #[cfg(feature="cache")]
- {
- if !self.configuration.allow_dm && message.is_private() {
- if let Some(ref message) = self.configuration.no_dm_message {
- let _ = context.channel_id.unwrap().say(message);
- }
-
- return;
- }
- }
-
- if self.configuration.blocked_users.contains(&message.author.id) {
- if let Some(ref message) = self.configuration.blocked_user_message {
- let _ = context.channel_id.unwrap().say(message);
- }
-
- return;
- }
-
- if self.configuration.disabled_commands.contains(&to_check) ||
- self.configuration.disabled_commands.contains(&built) {
- if let Some(ref message) = self.configuration.command_disabled_message {
- let msg = message.replace("%command%", &to_check);
-
- let _ = context.channel_id.unwrap().say(&msg);
- }
-
- return;
- }
-
- if let Some(ref bucket_name) = command.bucket {
- let rate_limit = self.ratelimit_time(bucket_name, message.author.id.0);
-
- if rate_limit > 0 {
- if let Some(ref message) = self.configuration.rate_limit_message {
- let msg = message.replace("%time%", &rate_limit.to_string());
-
- let _ = context.channel_id.unwrap().say(&msg);
- }
-
- return;
- }
- }
-
- #[cfg(feature="cache")]
- {
-
- let guild_id = {
- match CACHE.read().unwrap().get_channel(message.channel_id) {
- Some(Channel::Guild(channel)) => Some(channel.read().unwrap().guild_id),
- _ => None,
- }
- };
-
- if let Some(guild_id) = guild_id {
- if self.configuration.blocked_guilds.contains(&guild_id) {
- if let Some(ref message) = self.configuration.blocked_guild_message {
- let _ = context.channel_id.unwrap().say(message);
- }
-
- return;
- }
-
- if let Some(guild) = guild_id.find() {
- if self.configuration.blocked_users.contains(&guild.read().unwrap().owner_id) {
- if let Some(ref message) = self.configuration.blocked_guild_message {
- let _ = context.channel_id.unwrap().say(message);
- }
-
- return;
- }
- }
- }
-
- if message.is_private() {
- if command.guild_only {
- if let Some(ref message) = self.configuration.no_guild_message {
- let _ = context.channel_id.unwrap().say(message);
- }
-
- return;
- }
- } else if command.dm_only {
- if let Some(ref message) = self.configuration.no_dm_message {
- let _ = context.channel_id.unwrap().say(message);
- }
-
- return;
- }
- }
-
- for check in &command.checks {
- if !(check)(&mut context, &message) {
- if let Some(ref message) = self.configuration.invalid_check_message {
- let _ = context.channel_id.unwrap().say(message);
- }
-
- continue 'outer;
- }
- }
- }
-
let before = self.before.clone();
let command = command.clone();
let after = self.after.clone();
@@ -477,80 +488,16 @@ impl Framework {
.collect::<Vec<String>>()
};
- if let Some(x) = command.min_args {
- if args.len() < x as usize {
- if let Some(ref message) = self.configuration.not_enough_args_message {
- let msg = message.replace("%min%", &x.to_string())
- .replace("%given%", &args.len().to_string());
-
- let _ = context.channel_id.unwrap().say(&msg);
- }
-
- return;
- }
- }
-
- if let Some(x) = command.max_args {
- if args.len() > x as usize {
- if let Some(ref message) = self.configuration.too_many_args_message {
- let msg = message.replace("%max%", &x.to_string())
- .replace("%given%", &args.len().to_string());
-
- let _ = context.channel_id.unwrap().say(&msg);
- }
-
- return;
- }
- }
-
- #[cfg(feature="cache")]
- {
- if !is_owner && !command.required_permissions.is_empty() {
- let mut permissions_fulfilled = false;
-
- let cache = CACHE.read().unwrap();
-
- // Really **really** dirty code in the meantime
- // before the framework rewrite.
- let member = {
- let mut member_found = None;
-
- if let Some(Channel::Guild(channel)) = cache.get_channel(message.channel_id) {
- let guild_id = channel.read().unwrap().guild_id;
-
- if let Some(guild) = guild_id.find() {
- if let Some(member) = guild.read().unwrap().members.get(&message.author.id) {
- member_found = Some(member.clone());
- }
- }
- }
-
- member_found
- };
-
- if let Some(member) = member {
- if let Ok(guild_id) = member.find_guild() {
- if let Some(guild) = cache.get_guild(guild_id) {
- let perms = guild.read().unwrap().permissions_for(message.channel_id, message.author.id);
-
- permissions_fulfilled = perms.contains(command.required_permissions);
- }
- }
- }
-
- if !permissions_fulfilled {
- if let Some(ref message) = self.configuration.invalid_permission_message {
- let _ = context.channel_id.unwrap().say(message);
- }
-
- return;
- }
+ if let Some(error) = self.should_fail(&mut context, &message, &command, args.len(), &to_check, &built) {
+ if let Some(ref handler) = self.dispatch_error_handler {
+ handler(context, message, error);
}
+ return;
}
thread::spawn(move || {
if let Some(before) = before {
- if !(before)(&mut context, &message, &built) && !is_owner {
+ if !(before)(&mut context, &message, &built) {
return;
}
}
@@ -667,6 +614,16 @@ impl Framework {
self
}
+ /// Specify the function that's called in case a command wasn't executed for one reason or another.
+ ///
+ /// DispatchError represents all possible fail conditions.
+ pub fn on_dispatch_error<F>(mut self, f: F) -> Self
+ where F: Fn(Context, Message, DispatchError) + Send + Sync + 'static {
+ self.dispatch_error_handler = Some(Arc::new(f));
+
+ self
+ }
+
/// Specify the function to be called prior to every command's execution.
/// If that function returns true, the command will be executed.
pub fn before<F>(mut self, f: F) -> Self
@@ -685,57 +642,6 @@ impl Framework {
self
}
- /// Adds a "check" to a command, which checks whether or not the command's
- /// associated function should be called.
- ///
- /// # Examples
- ///
- /// Ensure that the user who created a message, calling a "ping" command,
- /// is the owner.
- ///
- /// ```rust,ignore
- /// use serenity::client::{Client, Context};
- /// use serenity::model::Message;
- /// use std::env;
- ///
- /// let mut client = Client::login(&env::var("DISCORD_TOKEN").unwrap());
- ///
- /// client.with_framework(|f| f
- /// .configure(|c| c.prefix("~"))
- /// .on("ping", ping)
- /// .set_check("ping", owner_check));
- ///
- /// command!(ping(_context, message) {
- /// let _ = message.channel_id.say("Pong!");
- /// });
- ///
- /// fn owner_check(_context: &mut Context, message: &Message) -> bool {
- /// // replace with your user ID
- /// message.author.id == 7
- /// }
- /// ```
- #[deprecated(since="0.1.2", note="Use the `CreateCommand` builder's `check` instead.")]
- pub fn set_check<F, S>(mut self, command: S, check: F) -> Self
- where F: Fn(&mut Context, &Message) -> bool + Send + Sync + 'static,
- S: Into<String> {
- {
- let ungrouped = self.groups.entry("Ungrouped".to_owned())
- .or_insert_with(|| Arc::new(CommandGroup::default()));
-
- if let Some(group) = Arc::get_mut(ungrouped) {
- let name = command.into();
-
- if let Some(&mut CommandOrAlias::Command(ref mut command)) = group.commands.get_mut(&name) {
- if let Some(command) = Arc::get_mut(command) {
- command.checks.push(Box::new(check));
- }
- }
- }
- }
-
- self
- }
-
#[doc(hidden)]
pub fn update_current_user(&mut self, user_id: UserId, is_bot: bool) {
self.user_info = (user_id.0, is_bot);