From 5f24a6aa889c29699e2f9f927c5dc5b5f14cb817 Mon Sep 17 00:00:00 2001 From: sam Date: Tue, 5 Nov 2024 15:32:53 +0100 Subject: [PATCH] feat: store system UUIDs of banned users per guild --- Catalogger.Backend/Api/MetaController.cs | 4 ++ .../Responders/Guilds/GuildBanAddResponder.cs | 7 ++- .../Guilds/GuildBanRemoveResponder.cs | 58 ++++++++++++++----- .../Responders/Guilds/GuildCreateResponder.cs | 4 +- Catalogger.Backend/CataloggerMetrics.cs | 5 ++ .../Migrations/002_store_pk_systems.down.sql | 1 + .../Migrations/002_store_pk_systems.up.sql | 9 +++ .../Database/Repositories/GuildRepository.cs | 49 +++++++++++++++- .../Services/MetricsCollectionService.cs | 2 + 9 files changed, 121 insertions(+), 18 deletions(-) create mode 100644 Catalogger.Backend/Database/Migrations/002_store_pk_systems.down.sql create mode 100644 Catalogger.Backend/Database/Migrations/002_store_pk_systems.up.sql diff --git a/Catalogger.Backend/Api/MetaController.cs b/Catalogger.Backend/Api/MetaController.cs index 17d3c27..9d7a7b1 100644 --- a/Catalogger.Backend/Api/MetaController.cs +++ b/Catalogger.Backend/Api/MetaController.cs @@ -65,6 +65,10 @@ public class MetaController( ); } + [HttpGet("coffee")] + public IActionResult BrewCoffee() => + Problem("Sorry, I'm a teapot!", statusCode: StatusCodes.Status418ImATeapot); + private record MetaResponse( int Guilds, string InviteUrl, diff --git a/Catalogger.Backend/Bot/Responders/Guilds/GuildBanAddResponder.cs b/Catalogger.Backend/Bot/Responders/Guilds/GuildBanAddResponder.cs index 0100b1f..f20d8c2 100644 --- a/Catalogger.Backend/Bot/Responders/Guilds/GuildBanAddResponder.cs +++ b/Catalogger.Backend/Bot/Responders/Guilds/GuildBanAddResponder.cs @@ -76,7 +76,12 @@ public class GuildBanAddResponder( evt.GuildID ); - await guildRepository.BanSystemAsync(evt.GuildID, pkSystem.Id, pkSystem.Uuid); + await guildRepository.BanSystemAsync( + evt.GuildID, + evt.User.ID, + pkSystem.Id, + pkSystem.Uuid + ); } embed.AddField( diff --git a/Catalogger.Backend/Bot/Responders/Guilds/GuildBanRemoveResponder.cs b/Catalogger.Backend/Bot/Responders/Guilds/GuildBanRemoveResponder.cs index ee1d1bc..99fcce2 100644 --- a/Catalogger.Backend/Bot/Responders/Guilds/GuildBanRemoveResponder.cs +++ b/Catalogger.Backend/Bot/Responders/Guilds/GuildBanRemoveResponder.cs @@ -67,20 +67,52 @@ public class GuildBanRemoveResponder( var pkSystem = await pluralkitApi.GetPluralKitSystemAsync(evt.User.ID.Value, ct); if (pkSystem != null) { - await guildRepository.UnbanSystemAsync(evt.GuildID, pkSystem.Id, pkSystem.Uuid); - - embed.AddField( - "PluralKit system", - $""" - **ID:** {pkSystem.Id} - **UUID:** `{pkSystem.Uuid}` - **Name:** {pkSystem.Name ?? "*(none)*"} - **Tag:** {pkSystem.Tag ?? "*(none)*"} - - This system has been unbanned. - Note that other accounts linked to the system might still be banned, check `pk;system {pkSystem.Id}` for the linked accounts. - """ + await guildRepository.UnbanSystemAsync( + evt.GuildID, + evt.User.ID, + pkSystem.Id, + pkSystem.Uuid ); + + var systemUsers = await guildRepository.GetSystemAccountsAsync( + evt.GuildID, + pkSystem.Uuid + ); + if (systemUsers.Length == 0) + { + embed.AddField( + "PluralKit system", + $""" + **ID:** {pkSystem.Id} + **UUID:** `{pkSystem.Uuid}` + **Name:** {pkSystem.Name ?? "*(none)*"} + **Tag:** {pkSystem.Tag ?? "*(none)*"} + + This system has been unbanned. + Note that other accounts linked to the system might still be banned, check `pk;system {pkSystem.Id}` for the linked accounts. + """ + ); + } + else + { + var users = new List(); + foreach (var id in systemUsers) + users.Add("- " + await userCache.TryFormatUserAsync(id)); + + embed.AddField( + "PluralKit system", + $""" + **ID:** {pkSystem.Id} + **UUID:** `{pkSystem.Uuid}` + **Name:** {pkSystem.Name ?? "*(none)*"} + **Tag:** {pkSystem.Tag ?? "*(none)*"} + + This system has been unbanned. + Note that the following accounts are known to be linked to this system and banned from this server: + {string.Join("\n", users)} + """ + ); + } } webhookExecutor.QueueLog( diff --git a/Catalogger.Backend/Bot/Responders/Guilds/GuildCreateResponder.cs b/Catalogger.Backend/Bot/Responders/Guilds/GuildCreateResponder.cs index ab12125..789d072 100644 --- a/Catalogger.Backend/Bot/Responders/Guilds/GuildCreateResponder.cs +++ b/Catalogger.Backend/Bot/Responders/Guilds/GuildCreateResponder.cs @@ -107,14 +107,14 @@ public class GuildCreateResponder( } // Clear the cache for this guild - guildCache.Remove(evt.ID, out _); + var wasCached = guildCache.Remove(evt.ID, out var guild); emojiCache.Remove(evt.ID); channelCache.RemoveGuild(evt.ID); roleCache.RemoveGuild(evt.ID); await memberCache.RemoveAllMembersAsync(evt.ID); await inviteCache.RemoveAsync(evt.ID); - if (!guildCache.TryGet(evt.ID, out var guild)) + if (!wasCached || guild == null) { _logger.Information("Left uncached guild {GuildId}", evt.ID); return Result.Success; diff --git a/Catalogger.Backend/CataloggerMetrics.cs b/Catalogger.Backend/CataloggerMetrics.cs index b8726b5..1dc0cf0 100644 --- a/Catalogger.Backend/CataloggerMetrics.cs +++ b/Catalogger.Backend/CataloggerMetrics.cs @@ -39,6 +39,11 @@ public static class CataloggerMetrics "Number of channels in the cache" ); + public static readonly Gauge RolesCached = Metrics.CreateGauge( + "catalogger_cache_roles", + "Number of roles in the cache" + ); + public static readonly Gauge UsersCached = Metrics.CreateGauge( "catalogger_cache_users", "Number of users in the cache" diff --git a/Catalogger.Backend/Database/Migrations/002_store_pk_systems.down.sql b/Catalogger.Backend/Database/Migrations/002_store_pk_systems.down.sql new file mode 100644 index 0000000..c411602 --- /dev/null +++ b/Catalogger.Backend/Database/Migrations/002_store_pk_systems.down.sql @@ -0,0 +1 @@ +drop table pluralkit_systems; diff --git a/Catalogger.Backend/Database/Migrations/002_store_pk_systems.up.sql b/Catalogger.Backend/Database/Migrations/002_store_pk_systems.up.sql new file mode 100644 index 0000000..d095e36 --- /dev/null +++ b/Catalogger.Backend/Database/Migrations/002_store_pk_systems.up.sql @@ -0,0 +1,9 @@ +create table pluralkit_systems ( + system_id uuid not null, + user_id bigint not null, + guild_id bigint not null, + + primary key (system_id, user_id, guild_id) +); + +create index ix_pluralkit_systems_user_guild on pluralkit_systems (user_id, guild_id); diff --git a/Catalogger.Backend/Database/Repositories/GuildRepository.cs b/Catalogger.Backend/Database/Repositories/GuildRepository.cs index 79d950a..5c1b3f7 100644 --- a/Catalogger.Backend/Database/Repositories/GuildRepository.cs +++ b/Catalogger.Backend/Database/Repositories/GuildRepository.cs @@ -15,6 +15,8 @@ using Catalogger.Backend.Database.Models; using Dapper; +using Npgsql.Replication; +using Remora.Discord.API; using Remora.Rest.Core; namespace Catalogger.Backend.Database.Repositories; @@ -58,13 +60,30 @@ public class GuildRepository(ILogger logger, DatabaseConnection conn) new { Id = id, Channels = new Guild.ChannelConfig() } ); - public async Task BanSystemAsync(Snowflake guildId, string hid, Guid uuid) => + public async Task BanSystemAsync(Snowflake guildId, Snowflake userId, string hid, Guid uuid) + { await conn.ExecuteAsync( "update guilds set banned_systems = array_cat(banned_systems, @SystemIds) where id = @GuildId", new { GuildId = guildId.Value, SystemIds = (string[])[hid, uuid.ToString()] } ); - public async Task UnbanSystemAsync(Snowflake guildId, string hid, Guid uuid) => + await conn.ExecuteAsync( + """ + insert into pluralkit_systems (system_id, user_id, guild_id) + values (@SystemId, @UserId, @GuildId) + on conflict (system_id, user_id, guild_id) do nothing + """, + new + { + SystemId = uuid, + UserId = userId.Value, + GuildId = guildId.Value, + } + ); + } + + public async Task UnbanSystemAsync(Snowflake guildId, Snowflake userId, string hid, Guid uuid) + { await conn.ExecuteAsync( "update guilds set banned_systems = array_remove(array_remove(banned_systems, @Hid), @Uuid) where id = @Id", new @@ -75,6 +94,32 @@ public class GuildRepository(ILogger logger, DatabaseConnection conn) } ); + await conn.ExecuteAsync( + """ + delete from pluralkit_systems where system_id = @SystemId + and user_id = @UserId + and guild_id = @GuildId + """, + new + { + SystemId = uuid, + UserId = userId.Value, + GuildId = guildId.Value, + } + ); + } + + public async Task GetSystemAccountsAsync(Snowflake guildId, Guid systemId) + { + var bannedAccounts = await conn.QueryAsync( + "select * from pluralkit_systems where system_id = @SystemId and guild_id = @GuildId", + new { SystemId = systemId, GuildId = guildId.Value } + ); + return bannedAccounts.Select(s => DiscordSnowflake.New(s.UserId)).ToArray(); + } + + private record BannedSystem(Guid SystemId, ulong UserId, ulong GuildId); + public async Task AddKeyRoleAsync(Snowflake guildId, Snowflake roleId) => await conn.ExecuteAsync( "update guilds set key_roles = array_append(key_roles, @RoleId) where id = @GuildId", diff --git a/Catalogger.Backend/Services/MetricsCollectionService.cs b/Catalogger.Backend/Services/MetricsCollectionService.cs index 7ac9ffe..5c003a0 100644 --- a/Catalogger.Backend/Services/MetricsCollectionService.cs +++ b/Catalogger.Backend/Services/MetricsCollectionService.cs @@ -26,6 +26,7 @@ public class MetricsCollectionService( ILogger logger, GuildCache guildCache, ChannelCache channelCache, + RoleCache roleCache, UserCache userCache, EmojiCache emojiCache, IServiceProvider services @@ -44,6 +45,7 @@ public class MetricsCollectionService( CataloggerMetrics.GuildsCached.Set(guildCache.Size); CataloggerMetrics.ChannelsCached.Set(channelCache.Size); + CataloggerMetrics.RolesCached.Set(roleCache.Size); CataloggerMetrics.UsersCached.Set(userCache.Size); CataloggerMetrics.EmojiCached.Set(emojiCache.Size); CataloggerMetrics.MessagesStored.Set(messageCount);