@@ -30,11 +30,11 @@ namespace Discord.Commands | |||||
public sealed class Command | public sealed class Command | ||||
{ | { | ||||
public string Text { get; } | public string Text { get; } | ||||
public int? MinArgs { get; private set; } | |||||
public int? MaxArgs { get; private set; } | |||||
public int MinPermissions { get; internal set; } | |||||
public string Category { get; internal set; } | |||||
public bool IsHidden { get; internal set; } | public bool IsHidden { get; internal set; } | ||||
public string Description { get; internal set; } | public string Description { get; internal set; } | ||||
public int? MinArgs { get; private set; } | |||||
public int? MaxArgs { get; private set; } | |||||
public IEnumerable<string> Aliases => _aliases; | public IEnumerable<string> Aliases => _aliases; | ||||
private string[] _aliases; | private string[] _aliases; | ||||
@@ -98,7 +98,12 @@ namespace Discord.Commands | |||||
{ | { | ||||
_handler = e => { func(e); return TaskHelper.CompletedTask; }; | _handler = e => { func(e); return TaskHelper.CompletedTask; }; | ||||
} | } | ||||
internal bool CanRun(User user, Channel channel) | |||||
{ | |||||
return true; | |||||
} | |||||
internal Task Run(CommandEventArgs args) | internal Task Run(CommandEventArgs args) | ||||
{ | { | ||||
var task = _handler(args); | var task = _handler(args); | ||||
@@ -13,10 +13,11 @@ namespace Discord.Commands | |||||
private bool _allowRequired, _isClosed; | private bool _allowRequired, _isClosed; | ||||
private string _prefix; | private string _prefix; | ||||
public CommandBuilder(CommandService service, Command command, string prefix) | |||||
internal CommandBuilder(CommandService service, Command command, string prefix, string category) | |||||
{ | { | ||||
_service = service; | _service = service; | ||||
_command = command; | _command = command; | ||||
_command.Category = category; | |||||
_params = new List<CommandParameter>(); | _params = new List<CommandParameter>(); | ||||
_prefix = prefix; | _prefix = prefix; | ||||
_allowRequired = true; | _allowRequired = true; | ||||
@@ -29,6 +30,11 @@ namespace Discord.Commands | |||||
_command.SetAliases(aliases); | _command.SetAliases(aliases); | ||||
return this; | return this; | ||||
} | } | ||||
/*public CommandBuilder Category(string category) | |||||
{ | |||||
_command.Category = category; | |||||
return this; | |||||
}*/ | |||||
public CommandBuilder Info(string description) | public CommandBuilder Info(string description) | ||||
{ | { | ||||
_command.Description = description; | _command.Description = description; | ||||
@@ -55,12 +61,6 @@ namespace Discord.Commands | |||||
return this; | return this; | ||||
} | } | ||||
public CommandBuilder MinPermissions(int level) | |||||
{ | |||||
_command.MinPermissions = level; | |||||
return this; | |||||
} | |||||
public void Do(Func<CommandEventArgs, Task> func) | public void Do(Func<CommandEventArgs, Task> func) | ||||
{ | { | ||||
_command.SetHandler(func); | _command.SetHandler(func); | ||||
@@ -101,23 +101,23 @@ namespace Discord.Commands | |||||
{ | { | ||||
internal readonly CommandService _service; | internal readonly CommandService _service; | ||||
private readonly string _prefix; | private readonly string _prefix; | ||||
private int _defaultMinPermissions; | |||||
private string _category; | |||||
internal CommandGroupBuilder(CommandService service, string prefix, int defaultMinPermissions) | |||||
internal CommandGroupBuilder(CommandService service, string prefix) | |||||
{ | { | ||||
_service = service; | _service = service; | ||||
_prefix = prefix; | _prefix = prefix; | ||||
_defaultMinPermissions = defaultMinPermissions; | |||||
} | } | ||||
public void DefaultMinPermissions(int level) | |||||
public CommandGroupBuilder Category(string category) | |||||
{ | { | ||||
_defaultMinPermissions = level; | |||||
_category = category; | |||||
return this; | |||||
} | } | ||||
public CommandGroupBuilder CreateCommandGroup(string cmd, Action<CommandGroupBuilder> config = null) | |||||
public CommandGroupBuilder CreateGroup(string cmd, Action<CommandGroupBuilder> config = null) | |||||
{ | { | ||||
config(new CommandGroupBuilder(_service, _prefix + ' ' + cmd, _defaultMinPermissions)); | |||||
config(new CommandGroupBuilder(_service, _prefix + ' ' + cmd)); | |||||
return this; | return this; | ||||
} | } | ||||
public CommandBuilder CreateCommand() | public CommandBuilder CreateCommand() | ||||
@@ -125,8 +125,7 @@ namespace Discord.Commands | |||||
public CommandBuilder CreateCommand(string cmd) | public CommandBuilder CreateCommand(string cmd) | ||||
{ | { | ||||
var command = new Command(CommandBuilder.AppendPrefix(_prefix, cmd)); | var command = new Command(CommandBuilder.AppendPrefix(_prefix, cmd)); | ||||
command.MinPermissions = _defaultMinPermissions; | |||||
return new CommandBuilder(_service, command, _prefix); | |||||
return new CommandBuilder(_service, command, _prefix, _category); | |||||
} | } | ||||
} | } | ||||
} | } |
@@ -12,12 +12,11 @@ namespace Discord.Commands | |||||
private Command _command; | private Command _command; | ||||
private readonly Dictionary<string, CommandMap> _items; | private readonly Dictionary<string, CommandMap> _items; | ||||
private int _minPermission; | |||||
private bool _isHidden; | private bool _isHidden; | ||||
public string Text => _text; | public string Text => _text; | ||||
public int MinPermissions => _minPermission; | |||||
public bool IsHidden => _isHidden; | public bool IsHidden => _isHidden; | ||||
public IEnumerable<KeyValuePair<string, CommandMap>> Items => _items; | |||||
public IEnumerable<Command> SubCommands => _items.Select(x => x.Value._command).Where(x => x != null); | public IEnumerable<Command> SubCommands => _items.Select(x => x.Value._command).Where(x => x != null); | ||||
public IEnumerable<CommandMap> SubGroups => _items.Select(x => x.Value).Where(x => x._items.Count > 0); | public IEnumerable<CommandMap> SubGroups => _items.Select(x => x.Value).Where(x => x._items.Count > 0); | ||||
@@ -26,7 +25,6 @@ namespace Discord.Commands | |||||
_parent = parent; | _parent = parent; | ||||
_text = text; | _text = text; | ||||
_items = new Dictionary<string, CommandMap>(); | _items = new Dictionary<string, CommandMap>(); | ||||
_minPermission = int.MaxValue; | |||||
_isHidden = true; | _isHidden = true; | ||||
} | } | ||||
@@ -88,8 +86,6 @@ namespace Discord.Commands | |||||
{ | { | ||||
if (index != parts.Length) | if (index != parts.Length) | ||||
{ | { | ||||
if (command.MinPermissions < _minPermission) | |||||
_minPermission = command.MinPermissions; | |||||
if (!command.IsHidden && _isHidden) | if (!command.IsHidden && _isHidden) | ||||
_isHidden = false; | _isHidden = false; | ||||
@@ -109,5 +105,17 @@ namespace Discord.Commands | |||||
_command = command; | _command = command; | ||||
} | } | ||||
} | } | ||||
public bool CanRun(User user, Channel channel) | |||||
{ | |||||
if (_command != null && _command.CanRun(user, channel)) | |||||
return true; | |||||
foreach (var item in _items) | |||||
{ | |||||
if (item.Value.CanRun(user, channel)) | |||||
return true; | |||||
} | |||||
return false; | |||||
} | |||||
} | } | ||||
} | } |
@@ -6,18 +6,16 @@ namespace Discord.Commands | |||||
{ | { | ||||
public Message Message { get; } | public Message Message { get; } | ||||
public Command Command { get; } | public Command Command { get; } | ||||
public int? UserPermissions { get; } | |||||
public string[] Args { get; } | public string[] Args { get; } | ||||
public User User => Message.User; | public User User => Message.User; | ||||
public Channel Channel => Message.Channel; | public Channel Channel => Message.Channel; | ||||
public Server Server => Message.Channel.Server; | public Server Server => Message.Channel.Server; | ||||
public CommandEventArgs(Message message, Command command, int? userPermissions, string[] args) | |||||
public CommandEventArgs(Message message, Command command, string[] args) | |||||
{ | { | ||||
Message = message; | Message = message; | ||||
Command = command; | Command = command; | ||||
UserPermissions = userPermissions; | |||||
Args = args; | Args = args; | ||||
} | } | ||||
} | } | ||||
@@ -29,7 +27,7 @@ namespace Discord.Commands | |||||
public Exception Exception { get; } | public Exception Exception { get; } | ||||
public CommandErrorEventArgs(CommandErrorType errorType, CommandEventArgs baseArgs, Exception ex) | public CommandErrorEventArgs(CommandErrorType errorType, CommandEventArgs baseArgs, Exception ex) | ||||
: base(baseArgs.Message, baseArgs.Command, baseArgs.UserPermissions, baseArgs.Args) | |||||
: base(baseArgs.Message, baseArgs.Command, baseArgs.Args) | |||||
{ | { | ||||
Exception = ex; | Exception = ex; | ||||
ErrorType = errorType; | ErrorType = errorType; | ||||
@@ -10,27 +10,35 @@ namespace Discord.Commands | |||||
public partial class CommandService : IService | public partial class CommandService : IService | ||||
{ | { | ||||
private DiscordClient _client; | private DiscordClient _client; | ||||
public CommandServiceConfig Config { get; } | |||||
CommandServiceConfig Config { get; } | |||||
public IEnumerable<Command> Commands => _commands; | |||||
private readonly List<Command> _commands; | |||||
//AllCommands store a flattened collection of all commands | |||||
public IEnumerable<Command> AllCommands => _allCommands; | |||||
private readonly List<Command> _allCommands; | |||||
//Command map stores all commands by their input text, used for fast resolving and parsing | |||||
internal CommandMap Map => _map; | internal CommandMap Map => _map; | ||||
private readonly CommandMap _map; | private readonly CommandMap _map; | ||||
//Groups store all commands by their module, used for more informative help | |||||
internal IEnumerable<CommandMap> Categories => _categories.Values; | |||||
private readonly Dictionary<string, CommandMap> _categories; | |||||
public CommandService(CommandServiceConfig config) | public CommandService(CommandServiceConfig config) | ||||
{ | { | ||||
Config = config; | Config = config; | ||||
_commands = new List<Command>(); | |||||
_allCommands = new List<Command>(); | |||||
_map = new CommandMap(null, null); | _map = new CommandMap(null, null); | ||||
} | |||||
_categories = new Dictionary<string, CommandMap>(); | |||||
} | |||||
void IService.Install(DiscordClient client) | void IService.Install(DiscordClient client) | ||||
{ | { | ||||
_client = client; | _client = client; | ||||
Config.Lock(); | Config.Lock(); | ||||
if (Config.HelpMode != HelpMode.Disable) | |||||
if (Config.HelpMode != HelpMode.Disable) | |||||
{ | { | ||||
CreateCommand("help") | CreateCommand("help") | ||||
.Parameter("command", ParameterType.Multiple) | .Parameter("command", ParameterType.Multiple) | ||||
@@ -54,7 +62,7 @@ namespace Discord.Commands | |||||
client.MessageReceived += async (s, e) => | client.MessageReceived += async (s, e) => | ||||
{ | { | ||||
if (_commands.Count == 0) return; | |||||
if (_allCommands.Count == 0) return; | |||||
if (e.Message.IsAuthor) return; | if (e.Message.IsAuthor) return; | ||||
string msg = e.Message.Text; | string msg = e.Message.Text; | ||||
@@ -75,28 +83,26 @@ namespace Discord.Commands | |||||
CommandParser.ParseCommand(msg, _map, out command, out argPos); | CommandParser.ParseCommand(msg, _map, out command, out argPos); | ||||
if (command == null) | if (command == null) | ||||
{ | { | ||||
CommandEventArgs errorArgs = new CommandEventArgs(e.Message, null, null, null); | |||||
CommandEventArgs errorArgs = new CommandEventArgs(e.Message, null, null); | |||||
RaiseCommandError(CommandErrorType.UnknownCommand, errorArgs); | RaiseCommandError(CommandErrorType.UnknownCommand, errorArgs); | ||||
return; | return; | ||||
} | } | ||||
else | else | ||||
{ | { | ||||
int userPermissions = Config.PermissionResolver?.Invoke(e.Message.User) ?? 0; | |||||
//Parse arguments | //Parse arguments | ||||
string[] args; | string[] args; | ||||
var error = CommandParser.ParseArgs(msg, argPos, command, out args); | var error = CommandParser.ParseArgs(msg, argPos, command, out args); | ||||
if (error != null) | if (error != null) | ||||
{ | { | ||||
var errorArgs = new CommandEventArgs(e.Message, command, userPermissions, null); | |||||
var errorArgs = new CommandEventArgs(e.Message, command, null); | |||||
RaiseCommandError(error.Value, errorArgs); | RaiseCommandError(error.Value, errorArgs); | ||||
return; | return; | ||||
} | } | ||||
var eventArgs = new CommandEventArgs(e.Message, command, userPermissions, args); | |||||
var eventArgs = new CommandEventArgs(e.Message, command, args); | |||||
// Check permissions | // Check permissions | ||||
if (userPermissions < command.MinPermissions) | |||||
if (!command.CanRun(eventArgs.User, eventArgs.Channel)) | |||||
{ | { | ||||
RaiseCommandError(CommandErrorType.BadPermissions, eventArgs); | RaiseCommandError(CommandErrorType.BadPermissions, eventArgs); | ||||
return; | return; | ||||
@@ -118,29 +124,72 @@ namespace Discord.Commands | |||||
public Task ShowHelp(User user, Channel channel) | public Task ShowHelp(User user, Channel channel) | ||||
{ | { | ||||
int permissions = Config.PermissionResolver(user); | |||||
StringBuilder output = new StringBuilder(); | StringBuilder output = new StringBuilder(); | ||||
output.AppendLine("These are the commands you can use:"); | |||||
output.Append(string.Join(", ", _map.SubCommands.Distinct() | |||||
.Where(x => permissions >= x.MinPermissions && !x.IsHidden) | |||||
/*output.AppendLine("These are the commands you can use:"); | |||||
output.Append(string.Join(", ", _map.SubCommands | |||||
.Where(x => x.CanRun(user, channel) && !x.IsHidden) | |||||
.Select(x => '`' + x.Text + '`' + | .Select(x => '`' + x.Text + '`' + | ||||
(x.Aliases.Count() > 0 ? ", `" + string.Join("`, `", x.Aliases) + '`' : "")))); | (x.Aliases.Count() > 0 ? ", `" + string.Join("`, `", x.Aliases) + '`' : "")))); | ||||
output.AppendLine("\nThese are the groups you can access:"); | output.AppendLine("\nThese are the groups you can access:"); | ||||
output.Append(string.Join(", ", _map.SubGroups.Distinct() | |||||
.Where(x => permissions >= x.MinPermissions && !x.IsHidden) | |||||
.Select(x => '`' + x.Text + '`'))); | |||||
output.Append(string.Join(", ", _map.SubGroups | |||||
.Where(x => /*x.CanRun(user, channel)*//* && !x.IsHidden) | |||||
.Select(x => '`' + x.Text + '`')));*/ | |||||
bool isFirstCategory = true; | |||||
foreach (var category in _categories) | |||||
{ | |||||
bool isFirstItem = true; | |||||
foreach (var item in category.Value.Items) | |||||
{ | |||||
var map = item.Value; | |||||
if (!map.IsHidden && map.CanRun(user, channel)) | |||||
{ | |||||
if (isFirstItem) | |||||
{ | |||||
isFirstItem = false; | |||||
//This is called for the first item in each category. If we never get here, we dont bother writing the header for a category type (since it's empty) | |||||
if (isFirstCategory) | |||||
{ | |||||
isFirstCategory = false; | |||||
//Called for the first non-empty category | |||||
output.AppendLine("These are the commands you can use:"); | |||||
} | |||||
else | |||||
output.AppendLine(); | |||||
if (category.Key != "") | |||||
{ | |||||
output.Append(Format.Bold(category.Key)); | |||||
output.Append(": "); | |||||
} | |||||
} | |||||
else | |||||
output.Append(", "); | |||||
output.Append('`'); | |||||
output.Append(map.Text); | |||||
if (map.Items.Any()) | |||||
output.Append(@"\*"); | |||||
output.Append('`'); | |||||
} | |||||
} | |||||
} | |||||
var chars = Config.CommandChars; | |||||
if (chars.Length > 0) | |||||
{ | |||||
if (chars.Length == 1) | |||||
output.AppendLine($"\nYou can use `{chars[0]}` to call a command."); | |||||
else | |||||
output.AppendLine($"\nYou can use `{string.Join(" ", chars.Take(chars.Length - 1))}` or `{chars.Last()}` to call a command."); | |||||
} | |||||
if (output.Length == 0) | |||||
output.Append("There are no commands you have permission to run."); | |||||
else | |||||
{ | |||||
output.AppendLine(); | |||||
var chars = Config.CommandChars; | |||||
if (chars.Length > 0) | |||||
{ | |||||
if (chars.Length == 1) | |||||
output.AppendLine($"You can use `{chars[0]}` to call a command."); | |||||
else | |||||
output.AppendLine($"You can use `{string.Join(" ", chars.Take(chars.Length - 1))}` or `{chars.Last()}` to call a command."); | |||||
} | |||||
output.AppendLine("`help` `<command>` can tell you more about how to use a command."); | |||||
output.AppendLine("`help <command>` can tell you more about how to use a command."); | |||||
} | |||||
return _client.SendMessage(channel, output.ToString()); | return _client.SendMessage(channel, output.ToString()); | ||||
} | } | ||||
@@ -168,8 +217,7 @@ namespace Discord.Commands | |||||
var sub = _map.GetItem(command.Text).SubCommands; | var sub = _map.GetItem(command.Text).SubCommands; | ||||
if (sub.Count() > 0) | if (sub.Count() > 0) | ||||
{ | { | ||||
int permissions = Config.PermissionResolver(user); | |||||
output.AppendLine("Sub Commands: `" + string.Join("`, `", sub.Where(x => permissions >= x.MinPermissions && !x.IsHidden) | |||||
output.AppendLine("Sub Commands: `" + string.Join("`, `", sub.Where(x => x.CanRun(user, channel) && !x.IsHidden) | |||||
.Select(x => x.Text.Substring(command.Text.Length + 1))) + '`'); | .Select(x => x.Text.Substring(command.Text.Length + 1))) + '`'); | ||||
} | } | ||||
@@ -179,17 +227,28 @@ namespace Discord.Commands | |||||
return _client.SendMessage(channel, output.ToString()); | return _client.SendMessage(channel, output.ToString()); | ||||
} | } | ||||
public void CreateCommandGroup(string cmd, Action<CommandGroupBuilder> config = null) | |||||
=> config(new CommandGroupBuilder(this, cmd, 0)); | |||||
public void CreateGroup(string cmd, Action<CommandGroupBuilder> config = null) | |||||
{ | |||||
var builder = new CommandGroupBuilder(this, cmd); | |||||
if (config != null) | |||||
config(builder); | |||||
} | |||||
public CommandBuilder CreateCommand(string cmd) | public CommandBuilder CreateCommand(string cmd) | ||||
{ | { | ||||
var command = new Command(cmd); | var command = new Command(cmd); | ||||
return new CommandBuilder(this, command, ""); | |||||
return new CommandBuilder(this, command, "", ""); | |||||
} | } | ||||
internal void AddCommand(Command command) | internal void AddCommand(Command command) | ||||
{ | { | ||||
_commands.Add(command); | |||||
_allCommands.Add(command); | |||||
CommandMap category; | |||||
string categoryName = command.Category ?? ""; | |||||
if (!_categories.TryGetValue(categoryName, out category)) | |||||
{ | |||||
category = new CommandMap(null, ""); | |||||
_categories.Add(categoryName, category); | |||||
} | |||||
_map.AddCommand(command.Text, command); | _map.AddCommand(command.Text, command); | ||||
} | } | ||||
} | } | ||||
@@ -13,8 +13,8 @@ namespace Discord.Commands | |||||
} | } | ||||
public class CommandServiceConfig | public class CommandServiceConfig | ||||
{ | { | ||||
public Func<User, int> PermissionResolver { get { return _permissionsResolver; } set { SetValue(ref _permissionsResolver, value); } } | |||||
private Func<User, int> _permissionsResolver; | |||||
/*public Func<User, int> PermissionResolver { get { return _permissionsResolver; } set { SetValue(ref _permissionsResolver, value); } } | |||||
private Func<User, int> _permissionsResolver;*/ | |||||
public char? CommandChar | public char? CommandChar | ||||
{ | { | ||||