Browse Source

Merge pull request #179 from DigiTechs/feature/172

Preconditions for commands
pull/193/merge
RogueException GitHub 9 years ago
parent
commit
2b9600686f
10 changed files with 186 additions and 2 deletions
  1. +1
    -0
      .gitignore
  2. +13
    -0
      src/Discord.Net.Commands/Attributes/PreconditionAttribute.cs
  3. +42
    -0
      src/Discord.Net.Commands/Attributes/Preconditions/RequireContextAttribute.cs
  4. +52
    -0
      src/Discord.Net.Commands/Attributes/Preconditions/RequirePermission.cs
  5. +28
    -2
      src/Discord.Net.Commands/Command.cs
  6. +1
    -0
      src/Discord.Net.Commands/CommandError.cs
  7. +10
    -0
      src/Discord.Net.Commands/CommandService.cs
  8. +10
    -0
      src/Discord.Net.Commands/Module.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
/docs/_build
*.pyc
/.editorconfig

+ 13
- 0
src/Discord.Net.Commands/Attributes/PreconditionAttribute.cs 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);
}
}

+ 42
- 0
src/Discord.Net.Commands/Attributes/Preconditions/RequireContextAttribute.cs 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}"));
}
}
}

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

+ 28
- 2
src/Discord.Net.Commands/Command.cs 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];


+ 1
- 0
src/Discord.Net.Commands/CommandError.cs View File

@@ -16,5 +16,6 @@

//Execute
Exception,
UnmetPrecondition
}
}

+ 10
- 0
src/Discord.Net.Commands/CommandService.cs 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)
{


+ 10
- 0
src/Discord.Net.Commands/Module.cs 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;
}


+ 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);
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
- 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