From 7f8780d749bf5812279a724caffa7a56b2602a83 Mon Sep 17 00:00:00 2001 From: RogueException Date: Sun, 8 Nov 2015 17:03:08 -0400 Subject: [PATCH] Added command categories, removed old permission resolver. --- src/Discord.Net.Commands/Command.cs | 13 +- src/Discord.Net.Commands/CommandBuilder.cs | 31 ++--- src/Discord.Net.Commands/CommandMap.cs | 18 ++- .../CommandService.Events.cs | 6 +- src/Discord.Net.Commands/CommandService.cs | 131 +++++++++++++----- .../CommandServiceConfig.cs | 4 +- 6 files changed, 136 insertions(+), 67 deletions(-) diff --git a/src/Discord.Net.Commands/Command.cs b/src/Discord.Net.Commands/Command.cs index 15ad98b95..548ef2bc9 100644 --- a/src/Discord.Net.Commands/Command.cs +++ b/src/Discord.Net.Commands/Command.cs @@ -30,11 +30,11 @@ namespace Discord.Commands public sealed class Command { 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 string Description { get; internal set; } + public int? MinArgs { get; private set; } + public int? MaxArgs { get; private set; } public IEnumerable Aliases => _aliases; private string[] _aliases; @@ -98,7 +98,12 @@ namespace Discord.Commands { _handler = e => { func(e); return TaskHelper.CompletedTask; }; } - + + internal bool CanRun(User user, Channel channel) + { + return true; + } + internal Task Run(CommandEventArgs args) { var task = _handler(args); diff --git a/src/Discord.Net.Commands/CommandBuilder.cs b/src/Discord.Net.Commands/CommandBuilder.cs index 4523dbbc6..30a071776 100644 --- a/src/Discord.Net.Commands/CommandBuilder.cs +++ b/src/Discord.Net.Commands/CommandBuilder.cs @@ -13,10 +13,11 @@ namespace Discord.Commands private bool _allowRequired, _isClosed; private string _prefix; - public CommandBuilder(CommandService service, Command command, string prefix) + internal CommandBuilder(CommandService service, Command command, string prefix, string category) { _service = service; _command = command; + _command.Category = category; _params = new List(); _prefix = prefix; _allowRequired = true; @@ -29,6 +30,11 @@ namespace Discord.Commands _command.SetAliases(aliases); return this; } + /*public CommandBuilder Category(string category) + { + _command.Category = category; + return this; + }*/ public CommandBuilder Info(string description) { _command.Description = description; @@ -55,12 +61,6 @@ namespace Discord.Commands return this; } - public CommandBuilder MinPermissions(int level) - { - _command.MinPermissions = level; - return this; - } - public void Do(Func func) { _command.SetHandler(func); @@ -101,23 +101,23 @@ namespace Discord.Commands { internal readonly CommandService _service; 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; _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 config = null) + public CommandGroupBuilder CreateGroup(string cmd, Action config = null) { - config(new CommandGroupBuilder(_service, _prefix + ' ' + cmd, _defaultMinPermissions)); + config(new CommandGroupBuilder(_service, _prefix + ' ' + cmd)); return this; } public CommandBuilder CreateCommand() @@ -125,8 +125,7 @@ namespace Discord.Commands public CommandBuilder CreateCommand(string 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); } } } diff --git a/src/Discord.Net.Commands/CommandMap.cs b/src/Discord.Net.Commands/CommandMap.cs index 906e2b860..bc006dee0 100644 --- a/src/Discord.Net.Commands/CommandMap.cs +++ b/src/Discord.Net.Commands/CommandMap.cs @@ -12,12 +12,11 @@ namespace Discord.Commands private Command _command; private readonly Dictionary _items; - private int _minPermission; private bool _isHidden; public string Text => _text; - public int MinPermissions => _minPermission; public bool IsHidden => _isHidden; + public IEnumerable> Items => _items; public IEnumerable SubCommands => _items.Select(x => x.Value._command).Where(x => x != null); public IEnumerable SubGroups => _items.Select(x => x.Value).Where(x => x._items.Count > 0); @@ -26,7 +25,6 @@ namespace Discord.Commands _parent = parent; _text = text; _items = new Dictionary(); - _minPermission = int.MaxValue; _isHidden = true; } @@ -88,8 +86,6 @@ namespace Discord.Commands { if (index != parts.Length) { - if (command.MinPermissions < _minPermission) - _minPermission = command.MinPermissions; if (!command.IsHidden && _isHidden) _isHidden = false; @@ -109,5 +105,17 @@ namespace Discord.Commands _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; + } } } diff --git a/src/Discord.Net.Commands/CommandService.Events.cs b/src/Discord.Net.Commands/CommandService.Events.cs index 92a3d429c..5acb3e92b 100644 --- a/src/Discord.Net.Commands/CommandService.Events.cs +++ b/src/Discord.Net.Commands/CommandService.Events.cs @@ -6,18 +6,16 @@ namespace Discord.Commands { public Message Message { get; } public Command Command { get; } - public int? UserPermissions { get; } public string[] Args { get; } public User User => Message.User; public Channel Channel => Message.Channel; 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; Command = command; - UserPermissions = userPermissions; Args = args; } } @@ -29,7 +27,7 @@ namespace Discord.Commands public Exception Exception { get; } 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; ErrorType = errorType; diff --git a/src/Discord.Net.Commands/CommandService.cs b/src/Discord.Net.Commands/CommandService.cs index 2b1794b4d..56a674490 100644 --- a/src/Discord.Net.Commands/CommandService.cs +++ b/src/Discord.Net.Commands/CommandService.cs @@ -10,27 +10,35 @@ namespace Discord.Commands public partial class CommandService : IService { private DiscordClient _client; + public CommandServiceConfig Config { get; } - CommandServiceConfig Config { get; } - public IEnumerable Commands => _commands; - private readonly List _commands; + //AllCommands store a flattened collection of all commands + public IEnumerable AllCommands => _allCommands; + private readonly List _allCommands; + //Command map stores all commands by their input text, used for fast resolving and parsing internal CommandMap Map => _map; private readonly CommandMap _map; + //Groups store all commands by their module, used for more informative help + internal IEnumerable Categories => _categories.Values; + private readonly Dictionary _categories; + + public CommandService(CommandServiceConfig config) { Config = config; - _commands = new List(); + _allCommands = new List(); _map = new CommandMap(null, null); - } + _categories = new Dictionary(); + } void IService.Install(DiscordClient client) { _client = client; Config.Lock(); - if (Config.HelpMode != HelpMode.Disable) + if (Config.HelpMode != HelpMode.Disable) { CreateCommand("help") .Parameter("command", ParameterType.Multiple) @@ -54,7 +62,7 @@ namespace Discord.Commands client.MessageReceived += async (s, e) => { - if (_commands.Count == 0) return; + if (_allCommands.Count == 0) return; if (e.Message.IsAuthor) return; string msg = e.Message.Text; @@ -75,28 +83,26 @@ namespace Discord.Commands CommandParser.ParseCommand(msg, _map, out command, out argPos); if (command == null) { - CommandEventArgs errorArgs = new CommandEventArgs(e.Message, null, null, null); + CommandEventArgs errorArgs = new CommandEventArgs(e.Message, null, null); RaiseCommandError(CommandErrorType.UnknownCommand, errorArgs); return; } else { - int userPermissions = Config.PermissionResolver?.Invoke(e.Message.User) ?? 0; - //Parse arguments string[] args; var error = CommandParser.ParseArgs(msg, argPos, command, out args); if (error != null) { - var errorArgs = new CommandEventArgs(e.Message, command, userPermissions, null); + var errorArgs = new CommandEventArgs(e.Message, command, null); RaiseCommandError(error.Value, errorArgs); return; } - var eventArgs = new CommandEventArgs(e.Message, command, userPermissions, args); + var eventArgs = new CommandEventArgs(e.Message, command, args); // Check permissions - if (userPermissions < command.MinPermissions) + if (!command.CanRun(eventArgs.User, eventArgs.Channel)) { RaiseCommandError(CommandErrorType.BadPermissions, eventArgs); return; @@ -118,29 +124,72 @@ namespace Discord.Commands public Task ShowHelp(User user, Channel channel) { - int permissions = Config.PermissionResolver(user); - 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 + '`' + (x.Aliases.Count() > 0 ? ", `" + string.Join("`, `", x.Aliases) + '`' : "")))); 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` `` can tell you more about how to use a command."); + output.AppendLine("`help ` can tell you more about how to use a command."); + } return _client.SendMessage(channel, output.ToString()); } @@ -168,8 +217,7 @@ namespace Discord.Commands var sub = _map.GetItem(command.Text).SubCommands; 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))) + '`'); } @@ -179,17 +227,28 @@ namespace Discord.Commands return _client.SendMessage(channel, output.ToString()); } - public void CreateCommandGroup(string cmd, Action config = null) - => config(new CommandGroupBuilder(this, cmd, 0)); + public void CreateGroup(string cmd, Action config = null) + { + var builder = new CommandGroupBuilder(this, cmd); + if (config != null) + config(builder); + } public CommandBuilder CreateCommand(string cmd) { var command = new Command(cmd); - return new CommandBuilder(this, command, ""); + return new CommandBuilder(this, 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); } } diff --git a/src/Discord.Net.Commands/CommandServiceConfig.cs b/src/Discord.Net.Commands/CommandServiceConfig.cs index f2903d861..70752f7ca 100644 --- a/src/Discord.Net.Commands/CommandServiceConfig.cs +++ b/src/Discord.Net.Commands/CommandServiceConfig.cs @@ -13,8 +13,8 @@ namespace Discord.Commands } public class CommandServiceConfig { - public Func PermissionResolver { get { return _permissionsResolver; } set { SetValue(ref _permissionsResolver, value); } } - private Func _permissionsResolver; + /*public Func PermissionResolver { get { return _permissionsResolver; } set { SetValue(ref _permissionsResolver, value); } } + private Func _permissionsResolver;*/ public char? CommandChar {