Merge pull request #179 from DigiTechs/feature/172

Preconditions for commands
This commit is contained in:
RogueException
2016-08-09 17:30:25 -03:00
committed by GitHub
10 changed files with 186 additions and 2 deletions

1
.gitignore vendored
View File

@@ -200,3 +200,4 @@ project.lock.json
/test/Discord.Net.Tests/config.json
/docs/_build
*.pyc
/.editorconfig

View File

@@ -0,0 +1,13 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace Discord.Commands
{
[AttributeUsage(AttributeTargets.Method | AttributeTargets.Class, AllowMultiple = true, Inherited = true)]
public abstract class PreconditionAttribute : Attribute
{
public abstract Task<PreconditionResult> CheckPermissions(IMessage context, Command executingCommand, object moduleInstance);
}
}

View File

@@ -0,0 +1,42 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace Discord.Commands
{
[Flags]
public enum ContextType
{
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;
}
public override Task<PreconditionResult> CheckPermissions(IMessage context, Command executingCommand, object moduleInstance)
{
var validContext = false;
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}"));
}
}
}

View File

@@ -0,0 +1,52 @@
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 : PreconditionAttribute
{
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 Task<PreconditionResult> CheckPermissions(IMessage context, Command executingCommand, object moduleInstance)
{
if (!(context.Channel is IGuildChannel))
return Task.FromResult(PreconditionResult.FromError("Command must be used in a guild channel"));
var author = context.Author as IGuildUser;
if (GuildPermission.HasValue)
{
var guildPerms = author.GuildPermissions.ToList();
if (!guildPerms.Contains(GuildPermission.Value))
return Task.FromResult(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 Task.FromResult(PreconditionResult.FromError($"User is missing channel permission {ChannelPermission.Value}"));
}
return Task.FromResult(PreconditionResult.FromSuccess());
}
}
}

View File

@@ -19,7 +19,8 @@ namespace Discord.Commands
public string Text { get; }
public Module Module { get; }
public IReadOnlyList<CommandParameter> Parameters { get; }
public IReadOnlyList<PreconditionAttribute> Preconditions { get; }
internal Command(Module module, object instance, CommandAttribute attribute, MethodInfo methodInfo, string groupPrefix)
{
Module = module;
@@ -37,9 +38,29 @@ namespace Discord.Commands
Synopsis = synopsis.Text;
Parameters = BuildParameters(methodInfo);
Preconditions = BuildPreconditions(methodInfo);
_action = BuildAction(methodInfo);
}
public async Task<PreconditionResult> CheckPreconditions(IMessage context)
{
foreach (PreconditionAttribute precondition in Module.Preconditions)
{
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, this, Module.Instance).ConfigureAwait(false);
if (!result.IsSuccess)
return result;
}
return PreconditionResult.FromSuccess();
}
public async Task<ParseResult> Parse(IMessage msg, SearchResult searchResult)
{
if (!searchResult.IsSuccess)
@@ -63,6 +84,11 @@ namespace Discord.Commands
}
}
private IReadOnlyList<PreconditionAttribute> BuildPreconditions(MethodInfo methodInfo)
{
return methodInfo.GetCustomAttributes<PreconditionAttribute>().ToImmutableArray();
}
private IReadOnlyList<CommandParameter> BuildParameters(MethodInfo methodInfo)
{
var parameters = methodInfo.GetParameters();
@@ -115,7 +141,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];

View File

@@ -16,5 +16,6 @@
//Execute
Exception,
UnmetPrecondition
}
}

View File

@@ -209,8 +209,18 @@ namespace Discord.Commands
return searchResult;
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)
{

View File

@@ -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<Command> Commands { get; }
internal object Instance { get; }
public IReadOnlyList<PreconditionAttribute> Preconditions { get; }
internal Module(CommandService service, object instance, ModuleAttribute moduleAttr, TypeInfo typeInfo)
{
Service = service;
@@ -21,6 +24,8 @@ namespace Discord.Commands
List<Command> commands = new List<Command>();
SearchClass(instance, commands, typeInfo, moduleAttr.Prefix ?? "");
Commands = commands;
Preconditions = BuildPreconditions(typeInfo);
}
private void SearchClass(object instance, List<Command> commands, TypeInfo typeInfo, string groupPrefix)
@@ -48,6 +53,11 @@ namespace Discord.Commands
}
}
private IReadOnlyList<PreconditionAttribute> BuildPreconditions(TypeInfo typeInfo)
{
return typeInfo.GetCustomAttributes<PreconditionAttribute>().ToImmutableArray();
}
public override string ToString() => Name;
private string DebuggerDisplay => Name;
}

View File

@@ -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}";

View File

@@ -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}";
}
}