Reorganized and renamed ModTools to ModCommands
This commit is contained in:
parent
06583a1472
commit
bfb699d62f
8 changed files with 76 additions and 66 deletions
|
@ -6,30 +6,35 @@ using System;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
namespace Noikoio.RegexBot.Module.ModTools
|
namespace Noikoio.RegexBot.Module.ModCommands
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// ModTools module.
|
|
||||||
/// This class manages reading configuration and creating instances based on it.
|
/// 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.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
class ModTools : BotModule
|
/// <remarks>
|
||||||
|
/// 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.
|
||||||
|
/// </remarks>
|
||||||
|
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;
|
client.MessageReceived += Client_MessageReceived;
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task Client_MessageReceived(SocketMessage arg)
|
private async Task Client_MessageReceived(SocketMessage arg)
|
||||||
{
|
{
|
||||||
// Always ignore bots
|
// Always ignore these
|
||||||
if (arg.Author.IsBot) return;
|
if (arg.Author.IsBot || arg.Author.IsWebhook) return;
|
||||||
|
|
||||||
if (arg.Channel is IGuildChannel) await CommandCheckInvoke(arg);
|
if (arg.Channel is IGuildChannel) await CommandCheckInvoke(arg);
|
||||||
}
|
}
|
||||||
|
|
||||||
[ConfigSection("ModTools")]
|
[ConfigSection("ModCommands")]
|
||||||
public override async Task<object> ProcessConfiguration(JToken configSection)
|
public override async Task<object> ProcessConfiguration(JToken configSection)
|
||||||
{
|
{
|
||||||
// Constructor throws exception on config errors
|
// Constructor throws exception on config errors
|
||||||
|
@ -69,12 +74,13 @@ namespace Noikoio.RegexBot.Module.ModTools
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
await Log($"'{c.Label}' invoked by {arg.Author.ToString()} in {g.Name}/#{arg.Channel.Name}");
|
|
||||||
await c.Invoke(g, arg);
|
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)
|
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());
|
await Log(ex.ToString());
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -7,9 +7,9 @@ using System.Linq;
|
||||||
using System.Text.RegularExpressions;
|
using System.Text.RegularExpressions;
|
||||||
using System.Threading.Tasks;
|
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.
|
// Ban and kick commands are highly similar in implementation, and thus are handled in a single class.
|
||||||
protected enum CommandMode { Ban, Kick }
|
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
|
// "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.
|
// 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.
|
// 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;
|
_mode = mode;
|
||||||
_forceReason = conf["forcereason"]?.Value<bool>() ?? false;
|
_forceReason = conf["forcereason"]?.Value<bool>() ?? false;
|
||||||
|
@ -190,7 +190,7 @@ namespace Noikoio.RegexBot.Module.ModTools.Commands
|
||||||
|
|
||||||
private async Task SendUsageMessage(SocketMessage m, string message)
|
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 += "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.";
|
desc += (_forceReason ? "L" : "Optionally l") + "ogs the reason for the ban to the Audit Log.";
|
||||||
if (_purgeDays > 0)
|
if (_purgeDays > 0)
|
||||||
|
@ -217,13 +217,13 @@ namespace Noikoio.RegexBot.Module.ModTools.Commands
|
||||||
|
|
||||||
class Ban : BanKick
|
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) { }
|
: base(l, label, conf, CommandMode.Ban) { }
|
||||||
}
|
}
|
||||||
|
|
||||||
class Kick : BanKick
|
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) { }
|
: base(l, label, conf, CommandMode.Kick) { }
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -2,12 +2,12 @@
|
||||||
using Newtonsoft.Json.Linq;
|
using Newtonsoft.Json.Linq;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
namespace Noikoio.RegexBot.Module.ModTools.Commands
|
namespace Noikoio.RegexBot.Module.ModCommands.Commands
|
||||||
{
|
{
|
||||||
class ConfReload : CommandBase
|
class ConfReload : Command
|
||||||
{
|
{
|
||||||
// No configuration.
|
// 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)
|
// Usage: (command)
|
||||||
public override async Task Invoke(SocketGuild g, SocketMessage msg)
|
public override async Task Invoke(SocketGuild g, SocketMessage msg)
|
|
@ -4,13 +4,13 @@ using Newtonsoft.Json.Linq;
|
||||||
using System;
|
using System;
|
||||||
using System.Threading.Tasks;
|
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.
|
// No configuration at the moment.
|
||||||
// TODO: Whitelist/blacklist - to limit which channels it can "say" into
|
// 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)
|
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)
|
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.";
|
desc += "Displays the given message exactly as specified to the given channel.";
|
||||||
|
|
||||||
var usageEmbed = new EmbedBuilder()
|
var usageEmbed = new EmbedBuilder()
|
|
@ -6,14 +6,14 @@ using System.Linq;
|
||||||
using System.Text.RegularExpressions;
|
using System.Text.RegularExpressions;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
namespace Noikoio.RegexBot.Module.ModTools.Commands
|
namespace Noikoio.RegexBot.Module.ModCommands.Commands
|
||||||
{
|
{
|
||||||
class Unban : CommandBase
|
class Unban : Command
|
||||||
{
|
{
|
||||||
// No configuration.
|
// No configuration.
|
||||||
// TODO bring in some options from BanKick. Particularly custom success msg.
|
// TODO bring in some options from BanKick. Particularly custom success msg.
|
||||||
// TODO when ModLogs fully implemented, add a reason?
|
// 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
|
#region Strings
|
||||||
const string FailPrefix = ":x: **Unable to unban:** ";
|
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)
|
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.";
|
desc += "Unbans the given user, allowing them to rejoin the server.";
|
||||||
|
|
||||||
var usageEmbed = new EmbedBuilder()
|
var usageEmbed = new EmbedBuilder()
|
|
@ -9,24 +9,25 @@ using System.Reflection;
|
||||||
using System.Text.RegularExpressions;
|
using System.Text.RegularExpressions;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
namespace Noikoio.RegexBot.Module.ModTools
|
namespace Noikoio.RegexBot.Module.ModCommands.Commands
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Base class for ModTools command.
|
/// Base class for a command within the module.
|
||||||
/// We are not using Discord.Net's Commands extension, as it does not allow for changes during runtime.
|
/// After implementing, don't forget to add a reference to
|
||||||
|
/// <see cref="CreateInstance(CommandListener, JProperty)"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[DebuggerDisplay("{Label}-type command")]
|
[DebuggerDisplay("Command def: {Label}")]
|
||||||
abstract class CommandBase
|
abstract class Command
|
||||||
{
|
{
|
||||||
private readonly ModTools _modtools;
|
private readonly CommandListener _modtools;
|
||||||
private readonly string _label;
|
private readonly string _label;
|
||||||
private readonly string _command;
|
private readonly string _command;
|
||||||
|
|
||||||
protected ModTools Mt => _modtools;
|
protected CommandListener Module => _modtools;
|
||||||
public string Label => _label;
|
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;
|
_modtools = l;
|
||||||
_label = label;
|
_label = label;
|
||||||
|
@ -46,14 +47,14 @@ namespace Noikoio.RegexBot.Module.ModTools
|
||||||
new Dictionary<string, Type>(StringComparer.OrdinalIgnoreCase)
|
new Dictionary<string, Type>(StringComparer.OrdinalIgnoreCase)
|
||||||
{
|
{
|
||||||
// Define all command types and their corresponding Types here
|
// Define all command types and their corresponding Types here
|
||||||
{ "ban", typeof(Commands.Ban) },
|
{ "ban", typeof(Ban) },
|
||||||
{ "confreload", typeof(Commands.ConfReload) },
|
{ "confreload", typeof(ConfReload) },
|
||||||
{ "kick", typeof(Commands.Kick) },
|
{ "kick", typeof(Kick) },
|
||||||
{ "say", typeof(Commands.Say) },
|
{ "say", typeof(Say) },
|
||||||
{ "unban", typeof(Commands.Unban) }
|
{ "unban", typeof(Unban) }
|
||||||
});
|
});
|
||||||
|
|
||||||
public static CommandBase CreateInstance(ModTools root, JProperty def)
|
public static Command CreateInstance(CommandListener root, JProperty def)
|
||||||
{
|
{
|
||||||
string label = def.Name;
|
string label = def.Name;
|
||||||
if (string.IsNullOrWhiteSpace(label)) throw new RuleImportException("Label cannot be blank.");
|
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<string>();
|
string ctypestr = definition["type"]?.Value<string>();
|
||||||
if (string.IsNullOrWhiteSpace(ctypestr))
|
if (string.IsNullOrWhiteSpace(ctypestr))
|
||||||
throw new RuleImportException($"Value 'type' must be specified in definition for '{label}'.");
|
throw new RuleImportException($"Value 'type' must be specified in definition for '{label}'.");
|
||||||
Type ctype;
|
if (_commands.TryGetValue(ctypestr, out Type ctype))
|
||||||
if (!_commands.TryGetValue(ctypestr, out ctype))
|
|
||||||
throw new RuleImportException($"The given 'type' value is invalid in definition for '{label}'.");
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
{
|
||||||
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($"The given 'type' value is invalid in definition for '{label}'.");
|
||||||
throw new RuleImportException($"Error in configuration for command '{label}': {ex.InnerException.Message}");
|
|
||||||
else throw;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
#region Helper methods and values
|
#region Helper methods and common values
|
||||||
protected static readonly Regex UserMention = new Regex(@"<@!?(?<snowflake>\d+)>", RegexOptions.Compiled);
|
protected static readonly Regex UserMention = new Regex(@"<@!?(?<snowflake>\d+)>", RegexOptions.Compiled);
|
||||||
protected static readonly Regex RoleMention = new Regex(@"<@&(?<snowflake>\d+)>", RegexOptions.Compiled);
|
protected static readonly Regex RoleMention = new Regex(@"<@&(?<snowflake>\d+)>", RegexOptions.Compiled);
|
||||||
protected static readonly Regex ChannelMention = new Regex(@"<#(?<snowflake>\d+)>", RegexOptions.Compiled);
|
protected static readonly Regex ChannelMention = new Regex(@"<#(?<snowflake>\d+)>", RegexOptions.Compiled);
|
|
@ -1,21 +1,22 @@
|
||||||
using Newtonsoft.Json.Linq;
|
using Newtonsoft.Json.Linq;
|
||||||
using Noikoio.RegexBot.ConfigItem;
|
using Noikoio.RegexBot.ConfigItem;
|
||||||
|
using Noikoio.RegexBot.Module.ModCommands.Commands;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Collections.ObjectModel;
|
using System.Collections.ObjectModel;
|
||||||
|
|
||||||
namespace Noikoio.RegexBot.Module.ModTools
|
namespace Noikoio.RegexBot.Module.ModCommands
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Represents ModTools configuration within one server.
|
/// Represents ModTools configuration within one server.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
class ConfigItem
|
class ConfigItem
|
||||||
{
|
{
|
||||||
private readonly ReadOnlyDictionary<string, CommandBase> _cmdInstances;
|
private readonly ReadOnlyDictionary<string, Command> _cmdInstances;
|
||||||
|
|
||||||
public ReadOnlyDictionary<string, CommandBase> Commands => _cmdInstances;
|
public ReadOnlyDictionary<string, Command> Commands => _cmdInstances;
|
||||||
|
|
||||||
public ConfigItem(ModTools instance, JToken inconf)
|
public ConfigItem(CommandListener instance, JToken inconf)
|
||||||
{
|
{
|
||||||
if (inconf.Type != JTokenType.Object)
|
if (inconf.Type != JTokenType.Object)
|
||||||
{
|
{
|
||||||
|
@ -23,9 +24,8 @@ namespace Noikoio.RegexBot.Module.ModTools
|
||||||
}
|
}
|
||||||
var config = (JObject)inconf;
|
var config = (JObject)inconf;
|
||||||
|
|
||||||
|
// Command instance creation
|
||||||
// Command instances
|
var commands = new Dictionary<string, Command>(StringComparer.OrdinalIgnoreCase);
|
||||||
var commands = new Dictionary<string, CommandBase>(StringComparer.OrdinalIgnoreCase);
|
|
||||||
var commandconf = config["Commands"];
|
var commandconf = config["Commands"];
|
||||||
if (commandconf != null)
|
if (commandconf != null)
|
||||||
{
|
{
|
||||||
|
@ -37,16 +37,16 @@ namespace Noikoio.RegexBot.Module.ModTools
|
||||||
foreach (var def in commandconf.Children<JProperty>())
|
foreach (var def in commandconf.Children<JProperty>())
|
||||||
{
|
{
|
||||||
string label = def.Name;
|
string label = def.Name;
|
||||||
var cmd = CommandBase.CreateInstance(instance, def);
|
var cmd = Command.CreateInstance(instance, def);
|
||||||
if (commands.ContainsKey(cmd.Command))
|
if (commands.ContainsKey(cmd.Trigger))
|
||||||
throw new RuleImportException(
|
throw new RuleImportException(
|
||||||
$"{label}: 'command' value must not be equal to that of another definition. " +
|
$"{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<string, CommandBase>(commands);
|
_cmdInstances = new ReadOnlyDictionary<string, Command>(commands);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -56,7 +56,7 @@ namespace Noikoio.RegexBot
|
||||||
{
|
{
|
||||||
new Module.DMLogger.DMLogger(_client),
|
new Module.DMLogger.DMLogger(_client),
|
||||||
new Module.AutoMod.AutoMod(_client),
|
new Module.AutoMod.AutoMod(_client),
|
||||||
new Module.ModTools.ModTools(_client),
|
new Module.ModCommands.CommandListener(_client),
|
||||||
new Module.AutoRespond.AutoRespond(_client),
|
new Module.AutoRespond.AutoRespond(_client),
|
||||||
new EntityCache.Module(_client) // EntityCache goes before anything else that uses its data
|
new EntityCache.Module(_client) // EntityCache goes before anything else that uses its data
|
||||||
};
|
};
|
||||||
|
|
Loading…
Reference in a new issue