diff --git a/Module/EntityCache/UserCacheItem.cs b/EntityCache/CacheUser.cs similarity index 55% rename from Module/EntityCache/UserCacheItem.cs rename to EntityCache/CacheUser.cs index d82ace5..56c58f6 100644 --- a/Module/EntityCache/UserCacheItem.cs +++ b/EntityCache/CacheUser.cs @@ -1,15 +1,18 @@ -using System; +using Discord.WebSocket; +using Npgsql; +using System; using System.Collections.Generic; using System.Data.Common; +using System.Linq; using System.Text.RegularExpressions; using System.Threading.Tasks; -namespace Noikoio.RegexBot.Module.EntityCache +namespace Noikoio.RegexBot.EntityCache { /// - /// Represents a cached user. + /// Representation of a cached user. /// - class UserCacheItem + class CacheUser { readonly ulong _userId; readonly ulong _guildId; @@ -57,8 +60,20 @@ namespace Noikoio.RegexBot.Module.EntityCache /// public string AvatarUrl => _avatarUrl; + private CacheUser(SocketGuildUser u) + { + _userId = u.Id; + _guildId = u.Guild.Id; + _cacheDate = DateTime.UtcNow; + _username = u.Username; + _discriminator = u.Discriminator; + _nickname = u.Nickname; + _avatarUrl = u.GetAvatarUrl(); + } - private UserCacheItem(DbDataReader r) + // Double-check SqlHelper if making changes to this constant + const string QueryColumns = "user_id, guild_id, cache_date, username, discriminator, nickname, avatar_url"; + private CacheUser(DbDataReader r) { // Double-check ordinals if making changes to QueryColumns unchecked @@ -77,19 +92,32 @@ namespace Noikoio.RegexBot.Module.EntityCache public override string ToString() => DisplayName; #region Queries - const string QueryColumns = "user_id, guild_id, cache_date, username, discriminator, nickname, avatar_url"; - - /// - /// Attempts to query for an exact result with the given parameters. - /// - /// Null on no result. - public static async Task QueryAsync(ulong guild, ulong user) + // Accessible by EntityCache. Documentation is there. + internal static async Task QueryAsync(DiscordSocketClient c, ulong guild, ulong user) { - using (var db = await RegexBot.Config.Database.GetOpenConnectionAsync()) + // Local cache search + var lresult = LocalQueryAsync(c, guild, user); + if (lresult != null) return lresult; + + // Database cache search + var db = await RegexBot.Config.Database.GetOpenConnectionAsync(); + if (db == null) return null; // Database not available for query. + using (db) return await DbQueryAsync(db, guild, user); + } + + private static CacheUser LocalQueryAsync(DiscordSocketClient c, ulong guild, ulong user) + { + var u = c.GetGuild(guild)?.GetUser(user); + if (u == null) return null; + return new CacheUser(u); + } + private static async Task DbQueryAsync(NpgsqlConnection db, ulong guild, ulong user) + { + using (db) { using (var c = db.CreateCommand()) { - c.CommandText = $"SELECT {QueryColumns} FROM {Sql.TableUser} WHERE " + c.CommandText = $"SELECT {QueryColumns} FROM {SqlHelper.TableUser} WHERE " + "user_id = @Uid AND guild_id = @Gid"; c.Parameters.Add("@Uid", NpgsqlTypes.NpgsqlDbType.Bigint).Value = user; c.Parameters.Add("@Gid", NpgsqlTypes.NpgsqlDbType.Bigint).Value = guild; @@ -98,7 +126,7 @@ namespace Noikoio.RegexBot.Module.EntityCache { if (await r.ReadAsync()) { - return new UserCacheItem(r); + return new CacheUser(r); } else { @@ -109,23 +137,21 @@ namespace Noikoio.RegexBot.Module.EntityCache } } - private static Regex DiscriminatorSearch = new Regex(@"(.+)#(\d{4}(?!\d))", RegexOptions.Compiled); + // ----- - /// - /// Attempts to look up the user given a search string. - /// This string looks up case-insensitive, exact matches of nicknames and usernames. - /// - /// An containing zero or more query results, sorted by cache date. - public static async Task> QueryAsync(ulong guild, string search) + private static Regex DiscriminatorSearch = new Regex(@"(.+)#(\d{4}(?!\d))", RegexOptions.Compiled); + // Accessible by EntityCache. Documentation is there. + internal static async Task> QueryAsync(DiscordSocketClient c, ulong guild, string search) { - // Is search just a number? It's an ID. + // Is search just a number? Assume ID, pass it on to the correct place. if (ulong.TryParse(search, out var presult)) { - var r = await QueryAsync(guild, presult); - if (r == null) return new UserCacheItem[0]; - else return new UserCacheItem[] { r }; + var r = await QueryAsync(c, guild, presult); + if (r == null) return new CacheUser[0]; + else return new CacheUser[] { r }; } + // Split name/discriminator string name; string disc; var split = DiscriminatorSearch.Match(search); @@ -140,14 +166,52 @@ namespace Noikoio.RegexBot.Module.EntityCache disc = null; } - // Storing in HashSet to enforce uniqueness - HashSet result = new HashSet(_uc); + // Local cache search + var lresult = LocalQueryAsync(c, guild, name, disc); + if (lresult.Count() != 0) return lresult; - using (var db = await RegexBot.Config.Database.GetOpenConnectionAsync()) + // Database cache search + var db = await RegexBot.Config.Database.GetOpenConnectionAsync(); + if (db == null) return null; // Database not available for query. + using (db) return await DbQueryAsync(db, guild, name, disc); + } + + private static IEnumerable LocalQueryAsync(DiscordSocketClient c, ulong guild, string name, string disc) + { + var g = c.GetGuild(guild); + if (g == null) return new CacheUser[] { }; + + bool Filter(string iun, string inn, string idc) + { + // Same logic as in the SQL query in the method below this one + bool match = + string.Equals(iun, name, StringComparison.InvariantCultureIgnoreCase) + || string.Equals(inn, name, StringComparison.InvariantCultureIgnoreCase); + + if (match && disc != null) + match = idc.Equals(disc); + + return match; + } + + var qresult = g.Users.Where(i => Filter(i.Username, i.Nickname, i.Discriminator)); + var result = new List(); + foreach (var item in qresult) + { + result.Add(new CacheUser(item)); + } + return result; + } + + private static async Task> DbQueryAsync(NpgsqlConnection db, ulong guild, string name, string disc) + { + var result = new List(); + + using (db = await RegexBot.Config.Database.GetOpenConnectionAsync()) { using (var c = db.CreateCommand()) { - c.CommandText = $"SELECT {QueryColumns} FROM {Sql.TableUser} WHERE " + c.CommandText = $"SELECT {QueryColumns} FROM {SqlHelper.TableUser} WHERE " + "( lower(username) = lower(@NameSearch) OR lower(nickname) = lower(@NameSearch) )"; c.Parameters.Add("@NameSearch", NpgsqlTypes.NpgsqlDbType.Text).Value = name; if (disc != null) @@ -161,21 +225,13 @@ namespace Noikoio.RegexBot.Module.EntityCache { while (await r.ReadAsync()) { - result.Add(new UserCacheItem(r)); + result.Add(new CacheUser(r)); } } } } return result; } - - private static UniqueChecker _uc = new UniqueChecker(); - class UniqueChecker : IEqualityComparer - { - public bool Equals(UserCacheItem x, UserCacheItem y) => x.UserId == y.UserId && x.GuildId == y.GuildId; - - public int GetHashCode(UserCacheItem obj) => unchecked((int)(obj.UserId ^ obj.GuildId)); - } #endregion } } diff --git a/EntityCache/EntityCache.cs b/EntityCache/EntityCache.cs new file mode 100644 index 0000000..5937f40 --- /dev/null +++ b/EntityCache/EntityCache.cs @@ -0,0 +1,38 @@ +using Discord.WebSocket; +using System.Collections.Generic; +using System.Threading.Tasks; + +namespace Noikoio.RegexBot.EntityCache +{ + /// + /// Static class for accessing the entity cache. + /// + static class EntityCache + { + /* + * The entity cache works by combining data known/cached by Discord.Net in addition to + * what has been stored in the database. If data does not exist in the former, it is + * retrieved from the latter. + * In either case, the resulting data is placed within a cache item object. + */ + + static DiscordSocketClient _client; + internal static void SetClient(DiscordSocketClient c) => _client = _client ?? c; + + /// + /// Attempts to query for an exact result with the given parameters. + /// Does not handle exceptions that may occur. + /// + /// Null on no result. + internal static Task QueryAsync(ulong guild, ulong user) + => CacheUser.QueryAsync(_client, guild, user); + + /// + /// Attempts to look up the user given a search string. + /// This string looks up case-insensitive, exact matches of nicknames and usernames. + /// + /// An containing zero or more query results, sorted by cache date. + internal static Task> QueryAsync(ulong guild, string search) + => CacheUser.QueryAsync(_client, guild, search); + } +} diff --git a/EntityCache/Module.cs b/EntityCache/Module.cs new file mode 100644 index 0000000..f976632 --- /dev/null +++ b/EntityCache/Module.cs @@ -0,0 +1,124 @@ +using Discord.WebSocket; +using Newtonsoft.Json.Linq; +using Noikoio.RegexBot.ConfigItem; +using Npgsql; +using System.Threading.Tasks; + +namespace Noikoio.RegexBot.EntityCache +{ + /// + /// Bot module portion of the entity cache. Caches information regarding all known guilds, channels, and users. + /// The function of this module should be transparent to the user, and thus no configuration is needed. + /// This module should be initialized BEFORE any other modules that make use of the entity cache. + /// + class Module : BotModule + { + private readonly DatabaseConfig _db; + + public override string Name => nameof(EntityCache); + + public Module(DiscordSocketClient client) : base(client) + { + _db = RegexBot.Config.Database; + + if (_db.Available) + { + SqlHelper.CreateCacheTablesAsync().Wait(); + + client.GuildAvailable += Client_GuildAvailable; + client.GuildUpdated += Client_GuildUpdated; + client.GuildMemberUpdated += Client_GuildMemberUpdated; + client.UserJoined += Client_UserJoined; + client.UserLeft += Client_UserLeft; + } + else + { + Log("No database storage available.").Wait(); + } + } + + public override Task ProcessConfiguration(JToken configSection) => Task.FromResult(null); + + // Guild and guild member information has become available. + // This is a very expensive operation, especially when joining larger guilds. + private async Task Client_GuildAvailable(SocketGuild arg) + { + await Task.Run(async () => + { + try + { + await SqlHelper.UpdateGuildAsync(arg); + await SqlHelper.UpdateGuildMemberAsync(arg.Users); + } + catch (NpgsqlException ex) + { + await Log($"SQL error in {nameof(Client_GuildAvailable)}: {ex.Message}"); + } + }); + } + + // Guild information has changed + private async Task Client_GuildUpdated(SocketGuild arg1, SocketGuild arg2) + { + await Task.Run(async () => + { + try + { + await SqlHelper.UpdateGuildAsync(arg2); + } + catch (NpgsqlException ex) + { + await Log($"SQL error in {nameof(Client_GuildUpdated)}: {ex.Message}"); + } + }); + } + + // Guild member information has changed + private async Task Client_GuildMemberUpdated(SocketGuildUser arg1, SocketGuildUser arg2) + { + await Task.Run(async () => + { + try + { + await SqlHelper.UpdateGuildMemberAsync(arg2); + } + catch (NpgsqlException ex) + { + await Log($"SQL error in {nameof(Client_GuildMemberUpdated)}: {ex.Message}"); + } + }); + } + + // A new guild member has appeared + private async Task Client_UserJoined(SocketGuildUser arg) + { + await Task.Run(async () => + { + try + { + await SqlHelper.UpdateGuildMemberAsync(arg); + } + catch (NpgsqlException ex) + { + await Log($"SQL error in {nameof(Client_UserJoined)}: {ex.Message}"); + } + }); + } + + // User left the guild. No new data, but gives an excuse to update the cache date. + private async Task Client_UserLeft(SocketGuildUser arg) + { + await Task.Run(async () => + { + try + { + await SqlHelper.UpdateGuildMemberAsync(arg); + } + catch (NpgsqlException ex) + { + await Log($"SQL error in {nameof(Client_UserLeft)}: {ex.Message}"); + } + }); + } + } +} diff --git a/EntityCache/SqlHelper.cs b/EntityCache/SqlHelper.cs new file mode 100644 index 0000000..d19489c --- /dev/null +++ b/EntityCache/SqlHelper.cs @@ -0,0 +1,155 @@ +using Discord.WebSocket; +using NpgsqlTypes; +using System; +using System.Collections.Generic; +using System.Threading.Tasks; + +namespace Noikoio.RegexBot.EntityCache +{ + /// + /// Helper methods for database operations. + /// Exceptions are not handled within methods of this class. + /// + static class SqlHelper + { + public const string TableGuild = "cache_guild"; + public const string TableTextChannel = "cache_textchannel"; + public const string TableUser = "cache_users"; + + // Reminder: Check Cache query methods if making changes to tables + internal static async Task CreateCacheTablesAsync() + { + var db = await RegexBot.Config.Database.GetOpenConnectionAsync(); + if (db == null) return; + using (db) + { + // Guild cache + using (var c = db.CreateCommand()) + { + c.CommandText = "CREATE TABLE IF NOT EXISTS " + TableGuild + " (" + + "guild_id bigint primary key, " + + "cache_date timestamptz not null, " + + "current_name text not null, " + + "display_name text null" + + ")"; + await c.ExecuteNonQueryAsync(); + } + // May not require other indexes. Add here if they become necessary. + + // Text channel cache + using (var c = db.CreateCommand()) + { + c.CommandText = "CREATE TABLE IF NOT EXISTS " + TableTextChannel + " (" + + "channel_id bigint not null primary key, " + + $"guild_id bigint not null references {TableGuild}, " + + "cache_date timestamptz not null, " + + "channel_name text not null" + + ")"; + await c.ExecuteNonQueryAsync(); + } + // As of the time of this commit, Discord doesn't allow any uppercase characters + // in channel names. No lowercase name index needed. + + // User cache + using (var c = db.CreateCommand()) + { + c.CommandText = "CREATE TABLE IF NOT EXISTS " + TableUser + " (" + + "user_id bigint not null, " + + $"guild_id bigint not null references {TableGuild}, " + + "cache_date timestamptz not null, " + + "username text not null, " + + "discriminator text not null, " + + "nickname text null, " + + "avatar_url text null" + + ")"; + await c.ExecuteNonQueryAsync(); + } + using (var c = db.CreateCommand()) + { + // guild_id is a foreign key, and also one half of the primary key here + c.CommandText = "CREATE UNIQUE INDEX IF NOT EXISTS " + + $"{TableUser}_ck_idx on {TableUser} (user_id, guild_id)"; + await c.ExecuteNonQueryAsync(); + } + using (var c = db.CreateCommand()) + { + c.CommandText = "CREATE INDEX IF NOT EXISTS " + + $"{TableUser}_usersearch_idx on {TableUser} (LOWER(username))"; + await c.ExecuteNonQueryAsync(); + } + } + } + + #region Insertions and updates + internal static async Task UpdateGuildAsync(SocketGuild g) + { + var db = await RegexBot.Config.Database.GetOpenConnectionAsync(); + if (db == null) return; + using (db) + { + using (var c = db.CreateCommand()) + { + c.CommandText = "INSERT INTO " + TableGuild + " (guild_id, cache_date, current_name) " + + "VALUES (@GuildId, @Date, @CurrentName) " + + "ON CONFLICT (guild_id) DO UPDATE SET " + + "current_name = EXCLUDED.current_name, cache_date = EXCLUDED.cache_date"; + c.Parameters.Add("@GuildId", NpgsqlDbType.Bigint).Value = g.Id; + c.Parameters.Add("@CurrentName", NpgsqlDbType.Text).Value = g.Name; + c.Parameters.Add("@Date", NpgsqlDbType.TimestampTZ).Value = DateTime.Now; + c.Prepare(); + await c.ExecuteNonQueryAsync(); + } + } + } + + internal static Task UpdateGuildMemberAsync(SocketGuildUser user) + { + var ml = new SocketGuildUser[] { user }; + return UpdateGuildMemberAsync(ml); + } + internal static async Task UpdateGuildMemberAsync(IEnumerable users) + { + var db = await RegexBot.Config.Database.GetOpenConnectionAsync(); + if (db == null) return; + using (db) + { + using (var c = db.CreateCommand()) + { + c.CommandText = "INSERT INTO " + TableUser + + " (user_id, guild_id, cache_date, username, discriminator, nickname, avatar_url)" + + " VALUES (@Uid, @Gid, @Date, @Uname, @Disc, @Nname, @Url) " + + "ON CONFLICT (user_id, guild_id) DO UPDATE SET " + + "cache_date = EXCLUDED.cache_date, username = EXCLUDED.username, " + + "discriminator = EXCLUDED.discriminator, " // I've seen someone's discriminator change this one time... + + "nickname = EXCLUDED.nickname, avatar_url = EXCLUDED.avatar_url"; + + var uid = c.Parameters.Add("@Uid", NpgsqlDbType.Bigint); + var gid = c.Parameters.Add("@Gid", NpgsqlDbType.Bigint); + c.Parameters.Add("@Date", NpgsqlDbType.TimestampTZ).Value = DateTime.Now; + var uname = c.Parameters.Add("@Uname", NpgsqlDbType.Text); + var disc = c.Parameters.Add("@Disc", NpgsqlDbType.Text); + var nname = c.Parameters.Add("@Nname", NpgsqlDbType.Text); + var url = c.Parameters.Add("@Url", NpgsqlDbType.Text); + c.Prepare(); + + foreach (var item in users) + { + if (item.IsBot || item.IsWebhook) continue; + + uid.Value = item.Id; + gid.Value = item.Guild.Id; + uname.Value = item.Username; + disc.Value = item.Discriminator; + nname.Value = item.Nickname; + if (nname.Value == null) nname.Value = DBNull.Value; // why can't ?? work here? + url.Value = item.GetAvatarUrl(); + if (url.Value == null) url.Value = DBNull.Value; + + await c.ExecuteNonQueryAsync(); + } + } + } + } + #endregion + } +} diff --git a/Module/EntityCache/EntityCache.cs b/Module/EntityCache/EntityCache.cs deleted file mode 100644 index 8af68ec..0000000 --- a/Module/EntityCache/EntityCache.cs +++ /dev/null @@ -1,154 +0,0 @@ -using Discord.WebSocket; -using Newtonsoft.Json.Linq; -using Noikoio.RegexBot.ConfigItem; -using Npgsql; -using NpgsqlTypes; -using System; -using System.Collections.Generic; -using System.Threading.Tasks; - -namespace Noikoio.RegexBot.Module.EntityCache -{ - /// - /// Caches information regarding all known guilds, channels, and users. - /// The function of this module should be transparent to the user, and thus no configuration is needed. - /// This module should be initialized BEFORE any other modules that make use of guild and user cache. - /// - class EntityCache : BotModule - { - private readonly DatabaseConfig _db; - - public override string Name => nameof(EntityCache); - - public EntityCache(DiscordSocketClient client) : base(client) - { - _db = RegexBot.Config.Database; - - if (_db.Available) - { - Sql.CreateCacheTables(); - - client.GuildAvailable += Client_GuildAvailable; - client.GuildUpdated += Client_GuildUpdated; - client.GuildMemberUpdated += Client_GuildMemberUpdated; - // it may not be necessary to handle JoinedGuild, as GuildAvailable provides this info - } - else - { - Log("No database storage available.").Wait(); - } - } - - public override Task ProcessConfiguration(JToken configSection) => Task.FromResult(null); - - #region Event handling - // Guild _and_ guild member information has become available - private async Task Client_GuildAvailable(SocketGuild arg) - { - await Task.Run(async () => - { - await UpdateGuild(arg); - await UpdateGuildMember(arg.Users); - } - ); - } - - // Guild information has changed - private async Task Client_GuildUpdated(SocketGuild arg1, SocketGuild arg2) - { - await Task.Run(() => UpdateGuild(arg2)); - } - - // Guild member information has changed - private async Task Client_GuildMemberUpdated(SocketGuildUser arg1, SocketGuildUser arg2) - { - await Task.Run(() => UpdateGuildMember(arg2)); - } -#endregion - -#region Table setup - -#endregion - - private async Task UpdateGuild(SocketGuild g) - { - try - { - using (var db = await _db.GetOpenConnectionAsync()) - { - using (var c = db.CreateCommand()) - { - c.CommandText = "INSERT INTO " + Sql.TableGuild + " (guild_id, current_name) " - + "VALUES (@GuildId, @CurrentName) " - + "ON CONFLICT (guild_id) DO UPDATE SET " - + "current_name = EXCLUDED.current_name"; - c.Parameters.Add("@GuildId", NpgsqlDbType.Bigint).Value = g.Id; - c.Parameters.Add("@CurrentName", NpgsqlDbType.Text).Value = g.Name; - c.Prepare(); - await c.ExecuteNonQueryAsync(); - } - } - } - catch (NpgsqlException ex) - { - await Log($"SQL error in {nameof(UpdateGuild)}: " + ex.Message); - } - } - - private async Task UpdateGuildMember(IEnumerable users) - { - try - { - using (var db = await _db.GetOpenConnectionAsync()) - { - using (var c = db.CreateCommand()) - { - c.CommandText = "INSERT INTO " + Sql.TableUser - + " (user_id, guild_id, cache_date, username, discriminator, nickname, avatar_url)" - + " VALUES (@Uid, @Gid, @Date, @Uname, @Disc, @Nname, @Url) " - + "ON CONFLICT (user_id, guild_id) DO UPDATE SET " - + "cache_date = EXCLUDED.cache_date, username = EXCLUDED.username, " - + "discriminator = EXCLUDED.discriminator, " // I've seen someone's discriminator change this one time... - + "nickname = EXCLUDED.nickname, avatar_url = EXCLUDED.avatar_url"; - - var uid = c.Parameters.Add("@Uid", NpgsqlDbType.Bigint); - var gid = c.Parameters.Add("@Gid", NpgsqlDbType.Bigint); - c.Parameters.Add("@Date", NpgsqlDbType.TimestampTZ).Value = DateTime.Now; - var uname = c.Parameters.Add("@Uname", NpgsqlDbType.Text); - var disc = c.Parameters.Add("@Disc", NpgsqlDbType.Text); - var nname = c.Parameters.Add("@Nname", NpgsqlDbType.Text); - var url = c.Parameters.Add("@Url", NpgsqlDbType.Text); - c.Prepare(); - - foreach (var item in users) - { - if (item.IsBot || item.IsWebhook) continue; - - uid.Value = item.Id; - gid.Value = item.Guild.Id; - uname.Value = item.Username; - disc.Value = item.Discriminator; - nname.Value = item.Nickname; - if (nname.Value == null) nname.Value = DBNull.Value; // why can't ?? work here? - url.Value = item.GetAvatarUrl(); - if (url.Value == null) url.Value = DBNull.Value; - - await c.ExecuteNonQueryAsync(); - } - } - } - } - catch (NpgsqlException ex) - { - await Log($"SQL error in {nameof(UpdateGuildMember)}: " + ex.Message); - } - } - - private Task UpdateGuildMember(SocketGuildUser user) - { - var gid = user.Guild.Id; - var ml = new SocketGuildUser[] { user }; - return UpdateGuildMember(ml); - } - } -} diff --git a/Module/EntityCache/Sql.cs b/Module/EntityCache/Sql.cs deleted file mode 100644 index ed55b0b..0000000 --- a/Module/EntityCache/Sql.cs +++ /dev/null @@ -1,56 +0,0 @@ -using Npgsql; -using System; -using System.Collections.Generic; -using System.Text; - -namespace Noikoio.RegexBot.Module.EntityCache -{ - /// - /// Contains common constants and static methods for cache access. - /// - class Sql - { - public const string TableGuild = "cache_guild"; - public const string TableUser = "cache_users"; - - private static NpgsqlConnection OpenDB() => - RegexBot.Config.Database.GetOpenConnectionAsync().GetAwaiter().GetResult(); - - public static void CreateCacheTables() - { - using (var db = OpenDB()) - { - using (var c = db.CreateCommand()) - { - c.CommandText = "CREATE TABLE IF NOT EXISTS " + TableGuild + "(" - + "guild_id bigint primary key, " - + "current_name text not null, " - + "display_name text null" - + ")"; - c.ExecuteNonQuery(); - } - - using (var c = db.CreateCommand()) - { - c.CommandText = "CREATE TABLE IF NOT EXISTS " + TableUser + "(" - + "user_id bigint not null, " - + $"guild_id bigint not null references {TableGuild}, " - + "cache_date timestamptz not null, " - + "username text not null, " - + "discriminator text not null, " - + "nickname text null, " - + "avatar_url text null" - + ")"; - c.ExecuteNonQuery(); - } - using (var c = db.CreateCommand()) - { - c.CommandText = "CREATE UNIQUE INDEX IF NOT EXISTS " - + $"{TableUser}_idx on {TableUser} (user_id, guild_id)"; - c.ExecuteNonQuery(); - } - // TODO create indexes for string-based queries - } - } - } -} diff --git a/Properties/PublishProfiles/DebugProfile.pubxml b/Properties/PublishProfiles/DebugProfile.pubxml index e2bc34c..a6217fd 100644 --- a/Properties/PublishProfiles/DebugProfile.pubxml +++ b/Properties/PublishProfiles/DebugProfile.pubxml @@ -1,13 +1,13 @@  FileSystem Debug - netcoreapp1.1 + netcoreapp2.0 bin\Debug\PublishOutput \ No newline at end of file diff --git a/RegexBot.cs b/RegexBot.cs index b12fade..e4e8669 100644 --- a/RegexBot.cs +++ b/RegexBot.cs @@ -38,6 +38,7 @@ namespace Noikoio.RegexBot Environment.Exit(1); } + // Set Discord client settings _client = new DiscordSocketClient(new DiscordSocketConfig() { LogLevel = LogSeverity.Info, @@ -46,8 +47,9 @@ namespace Noikoio.RegexBot MessageCacheSize = 0 }); - // Hook up handlers for basic functions + // Hook up basic handlers and other references _client.Connected += _client_Connected; + EntityCache.EntityCache.SetClient(_client); // Initialize modules _modules = new BotModule[] @@ -56,15 +58,17 @@ namespace Noikoio.RegexBot new Module.AutoMod.AutoMod(_client), new Module.ModTools.ModTools(_client), new Module.AutoRespond.AutoRespond(_client), - new Module.EntityCache.EntityCache(_client) // EntityCache goes before anything else that uses its data + new EntityCache.Module(_client) // EntityCache goes before anything else that uses its data }; + + // Set up logging var dlog = Logger.GetLogger("Discord.Net"); _client.Log += async (arg) => await dlog( String.Format("{0}: {1}{2}", arg.Source, ((int)arg.Severity < 3 ? arg.Severity + ": " : ""), arg.Message)); - // With modules initialized, finish loading configuration + // Finish loading configuration var conf = _config.ReloadServerConfig().Result; if (conf == false) {