diff --git a/Module/ModTools/CommandBase.cs b/Module/ModTools/CommandBase.cs
index d63cdc1..9542ea4 100644
--- a/Module/ModTools/CommandBase.cs
+++ b/Module/ModTools/CommandBase.cs
@@ -11,13 +11,18 @@ using System.Threading.Tasks;
namespace Noikoio.RegexBot.Module.ModTools
{
+ ///
+ /// Base class for ModTools command.
+ /// We are not using Discord.Net's Commands extension, as it does not allow for changes during runtime.
+ ///
[DebuggerDisplay("{Label}-type command")]
abstract class CommandBase
{
private readonly ModTools _modtools;
private readonly string _label;
private readonly string _command;
-
+
+ protected ModTools Mt => _modtools;
public string Label => _label;
public string Command => _command;
diff --git a/Module/ModTools/Commands/BanKick.cs b/Module/ModTools/Commands/BanKick.cs
index 6e4538c..872b57b 100644
--- a/Module/ModTools/Commands/BanKick.cs
+++ b/Module/ModTools/Commands/BanKick.cs
@@ -8,7 +8,6 @@ using System.Threading.Tasks;
namespace Noikoio.RegexBot.Module.ModTools.Commands
{
-
class BanKick : CommandBase
{
// Ban and kick commands are highly similar in implementation, and thus are handled in a single class.
@@ -20,6 +19,11 @@ namespace Noikoio.RegexBot.Module.ModTools.Commands
private readonly string _successMsg;
private readonly string _notifyMsg;
+ const string DefaultMsg = "You have been {0} from $s for the following reason:\n$r";
+ const string DefaultMsgBanAppend = "\n\nIf the moderators have allowed it, you may petition your ban by" +
+ " submitting **one** message to the moderation team. To do so, reply to this message with" +
+ " `!petition [Your message here]`.";
+
// Configuration:
// "forcereason" - boolean; Force a reason to be given. Defaults to false.
// "purgedays" - integer; Number of days of target's post history to delete, if banning.
@@ -41,8 +45,8 @@ namespace Noikoio.RegexBot.Module.ModTools.Commands
if (conf["notifymsg"] == null)
{
// Message not specified - use default
- string act = _mode == CommandMode.Ban ? "banned" : "kicked";
- _notifyMsg = $"You have been {act} from $s for the following reason:\n$r";
+ _notifyMsg = string.Format(DefaultMsg, mode == CommandMode.Ban ? "banned" : "kicked");
+ if (_mode == CommandMode.Ban) _notifyMsg += DefaultMsgBanAppend;
}
else
{
@@ -127,6 +131,9 @@ namespace Noikoio.RegexBot.Module.ModTools.Commands
}
else notifyfail = true;
+ // Give target user ability to petition
+ if (_mode == CommandMode.Ban) Mt.AddPetition(g.Id, targetuid);
+
// Do the action
try
{
@@ -134,8 +141,12 @@ namespace Noikoio.RegexBot.Module.ModTools.Commands
if (reason != null) reasonlog += $" Reason: {reason}";
reasonlog = Uri.EscapeDataString(reasonlog);
#warning Remove EscapeDataString call on next Discord.Net update
+#if !DEBUG
if (_mode == CommandMode.Ban) await g.AddBanAsync(targetuid, _purgeDays, reasonlog);
else await targetobj.KickAsync(reason);
+#else
+#warning "Actual kick/ban action is DISABLED during debug."
+#endif
string resultmsg = BuildSuccessMessage(targetdisp);
if (notifyfail)
{
diff --git a/Module/ModTools/ConfigItem.cs b/Module/ModTools/ConfigItem.cs
new file mode 100644
index 0000000..a135a3f
--- /dev/null
+++ b/Module/ModTools/ConfigItem.cs
@@ -0,0 +1,75 @@
+using Newtonsoft.Json.Linq;
+using Noikoio.RegexBot.ConfigItem;
+using System;
+using System.Collections.Generic;
+using System.Collections.ObjectModel;
+
+namespace Noikoio.RegexBot.Module.ModTools
+{
+ ///
+ /// Represents ModTools configuration within one server.
+ ///
+ class ConfigItem
+ {
+ private EntityName? _petitionReportCh;
+ private readonly ReadOnlyDictionary _cmdInstances;
+
+ public EntityName? PetitionReportingChannel => _petitionReportCh;
+ public ReadOnlyDictionary Commands => _cmdInstances;
+
+ public ConfigItem(ModTools instance, JToken inconf)
+ {
+ if (inconf.Type != JTokenType.Object)
+ {
+ throw new RuleImportException("Configuration for this section is invalid.");
+ }
+ var config = (JObject)inconf;
+
+ // Ban petition reporting channel
+ var petitionstr = config["PetitionRelay"]?.Value();
+ if (string.IsNullOrEmpty(petitionstr)) _petitionReportCh = null;
+ else if (petitionstr.Length > 1 && petitionstr[0] != '#')
+ {
+ // Not a channel.
+ throw new RuleImportException("PetitionRelay value must be set to a channel.");
+ }
+ else
+ {
+ _petitionReportCh = new EntityName(petitionstr.Substring(1), EntityType.Channel);
+ }
+
+ // Command instances
+ var commands = new Dictionary(StringComparer.OrdinalIgnoreCase);
+ var commandconf = config["Commands"];
+ if (commandconf != null)
+ {
+ if (commandconf.Type != JTokenType.Object)
+ {
+ throw new RuleImportException("CommandDefs is not properly defined.");
+ }
+
+ foreach (var def in commandconf.Children())
+ {
+ string label = def.Name;
+ var cmd = CommandBase.CreateInstance(instance, def);
+ if (commands.ContainsKey(cmd.Command))
+ 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}.");
+
+ commands.Add(cmd.Command, cmd);
+ }
+ }
+ _cmdInstances = new ReadOnlyDictionary(commands);
+ }
+
+ public void UpdatePetitionChannel(ulong id)
+ {
+ if (!PetitionReportingChannel.HasValue) return;
+ if (PetitionReportingChannel.Value.Id.HasValue) return; // nothing to update
+
+ // For lack of a better option - create a new EntityName with ID already provided
+ _petitionReportCh = new EntityName($"{id}::{PetitionReportingChannel.Value.Name}", EntityType.Channel);
+ }
+ }
+}
diff --git a/Module/ModTools/ModTools.cs b/Module/ModTools/ModTools.cs
index 1c1b17e..ff62bae 100644
--- a/Module/ModTools/ModTools.cs
+++ b/Module/ModTools/ModTools.cs
@@ -1,19 +1,18 @@
-using Discord.WebSocket;
+using Discord;
+using Discord.WebSocket;
using Newtonsoft.Json.Linq;
using Noikoio.RegexBot.ConfigItem;
using System;
using System.Collections.Generic;
-using System.Collections.ObjectModel;
using System.Linq;
using System.Threading.Tasks;
namespace Noikoio.RegexBot.Module.ModTools
{
///
- /// ModTools module object.
- /// Implements moderation commands that are individually defined and enabled in configuration.
+ /// ModTools module.
+ /// This class manages reading configuration and creating instances based on it.
///
- // We are not using Discord.Net's Commands extension, as it does not allow for changes during runtime.
class ModTools : BotModule
{
public override string Name => "ModTools";
@@ -25,12 +24,35 @@ namespace Noikoio.RegexBot.Module.ModTools
private async Task Client_MessageReceived(SocketMessage arg)
{
- // Ignore bots
+ // Always ignore bots
if (arg.Author.IsBot) return;
- // Disregard if not in a guild
- SocketGuild g = (arg.Author as SocketGuildUser)?.Guild;
- if (g == null) return;
+ if (arg.Channel is IDMChannel) await PetitionRelayCheck(arg);
+ else if (arg.Channel is IGuildChannel) await CommandCheckInvoke(arg);
+ }
+
+ [ConfigSection("modtools")]
+ public override async Task