diff options
Diffstat (limited to 'src/ext/framework')
| -rw-r--r-- | src/ext/framework/buckets.rs | 60 | ||||
| -rw-r--r-- | src/ext/framework/command.rs | 164 | ||||
| -rw-r--r-- | src/ext/framework/configuration.rs | 261 | ||||
| -rw-r--r-- | src/ext/framework/create_command.rs | 226 | ||||
| -rw-r--r-- | src/ext/framework/create_group.rs | 68 | ||||
| -rw-r--r-- | src/ext/framework/help_commands.rs | 285 | ||||
| -rw-r--r-- | src/ext/framework/mod.rs | 665 |
7 files changed, 0 insertions, 1729 deletions
diff --git a/src/ext/framework/buckets.rs b/src/ext/framework/buckets.rs deleted file mode 100644 index 02cd658..0000000 --- a/src/ext/framework/buckets.rs +++ /dev/null @@ -1,60 +0,0 @@ -use std::collections::HashMap; -use std::default::Default; -use time; - -#[doc(hidden)] -pub struct Ratelimit { - pub delay: i64, - pub limit: Option<(i64, i32)>, -} - -#[doc(hidden)] -pub struct MemberRatelimit { - pub tickets: i32, - pub last_time: i64, - pub set_time: i64, -} - -impl Default for MemberRatelimit { - fn default() -> Self { - MemberRatelimit { - tickets: 0, - last_time: 0, - set_time: 0, - } - } -} - -#[doc(hidden)] -pub struct Bucket { - pub ratelimit: Ratelimit, - pub users: HashMap<u64, MemberRatelimit>, -} - -impl Bucket { - pub fn take(&mut self, user_id: u64) -> i64 { - let time = time::get_time().sec; - let user = self.users.entry(user_id) - .or_insert_with(MemberRatelimit::default); - - if let Some((timespan, limit)) = self.ratelimit.limit { - if (user.tickets + 1) > limit { - if time < (user.set_time + timespan) { - return (user.set_time + timespan) - time; - } else { - user.tickets = 0; - user.set_time = time; - } - } - } - - if time < user.last_time + self.ratelimit.delay { - (user.last_time + self.ratelimit.delay) - time - } else { - user.tickets += 1; - user.last_time = time; - - 0 - } - } -} diff --git a/src/ext/framework/command.rs b/src/ext/framework/command.rs deleted file mode 100644 index 5f43284..0000000 --- a/src/ext/framework/command.rs +++ /dev/null @@ -1,164 +0,0 @@ -use std::sync::Arc; -use super::Configuration; -use ::client::Context; -use ::model::Message; -use ::model::Permissions; -use std::collections::HashMap; - -pub type Check = Fn(&mut Context, &Message) -> bool + Send + Sync + 'static; -pub type Exec = Fn(&mut Context, &Message, Vec<String>) -> Result<(), String> + Send + Sync + 'static; -pub type Help = Fn(&mut Context, &Message, HashMap<String, Arc<CommandGroup>>, &[String]) -> Result<(), String> + Send + Sync + 'static; -pub type BeforeHook = Fn(&mut Context, &Message, &String) -> bool + Send + Sync + 'static; -pub type AfterHook = Fn(&mut Context, &Message, &String, Result<(), String>) + Send + Sync + 'static; -#[doc(hidden)] -pub type InternalCommand = Arc<Command>; -pub type PrefixCheck = Fn(&mut Context) -> Option<String> + Send + Sync + 'static; - -#[doc(hidden)] -pub enum CommandOrAlias { - Alias(String), - Command(InternalCommand), -} - -/// Command function type. Allows to access internal framework things inside -/// your commands. -pub enum CommandType { - StringResponse(String), - Basic(Box<Exec>), - WithCommands(Box<Help>), -} - -#[derive(Default)] -pub struct CommandGroup { - pub prefix: Option<String>, - pub commands: HashMap<String, CommandOrAlias>, -} - -/// Command struct used to store commands internally. -pub struct Command { - /// A set of checks to be called prior to executing the command. The checks - /// will short-circuit on the first check that returns `false`. - pub checks: Vec<Box<Check>>, - /// Function called when the command is called. - pub exec: CommandType, - /// Ratelimit bucket. - pub bucket: Option<String>, - /// Command description, used by other commands. - pub desc: Option<String>, - /// Example arguments, used by other commands. - pub example: Option<String>, - /// Command usage schema, used by other commands. - pub usage: Option<String>, - /// Whether arguments should be parsed using quote parser or not. - pub use_quotes: bool, - /// Minumum 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>, - /// Permissions required to use this command. - pub required_permissions: Permissions, - /// Whether command should be displayed in help list or not, used by other commands. - pub help_available: bool, - /// Whether command can be used only privately or not. - pub dm_only: bool, - /// Whether command can be used only in guilds or not. - pub guild_only: bool, - /// Whether command can only be used by owners or not. - pub owners_only: bool, - #[doc(hidden)] - pub aliases: Vec<String>, -} - -impl Command { - pub fn new<F>(f: F) -> Self - where F: Fn(&mut Context, &Message, Vec<String>) -> Result<(), String> + Send + Sync + 'static { - Command { - aliases: Vec::new(), - checks: Vec::default(), - exec: CommandType::Basic(Box::new(f)), - desc: None, - usage: None, - example: None, - use_quotes: false, - dm_only: false, - bucket: None, - guild_only: false, - help_available: true, - min_args: None, - max_args: None, - owners_only: false, - required_permissions: Permissions::empty(), - } - } -} - -pub fn positions(ctx: &mut Context, content: &str, conf: &Configuration) -> Option<Vec<usize>> { - if !conf.prefixes.is_empty() || conf.dynamic_prefix.is_some() { - // Find out if they were mentioned. If not, determine if the prefix - // was used. If not, return None. - let mut positions: Vec<usize> = vec![]; - - if let Some(mention_end) = find_mention_end(content, conf) { - positions.push(mention_end); - } else if let Some(ref func) = conf.dynamic_prefix { - if let Some(x) = func(ctx) { - if content.starts_with(&x) { - positions.push(x.len()); - } - } else { - for n in conf.prefixes.clone() { - if content.starts_with(&n) { - positions.push(n.len()); - } - } - } - } else { - for n in conf.prefixes.clone() { - if content.starts_with(&n) { - positions.push(n.len()); - } - } - }; - - if positions.is_empty() { - return None; - } - - if conf.allow_whitespace { - let pos = *unsafe { positions.get_unchecked(0) }; - - positions.insert(0, pos + 1); - } - - Some(positions) - } else if conf.on_mention.is_some() { - match find_mention_end(content, conf) { - Some(mention_end) => { - let mut positions = vec![mention_end]; - - if conf.allow_whitespace { - positions.insert(0, mention_end + 1); - } - - Some(positions) - }, - None => None, - } - } else { - None - } -} - -fn find_mention_end(content: &str, conf: &Configuration) -> Option<usize> { - if let Some(ref mentions) = conf.on_mention { - for mention in mentions { - if !content.starts_with(&mention[..]) { - continue; - } - - return Some(mention.len()); - } - } - - None -} diff --git a/src/ext/framework/configuration.rs b/src/ext/framework/configuration.rs deleted file mode 100644 index 4f73359..0000000 --- a/src/ext/framework/configuration.rs +++ /dev/null @@ -1,261 +0,0 @@ -use std::collections::HashSet; -use std::default::Default; -use super::command::PrefixCheck; -use ::client::{Context, rest}; -use ::model::{GuildId, UserId}; - -/// The configuration to use for a [`Framework`] associated with a [`Client`] -/// instance. -/// -/// This allows setting configurations like the depth to search for commands, -/// whether to treat mentions like a command prefix, etc. -/// -/// # Examples -/// -/// Responding to mentions and setting a command prefix of `"~"`: -/// -/// ```rust,no_run -/// use serenity::Client; -/// use std::env; -/// -/// let mut client = Client::login(&env::var("DISCORD_BOT_TOKEN").unwrap()); -/// -/// client.with_framework(|f| f -/// .configure(|c| c.on_mention(true).prefix("~"))); -/// ``` -/// -/// [`Client`]: ../../client/struct.Client.html -/// [`Framework`]: struct.Framework.html -pub struct Configuration { - #[doc(hidden)] - pub depth: usize, - #[doc(hidden)] - pub on_mention: Option<Vec<String>>, - #[doc(hidden)] - pub allow_whitespace: bool, - #[doc(hidden)] - pub prefixes: Vec<String>, - #[doc(hidden)] - pub dynamic_prefix: Option<Box<PrefixCheck>>, - #[doc(hidden)] - pub ignore_bots: bool, - #[doc(hidden)] - pub blocked_users: HashSet<UserId>, - #[doc(hidden)] - pub blocked_guilds: HashSet<GuildId>, - #[doc(hidden)] - pub owners: HashSet<UserId>, - #[doc(hidden)] - pub disabled_commands: HashSet<String>, - #[doc(hidden)] - pub allow_dm: bool, - #[doc(hidden)] - pub ignore_webhooks: bool, -} - -impl Configuration { - /// 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 - } - - /// Whether to allow whitespace being optional between a mention/prefix and - /// a command. - /// - /// **Note**: Defaults to `false`. - /// - /// # Examples - /// - /// Setting this to `false` will _only_ allow this scenario to occur: - /// - /// ```ignore - /// <@245571012924538880> about - /// !about - /// - /// // bot processes and executes the "about" command if it exists - /// ``` - /// - /// while setting this to `true` will _also_ allow this scenario to occur: - /// - /// ```ignore - /// <@245571012924538880>about - /// ! about - /// - /// // bot processes and executes the "about" command if it exists - /// ``` - pub fn allow_whitespace(mut self, allow_whitespace: bool) -> Self { - self.allow_whitespace = allow_whitespace; - - self - } - - /// If set to false, bot will ignore any private messages. - pub fn allow_dm(mut self, allow_dm: bool) -> Self { - self.allow_dm = allow_dm; - - self - } - - /// If set to true, bot will ignore all commands called by webhooks. - /// True by default. - pub fn ignore_webhooks(mut self, ignore_webhooks: bool) -> Self { - self.ignore_webhooks = ignore_webhooks; - - self - } - - /// HashSet of user Ids whose commands will be ignored. - /// Guilds owned by user Ids will also be ignored. - pub fn blocked_users(mut self, users: HashSet<UserId>) -> Self { - self.blocked_users = users; - - self - } - - /// HashSet of guild Ids where commands will be ignored. - pub fn blocked_guilds(mut self, guilds: HashSet<GuildId>) -> Self { - self.blocked_guilds = guilds; - - self - } - - /// The default depth of the message to check for commands. Defaults to 5. - /// This determines how "far" into a message to check for a valid command. - /// - /// # Examples - /// - /// If you set a depth of `1`, and make a command of `"music play"`, but - /// not a `"music"` command, then the former command will never be - /// triggered, as its "depth" is `2`. - pub fn depth(mut self, depth: u8) -> Self { - self.depth = depth as usize; - - self - } - - /// HashSet of command names that won't be run. - pub fn disabled_commands(mut self, commands: HashSet<String>) -> Self { - self.disabled_commands = commands; - - self - } - - /// Sets the prefix to respond to dynamically based on conditions. - /// - /// Return `None` to not have a special prefix for the dispatch, and to - /// instead use the inherited prefix. - /// - /// # Examples - /// - /// If the Id of the channel is divisible by 5, return a prefix of `"!"`, - /// otherwise return a prefix of `"~"`. - /// - /// ```rust,no_run - /// # use serenity::Client; - /// # - /// # let mut client = Client::login("token"); - /// client.with_framework(|f| f - /// .command("ping", |c| c.exec_str("Pong!")) - /// .configure(|c| c.dynamic_prefix(|ctx| { - /// Some(if ctx.channel_id.unwrap().0 % 5 == 0 { - /// "!" - /// } else { - /// "~" - /// }.to_owned()) - /// }))); - /// ``` - pub fn dynamic_prefix<F>(mut self, dynamic_prefix: F) -> Self - where F: Fn(&mut Context) -> Option<String> + Send + Sync + 'static { - self.dynamic_prefix = Some(Box::new(dynamic_prefix)); - - self - } - - /// Whether or not to respond to commands initiated with a mention. Note - /// that this can be used in conjunction with [`prefix`]. - /// - /// By default this is set to `false`. - /// - /// # Examples - /// - /// Setting this to `true` will allow the following types of mentions to be - /// responded to: - /// - /// ```ignore - /// <@245571012924538880> about - /// <@!245571012924538880> about - /// ``` - /// - /// The former is a direct mention, while the latter is a nickname mention, - /// which aids mobile devices in determining whether to display a user's - /// nickname. It has no real meaning for your bot, and the library - /// encourages you to ignore differentiating between the two. - /// - /// [`prefix`]: #method.prefix - pub fn on_mention(mut self, on_mention: bool) -> Self { - if !on_mention { - return self; - } - - if let Ok(current_user) = rest::get_current_user() { - self.on_mention = Some(vec![ - format!("<@{}>", current_user.id), // Regular mention - format!("<@!{}>", current_user.id), // Nickname mention - ]); - } - - self - } - - /// A `HashSet` of user Ids checks won't apply to. - pub fn owners(mut self, user_ids: HashSet<UserId>) -> Self { - self.owners = user_ids; - - self - } - - /// Sets the prefix to respond to. This can either be a single-char or - /// multi-char string. - pub fn prefix(mut self, prefix: &str) -> Self { - self.prefixes = vec![prefix.to_owned()]; - - self - } - - /// Sets the prefixes to respond to. Those can either be single-chararacter or - /// multi-chararacter strings. - pub fn prefixes(mut self, prefixes: Vec<&str>) -> Self { - self.prefixes = prefixes.iter().map(|x| x.to_string()).collect(); - - self - } -} - -impl Default for Configuration { - /// Builds a default framework configuration, setting the following: - /// - /// - **allow_whitespace** to `false` - /// - **depth** to `5` - /// - **on_mention** to `false` (basically) - /// - **prefix** to `None` - fn default() -> Configuration { - Configuration { - depth: 5, - on_mention: None, - dynamic_prefix: None, - allow_whitespace: false, - prefixes: vec![], - ignore_bots: true, - owners: HashSet::default(), - blocked_users: HashSet::default(), - blocked_guilds: HashSet::default(), - disabled_commands: HashSet::default(), - allow_dm: true, - ignore_webhooks: true, - } - } -} diff --git a/src/ext/framework/create_command.rs b/src/ext/framework/create_command.rs deleted file mode 100644 index 512e82c..0000000 --- a/src/ext/framework/create_command.rs +++ /dev/null @@ -1,226 +0,0 @@ -pub use super::{Command, CommandType, CommandGroup}; - -use std::collections::HashMap; -use std::default::Default; -use std::sync::Arc; -use ::client::Context; -use ::model::{Message, Permissions}; - -pub struct CreateCommand(pub Command); - -impl CreateCommand { - /// Adds a ratelimit bucket. - pub fn bucket(mut self, bucket: &str) -> Self { - self.0.bucket = Some(bucket.to_owned()); - - self - } - - /// Adds an alias, allowing users to use the command under a different name. - pub fn known_as(mut self, name: &str) -> Self { - self.0.aliases.push(name.to_owned()); - - self - } - - /// Adds multiple aliases. - pub fn batch_known_as(mut self, names: Vec<&str>) -> Self { - for n in names { - self.0.aliases.push(n.to_owned()); - } - - self - } - - /// Adds a "check" to a command, which checks whether or not the command's - /// function should be called. - /// - /// # Examples - /// - /// Ensure that the user who created a message, calling a "ping" command, - /// is the owner. - /// - /// ```rust,no_run - /// 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("~")) - /// .command("ping", |c| c - /// .check(owner_check) - /// .desc("Replies to a ping with a pong") - /// .exec(ping))); - /// - /// fn ping(_context: &mut Context, message: &Message, _args: Vec<String>) -> Result<(), String> { - /// let _ = message.channel_id.say("Pong!"); - /// - /// Ok(()) - /// } - /// - /// fn owner_check(_context: &mut Context, message: &Message) -> bool { - /// // replace with your user ID - /// message.author.id == 7 - /// } - /// ``` - pub fn check<F>(mut self, check: F) -> Self - where F: Fn(&mut Context, &Message) -> bool + Send + Sync + 'static { - self.0.checks.push(Box::new(check)); - - self - } - - /// Description, used by other commands. - pub fn desc(mut self, desc: &str) -> Self { - self.0.desc = Some(desc.to_owned()); - - self - } - - /// Whether command can be used only privately or not. - pub fn dm_only(mut self, dm_only: bool) -> Self { - self.0.dm_only = dm_only; - - self - } - - /// Example arguments, used by other commands. - pub fn example(mut self, example: &str) -> Self { - self.0.example = Some(example.to_owned()); - - self - } - - /// A function that can be called when a command is received. - /// You can return `Err(string)` if there's an error. - /// - /// See [`exec_str`] if you _only_ need to return a string on command use. - /// - /// [`exec_str`]: #method.exec_str - pub fn exec<F>(mut self, func: F) -> Self - where F: Fn(&mut Context, &Message, Vec<String>) -> Result<(), String> + Send + Sync + 'static { - self.0.exec = CommandType::Basic(Box::new(func)); - - self - } - - /// Sets a function that's called when a command is called that can access - /// the internal HashMap of commands, used specifically for creating a help - /// command. - /// - /// You can return `Err(string)` if there's an error. - pub fn exec_help<F>(mut self, f: F) -> Self - where F: Fn(&mut Context, &Message, HashMap<String, Arc<CommandGroup>>, &[String]) -> Result<(), String> + Send + Sync + 'static { - self.0.exec = CommandType::WithCommands(Box::new(f)); - - self - } - - /// Sets a string to be sent in the channel of context on command. This can - /// be useful for an `about`, `invite`, `ping`, etc. command. - /// - /// # Examples - /// - /// Create a command named "ping" that returns "Pong!": - /// - /// ```rust,ignore - /// client.with_framework(|f| f - /// .command("ping", |c| c.exec_str("Pong!"))); - /// ``` - pub fn exec_str(mut self, content: &str) -> Self { - self.0.exec = CommandType::StringResponse(content.to_owned()); - - self - } - - /// Whether command can be used only in guilds or not. - pub fn guild_only(mut self, guild_only: bool) -> Self { - self.0.guild_only = guild_only; - - 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; - - self - } - - /// Maximum amount of arguments that can be passed. - pub fn max_args(mut self, max_args: i32) -> Self { - self.0.max_args = Some(max_args); - - self - } - - /// Minumum amount of arguments that should be passed. - pub fn min_args(mut self, min_args: i32) -> Self { - self.0.min_args = Some(min_args); - - self - } - - /// Whether command can be used only privately or not. - pub fn owners_only(mut self, owners_only: bool) -> Self { - self.0.owners_only = owners_only; - - self - } - - /// The permissions that a user must have in the contextual channel in order - /// for the command to be processed. - pub fn required_permissions(mut self, permissions: Permissions) -> Self { - self.0.required_permissions = permissions; - - self - } - - /// Command usage schema, used by other commands. - pub fn usage(mut self, usage: &str) -> Self { - self.0.usage = Some(usage.to_owned()); - - self - } - - /// Whether or not arguments should be parsed using the quotation parser. - /// - /// Enabling this will parse `~command "this is arg 1" "this is arg 2"` as - /// two arguments: `this is arg 1` and `this is arg 2`. - /// - /// Disabling this will parse `~command "this is arg 1" "this is arg 2"` as - /// eight arguments: `"this`, `is`, `arg`, `1"`, `"this`, `is`, `arg`, `2"`. - /// - /// Refer to [`utils::parse_quotes`] for information on the parser. - /// - /// [`utils::parse_quotes`]: ../../utils/fn.parse_quotes.html - pub fn use_quotes(mut self, use_quotes: bool) -> Self { - self.0.use_quotes = use_quotes; - - self - } -} - -impl Default for Command { - fn default() -> Command { - Command { - aliases: Vec::new(), - checks: Vec::default(), - exec: CommandType::Basic(Box::new(|_, _, _| Ok(()))), - desc: None, - usage: None, - example: None, - use_quotes: false, - min_args: None, - bucket: None, - max_args: None, - required_permissions: Permissions::empty(), - dm_only: false, - guild_only: false, - help_available: true, - owners_only: false, - } - } -} diff --git a/src/ext/framework/create_group.rs b/src/ext/framework/create_group.rs deleted file mode 100644 index 03fc33e..0000000 --- a/src/ext/framework/create_group.rs +++ /dev/null @@ -1,68 +0,0 @@ -pub use ext::framework::command::{Command, CommandType, CommandGroup, CommandOrAlias}; -pub use ext::framework::create_command::CreateCommand; - -use std::default::Default; -use std::sync::Arc; -use ::client::Context; -use ::model::Message; - -#[derive(Default)] -pub struct CreateGroup(pub CommandGroup); - -/// Used to create command groups -/// -/// # Examples -/// -/// Create group named Information where all commands are prefixed with info, -/// and add one command named "name". For example, if prefix is "~", we say "~info name" -/// to call the "name" command. -/// -/// ```rust,ignore -/// framework.group("Information", |g| g -/// .prefix("info") -/// .command("name", |c| c -/// .exec_str("Hakase"))) -/// ``` -impl CreateGroup { - /// Adds a command to group. - pub fn command<F>(mut self, command_name: &str, f: F) -> Self - where F: FnOnce(CreateCommand) -> CreateCommand { - let cmd = f(CreateCommand(Command::default())).0; - - for n in &cmd.aliases { - if let Some(ref prefix) = self.0.prefix { - self.0.commands.insert(format!("{} {}", prefix, n.to_owned()), CommandOrAlias::Alias(format!("{} {}", prefix, command_name.to_string()))); - } else { - self.0.commands.insert(n.to_owned(), CommandOrAlias::Alias(command_name.to_string())); - } - } - - self.0.commands.insert(command_name.to_owned(), CommandOrAlias::Command(Arc::new(cmd))); - - self - } - - /// Adds a command to group with simplified API. - /// You can return Err(string) if there's an error. - pub fn on<F>(mut self, command_name: &str, f: F) -> Self - where F: Fn(&mut Context, &Message, Vec<String>) -> Result<(), String> + Send + Sync + 'static { - let cmd = Arc::new(Command::new(f)); - - self.0.commands.insert(command_name.to_owned(), CommandOrAlias::Command(cmd)); - - self - } - - /// If prefix is set, it will be required before all command names. - /// For example, if bot prefix is "~" and group prefix is "image" - /// we'd call a subcommand named "hibiki" by sending "~image hibiki". - /// - /// **Note**: serenity automatically puts a space after group prefix. - /// - /// **Note**: It's suggested to call this first when making a group. - pub fn prefix(mut self, desc: &str) -> Self { - self.0.prefix = Some(desc.to_owned()); - - self - } -} diff --git a/src/ext/framework/help_commands.rs b/src/ext/framework/help_commands.rs deleted file mode 100644 index 1f38bd5..0000000 --- a/src/ext/framework/help_commands.rs +++ /dev/null @@ -1,285 +0,0 @@ -//! A collection of default help commands for the framework. -//! -//! # Example -//! -//! Using the [`with_embeds`] function to have the framework's help message use -//! embeds: -//! -//! ```rs,no_run -//! use serenity::ext::framework::help_commands; -//! use serenity::Client; -//! use std::env; -//! -//! let mut client = Client::login(&env::var("DISCORD_TOKEN").unwrap()); -//! client.with_framework(|f| f -//! .command("help", |c| c.exec_help(help_commands::with_embeds))); -//! ``` -//! -//! The same can be accomplished with no embeds by substituting `with_embeds` -//! with the [`plain`] function. -//! -//! [`plain`]: fn.plain.html -//! [`with_embeds`]: fn.with_embeds.html - -use std::collections::HashMap; -use std::sync::Arc; -use std::fmt::Write; -use super::command::InternalCommand; -use super::{Command, CommandGroup, CommandOrAlias}; -use ::client::Context; -use ::model::Message; -use ::utils::Colour; - -fn error_embed(ctx: &mut Context, input: &str) { - let _ = ctx.channel_id - .unwrap() - .send_message(|m| m - .embed(|e| e - .colour(Colour::dark_red()) - .description(input))); -} - -fn remove_aliases(cmds: &HashMap<String, CommandOrAlias>) -> HashMap<&String, &InternalCommand> { - let mut result = HashMap::new(); - - for (n, v) in cmds { - if let CommandOrAlias::Command(ref cmd) = *v { - result.insert(n, cmd); - } - } - - result -} - -pub fn with_embeds(ctx: &mut Context, - _: &Message, - groups: HashMap<String, Arc<CommandGroup>>, - args: &[String]) -> Result<(), String> { - if !args.is_empty() { - let name = args.join(" "); - - for (group_name, group) in groups { - let mut found: Option<(&String, &InternalCommand)> = None; - - for (command_name, command) in &group.commands { - let with_prefix = if let Some(ref prefix) = group.prefix { - format!("{} {}", prefix, command_name) - } else { - command_name.to_owned() - }; - - if name == with_prefix || name == *command_name { - match *command { - CommandOrAlias::Command(ref cmd) => { - found = Some((command_name, cmd)); - }, - CommandOrAlias::Alias(ref name) => { - error_embed(ctx, &format!("Did you mean \"{}\"?", name)); - return Ok(()); - } - } - } - } - - if let Some((command_name, command)) = found { - if !command.help_available { - error_embed(ctx, "**Error**: No help available."); - - return Ok(()); - } - - let _ = ctx.channel_id.unwrap().send_message(|m| { - m.embed(|e| { - let mut embed = e.colour(Colour::rosewater()) - .title(command_name); - if let Some(ref desc) = command.desc { - embed = embed.description(desc); - } - - if let Some(ref usage) = command.usage { - embed = embed.field(|f| f - .name("Usage") - .value(&format!("`{} {}`", command_name, usage))); - } - - if let Some(ref example) = command.example { - embed = embed.field(|f| f - .name("Sample usage") - .value(&format!("`{} {}`", command_name, example))); - } - - if group_name != "Ungrouped" { - embed = embed.field(|f| f - .name("Group") - .value(&group_name)); - } - - let available = if command.dm_only { - "Only in DM" - } else if command.guild_only { - "Only in guilds" - } else { - "In DM and guilds" - }; - - embed = embed.field(|f| f - .name("Available") - .value(available)); - - embed - }) - }); - - return Ok(()); - } - } - - let error_msg = format!("**Error**: Command `{}` not found.", name); - error_embed(ctx, &error_msg); - - return Ok(()); - } - - let _ = ctx.channel_id.unwrap().send_message(|m| m - .embed(|mut e| { - e = e.colour(Colour::rosewater()) - .description("To get help with an individual command, pass its \ - name as an argument to this command."); - - for (group_name, group) in groups { - let mut desc = String::new(); - - if let Some(ref x) = group.prefix { - let _ = write!(desc, "Prefix: {}\n", x); - } - - let mut no_commands = true; - - for (n, cmd) in remove_aliases(&group.commands) { - if cmd.help_available { - let _ = write!(desc, "`{}`\n", n); - - no_commands = false; - } - } - - if no_commands { - let _ = write!(desc, "*[No commands]*"); - } - - e = e.field(|f| f.name(&group_name).value(&desc)); - } - - e - })); - - Ok(()) -} - -pub fn plain(ctx: &mut Context, - _: &Message, - groups: HashMap<String, Arc<CommandGroup>>, - args: &[String]) -> Result<(), String> { - if !args.is_empty() { - let name = args.join(" "); - - for (group_name, group) in groups { - let mut found: Option<(&String, &Command)> = None; - - for (command_name, command) in &group.commands { - let with_prefix = if let Some(ref prefix) = group.prefix { - format!("{} {}", prefix, command_name) - } else { - command_name.to_owned() - }; - - if name == with_prefix || name == *command_name { - match *command { - CommandOrAlias::Command(ref cmd) => { - found = Some((command_name, cmd)); - }, - CommandOrAlias::Alias(ref name) => { - let _ = ctx.channel_id.unwrap().say(&format!("Did you mean {:?}?", name)); - return Ok(()); - } - } - } - } - - if let Some((command_name, command)) = found { - if !command.help_available { - let _ = ctx.channel_id.unwrap().say("**Error**: No help available."); - return Ok(()); - } - - let mut result = format!("**{}**\n", command_name); - - if let Some(ref desc) = command.desc { - let _ = write!(result, "**Description:** {}\n", desc); - } - - if let Some(ref usage) = command.usage { - let _ = write!(result, "**Usage:** `{} {}`\n", command_name, usage); - } - - if let Some(ref example) = command.example { - let _ = write!(result, "**Sample usage:** `{} {}`\n", command_name, example); - } - - if group_name != "Ungrouped" { - let _ = write!(result, "**Group:** {}\n", group_name); - } - - result.push_str("**Available:** "); - result.push_str(if command.dm_only { - "Only in DM" - } else if command.guild_only { - "Only in guilds" - } else { - "In DM and guilds" - }); - result.push_str("\n"); - - let _ = ctx.channel_id.unwrap().say(&result); - - return Ok(()); - } - } - - let _ = ctx.channel_id.unwrap().say(&format!("**Error**: Command `{}` not found.", name)); - - return Ok(()); - } - - let mut result = "**Commands**\nTo get help with an individual command, pass its \ - name as an argument to this command.\n\n" - .to_string(); - - for (group_name, group) in groups { - let _ = write!(result, "**{}:** ", group_name); - - if let Some(ref x) = group.prefix { - let _ = write!(result, "(prefix: `{}`): ", x); - } - - let mut no_commands = true; - - for (n, cmd) in remove_aliases(&group.commands) { - if cmd.help_available { - let _ = write!(result, "`{}` ", n); - - no_commands = false; - } - } - - if no_commands { - result.push_str("*[No Commands]*"); - } - - result.push('\n'); - } - - let _ = ctx.channel_id.unwrap().say(&result); - - Ok(()) -} diff --git a/src/ext/framework/mod.rs b/src/ext/framework/mod.rs deleted file mode 100644 index cde10b2..0000000 --- a/src/ext/framework/mod.rs +++ /dev/null @@ -1,665 +0,0 @@ -//! The framework is a customizable method of separating commands. -//! -//! This is used in combination with [`Client::with_framework`]. -//! -//! The framework has a number of configurations, and can have any number of -//! commands bound to it. The primary purpose of it is to offer the utility of -//! not needing to manually match message content strings to determine if a -//! message is a command. -//! -//! Additionally, "checks" can be added to commands, to ensure that a certain -//! condition is met prior to calling a command; this could be a check that the -//! user who posted a message owns the bot, for example. -//! -//! Each command has a given named, and an associated function/closure. For -//! example, you might have two commands: `"ping"` and `"weather"`. These each -//! have an associated function that are called if the framework determines -//! that a message is of that command. -//! -//! Assuming a command prefix of `"~"`, then the following would occur with the -//! two previous commands: -//! -//! ```ignore -//! ~ping // calls the ping command's function -//! ~pin // does not -//! ~ ping // _does_ call it _if_ the `allow_whitespace` option is enabled -//! ~~ping // does not -//! ``` -//! -//! # Examples -//! -//! Configuring a Client with a framework, which has a prefix of `"~"` and a -//! ping and about command: -//! -//! ```rust,ignore -//! use serenity::client::{Client, Context}; -//! use serenity::model::Message; -//! use std::env; -//! -//! let mut client = Client::login(&env::var("DISCORD_BOT_TOKEN").unwrap()); -//! -//! client.with_framework(|f| f -//! .configure(|c| c.prefix("~")) -//! .command("about", |c| c.exec_str("A simple test bot")) -//! .command("ping", |c| c.exec(ping))); -//! -//! command!(about(_context, message) { -//! let _ = message.channel_id.say("A simple test bot"); -//! }); -//! -//! command!(ping(_context, message) { -//! let _ = message.channel_id.say("Pong!"); -//! }); -//! ``` -//! -//! [`Client::with_framework`]: ../../client/struct.Client.html#method.with_framework - -pub mod help_commands; - -mod command; -mod configuration; -mod create_command; -mod create_group; -mod buckets; - -pub use self::buckets::{Bucket, MemberRatelimit, Ratelimit}; -pub use self::command::{Command, CommandType, CommandGroup, 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 std::thread; -use ::client::Context; -use ::model::{Message, UserId}; -use ::model::permissions::Permissions; -use ::utils; - -#[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`]: ext/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>) -> ::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>) -> ::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>) -> ::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>) -> ::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, - /// 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. -/// -/// [module-level documentation]: index.html -#[allow(type_complexity)] -#[derive(Default)] -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". - /// - /// 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 [`Client::on_message`] handler - to have the - /// framework check if a [`Event::MessageCreate`] should be processed by - /// itself. - /// - /// [`Client::on_message`]: ../../client/struct.Client.html#method.on_message - /// [`Event::MessageCreate`]: ../../model/event/enum.Event.html#variant.MessageCreate - pub initialized: bool, - user_info: (u64, bool), -} - -impl Framework { - /// 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::Client; - /// use std::env; - /// - /// let mut client = Client::login(&env::var("DISCORD_TOKEN").unwrap()); - /// client.with_framework(|f| f - /// .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`. - 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(), - }); - - self - } - - /// 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 { - ratelimit: Ratelimit { - delay: delay, - limit: None, - }, - users: HashMap::new(), - }); - - 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 - } - - 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 - } - - #[allow(too_many_arguments)] - 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) { - None - } else { - if let Some(rate_limit) = command.bucket.clone().map(|x| self.ratelimit_time(x.as_str(), message.author.id.0)) { - if rate_limit > 0i64 { - return Some(DispatchError::RateLimited(rate_limit)); - } - } - - if let Some(x) = command.min_args { - if args < x as usize { - return Some(DispatchError::NotEnoughArguments { - min: x, - given: args - }); - } - } - - 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 !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.checks_passed(command, &mut context, message) { - Some(DispatchError::CheckFailed) - } 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 { - 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 { - 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 = { - let content = message.content[position..].trim(); - - if command.use_quotes { - utils::parse_quotes(&content[command_length..]) - } else { - content[command_length..] - .split_whitespace() - .map(|arg| arg.to_owned()) - .collect::<Vec<String>>() - } - }; - - 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) { - return; - } - } - - let result = match command.exec { - CommandType::StringResponse(ref x) => { - let _ = &mut context.channel_id.unwrap().say(x); - - Ok(()) - }, - CommandType::Basic(ref x) => { - (x)(&mut context, &message, args) - }, - CommandType::WithCommands(ref x) => { - (x)(&mut context, &message, groups, &args) - } - }; - - if let Some(after) = after { - (after)(&mut context, &message, &built, result); - } - }); - - return; - } - } - } - } - } - - /// 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. - /// - /// Note that once v0.2.0 lands, 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 - pub fn on<F, S>(mut self, command_name: S, f: F) -> Self - where F: Fn(&mut Context, &Message, Vec<String>) -> Result<(), String> + Send + Sync + '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.to_owned()), 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 - } - - 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. - 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 - where F: Fn(&mut Context, &Message, &String) -> bool + Send + Sync + '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. - pub fn after<F>(mut self, f: F) -> Self - where F: Fn(&mut Context, &Message, &String, Result<(), String>) + Send + Sync + 'static { - self.after = Some(Arc::new(f)); - - self - } - - #[doc(hidden)] - pub fn update_current_user(&mut self, user_id: UserId, is_bot: bool) { - self.user_info = (user_id.0, is_bot); - } - - fn ratelimit_time(&mut self, bucket_name: &str, user_id: u64) -> i64 { - self.buckets - .get_mut(bucket_name) - .map(|bucket| bucket.take(user_id)) - .unwrap_or(0) - } -} |