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
|
/test/Discord.Net.Tests/config.json
|
||||||
/docs/_build
|
/docs/_build
|
||||||
*.pyc
|
*.pyc
|
||||||
|
/.editorconfig
|
||||||
|
|||||||
@@ -7,6 +7,6 @@ namespace Discord.Commands
|
|||||||
{
|
{
|
||||||
public abstract class PreconditionAttribute : Attribute
|
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 class RequireDMAttribute : PreconditionAttribute
|
||||||
{
|
{
|
||||||
public override void CheckPermissions(PreconditionContext context)
|
public override Task<PreconditionResult> CheckPermissions(IMessage context)
|
||||||
{
|
{
|
||||||
if (context.Message.Channel is IGuildChannel)
|
if (context.Channel is IGuildChannel)
|
||||||
context.Handled = true;
|
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 class RequireGuildAttribute : PreconditionAttribute
|
||||||
{
|
{
|
||||||
public override void CheckPermissions(PreconditionContext context)
|
public override Task<PreconditionResult> CheckPermissions(IMessage context)
|
||||||
{
|
{
|
||||||
if (!(context.Message.Channel is IGuildChannel))
|
if (!(context.Channel is IGuildChannel))
|
||||||
context.Handled = true;
|
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 string Text { get; }
|
||||||
public Module Module { get; }
|
public Module Module { get; }
|
||||||
public IReadOnlyList<CommandParameter> Parameters { 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)
|
internal Command(Module module, object instance, CommandAttribute attribute, MethodInfo methodInfo, string groupPrefix)
|
||||||
{
|
{
|
||||||
@@ -38,22 +38,20 @@ namespace Discord.Commands
|
|||||||
Synopsis = synopsis.Text;
|
Synopsis = synopsis.Text;
|
||||||
|
|
||||||
Parameters = BuildParameters(methodInfo);
|
Parameters = BuildParameters(methodInfo);
|
||||||
Permissions = BuildPermissions(methodInfo);
|
Preconditions = BuildPreconditions(methodInfo);
|
||||||
_action = BuildAction(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 Preconditions)
|
||||||
|
|
||||||
foreach (PreconditionAttribute permission in Permissions)
|
|
||||||
{
|
{
|
||||||
permission.CheckPermissions(context);
|
var result = await permission.CheckPermissions(context).ConfigureAwait(false);
|
||||||
if (context.Handled)
|
if (!result.IsSuccess)
|
||||||
return false;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return PreconditionResult.FromSuccess();
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<ParseResult> Parse(IMessage msg, SearchResult searchResult)
|
public async Task<ParseResult> Parse(IMessage msg, SearchResult searchResult)
|
||||||
@@ -68,8 +66,9 @@ namespace Discord.Commands
|
|||||||
if (!parseResult.IsSuccess)
|
if (!parseResult.IsSuccess)
|
||||||
return ExecuteResult.FromError(parseResult);
|
return ExecuteResult.FromError(parseResult);
|
||||||
|
|
||||||
if (!MeetsPreconditions(msg)) // TODO: should we have to check this here, or leave it entirely to the bot dev?
|
var precondition = await CheckPreconditions(msg).ConfigureAwait(false);
|
||||||
return ExecuteResult.FromError(CommandError.UnmetPrecondition, "Permissions check failed");
|
if (!precondition.IsSuccess) // TODO: should we have to check this here, or leave it entirely to the bot dev?
|
||||||
|
return ExecuteResult.FromError(precondition);
|
||||||
|
|
||||||
try
|
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();
|
return methodInfo.GetCustomAttributes<PreconditionAttribute>().ToImmutableArray();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -208,16 +208,19 @@ namespace Discord.Commands
|
|||||||
if (!searchResult.IsSuccess)
|
if (!searchResult.IsSuccess)
|
||||||
return searchResult;
|
return searchResult;
|
||||||
|
|
||||||
// TODO: this logic is for users who don't manually search/execute: should we keep it?
|
var commands = searchResult.Commands;
|
||||||
|
|
||||||
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");
|
|
||||||
|
|
||||||
for (int i = commands.Count - 1; i >= 0; i--)
|
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);
|
var parseResult = await commands[i].Parse(message, searchResult);
|
||||||
if (!parseResult.IsSuccess)
|
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);
|
=> new ExecuteResult(ex, CommandError.Exception, ex.Message);
|
||||||
internal static ExecuteResult FromError(ParseResult result)
|
internal static ExecuteResult FromError(ParseResult result)
|
||||||
=> new ExecuteResult(null, result.Error, result.ErrorReason);
|
=> 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}";
|
public override string ToString() => IsSuccess ? "Success" : $"{Error}: {ErrorReason}";
|
||||||
private string DebuggerDisplay => 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