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