use client::Context;
use model::{
channel::{
Message,
Channel,
},
Permissions
};
use std::{
collections::HashMap,
fmt,
fmt::{Debug, Formatter},
sync::Arc
};
use utils::Colour;
use super::{Args, Configuration, HelpBehaviour};
type CheckFunction = Fn(&mut Context, &Message, &mut Args, &CommandOptions) -> bool
+ Send
+ Sync
+ 'static;
pub struct Check(pub(crate) Box<CheckFunction>);
impl Check {
pub(crate) fn new<F: Send + Sync + 'static>(f: F) -> Self
where F: Fn(&mut Context, &Message, &mut Args, &CommandOptions) -> bool
{
Check(Box::new(f))
}
}
impl Debug for Check {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
f.debug_tuple("Check")
.field(&"<fn>")
.finish()
}
}
pub type HelpFunction = fn(&mut Context, &Message, &HelpOptions, HashMap<String, Arc<CommandGroup>>, &Args)
-> Result<(), Error>;
pub struct Help(pub HelpFunction, pub Arc<HelpOptions>);
impl Debug for Help {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
f.debug_struct("Help")
.field("options", &self.1)
.finish()
}
}
impl HelpCommand for Help {
fn execute(&self, c: &mut Context, m: &Message, ho: &HelpOptions,hm: HashMap<String, Arc<CommandGroup>>, a: &Args) -> Result<(), Error> {
(self.0)(c, m, ho, hm, a)
}
}
pub type BeforeHook = Fn(&mut Context, &Message, &str) -> bool + Send + Sync + 'static;
pub type AfterHook = Fn(&mut Context, &Message, &str, Result<(), Error>) + Send + Sync + 'static;
pub type UnrecognisedCommandHook = Fn(&mut Context, &Message, &str) + Send + Sync + 'static;
pub type MessageWithoutCommandHook = Fn(&mut Context, &Message) + Send + Sync + 'static;
pub(crate) type InternalCommand = Arc<Command>;
pub type PrefixCheck = Fn(&mut Context, &Message) -> Option<String> + Send + Sync + 'static;
pub enum CommandOrAlias {
Alias(String),
Command(InternalCommand),
}
impl fmt::Debug for CommandOrAlias {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match *self {
CommandOrAlias::Alias(ref s) => f.debug_tuple("CommandOrAlias::Alias").field(&s).finish(),
CommandOrAlias::Command(ref arc) => f.debug_tuple("CommandOrAlias::Command").field(&arc.options()).finish(),
}
}
}
#[derive(Clone, Debug)]
pub struct Error(pub String);
impl<D: fmt::Display> From<D> for Error {
fn from(d: D) -> Self {
Error(d.to_string())
}
}
#[derive(Debug)]
pub struct CommandGroup {
pub prefixes: Option<Vec<String>>,
pub commands: HashMap<String, CommandOrAlias>,
pub bucket: Option<String>,
pub required_permissions: Permissions,
pub allowed_roles: Vec<String>,
pub help_available: bool,
pub dm_only: bool,
pub guild_only: bool,
pub owner_privileges: bool,
pub owners_only: bool,
pub help: Option<Arc<Help>>,
pub checks: Vec<Check>,
pub default_command: Option<CommandOrAlias>,
pub description: Option<String>,
}
impl Default for CommandGroup {
fn default() -> CommandGroup {
CommandGroup {
prefixes: None,
commands: HashMap::new(),
bucket: None,
required_permissions: Permissions::empty(),
dm_only: false,
guild_only: false,
owner_privileges: true,
help_available: true,
owners_only: false,
allowed_roles: Vec::new(),
help: None,
checks: Vec::new(),
default_command: None,
description: None,
}
}
}
#[derive(Debug)]
pub struct CommandOptions {
pub checks: Vec<Check>,
pub bucket: Option<String>,
pub desc: Option<String>,
pub example: Option<String>,
pub usage: Option<String>,
pub min_args: Option<i32>,
pub max_args: Option<i32>,
pub required_permissions: Permissions,
pub allowed_roles: Vec<String>,
pub help_available: bool,
pub dm_only: bool,
pub guild_only: bool,
pub owner_privileges: bool,
pub owners_only: bool,
pub aliases: Vec<String>,
}
#[derive(Debug)]
pub struct HelpOptions {
pub suggestion_text: String,
pub no_help_available_text: String,
pub usage_label: String,
pub usage_sample_label: String,
pub ungrouped_label: String,
pub description_label: String,
pub grouped_label: String,
pub aliases_label: String,
pub guild_only_text: String,
pub dm_only_text: String,
pub dm_and_guild_text: String,
pub available_text: String,
pub command_not_found_text: String,
pub individual_command_tip: String,
pub striked_commands_tip_in_dm: Option<String>,
pub striked_commands_tip_in_guild: Option<String>,
pub group_prefix: String,
pub lacking_role: HelpBehaviour,
pub lacking_permissions: HelpBehaviour,
pub wrong_channel: HelpBehaviour,
pub embed_error_colour: Colour,
pub embed_success_colour: Colour,
pub max_levenshtein_distance: usize,
}
pub trait HelpCommand: Send + Sync + 'static {
fn execute(&self, &mut Context, &Message, &HelpOptions, HashMap<String, Arc<CommandGroup>>, &Args) -> Result<(), Error>;
fn options(&self) -> Arc<CommandOptions> {
Arc::clone(&DEFAULT_OPTIONS)
}
}
impl HelpCommand for Arc<HelpCommand> {
fn execute(&self, c: &mut Context, m: &Message, ho: &HelpOptions, hm: HashMap<String, Arc<CommandGroup>>, a: &Args) -> Result<(), Error> {
(**self).execute(c, m, ho, hm, a)
}
}
impl Default for HelpOptions {
fn default() -> HelpOptions {
HelpOptions {
suggestion_text: "Did you mean `{}`?".to_string(),
no_help_available_text: "**Error**: No help available.".to_string(),
usage_label: "Usage".to_string(),
usage_sample_label: "Sample usage".to_string(),
ungrouped_label: "Ungrouped".to_string(),
grouped_label: "Group".to_string(),
aliases_label: "Aliases".to_string(),
description_label: "Description".to_string(),
guild_only_text: "Only in guilds".to_string(),
dm_only_text: "Only in DM".to_string(),
dm_and_guild_text: "In DM and guilds".to_string(),
available_text: "Available".to_string(),
command_not_found_text: "**Error**: Command `{}` not found.".to_string(),
individual_command_tip: "To get help with an individual command, pass its \
name as an argument to this command.".to_string(),
group_prefix: "Prefix".to_string(),
striked_commands_tip_in_dm: Some(String::new()),
striked_commands_tip_in_guild: Some(String::new()),
lacking_role: HelpBehaviour::Strike,
lacking_permissions: HelpBehaviour::Strike,
wrong_channel: HelpBehaviour::Strike,
embed_error_colour: Colour::DARK_RED,
embed_success_colour: Colour::ROSEWATER,
max_levenshtein_distance: 0,
}
}
}
lazy_static! {
static ref DEFAULT_OPTIONS: Arc<CommandOptions> = Arc::new(CommandOptions::default());
}
pub trait Command: Send + Sync + 'static {
fn execute(&self, &mut Context, &Message, Args) -> Result<(), Error>;
fn options(&self) -> Arc<CommandOptions> {
Arc::clone(&DEFAULT_OPTIONS)
}
fn init(&self) {}
fn before(&self, &mut Context, &Message) -> bool { true }
fn after(&self, &mut Context, &Message, &Result<(), Error>) { }
}
impl Command for Arc<Command> {
fn execute(&self, c: &mut Context, m: &Message, a: Args) -> Result<(), Error> {
(**self).execute(c, m, a)
}
fn options(&self) -> Arc<CommandOptions> {
(**self).options()
}
fn init(&self) {
(**self).init()
}
fn before(&self, c: &mut Context, m: &Message) -> bool {
(**self).before(c, m)
}
fn after(&self, c: &mut Context, m: &Message, res: &Result<(), Error>) {
(**self).after(c, m, res)
}
}
impl<F> Command for F where F: Fn(&mut Context, &Message, Args) -> Result<(), Error>
+ Send
+ Sync
+ ?Sized
+ 'static {
fn execute(&self, c: &mut Context, m: &Message, a: Args) -> Result<(), Error> {
(*self)(c, m, a)
}
}
impl Default for CommandOptions {
fn default() -> CommandOptions {
CommandOptions {
aliases: Vec::new(),
checks: Vec::default(),
desc: None,
usage: None,
example: None,
min_args: None,
bucket: None,
max_args: None,
required_permissions: Permissions::empty(),
dm_only: false,
guild_only: false,
owner_privileges: true,
help_available: true,
owners_only: false,
allowed_roles: Vec::new(),
}
}
}
pub fn positions(ctx: &mut Context, msg: &Message, conf: &Configuration) -> Option<Vec<usize>> {
if let Some(mention_end) = find_mention_end(&msg.content, conf) {
return Some(vec![mention_end]);
}
if !conf.prefixes.is_empty() || conf.dynamic_prefix.is_some() {
let mut positions = Vec::new();
if let Some(x) = conf.dynamic_prefix.as_ref().and_then(|f| f(ctx, msg)) {
if msg.content.starts_with(&x) {
positions.push(x.chars().count());
}
} else {
for n in &conf.prefixes {
if msg.content.starts_with(n) {
positions.push(n.chars().count());
}
}
}
#[cfg(feature = "cache")]
{
let private = match msg.channel() {
Some(Channel::Private(_)) => true,
_ => false,
};
if conf.no_dm_prefix && private && positions.is_empty() &&
!(conf.ignore_bots && msg.author.bot) {
positions.push(0);
}
}
if positions.is_empty() {
return None;
}
let pos = *unsafe { positions.get_unchecked(0) };
let with_whitespace = find_end_of_prefix_with_whitespace(&msg.content, pos);
if conf.allow_whitespace {
positions.insert(0, with_whitespace.unwrap_or(pos));
} else if with_whitespace.is_some() {
return None;
}
Some(positions)
} else {
None
}
}
fn find_mention_end(content: &str, conf: &Configuration) -> Option<usize> {
conf.on_mention.as_ref().and_then(|mentions| {
mentions
.iter()
.find(|mention| content.starts_with(&mention[..]))
.map(|m| m.len())
})
}
fn find_end_of_prefix_with_whitespace(content: &str, position: usize) -> Option<usize> {
let content_len = content.len();
if position >= content_len { return None; }
let mut i = 0;
let chars = content.chars().skip(position);
for char in chars {
match char {
'\t' | '\n' | '\r' | ' ' => i += 1,
_ => return if i == 0 { None } else { Some(position + i) }
}
}
Some(content.len())
}