Browse Source

Complete Preconditions implementation

tags/1.0-rc
Finite Reality 9 years ago
parent
commit
0e920da21f
10 changed files with 108 additions and 50 deletions
  1. +1
    -0
      .gitignore
  2. +1
    -1
      src/Discord.Net.Commands/Attributes/PreconditionAttribute.cs
  3. +5
    -3
      src/Discord.Net.Commands/Attributes/Preconditions/RequireDMAttribute.cs
  4. +5
    -3
      src/Discord.Net.Commands/Attributes/Preconditions/RequireGuildAttribute.cs
  5. +45
    -0
      src/Discord.Net.Commands/Attributes/Preconditions/RequireRoleAttribute.cs
  6. +12
    -13
      src/Discord.Net.Commands/Command.cs
  7. +10
    -7
      src/Discord.Net.Commands/CommandService.cs
  8. +0
    -23
      src/Discord.Net.Commands/Context/PreconditionContext.cs
  9. +2
    -0
      src/Discord.Net.Commands/Results/ExecuteResult.cs
  10. +27
    -0
      src/Discord.Net.Commands/Results/PreconditionResult.cs

+ 1
- 0
.gitignore View File

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

+ 1
- 1
src/Discord.Net.Commands/Attributes/PreconditionAttribute.cs View File

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

+ 5
- 3
src/Discord.Net.Commands/Attributes/Preconditions/RequireDMAttribute.cs View File

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

+ 5
- 3
src/Discord.Net.Commands/Attributes/Preconditions/RequireGuildAttribute.cs View File

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

+ 45
- 0
src/Discord.Net.Commands/Attributes/Preconditions/RequireRoleAttribute.cs 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();
}
}
}

+ 12
- 13
src/Discord.Net.Commands/Command.cs View File

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


+ 10
- 7
src/Discord.Net.Commands/CommandService.cs View File

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


+ 0
- 23
src/Discord.Net.Commands/Context/PreconditionContext.cs 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;
}
}
}

+ 2
- 0
src/Discord.Net.Commands/Results/ExecuteResult.cs View File

@@ -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
- 0
src/Discord.Net.Commands/Results/PreconditionResult.cs 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}";
}
}

Loading…
Cancel
Save