diff --git a/Module/ModLogs/EventType.cs b/Module/ModLogs/EventType.cs
deleted file mode 100644
index 6a0a964..0000000
--- a/Module/ModLogs/EventType.cs
+++ /dev/null
@@ -1,22 +0,0 @@
-using System;
-
-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/GuildState.cs
similarity index 72%
rename from Module/ModLogs/GuildConfig.cs
rename to Module/ModLogs/GuildState.cs
index f150c5a..313bf0f 100644
--- a/Module/ModLogs/GuildConfig.cs
+++ b/Module/ModLogs/GuildState.cs
@@ -5,13 +5,13 @@ using System;
namespace Noikoio.RegexBot.Module.ModLogs
{
///
- /// ModLogs guild-specific configuration values.
+ /// ModLogs guild-specific values.
///
- class GuildConfig
+ class GuildState
{
// Event reporting
private readonly EntityName _rptTarget;
- private EventType _rptTypes;
+ private LogEntry.LogType _rptTypes;
///
/// Target reporting channel.
///
@@ -19,12 +19,12 @@ namespace Noikoio.RegexBot.Module.ModLogs
///
/// Event types to send to the reporting channel.
///
- public EventType RptTypes => _rptTypes;
+ public LogEntry.LogType 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
+ private readonly LogEntry.LogType _qDefaultAnswer; // default entry types to display
///
/// Query command. The first word in an incoming message, including prefix, that triggers a query.
///
@@ -37,16 +37,16 @@ namespace Noikoio.RegexBot.Module.ModLogs
///
/// Event types to display in a query.
///
- public EventType QrTypes => _qDefaultAnswer;
+ public LogEntry.LogType QrTypes => _qDefaultAnswer;
- public GuildConfig(JObject cfgRoot)
+ public GuildState(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;
+ _rptTypes = LogEntry.LogType.None;
}
else if (arcfg.Type == JTokenType.Object)
{
@@ -61,7 +61,14 @@ namespace Noikoio.RegexBot.Module.ModLogs
// TODO make optional
string rpval = arcfg["Events"]?.Value();
- _rptTypes = GetTypesFromString(rpval);
+ try
+ {
+ _rptTypes = LogEntry.GetLogTypeFromString(rpval);
+ }
+ catch (ArgumentException ex)
+ {
+ throw new RuleImportException(ex.Message);
+ }
}
else
{
@@ -74,7 +81,7 @@ namespace Noikoio.RegexBot.Module.ModLogs
{
_qCmd = null;
_qAccess = null;
- _qDefaultAnswer = EventType.None;
+ _qDefaultAnswer = LogEntry.LogType.None;
}
else if (arcfg.Type == JTokenType.Object)
{
@@ -90,38 +97,20 @@ namespace Noikoio.RegexBot.Module.ModLogs
// TODO make optional
string ansval = arcfg["DefaultEvents"]?.Value();
- _qDefaultAnswer = GetTypesFromString(ansval);
+ try
+ {
+ _qDefaultAnswer = LogEntry.GetLogTypeFromString(ansval);
+ }
+ catch (ArgumentException ex)
+ {
+ throw new RuleImportException(ex.Message);
+ }
+
}
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/Entry.cs b/Module/ModLogs/LogEntry.cs
similarity index 53%
rename from Module/ModLogs/Entry.cs
rename to Module/ModLogs/LogEntry.cs
index d09e1e3..4e96364 100644
--- a/Module/ModLogs/Entry.cs
+++ b/Module/ModLogs/LogEntry.cs
@@ -1,8 +1,6 @@
-using Npgsql;
-using System;
+using System;
using System.Collections.Generic;
using System.Data.Common;
-using System.Text;
using System.Threading.Tasks;
namespace Noikoio.RegexBot.Module.ModLogs
@@ -10,7 +8,7 @@ namespace Noikoio.RegexBot.Module.ModLogs
///
/// Represents a log entry in the database.
///
- class Entry
+ class LogEntry
{
readonly int _logId;
readonly DateTime _ts;
@@ -18,7 +16,7 @@ namespace Noikoio.RegexBot.Module.ModLogs
readonly ulong? _invokeId;
readonly ulong _targetId;
readonly ulong? _channelId;
- readonly string _type;
+ readonly LogType _type;
readonly string _message;
///
@@ -47,15 +45,15 @@ namespace Noikoio.RegexBot.Module.ModLogs
///
public ulong? TargetChannel => _channelId;
///
- /// Gets this log entry's category.
+ /// Gets this log entry's type.
///
- public string Category => _type;
+ public LogType Type => _type;
///
/// Gets the content of this log entry.
///
public string Message => _message;
- public Entry(DbDataReader r)
+ public LogEntry(DbDataReader r)
{
// Double-check ordinals if making changes to QueryColumns
@@ -70,7 +68,7 @@ namespace Noikoio.RegexBot.Module.ModLogs
if (r.IsDBNull(5)) _channelId = null;
else _channelId = (ulong)r.GetInt64(5);
}
- _type = r.GetString(6);
+ _type = (LogType)r.GetInt32(6);
_message = r.GetString(7);
}
@@ -79,40 +77,128 @@ namespace Noikoio.RegexBot.Module.ModLogs
// TODO figure out some helper methods to retrieve data of other entities by ID, if it becomes necessary
- #region Queries
+ #region Log entry types
+ ///
+ /// Enumeration of all possible event flags. Names will show themselves to users
+ /// and associated values will be saved to the databaase.
+ /// Once they're included in a release build, they should never be changed again.
+ ///
+ [Flags]
+ public enum LogType
+ {
+ /// Should only be useful in GuildState and ignored elsewhere.
+ None = 0x0,
+ Note = 0x1,
+ Warn = 0x2,
+ Kick = 0x4,
+ Ban = 0x8,
+ /// Record of a user joining a guild.
+ JoinGuild = 0x10,
+ /// Record of a user leaving a guild, voluntarily or by force (kick, ban).
+ LeaveGuild = 0x20,
+ NameChange = 0x40,
+ /// Not a database entry, but exists for MessageCache configuration.
+ MsgEdit = 0x80,
+ /// Not a database entry, but exists for MessageCache configuration.
+ MsgDelete = 0x100
+ }
+
+ public static LogType GetLogTypeFromString(string input)
+ {
+ if (string.IsNullOrWhiteSpace(input))
+ throw new ArgumentException("Types are not properly defined.");
+
+ var strTypes = input.Split(
+ new char[] { ' ', ',', '/', '+' }, // and more?
+ StringSplitOptions.RemoveEmptyEntries);
+
+ LogType endResult = LogType.None;
+ foreach (var item in strTypes)
+ {
+ try
+ {
+ var result = Enum.Parse(item, true);
+ endResult |= result;
+ }
+ catch (ArgumentException)
+ {
+ throw new ArgumentException($"Unable to determine the given event type \"{item}\".");
+ }
+ }
+
+ return endResult;
+ }
+ #endregion
+
+ #region SQL setup and querying
+ public const string TblEntry = "modlogs_entries";
+ public const string TblEntryIncr = TblEntry + "_id";
+
+ internal static void CreateTables()
+ {
+ using (var db = RegexBot.Config.GetOpenDatabaseConnectionAsync().GetAwaiter().GetResult())
+ {
+ using (var c = db.CreateCommand())
+ {
+ c.CommandText = "CREATE TABLE IF NOT EXISTS " + TblEntry + " ("
+ + "id int primary key, "
+ + "entry_ts timestamptz not null, "
+ + "guild_id bigint not null, "
+ + "target_id bigint not null, "
+ + $"invoke_id bigint null references {EntityCache.SqlHelper.TableUser}.user_id, "
+ + "target_channel_id bigint null, " // TODO channel cache reference?
+ + "entry_type integer not null, "
+ + "message text not null, "
+ + $"FOREIGN KEY (target_id, guild_id) REFERENCES {EntityCache.SqlHelper.TableUser} (user_id, guild_id)";
+ c.ExecuteNonQuery();
+ }
+ using (var c = db.CreateCommand())
+ {
+ c.CommandText = $"CREATE SEQUENCE IF NOT EXISTS {TblEntryIncr} "
+ + $"START 1000 MAXVALUE {int.MaxValue}";
+ c.ExecuteNonQuery();
+ }
+ }
+ }
+
// Double-check constructor if making changes to this constant
- const string QueryColumns = "id, entry_ts, guild_id, target_id, invoke_id, target_channel_id, category, message";
+ const string QueryColumns = "id, entry_ts, guild_id, target_id, invoke_id, target_channel_id, entry_type, message";
///
- /// Attempts to look up a log entry with the given ID.
+ /// Attempts to look up a log entry by its ID.
///
/// Null if no result.
- public static async Task QueryIdAsync(ulong guild, int id)
+ public static async Task QueryIdAsync(ulong guild, int id)
{
using (var db = await RegexBot.Config.GetOpenDatabaseConnectionAsync())
{
using (var c = db.CreateCommand())
{
- c.CommandText = $"SELECT {QueryColumns} FROM {Sql.TableLog} "
+ c.CommandText = $"SELECT {QueryColumns} FROM {TblEntry} "
+ "WHERE guild_id = @Guild and id = @Id";
c.Parameters.Add("@Guild", NpgsqlTypes.NpgsqlDbType.Bigint).Value = guild;
c.Parameters.Add("@Id", NpgsqlTypes.NpgsqlDbType.Numeric).Value = id;
c.Prepare();
using (var r = await c.ExecuteReaderAsync())
{
- if (r.Read()) return new Entry(r);
+ if (r.Read()) return new LogEntry(r);
else return null;
}
}
}
}
- public static async Task> QueryLogAsync
+ ///
+ /// Attempts to look up a log entry by a number of parameters.
+ /// At least "target" or "invoker" are required when calling this method.
+ ///
+ ///
+ public static async Task> QueryLogAsync
(ulong guild,
ulong? target = null,
ulong? invoker = null,
ulong? channel = null,
- IEnumerable category = null)
+ LogType? category = null)
{
// Enforce some limits - can't search too broadly here. Requires this at a minimum:
if (target.HasValue == false && invoker.HasValue == false)
@@ -120,12 +206,12 @@ namespace Noikoio.RegexBot.Module.ModLogs
throw new ArgumentNullException("Query requires at minimum searching of a target or invoker.");
}
- var result = new List();
+ var result = new List();
using (var db = await RegexBot.Config.GetOpenDatabaseConnectionAsync())
{
using (var c = db.CreateCommand())
{
- c.CommandText = $"SELECT {QueryColumns} FROM {Sql.TableLog} WHERE";
+ c.CommandText = $"SELECT {QueryColumns} FROM {TblEntry} WHERE";
bool and = false;
if (target.HasValue)
@@ -149,13 +235,20 @@ namespace Noikoio.RegexBot.Module.ModLogs
c.CommandText += " target_channel_id = @ChannelId";
c.Parameters.Add("@ChannelId", NpgsqlTypes.NpgsqlDbType.Bigint).Value = channel.Value;
}
+ if (category.HasValue)
+ {
+ if (and) c.CommandText += " AND";
+ else and = true;
+ c.CommandText += " entry_type = @Category";
+ c.Parameters.Add("@Category", NpgsqlTypes.NpgsqlDbType.Integer).Value = (int)category;
+ }
c.Prepare();
using (var r = await c.ExecuteReaderAsync())
{
while (r.Read())
{
- result.Add(new Entry(r));
+ result.Add(new LogEntry(r));
}
}
}
diff --git a/Module/ModLogs/MessageCache.cs b/Module/ModLogs/MessageCache.cs
index 8b75462..f3393c8 100644
--- a/Module/ModLogs/MessageCache.cs
+++ b/Module/ModLogs/MessageCache.cs
@@ -17,11 +17,11 @@ namespace Noikoio.RegexBot.Module.ModLogs
{
private readonly DiscordSocketClient _dClient;
private readonly AsyncLogger _outLog;
- private readonly Func _outGetConfig;
+ private readonly Func _outGetConfig;
// TODO: How to clear the cache after a time? Can't hold on to this forever.
- public MessageCache(DiscordSocketClient client, AsyncLogger logger, Func getConfFunc)
+ public MessageCache(DiscordSocketClient client, AsyncLogger logger, Func getConfFunc)
{
_dClient = client;
_outLog = logger;
@@ -87,8 +87,8 @@ namespace Noikoio.RegexBot.Module.ModLogs
// Check if this feature is enabled before doing anything else.
var cfg = _outGetConfig(guildId);
if (cfg == null) return;
- if (isDelete && (cfg.RptTypes & EventType.MsgDelete) == 0) return;
- if (!isDelete && (cfg.RptTypes & EventType.MsgEdit) == 0) return;
+ if (isDelete && (cfg.RptTypes & LogEntry.LogType.MsgDelete) == 0) return;
+ if (!isDelete && (cfg.RptTypes & LogEntry.LogType.MsgEdit) == 0) return;
// Ignore if it's a message being deleted withing the reporting channel.
if (isDelete && cfg.RptTarget.Value.Id.Value == ch.Id) return;
@@ -122,7 +122,7 @@ namespace Noikoio.RegexBot.Module.ModLogs
}
}
}
- if (userId != 0) ucd = await EntityCache.EntityCache.QueryAsync(guildId, userId);
+ if (userId != 0) ucd = await EntityCache.EntityCache.QueryUserAsync(guildId, userId);
}
catch (NpgsqlException ex)
{
diff --git a/Module/ModLogs/ModLogs.cs b/Module/ModLogs/ModLogs.cs
index 7ae1136..9d53c8a 100644
--- a/Module/ModLogs/ModLogs.cs
+++ b/Module/ModLogs/ModLogs.cs
@@ -19,7 +19,9 @@ namespace Noikoio.RegexBot.Module.ModLogs
if (!RegexBot.Config.DatabaseAvailable) return;
// MessageCache (reporting of MessageEdit, MessageDelete) handled by helper class
- _msgCacheInstance = new MessageCache(client, Log, delegate (ulong id) { return GetState(id); });
+ _msgCacheInstance = new MessageCache(client, Log, delegate (ulong id) { return GetState(id); });
+
+ LogEntry.CreateTables();
// TODO add handlers for detecting joins, leaves, bans, kicks, user edits (nick/username/discr)
// TODO add handler for processing the log query command
@@ -37,8 +39,8 @@ namespace Noikoio.RegexBot.Module.ModLogs
return null;
}
- var conf = new GuildConfig((JObject)configSection);
- if (conf.RptTypes != EventType.None)
+ var conf = new GuildState((JObject)configSection);
+ if (conf.RptTypes != LogEntry.LogType.None)
await Log("Enabled event autoreporting to " + conf.RptTarget);
return conf;
diff --git a/Module/ModLogs/Sql.cs b/Module/ModLogs/Sql.cs
deleted file mode 100644
index 238b26f..0000000
--- a/Module/ModLogs/Sql.cs
+++ /dev/null
@@ -1,47 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Text;
-
-namespace Noikoio.RegexBot.Module.ModLogs
-{
- ///
- /// Contains common constants and static methods used for accessing the log database.
- ///
- class Sql
- {
- public const string TableLog = "modlogs_entries";
- public const string TableLogIncr = TableLog + "_id";
- public const string TableMsgCache = "modlogs_msgcache";
-
- static void CreateTables()
- {
- using (var db = RegexBot.Config.GetOpenDatabaseConnectionAsync().GetAwaiter().GetResult())
- {
- using (var c = db.CreateCommand())
- {
- c.CommandText = "CREATE TABLE IF NOT EXISTS " + TableLog + " ("
- + "id int primary key, "
- + "entry_ts timestamptz not null, "
- + "guild_id bigint not null, "
- + "target_id bigint not null, "
- + $"invoke_id bigint null references {EntityCache.SqlHelper.TableUser}.user_id, "
- + "target_channel_id bigint null, " // TODO channel cache reference?
- + "category text not null, "
- + "message text not null, "
- + $"FOREIGN KEY (target_id, guild_id) REFERENCES {EntityCache.SqlHelper.TableUser} (user_id, guild_id)";
- c.ExecuteNonQuery();
- }
- using (var c = db.CreateCommand())
- {
- c.CommandText = $"CREATE SEQUENCE IF NOT EXISTS {TableLogIncr} "
- + $"START 100 MAXVALUE {int.MaxValue}";
- c.ExecuteNonQuery();
- }
- }
- }
-
- #region Log entry manipulation
- // what was I doing again
- #endregion
- }
-}