Even more changes
This commit is contained in:
parent
ba1096be6d
commit
adf1766690
|
@ -0,0 +1,10 @@
|
|||
using System;
|
||||
|
||||
namespace DeukBot4.MessageHandlers
|
||||
{
|
||||
[AttributeUsage(AttributeTargets.Method)]
|
||||
public class RequireParameterMatchAttribute : Attribute
|
||||
{
|
||||
|
||||
}
|
||||
}
|
|
@ -1,49 +1,55 @@
|
|||
using System.Diagnostics;
|
||||
using System.Reflection;
|
||||
using System.Reflection;
|
||||
using System.Threading.Tasks;
|
||||
using DeukBot4.MessageHandlers.CommandHandler.RequestStructure;
|
||||
using DeukBot4.MessageHandlers.Permissions;
|
||||
using Discord.WebSocket;
|
||||
|
||||
namespace DeukBot4.MessageHandlers.CommandHandler
|
||||
{
|
||||
public class Command
|
||||
{
|
||||
public Command(string name, PermissionLevel permission, string shortHelp, string longHelp, bool forbidInPm,
|
||||
ParameterMatcher.ParameterType[][] parameterTypes, MethodInfo function, CommandContainerBase commandContainer)
|
||||
public Command(string name, PermissionLevel permission, string shortHelp,
|
||||
string longHelp,
|
||||
ParameterMatcher.ParameterType[][] parameterTypes, bool forbidInPm, bool requireParameterMatch,
|
||||
MethodInfo function,
|
||||
CommandContainerBase commandContainer)
|
||||
{
|
||||
Name = name;
|
||||
Permission = permission;
|
||||
ShortHelp = shortHelp;
|
||||
LongHelp = longHelp;
|
||||
Function = function;
|
||||
CommandContainer = commandContainer;
|
||||
ParameterTypes = parameterTypes;
|
||||
HasHelp = true;
|
||||
ForbidInPm = forbidInPm;
|
||||
Name = name;
|
||||
Permission = permission;
|
||||
ShortHelp = shortHelp;
|
||||
LongHelp = longHelp;
|
||||
Function = function;
|
||||
CommandContainer = commandContainer;
|
||||
ParameterTypes = parameterTypes;
|
||||
HasHelp = true;
|
||||
ForbidInPm = forbidInPm;
|
||||
RequireParameterMatch = requireParameterMatch;
|
||||
}
|
||||
|
||||
public Command(string name, PermissionLevel permission, ParameterMatcher.ParameterType[][] parameterTypes, bool forbidInPm,
|
||||
MethodInfo function, CommandContainerBase commandContainer)
|
||||
public Command(string name, PermissionLevel permission,
|
||||
ParameterMatcher.ParameterType[][] parameterTypes,
|
||||
bool forbidInPm, bool requireParameterMatch,
|
||||
MethodInfo function, CommandContainerBase commandContainer)
|
||||
{
|
||||
Name = name;
|
||||
Permission = permission;
|
||||
Function = function;
|
||||
CommandContainer = commandContainer;
|
||||
ParameterTypes = parameterTypes;
|
||||
HasHelp = false;
|
||||
ForbidInPm = forbidInPm;
|
||||
Name = name;
|
||||
Permission = permission;
|
||||
Function = function;
|
||||
CommandContainer = commandContainer;
|
||||
ParameterTypes = parameterTypes;
|
||||
HasHelp = false;
|
||||
ForbidInPm = forbidInPm;
|
||||
RequireParameterMatch = requireParameterMatch;
|
||||
}
|
||||
|
||||
public string Name { get; }
|
||||
public PermissionLevel Permission { get; }
|
||||
public string ShortHelp { get; }
|
||||
public string LongHelp { get; }
|
||||
public MethodInfo Function { get; }
|
||||
public CommandContainerBase CommandContainer { get; }
|
||||
public bool HasHelp { get; }
|
||||
public ParameterMatcher.ParameterType[][] ParameterTypes { get; }
|
||||
public bool ForbidInPm { get; }
|
||||
public string Name { get; }
|
||||
public PermissionLevel Permission { get; }
|
||||
public string ShortHelp { get; }
|
||||
public string LongHelp { get; }
|
||||
public MethodInfo Function { get; }
|
||||
public CommandContainerBase CommandContainer { get; }
|
||||
public bool HasHelp { get; }
|
||||
public ParameterMatcher.ParameterType[][] ParameterTypes { get; }
|
||||
public bool ForbidInPm { get; }
|
||||
public bool RequireParameterMatch { get; }
|
||||
|
||||
private string[] _parameterMatchers;
|
||||
|
||||
|
|
|
@ -17,37 +17,44 @@ namespace DeukBot4.MessageHandlers.CommandHandler
|
|||
|
||||
foreach (var methodInfo in funcs)
|
||||
{
|
||||
// grab all command attributes, cast them properly
|
||||
var commandAttributes = methodInfo.GetCustomAttributes(typeof(CommandAttribute), true)
|
||||
.Select(x => x as CommandAttribute);
|
||||
|
||||
// get the help attribute if it exists
|
||||
CommandHelpAttribute helpAttribute = null;
|
||||
var helpAttributes = methodInfo.GetCustomAttributes(typeof(CommandHelpAttribute), true)
|
||||
.Select(x => x as CommandHelpAttribute);
|
||||
var commandHelpAttributes = helpAttributes as CommandHelpAttribute[] ?? helpAttributes.ToArray();
|
||||
if (commandHelpAttributes.Any())
|
||||
helpAttribute = commandHelpAttributes[0];
|
||||
.Select(x => x as CommandHelpAttribute).ToArray();
|
||||
if (helpAttributes.Any())
|
||||
helpAttribute = helpAttributes[0];
|
||||
|
||||
var parametersAttributes = methodInfo.GetCustomAttributes(typeof(CommandParametersAttribute), true)
|
||||
.Select(x => x as CommandParametersAttribute);
|
||||
var commandParametersAttributes = parametersAttributes as CommandParametersAttribute[] ??
|
||||
parametersAttributes.ToArray();
|
||||
var parameters = commandParametersAttributes.Select(x => x.Types).ToArray();
|
||||
|
||||
// grab all of the potential parameter type arrays
|
||||
var parameters = methodInfo.GetCustomAttributes(typeof(CommandParametersAttribute), true)
|
||||
.Select(x => (CommandParametersAttribute) x)
|
||||
.Select(x => x.Types.ToArray())
|
||||
.ToArray();
|
||||
|
||||
// check if the function has the attribute for blocking usage in PMs
|
||||
var forbidPm = methodInfo.GetCustomAttributes(typeof(BlockUsageInPmAttribute), true).Any();
|
||||
|
||||
var matchParametersExactly =
|
||||
methodInfo.GetCustomAttributes(typeof(RequireParameterMatchAttribute), true).Any();
|
||||
|
||||
foreach (var commandAttribute in commandAttributes)
|
||||
{
|
||||
if (commandAttribute == null)
|
||||
continue;
|
||||
if (helpAttribute == null)
|
||||
{
|
||||
commands.Add(new Command(commandAttribute.Command, commandAttribute.Permission, parameters, forbidPm,
|
||||
methodInfo, this));
|
||||
commands.Add(new Command(commandAttribute.Command, commandAttribute.Permission, parameters,
|
||||
forbidPm, matchParametersExactly, methodInfo, this));
|
||||
}
|
||||
else
|
||||
{
|
||||
commands.Add(new Command(commandAttribute.Command, commandAttribute.Permission,
|
||||
helpAttribute.ShortHelp, helpAttribute.LongHelp, forbidPm, parameters, methodInfo, this));
|
||||
helpAttribute.ShortHelp, helpAttribute.LongHelp, parameters, forbidPm,
|
||||
matchParametersExactly, methodInfo, this));
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
@ -13,8 +13,8 @@ namespace DeukBot4.MessageHandlers.CommandHandler
|
|||
{
|
||||
public static class CommandHandler
|
||||
{
|
||||
public static Dictionary<string, Command> Commands { get; private set; } = new Dictionary<string, Command>();
|
||||
public const char CommandTrigger = '!';
|
||||
public static Dictionary<string, Command> Commands { get; } = new Dictionary<string, Command>();
|
||||
private const char CommandTrigger = '!';
|
||||
|
||||
public static void Build()
|
||||
{
|
||||
|
@ -31,7 +31,8 @@ namespace DeukBot4.MessageHandlers.CommandHandler
|
|||
Commands.Add(command.Name.ToLowerInvariant(), command);
|
||||
}
|
||||
|
||||
Logger.Log($"Loaded following commands for container {obj.Name}: {commands.Select(x => x.Name).Join(", ")}");
|
||||
Logger.Log(
|
||||
$"Loaded following commands for container {obj.Name}: {commands.Select(x => x.Name).Join(", ")}");
|
||||
|
||||
}
|
||||
|
||||
|
@ -42,34 +43,54 @@ namespace DeukBot4.MessageHandlers.CommandHandler
|
|||
if (string.IsNullOrWhiteSpace(message.Content))
|
||||
return;
|
||||
|
||||
if (message.Content[0] != CommandTrigger) return;
|
||||
if (message.Content[0] != CommandTrigger)
|
||||
return;
|
||||
|
||||
var req = await CommandRequest.Create(message);
|
||||
var resultCode = req.Item2;
|
||||
if (resultCode == CommandRequest.RequestCode.Invalid)
|
||||
switch (resultCode)
|
||||
{
|
||||
await Logger.LogError("Invalid content: " + message.Content);
|
||||
return;
|
||||
}
|
||||
else if (resultCode == CommandRequest.RequestCode.Forbidden)
|
||||
{
|
||||
await Logger.Log(
|
||||
$"Unauthorized user tried to run command: {message.Author.Username} -> {message.Content}");
|
||||
}
|
||||
|
||||
else if (resultCode == CommandRequest.RequestCode.OK)
|
||||
{
|
||||
if (!(message.Channel is IGuildChannel) && req.Item1.Command.ForbidInPm)
|
||||
{
|
||||
await Logger.Log(
|
||||
$"User is trying to use blocked command in PM: {message.Author.Username}");
|
||||
case CommandRequest.RequestCode.Invalid:
|
||||
await Logger.LogError("Invalid content: " + message.Content);
|
||||
return;
|
||||
}
|
||||
|
||||
await req.Item1.Command.Invoke(req.Item1);
|
||||
|
||||
case CommandRequest.RequestCode.Forbidden:
|
||||
await Logger.Log(
|
||||
$"Unauthorized user tried to run command: {message.Author.Username} -> {message.Content}");
|
||||
break;
|
||||
case CommandRequest.RequestCode.OK:
|
||||
if (!(message.Channel is IGuildChannel) && req.Item1.Command.ForbidInPm)
|
||||
{
|
||||
await Logger.Log(
|
||||
$"User is trying to use blocked command in PM: {message.Author.Username}");
|
||||
return;
|
||||
}
|
||||
await req.Item1.Command.Invoke(req.Item1);
|
||||
break;
|
||||
case CommandRequest.RequestCode.UnknownCommand:
|
||||
var similar = await GetSimilarCommand(req.Item3.ToString());
|
||||
await message.Channel.SendMessageAsync(
|
||||
$"Unknown command: ``{req.Item3.ToString()}``. Did you mean: ``{similar}``?");
|
||||
break;
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException();
|
||||
}
|
||||
}
|
||||
|
||||
private static async Task<string> GetSimilarCommand(string command)
|
||||
{
|
||||
var closestString = "";
|
||||
var similarity = int.MaxValue;
|
||||
foreach (var cmd in Commands)
|
||||
{
|
||||
var distance = Lehvenstein.LevenshteinDistance(command, cmd.Key);
|
||||
if (distance >= similarity)
|
||||
continue;
|
||||
similarity = distance;
|
||||
closestString = cmd.Key;
|
||||
}
|
||||
return closestString;
|
||||
}
|
||||
|
||||
public static Command GetCommand(string name)
|
||||
{
|
||||
return Commands.TryGetValue(name.ToLowerInvariant(), out var com) ? com : null;
|
||||
|
|
|
@ -55,8 +55,9 @@ usage:
|
|||
}
|
||||
else
|
||||
{
|
||||
await request.OriginalMessage.Channel.SendMessageAsync(
|
||||
HelpCommandGenerator.GenerateSpecificHelp(request.Parameters[0].AsString(), request.RequestPermissions));
|
||||
await request.OriginalMessage.Channel.SendMessageAsync("", embed:
|
||||
HelpCommandGenerator.GenerateSpecificHelp(request.Parameters[0].AsString(),
|
||||
request.RequestPermissions));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -12,29 +12,25 @@ namespace DeukBot4.MessageHandlers.CommandHandler
|
|||
|
||||
[Command("kick", PermissionLevel.Moderator)]
|
||||
[CommandParameters(ParameterMatcher.ParameterType.User, ParameterMatcher.ParameterType.Remainder)]
|
||||
[BlockUsageInPm]
|
||||
[CommandHelp("Kicks a user from the server",
|
||||
"Kicks a user from the server. Will not work on people with a helper role, or higher.\n" +
|
||||
"Usage: \n" +
|
||||
"``!kick {User Mention} {optional: Reason}``\n" +
|
||||
"``!kick {User ID} {optional: Reason}``")]
|
||||
[BlockUsageInPm, RequireParameterMatch]
|
||||
public async Task KickUser(CommandRequest request)
|
||||
{
|
||||
// get the server channel object out of message. Return if it's somehow not a server channel
|
||||
if (!(request.OriginalMessage.Channel is IGuildChannel channel))
|
||||
return;
|
||||
|
||||
// if no parameters are found, stop
|
||||
if (request.Parameters.Length == 0)
|
||||
return;
|
||||
// if the first parameter is empty, stop, this means it's not a valid user id
|
||||
if (string.IsNullOrWhiteSpace(request.Parameters[0].AsString()))
|
||||
return;
|
||||
|
||||
// get the id, this parses the string to an id
|
||||
var id = request.Parameters[0].AsUlong();
|
||||
// get the user using this id and the channel object.
|
||||
var user = await channel.Guild.GetUserAsync(id);
|
||||
// get the id of the user, this parses the string to an id
|
||||
var user = await request.Parameters[0].AsDiscordUser(channel.Guild);
|
||||
|
||||
// get the permissions of the user we want to kick
|
||||
var userPermissions =
|
||||
await PermissionValidator.GetUserPermissionLevel(request.OriginalMessage.Channel, (SocketUser) user);
|
||||
// if the user has sufficient permissions, or is this bot, warn the user that he's not allowed to do that, and stop
|
||||
// if the user has sufficient permissions, or is deukbot, warn the user that he's not allowed to do that, and stop
|
||||
if (userPermissions >= PermissionLevel.Helper || user.Id == Program.Client.CurrentUser.Id)
|
||||
{
|
||||
await request.OriginalMessage.Channel.SendMessageAsync("You are not allowed to kick that user");
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
using System.Collections.Generic;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
using DeukBot4.MessageHandlers.Permissions;
|
||||
using Discord;
|
||||
|
||||
namespace DeukBot4.MessageHandlers.CommandHandler
|
||||
{
|
||||
|
@ -35,16 +37,24 @@ namespace DeukBot4.MessageHandlers.CommandHandler
|
|||
return sb.ToString();
|
||||
}
|
||||
|
||||
public static string GenerateSpecificHelp(string command, PermissionLevel level)
|
||||
public static Embed GenerateSpecificHelp(string command, PermissionLevel level)
|
||||
{
|
||||
if (!CommandHandler.Commands.TryGetValue(command, out var cmd))
|
||||
if (!CommandHandler.Commands.TryGetValue(command.ToLowerInvariant(), out var cmd))
|
||||
return null;
|
||||
if (cmd.Permission > level)
|
||||
return null;
|
||||
if (!cmd.HasHelp)
|
||||
return null;
|
||||
|
||||
return $"**{cmd.Name}** - *{cmd.LongHelp}*";
|
||||
var eb = new EmbedBuilder
|
||||
{
|
||||
Title = cmd.Name,
|
||||
Description = cmd.LongHelp,
|
||||
Color = Color.Gold,
|
||||
};
|
||||
|
||||
|
||||
return eb.Build();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -28,22 +28,22 @@ namespace DeukBot4.MessageHandlers.CommandHandler.RequestStructure
|
|||
|
||||
public enum RequestCode
|
||||
{
|
||||
OK, Invalid, Forbidden
|
||||
OK, UnknownCommand, Invalid, Forbidden
|
||||
}
|
||||
|
||||
public static async Task<(CommandRequest, RequestCode)> Create(SocketMessage message)
|
||||
public static async Task<(CommandRequest, RequestCode, object)> Create(SocketMessage message)
|
||||
{
|
||||
var originalMessage = message;
|
||||
var content = message.Content;
|
||||
var res = CommandNameMatcher.Match(content);
|
||||
if (res.Groups.Count <= 2)
|
||||
return (null, RequestCode.Invalid);
|
||||
return (null, RequestCode.Invalid, null);
|
||||
|
||||
var commandName = res.Groups[1].Value;
|
||||
var command = CommandHandler.GetCommand(commandName);
|
||||
if (command == null)
|
||||
{
|
||||
return (null, RequestCode.Invalid);
|
||||
return (null, RequestCode.UnknownCommand, commandName);
|
||||
}
|
||||
|
||||
PermissionLevel permission;
|
||||
|
@ -54,15 +54,19 @@ namespace DeukBot4.MessageHandlers.CommandHandler.RequestStructure
|
|||
catch (Exception e)
|
||||
{
|
||||
await Logger.LogError(e.Message);
|
||||
return (null, RequestCode.Forbidden);
|
||||
return (null, RequestCode.Forbidden, null);
|
||||
}
|
||||
if (permission < command.Permission)
|
||||
{
|
||||
return (null, RequestCode.Forbidden);
|
||||
return (null, RequestCode.Forbidden, permission);
|
||||
}
|
||||
var parameterString = res.Groups[2].Value;
|
||||
var parameters = ParameterMatcher.GetParameterValues(command, parameterString);
|
||||
return (new CommandRequest(originalMessage, command, permission, parameters), RequestCode.OK);
|
||||
if (parameters == null)
|
||||
{
|
||||
return (null, RequestCode.Invalid, parameterString);
|
||||
}
|
||||
return (new CommandRequest(originalMessage, command, permission, parameters), RequestCode.OK, null);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -40,7 +40,7 @@ namespace DeukBot4.MessageHandlers.CommandHandler.RequestStructure
|
|||
case ParameterType.Remainder:
|
||||
return " *(.*)";
|
||||
case ParameterType.User:
|
||||
return " *(?:<@(?<id>\\d*)>)|(?<id>\\d*)";
|
||||
return " *<@(?<id>\\d+)>|(?<id>\\d+)";
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException(nameof(type), type, null);
|
||||
}
|
||||
|
@ -56,7 +56,8 @@ namespace DeukBot4.MessageHandlers.CommandHandler.RequestStructure
|
|||
return matches.Groups.Skip(1).Select(x => new RequestParameter(x.Value)).ToArray();
|
||||
}
|
||||
}
|
||||
return new RequestParameter[0];
|
||||
|
||||
return command.RequireParameterMatch ? null : new RequestParameter[0];
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,4 +1,7 @@
|
|||
using System;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Discord;
|
||||
|
||||
namespace DeukBot4.MessageHandlers.CommandHandler.RequestStructure
|
||||
{
|
||||
|
@ -33,5 +36,14 @@ namespace DeukBot4.MessageHandlers.CommandHandler.RequestStructure
|
|||
}
|
||||
throw new ArgumentException();
|
||||
}
|
||||
|
||||
public async Task<IGuildUser> AsDiscordUser(IGuild guild)
|
||||
{
|
||||
if (ulong.TryParse(_value, out var i))
|
||||
{
|
||||
return await guild.GetUserAsync(i);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -14,12 +14,23 @@ namespace DeukBot4.MessageHandlers
|
|||
}
|
||||
try
|
||||
{
|
||||
await CommandHandler.CommandHandler.HandleMessage(message);
|
||||
#pragma warning disable 4014
|
||||
CommandHandler.CommandHandler.HandleMessage(message);
|
||||
HandlePrivateMessage(message);
|
||||
#pragma warning restore 4014
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
await Logger.LogError(e.ToString());
|
||||
}
|
||||
}
|
||||
|
||||
private static async Task HandlePrivateMessage(SocketMessage message)
|
||||
{
|
||||
if (message.Channel is ISocketPrivateChannel)
|
||||
{
|
||||
await Logger.Log(($"Private Message: {message.Author.Username}- {message.Content}"));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,59 @@
|
|||
using System;
|
||||
|
||||
namespace DeukBot4.Utilities
|
||||
{
|
||||
public class Lehvenstein
|
||||
{
|
||||
/// <summary>
|
||||
/// Calculates the Levenshtein distance between two strings--the number of changes that need to be made for the first string to become the second.
|
||||
/// </summary>
|
||||
/// <param name="first">The first string, used as a source.</param>
|
||||
/// <param name="second">The second string, used as a target.</param>
|
||||
/// <returns>The number of changes that need to be made to convert the first string to the second.</returns>
|
||||
/// <remarks>
|
||||
/// From http://www.merriampark.com/ldcsharp.htm
|
||||
/// </remarks>
|
||||
public static int LevenshteinDistance(string first, string second)
|
||||
{
|
||||
if (first == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(first));
|
||||
}
|
||||
if (second == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(second));
|
||||
}
|
||||
|
||||
int n = first.Length;
|
||||
int m = second.Length;
|
||||
var d = new int[n + 1, m + 1]; // matrix
|
||||
|
||||
if (n == 0) return m;
|
||||
if (m == 0) return n;
|
||||
|
||||
for (int i = 0; i <= n; d[i, 0] = i++)
|
||||
{
|
||||
}
|
||||
|
||||
for (int j = 0; j <= m; d[0, j] = j++)
|
||||
{
|
||||
}
|
||||
|
||||
for (int i = 1; i <= n; i++)
|
||||
{
|
||||
|
||||
for (int j = 1; j <= m; j++)
|
||||
{
|
||||
int cost = (second.Substring(j - 1, 1) == first.Substring(i - 1, 1) ? 0 : 1); // cost
|
||||
d[i, j] = Math.Min(
|
||||
Math.Min(
|
||||
d[i - 1, j] + 1,
|
||||
d[i, j - 1] + 1),
|
||||
d[i - 1, j - 1] + cost);
|
||||
}
|
||||
}
|
||||
|
||||
return d[n, m];
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue