Fully implemented text channel cache
Additionally, fixed a bug where user cache would quit immediately after encountering a bot or webhook user.
This commit is contained in:
parent
e5758e9aac
commit
84d3b1665d
4 changed files with 79 additions and 24 deletions
|
@ -13,8 +13,6 @@ namespace Noikoio.RegexBot.EntityCache
|
|||
/// </summary>
|
||||
class ECModule : BotModule
|
||||
{
|
||||
private readonly DatabaseConfig _db;
|
||||
|
||||
public ECModule(DiscordSocketClient client) : base(client)
|
||||
{
|
||||
if (RegexBot.Config.DatabaseAvailable)
|
||||
|
@ -26,13 +24,27 @@ namespace Noikoio.RegexBot.EntityCache
|
|||
client.GuildMemberUpdated += Client_GuildMemberUpdated;
|
||||
client.UserJoined += Client_UserJoined;
|
||||
client.UserLeft += Client_UserLeft;
|
||||
client.ChannelCreated += Client_ChannelCreated;
|
||||
client.ChannelUpdated += Client_ChannelUpdated;
|
||||
}
|
||||
else
|
||||
{
|
||||
Log("No database storage available.").Wait();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private async Task Client_ChannelUpdated(SocketChannel arg1, SocketChannel arg2)
|
||||
{
|
||||
if (arg2 is SocketGuildChannel ch)
|
||||
await SqlHelper.UpdateGuildChannelAsync(ch);
|
||||
}
|
||||
|
||||
private async Task Client_ChannelCreated(SocketChannel arg)
|
||||
{
|
||||
if (arg is SocketGuildChannel ch)
|
||||
await SqlHelper.UpdateGuildChannelAsync(ch);
|
||||
}
|
||||
|
||||
// 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)
|
||||
|
@ -43,6 +55,7 @@ namespace Noikoio.RegexBot.EntityCache
|
|||
{
|
||||
await SqlHelper.UpdateGuildAsync(arg);
|
||||
await SqlHelper.UpdateGuildMemberAsync(arg.Users);
|
||||
await SqlHelper.UpdateGuildChannelAsync(arg.Channels);
|
||||
}
|
||||
catch (NpgsqlException ex)
|
||||
{
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
using Discord.WebSocket;
|
||||
using Discord;
|
||||
using Discord.WebSocket;
|
||||
using NpgsqlTypes;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
@ -40,13 +41,20 @@ namespace Noikoio.RegexBot.EntityCache
|
|||
using (var c = db.CreateCommand())
|
||||
{
|
||||
c.CommandText = "CREATE TABLE IF NOT EXISTS " + TableTextChannel + " ("
|
||||
+ "channel_id bigint not null primary key, "
|
||||
+ "channel_id bigint not null, "
|
||||
+ $"guild_id bigint not null references {TableGuild}, "
|
||||
+ "cache_date timestamptz not null, "
|
||||
+ "channel_name text not 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 "
|
||||
+ $"{TableTextChannel}_ck_idx on {TableTextChannel} (channel_id, guild_id)";
|
||||
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.
|
||||
|
||||
|
@ -66,7 +74,7 @@ namespace Noikoio.RegexBot.EntityCache
|
|||
}
|
||||
using (var c = db.CreateCommand())
|
||||
{
|
||||
// guild_id is a foreign key, and also one half of the primary key here
|
||||
// compound primary key
|
||||
c.CommandText = "CREATE UNIQUE INDEX IF NOT EXISTS "
|
||||
+ $"{TableUser}_ck_idx on {TableUser} (user_id, guild_id)";
|
||||
await c.ExecuteNonQueryAsync();
|
||||
|
@ -103,10 +111,7 @@ namespace Noikoio.RegexBot.EntityCache
|
|||
}
|
||||
|
||||
internal static Task UpdateGuildMemberAsync(SocketGuildUser user)
|
||||
{
|
||||
var ml = new SocketGuildUser[] { user };
|
||||
return UpdateGuildMemberAsync(ml);
|
||||
}
|
||||
=> UpdateGuildMemberAsync(new SocketGuildUser[] { user });
|
||||
internal static async Task UpdateGuildMemberAsync(IEnumerable<SocketGuildUser> users)
|
||||
{
|
||||
var db = await RegexBot.Config.GetOpenDatabaseConnectionAsync();
|
||||
|
@ -150,6 +155,41 @@ namespace Noikoio.RegexBot.EntityCache
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal static Task UpdateGuildChannelAsync(SocketGuildChannel channel)
|
||||
=> UpdateGuildChannelAsync(new SocketGuildChannel[] { channel });
|
||||
internal static async Task UpdateGuildChannelAsync(IEnumerable<SocketGuildChannel> channels)
|
||||
{
|
||||
var db = await RegexBot.Config.GetOpenDatabaseConnectionAsync();
|
||||
if (db == null) return;
|
||||
using (db)
|
||||
{
|
||||
using (var c = db.CreateCommand())
|
||||
{
|
||||
c.CommandText = "INSERT INTO " + TableTextChannel
|
||||
+ " (channel_id, guild_id, cache_date, channel_name)"
|
||||
+ " VALUES (@Cid, @Gid, @Date, @Name) "
|
||||
+ "ON CONFLICT (channel_id, guild_id) DO UPDATE SET "
|
||||
+ "cache_date = EXCLUDED.cache_date, channel_name = EXCLUDED.channel_name";
|
||||
|
||||
var cid = c.Parameters.Add("@Cid", NpgsqlDbType.Bigint);
|
||||
var gid = c.Parameters.Add("@Gid", NpgsqlDbType.Bigint);
|
||||
c.Parameters.Add("@Date", NpgsqlDbType.TimestampTZ).Value = DateTime.Now;
|
||||
var cname = c.Parameters.Add("@Name", NpgsqlDbType.Text);
|
||||
c.Prepare();
|
||||
|
||||
foreach (var item in channels)
|
||||
{
|
||||
if (!(item is ITextChannel ich)) continue;
|
||||
|
||||
cid.Value = item.Id;
|
||||
gid.Value = item.Guild.Id;
|
||||
cname.Value = item.Name;
|
||||
await c.ExecuteNonQueryAsync();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
|
|
|
@ -142,7 +142,7 @@ namespace Noikoio.RegexBot.Module.ModCommands.Commands
|
|||
|
||||
try
|
||||
{
|
||||
cdata = (await EntityCache.EntityCache.QueryAsync(guild, input))
|
||||
cdata = (await EntityCache.EntityCache.QueryUserAsync(guild, input))
|
||||
.FirstOrDefault();
|
||||
if (cdata != null) uid = cdata.UserId;
|
||||
}
|
||||
|
|
|
@ -11,9 +11,9 @@ namespace Noikoio.RegexBot.Module.ModLogs
|
|||
class LogEntry
|
||||
{
|
||||
readonly int _logId;
|
||||
readonly DateTime _ts;
|
||||
readonly DateTimeOffset _ts;
|
||||
readonly ulong _guildId;
|
||||
readonly ulong? _invokeId;
|
||||
readonly ulong _invokeId;
|
||||
readonly ulong _targetId;
|
||||
readonly ulong? _channelId;
|
||||
readonly LogType _type;
|
||||
|
@ -24,9 +24,9 @@ namespace Noikoio.RegexBot.Module.ModLogs
|
|||
/// </summary>
|
||||
public int Id => _logId;
|
||||
/// <summary>
|
||||
/// Gets the timestamp (a <see cref="DateTime"/> with <see cref="DateTimeKind.Utc"/>) of the entry.
|
||||
/// Gets the UTC timestamp of the entry.
|
||||
/// </summary>
|
||||
public DateTime Timestamp => _ts;
|
||||
public DateTimeOffset Timestamp => _ts;
|
||||
/// <summary>
|
||||
/// Gets the ID of the guild to which this log entry corresponds.
|
||||
/// </summary>
|
||||
|
@ -37,9 +37,10 @@ namespace Noikoio.RegexBot.Module.ModLogs
|
|||
public ulong Target => _targetId;
|
||||
/// <summary>
|
||||
/// Gets the ID of the invoking user.
|
||||
/// This value exists only if this entry was created through action of another user that is not the target.
|
||||
/// This value differs from <see cref="Target"/> if this entry was created through
|
||||
/// action of another user, such as the issuer of notes and warnings.
|
||||
/// </summary>
|
||||
public ulong? Invoker => _invokeId;
|
||||
public ulong Invoker => _invokeId;
|
||||
/// <summary>
|
||||
/// Gets the guild channel ID to which this log entry corresponds, if any.
|
||||
/// </summary>
|
||||
|
@ -63,8 +64,7 @@ namespace Noikoio.RegexBot.Module.ModLogs
|
|||
{
|
||||
_guildId = (ulong)r.GetInt64(2);
|
||||
_targetId = (ulong)r.GetInt64(3);
|
||||
if (r.IsDBNull(4)) _invokeId = null;
|
||||
else _invokeId = (ulong)r.GetInt64(4);
|
||||
_invokeId = (ulong)r.GetInt64(4);
|
||||
if (r.IsDBNull(5)) _channelId = null;
|
||||
else _channelId = (ulong)r.GetInt64(5);
|
||||
}
|
||||
|
@ -144,12 +144,14 @@ namespace Noikoio.RegexBot.Module.ModLogs
|
|||
+ "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?
|
||||
+ "target_id bigint not null, " // No foreign constraint: some targets may not be cached
|
||||
+ "invoker_id bigint not null, "
|
||||
+ "target_channel_id bigint null, "
|
||||
+ "entry_type integer not null, "
|
||||
+ "message text not null, "
|
||||
+ $"FOREIGN KEY (target_id, guild_id) REFERENCES {EntityCache.SqlHelper.TableUser} (user_id, guild_id)";
|
||||
+ $"FOREIGN KEY (invoker_id, guild_id) REFERENCES {EntityCache.SqlHelper.TableUser} (user_id, guild_id), "
|
||||
+ $"FOREIGN KEY (target_channel_id, guild_id) REFERENCES {EntityCache.SqlHelper.TableTextChannel} (channel_id, guild_id)"
|
||||
+ ")";
|
||||
c.ExecuteNonQuery();
|
||||
}
|
||||
using (var c = db.CreateCommand())
|
||||
|
@ -162,7 +164,7 @@ namespace Noikoio.RegexBot.Module.ModLogs
|
|||
}
|
||||
|
||||
// Double-check constructor if making changes to this constant
|
||||
const string QueryColumns = "id, entry_ts, guild_id, target_id, invoke_id, target_channel_id, entry_type, message";
|
||||
const string QueryColumns = "id, entry_ts, guild_id, target_id, invoker_id, target_channel_id, entry_type, message";
|
||||
|
||||
/// <summary>
|
||||
/// Attempts to look up a log entry by its ID.
|
||||
|
|
Loading…
Reference in a new issue