Complete Preconditions implementation

This commit is contained in:
Finite Reality
2016-08-03 17:21:38 +01:00
parent a5393dc937
commit 0e920da21f
10 changed files with 108 additions and 50 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

@@ -7,6 +7,6 @@ namespace Discord.Commands
{
public abstract class PreconditionAttribute : Attribute
{
public abstract void CheckPermissions(PreconditionContext context);
public abstract Task<PreconditionResult> CheckPermissions(IMessage context);
}
}

View File

@@ -7,10 +7,12 @@ namespace Discord.Commands
{
public class RequireDMAttribute : PreconditionAttribute
{
public override void CheckPermissions(PreconditionContext context)
public override Task<PreconditionResult> 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());
}
}
}

View File

@@ -7,10 +7,12 @@ namespace Discord.Commands
{
public class RequireGuildAttribute : PreconditionAttribute
{
public override void CheckPermissions(PreconditionContext context)
public override Task<PreconditionResult> 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());
}
}
}

View File

@@ -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<PreconditionResult> 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();
}
}
}

View File

@@ -19,7 +19,7 @@ namespace Discord.Commands
public string Text { get; }
public Module Module { get; }
public IReadOnlyList<CommandParameter> Parameters { get; }
public IReadOnlyList<PreconditionAttribute> Permissions { get; }
public IReadOnlyList<PreconditionAttribute> 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<PreconditionResult> 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<ParseResult> 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<PreconditionAttribute> BuildPermissions(MethodInfo methodInfo)
private IReadOnlyList<PreconditionAttribute> BuildPreconditions(MethodInfo methodInfo)
{
return methodInfo.GetCustomAttributes<PreconditionAttribute>().ToImmutableArray();
}

View File

@@ -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<Command> 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)
{

View File

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

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