From bfb699d62f4817a3bfe2e94c76fbb3bed0b8b5f3 Mon Sep 17 00:00:00 2001 From: Noikoio Date: Fri, 9 Mar 2018 22:17:55 -0800 Subject: [PATCH] Reorganized and renamed ModTools to ModCommands --- .../CommandListener.cs} | 26 +++++---- .../Commands/BanKick.cs | 12 ++-- .../Commands/ConfReload.cs | 6 +- .../{ModTools => ModCommands}/Commands/Say.cs | 8 +-- .../Commands/Unban.cs | 8 +-- .../Commands/_CommandBase.cs} | 56 ++++++++++--------- .../{ModTools => ModCommands}/ConfigItem.cs | 24 ++++---- RegexBot.cs | 2 +- 8 files changed, 76 insertions(+), 66 deletions(-) rename Module/{ModTools/ModTools.cs => ModCommands/CommandListener.cs} (67%) rename Module/{ModTools => ModCommands}/Commands/BanKick.cs (95%) rename Module/{ModTools => ModCommands}/Commands/ConfReload.cs (79%) rename Module/{ModTools => ModCommands}/Commands/Say.cs (91%) rename Module/{ModTools => ModCommands}/Commands/Unban.cs (93%) rename Module/{ModTools/CommandBase.cs => ModCommands/Commands/_CommandBase.cs} (65%) rename Module/{ModTools => ModCommands}/ConfigItem.cs (62%) diff --git a/Module/ModTools/ModTools.cs b/Module/ModCommands/CommandListener.cs similarity index 67% rename from Module/ModTools/ModTools.cs rename to Module/ModCommands/CommandListener.cs index 231de46..884e31c 100644 --- a/Module/ModTools/ModTools.cs +++ b/Module/ModCommands/CommandListener.cs @@ -6,30 +6,35 @@ using System; using System.Linq; using System.Threading.Tasks; -namespace Noikoio.RegexBot.Module.ModTools +namespace Noikoio.RegexBot.Module.ModCommands { /// - /// ModTools module. /// This class manages reading configuration and creating instances based on it. + /// It processes input and looks for messages that intend to invoke commands defined in configuration. /// - class ModTools : BotModule + /// + /// Discord.Net has its own recommended way of implementing commands, but it's not exactly + /// done in a way that would easily allow for flexibility and modifications during runtime. + /// Thus, reinventing the wheel right here. + /// + class CommandListener : BotModule { - public override string Name => "ModTools"; + public override string Name => "ModCommands"; - public ModTools(DiscordSocketClient client) : base(client) + public CommandListener(DiscordSocketClient client) : base(client) { client.MessageReceived += Client_MessageReceived; } private async Task Client_MessageReceived(SocketMessage arg) { - // Always ignore bots - if (arg.Author.IsBot) return; + // Always ignore these + if (arg.Author.IsBot || arg.Author.IsWebhook) return; if (arg.Channel is IGuildChannel) await CommandCheckInvoke(arg); } - [ConfigSection("ModTools")] + [ConfigSection("ModCommands")] public override async Task ProcessConfiguration(JToken configSection) { // Constructor throws exception on config errors @@ -69,12 +74,13 @@ namespace Noikoio.RegexBot.Module.ModTools { try { - await Log($"'{c.Label}' invoked by {arg.Author.ToString()} in {g.Name}/#{arg.Channel.Name}"); await c.Invoke(g, arg); + // TODO Custom invocation log messages? Not by the user, but by the command. + await Log($"{g.Name}/#{arg.Channel.Name}: {arg.Author} invoked {arg.Content}"); } catch (Exception ex) { - await Log($"Encountered an error for the command '{c.Label}'. Details follow:"); + await Log($"Encountered an unhandled exception while processing '{c.Label}'. Details follow:"); await Log(ex.ToString()); } } diff --git a/Module/ModTools/Commands/BanKick.cs b/Module/ModCommands/Commands/BanKick.cs similarity index 95% rename from Module/ModTools/Commands/BanKick.cs rename to Module/ModCommands/Commands/BanKick.cs index 5302bf6..1b9de33 100644 --- a/Module/ModTools/Commands/BanKick.cs +++ b/Module/ModCommands/Commands/BanKick.cs @@ -7,9 +7,9 @@ using System.Linq; using System.Text.RegularExpressions; using System.Threading.Tasks; -namespace Noikoio.RegexBot.Module.ModTools.Commands +namespace Noikoio.RegexBot.Module.ModCommands.Commands { - class BanKick : CommandBase + class BanKick : Command { // Ban and kick commands are highly similar in implementation, and thus are handled in a single class. protected enum CommandMode { Ban, Kick } @@ -28,7 +28,7 @@ namespace Noikoio.RegexBot.Module.ModTools.Commands // "notifymsg" - Message to send to the target user being acted upon. Default message is used // if the value is not specified. If a blank value is given, the feature is disabled. // Takes the special values $s for server name and $r for reason text. - protected BanKick(ModTools l, string label, JObject conf, CommandMode mode) : base(l, label, conf) + protected BanKick(CommandListener l, string label, JObject conf, CommandMode mode) : base(l, label, conf) { _mode = mode; _forceReason = conf["forcereason"]?.Value() ?? false; @@ -190,7 +190,7 @@ namespace Noikoio.RegexBot.Module.ModTools.Commands private async Task SendUsageMessage(SocketMessage m, string message) { - string desc = $"{this.Command} [user or user ID] " + (_forceReason ? "[reason]" : "*[reason]*") + "\n"; + string desc = $"{this.Trigger} [user or user ID] " + (_forceReason ? "[reason]" : "*[reason]*") + "\n"; desc += "Removes the given user from this server and prevents the user from rejoining. "; desc += (_forceReason ? "L" : "Optionally l") + "ogs the reason for the ban to the Audit Log."; if (_purgeDays > 0) @@ -217,13 +217,13 @@ namespace Noikoio.RegexBot.Module.ModTools.Commands class Ban : BanKick { - public Ban(ModTools l, string label, JObject conf) + public Ban(CommandListener l, string label, JObject conf) : base(l, label, conf, CommandMode.Ban) { } } class Kick : BanKick { - public Kick(ModTools l, string label, JObject conf) + public Kick(CommandListener l, string label, JObject conf) : base(l, label, conf, CommandMode.Kick) { } } } diff --git a/Module/ModTools/Commands/ConfReload.cs b/Module/ModCommands/Commands/ConfReload.cs similarity index 79% rename from Module/ModTools/Commands/ConfReload.cs rename to Module/ModCommands/Commands/ConfReload.cs index 093f00b..22d66df 100644 --- a/Module/ModTools/Commands/ConfReload.cs +++ b/Module/ModCommands/Commands/ConfReload.cs @@ -2,12 +2,12 @@ using Newtonsoft.Json.Linq; using System.Threading.Tasks; -namespace Noikoio.RegexBot.Module.ModTools.Commands +namespace Noikoio.RegexBot.Module.ModCommands.Commands { - class ConfReload : CommandBase + class ConfReload : Command { // No configuration. - public ConfReload(ModTools l, string label, JObject conf) : base(l, label, conf) { } + public ConfReload(CommandListener l, string label, JObject conf) : base(l, label, conf) { } // Usage: (command) public override async Task Invoke(SocketGuild g, SocketMessage msg) diff --git a/Module/ModTools/Commands/Say.cs b/Module/ModCommands/Commands/Say.cs similarity index 91% rename from Module/ModTools/Commands/Say.cs rename to Module/ModCommands/Commands/Say.cs index ff6b3c3..c2a103e 100644 --- a/Module/ModTools/Commands/Say.cs +++ b/Module/ModCommands/Commands/Say.cs @@ -4,13 +4,13 @@ using Newtonsoft.Json.Linq; using System; using System.Threading.Tasks; -namespace Noikoio.RegexBot.Module.ModTools.Commands +namespace Noikoio.RegexBot.Module.ModCommands.Commands { - class Say : CommandBase + class Say : Command { // No configuration at the moment. // TODO: Whitelist/blacklist - to limit which channels it can "say" into - public Say(ModTools l, string label, JObject conf) : base(l, label, conf) { } + public Say(CommandListener l, string label, JObject conf) : base(l, label, conf) { } public override async Task Invoke(SocketGuild g, SocketMessage msg) { @@ -33,7 +33,7 @@ namespace Noikoio.RegexBot.Module.ModTools.Commands private async Task SendUsageMessage(SocketMessage m, string message) { - string desc = $"{this.Command} [channel] [message]\n"; + string desc = $"{this.Trigger} [channel] [message]\n"; desc += "Displays the given message exactly as specified to the given channel."; var usageEmbed = new EmbedBuilder() diff --git a/Module/ModTools/Commands/Unban.cs b/Module/ModCommands/Commands/Unban.cs similarity index 93% rename from Module/ModTools/Commands/Unban.cs rename to Module/ModCommands/Commands/Unban.cs index aa14484..e38cc86 100644 --- a/Module/ModTools/Commands/Unban.cs +++ b/Module/ModCommands/Commands/Unban.cs @@ -6,14 +6,14 @@ using System.Linq; using System.Text.RegularExpressions; using System.Threading.Tasks; -namespace Noikoio.RegexBot.Module.ModTools.Commands +namespace Noikoio.RegexBot.Module.ModCommands.Commands { - class Unban : CommandBase + class Unban : Command { // No configuration. // TODO bring in some options from BanKick. Particularly custom success msg. // TODO when ModLogs fully implemented, add a reason? - public Unban(ModTools l, string label, JObject conf) : base(l, label, conf) { } + public Unban(CommandListener l, string label, JObject conf) : base(l, label, conf) { } #region Strings const string FailPrefix = ":x: **Unable to unban:** "; @@ -93,7 +93,7 @@ namespace Noikoio.RegexBot.Module.ModTools.Commands private async Task SendUsageMessage(SocketMessage m, string message) { - string desc = $"{this.Command} [user or user ID]\n"; + string desc = $"{this.Trigger} [user or user ID]\n"; desc += "Unbans the given user, allowing them to rejoin the server."; var usageEmbed = new EmbedBuilder() diff --git a/Module/ModTools/CommandBase.cs b/Module/ModCommands/Commands/_CommandBase.cs similarity index 65% rename from Module/ModTools/CommandBase.cs rename to Module/ModCommands/Commands/_CommandBase.cs index c4e1699..3896365 100644 --- a/Module/ModTools/CommandBase.cs +++ b/Module/ModCommands/Commands/_CommandBase.cs @@ -9,24 +9,25 @@ using System.Reflection; using System.Text.RegularExpressions; using System.Threading.Tasks; -namespace Noikoio.RegexBot.Module.ModTools +namespace Noikoio.RegexBot.Module.ModCommands.Commands { /// - /// Base class for ModTools command. - /// We are not using Discord.Net's Commands extension, as it does not allow for changes during runtime. + /// Base class for a command within the module. + /// After implementing, don't forget to add a reference to + /// . /// - [DebuggerDisplay("{Label}-type command")] - abstract class CommandBase + [DebuggerDisplay("Command def: {Label}")] + abstract class Command { - private readonly ModTools _modtools; + private readonly CommandListener _modtools; private readonly string _label; private readonly string _command; - protected ModTools Mt => _modtools; + protected CommandListener Module => _modtools; public string Label => _label; - public string Command => _command; + public string Trigger => _command; - protected CommandBase(ModTools l, string label, JObject conf) + public Command(CommandListener l, string label, JObject conf) { _modtools = l; _label = label; @@ -46,14 +47,14 @@ namespace Noikoio.RegexBot.Module.ModTools new Dictionary(StringComparer.OrdinalIgnoreCase) { // Define all command types and their corresponding Types here - { "ban", typeof(Commands.Ban) }, - { "confreload", typeof(Commands.ConfReload) }, - { "kick", typeof(Commands.Kick) }, - { "say", typeof(Commands.Say) }, - { "unban", typeof(Commands.Unban) } + { "ban", typeof(Ban) }, + { "confreload", typeof(ConfReload) }, + { "kick", typeof(Kick) }, + { "say", typeof(Say) }, + { "unban", typeof(Unban) } }); - public static CommandBase CreateInstance(ModTools root, JProperty def) + public static Command CreateInstance(CommandListener root, JProperty def) { string label = def.Name; if (string.IsNullOrWhiteSpace(label)) throw new RuleImportException("Label cannot be blank."); @@ -68,24 +69,27 @@ namespace Noikoio.RegexBot.Module.ModTools string ctypestr = definition["type"]?.Value(); if (string.IsNullOrWhiteSpace(ctypestr)) throw new RuleImportException($"Value 'type' must be specified in definition for '{label}'."); - Type ctype; - if (!_commands.TryGetValue(ctypestr, out ctype)) - throw new RuleImportException($"The given 'type' value is invalid in definition for '{label}'."); - - try + if (_commands.TryGetValue(ctypestr, out Type ctype)) { - return (CommandBase)Activator.CreateInstance(ctype, root, label, definition); + try + { + return (Command)Activator.CreateInstance(ctype, root, label, definition); + } + catch (TargetInvocationException ex) + { + if (ex.InnerException is RuleImportException) + throw new RuleImportException($"Error in configuration for command '{label}': {ex.InnerException.Message}"); + else throw; + } } - catch (TargetInvocationException ex) + else { - if (ex.InnerException is RuleImportException) - throw new RuleImportException($"Error in configuration for command '{label}': {ex.InnerException.Message}"); - else throw; + throw new RuleImportException($"The given 'type' value is invalid in definition for '{label}'."); } } #endregion - #region Helper methods and values + #region Helper methods and common values protected static readonly Regex UserMention = new Regex(@"<@!?(?\d+)>", RegexOptions.Compiled); protected static readonly Regex RoleMention = new Regex(@"<@&(?\d+)>", RegexOptions.Compiled); protected static readonly Regex ChannelMention = new Regex(@"<#(?\d+)>", RegexOptions.Compiled); diff --git a/Module/ModTools/ConfigItem.cs b/Module/ModCommands/ConfigItem.cs similarity index 62% rename from Module/ModTools/ConfigItem.cs rename to Module/ModCommands/ConfigItem.cs index 5a29ac6..87c59c2 100644 --- a/Module/ModTools/ConfigItem.cs +++ b/Module/ModCommands/ConfigItem.cs @@ -1,21 +1,22 @@ using Newtonsoft.Json.Linq; using Noikoio.RegexBot.ConfigItem; +using Noikoio.RegexBot.Module.ModCommands.Commands; using System; using System.Collections.Generic; using System.Collections.ObjectModel; -namespace Noikoio.RegexBot.Module.ModTools +namespace Noikoio.RegexBot.Module.ModCommands { /// /// Represents ModTools configuration within one server. /// class ConfigItem { - private readonly ReadOnlyDictionary _cmdInstances; + private readonly ReadOnlyDictionary _cmdInstances; - public ReadOnlyDictionary Commands => _cmdInstances; + public ReadOnlyDictionary Commands => _cmdInstances; - public ConfigItem(ModTools instance, JToken inconf) + public ConfigItem(CommandListener instance, JToken inconf) { if (inconf.Type != JTokenType.Object) { @@ -23,9 +24,8 @@ namespace Noikoio.RegexBot.Module.ModTools } var config = (JObject)inconf; - - // Command instances - var commands = new Dictionary(StringComparer.OrdinalIgnoreCase); + // Command instance creation + var commands = new Dictionary(StringComparer.OrdinalIgnoreCase); var commandconf = config["Commands"]; if (commandconf != null) { @@ -37,16 +37,16 @@ namespace Noikoio.RegexBot.Module.ModTools foreach (var def in commandconf.Children()) { string label = def.Name; - var cmd = CommandBase.CreateInstance(instance, def); - if (commands.ContainsKey(cmd.Command)) + var cmd = Command.CreateInstance(instance, def); + if (commands.ContainsKey(cmd.Trigger)) throw new RuleImportException( $"{label}: 'command' value must not be equal to that of another definition. " + - $"Given value is being used for {commands[cmd.Command].Label}."); + $"Given value is being used for \"{commands[cmd.Trigger].Label}\"."); - commands.Add(cmd.Command, cmd); + commands.Add(cmd.Trigger, cmd); } } - _cmdInstances = new ReadOnlyDictionary(commands); + _cmdInstances = new ReadOnlyDictionary(commands); } } } diff --git a/RegexBot.cs b/RegexBot.cs index e4e8669..277ad5b 100644 --- a/RegexBot.cs +++ b/RegexBot.cs @@ -56,7 +56,7 @@ namespace Noikoio.RegexBot { new Module.DMLogger.DMLogger(_client), new Module.AutoMod.AutoMod(_client), - new Module.ModTools.ModTools(_client), + new Module.ModCommands.CommandListener(_client), new Module.AutoRespond.AutoRespond(_client), new EntityCache.Module(_client) // EntityCache goes before anything else that uses its data };