Complete Preconditions implementation
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -200,3 +200,4 @@ project.lock.json
|
||||
/test/Discord.Net.Tests/config.json
|
||||
/docs/_build
|
||||
*.pyc
|
||||
/.editorconfig
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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}";
|
||||
|
||||
27
src/Discord.Net.Commands/Results/PreconditionResult.cs
Normal file
27
src/Discord.Net.Commands/Results/PreconditionResult.cs
Normal 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}";
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user