Added proper MessageCache configuration

Though it's now capable of loading configuration for the full
ModLogs module, not all features are available yet.
This commit is contained in:
Noikoio 2018-03-01 19:25:08 -08:00
parent 419370c379
commit 6ca73a9b6b
4 changed files with 166 additions and 42 deletions

View file

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

View file

@ -0,0 +1,127 @@
using Newtonsoft.Json.Linq;
using Noikoio.RegexBot.ConfigItem;
using System;
namespace Noikoio.RegexBot.Module.ModLogs
{
/// <summary>
/// ModLogs guild-specific configuration values.
/// </summary>
class GuildConfig
{
// Event reporting
private readonly EntityName _rptTarget;
private EventType _rptTypes;
/// <summary>
/// Target reporting channel.
/// </summary>
public EntityName? RptTarget => _rptTarget;
/// <summary>
/// Event types to send to the reporting channel.
/// </summary>
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
/// <summary>
/// Query command. The first word in an incoming message, including prefix, that triggers a query.
/// </summary>
public string QrCommand => _qCmd;
/// <summary>
/// List of users permitted to invoke the query command.
/// If null, refer to the guild's Moderators list.
/// </summary>
public EntityList QrPermittedUsers => _qAccess;
/// <summary>
/// Event types to display in a query.
/// </summary>
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<string>();
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<string>();
_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<string>();
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<string>();
_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<EventType>(item, true);
endResult |= result;
}
catch (ArgumentException)
{
throw new RuleImportException($"Unable to determine the given event type \"{item}\"");
}
}
return endResult;
}
}
}

View file

@ -9,9 +9,9 @@ using System.Threading.Tasks;
namespace Noikoio.RegexBot.Module.ModLogs
{
/// <summary>
/// Helper class for <see cref="ModLogs"/>. Keeps a database-backed cache of recent messages and assists
/// Helper class for <see cref="ModLogs"/>. 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.
/// </summary>
class MessageCache
{
@ -66,6 +66,7 @@ namespace Noikoio.RegexBot.Module.ModLogs
private async Task Client_MessageDeleted(Cacheable<Discord.IMessage, ulong> 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));

View file

@ -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<string>(), 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);
}
}
}