From 772fd97080bcfc5909d9a958158ec538cb1e39ea Mon Sep 17 00:00:00 2001 From: Finite Reality Date: Tue, 2 Aug 2016 17:29:39 +0100 Subject: [PATCH 01/12] Implement initial command permissions system After our initial discussion on the matter (see #172) this is the system that we all seem to have agreed on. As a result, I have implemented a simple system which effectively implements permissions, while being extensible and tweakable so bot devs can decide what they want to do for permissions. As for default 'permissions', I'm not sure what the best approach would be here; bot devs are likely to implement their own permissions 'levels' and use those. I think the most we could do for now is add attributes to require certain users (by id) and certain roles (by id and possibly by name?) This would probably be the best option for now as it requires less work from us, nor do we know the *exact* approach bot devs want to take with permissions. --- .../Attributes/FilterAttribute.cs | 12 +++++++++++ src/Discord.Net.Commands/Command.cs | 15 +++++++++++++ src/Discord.Net.Commands/CommandError.cs | 1 + .../Context/CommandExecutionContext.cs | 25 ++++++++++++++++++++++ 4 files changed, 53 insertions(+) create mode 100644 src/Discord.Net.Commands/Attributes/FilterAttribute.cs create mode 100644 src/Discord.Net.Commands/Context/CommandExecutionContext.cs diff --git a/src/Discord.Net.Commands/Attributes/FilterAttribute.cs b/src/Discord.Net.Commands/Attributes/FilterAttribute.cs new file mode 100644 index 000000000..6e1cfff1c --- /dev/null +++ b/src/Discord.Net.Commands/Attributes/FilterAttribute.cs @@ -0,0 +1,12 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace Discord.Commands +{ + public abstract class FilterAttribute : Attribute + { + public abstract void OnCommandExecuting(CommandExecutionContext context); + } +} diff --git a/src/Discord.Net.Commands/Command.cs b/src/Discord.Net.Commands/Command.cs index 5729e4c81..cd17b6916 100644 --- a/src/Discord.Net.Commands/Command.cs +++ b/src/Discord.Net.Commands/Command.cs @@ -19,6 +19,7 @@ namespace Discord.Commands public string Text { get; } public Module Module { get; } public IReadOnlyList Parameters { get; } + public IReadOnlyList Filters { get; } internal Command(Module module, object instance, CommandAttribute attribute, MethodInfo methodInfo, string groupPrefix) { @@ -37,6 +38,7 @@ namespace Discord.Commands Synopsis = synopsis.Text; Parameters = BuildParameters(methodInfo); + Filters = BuildFilters(methodInfo); _action = BuildAction(methodInfo); } @@ -52,6 +54,14 @@ namespace Discord.Commands if (!parseResult.IsSuccess) return ExecuteResult.FromError(parseResult); + var context = new CommandExecutionContext(this, parseResult, msg); + foreach (FilterAttribute filter in Filters) + { + filter.OnCommandExecuting(context); + if (context.Handled) + return ExecuteResult.FromError(CommandError.InvalidPermissions, $"Permission check for {filter.GetType().FullName} failed"); + } + try { await _action.Invoke(msg, parseResult.Values);//Note: This code may need context @@ -63,6 +73,11 @@ namespace Discord.Commands } } + private IReadOnlyList BuildFilters(MethodInfo methodInfo) + { + return methodInfo.GetCustomAttributes().ToImmutableArray(); + } + private IReadOnlyList BuildParameters(MethodInfo methodInfo) { var parameters = methodInfo.GetParameters(); diff --git a/src/Discord.Net.Commands/CommandError.cs b/src/Discord.Net.Commands/CommandError.cs index 135930dd9..c90d4be50 100644 --- a/src/Discord.Net.Commands/CommandError.cs +++ b/src/Discord.Net.Commands/CommandError.cs @@ -16,5 +16,6 @@ //Execute Exception, + InvalidPermissions } } diff --git a/src/Discord.Net.Commands/Context/CommandExecutionContext.cs b/src/Discord.Net.Commands/Context/CommandExecutionContext.cs new file mode 100644 index 000000000..bec8291cc --- /dev/null +++ b/src/Discord.Net.Commands/Context/CommandExecutionContext.cs @@ -0,0 +1,25 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace Discord.Commands +{ + public class CommandExecutionContext + { + public Command ExecutingCommand { get; internal set; } + public ParseResult ParseResult { get; internal set; } + public IMessage Message { get; internal set; } + + public bool Handled { get; set; } + + internal CommandExecutionContext(Command command, ParseResult parseResult, IMessage message) + { + ExecutingCommand = command; + ParseResult = parseResult; + Message = message; + + Handled = false; + } + } +} From eb38bdd43795ca3d028f0cc25bd8fe125ab7eeb2 Mon Sep 17 00:00:00 2001 From: Finite Reality Date: Tue, 2 Aug 2016 17:54:52 +0100 Subject: [PATCH 02/12] Fix indentation --- .../Attributes/FilterAttribute.cs | 2 +- src/Discord.Net.Commands/Command.cs | 30 +++++++++++----------- src/Discord.Net.Commands/CommandError.cs | 2 +- .../Context/CommandExecutionContext.cs | 22 ++++++++-------- 4 files changed, 28 insertions(+), 28 deletions(-) diff --git a/src/Discord.Net.Commands/Attributes/FilterAttribute.cs b/src/Discord.Net.Commands/Attributes/FilterAttribute.cs index 6e1cfff1c..a3a31dfbd 100644 --- a/src/Discord.Net.Commands/Attributes/FilterAttribute.cs +++ b/src/Discord.Net.Commands/Attributes/FilterAttribute.cs @@ -7,6 +7,6 @@ namespace Discord.Commands { public abstract class FilterAttribute : Attribute { - public abstract void OnCommandExecuting(CommandExecutionContext context); + public abstract void OnCommandExecuting(CommandExecutionContext context); } } diff --git a/src/Discord.Net.Commands/Command.cs b/src/Discord.Net.Commands/Command.cs index cd17b6916..44be5de04 100644 --- a/src/Discord.Net.Commands/Command.cs +++ b/src/Discord.Net.Commands/Command.cs @@ -19,8 +19,8 @@ namespace Discord.Commands public string Text { get; } public Module Module { get; } public IReadOnlyList Parameters { get; } - public IReadOnlyList Filters { get; } - + public IReadOnlyList Filters { get; } + internal Command(Module module, object instance, CommandAttribute attribute, MethodInfo methodInfo, string groupPrefix) { Module = module; @@ -38,7 +38,7 @@ namespace Discord.Commands Synopsis = synopsis.Text; Parameters = BuildParameters(methodInfo); - Filters = BuildFilters(methodInfo); + Filters = BuildFilters(methodInfo); _action = BuildAction(methodInfo); } @@ -54,13 +54,13 @@ namespace Discord.Commands if (!parseResult.IsSuccess) return ExecuteResult.FromError(parseResult); - var context = new CommandExecutionContext(this, parseResult, msg); - foreach (FilterAttribute filter in Filters) - { - filter.OnCommandExecuting(context); - if (context.Handled) - return ExecuteResult.FromError(CommandError.InvalidPermissions, $"Permission check for {filter.GetType().FullName} failed"); - } + var context = new CommandExecutionContext(this, parseResult, msg); + foreach (FilterAttribute filter in Filters) + { + filter.OnCommandExecuting(context); + if (context.Handled) + return ExecuteResult.FromError(CommandError.InvalidPermissions, $"Permission check for {filter.GetType().FullName} failed"); + } try { @@ -73,10 +73,10 @@ namespace Discord.Commands } } - private IReadOnlyList BuildFilters(MethodInfo methodInfo) - { - return methodInfo.GetCustomAttributes().ToImmutableArray(); - } + private IReadOnlyList BuildFilters(MethodInfo methodInfo) + { + return methodInfo.GetCustomAttributes().ToImmutableArray(); + } private IReadOnlyList BuildParameters(MethodInfo methodInfo) { @@ -130,7 +130,7 @@ namespace Discord.Commands { if (methodInfo.ReturnType != typeof(Task)) throw new InvalidOperationException("Commands must return a non-generic Task."); - + return (msg, args) => { object[] newArgs = new object[args.Count + 1]; diff --git a/src/Discord.Net.Commands/CommandError.cs b/src/Discord.Net.Commands/CommandError.cs index c90d4be50..696e5130f 100644 --- a/src/Discord.Net.Commands/CommandError.cs +++ b/src/Discord.Net.Commands/CommandError.cs @@ -16,6 +16,6 @@ //Execute Exception, - InvalidPermissions + InvalidPermissions } } diff --git a/src/Discord.Net.Commands/Context/CommandExecutionContext.cs b/src/Discord.Net.Commands/Context/CommandExecutionContext.cs index bec8291cc..0eaac9fa7 100644 --- a/src/Discord.Net.Commands/Context/CommandExecutionContext.cs +++ b/src/Discord.Net.Commands/Context/CommandExecutionContext.cs @@ -7,19 +7,19 @@ namespace Discord.Commands { public class CommandExecutionContext { - public Command ExecutingCommand { get; internal set; } - public ParseResult ParseResult { get; internal set; } - public IMessage Message { get; internal set; } + public Command ExecutingCommand { get; internal set; } + public ParseResult ParseResult { get; internal set; } + public IMessage Message { get; internal set; } - public bool Handled { get; set; } + public bool Handled { get; set; } - internal CommandExecutionContext(Command command, ParseResult parseResult, IMessage message) - { - ExecutingCommand = command; - ParseResult = parseResult; - Message = message; + internal CommandExecutionContext(Command command, ParseResult parseResult, IMessage message) + { + ExecutingCommand = command; + ParseResult = parseResult; + Message = message; - Handled = false; - } + Handled = false; + } } } From 757c36ccd146d0f8a0d53a52d6f79754c84fbaa5 Mon Sep 17 00:00:00 2001 From: Finite Reality Date: Wed, 3 Aug 2016 16:09:58 +0100 Subject: [PATCH 03/12] Rename and move things about --- .../{FilterAttribute.cs => PermissionAttribute.cs} | 4 +-- src/Discord.Net.Commands/Command.cs | 31 ++++++++++++++-------- ...ndExecutionContext.cs => PermissionsContext.cs} | 6 ++--- 3 files changed, 24 insertions(+), 17 deletions(-) rename src/Discord.Net.Commands/Attributes/{FilterAttribute.cs => PermissionAttribute.cs} (51%) rename src/Discord.Net.Commands/Context/{CommandExecutionContext.cs => PermissionsContext.cs} (63%) diff --git a/src/Discord.Net.Commands/Attributes/FilterAttribute.cs b/src/Discord.Net.Commands/Attributes/PermissionAttribute.cs similarity index 51% rename from src/Discord.Net.Commands/Attributes/FilterAttribute.cs rename to src/Discord.Net.Commands/Attributes/PermissionAttribute.cs index a3a31dfbd..70825aec1 100644 --- a/src/Discord.Net.Commands/Attributes/FilterAttribute.cs +++ b/src/Discord.Net.Commands/Attributes/PermissionAttribute.cs @@ -5,8 +5,8 @@ using System.Threading.Tasks; namespace Discord.Commands { - public abstract class FilterAttribute : Attribute + public abstract class PermissionAttribute : Attribute { - public abstract void OnCommandExecuting(CommandExecutionContext context); + public abstract void CheckPermissions(PermissionsContext context); } } diff --git a/src/Discord.Net.Commands/Command.cs b/src/Discord.Net.Commands/Command.cs index 44be5de04..897106ac3 100644 --- a/src/Discord.Net.Commands/Command.cs +++ b/src/Discord.Net.Commands/Command.cs @@ -19,7 +19,7 @@ namespace Discord.Commands public string Text { get; } public Module Module { get; } public IReadOnlyList Parameters { get; } - public IReadOnlyList Filters { get; } + public IReadOnlyList Permissions { get; } internal Command(Module module, object instance, CommandAttribute attribute, MethodInfo methodInfo, string groupPrefix) { @@ -38,10 +38,24 @@ namespace Discord.Commands Synopsis = synopsis.Text; Parameters = BuildParameters(methodInfo); - Filters = BuildFilters(methodInfo); + Permissions = BuildPermissions(methodInfo); _action = BuildAction(methodInfo); } + public bool CanExecute(IMessage message) + { + var context = new PermissionsContext(this, message); + + foreach (PermissionAttribute permission in Permissions) + { + permission.CheckPermissions(context); + if (context.Handled) + return false; + } + + return true; + } + public async Task Parse(IMessage msg, SearchResult searchResult) { if (!searchResult.IsSuccess) @@ -54,13 +68,8 @@ namespace Discord.Commands if (!parseResult.IsSuccess) return ExecuteResult.FromError(parseResult); - var context = new CommandExecutionContext(this, parseResult, msg); - foreach (FilterAttribute filter in Filters) - { - filter.OnCommandExecuting(context); - if (context.Handled) - return ExecuteResult.FromError(CommandError.InvalidPermissions, $"Permission check for {filter.GetType().FullName} failed"); - } + if (!CanExecute(msg)) // TODO: should we have to check this here, or leave it entirely to the bot dev? + return ExecuteResult.FromError(CommandError.InvalidPermissions, "Permissions check failed"); try { @@ -73,9 +82,9 @@ namespace Discord.Commands } } - private IReadOnlyList BuildFilters(MethodInfo methodInfo) + private IReadOnlyList BuildPermissions(MethodInfo methodInfo) { - return methodInfo.GetCustomAttributes().ToImmutableArray(); + return methodInfo.GetCustomAttributes().ToImmutableArray(); } private IReadOnlyList BuildParameters(MethodInfo methodInfo) diff --git a/src/Discord.Net.Commands/Context/CommandExecutionContext.cs b/src/Discord.Net.Commands/Context/PermissionsContext.cs similarity index 63% rename from src/Discord.Net.Commands/Context/CommandExecutionContext.cs rename to src/Discord.Net.Commands/Context/PermissionsContext.cs index 0eaac9fa7..8c9b79e22 100644 --- a/src/Discord.Net.Commands/Context/CommandExecutionContext.cs +++ b/src/Discord.Net.Commands/Context/PermissionsContext.cs @@ -5,18 +5,16 @@ using System.Threading.Tasks; namespace Discord.Commands { - public class CommandExecutionContext + public class PermissionsContext { public Command ExecutingCommand { get; internal set; } - public ParseResult ParseResult { get; internal set; } public IMessage Message { get; internal set; } public bool Handled { get; set; } - internal CommandExecutionContext(Command command, ParseResult parseResult, IMessage message) + internal PermissionsContext(Command command, IMessage message) { ExecutingCommand = command; - ParseResult = parseResult; Message = message; Handled = false; From 023703c996743a11adbcc6ed6ad4bf3e3522b446 Mon Sep 17 00:00:00 2001 From: Finite Reality Date: Wed, 3 Aug 2016 16:43:25 +0100 Subject: [PATCH 04/12] Add logic to CommandService.Execute to handle basic permissions checks --- src/Discord.Net.Commands/CommandService.cs | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/Discord.Net.Commands/CommandService.cs b/src/Discord.Net.Commands/CommandService.cs index 2ce7c5517..07566c2c4 100644 --- a/src/Discord.Net.Commands/CommandService.cs +++ b/src/Discord.Net.Commands/CommandService.cs @@ -208,7 +208,14 @@ namespace Discord.Commands if (!searchResult.IsSuccess) return searchResult; - var commands = searchResult.Commands; + // TODO: this logic is for users who don't manually search/execute: should we keep it? + + IReadOnlyList commands = searchResult.Commands + .Where(x => x.CanExecute(message)).ToImmutableArray(); + + if (commands.Count == 0 && searchResult.Commands.Count > 0) + return ParseResult.FromError(CommandError.InvalidPermissions, "Invalid permissions"); + for (int i = commands.Count - 1; i >= 0; i--) { var parseResult = await commands[i].Parse(message, searchResult); From a5393dc937cab2d31398e91c1a99c3f3f5fd31b2 Mon Sep 17 00:00:00 2001 From: Finite Reality Date: Wed, 3 Aug 2016 16:50:51 +0100 Subject: [PATCH 05/12] Rename Permission to Precondition --- .../{PermissionAttribute.cs => PreconditionAttribute.cs} | 4 ++-- .../Attributes/Preconditions/RequireDMAttribute.cs | 16 ++++++++++++++++ .../Attributes/Preconditions/RequireGuildAttribute.cs | 16 ++++++++++++++++ src/Discord.Net.Commands/Command.cs | 16 ++++++++-------- src/Discord.Net.Commands/CommandError.cs | 2 +- src/Discord.Net.Commands/CommandService.cs | 4 ++-- .../{PermissionsContext.cs => PreconditionContext.cs} | 8 ++++---- 7 files changed, 49 insertions(+), 17 deletions(-) rename src/Discord.Net.Commands/Attributes/{PermissionAttribute.cs => PreconditionAttribute.cs} (51%) create mode 100644 src/Discord.Net.Commands/Attributes/Preconditions/RequireDMAttribute.cs create mode 100644 src/Discord.Net.Commands/Attributes/Preconditions/RequireGuildAttribute.cs rename src/Discord.Net.Commands/Context/{PermissionsContext.cs => PreconditionContext.cs} (60%) diff --git a/src/Discord.Net.Commands/Attributes/PermissionAttribute.cs b/src/Discord.Net.Commands/Attributes/PreconditionAttribute.cs similarity index 51% rename from src/Discord.Net.Commands/Attributes/PermissionAttribute.cs rename to src/Discord.Net.Commands/Attributes/PreconditionAttribute.cs index 70825aec1..38130aa76 100644 --- a/src/Discord.Net.Commands/Attributes/PermissionAttribute.cs +++ b/src/Discord.Net.Commands/Attributes/PreconditionAttribute.cs @@ -5,8 +5,8 @@ using System.Threading.Tasks; namespace Discord.Commands { - public abstract class PermissionAttribute : Attribute + public abstract class PreconditionAttribute : Attribute { - public abstract void CheckPermissions(PermissionsContext context); + public abstract void CheckPermissions(PreconditionContext context); } } diff --git a/src/Discord.Net.Commands/Attributes/Preconditions/RequireDMAttribute.cs b/src/Discord.Net.Commands/Attributes/Preconditions/RequireDMAttribute.cs new file mode 100644 index 000000000..32d863a0e --- /dev/null +++ b/src/Discord.Net.Commands/Attributes/Preconditions/RequireDMAttribute.cs @@ -0,0 +1,16 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace Discord.Commands +{ + public class RequireDMAttribute : PreconditionAttribute + { + public override void CheckPermissions(PreconditionContext context) + { + if (context.Message.Channel is IGuildChannel) + context.Handled = true; + } + } +} diff --git a/src/Discord.Net.Commands/Attributes/Preconditions/RequireGuildAttribute.cs b/src/Discord.Net.Commands/Attributes/Preconditions/RequireGuildAttribute.cs new file mode 100644 index 000000000..d1615f299 --- /dev/null +++ b/src/Discord.Net.Commands/Attributes/Preconditions/RequireGuildAttribute.cs @@ -0,0 +1,16 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace Discord.Commands +{ + public class RequireGuildAttribute : PreconditionAttribute + { + public override void CheckPermissions(PreconditionContext context) + { + if (!(context.Message.Channel is IGuildChannel)) + context.Handled = true; + } + } +} diff --git a/src/Discord.Net.Commands/Command.cs b/src/Discord.Net.Commands/Command.cs index 897106ac3..4c7e6e7cd 100644 --- a/src/Discord.Net.Commands/Command.cs +++ b/src/Discord.Net.Commands/Command.cs @@ -19,7 +19,7 @@ namespace Discord.Commands public string Text { get; } public Module Module { get; } public IReadOnlyList Parameters { get; } - public IReadOnlyList Permissions { get; } + public IReadOnlyList Permissions { get; } internal Command(Module module, object instance, CommandAttribute attribute, MethodInfo methodInfo, string groupPrefix) { @@ -42,11 +42,11 @@ namespace Discord.Commands _action = BuildAction(methodInfo); } - public bool CanExecute(IMessage message) + public bool MeetsPreconditions(IMessage message) { - var context = new PermissionsContext(this, message); + var context = new PreconditionContext(this, message); - foreach (PermissionAttribute permission in Permissions) + foreach (PreconditionAttribute permission in Permissions) { permission.CheckPermissions(context); if (context.Handled) @@ -68,8 +68,8 @@ namespace Discord.Commands if (!parseResult.IsSuccess) return ExecuteResult.FromError(parseResult); - if (!CanExecute(msg)) // TODO: should we have to check this here, or leave it entirely to the bot dev? - return ExecuteResult.FromError(CommandError.InvalidPermissions, "Permissions check failed"); + if (!MeetsPreconditions(msg)) // TODO: should we have to check this here, or leave it entirely to the bot dev? + return ExecuteResult.FromError(CommandError.UnmetPrecondition, "Permissions check failed"); try { @@ -82,9 +82,9 @@ namespace Discord.Commands } } - private IReadOnlyList BuildPermissions(MethodInfo methodInfo) + private IReadOnlyList BuildPermissions(MethodInfo methodInfo) { - return methodInfo.GetCustomAttributes().ToImmutableArray(); + return methodInfo.GetCustomAttributes().ToImmutableArray(); } private IReadOnlyList BuildParameters(MethodInfo methodInfo) diff --git a/src/Discord.Net.Commands/CommandError.cs b/src/Discord.Net.Commands/CommandError.cs index 696e5130f..31a84ea1a 100644 --- a/src/Discord.Net.Commands/CommandError.cs +++ b/src/Discord.Net.Commands/CommandError.cs @@ -16,6 +16,6 @@ //Execute Exception, - InvalidPermissions + UnmetPrecondition } } diff --git a/src/Discord.Net.Commands/CommandService.cs b/src/Discord.Net.Commands/CommandService.cs index 07566c2c4..dbf385a5c 100644 --- a/src/Discord.Net.Commands/CommandService.cs +++ b/src/Discord.Net.Commands/CommandService.cs @@ -211,10 +211,10 @@ namespace Discord.Commands // TODO: this logic is for users who don't manually search/execute: should we keep it? IReadOnlyList commands = searchResult.Commands - .Where(x => x.CanExecute(message)).ToImmutableArray(); + .Where(x => x.MeetsPreconditions(message)).ToImmutableArray(); if (commands.Count == 0 && searchResult.Commands.Count > 0) - return ParseResult.FromError(CommandError.InvalidPermissions, "Invalid permissions"); + return ParseResult.FromError(CommandError.UnmetPrecondition, "Unmet precondition"); for (int i = commands.Count - 1; i >= 0; i--) { diff --git a/src/Discord.Net.Commands/Context/PermissionsContext.cs b/src/Discord.Net.Commands/Context/PreconditionContext.cs similarity index 60% rename from src/Discord.Net.Commands/Context/PermissionsContext.cs rename to src/Discord.Net.Commands/Context/PreconditionContext.cs index 8c9b79e22..7a1ef118e 100644 --- a/src/Discord.Net.Commands/Context/PermissionsContext.cs +++ b/src/Discord.Net.Commands/Context/PreconditionContext.cs @@ -5,16 +5,16 @@ using System.Threading.Tasks; namespace Discord.Commands { - public class PermissionsContext + public class PreconditionContext { - public Command ExecutingCommand { get; internal set; } + public Command Command { get; internal set; } public IMessage Message { get; internal set; } public bool Handled { get; set; } - internal PermissionsContext(Command command, IMessage message) + internal PreconditionContext(Command command, IMessage message) { - ExecutingCommand = command; + Command = command; Message = message; Handled = false; From 0e920da21f95ae431c04c4cfb5e2ca5495545db7 Mon Sep 17 00:00:00 2001 From: Finite Reality Date: Wed, 3 Aug 2016 17:21:38 +0100 Subject: [PATCH 06/12] Complete Preconditions implementation --- .gitignore | 1 + .../Attributes/PreconditionAttribute.cs | 2 +- .../Attributes/Preconditions/RequireDMAttribute.cs | 8 ++-- .../Preconditions/RequireGuildAttribute.cs | 8 ++-- .../Preconditions/RequireRoleAttribute.cs | 45 ++++++++++++++++++++++ src/Discord.Net.Commands/Command.cs | 25 ++++++------ src/Discord.Net.Commands/CommandService.cs | 17 ++++---- .../Context/PreconditionContext.cs | 23 ----------- src/Discord.Net.Commands/Results/ExecuteResult.cs | 2 + .../Results/PreconditionResult.cs | 27 +++++++++++++ 10 files changed, 108 insertions(+), 50 deletions(-) create mode 100644 src/Discord.Net.Commands/Attributes/Preconditions/RequireRoleAttribute.cs delete mode 100644 src/Discord.Net.Commands/Context/PreconditionContext.cs create mode 100644 src/Discord.Net.Commands/Results/PreconditionResult.cs diff --git a/.gitignore b/.gitignore index d6c4cf780..d7bf0ef19 100644 --- a/.gitignore +++ b/.gitignore @@ -200,3 +200,4 @@ project.lock.json /test/Discord.Net.Tests/config.json /docs/_build *.pyc +/.editorconfig diff --git a/src/Discord.Net.Commands/Attributes/PreconditionAttribute.cs b/src/Discord.Net.Commands/Attributes/PreconditionAttribute.cs index 38130aa76..834cf8162 100644 --- a/src/Discord.Net.Commands/Attributes/PreconditionAttribute.cs +++ b/src/Discord.Net.Commands/Attributes/PreconditionAttribute.cs @@ -7,6 +7,6 @@ namespace Discord.Commands { public abstract class PreconditionAttribute : Attribute { - public abstract void CheckPermissions(PreconditionContext context); + public abstract Task CheckPermissions(IMessage context); } } diff --git a/src/Discord.Net.Commands/Attributes/Preconditions/RequireDMAttribute.cs b/src/Discord.Net.Commands/Attributes/Preconditions/RequireDMAttribute.cs index 32d863a0e..ac621a602 100644 --- a/src/Discord.Net.Commands/Attributes/Preconditions/RequireDMAttribute.cs +++ b/src/Discord.Net.Commands/Attributes/Preconditions/RequireDMAttribute.cs @@ -7,10 +7,12 @@ namespace Discord.Commands { public class RequireDMAttribute : PreconditionAttribute { - public override void CheckPermissions(PreconditionContext context) + public override Task CheckPermissions(IMessage context) { - if (context.Message.Channel is IGuildChannel) - context.Handled = true; + if (context.Channel is IGuildChannel) + return Task.FromResult(PreconditionResult.FromError("Command must be used in a DM")); + + return Task.FromResult(PreconditionResult.FromSuccess()); } } } diff --git a/src/Discord.Net.Commands/Attributes/Preconditions/RequireGuildAttribute.cs b/src/Discord.Net.Commands/Attributes/Preconditions/RequireGuildAttribute.cs index d1615f299..58de17222 100644 --- a/src/Discord.Net.Commands/Attributes/Preconditions/RequireGuildAttribute.cs +++ b/src/Discord.Net.Commands/Attributes/Preconditions/RequireGuildAttribute.cs @@ -7,10 +7,12 @@ namespace Discord.Commands { public class RequireGuildAttribute : PreconditionAttribute { - public override void CheckPermissions(PreconditionContext context) + public override Task CheckPermissions(IMessage context) { - if (!(context.Message.Channel is IGuildChannel)) - context.Handled = true; + if (!(context.Channel is IGuildChannel)) + return Task.FromResult(PreconditionResult.FromError("Command must be used in a guild")); + + return Task.FromResult(PreconditionResult.FromSuccess()); } } } diff --git a/src/Discord.Net.Commands/Attributes/Preconditions/RequireRoleAttribute.cs b/src/Discord.Net.Commands/Attributes/Preconditions/RequireRoleAttribute.cs new file mode 100644 index 000000000..7412315f3 --- /dev/null +++ b/src/Discord.Net.Commands/Attributes/Preconditions/RequireRoleAttribute.cs @@ -0,0 +1,45 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace Discord.Commands +{ + public class RequireRoleAttribute : RequireGuildAttribute + { + public string Role { get; set; } + public StringComparer Comparer { get; set; } + + public RequireRoleAttribute(string roleName) + { + Role = roleName; + Comparer = StringComparer.Ordinal; + } + + public RequireRoleAttribute(string roleName, StringComparer comparer) + { + Role = roleName; + Comparer = comparer; + } + + public override async Task CheckPermissions(IMessage context) + { + var result = await base.CheckPermissions(context).ConfigureAwait(false); + + if (!result.IsSuccess) + return result; + + var author = (context.Author as IGuildUser); + + if (author != null) + { + var hasRole = author.Roles.Any(x => Comparer.Compare(x.Name, Role) == 0); + + if (!hasRole) + return PreconditionResult.FromError($"User does not have the '{Role}' role."); + } + + return PreconditionResult.FromSuccess(); + } + } +} diff --git a/src/Discord.Net.Commands/Command.cs b/src/Discord.Net.Commands/Command.cs index 4c7e6e7cd..482622d6a 100644 --- a/src/Discord.Net.Commands/Command.cs +++ b/src/Discord.Net.Commands/Command.cs @@ -19,7 +19,7 @@ namespace Discord.Commands public string Text { get; } public Module Module { get; } public IReadOnlyList Parameters { get; } - public IReadOnlyList Permissions { get; } + public IReadOnlyList Preconditions { get; } internal Command(Module module, object instance, CommandAttribute attribute, MethodInfo methodInfo, string groupPrefix) { @@ -38,22 +38,20 @@ namespace Discord.Commands Synopsis = synopsis.Text; Parameters = BuildParameters(methodInfo); - Permissions = BuildPermissions(methodInfo); + Preconditions = BuildPreconditions(methodInfo); _action = BuildAction(methodInfo); } - public bool MeetsPreconditions(IMessage message) + public async Task CheckPreconditions(IMessage context) { - var context = new PreconditionContext(this, message); - - foreach (PreconditionAttribute permission in Permissions) + foreach (PreconditionAttribute permission in Preconditions) { - permission.CheckPermissions(context); - if (context.Handled) - return false; + var result = await permission.CheckPermissions(context).ConfigureAwait(false); + if (!result.IsSuccess) + return result; } - return true; + return PreconditionResult.FromSuccess(); } public async Task Parse(IMessage msg, SearchResult searchResult) @@ -68,8 +66,9 @@ namespace Discord.Commands if (!parseResult.IsSuccess) return ExecuteResult.FromError(parseResult); - if (!MeetsPreconditions(msg)) // TODO: should we have to check this here, or leave it entirely to the bot dev? - return ExecuteResult.FromError(CommandError.UnmetPrecondition, "Permissions check failed"); + var precondition = await CheckPreconditions(msg).ConfigureAwait(false); + if (!precondition.IsSuccess) // TODO: should we have to check this here, or leave it entirely to the bot dev? + return ExecuteResult.FromError(precondition); try { @@ -82,7 +81,7 @@ namespace Discord.Commands } } - private IReadOnlyList BuildPermissions(MethodInfo methodInfo) + private IReadOnlyList BuildPreconditions(MethodInfo methodInfo) { return methodInfo.GetCustomAttributes().ToImmutableArray(); } diff --git a/src/Discord.Net.Commands/CommandService.cs b/src/Discord.Net.Commands/CommandService.cs index dbf385a5c..9446d5700 100644 --- a/src/Discord.Net.Commands/CommandService.cs +++ b/src/Discord.Net.Commands/CommandService.cs @@ -208,16 +208,19 @@ namespace Discord.Commands if (!searchResult.IsSuccess) return searchResult; - // TODO: this logic is for users who don't manually search/execute: should we keep it? - - IReadOnlyList commands = searchResult.Commands - .Where(x => x.MeetsPreconditions(message)).ToImmutableArray(); - - if (commands.Count == 0 && searchResult.Commands.Count > 0) - return ParseResult.FromError(CommandError.UnmetPrecondition, "Unmet precondition"); + var commands = searchResult.Commands; for (int i = commands.Count - 1; i >= 0; i--) { + var preconditionResult = await commands[i].CheckPreconditions(message); + if (!preconditionResult.IsSuccess) + { + if (commands.Count == 1) + return preconditionResult; + else + continue; + } + var parseResult = await commands[i].Parse(message, searchResult); if (!parseResult.IsSuccess) { diff --git a/src/Discord.Net.Commands/Context/PreconditionContext.cs b/src/Discord.Net.Commands/Context/PreconditionContext.cs deleted file mode 100644 index 7a1ef118e..000000000 --- a/src/Discord.Net.Commands/Context/PreconditionContext.cs +++ /dev/null @@ -1,23 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; - -namespace Discord.Commands -{ - public class PreconditionContext - { - public Command Command { get; internal set; } - public IMessage Message { get; internal set; } - - public bool Handled { get; set; } - - internal PreconditionContext(Command command, IMessage message) - { - Command = command; - Message = message; - - Handled = false; - } - } -} diff --git a/src/Discord.Net.Commands/Results/ExecuteResult.cs b/src/Discord.Net.Commands/Results/ExecuteResult.cs index a06e8dd99..60d47c7cb 100644 --- a/src/Discord.Net.Commands/Results/ExecuteResult.cs +++ b/src/Discord.Net.Commands/Results/ExecuteResult.cs @@ -28,6 +28,8 @@ namespace Discord.Commands => new ExecuteResult(ex, CommandError.Exception, ex.Message); internal static ExecuteResult FromError(ParseResult result) => new ExecuteResult(null, result.Error, result.ErrorReason); + internal static ExecuteResult FromError(PreconditionResult result) + => new ExecuteResult(null, result.Error, result.ErrorReason); public override string ToString() => IsSuccess ? "Success" : $"{Error}: {ErrorReason}"; private string DebuggerDisplay => IsSuccess ? "Success" : $"{Error}: {ErrorReason}"; diff --git a/src/Discord.Net.Commands/Results/PreconditionResult.cs b/src/Discord.Net.Commands/Results/PreconditionResult.cs new file mode 100644 index 000000000..9d36ba23f --- /dev/null +++ b/src/Discord.Net.Commands/Results/PreconditionResult.cs @@ -0,0 +1,27 @@ +using System.Diagnostics; + +namespace Discord.Commands +{ + [DebuggerDisplay(@"{DebuggerDisplay,nq}")] + public struct PreconditionResult : IResult + { + public CommandError? Error { get; } + public string ErrorReason { get; } + + public bool IsSuccess => !Error.HasValue; + + private PreconditionResult(CommandError? error, string errorReason) + { + Error = error; + ErrorReason = errorReason; + } + + internal static PreconditionResult FromSuccess() + => new PreconditionResult(null, null); + internal static PreconditionResult FromError(string reason) + => new PreconditionResult(CommandError.UnmetPrecondition, reason); + + public override string ToString() => IsSuccess ? "Success" : $"{Error}: {ErrorReason}"; + private string DebuggerDisplay => IsSuccess ? "Success" : $"{Error}: {ErrorReason}"; + } +} From 046d56cab575ce4df12086d28d7c0e5573b8464c Mon Sep 17 00:00:00 2001 From: Finite Reality Date: Thu, 4 Aug 2016 19:36:14 +0100 Subject: [PATCH 07/12] Add module-level preconditions and precondition for individual permissions --- .../Attributes/PreconditionAttribute.cs | 1 + .../Attributes/Preconditions/RequireDMAttribute.cs | 1 + .../Preconditions/RequireGuildAttribute.cs | 1 + .../Attributes/Preconditions/RequirePermission.cs | 54 ++++++++++++++++++++++ .../Preconditions/RequireRoleAttribute.cs | 1 + src/Discord.Net.Commands/Command.cs | 11 ++++- src/Discord.Net.Commands/Module.cs | 10 ++++ 7 files changed, 77 insertions(+), 2 deletions(-) create mode 100644 src/Discord.Net.Commands/Attributes/Preconditions/RequirePermission.cs diff --git a/src/Discord.Net.Commands/Attributes/PreconditionAttribute.cs b/src/Discord.Net.Commands/Attributes/PreconditionAttribute.cs index 834cf8162..d4a3b4a11 100644 --- a/src/Discord.Net.Commands/Attributes/PreconditionAttribute.cs +++ b/src/Discord.Net.Commands/Attributes/PreconditionAttribute.cs @@ -5,6 +5,7 @@ using System.Threading.Tasks; namespace Discord.Commands { + [AttributeUsage(AttributeTargets.Method | AttributeTargets.Class, AllowMultiple = true, Inherited = true)] public abstract class PreconditionAttribute : Attribute { public abstract Task CheckPermissions(IMessage context); diff --git a/src/Discord.Net.Commands/Attributes/Preconditions/RequireDMAttribute.cs b/src/Discord.Net.Commands/Attributes/Preconditions/RequireDMAttribute.cs index ac621a602..eac63f66f 100644 --- a/src/Discord.Net.Commands/Attributes/Preconditions/RequireDMAttribute.cs +++ b/src/Discord.Net.Commands/Attributes/Preconditions/RequireDMAttribute.cs @@ -5,6 +5,7 @@ using System.Threading.Tasks; namespace Discord.Commands { + [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false, Inherited = true)] public class RequireDMAttribute : PreconditionAttribute { public override Task CheckPermissions(IMessage context) diff --git a/src/Discord.Net.Commands/Attributes/Preconditions/RequireGuildAttribute.cs b/src/Discord.Net.Commands/Attributes/Preconditions/RequireGuildAttribute.cs index 58de17222..e909dc4f3 100644 --- a/src/Discord.Net.Commands/Attributes/Preconditions/RequireGuildAttribute.cs +++ b/src/Discord.Net.Commands/Attributes/Preconditions/RequireGuildAttribute.cs @@ -5,6 +5,7 @@ using System.Threading.Tasks; namespace Discord.Commands { + [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false, Inherited = true)] public class RequireGuildAttribute : PreconditionAttribute { public override Task CheckPermissions(IMessage context) diff --git a/src/Discord.Net.Commands/Attributes/Preconditions/RequirePermission.cs b/src/Discord.Net.Commands/Attributes/Preconditions/RequirePermission.cs new file mode 100644 index 000000000..b88177818 --- /dev/null +++ b/src/Discord.Net.Commands/Attributes/Preconditions/RequirePermission.cs @@ -0,0 +1,54 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace Discord.Commands.Attributes.Preconditions +{ + [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = true, Inherited = true)] + public class RequirePermission : RequireGuildAttribute + { + public GuildPermission? GuildPermission { get; set; } + public ChannelPermission? ChannelPermission { get; set; } + + public RequirePermission(GuildPermission permission) + { + GuildPermission = permission; + ChannelPermission = null; + } + + public RequirePermission(ChannelPermission permission) + { + ChannelPermission = permission; + GuildPermission = null; + } + + public override async Task CheckPermissions(IMessage context) + { + var result = await base.CheckPermissions(context).ConfigureAwait(false); + + if (!result.IsSuccess) + return result; + + var author = context.Author as IGuildUser; + + if (GuildPermission.HasValue) + { + var guildPerms = author.GuildPermissions.ToList(); + if (!guildPerms.Contains(GuildPermission.Value)) + return PreconditionResult.FromError($"User is missing guild permission {GuildPermission.Value}"); + } + + if (ChannelPermission.HasValue) + { + var channel = context.Channel as IGuildChannel; + var channelPerms = author.GetPermissions(channel).ToList(); + + if (!channelPerms.Contains(ChannelPermission.Value)) + return PreconditionResult.FromError($"User is missing channel permission {ChannelPermission.Value}"); + } + + return PreconditionResult.FromSuccess(); + } + } +} diff --git a/src/Discord.Net.Commands/Attributes/Preconditions/RequireRoleAttribute.cs b/src/Discord.Net.Commands/Attributes/Preconditions/RequireRoleAttribute.cs index 7412315f3..4eda589c2 100644 --- a/src/Discord.Net.Commands/Attributes/Preconditions/RequireRoleAttribute.cs +++ b/src/Discord.Net.Commands/Attributes/Preconditions/RequireRoleAttribute.cs @@ -5,6 +5,7 @@ using System.Threading.Tasks; namespace Discord.Commands { + [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)] public class RequireRoleAttribute : RequireGuildAttribute { public string Role { get; set; } diff --git a/src/Discord.Net.Commands/Command.cs b/src/Discord.Net.Commands/Command.cs index 482622d6a..e3b9367dc 100644 --- a/src/Discord.Net.Commands/Command.cs +++ b/src/Discord.Net.Commands/Command.cs @@ -44,9 +44,16 @@ namespace Discord.Commands public async Task CheckPreconditions(IMessage context) { - foreach (PreconditionAttribute permission in Preconditions) + foreach (PreconditionAttribute precondition in Module.Preconditions) { - var result = await permission.CheckPermissions(context).ConfigureAwait(false); + var result = await precondition.CheckPermissions(context).ConfigureAwait(false); + if (!result.IsSuccess) + return result; + } + + foreach (PreconditionAttribute precondition in Preconditions) + { + var result = await precondition.CheckPermissions(context).ConfigureAwait(false); if (!result.IsSuccess) return result; } diff --git a/src/Discord.Net.Commands/Module.cs b/src/Discord.Net.Commands/Module.cs index b884832bc..07feaeca2 100644 --- a/src/Discord.Net.Commands/Module.cs +++ b/src/Discord.Net.Commands/Module.cs @@ -1,4 +1,5 @@ using System.Collections.Generic; +using System.Collections.Immutable; using System.Diagnostics; using System.Reflection; @@ -12,6 +13,8 @@ namespace Discord.Commands public IEnumerable Commands { get; } internal object Instance { get; } + public IReadOnlyList Preconditions { get; } + internal Module(CommandService service, object instance, ModuleAttribute moduleAttr, TypeInfo typeInfo) { Service = service; @@ -21,6 +24,8 @@ namespace Discord.Commands List commands = new List(); SearchClass(instance, commands, typeInfo, moduleAttr.Prefix ?? ""); Commands = commands; + + Preconditions = BuildPreconditions(typeInfo); } private void SearchClass(object instance, List commands, TypeInfo typeInfo, string groupPrefix) @@ -48,6 +53,11 @@ namespace Discord.Commands } } + private IReadOnlyList BuildPreconditions(TypeInfo typeInfo) + { + return typeInfo.GetCustomAttributes().ToImmutableArray(); + } + public override string ToString() => Name; private string DebuggerDisplay => Name; } From 1e72eca34a76226c7daf6dc777f52a2569f048dd Mon Sep 17 00:00:00 2001 From: Finite Reality Date: Thu, 4 Aug 2016 21:48:44 +0100 Subject: [PATCH 08/12] Remove Preconditions check from Command.Execute Preconditions are still handled by CommandService.Execute --- src/Discord.Net.Commands/Command.cs | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/Discord.Net.Commands/Command.cs b/src/Discord.Net.Commands/Command.cs index e3b9367dc..1737cb9af 100644 --- a/src/Discord.Net.Commands/Command.cs +++ b/src/Discord.Net.Commands/Command.cs @@ -73,10 +73,6 @@ namespace Discord.Commands if (!parseResult.IsSuccess) return ExecuteResult.FromError(parseResult); - var precondition = await CheckPreconditions(msg).ConfigureAwait(false); - if (!precondition.IsSuccess) // TODO: should we have to check this here, or leave it entirely to the bot dev? - return ExecuteResult.FromError(precondition); - try { await _action.Invoke(msg, parseResult.Values);//Note: This code may need context From 11f1163ec2fc28501974c452303e70a8f72c8389 Mon Sep 17 00:00:00 2001 From: Finite Reality Date: Thu, 4 Aug 2016 23:31:21 +0100 Subject: [PATCH 09/12] Add Command and module instance parameters to CheckPermissions After a small discussion with Joe4evr on discord, a way of retrieving the state of a module appeared to be needed. The new override should provide enough context to a bot dev to allow them to do what they want. --- src/Discord.Net.Commands/Attributes/PreconditionAttribute.cs | 2 +- .../Attributes/Preconditions/RequireDMAttribute.cs | 2 +- .../Attributes/Preconditions/RequireGuildAttribute.cs | 2 +- .../Attributes/Preconditions/RequirePermission.cs | 4 ++-- .../Attributes/Preconditions/RequireRoleAttribute.cs | 4 ++-- src/Discord.Net.Commands/Command.cs | 4 ++-- 6 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/Discord.Net.Commands/Attributes/PreconditionAttribute.cs b/src/Discord.Net.Commands/Attributes/PreconditionAttribute.cs index d4a3b4a11..9d7ec8983 100644 --- a/src/Discord.Net.Commands/Attributes/PreconditionAttribute.cs +++ b/src/Discord.Net.Commands/Attributes/PreconditionAttribute.cs @@ -8,6 +8,6 @@ namespace Discord.Commands [AttributeUsage(AttributeTargets.Method | AttributeTargets.Class, AllowMultiple = true, Inherited = true)] public abstract class PreconditionAttribute : Attribute { - public abstract Task CheckPermissions(IMessage context); + public abstract Task CheckPermissions(IMessage context, Command executingCommand, object moduleInstance); } } diff --git a/src/Discord.Net.Commands/Attributes/Preconditions/RequireDMAttribute.cs b/src/Discord.Net.Commands/Attributes/Preconditions/RequireDMAttribute.cs index eac63f66f..269160e82 100644 --- a/src/Discord.Net.Commands/Attributes/Preconditions/RequireDMAttribute.cs +++ b/src/Discord.Net.Commands/Attributes/Preconditions/RequireDMAttribute.cs @@ -8,7 +8,7 @@ namespace Discord.Commands [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false, Inherited = true)] public class RequireDMAttribute : PreconditionAttribute { - public override Task CheckPermissions(IMessage context) + public override Task CheckPermissions(IMessage context, Command executingCommand, object moduleInstance) { if (context.Channel is IGuildChannel) return Task.FromResult(PreconditionResult.FromError("Command must be used in a DM")); diff --git a/src/Discord.Net.Commands/Attributes/Preconditions/RequireGuildAttribute.cs b/src/Discord.Net.Commands/Attributes/Preconditions/RequireGuildAttribute.cs index e909dc4f3..9ebfd5117 100644 --- a/src/Discord.Net.Commands/Attributes/Preconditions/RequireGuildAttribute.cs +++ b/src/Discord.Net.Commands/Attributes/Preconditions/RequireGuildAttribute.cs @@ -8,7 +8,7 @@ namespace Discord.Commands [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false, Inherited = true)] public class RequireGuildAttribute : PreconditionAttribute { - public override Task CheckPermissions(IMessage context) + public override Task CheckPermissions(IMessage context, Command executingCommand, object moduleInstance) { if (!(context.Channel is IGuildChannel)) return Task.FromResult(PreconditionResult.FromError("Command must be used in a guild")); diff --git a/src/Discord.Net.Commands/Attributes/Preconditions/RequirePermission.cs b/src/Discord.Net.Commands/Attributes/Preconditions/RequirePermission.cs index b88177818..1529fe405 100644 --- a/src/Discord.Net.Commands/Attributes/Preconditions/RequirePermission.cs +++ b/src/Discord.Net.Commands/Attributes/Preconditions/RequirePermission.cs @@ -23,9 +23,9 @@ namespace Discord.Commands.Attributes.Preconditions GuildPermission = null; } - public override async Task CheckPermissions(IMessage context) + public override async Task CheckPermissions(IMessage context, Command executingCommand, object moduleInstance) { - var result = await base.CheckPermissions(context).ConfigureAwait(false); + var result = await base.CheckPermissions(context, executingCommand, moduleInstance).ConfigureAwait(false); if (!result.IsSuccess) return result; diff --git a/src/Discord.Net.Commands/Attributes/Preconditions/RequireRoleAttribute.cs b/src/Discord.Net.Commands/Attributes/Preconditions/RequireRoleAttribute.cs index 4eda589c2..e0ce73eff 100644 --- a/src/Discord.Net.Commands/Attributes/Preconditions/RequireRoleAttribute.cs +++ b/src/Discord.Net.Commands/Attributes/Preconditions/RequireRoleAttribute.cs @@ -23,9 +23,9 @@ namespace Discord.Commands Comparer = comparer; } - public override async Task CheckPermissions(IMessage context) + public override async Task CheckPermissions(IMessage context, Command executingCommand, object moduleInstance) { - var result = await base.CheckPermissions(context).ConfigureAwait(false); + var result = await base.CheckPermissions(context, executingCommand, moduleInstance).ConfigureAwait(false); if (!result.IsSuccess) return result; diff --git a/src/Discord.Net.Commands/Command.cs b/src/Discord.Net.Commands/Command.cs index 1737cb9af..2b0f34eb3 100644 --- a/src/Discord.Net.Commands/Command.cs +++ b/src/Discord.Net.Commands/Command.cs @@ -46,14 +46,14 @@ namespace Discord.Commands { foreach (PreconditionAttribute precondition in Module.Preconditions) { - var result = await precondition.CheckPermissions(context).ConfigureAwait(false); + var result = await precondition.CheckPermissions(context, this, Module.Instance).ConfigureAwait(false); if (!result.IsSuccess) return result; } foreach (PreconditionAttribute precondition in Preconditions) { - var result = await precondition.CheckPermissions(context).ConfigureAwait(false); + var result = await precondition.CheckPermissions(context, this, Module.Instance).ConfigureAwait(false); if (!result.IsSuccess) return result; } From 6db3f52ac1f8b81e96a25cd90300c06bd8067959 Mon Sep 17 00:00:00 2001 From: Finite Reality Date: Sat, 6 Aug 2016 00:15:11 +0100 Subject: [PATCH 10/12] eplace DM/Guild preconditions with context The new RequireContextAttribute works just like RequireDM/RequireGuild, but is more powerful as developers can specify multiple 'contexts' for their command to require using the ContextType flags. --- .../Preconditions/RequireContextAttribute.cs | 49 ++++++++++++++++++++++ .../Attributes/Preconditions/RequireDMAttribute.cs | 19 --------- .../Preconditions/RequireGuildAttribute.cs | 19 --------- .../Preconditions/RequireRoleAttribute.cs | 46 -------------------- 4 files changed, 49 insertions(+), 84 deletions(-) create mode 100644 src/Discord.Net.Commands/Attributes/Preconditions/RequireContextAttribute.cs delete mode 100644 src/Discord.Net.Commands/Attributes/Preconditions/RequireDMAttribute.cs delete mode 100644 src/Discord.Net.Commands/Attributes/Preconditions/RequireGuildAttribute.cs delete mode 100644 src/Discord.Net.Commands/Attributes/Preconditions/RequireRoleAttribute.cs diff --git a/src/Discord.Net.Commands/Attributes/Preconditions/RequireContextAttribute.cs b/src/Discord.Net.Commands/Attributes/Preconditions/RequireContextAttribute.cs new file mode 100644 index 000000000..b336f88a6 --- /dev/null +++ b/src/Discord.Net.Commands/Attributes/Preconditions/RequireContextAttribute.cs @@ -0,0 +1,49 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace Discord.Commands +{ + [Flags] + public enum ContextType + { + Invalid = 0, // 00 + Guild = 1, // 01 + DM = 2 // 10 + } + + + [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false, Inherited = true)] + public class RequireContextAttribute : PreconditionAttribute + { + public ContextType Context { get; set; } + + public RequireContextAttribute(ContextType context) + { + Context = context; + + if (Context == ContextType.Invalid) + throw new ArgumentException("Context must be a bitfield of ContextType.Guild and ContextType.DM", "context"); + } + + public override Task CheckPermissions(IMessage context, Command executingCommand, object moduleInstance) + { + var validContext = false; + + if (Context == ContextType.Invalid) + throw new InvalidOperationException("Invalid ContextType"); + + if (Context.HasFlag(ContextType.Guild)) + validContext = validContext || context.Channel is IGuildChannel; + + if (Context.HasFlag(ContextType.DM)) + validContext = validContext || context.Channel is IDMChannel; + + if (validContext) + return Task.FromResult(PreconditionResult.FromSuccess()); + else + return Task.FromResult(PreconditionResult.FromError($"Invalid context for command; accepted contexts: {Context}")); + } + } +} diff --git a/src/Discord.Net.Commands/Attributes/Preconditions/RequireDMAttribute.cs b/src/Discord.Net.Commands/Attributes/Preconditions/RequireDMAttribute.cs deleted file mode 100644 index 269160e82..000000000 --- a/src/Discord.Net.Commands/Attributes/Preconditions/RequireDMAttribute.cs +++ /dev/null @@ -1,19 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; - -namespace Discord.Commands -{ - [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false, Inherited = true)] - public class RequireDMAttribute : PreconditionAttribute - { - public override Task CheckPermissions(IMessage context, Command executingCommand, object moduleInstance) - { - if (context.Channel is IGuildChannel) - return Task.FromResult(PreconditionResult.FromError("Command must be used in a DM")); - - return Task.FromResult(PreconditionResult.FromSuccess()); - } - } -} diff --git a/src/Discord.Net.Commands/Attributes/Preconditions/RequireGuildAttribute.cs b/src/Discord.Net.Commands/Attributes/Preconditions/RequireGuildAttribute.cs deleted file mode 100644 index 9ebfd5117..000000000 --- a/src/Discord.Net.Commands/Attributes/Preconditions/RequireGuildAttribute.cs +++ /dev/null @@ -1,19 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; - -namespace Discord.Commands -{ - [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false, Inherited = true)] - public class RequireGuildAttribute : PreconditionAttribute - { - public override Task CheckPermissions(IMessage context, Command executingCommand, object moduleInstance) - { - if (!(context.Channel is IGuildChannel)) - return Task.FromResult(PreconditionResult.FromError("Command must be used in a guild")); - - return Task.FromResult(PreconditionResult.FromSuccess()); - } - } -} diff --git a/src/Discord.Net.Commands/Attributes/Preconditions/RequireRoleAttribute.cs b/src/Discord.Net.Commands/Attributes/Preconditions/RequireRoleAttribute.cs deleted file mode 100644 index e0ce73eff..000000000 --- a/src/Discord.Net.Commands/Attributes/Preconditions/RequireRoleAttribute.cs +++ /dev/null @@ -1,46 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; - -namespace Discord.Commands -{ - [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)] - public class RequireRoleAttribute : RequireGuildAttribute - { - public string Role { get; set; } - public StringComparer Comparer { get; set; } - - public RequireRoleAttribute(string roleName) - { - Role = roleName; - Comparer = StringComparer.Ordinal; - } - - public RequireRoleAttribute(string roleName, StringComparer comparer) - { - Role = roleName; - Comparer = comparer; - } - - public override async Task CheckPermissions(IMessage context, Command executingCommand, object moduleInstance) - { - var result = await base.CheckPermissions(context, executingCommand, moduleInstance).ConfigureAwait(false); - - if (!result.IsSuccess) - return result; - - var author = (context.Author as IGuildUser); - - if (author != null) - { - var hasRole = author.Roles.Any(x => Comparer.Compare(x.Name, Role) == 0); - - if (!hasRole) - return PreconditionResult.FromError($"User does not have the '{Role}' role."); - } - - return PreconditionResult.FromSuccess(); - } - } -} From c77c84bec7cca32b8d4c081acaf57e197461d6f5 Mon Sep 17 00:00:00 2001 From: Finite Reality Date: Sat, 6 Aug 2016 00:43:11 +0100 Subject: [PATCH 11/12] Remove ContextType.Invalid --- .../Attributes/Preconditions/RequireContextAttribute.cs | 7 ------- 1 file changed, 7 deletions(-) diff --git a/src/Discord.Net.Commands/Attributes/Preconditions/RequireContextAttribute.cs b/src/Discord.Net.Commands/Attributes/Preconditions/RequireContextAttribute.cs index b336f88a6..da9391fad 100644 --- a/src/Discord.Net.Commands/Attributes/Preconditions/RequireContextAttribute.cs +++ b/src/Discord.Net.Commands/Attributes/Preconditions/RequireContextAttribute.cs @@ -8,7 +8,6 @@ namespace Discord.Commands [Flags] public enum ContextType { - Invalid = 0, // 00 Guild = 1, // 01 DM = 2 // 10 } @@ -22,18 +21,12 @@ namespace Discord.Commands public RequireContextAttribute(ContextType context) { Context = context; - - if (Context == ContextType.Invalid) - throw new ArgumentException("Context must be a bitfield of ContextType.Guild and ContextType.DM", "context"); } public override Task CheckPermissions(IMessage context, Command executingCommand, object moduleInstance) { var validContext = false; - if (Context == ContextType.Invalid) - throw new InvalidOperationException("Invalid ContextType"); - if (Context.HasFlag(ContextType.Guild)) validContext = validContext || context.Channel is IGuildChannel; From f2d80de343e74702ffb75ef82b430bd0def6ef5e Mon Sep 17 00:00:00 2001 From: Finite Reality Date: Sun, 7 Aug 2016 17:23:26 +0100 Subject: [PATCH 12/12] Fix error with RequirePermission precondition --- .../Attributes/Preconditions/RequirePermission.cs | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/src/Discord.Net.Commands/Attributes/Preconditions/RequirePermission.cs b/src/Discord.Net.Commands/Attributes/Preconditions/RequirePermission.cs index 1529fe405..a970685f5 100644 --- a/src/Discord.Net.Commands/Attributes/Preconditions/RequirePermission.cs +++ b/src/Discord.Net.Commands/Attributes/Preconditions/RequirePermission.cs @@ -6,7 +6,7 @@ using System.Threading.Tasks; namespace Discord.Commands.Attributes.Preconditions { [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = true, Inherited = true)] - public class RequirePermission : RequireGuildAttribute + public class RequirePermission : PreconditionAttribute { public GuildPermission? GuildPermission { get; set; } public ChannelPermission? ChannelPermission { get; set; } @@ -23,12 +23,10 @@ namespace Discord.Commands.Attributes.Preconditions GuildPermission = null; } - public override async Task CheckPermissions(IMessage context, Command executingCommand, object moduleInstance) + public override Task CheckPermissions(IMessage context, Command executingCommand, object moduleInstance) { - var result = await base.CheckPermissions(context, executingCommand, moduleInstance).ConfigureAwait(false); - - if (!result.IsSuccess) - return result; + if (!(context.Channel is IGuildChannel)) + return Task.FromResult(PreconditionResult.FromError("Command must be used in a guild channel")); var author = context.Author as IGuildUser; @@ -36,7 +34,7 @@ namespace Discord.Commands.Attributes.Preconditions { var guildPerms = author.GuildPermissions.ToList(); if (!guildPerms.Contains(GuildPermission.Value)) - return PreconditionResult.FromError($"User is missing guild permission {GuildPermission.Value}"); + return Task.FromResult(PreconditionResult.FromError($"User is missing guild permission {GuildPermission.Value}")); } if (ChannelPermission.HasValue) @@ -45,10 +43,10 @@ namespace Discord.Commands.Attributes.Preconditions var channelPerms = author.GetPermissions(channel).ToList(); if (!channelPerms.Contains(ChannelPermission.Value)) - return PreconditionResult.FromError($"User is missing channel permission {ChannelPermission.Value}"); + return Task.FromResult(PreconditionResult.FromError($"User is missing channel permission {ChannelPermission.Value}")); } - return PreconditionResult.FromSuccess(); + return Task.FromResult(PreconditionResult.FromSuccess()); } } }