aboutsummaryrefslogtreecommitdiff
path: root/src/framework
diff options
context:
space:
mode:
authorZeyla Hellyer <[email protected]>2017-08-19 09:36:15 -0700
committerZeyla Hellyer <[email protected]>2017-08-19 09:39:44 -0700
commit948b27ce74e8dce458d427d8159f2a821d4d7cec (patch)
treebf82bedd1821ca210e4a9f08644581486738aed6 /src/framework
parentAdd html_root_url (diff)
downloadserenity-948b27ce74e8dce458d427d8159f2a821d4d7cec.tar.xz
serenity-948b27ce74e8dce458d427d8159f2a821d4d7cec.zip
Move builtin framework impl to its own module
The framework is now moved in its entirity to the `framework` module, with the `Framework` trait currently on its own and the builtin implementation provided. The builtin implementation has been renamed to "Standard". Upgrade path: Rename the `BuiltinFramework` import to `StandardFramework`. Instead of importing builtin framework items from `serenity::framework`, import them from `serenity::framework::standard`. This is the beginning to #60. The root `framework` module (non-standard implementation) will be built more by the time it's closed.
Diffstat (limited to 'src/framework')
-rw-r--r--src/framework/mod.rs1032
-rw-r--r--src/framework/standard/buckets.rs (renamed from src/framework/buckets.rs)0
-rw-r--r--src/framework/standard/command.rs (renamed from src/framework/command.rs)0
-rw-r--r--src/framework/standard/configuration.rs (renamed from src/framework/configuration.rs)44
-rw-r--r--src/framework/standard/create_command.rs (renamed from src/framework/create_command.rs)4
-rw-r--r--src/framework/standard/create_group.rs (renamed from src/framework/create_group.rs)0
-rw-r--r--src/framework/standard/help_commands.rs (renamed from src/framework/help_commands.rs)16
-rw-r--r--src/framework/standard/mod.rs1026
8 files changed, 1066 insertions, 1056 deletions
diff --git a/src/framework/mod.rs b/src/framework/mod.rs
index 1f62e1b..c59c8a2 100644
--- a/src/framework/mod.rs
+++ b/src/framework/mod.rs
@@ -54,1031 +54,18 @@
//!
//! [`Client::with_framework`]: ../client/struct.Client.html#method.with_framework
-pub mod help_commands;
+#[cfg(feature="standard_framework")]
+pub mod standard;
-mod command;
-mod configuration;
-mod create_command;
-mod create_group;
-mod buckets;
+#[cfg(feature = "standard_framework")]
+pub use self::standard::StandardFramework;
-pub(crate) use self::buckets::{Bucket, Ratelimit};
-pub use self::command::{Command, CommandGroup, CommandType};
-pub use self::command::CommandOrAlias;
-pub use self::configuration::Configuration;
-pub use self::create_command::CreateCommand;
-pub use self::create_group::CreateGroup;
-
-use self::command::{AfterHook, BeforeHook};
-use std::collections::HashMap;
-use std::default::Default;
-use std::sync::Arc;
use client::Context;
-use model::{ChannelId, GuildId, Message, UserId};
-use model::permissions::Permissions;
-use utils;
+use model::Message;
use tokio_core::reactor::Handle;
-use itertools::Itertools;
-use regex::Regex;
-use regex::escape;
-
-#[cfg(feature = "cache")]
-use client::CACHE;
-#[cfg(feature = "cache")]
-use model::Channel;
-
-/// A macro to generate "named parameters". This is useful to avoid manually
-/// using the "arguments" parameter and manually parsing types.
-///
-/// This is meant for use with the command [`Framework`].
-///
-/// # Examples
-///
-/// Create a regular `ping` command which takes no arguments:
-///
-/// ```rust,ignore
-/// command!(ping(_context, message, _args) {
-/// if let Err(why) = message.reply("Pong!") {
-/// println!("Error sending pong: {:?}", why);
-/// }
-/// });
-/// ```
-///
-/// Create a command named `multiply` which accepts 2 floats and multiplies
-/// them, sending the product as a reply:
-///
-/// ```rust,ignore
-/// command!(multiply(_context, message, _args, first: f64, second: f64) {
-/// let product = first * second;
-///
-/// if let Err(why) = message.reply(&product.to_string()) {
-/// println!("Error sending product: {:?}", why);
-/// }
-/// });
-/// ```
-///
-/// [`Framework`]: framework/index.html
-#[macro_export]
-macro_rules! command {
- ($fname:ident($c:ident) $b:block) => {
- #[allow(unreachable_code, unused_mut)]
- pub fn $fname(mut $c: &mut $crate::client::Context,
- _: &$crate::model::Message,
- _: Vec<String>,
- _: String)
- -> ::std::result::Result<(), String> {
- $b
-
- Ok(())
- }
- };
- ($fname:ident($c:ident, $m:ident) $b:block) => {
- #[allow(unreachable_code, unused_mut)]
- pub fn $fname(mut $c: &mut $crate::client::Context,
- $m: &$crate::model::Message,
- _: Vec<String>,
- _: String)
- -> ::std::result::Result<(), String> {
- $b
-
- Ok(())
- }
- };
- ($fname:ident($c:ident, $m:ident, $a:ident) $b:block) => {
- #[allow(unreachable_code, unused_mut)]
- pub fn $fname(mut $c: &mut $crate::client::Context,
- $m: &$crate::model::Message,
- $a: Vec<String>,
- _: String)
- -> ::std::result::Result<(), String> {
- $b
-
- Ok(())
- }
- };
- ($fname:ident($c:ident, $m:ident, @$a:ident) $b:block) => {
- #[allow(unreachable_code, unused_mut)]
- pub fn $fname(mut $c: &mut $crate::client::Context,
- $m: &$crate::model::Message,
- _: Vec<String>,
- $a: String)
- -> ::std::result::Result<(), String> {
- $b
-
- Ok(())
- }
- };
- ($fname:ident($c:ident, $m:ident, $a:ident, @$f:ident) $b:block) => {
- #[allow(unreachable_code, unused_mut)]
- pub fn $fname(mut $c: &mut $crate::client::Context,
- $m: &$crate::model::Message,
- $a: Vec<String>,
- $f: String)
- -> ::std::result::Result<(), String> {
- $b
-
- Ok(())
- }
- };
- ($fname:ident($c:ident, $m:ident, $a:ident, $($name:ident: $t:ty),*) $b:block) => {
- #[allow(unreachable_code, unreachable_patterns, unused_mut)]
- pub fn $fname(mut $c: &mut $crate::client::Context,
- $m: &$crate::model::Message,
- $a: Vec<String>,
- _: String)
- -> ::std::result::Result<(), String> {
- let mut i = $a.iter();
- let mut arg_counter = 0;
-
- $(
- arg_counter += 1;
-
- let $name = match i.next() {
- Some(v) => match v.parse::<$t>() {
- Ok(v) => v,
- Err(_) => return Err(format!("Failed to parse argument #{} of type {:?}",
- arg_counter,
- stringify!($t))),
- },
- None => return Err(format!("Missing argument #{} of type {:?}",
- arg_counter,
- stringify!($t))),
- };
- )*
-
- drop(i);
-
- $b
-
- Ok(())
- }
- };
-}
-
-/// 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(Arc<Command>),
- /// 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) + 'static;
-
-/// A utility for easily managing dispatches to commands.
-///
-/// Refer to the [module-level documentation] for more information.
-///
-/// [module-level documentation]: index.html
-#[derive(Default)]
-pub struct BuiltinFramework {
- 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".
- ///
- /// The framework is initialized once one of the following occurs:
- ///
- /// - configuration has been set;
- /// - a command handler has been set;
- /// - a command check has been set.
- ///
- /// This is used internally to determine whether or not - in addition to
- /// dispatching to the [`EventHandler::on_message`] handler - to have the
- /// framework check if a [`Event::MessageCreate`] should be processed by
- /// itself.
- ///
- /// [`EventHandler::on_message`]:
- /// ../client/event_handler/trait.EventHandler.html#method.on_message
- /// [`Event::MessageCreate`]: ../model/event/enum.Event.html#variant.MessageCreate
- pub initialized: bool,
- user_info: (u64, bool),
-}
-
-impl BuiltinFramework {
- pub fn new() -> Self { BuiltinFramework::default() }
-
- /// Configures the framework, setting non-default values. All fields are
- /// optional. Refer to [`Configuration::default`] for more information on
- /// the default values.
- ///
- /// # Examples
- ///
- /// Configuring the framework for a [`Client`], setting the [`depth`] to 3,
- /// [allowing whitespace], and setting the [`prefix`] to `"~"`:
- ///
- /// ```rust,no_run
- /// # use serenity::prelude::EventHandler;
- /// # struct Handler;
- /// # impl EventHandler for Handler {}
- /// use serenity::Client;
- /// use serenity::framework::BuiltinFramework;
- /// use std::env;
- ///
- /// let mut client = Client::new(&env::var("DISCORD_TOKEN").unwrap(), Handler);
- /// client.with_framework(BuiltinFramework::new()
- /// .configure(|c| c
- /// .depth(3)
- /// .allow_whitespace(true)
- /// .prefix("~")));
- /// ```
- ///
- /// [`Client`]: ../client/struct.Client.html
- /// [`Configuration::default`]: struct.Configuration.html#method.default
- /// [`depth`]: struct.Configuration.html#method.depth
- /// [`prefix`]: struct.Configuration.html#method.prefix
- /// [allowing whitespace]: struct.Configuration.html#method.allow_whitespace
- pub fn configure<F>(mut self, f: F) -> Self
- where F: FnOnce(Configuration) -> Configuration {
- self.configuration = f(self.configuration);
-
- self
- }
-
- /// Defines a bucket with `delay` between each command, and the `limit` of uses
- /// per `time_span`.
- ///
- /// # Examples
- ///
- /// Create and use a bucket that limits a command to 3 uses per 10 seconds with
- /// a 2 second delay inbetween invocations:
- ///
- /// ```rust
- /// # use serenity::prelude::*;
- /// # struct Handler;
- /// #
- /// # impl EventHandler for Handler {}
- /// # let mut client = Client::new("token", Handler);
- /// #
- /// use serenity::framework::BuiltinFramework;
- ///
- /// client.with_framework(BuiltinFramework::new()
- /// .bucket("basic", 2, 10, 3)
- /// .command("ping", |c| c
- /// .bucket("basic")
- /// .exec_str("pong!")));
- /// ```
- pub fn bucket<S>(mut self, s: S, delay: i64, time_span: i64, limit: i32) -> Self
- where S: Into<String> {
- self.buckets.insert(
- s.into(),
- Bucket {
- ratelimit: Ratelimit {
- delay: delay,
- limit: Some((time_span, limit)),
- },
- users: HashMap::new(),
- check: None,
- },
- );
-
- self
- }
-
- /// Same as [`bucket`] but with a check added.
- ///
- /// # Examples
- ///
- /// ```rust
- /// # use serenity::prelude::*;
- /// # struct Handler;
- /// #
- /// # impl EventHandler for Handler {}
- /// # let mut client = Client::new("token", Handler);
- /// #
- /// use serenity::framework::BuiltinFramework;
- ///
- /// client.with_framework(BuiltinFramework::new()
- /// .complex_bucket("basic", 2, 10, 3, |_, guild_id, channel_id, user_id| {
- /// // check if the guild is `123` and the channel where the command(s) was called:
- /// // `456`
- /// // and if the user who called the command(s) is `789`
- /// // otherwise don't apply the bucket at all.
- /// guild_id.is_some() && guild_id.unwrap() == 123 && channel_id == 456
- /// && user_id == 789
- /// })
- /// .command("ping", |c| c
- /// .bucket("basic")
- /// .exec_str("pong!")));
- /// ```
- ///
- /// [`bucket`]: #method.bucket
- #[cfg(feature = "cache")]
- pub fn complex_bucket<S, Check>(mut self,
- s: S,
- delay: i64,
- time_span: i64,
- limit: i32,
- check: Check)
- -> Self
- where Check: Fn(&mut Context, Option<GuildId>, ChannelId, UserId) -> bool + 'static,
- S: Into<String> {
- self.buckets.insert(
- s.into(),
- Bucket {
- ratelimit: Ratelimit {
- delay,
- limit: Some((time_span, limit)),
- },
- users: HashMap::new(),
- check: Some(Box::new(check)),
- },
- );
-
- self
- }
-
- /// Same as [`bucket`] but with a check added.
- ///
- /// # Examples
- ///
- /// ```rust
- /// # use serenity::prelude::*;
- /// # struct Handler;
- /// #
- /// # impl EventHandler for Handler {}
- /// # let mut client = Client::new("token", Handler);
- /// #
- /// use serenity::framework::BuiltinFramework;
- ///
- /// client.with_framework(BuiltinFramework::new()
- /// .complex_bucket("basic", 2, 10, 3, |_, channel_id, user_id| {
- /// // check if the channel's id where the command(s) was called is `456`
- /// // and if the user who called the command(s) is `789`
- /// // otherwise don't apply the bucket at all.
- /// channel_id == 456 && user_id == 789
- /// })
- /// .command("ping", |c| c
- /// .bucket("basic")
- /// .exec_str("pong!")));
- /// ```
- ///
- /// [`bucket`]: #method.bucket
- #[cfg(not(feature = "cache"))]
- pub fn complex_bucket<S, Check>(mut self,
- s: S,
- delay: i64,
- time_span: i64,
- limit: i32,
- check: Check)
- -> Self
- where Check: Fn(&mut Context, ChannelId, UserId) -> bool + 'static, S: Into<String> {
- self.buckets.insert(
- s.into(),
- Bucket {
- ratelimit: Ratelimit {
- delay,
- limit: Some((time_span, limit)),
- },
- users: HashMap::new(),
- check: Some(Box::new(check)),
- },
- );
-
- self
- }
-
- /// Defines a bucket with only a `delay` between each command.
- ///
- /// # Examples
- ///
- /// Create and use a simple bucket that has a 2 second delay between invocations:
- ///
- /// ```rust
- /// # use serenity::prelude::*;
- /// # struct Handler;
- /// #
- /// # impl EventHandler for Handler {}
- /// # let mut client = Client::new("token", Handler);
- /// #
- /// use serenity::framework::BuiltinFramework;
- ///
- /// client.with_framework(BuiltinFramework::new()
- /// .simple_bucket("simple", 2)
- /// .command("ping", |c| c
- /// .bucket("simple")
- /// .exec_str("pong!")));
- /// ```
- pub fn simple_bucket<S>(mut self, s: S, delay: i64) -> Self
- where S: Into<String> {
- self.buckets.insert(
- s.into(),
- Bucket {
- ratelimit: Ratelimit {
- delay: delay,
- limit: None,
- },
- users: HashMap::new(),
- check: None,
- },
- );
-
- self
- }
-
- #[cfg(feature = "cache")]
- fn is_blocked_guild(&self, message: &Message) -> bool {
- if let Some(Channel::Guild(channel)) = CACHE.read().unwrap().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
- }
-
- #[cfg(feature = "cache")]
- 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
- }
-
- #[allow(too_many_arguments)]
- fn should_fail(&mut self,
- mut context: &mut Context,
- message: &Message,
- command: &Arc<Command>,
- args: &[String],
- 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) {
- None
- } else {
- if let Some(ref bucket) = command.bucket {
- if let Some(ref mut bucket) = self.buckets.get_mut(bucket) {
- let rate_limit = bucket.take(message.author.id.0);
- match bucket.check {
- Some(ref check) => {
- let apply =
- feature_cache! {{
- let guild_id = message.guild_id();
- (check)(context, guild_id, message.channel_id, message.author.id)
- } else {
- (check)(context, message.channel_id, message.author.id)
- }};
-
- if apply && rate_limit > 0i64 {
- return Some(DispatchError::RateLimited(rate_limit));
- }
- },
- None => {
- if rate_limit > 0i64 {
- return Some(DispatchError::RateLimited(rate_limit));
- }
- },
- }
- }
- }
-
- let arg_len = args.len();
-
- if let Some(x) = command.min_args {
- if arg_len < x as usize {
- return Some(DispatchError::NotEnoughArguments {
- min: x,
- given: arg_len,
- });
- }
- }
-
- if let Some(x) = command.max_args {
- if arg_len > x as usize {
- return Some(DispatchError::TooManyArguments {
- max: x,
- given: arg_len,
- });
- }
- }
-
- #[cfg(feature = "cache")]
- {
- if self.is_blocked_guild(message) {
- return Some(DispatchError::BlockedGuild);
- }
- if !self.has_correct_permissions(command, message) {
- return Some(DispatchError::LackOfPermissions(
- command.required_permissions,
- ));
- }
-
- if (!self.configuration.allow_dm && message.is_private()) ||
- (command.guild_only && message.is_private()) {
- return Some(DispatchError::OnlyForGuilds);
- }
-
- if command.dm_only && !message.is_private() {
- return Some(DispatchError::OnlyForDM);
- }
- }
-
- if command.owners_only {
- Some(DispatchError::OnlyForOwners)
- } else if self.configuration.blocked_users.contains(
- &message.author.id,
- ) {
- Some(DispatchError::BlockedUser)
- } 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 {
- let all_passed = command.checks.iter().all(|check| {
- check(&mut context, message, args, command)
- });
-
- if all_passed {
- None
- } else {
- Some(DispatchError::CheckFailed(command.to_owned()))
- }
- }
- }
- }
-
- /// Adds a function to be associated with a command, which will be called
- /// when a command is used in a message.
- ///
- /// This requires that a check - if one exists - passes, prior to being
- /// called.
- ///
- /// Prior to v0.2.0, you will need to use the command builder
- /// via the [`command`] method to set checks. This command will otherwise
- /// only be for simple commands.
- ///
- /// Refer to the [module-level documentation] for more information and
- /// usage.
- ///
- /// [`command`]: #method.command
- /// [module-level documentation]: index.html
- ///
- /// # Examples
- ///
- /// Create and use a simple command:
- ///
- /// ```rust
- /// # #[macro_use] extern crate serenity;
- /// #
- /// # fn main() {
- /// # use serenity::prelude::*;
- /// # struct Handler;
- /// #
- /// # impl EventHandler for Handler {}
- /// # let mut client = Client::new("token", Handler);
- /// #
- /// use serenity::framework::BuiltinFramework;
- ///
- /// client.with_framework(BuiltinFramework::new().on("ping", ping));
- ///
- /// command!(ping(_ctx, msg) {
- /// let _ = msg.channel_id.say("pong!");
- /// });
- /// # }
- /// ```
- pub fn on<F, S>(mut self, command_name: S, f: F) -> Self
- where F: Fn(&mut Context, &Message, Vec<String>, String) -> Result<(), String> + 'static,
- S: Into<String> {
- {
- let ungrouped = self.groups.entry("Ungrouped".to_owned()).or_insert_with(
- || {
- Arc::new(CommandGroup::default())
- },
- );
-
- if let Some(ref mut group) = Arc::get_mut(ungrouped) {
- let name = command_name.into();
-
- group.commands.insert(
- name,
- CommandOrAlias::Command(
- Arc::new(Command::new(f)),
- ),
- );
- }
- }
-
- self.initialized = true;
-
- self
- }
-
- /// Adds a command using command builder.
- ///
- /// # Examples
- ///
- /// ```rust,ignore
- /// framework.command("ping", |c| c
- /// .description("Responds with 'pong'.")
- /// .exec(|ctx, _, _| {
- /// let _ = ctx.say("pong");
- /// }));
- /// ```
- pub fn command<F, S>(mut self, command_name: S, f: F) -> Self
- where F: FnOnce(CreateCommand) -> CreateCommand, S: Into<String> {
- {
- let ungrouped = self.groups.entry("Ungrouped".to_owned()).or_insert_with(
- || {
- Arc::new(CommandGroup::default())
- },
- );
-
- if let Some(ref mut group) = Arc::get_mut(ungrouped) {
- let cmd = f(CreateCommand(Command::default())).0;
- let name = command_name.into();
-
- if let Some(ref prefix) = group.prefix {
- for v in &cmd.aliases {
- group.commands.insert(
- format!("{} {}", prefix, v),
- CommandOrAlias::Alias(format!("{} {}", prefix, name)),
- );
- }
- } else {
- for v in &cmd.aliases {
- group.commands.insert(
- v.to_owned(),
- CommandOrAlias::Alias(name.clone()),
- );
- }
- }
-
- group.commands.insert(
- name,
- CommandOrAlias::Command(Arc::new(cmd)),
- );
- }
- }
-
- self.initialized = true;
-
- self
- }
-
- /// Adds a group which can organize several related commands.
- /// Groups are taken into account when using `serenity::framework::help_commands`.
- ///
- /// # Examples
- ///
- /// Creating a simple group:
- ///
- /// ```rust
- /// # use serenity::prelude::*;
- /// # struct Handler;
- /// #
- /// # impl EventHandler for Handler {}
- /// # let mut client = Client::new("token", Handler);
- /// #
- /// use serenity::framework::BuiltinFramework;
- ///
- /// client.with_framework(BuiltinFramework::new()
- /// .group("ping-pong", |g| g
- /// .command("ping", |c| c.exec_str("pong!"))
- /// .command("pong", |c| c.exec_str("ping!"))));
- /// ```
- pub fn group<F, S>(mut self, group_name: S, f: F) -> Self
- where F: FnOnce(CreateGroup) -> CreateGroup, S: Into<String> {
- let group = f(CreateGroup(CommandGroup::default())).0;
-
- self.groups.insert(group_name.into(), Arc::new(group));
- self.initialized = true;
-
- 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.
- ///
- /// # Examples
- ///
- /// Making a simple argument error responder:
- ///
- /// ```rust
- /// # use serenity::prelude::*;
- /// # struct Handler;
- /// #
- /// # impl EventHandler for Handler {}
- /// # let mut client = Client::new("token", Handler);
- /// use serenity::framework::DispatchError::{NotEnoughArguments, TooManyArguments};
- /// use serenity::framework::BuiltinFramework;
- ///
- /// client.with_framework(BuiltinFramework::new()
- /// .on_dispatch_error(|_, msg, error| {
- /// match error {
- /// NotEnoughArguments { min, given } => {
- /// let s = format!("Need {} arguments, but only got {}.", min, given);
- ///
- /// let _ = msg.channel_id.say(&s);
- /// },
- /// TooManyArguments { max, given } => {
- /// let s = format!("Max arguments allowed is {}, but got {}.", max, given);
- ///
- /// let _ = msg.channel_id.say(&s);
- /// },
- /// _ => println!("Unhandled dispatch error."),
- /// }
- /// }));
- /// ```
- pub fn on_dispatch_error<F>(mut self, f: F) -> Self
- where F: Fn(Context, Message, DispatchError) + '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.
- ///
- /// # Examples
- ///
- /// Using `before` to log command usage:
- ///
- /// ```rust
- /// # use serenity::prelude::*;
- /// # struct Handler;
- /// #
- /// # impl EventHandler for Handler {}
- /// # let mut client = Client::new("token", Handler);
- /// #
- /// use serenity::framework::BuiltinFramework;
- ///
- /// client.with_framework(BuiltinFramework::new()
- /// .before(|ctx, msg, cmd_name| {
- /// println!("Running command {}", cmd_name);
- /// true
- /// }));
- /// ```
- ///
- /// Using before to prevent command usage:
- ///
- /// ```rust
- /// # use serenity::prelude::*;
- /// # struct Handler;
- /// #
- /// # impl EventHandler for Handler {}
- /// # let mut client = Client::new("token", Handler);
- /// #
- /// use serenity::framework::BuiltinFramework;
- ///
- /// client.with_framework(BuiltinFramework::new()
- /// .before(|_, msg, cmd_name| {
- /// if let Ok(channel) = msg.channel_id.get() {
- /// // Don't run unless in nsfw channel
- /// if !channel.is_nsfw() {
- /// return false;
- /// }
- /// }
- ///
- /// println!("Running command {}", cmd_name);
- ///
- /// true
- /// }));
- /// ```
- ///
- pub fn before<F>(mut self, f: F) -> Self
- where F: Fn(&mut Context, &Message, &str) -> bool + 'static {
- self.before = Some(Arc::new(f));
-
- self
- }
-
- /// Specify the function to be called after every command's execution.
- /// Fourth argument exists if command returned an error which you can handle.
- ///
- /// # Examples
- ///
- /// Using `after` to log command usage:
- ///
- /// ```rust
- /// # use serenity::prelude::*;
- /// # struct Handler;
- /// #
- /// # impl EventHandler for Handler {}
- /// # let mut client = Client::new("token", Handler);
- /// #
- /// use serenity::framework::BuiltinFramework;
- ///
- /// client.with_framework(BuiltinFramework::new()
- /// .after(|ctx, msg, cmd_name, error| {
- /// // Print out an error if it happened
- /// if let Err(why) = error {
- /// println!("Error in {}: {:?}", cmd_name, why);
- /// }
- /// }));
- /// ```
- pub fn after<F>(mut self, f: F) -> Self
- where F: Fn(&mut Context, &Message, &str, Result<(), String>) + 'static {
- self.after = Some(Arc::new(f));
-
- self
- }
-}
-
-impl Framework for BuiltinFramework {
- fn dispatch(&mut self, mut context: Context, message: Message, tokio_handle: &Handle) {
- let res = command::positions(&mut context, &message, &self.configuration);
-
- let positions = match res {
- Some(mut positions) => {
- // First, take out the prefixes that are as long as _or_ longer
- // than the message, to avoid character boundary violations.
- positions.retain(|p| *p < message.content.len());
-
- // Ensure that there is _at least one_ position remaining. There
- // is no point in continuing if there is not.
- if positions.is_empty() {
- return;
- }
-
- positions
- },
- None => return,
- };
-
- 'outer: for position in positions {
- let mut built = String::new();
- let round = message.content.chars().skip(position).collect::<String>();
- let round = round.trim().split_whitespace().collect::<Vec<&str>>();
-
- for i in 0..self.configuration.depth {
- if i != 0 {
- built.push(' ');
- }
-
- built.push_str(match round.get(i) {
- Some(piece) => piece,
- None => continue 'outer,
- });
-
- let groups = self.groups.clone();
-
- for group in groups.values() {
- let command_length = built.len();
-
- if let Some(&CommandOrAlias::Alias(ref points_to)) =
- group.commands.get(&built) {
- built = points_to.to_owned();
- }
-
- let to_check = if let Some(ref prefix) = group.prefix {
- if built.starts_with(prefix) && command_length > prefix.len() + 1 {
- built[(prefix.len() + 1)..].to_owned()
- } else {
- continue;
- }
- } else {
- built.clone()
- };
-
- if let Some(&CommandOrAlias::Command(ref command)) =
- group.commands.get(&to_check) {
- let before = self.before.clone();
- let command = command.clone();
- let after = self.after.clone();
- let groups = self.groups.clone();
-
- let (args, content) = {
- let mut content = message.content[position..].trim();
- content = content[command_length..].trim();
-
- if command.use_quotes {
- (utils::parse_quotes(content), content.to_string())
- } else {
- let delimiters = &self.configuration.delimiters;
- let regular_expression = delimiters
- .iter()
- .map(|delimiter| escape(delimiter))
- .join("|");
-
- let regex = Regex::new(&regular_expression).unwrap();
-
- (
- regex
- .split(content)
- .filter_map(|p| if p.is_empty() {
- None
- } else {
- Some(p.to_string())
- })
- .collect::<Vec<_>>(),
- content.to_string(),
- )
- }
- };
-
- if let Some(error) = self.should_fail(
- &mut context,
- &message,
- &command,
- &args,
- &to_check,
- &built,
- ) {
- if let Some(ref handler) = self.dispatch_error_handler {
- handler(context, message, error);
- }
- return;
- }
-
- tokio_handle.spawn_fn(move || {
- if let Some(before) = before {
- if !(before)(&mut context, &message, &built) {
- return Ok(());
- }
- }
-
- let result = match command.exec {
- CommandType::StringResponse(ref x) => {
- let _ = message.channel_id.say(x);
-
- Ok(())
- },
- CommandType::Basic(ref x) => {
- (x)(&mut context, &message, args, content)
- },
- CommandType::WithCommands(ref x) => {
- (x)(&mut context, &message, groups, &args)
- },
- };
-
- if let Some(after) = after {
- (after)(&mut context, &message, &built, result);
- }
-
- Ok(())
- });
-
- return;
- }
- }
- }
- }
- }
-
- #[cfg(feature = "builtin_framework")]
- fn update_current_user(&mut self, user_id: UserId, is_bot: bool) {
- self.user_info = (user_id.0, is_bot);
- }
-
- #[cfg(feature = "builtin_framework")]
- fn initialized(&self) -> bool { self.initialized }
-}
+#[cfg(feature="standard_framework")]
+use model::UserId;
/// This trait allows for serenity to either use its builtin framework, or yours.
///
@@ -1087,12 +74,11 @@ impl Framework for BuiltinFramework {
///
/// Note that you may see some other methods in here as well, but they're meant to be internal only
/// for the builtin framework.
-#[cfg(feature = "framework")]
pub trait Framework {
fn dispatch(&mut self, Context, Message, &Handle);
- #[cfg(feature = "builtin_framework")]
+ #[cfg(feature = "standard_framework")]
fn update_current_user(&mut self, UserId, bool) {}
- #[cfg(feature = "builtin_framework")]
+ #[cfg(feature = "standard_framework")]
fn initialized(&self) -> bool { false }
}
diff --git a/src/framework/buckets.rs b/src/framework/standard/buckets.rs
index f2c4486..f2c4486 100644
--- a/src/framework/buckets.rs
+++ b/src/framework/standard/buckets.rs
diff --git a/src/framework/command.rs b/src/framework/standard/command.rs
index 01c4c5e..01c4c5e 100644
--- a/src/framework/command.rs
+++ b/src/framework/standard/command.rs
diff --git a/src/framework/configuration.rs b/src/framework/standard/configuration.rs
index bd64cba..491bbc4 100644
--- a/src/framework/configuration.rs
+++ b/src/framework/standard/configuration.rs
@@ -22,11 +22,11 @@ use model::{GuildId, Message, UserId};
/// impl EventHandler for Handler {}
/// use serenity::Client;
/// use std::env;
-/// use serenity::framework::BuiltinFramework;
+/// use serenity::framework::StandardFramework;
///
/// let mut client = Client::new(&env::var("DISCORD_BOT_TOKEN").unwrap(), Handler);
///
-/// client.with_framework(BuiltinFramework::new()
+/// client.with_framework(StandardFramework::new()
/// .configure(|c| c.on_mention(true).prefix("~")));
/// ```
///
@@ -112,9 +112,9 @@ impl Configuration {
/// # impl EventHandler for Handler {}
/// # let mut client = Client::new("token", Handler);
/// use serenity::model::GuildId;
- /// use serenity::framework::BuiltinFramework;
+ /// use serenity::framework::StandardFramework;
///
- /// client.with_framework(BuiltinFramework::new().configure(|c| c
+ /// client.with_framework(StandardFramework::new().configure(|c| c
/// .blocked_guilds(vec![GuildId(7), GuildId(77)].into_iter().collect())));
/// ```
pub fn blocked_guilds(mut self, guilds: HashSet<GuildId>) -> Self {
@@ -137,9 +137,9 @@ impl Configuration {
/// # impl EventHandler for Handler {}
/// # let mut client = Client::new("token", Handler);
/// use serenity::model::UserId;
- /// use serenity::framework::BuiltinFramework;
+ /// use serenity::framework::StandardFramework;
///
- /// client.with_framework(BuiltinFramework::new().configure(|c| c
+ /// client.with_framework(StandardFramework::new().configure(|c| c
/// .blocked_users(vec![UserId(7), UserId(77)].into_iter().collect())));
/// ```
pub fn blocked_users(mut self, users: HashSet<UserId>) -> Self {
@@ -174,11 +174,11 @@ impl Configuration {
/// #
/// # impl EventHandler for Handler {}
/// # let mut client = Client::new("token", Handler);
- /// use serenity::framework::BuiltinFramework;
+ /// use serenity::framework::StandardFramework;
///
/// let disabled = vec!["ping"].into_iter().map(|x| x.to_owned()).collect();
///
- /// client.with_framework(BuiltinFramework::new()
+ /// client.with_framework(StandardFramework::new()
/// .command("ping", |c| c.exec_str("pong!"))
/// .configure(|c| c.disabled_commands(disabled)));
/// ```
@@ -204,9 +204,9 @@ impl Configuration {
/// #
/// # impl EventHandler for Handler {}
/// # let mut client = Client::new("token", Handler);
- /// use serenity::framework::BuiltinFramework;
+ /// use serenity::framework::StandardFramework;
///
- /// client.with_framework(BuiltinFramework::new()
+ /// client.with_framework(StandardFramework::new()
/// .command("ping", |c| c.exec_str("Pong!"))
/// .configure(|c| c.dynamic_prefix(|_, msg| {
/// Some(if msg.channel_id.0 % 5 == 0 {
@@ -290,9 +290,9 @@ impl Configuration {
/// # impl EventHandler for Handler {}
/// # let mut client = Client::new("token", Handler);
/// use serenity::model::UserId;
- /// use serenity::framework::BuiltinFramework;
+ /// use serenity::framework::StandardFramework;
///
- /// client.with_framework(BuiltinFramework::new().configure(|c| c
+ /// client.with_framework(StandardFramework::new().configure(|c| c
/// .owners(vec![UserId(7), UserId(77)].into_iter().collect())));
/// ```
///
@@ -306,13 +306,13 @@ impl Configuration {
/// # let mut client = Client::new("token", Handler);
/// use serenity::model::UserId;
/// use std::collections::HashSet;
- /// use serenity::framework::BuiltinFramework;
+ /// use serenity::framework::StandardFramework;
///
/// let mut set = HashSet::new();
/// set.insert(UserId(7));
/// set.insert(UserId(77));
///
- /// client.with_framework(BuiltinFramework::new().configure(|c| c.owners(set)));
+ /// client.with_framework(StandardFramework::new().configure(|c| c.owners(set)));
/// ```
pub fn owners(mut self, user_ids: HashSet<UserId>) -> Self {
self.owners = user_ids;
@@ -334,9 +334,9 @@ impl Configuration {
/// # impl EventHandler for Handler {}
/// # let mut client = Client::new("token", Handler);
/// #
- /// use serenity::framework::BuiltinFramework;
+ /// use serenity::framework::StandardFramework;
///
- /// client.with_framework(BuiltinFramework::new().configure(|c| c
+ /// client.with_framework(StandardFramework::new().configure(|c| c
/// .prefix("!")));
/// ```
pub fn prefix(mut self, prefix: &str) -> Self {
@@ -359,9 +359,9 @@ impl Configuration {
/// # impl EventHandler for Handler {}
/// # let mut client = Client::new("token", Handler);
/// #
- /// use serenity::framework::BuiltinFramework;
+ /// use serenity::framework::StandardFramework;
///
- /// client.with_framework(BuiltinFramework::new().configure(|c| c
+ /// client.with_framework(StandardFramework::new().configure(|c| c
/// .prefixes(vec!["!", ">", "+"])));
/// ```
pub fn prefixes(mut self, prefixes: Vec<&str>) -> Self {
@@ -383,9 +383,9 @@ impl Configuration {
/// # impl EventHandler for Handler {}
/// # let mut client = Client::new("token", Handler);
/// #
- /// use serenity::framework::BuiltinFramework;
+ /// use serenity::framework::StandardFramework;
///
- /// client.with_framework(BuiltinFramework::new().configure(|c| c
+ /// client.with_framework(StandardFramework::new().configure(|c| c
/// .delimiter(", ")));
/// ```
pub fn delimiter(mut self, delimiter: &str) -> Self {
@@ -408,9 +408,9 @@ impl Configuration {
/// # impl EventHandler for Handler {}
/// # let mut client = Client::new("token", Handler);
/// #
- /// use serenity::framework::BuiltinFramework;
+ /// use serenity::framework::StandardFramework;
///
- /// client.with_framework(BuiltinFramework::new().configure(|c| c
+ /// client.with_framework(StandardFramework::new().configure(|c| c
/// .delimiters(vec![", ", " "])));
/// ```
pub fn delimiters(mut self, delimiters: Vec<&str>) -> Self {
diff --git a/src/framework/create_command.rs b/src/framework/standard/create_command.rs
index 3110a9c..160a508 100644
--- a/src/framework/create_command.rs
+++ b/src/framework/standard/create_command.rs
@@ -38,14 +38,14 @@ impl CreateCommand {
/// # struct Handler;
/// # impl EventHandler for Handler {}
/// use serenity::client::{Client, Context};
- /// use serenity::framework::{Command, BuiltinFramework};
+ /// use serenity::framework::standard::{Command, StandardFramework};
/// use serenity::model::Message;
/// use std::env;
/// use std::sync::Arc;
///
/// let mut client = Client::new(&env::var("DISCORD_TOKEN").unwrap(), Handler);
///
- /// client.with_framework(BuiltinFramework::new()
+ /// client.with_framework(StandardFramework::new()
/// .configure(|c| c.prefix("~"))
/// .command("ping", |c| c
/// .check(owner_check)
diff --git a/src/framework/create_group.rs b/src/framework/standard/create_group.rs
index b45c41e..b45c41e 100644
--- a/src/framework/create_group.rs
+++ b/src/framework/standard/create_group.rs
diff --git a/src/framework/help_commands.rs b/src/framework/standard/help_commands.rs
index 7846259..a7c20fd 100644
--- a/src/framework/help_commands.rs
+++ b/src/framework/standard/help_commands.rs
@@ -6,14 +6,14 @@
//! embeds:
//!
//! ```rs,no_run
-//! use serenity::framework::help_commands;
+//! use serenity::framework::standard::help_commands;
//! use serenity::Client;
//! use std::env;
//!
//! let mut client = Client::new(&env::var("DISCORD_TOKEN").unwrap());
-//! use serenity::framework::BuiltinFramework;
+//! use serenity::framework::StandardFramework;
//!
-//! client.with_framework(BuiltinFramework::new()
+//! client.with_framework(StandardFramework::new()
//! .command("help", |c| c.exec_help(help_commands::with_embeds)));
//! ```
//!
@@ -63,10 +63,9 @@ fn remove_aliases(cmds: &HashMap<String, CommandOrAlias>) -> HashMap<&String, &I
/// # impl EventHandler for Handler {}
/// # let mut client = Client::new("token", Handler);
/// #
-/// use serenity::framework::help_commands;
-/// use serenity::framework::BuiltinFramework;
+/// use serenity::framework::standard::{StandardFramework, help_commands};
///
-/// client.with_framework(BuiltinFramework::new()
+/// client.with_framework(StandardFramework::new()
/// .command("help", |c| c.exec_help(help_commands::with_embeds)));
/// ```
pub fn with_embeds(_: &mut Context,
@@ -220,10 +219,9 @@ pub fn with_embeds(_: &mut Context,
/// # impl EventHandler for Handler {}
/// # let mut client = Client::new("token", Handler);
/// #
-/// use serenity::framework::help_commands;
-/// use serenity::framework::BuiltinFramework;
+/// use serenity::framework::standard::{StandardFramework, help_commands};
///
-/// client.with_framework(BuiltinFramework::new()
+/// client.with_framework(StandardFramework::new()
/// .command("help", |c| c.exec_help(help_commands::plain)));
/// ```
pub fn plain(_: &mut Context,
diff --git a/src/framework/standard/mod.rs b/src/framework/standard/mod.rs
new file mode 100644
index 0000000..f9c0e11
--- /dev/null
+++ b/src/framework/standard/mod.rs
@@ -0,0 +1,1026 @@
+
+pub mod help_commands;
+
+mod command;
+mod configuration;
+mod create_command;
+mod create_group;
+mod buckets;
+
+pub(crate) use self::buckets::{Bucket, Ratelimit};
+pub use self::command::{Command, CommandGroup, CommandType};
+pub use self::command::CommandOrAlias;
+pub use self::configuration::Configuration;
+pub use self::create_command::CreateCommand;
+pub use self::create_group::CreateGroup;
+
+use self::command::{AfterHook, BeforeHook};
+use std::collections::HashMap;
+use std::default::Default;
+use std::sync::Arc;
+use client::Context;
+use super::Framework;
+use model::{ChannelId, GuildId, Message, UserId};
+use model::permissions::Permissions;
+use utils;
+use tokio_core::reactor::Handle;
+use itertools::Itertools;
+use regex::Regex;
+use regex::escape;
+
+#[cfg(feature = "cache")]
+use client::CACHE;
+#[cfg(feature = "cache")]
+use model::Channel;
+
+/// A macro to generate "named parameters". This is useful to avoid manually
+/// using the "arguments" parameter and manually parsing types.
+///
+/// This is meant for use with the command [`Framework`].
+///
+/// # Examples
+///
+/// Create a regular `ping` command which takes no arguments:
+///
+/// ```rust,ignore
+/// command!(ping(_context, message, _args) {
+/// if let Err(why) = message.reply("Pong!") {
+/// println!("Error sending pong: {:?}", why);
+/// }
+/// });
+/// ```
+///
+/// Create a command named `multiply` which accepts 2 floats and multiplies
+/// them, sending the product as a reply:
+///
+/// ```rust,ignore
+/// command!(multiply(_context, message, _args, first: f64, second: f64) {
+/// let product = first * second;
+///
+/// if let Err(why) = message.reply(&product.to_string()) {
+/// println!("Error sending product: {:?}", why);
+/// }
+/// });
+/// ```
+///
+/// [`Framework`]: framework/index.html
+#[macro_export]
+macro_rules! command {
+ ($fname:ident($c:ident) $b:block) => {
+ #[allow(unreachable_code, unused_mut)]
+ pub fn $fname(mut $c: &mut $crate::client::Context,
+ _: &$crate::model::Message,
+ _: Vec<String>,
+ _: String)
+ -> ::std::result::Result<(), String> {
+ $b
+
+ Ok(())
+ }
+ };
+ ($fname:ident($c:ident, $m:ident) $b:block) => {
+ #[allow(unreachable_code, unused_mut)]
+ pub fn $fname(mut $c: &mut $crate::client::Context,
+ $m: &$crate::model::Message,
+ _: Vec<String>,
+ _: String)
+ -> ::std::result::Result<(), String> {
+ $b
+
+ Ok(())
+ }
+ };
+ ($fname:ident($c:ident, $m:ident, $a:ident) $b:block) => {
+ #[allow(unreachable_code, unused_mut)]
+ pub fn $fname(mut $c: &mut $crate::client::Context,
+ $m: &$crate::model::Message,
+ $a: Vec<String>,
+ _: String)
+ -> ::std::result::Result<(), String> {
+ $b
+
+ Ok(())
+ }
+ };
+ ($fname:ident($c:ident, $m:ident, @$a:ident) $b:block) => {
+ #[allow(unreachable_code, unused_mut)]
+ pub fn $fname(mut $c: &mut $crate::client::Context,
+ $m: &$crate::model::Message,
+ _: Vec<String>,
+ $a: String)
+ -> ::std::result::Result<(), String> {
+ $b
+
+ Ok(())
+ }
+ };
+ ($fname:ident($c:ident, $m:ident, $a:ident, @$f:ident) $b:block) => {
+ #[allow(unreachable_code, unused_mut)]
+ pub fn $fname(mut $c: &mut $crate::client::Context,
+ $m: &$crate::model::Message,
+ $a: Vec<String>,
+ $f: String)
+ -> ::std::result::Result<(), String> {
+ $b
+
+ Ok(())
+ }
+ };
+ ($fname:ident($c:ident, $m:ident, $a:ident, $($name:ident: $t:ty),*) $b:block) => {
+ #[allow(unreachable_code, unreachable_patterns, unused_mut)]
+ pub fn $fname(mut $c: &mut $crate::client::Context,
+ $m: &$crate::model::Message,
+ $a: Vec<String>,
+ _: String)
+ -> ::std::result::Result<(), String> {
+ let mut i = $a.iter();
+ let mut arg_counter = 0;
+
+ $(
+ arg_counter += 1;
+
+ let $name = match i.next() {
+ Some(v) => match v.parse::<$t>() {
+ Ok(v) => v,
+ Err(_) => return Err(format!("Failed to parse argument #{} of type {:?}",
+ arg_counter,
+ stringify!($t))),
+ },
+ None => return Err(format!("Missing argument #{} of type {:?}",
+ arg_counter,
+ stringify!($t))),
+ };
+ )*
+
+ drop(i);
+
+ $b
+
+ Ok(())
+ }
+ };
+}
+
+/// 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(Arc<Command>),
+ /// 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) + 'static;
+
+/// A utility for easily managing dispatches to commands.
+///
+/// Refer to the [module-level documentation] for more information.
+///
+/// [module-level documentation]: index.html
+#[derive(Default)]
+pub struct StandardFramework {
+ 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".
+ ///
+ /// The framework is initialized once one of the following occurs:
+ ///
+ /// - configuration has been set;
+ /// - a command handler has been set;
+ /// - a command check has been set.
+ ///
+ /// This is used internally to determine whether or not - in addition to
+ /// dispatching to the [`EventHandler::on_message`] handler - to have the
+ /// framework check if a [`Event::MessageCreate`] should be processed by
+ /// itself.
+ ///
+ /// [`EventHandler::on_message`]:
+ /// ../client/event_handler/trait.EventHandler.html#method.on_message
+ /// [`Event::MessageCreate`]: ../model/event/enum.Event.html#variant.MessageCreate
+ pub initialized: bool,
+ user_info: (u64, bool),
+}
+
+impl StandardFramework {
+ pub fn new() -> Self { StandardFramework::default() }
+
+ /// Configures the framework, setting non-default values. All fields are
+ /// optional. Refer to [`Configuration::default`] for more information on
+ /// the default values.
+ ///
+ /// # Examples
+ ///
+ /// Configuring the framework for a [`Client`], setting the [`depth`] to 3,
+ /// [allowing whitespace], and setting the [`prefix`] to `"~"`:
+ ///
+ /// ```rust,no_run
+ /// # use serenity::prelude::EventHandler;
+ /// # struct Handler;
+ /// # impl EventHandler for Handler {}
+ /// use serenity::Client;
+ /// use serenity::framework::StandardFramework;
+ /// use std::env;
+ ///
+ /// let mut client = Client::new(&env::var("DISCORD_TOKEN").unwrap(), Handler);
+ /// client.with_framework(StandardFramework::new()
+ /// .configure(|c| c
+ /// .depth(3)
+ /// .allow_whitespace(true)
+ /// .prefix("~")));
+ /// ```
+ ///
+ /// [`Client`]: ../client/struct.Client.html
+ /// [`Configuration::default`]: struct.Configuration.html#method.default
+ /// [`depth`]: struct.Configuration.html#method.depth
+ /// [`prefix`]: struct.Configuration.html#method.prefix
+ /// [allowing whitespace]: struct.Configuration.html#method.allow_whitespace
+ pub fn configure<F>(mut self, f: F) -> Self
+ where F: FnOnce(Configuration) -> Configuration {
+ self.configuration = f(self.configuration);
+
+ self
+ }
+
+ /// Defines a bucket with `delay` between each command, and the `limit` of uses
+ /// per `time_span`.
+ ///
+ /// # Examples
+ ///
+ /// Create and use a bucket that limits a command to 3 uses per 10 seconds with
+ /// a 2 second delay inbetween invocations:
+ ///
+ /// ```rust
+ /// # use serenity::prelude::*;
+ /// # struct Handler;
+ /// #
+ /// # impl EventHandler for Handler {}
+ /// # let mut client = Client::new("token", Handler);
+ /// #
+ /// use serenity::framework::StandardFramework;
+ ///
+ /// client.with_framework(StandardFramework::new()
+ /// .bucket("basic", 2, 10, 3)
+ /// .command("ping", |c| c
+ /// .bucket("basic")
+ /// .exec_str("pong!")));
+ /// ```
+ pub fn bucket<S>(mut self, s: S, delay: i64, time_span: i64, limit: i32) -> Self
+ where S: Into<String> {
+ self.buckets.insert(
+ s.into(),
+ Bucket {
+ ratelimit: Ratelimit {
+ delay: delay,
+ limit: Some((time_span, limit)),
+ },
+ users: HashMap::new(),
+ check: None,
+ },
+ );
+
+ self
+ }
+
+ /// Same as [`bucket`] but with a check added.
+ ///
+ /// # Examples
+ ///
+ /// ```rust
+ /// # use serenity::prelude::*;
+ /// # struct Handler;
+ /// #
+ /// # impl EventHandler for Handler {}
+ /// # let mut client = Client::new("token", Handler);
+ /// #
+ /// use serenity::framework::StandardFramework;
+ ///
+ /// client.with_framework(StandardFramework::new()
+ /// .complex_bucket("basic", 2, 10, 3, |_, guild_id, channel_id, user_id| {
+ /// // check if the guild is `123` and the channel where the command(s) was called:
+ /// // `456`
+ /// // and if the user who called the command(s) is `789`
+ /// // otherwise don't apply the bucket at all.
+ /// guild_id.is_some() && guild_id.unwrap() == 123 && channel_id == 456
+ /// && user_id == 789
+ /// })
+ /// .command("ping", |c| c
+ /// .bucket("basic")
+ /// .exec_str("pong!")));
+ /// ```
+ ///
+ /// [`bucket`]: #method.bucket
+ #[cfg(feature = "cache")]
+ pub fn complex_bucket<S, Check>(mut self,
+ s: S,
+ delay: i64,
+ time_span: i64,
+ limit: i32,
+ check: Check)
+ -> Self
+ where Check: Fn(&mut Context, Option<GuildId>, ChannelId, UserId) -> bool + 'static,
+ S: Into<String> {
+ self.buckets.insert(
+ s.into(),
+ Bucket {
+ ratelimit: Ratelimit {
+ delay,
+ limit: Some((time_span, limit)),
+ },
+ users: HashMap::new(),
+ check: Some(Box::new(check)),
+ },
+ );
+
+ self
+ }
+
+ /// Same as [`bucket`] but with a check added.
+ ///
+ /// # Examples
+ ///
+ /// ```rust
+ /// # use serenity::prelude::*;
+ /// # struct Handler;
+ /// #
+ /// # impl EventHandler for Handler {}
+ /// # let mut client = Client::new("token", Handler);
+ /// #
+ /// use serenity::framework::StandardFramework;
+ ///
+ /// client.with_framework(StandardFramework::new()
+ /// .complex_bucket("basic", 2, 10, 3, |_, channel_id, user_id| {
+ /// // check if the channel's id where the command(s) was called is `456`
+ /// // and if the user who called the command(s) is `789`
+ /// // otherwise don't apply the bucket at all.
+ /// channel_id == 456 && user_id == 789
+ /// })
+ /// .command("ping", |c| c
+ /// .bucket("basic")
+ /// .exec_str("pong!")));
+ /// ```
+ ///
+ /// [`bucket`]: #method.bucket
+ #[cfg(not(feature = "cache"))]
+ pub fn complex_bucket<S, Check>(mut self,
+ s: S,
+ delay: i64,
+ time_span: i64,
+ limit: i32,
+ check: Check)
+ -> Self
+ where Check: Fn(&mut Context, ChannelId, UserId) -> bool + 'static, S: Into<String> {
+ self.buckets.insert(
+ s.into(),
+ Bucket {
+ ratelimit: Ratelimit {
+ delay,
+ limit: Some((time_span, limit)),
+ },
+ users: HashMap::new(),
+ check: Some(Box::new(check)),
+ },
+ );
+
+ self
+ }
+
+ /// Defines a bucket with only a `delay` between each command.
+ ///
+ /// # Examples
+ ///
+ /// Create and use a simple bucket that has a 2 second delay between invocations:
+ ///
+ /// ```rust
+ /// # use serenity::prelude::*;
+ /// # struct Handler;
+ /// #
+ /// # impl EventHandler for Handler {}
+ /// # let mut client = Client::new("token", Handler);
+ /// #
+ /// use serenity::framework::StandardFramework;
+ ///
+ /// client.with_framework(StandardFramework::new()
+ /// .simple_bucket("simple", 2)
+ /// .command("ping", |c| c
+ /// .bucket("simple")
+ /// .exec_str("pong!")));
+ /// ```
+ pub fn simple_bucket<S>(mut self, s: S, delay: i64) -> Self
+ where S: Into<String> {
+ self.buckets.insert(
+ s.into(),
+ Bucket {
+ ratelimit: Ratelimit {
+ delay: delay,
+ limit: None,
+ },
+ users: HashMap::new(),
+ check: None,
+ },
+ );
+
+ self
+ }
+
+ #[cfg(feature = "cache")]
+ fn is_blocked_guild(&self, message: &Message) -> bool {
+ if let Some(Channel::Guild(channel)) = CACHE.read().unwrap().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
+ }
+
+ #[cfg(feature = "cache")]
+ 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
+ }
+
+ #[allow(too_many_arguments)]
+ fn should_fail(&mut self,
+ mut context: &mut Context,
+ message: &Message,
+ command: &Arc<Command>,
+ args: &[String],
+ 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) {
+ None
+ } else {
+ if let Some(ref bucket) = command.bucket {
+ if let Some(ref mut bucket) = self.buckets.get_mut(bucket) {
+ let rate_limit = bucket.take(message.author.id.0);
+ match bucket.check {
+ Some(ref check) => {
+ let apply =
+ feature_cache! {{
+ let guild_id = message.guild_id();
+ (check)(context, guild_id, message.channel_id, message.author.id)
+ } else {
+ (check)(context, message.channel_id, message.author.id)
+ }};
+
+ if apply && rate_limit > 0i64 {
+ return Some(DispatchError::RateLimited(rate_limit));
+ }
+ },
+ None => {
+ if rate_limit > 0i64 {
+ return Some(DispatchError::RateLimited(rate_limit));
+ }
+ },
+ }
+ }
+ }
+
+ let arg_len = args.len();
+
+ if let Some(x) = command.min_args {
+ if arg_len < x as usize {
+ return Some(DispatchError::NotEnoughArguments {
+ min: x,
+ given: arg_len,
+ });
+ }
+ }
+
+ if let Some(x) = command.max_args {
+ if arg_len > x as usize {
+ return Some(DispatchError::TooManyArguments {
+ max: x,
+ given: arg_len,
+ });
+ }
+ }
+
+ #[cfg(feature = "cache")]
+ {
+ if self.is_blocked_guild(message) {
+ return Some(DispatchError::BlockedGuild);
+ }
+
+ if !self.has_correct_permissions(command, message) {
+ return Some(DispatchError::LackOfPermissions(
+ command.required_permissions,
+ ));
+ }
+
+ if (!self.configuration.allow_dm && message.is_private()) ||
+ (command.guild_only && message.is_private()) {
+ return Some(DispatchError::OnlyForGuilds);
+ }
+
+ if command.dm_only && !message.is_private() {
+ return Some(DispatchError::OnlyForDM);
+ }
+ }
+
+ if command.owners_only {
+ Some(DispatchError::OnlyForOwners)
+ } else if self.configuration.blocked_users.contains(
+ &message.author.id,
+ ) {
+ Some(DispatchError::BlockedUser)
+ } 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 {
+ let all_passed = command.checks.iter().all(|check| {
+ check(&mut context, message, args, command)
+ });
+
+ if all_passed {
+ None
+ } else {
+ Some(DispatchError::CheckFailed(command.to_owned()))
+ }
+ }
+ }
+ }
+
+ /// Adds a function to be associated with a command, which will be called
+ /// when a command is used in a message.
+ ///
+ /// This requires that a check - if one exists - passes, prior to being
+ /// called.
+ ///
+ /// Prior to v0.2.0, you will need to use the command builder
+ /// via the [`command`] method to set checks. This command will otherwise
+ /// only be for simple commands.
+ ///
+ /// Refer to the [module-level documentation] for more information and
+ /// usage.
+ ///
+ /// [`command`]: #method.command
+ /// [module-level documentation]: index.html
+ ///
+ /// # Examples
+ ///
+ /// Create and use a simple command:
+ ///
+ /// ```rust
+ /// # #[macro_use] extern crate serenity;
+ /// #
+ /// # fn main() {
+ /// # use serenity::prelude::*;
+ /// # struct Handler;
+ /// #
+ /// # impl EventHandler for Handler {}
+ /// # let mut client = Client::new("token", Handler);
+ /// #
+ /// use serenity::framework::StandardFramework;
+ ///
+ /// client.with_framework(StandardFramework::new().on("ping", ping));
+ ///
+ /// command!(ping(_ctx, msg) {
+ /// let _ = msg.channel_id.say("pong!");
+ /// });
+ /// # }
+ /// ```
+ pub fn on<F, S>(mut self, command_name: S, f: F) -> Self
+ where F: Fn(&mut Context, &Message, Vec<String>, String) -> Result<(), String> + 'static,
+ S: Into<String> {
+ {
+ let ungrouped = self.groups.entry("Ungrouped".to_owned()).or_insert_with(
+ || {
+ Arc::new(CommandGroup::default())
+ },
+ );
+
+ if let Some(ref mut group) = Arc::get_mut(ungrouped) {
+ let name = command_name.into();
+
+ group.commands.insert(
+ name,
+ CommandOrAlias::Command(
+ Arc::new(Command::new(f)),
+ ),
+ );
+ }
+ }
+
+ self.initialized = true;
+
+ self
+ }
+
+ /// Adds a command using command builder.
+ ///
+ /// # Examples
+ ///
+ /// ```rust,ignore
+ /// framework.command("ping", |c| c
+ /// .description("Responds with 'pong'.")
+ /// .exec(|ctx, _, _| {
+ /// let _ = ctx.say("pong");
+ /// }));
+ /// ```
+ pub fn command<F, S>(mut self, command_name: S, f: F) -> Self
+ where F: FnOnce(CreateCommand) -> CreateCommand, S: Into<String> {
+ {
+ let ungrouped = self.groups.entry("Ungrouped".to_owned()).or_insert_with(
+ || {
+ Arc::new(CommandGroup::default())
+ },
+ );
+
+ if let Some(ref mut group) = Arc::get_mut(ungrouped) {
+ let cmd = f(CreateCommand(Command::default())).0;
+ let name = command_name.into();
+
+ if let Some(ref prefix) = group.prefix {
+ for v in &cmd.aliases {
+ group.commands.insert(
+ format!("{} {}", prefix, v),
+ CommandOrAlias::Alias(format!("{} {}", prefix, name)),
+ );
+ }
+ } else {
+ for v in &cmd.aliases {
+ group.commands.insert(
+ v.to_owned(),
+ CommandOrAlias::Alias(name.clone()),
+ );
+ }
+ }
+
+ group.commands.insert(
+ name,
+ CommandOrAlias::Command(Arc::new(cmd)),
+ );
+ }
+ }
+
+ self.initialized = true;
+
+ self
+ }
+
+ /// Adds a group which can organize several related commands.
+ /// Groups are taken into account when using
+ /// `serenity::framework::standard::help_commands`.
+ ///
+ /// # Examples
+ ///
+ /// Creating a simple group:
+ ///
+ /// ```rust
+ /// # use serenity::prelude::*;
+ /// # struct Handler;
+ /// #
+ /// # impl EventHandler for Handler {}
+ /// # let mut client = Client::new("token", Handler);
+ /// #
+ /// use serenity::framework::StandardFramework;
+ ///
+ /// client.with_framework(StandardFramework::new()
+ /// .group("ping-pong", |g| g
+ /// .command("ping", |c| c.exec_str("pong!"))
+ /// .command("pong", |c| c.exec_str("ping!"))));
+ /// ```
+ pub fn group<F, S>(mut self, group_name: S, f: F) -> Self
+ where F: FnOnce(CreateGroup) -> CreateGroup, S: Into<String> {
+ let group = f(CreateGroup(CommandGroup::default())).0;
+
+ self.groups.insert(group_name.into(), Arc::new(group));
+ self.initialized = true;
+
+ 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.
+ ///
+ /// # Examples
+ ///
+ /// Making a simple argument error responder:
+ ///
+ /// ```rust
+ /// # use serenity::prelude::*;
+ /// # struct Handler;
+ /// #
+ /// # impl EventHandler for Handler {}
+ /// # let mut client = Client::new("token", Handler);
+ /// use serenity::framework::standard::DispatchError::{NotEnoughArguments, TooManyArguments};
+ /// use serenity::framework::StandardFramework;
+ ///
+ /// client.with_framework(StandardFramework::new()
+ /// .on_dispatch_error(|_, msg, error| {
+ /// match error {
+ /// NotEnoughArguments { min, given } => {
+ /// let s = format!("Need {} arguments, but only got {}.", min, given);
+ ///
+ /// let _ = msg.channel_id.say(&s);
+ /// },
+ /// TooManyArguments { max, given } => {
+ /// let s = format!("Max arguments allowed is {}, but got {}.", max, given);
+ ///
+ /// let _ = msg.channel_id.say(&s);
+ /// },
+ /// _ => println!("Unhandled dispatch error."),
+ /// }
+ /// }));
+ /// ```
+ pub fn on_dispatch_error<F>(mut self, f: F) -> Self
+ where F: Fn(Context, Message, DispatchError) + '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.
+ ///
+ /// # Examples
+ ///
+ /// Using `before` to log command usage:
+ ///
+ /// ```rust
+ /// # use serenity::prelude::*;
+ /// # struct Handler;
+ /// #
+ /// # impl EventHandler for Handler {}
+ /// # let mut client = Client::new("token", Handler);
+ /// #
+ /// use serenity::framework::StandardFramework;
+ ///
+ /// client.with_framework(StandardFramework::new()
+ /// .before(|ctx, msg, cmd_name| {
+ /// println!("Running command {}", cmd_name);
+ /// true
+ /// }));
+ /// ```
+ ///
+ /// Using before to prevent command usage:
+ ///
+ /// ```rust
+ /// # use serenity::prelude::*;
+ /// # struct Handler;
+ /// #
+ /// # impl EventHandler for Handler {}
+ /// # let mut client = Client::new("token", Handler);
+ /// #
+ /// use serenity::framework::StandardFramework;
+ ///
+ /// client.with_framework(StandardFramework::new()
+ /// .before(|_, msg, cmd_name| {
+ /// if let Ok(channel) = msg.channel_id.get() {
+ /// // Don't run unless in nsfw channel
+ /// if !channel.is_nsfw() {
+ /// return false;
+ /// }
+ /// }
+ ///
+ /// println!("Running command {}", cmd_name);
+ ///
+ /// true
+ /// }));
+ /// ```
+ ///
+ pub fn before<F>(mut self, f: F) -> Self
+ where F: Fn(&mut Context, &Message, &str) -> bool + 'static {
+ self.before = Some(Arc::new(f));
+
+ self
+ }
+
+ /// Specify the function to be called after every command's execution.
+ /// Fourth argument exists if command returned an error which you can handle.
+ ///
+ /// # Examples
+ ///
+ /// Using `after` to log command usage:
+ ///
+ /// ```rust
+ /// # use serenity::prelude::*;
+ /// # struct Handler;
+ /// #
+ /// # impl EventHandler for Handler {}
+ /// # let mut client = Client::new("token", Handler);
+ /// #
+ /// use serenity::framework::StandardFramework;
+ ///
+ /// client.with_framework(StandardFramework::new()
+ /// .after(|ctx, msg, cmd_name, error| {
+ /// // Print out an error if it happened
+ /// if let Err(why) = error {
+ /// println!("Error in {}: {:?}", cmd_name, why);
+ /// }
+ /// }));
+ /// ```
+ pub fn after<F>(mut self, f: F) -> Self
+ where F: Fn(&mut Context, &Message, &str, Result<(), String>) + 'static {
+ self.after = Some(Arc::new(f));
+
+ self
+ }
+}
+
+impl Framework for StandardFramework {
+ fn dispatch(&mut self, mut context: Context, message: Message, tokio_handle: &Handle) {
+ let res = command::positions(&mut context, &message, &self.configuration);
+
+ let positions = match res {
+ Some(mut positions) => {
+ // First, take out the prefixes that are as long as _or_ longer
+ // than the message, to avoid character boundary violations.
+ positions.retain(|p| *p < message.content.len());
+
+ // Ensure that there is _at least one_ position remaining. There
+ // is no point in continuing if there is not.
+ if positions.is_empty() {
+ return;
+ }
+
+ positions
+ },
+ None => return,
+ };
+
+ 'outer: for position in positions {
+ let mut built = String::new();
+ let round = message.content.chars().skip(position).collect::<String>();
+ let round = round.trim().split_whitespace().collect::<Vec<&str>>();
+
+ for i in 0..self.configuration.depth {
+ if i != 0 {
+ built.push(' ');
+ }
+
+ built.push_str(match round.get(i) {
+ Some(piece) => piece,
+ None => continue 'outer,
+ });
+
+ let groups = self.groups.clone();
+
+ for group in groups.values() {
+ let command_length = built.len();
+
+ if let Some(&CommandOrAlias::Alias(ref points_to)) =
+ group.commands.get(&built) {
+ built = points_to.to_owned();
+ }
+
+ let to_check = if let Some(ref prefix) = group.prefix {
+ if built.starts_with(prefix) && command_length > prefix.len() + 1 {
+ built[(prefix.len() + 1)..].to_owned()
+ } else {
+ continue;
+ }
+ } else {
+ built.clone()
+ };
+
+ if let Some(&CommandOrAlias::Command(ref command)) =
+ group.commands.get(&to_check) {
+ let before = self.before.clone();
+ let command = command.clone();
+ let after = self.after.clone();
+ let groups = self.groups.clone();
+
+ let (args, content) = {
+ let mut content = message.content[position..].trim();
+ content = content[command_length..].trim();
+
+ if command.use_quotes {
+ (utils::parse_quotes(content), content.to_string())
+ } else {
+ let delimiters = &self.configuration.delimiters;
+ let regular_expression = delimiters
+ .iter()
+ .map(|delimiter| escape(delimiter))
+ .join("|");
+
+ let regex = Regex::new(&regular_expression).unwrap();
+
+ (
+ regex
+ .split(content)
+ .filter_map(|p| if p.is_empty() {
+ None
+ } else {
+ Some(p.to_string())
+ })
+ .collect::<Vec<_>>(),
+ content.to_string(),
+ )
+ }
+ };
+
+ if let Some(error) = self.should_fail(
+ &mut context,
+ &message,
+ &command,
+ &args,
+ &to_check,
+ &built,
+ ) {
+ if let Some(ref handler) = self.dispatch_error_handler {
+ handler(context, message, error);
+ }
+ return;
+ }
+
+ tokio_handle.spawn_fn(move || {
+ if let Some(before) = before {
+ if !(before)(&mut context, &message, &built) {
+ return Ok(());
+ }
+ }
+
+ let result = match command.exec {
+ CommandType::StringResponse(ref x) => {
+ let _ = message.channel_id.say(x);
+
+ Ok(())
+ },
+ CommandType::Basic(ref x) => {
+ (x)(&mut context, &message, args, content)
+ },
+ CommandType::WithCommands(ref x) => {
+ (x)(&mut context, &message, groups, &args)
+ },
+ };
+
+ if let Some(after) = after {
+ (after)(&mut context, &message, &built, result);
+ }
+
+ Ok(())
+ });
+
+ return;
+ }
+ }
+ }
+ }
+ }
+
+ fn update_current_user(&mut self, user_id: UserId, is_bot: bool) {
+ self.user_info = (user_id.0, is_bot);
+ }
+
+ fn initialized(&self) -> bool { self.initialized }
+}