diff --git a/Catalogger.Backend/Bot/Commands/InviteCommands.cs b/Catalogger.Backend/Bot/Commands/InviteCommands.cs index a0bfe4e..94199b5 100644 --- a/Catalogger.Backend/Bot/Commands/InviteCommands.cs +++ b/Catalogger.Backend/Bot/Commands/InviteCommands.cs @@ -32,6 +32,7 @@ public class InviteCommands( GuildCache guildCache, IInviteCache inviteCache, IDiscordRestChannelAPI channelApi, + IDiscordRestGuildAPI guildApi, FeedbackService feedbackService, ContextInjectionService contextInjection ) : CommandGroup @@ -43,7 +44,7 @@ public class InviteCommands( public async Task ListInvitesAsync() { var (userId, guildId) = contextInjection.GetUserAndGuild(); - var guildInvites = await inviteCache.TryGetAsync(guildId); + var guildInvites = await guildApi.GetGuildInvitesAsync(guildId).GetOrThrow(); if (!guildCache.TryGet(guildId, out var guild)) throw new CataloggerError("Guild not in cache"); diff --git a/Catalogger.Backend/Bot/Responders/Invites/InviteDeleteResponder.cs b/Catalogger.Backend/Bot/Responders/Invites/InviteDeleteResponder.cs new file mode 100644 index 0000000..2c5f02d --- /dev/null +++ b/Catalogger.Backend/Bot/Responders/Invites/InviteDeleteResponder.cs @@ -0,0 +1,85 @@ +using Catalogger.Backend.Cache; +using Catalogger.Backend.Database; +using Catalogger.Backend.Database.Queries; +using Catalogger.Backend.Extensions; +using Catalogger.Backend.Services; +using Microsoft.EntityFrameworkCore; +using Remora.Discord.API.Abstractions.Gateway.Events; +using Remora.Discord.API.Abstractions.Rest; +using Remora.Discord.Extensions.Embeds; +using Remora.Discord.Gateway.Responders; +using Remora.Results; + +namespace Catalogger.Backend.Bot.Responders.Invites; + +public class InviteDeleteResponder( + ILogger logger, + DatabaseContext db, + IInviteCache inviteCache, + WebhookExecutorService webhookExecutor, + IDiscordRestGuildAPI guildApi +) : IResponder +{ + private readonly ILogger _logger = logger.ForContext(); + + public async Task RespondAsync(IInviteDelete evt, CancellationToken ct = default) + { + var guildId = evt.GuildID.Value; + + var dbDeleteCount = await db + .Invites.Where(i => i.GuildId == guildId.Value && i.Code == evt.Code) + .ExecuteDeleteAsync(ct); + if (dbDeleteCount != 0) + _logger.Information( + "Deleted named invite {Invite} for guild {Guild}", + evt.Code, + guildId + ); + + var invite = (await inviteCache.TryGetAsync(guildId)).FirstOrDefault(i => + i.Code == evt.Code + ); + + var invitesResult = await guildApi.GetGuildInvitesAsync(guildId, ct); + if (!invitesResult.IsSuccess) + { + _logger.Error( + "Could not fetch new invites for guild {GuildId}: {Error}", + guildId, + invitesResult.Error + ); + } + else + { + await inviteCache.SetAsync(guildId, invitesResult.Entity); + } + + if (invite == null) + { + _logger.Debug("Could not find invite {Invite} in cache, ignoring event", evt.Code); + return Result.Success; + } + + var embed = new EmbedBuilder() + .WithTitle("Invite deleted") + .WithDescription($"An invite (**{evt.Code}**) for <#{evt.ChannelID}> was deleted.") + .WithColour(DiscordUtils.Red) + .WithFooter($"Code: {evt.Code}"); + + embed.AddField("Created by", invite.Inviter.GetOrThrow().PrettyFormat()); + embed.AddField("Uses", invite.Uses.ToString(), inline: true); + embed.AddField( + "Maximum uses", + invite.MaxUses != 0 ? invite.MaxUses.ToString() : "Infinite", + inline: true + ); + + var guildConfig = await db.GetGuildAsync(guildId, ct); + webhookExecutor.QueueLog( + guildConfig, + LogChannelType.InviteDelete, + embed.Build().GetOrThrow() + ); + return Result.Success; + } +} diff --git a/Catalogger.Backend/Bot/Responders/Messages/MessageDeleteResponder.cs b/Catalogger.Backend/Bot/Responders/Messages/MessageDeleteResponder.cs index 1a280c5..cf8e3d4 100644 --- a/Catalogger.Backend/Bot/Responders/Messages/MessageDeleteResponder.cs +++ b/Catalogger.Backend/Bot/Responders/Messages/MessageDeleteResponder.cs @@ -32,33 +32,33 @@ public class MessageDeleteResponder( private static bool MaybePkProxyTrigger(Snowflake id) => id.Timestamp > DateTimeOffset.Now - 1.Minutes(); - public async Task RespondAsync(IMessageDelete ev, CancellationToken ct = default) + public async Task RespondAsync(IMessageDelete evt, CancellationToken ct = default) { - if (!ev.GuildID.IsDefined()) + if (!evt.GuildID.IsDefined()) return Result.Success; - if (MaybePkProxyTrigger(ev.ID)) + if (MaybePkProxyTrigger(evt.ID)) { _logger.Debug( "Deleted message {MessageId} is less than 1 minute old, delaying 5 seconds to give PK time to catch up", - ev.ID + evt.ID ); await Task.Delay(5.Seconds(), ct); } - if (await messageRepository.IsMessageIgnoredAsync(ev.ID.Value, ct)) + if (await messageRepository.IsMessageIgnoredAsync(evt.ID.Value, ct)) return Result.Success; - var guild = await db.GetGuildAsync(ev.GuildID, ct); - if (guild.IsMessageIgnored(ev.ChannelID, ev.ID)) + var guild = await db.GetGuildAsync(evt.GuildID, ct); + if (guild.IsMessageIgnored(evt.ChannelID, evt.ID)) return Result.Success; var logChannel = webhookExecutor.GetLogChannel( guild, LogChannelType.MessageDelete, - ev.ChannelID + evt.ChannelID ); - var msg = await messageRepository.GetMessageAsync(ev.ID.Value, ct); + var msg = await messageRepository.GetMessageAsync(evt.ID.Value, ct); // Sometimes a message that *should* be logged isn't stored in the database, notify the user of that if (msg == null) { @@ -68,8 +68,8 @@ public class MessageDeleteResponder( logChannel.Value, new Embed( Title: "Message deleted", - Description: $"A message not found in the database was deleted in <#{ev.ChannelID}> ({ev.ChannelID}).", - Footer: new EmbedFooter(Text: $"ID: {ev.ID}"), + Description: $"A message not found in the database was deleted in <#{evt.ChannelID}> ({evt.ChannelID}).", + Footer: new EmbedFooter(Text: $"ID: {evt.ID}"), Timestamp: clock.GetCurrentInstant().ToDateTimeOffset() ) ); @@ -80,14 +80,14 @@ public class MessageDeleteResponder( // Check if the message is an edit trigger message. // If it is, the API will return a valid message for its ID, but the ID won't match either `Id` or `Original`. // (We also won't have any system/member information stored for it) - if (msg is { System: null, Member: null } && MaybePkProxyTrigger(ev.ID)) + if (msg is { System: null, Member: null } && MaybePkProxyTrigger(evt.ID)) { - var pkMsg = await pluralkitApi.GetPluralKitMessageAsync(ev.ID.Value, ct); - if (pkMsg != null && pkMsg.Id != ev.ID.Value && pkMsg.Original != ev.ID.Value) + var pkMsg = await pluralkitApi.GetPluralKitMessageAsync(evt.ID.Value, ct); + if (pkMsg != null && pkMsg.Id != evt.ID.Value && pkMsg.Original != evt.ID.Value) { _logger.Debug( "Deleted message {MessageId} is a `pk;edit` message, ignoring", - ev.ID + evt.ID ); return Result.Success; } @@ -96,7 +96,7 @@ public class MessageDeleteResponder( logChannel = webhookExecutor.GetLogChannel( guild, LogChannelType.MessageDelete, - ev.ChannelID, + evt.ChannelID, msg.UserId ); if (logChannel == null) @@ -108,7 +108,7 @@ public class MessageDeleteResponder( .WithDescription(msg.Content) .WithColour(DiscordUtils.Red) .WithFooter($"ID: {msg.Id}") - .WithTimestamp(ev.ID); + .WithTimestamp(evt.ID); if (user != null) builder.WithAuthor(user.Tag(), url: null, iconUrl: user.AvatarUrl()); @@ -116,7 +116,7 @@ public class MessageDeleteResponder( builder.WithTitle($"Message by {msg.Username} deleted"); string channelMention; - if (!channelCache.TryGet(ev.ChannelID, out var channel)) + if (!channelCache.TryGet(evt.ChannelID, out var channel)) channelMention = $"<#{msg.ChannelId}>"; else if ( channel.Type