diff --git a/Module/ModLogs/EventType.cs b/Module/ModLogs/EventType.cs
new file mode 100644
index 0000000..c364bed
--- /dev/null
+++ b/Module/ModLogs/EventType.cs
@@ -0,0 +1,24 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+
+namespace Noikoio.RegexBot.Module.ModLogs
+{
+ // Types of non-custom events that can be referenced by ModLogs in configuration.
+ // Enum value names will show themselves to the user in the form of strings valid in configuration,
+ // so try not to change those without good reason.
+ [Flags]
+ enum EventType
+ {
+ None = 0x0,
+ Note = 0x1,
+ Warn = 0x2,
+ Kick = 0x4,
+ Ban = 0x8,
+ JoinGuild = 0x10,
+ LeaveGuild = 0x20,
+ NameChange = 0x40,
+ MsgEdit = 0x80,
+ MsgDelete = 0x100
+ }
+}
diff --git a/Module/ModLogs/GuildConfig.cs b/Module/ModLogs/GuildConfig.cs
new file mode 100644
index 0000000..f150c5a
--- /dev/null
+++ b/Module/ModLogs/GuildConfig.cs
@@ -0,0 +1,127 @@
+using Newtonsoft.Json.Linq;
+using Noikoio.RegexBot.ConfigItem;
+using System;
+
+namespace Noikoio.RegexBot.Module.ModLogs
+{
+ ///
+ /// ModLogs guild-specific configuration values.
+ ///
+ class GuildConfig
+ {
+ // Event reporting
+ private readonly EntityName _rptTarget;
+ private EventType _rptTypes;
+ ///
+ /// Target reporting channel.
+ ///
+ public EntityName? RptTarget => _rptTarget;
+ ///
+ /// Event types to send to the reporting channel.
+ ///
+ public EventType RptTypes => _rptTypes;
+
+ // Query command
+ private readonly string _qCmd; // command name
+ private readonly EntityList _qAccess; // list of those able to issue the command
+ private readonly EventType _qDefaultAnswer; // default entry types to display
+ ///
+ /// Query command. The first word in an incoming message, including prefix, that triggers a query.
+ ///
+ public string QrCommand => _qCmd;
+ ///
+ /// List of users permitted to invoke the query command.
+ /// If null, refer to the guild's Moderators list.
+ ///
+ public EntityList QrPermittedUsers => _qAccess;
+ ///
+ /// Event types to display in a query.
+ ///
+ public EventType QrTypes => _qDefaultAnswer;
+
+ public GuildConfig(JObject cfgRoot)
+ {
+ // AutoReporting settings
+ var arcfg = cfgRoot["AutoReporting"];
+ if (arcfg == null)
+ {
+ _rptTarget = default(EntityName); // NOTE: Change this if EntityName becomes a class later
+ _rptTypes = EventType.None;
+ }
+ else if (arcfg.Type == JTokenType.Object)
+ {
+ string chval = arcfg["Channel"]?.Value();
+ if (chval == null) throw new RuleImportException("Reporting channel is not defined.");
+ if (!string.IsNullOrWhiteSpace(chval) && chval[0] == '#')
+ _rptTarget = new EntityName(chval.Substring(1, chval.Length-1), EntityType.Channel);
+ else
+ throw new RuleImportException("Reporting channel is not properly defined.");
+ // Require the channel's ID for now.
+ if (!_rptTarget.Id.HasValue) throw new RuleImportException("Reporting channel's ID must be specified.");
+
+ // TODO make optional
+ string rpval = arcfg["Events"]?.Value();
+ _rptTypes = GetTypesFromString(rpval);
+ }
+ else
+ {
+ throw new RuleImportException("Section for AutoReporting is not correctly defined.");
+ }
+
+ // QueryCommand settings
+ var qccfg = cfgRoot["QueryCommand"];
+ if (qccfg == null)
+ {
+ _qCmd = null;
+ _qAccess = null;
+ _qDefaultAnswer = EventType.None;
+ }
+ else if (arcfg.Type == JTokenType.Object)
+ {
+ _qCmd = arcfg["Command"]?.Value();
+ if (string.IsNullOrWhiteSpace(_qCmd))
+ throw new RuleImportException("Query command option must have a value.");
+ if (_qCmd.Contains(" "))
+ throw new RuleImportException("Query command must not contain spaces.");
+
+ var acl = arcfg["AllowedUsers"];
+ if (acl == null) _qAccess = null;
+ else _qAccess = new EntityList(acl);
+
+ // TODO make optional
+ string ansval = arcfg["DefaultEvents"]?.Value();
+ _qDefaultAnswer = GetTypesFromString(ansval);
+ }
+ else
+ {
+ throw new RuleImportException("Section for QueryCommand is not correctly defined.");
+ }
+ }
+
+ public static EventType GetTypesFromString(string input)
+ {
+ if (string.IsNullOrWhiteSpace(input))
+ throw new RuleImportException("Types are not properly defined.");
+
+ var strTypes = input.Split(
+ new char[] { ' ', ',', '/', '+' }, // and more?
+ StringSplitOptions.RemoveEmptyEntries);
+
+ EventType endResult = EventType.None;
+ foreach (var item in strTypes)
+ {
+ try
+ {
+ var result = Enum.Parse(item, true);
+ endResult |= result;
+ }
+ catch (ArgumentException)
+ {
+ throw new RuleImportException($"Unable to determine the given event type \"{item}\"");
+ }
+ }
+
+ return endResult;
+ }
+ }
+}
diff --git a/Module/ModLogs/MessageCache.cs b/Module/ModLogs/MessageCache.cs
index 23cf58b..6ab2dfb 100644
--- a/Module/ModLogs/MessageCache.cs
+++ b/Module/ModLogs/MessageCache.cs
@@ -9,9 +9,9 @@ using System.Threading.Tasks;
namespace Noikoio.RegexBot.Module.ModLogs
{
///
- /// Helper class for . Keeps a database-backed cache of recent messages and assists
+ /// Helper class for . Keeps a database-backed cache of recent messages for use
/// in reporting message changes and deletions, if configured to do so.
- /// Does not manipulate the moderation log managed by the main class, but rather provides supplemental features.
+ /// Despite its place, it does not manipulate moderation logs. It simply pulls from the same configuration.
///
class MessageCache
{
@@ -66,6 +66,7 @@ namespace Noikoio.RegexBot.Module.ModLogs
private async Task Client_MessageDeleted(Cacheable msg, ISocketMessageChannel channel)
{
+ if (channel is IDMChannel) return; // No DMs
await ProcessReportMessage(true, msg.Id, channel, null);
}
#endregion
@@ -84,11 +85,13 @@ namespace Noikoio.RegexBot.Module.ModLogs
else return;
// Check if this feature is enabled before doing anything else.
- var rptTarget = _outGetConfig(guildId) as ConfigItem.EntityName?;
- if (!rptTarget.HasValue) return;
+ var cfg = _outGetConfig(guildId) as GuildConfig;
+ if (cfg == null) return;
+ if (isDelete && (cfg.RptTypes & EventType.MsgDelete) == 0) return;
+ if (!isDelete && (cfg.RptTypes & EventType.MsgEdit) == 0) return;
// Ignore if it's a message being deleted withing the reporting channel.
- if (isDelete && rptTarget.Value.Id.Value == ch.Id) return;
+ if (isDelete && cfg.RptTarget.Value.Id.Value == ch.Id) return;
// Regardless of delete or edit, it is necessary to get the equivalent database information.
EntityCache.CacheUser ucd = null;
@@ -128,11 +131,11 @@ namespace Noikoio.RegexBot.Module.ModLogs
}
// Find target channel, prepare and send out message
- var rptTargetChannel = _dClient.GetGuild(guildId)?.GetTextChannel(rptTarget.Value.Id.Value);
+ var g = _dClient.GetGuild(guildId);
+ var rptTargetChannel = g?.GetTextChannel(cfg.RptTarget.Value.Id.Value);
if (rptTargetChannel == null)
{
- await _outLog("Target channel not found.");
- // TODO make a more descriptive error message
+ await _outLog($"WARNING: Reporting channel {cfg.RptTarget.Value.ToString()} could not be determined.");
return;
}
var em = CreateReportEmbed(isDelete, ucd, messageId, ch, (cacheMsg, editMsg));
diff --git a/Module/ModLogs/ModLogs.cs b/Module/ModLogs/ModLogs.cs
index acfc8ff..4eeb3e4 100644
--- a/Module/ModLogs/ModLogs.cs
+++ b/Module/ModLogs/ModLogs.cs
@@ -23,9 +23,11 @@ namespace Noikoio.RegexBot.Module.ModLogs
// Do nothing if database unavailable. The user will be informed by ProcessConfiguration.
if (!RegexBot.Config.DatabaseAvailable) return;
+ // MessageCache (reporting of MessageEdit, MessageDelete) handled by helper class
_msgCacheInstance = new MessageCache(client, Log, GetConfig);
- //throw new NotImplementedException();
+ // TODO add handlers for detecting joins, leaves, bans, kicks, user edits (nick/username/discr)
+ // TODO add handler for processing the log query command
}
[ConfigSection("ModLogs")]
@@ -33,46 +35,14 @@ namespace Noikoio.RegexBot.Module.ModLogs
{
if (configSection.Type != JTokenType.Object)
throw new RuleImportException("Configuration for this section is invalid.");
- var conf = (JObject)configSection;
if (!RegexBot.Config.DatabaseAvailable)
{
await Log("Database access is not available. This module be unavailable.");
return null;
}
-
- try
- {
- // MessageCache testing: will store an EntityName or die trying
- EntityName? mctarget = new EntityName(conf["mctarget"].Value(), EntityType.Channel);
- await Log("Enabled MessageCache test on " + mctarget.Value.ToString());
- return mctarget;
- }
- catch (Exception)
- {
- // well, not really die
- return null;
- }
- /*
- * Concept:
- * "ModLogs": {
- * "AutoReporting": {
- * // behavior for how to output to the reporting channel
- * // MessageCache looks for configuration values within here.
- * "Channel": "something compatible with EntityName",
- * "Events": "perhaps a single string of separated event types"
- * },
- * "QueryOptions": {
- * // Behavior for the query command (which is defined here rather than ModTools)
- * // Need to stress in the documentation that "msgedit" and "msgdelete" events
- * // are not kept and cannot be queried
- * "QueryCommand": "!modlogs",
- * "Permission": "Moderators", // either a string that says "Moderators" or an EntityList
- * "DefaultQueryEvents": "another single string of separated event types",
- * }
- * }
- */
+ return new GuildConfig((JObject)configSection);
}
}
}
\ No newline at end of file