From 5339701befb97387ea83007ad473d4ac90f72701 Mon Sep 17 00:00:00 2001 From: Noikoio Date: Tue, 27 Mar 2018 15:15:13 -0700 Subject: [PATCH] Reorganized code and files --- Module/ModLogs/EventType.cs | 22 --- .../ModLogs/{GuildConfig.cs => GuildState.cs} | 63 ++++----- Module/ModLogs/{Entry.cs => LogEntry.cs} | 133 +++++++++++++++--- Module/ModLogs/MessageCache.cs | 10 +- Module/ModLogs/ModLogs.cs | 8 +- Module/ModLogs/Sql.cs | 47 ------- 6 files changed, 149 insertions(+), 134 deletions(-) delete mode 100644 Module/ModLogs/EventType.cs rename Module/ModLogs/{GuildConfig.cs => GuildState.cs} (72%) rename Module/ModLogs/{Entry.cs => LogEntry.cs} (53%) delete mode 100644 Module/ModLogs/Sql.cs 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 - } -}