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>
|
/// </summary>
|
||||||
class ECModule : BotModule
|
class ECModule : BotModule
|
||||||
{
|
{
|
||||||
private readonly DatabaseConfig _db;
|
|
||||||
|
|
||||||
public ECModule(DiscordSocketClient client) : base(client)
|
public ECModule(DiscordSocketClient client) : base(client)
|
||||||
{
|
{
|
||||||
if (RegexBot.Config.DatabaseAvailable)
|
if (RegexBot.Config.DatabaseAvailable)
|
||||||
|
@ -26,6 +24,8 @@ namespace Noikoio.RegexBot.EntityCache
|
||||||
client.GuildMemberUpdated += Client_GuildMemberUpdated;
|
client.GuildMemberUpdated += Client_GuildMemberUpdated;
|
||||||
client.UserJoined += Client_UserJoined;
|
client.UserJoined += Client_UserJoined;
|
||||||
client.UserLeft += Client_UserLeft;
|
client.UserLeft += Client_UserLeft;
|
||||||
|
client.ChannelCreated += Client_ChannelCreated;
|
||||||
|
client.ChannelUpdated += Client_ChannelUpdated;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
@ -33,6 +33,18 @@ namespace Noikoio.RegexBot.EntityCache
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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.
|
// Guild and guild member information has become available.
|
||||||
// This is a very expensive operation, especially when joining larger guilds.
|
// This is a very expensive operation, especially when joining larger guilds.
|
||||||
private async Task Client_GuildAvailable(SocketGuild arg)
|
private async Task Client_GuildAvailable(SocketGuild arg)
|
||||||
|
@ -43,6 +55,7 @@ namespace Noikoio.RegexBot.EntityCache
|
||||||
{
|
{
|
||||||
await SqlHelper.UpdateGuildAsync(arg);
|
await SqlHelper.UpdateGuildAsync(arg);
|
||||||
await SqlHelper.UpdateGuildMemberAsync(arg.Users);
|
await SqlHelper.UpdateGuildMemberAsync(arg.Users);
|
||||||
|
await SqlHelper.UpdateGuildChannelAsync(arg.Channels);
|
||||||
}
|
}
|
||||||
catch (NpgsqlException ex)
|
catch (NpgsqlException ex)
|
||||||
{
|
{
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
using Discord.WebSocket;
|
using Discord;
|
||||||
|
using Discord.WebSocket;
|
||||||
using NpgsqlTypes;
|
using NpgsqlTypes;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
@ -40,13 +41,20 @@ namespace Noikoio.RegexBot.EntityCache
|
||||||
using (var c = db.CreateCommand())
|
using (var c = db.CreateCommand())
|
||||||
{
|
{
|
||||||
c.CommandText = "CREATE TABLE IF NOT EXISTS " + TableTextChannel + " ("
|
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}, "
|
+ $"guild_id bigint not null references {TableGuild}, "
|
||||||
+ "cache_date timestamptz not null, "
|
+ "cache_date timestamptz not null, "
|
||||||
+ "channel_name text not null"
|
+ "channel_name text not null"
|
||||||
+ ")";
|
+ ")";
|
||||||
await c.ExecuteNonQueryAsync();
|
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
|
// As of the time of this commit, Discord doesn't allow any uppercase characters
|
||||||
// in channel names. No lowercase name index needed.
|
// in channel names. No lowercase name index needed.
|
||||||
|
|
||||||
|
@ -66,7 +74,7 @@ namespace Noikoio.RegexBot.EntityCache
|
||||||
}
|
}
|
||||||
using (var c = db.CreateCommand())
|
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 "
|
c.CommandText = "CREATE UNIQUE INDEX IF NOT EXISTS "
|
||||||
+ $"{TableUser}_ck_idx on {TableUser} (user_id, guild_id)";
|
+ $"{TableUser}_ck_idx on {TableUser} (user_id, guild_id)";
|
||||||
await c.ExecuteNonQueryAsync();
|
await c.ExecuteNonQueryAsync();
|
||||||
|
@ -103,10 +111,7 @@ namespace Noikoio.RegexBot.EntityCache
|
||||||
}
|
}
|
||||||
|
|
||||||
internal static Task UpdateGuildMemberAsync(SocketGuildUser user)
|
internal static Task UpdateGuildMemberAsync(SocketGuildUser user)
|
||||||
{
|
=> UpdateGuildMemberAsync(new SocketGuildUser[] { user });
|
||||||
var ml = new SocketGuildUser[] { user };
|
|
||||||
return UpdateGuildMemberAsync(ml);
|
|
||||||
}
|
|
||||||
internal static async Task UpdateGuildMemberAsync(IEnumerable<SocketGuildUser> users)
|
internal static async Task UpdateGuildMemberAsync(IEnumerable<SocketGuildUser> users)
|
||||||
{
|
{
|
||||||
var db = await RegexBot.Config.GetOpenDatabaseConnectionAsync();
|
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
|
#endregion
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -142,7 +142,7 @@ namespace Noikoio.RegexBot.Module.ModCommands.Commands
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
cdata = (await EntityCache.EntityCache.QueryAsync(guild, input))
|
cdata = (await EntityCache.EntityCache.QueryUserAsync(guild, input))
|
||||||
.FirstOrDefault();
|
.FirstOrDefault();
|
||||||
if (cdata != null) uid = cdata.UserId;
|
if (cdata != null) uid = cdata.UserId;
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,9 +11,9 @@ namespace Noikoio.RegexBot.Module.ModLogs
|
||||||
class LogEntry
|
class LogEntry
|
||||||
{
|
{
|
||||||
readonly int _logId;
|
readonly int _logId;
|
||||||
readonly DateTime _ts;
|
readonly DateTimeOffset _ts;
|
||||||
readonly ulong _guildId;
|
readonly ulong _guildId;
|
||||||
readonly ulong? _invokeId;
|
readonly ulong _invokeId;
|
||||||
readonly ulong _targetId;
|
readonly ulong _targetId;
|
||||||
readonly ulong? _channelId;
|
readonly ulong? _channelId;
|
||||||
readonly LogType _type;
|
readonly LogType _type;
|
||||||
|
@ -24,9 +24,9 @@ namespace Noikoio.RegexBot.Module.ModLogs
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public int Id => _logId;
|
public int Id => _logId;
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the timestamp (a <see cref="DateTime"/> with <see cref="DateTimeKind.Utc"/>) of the entry.
|
/// Gets the UTC timestamp of the entry.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public DateTime Timestamp => _ts;
|
public DateTimeOffset Timestamp => _ts;
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the ID of the guild to which this log entry corresponds.
|
/// Gets the ID of the guild to which this log entry corresponds.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -37,9 +37,10 @@ namespace Noikoio.RegexBot.Module.ModLogs
|
||||||
public ulong Target => _targetId;
|
public ulong Target => _targetId;
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the ID of the invoking user.
|
/// 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>
|
/// </summary>
|
||||||
public ulong? Invoker => _invokeId;
|
public ulong Invoker => _invokeId;
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the guild channel ID to which this log entry corresponds, if any.
|
/// Gets the guild channel ID to which this log entry corresponds, if any.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -63,8 +64,7 @@ namespace Noikoio.RegexBot.Module.ModLogs
|
||||||
{
|
{
|
||||||
_guildId = (ulong)r.GetInt64(2);
|
_guildId = (ulong)r.GetInt64(2);
|
||||||
_targetId = (ulong)r.GetInt64(3);
|
_targetId = (ulong)r.GetInt64(3);
|
||||||
if (r.IsDBNull(4)) _invokeId = null;
|
_invokeId = (ulong)r.GetInt64(4);
|
||||||
else _invokeId = (ulong)r.GetInt64(4);
|
|
||||||
if (r.IsDBNull(5)) _channelId = null;
|
if (r.IsDBNull(5)) _channelId = null;
|
||||||
else _channelId = (ulong)r.GetInt64(5);
|
else _channelId = (ulong)r.GetInt64(5);
|
||||||
}
|
}
|
||||||
|
@ -144,12 +144,14 @@ namespace Noikoio.RegexBot.Module.ModLogs
|
||||||
+ "id int primary key, "
|
+ "id int primary key, "
|
||||||
+ "entry_ts timestamptz not null, "
|
+ "entry_ts timestamptz not null, "
|
||||||
+ "guild_id bigint not null, "
|
+ "guild_id bigint not null, "
|
||||||
+ "target_id bigint not null, "
|
+ "target_id bigint not null, " // No foreign constraint: some targets may not be cached
|
||||||
+ $"invoke_id bigint null references {EntityCache.SqlHelper.TableUser}.user_id, "
|
+ "invoker_id bigint not null, "
|
||||||
+ "target_channel_id bigint null, " // TODO channel cache reference?
|
+ "target_channel_id bigint null, "
|
||||||
+ "entry_type integer not null, "
|
+ "entry_type integer not null, "
|
||||||
+ "message text 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();
|
c.ExecuteNonQuery();
|
||||||
}
|
}
|
||||||
using (var c = db.CreateCommand())
|
using (var c = db.CreateCommand())
|
||||||
|
@ -162,7 +164,7 @@ namespace Noikoio.RegexBot.Module.ModLogs
|
||||||
}
|
}
|
||||||
|
|
||||||
// Double-check constructor if making changes to this constant
|
// 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>
|
/// <summary>
|
||||||
/// Attempts to look up a log entry by its ID.
|
/// Attempts to look up a log entry by its ID.
|
||||||
|
|
Loading…
Reference in a new issue