fix message edit embed, ignore pk;edit triggers

This commit is contained in:
sam 2024-08-16 17:03:26 +02:00
parent 8231c57bdf
commit 7ea945b427
Signed by: sam
GPG key ID: 5F3C3C1B3166639D
6 changed files with 63 additions and 15 deletions

View file

@ -1,9 +1,13 @@
using System.Drawing; using System.Drawing;
using Remora.Discord.API;
using Remora.Rest.Core;
namespace Catalogger.Backend.Bot; namespace Catalogger.Backend.Bot;
public static class DiscordUtils public static class DiscordUtils
{ {
public static readonly Snowflake PkUserId = DiscordSnowflake.New(466378653216014359);
public static readonly Color Red = Color.FromArgb(231, 76, 60); public static readonly Color Red = Color.FromArgb(231, 76, 60);
public static readonly Color Purple = Color.FromArgb(155, 89, 182); public static readonly Color Purple = Color.FromArgb(155, 89, 182);
} }

View file

@ -26,7 +26,6 @@ public class MessageCreateResponder(
: IResponder<IMessageCreate> : IResponder<IMessageCreate>
{ {
private readonly ILogger _logger = logger.ForContext<MessageCreateResponder>(); private readonly ILogger _logger = logger.ForContext<MessageCreateResponder>();
private static readonly Snowflake PkUserId = DiscordSnowflake.New(466378653216014359);
public async Task<Result> RespondAsync(IMessageCreate msg, CancellationToken ct = default) public async Task<Result> RespondAsync(IMessageCreate msg, CancellationToken ct = default)
{ {
@ -50,11 +49,11 @@ public class MessageCreateResponder(
return Result.Success; return Result.Success;
} }
if (msg.Author.ID == PkUserId) if (msg.Author.ID == DiscordUtils.PkUserId)
_ = pkMessageHandler.HandlePkMessageAsync(msg); _ = pkMessageHandler.HandlePkMessageAsync(msg);
if (msg.ApplicationID.IsDefined(out var appId) && appId == PkUserId) if (msg.ApplicationID.Is(DiscordUtils.PkUserId))
_ = pkMessageHandler.HandleProxiedMessageAsync(msg.ID.Value); _ = pkMessageHandler.HandleProxiedMessageAsync(msg.ID.Value);
else if (msg.ApplicationID.HasValue && appId == config.Discord.ApplicationId) else if (msg.ApplicationID.HasValue && msg.ApplicationID.Is(config.Discord.ApplicationId))
{ {
db.IgnoredMessages.Add(new IgnoredMessage(msg.ID.Value)); db.IgnoredMessages.Add(new IgnoredMessage(msg.ID.Value));
await db.SaveChangesAsync(ct); await db.SaveChangesAsync(ct);

View file

@ -4,6 +4,7 @@ using Catalogger.Backend.Database.Queries;
using Catalogger.Backend.Extensions; using Catalogger.Backend.Extensions;
using Catalogger.Backend.Services; using Catalogger.Backend.Services;
using Humanizer; using Humanizer;
using Microsoft.VisualBasic;
using NodaTime; using NodaTime;
using Remora.Discord.API; using Remora.Discord.API;
using Remora.Discord.API.Abstractions.Gateway.Events; using Remora.Discord.API.Abstractions.Gateway.Events;
@ -11,6 +12,7 @@ using Remora.Discord.API.Abstractions.Objects;
using Remora.Discord.API.Objects; using Remora.Discord.API.Objects;
using Remora.Discord.Extensions.Embeds; using Remora.Discord.Extensions.Embeds;
using Remora.Discord.Gateway.Responders; using Remora.Discord.Gateway.Responders;
using Remora.Rest.Core;
using Remora.Results; using Remora.Results;
namespace Catalogger.Backend.Bot.Responders; namespace Catalogger.Backend.Bot.Responders;
@ -22,15 +24,18 @@ public class MessageDeleteResponder(
WebhookExecutorService webhookExecutor, WebhookExecutorService webhookExecutor,
ChannelCacheService channelCache, ChannelCacheService channelCache,
UserCacheService userCache, UserCacheService userCache,
IClock clock) : IResponder<IMessageDelete> IClock clock,
PluralkitApiService pluralkitApi) : IResponder<IMessageDelete>
{ {
private readonly ILogger _logger = logger.ForContext<MessageDeleteResponder>(); private readonly ILogger _logger = logger.ForContext<MessageDeleteResponder>();
private static bool MaybePkProxyTrigger(Snowflake id) => id.Timestamp > DateTimeOffset.Now - 1.Minutes();
public async Task<Result> RespondAsync(IMessageDelete ev, CancellationToken ct = default) public async Task<Result> RespondAsync(IMessageDelete ev, CancellationToken ct = default)
{ {
if (!ev.GuildID.IsDefined()) return Result.Success; if (!ev.GuildID.IsDefined()) return Result.Success;
if (ev.ID.Timestamp < DateTimeOffset.Now - 1.Minutes()) if (MaybePkProxyTrigger(ev.ID))
{ {
_logger.Debug( _logger.Debug(
"Deleted message {MessageId} is less than 1 minute old, delaying 5 seconds to give PK time to catch up", "Deleted message {MessageId} is less than 1 minute old, delaying 5 seconds to give PK time to catch up",
@ -59,6 +64,20 @@ public class MessageDeleteResponder(
return Result.Success; return Result.Success;
} }
// 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) && false)
{
// TODO: remove the "false" if/when the API is updated to actually return this :neofox_woozy:
var pkMsg = await pluralkitApi.GetPluralKitMessageAsync(ev.ID.Value, ct);
if (pkMsg != null && pkMsg.Id != ev.ID.Value && pkMsg.Original != ev.ID.Value)
{
_logger.Debug("Deleted message {MessageId} is a `pk;edit` message, ignoring", ev.ID);
return Result.Success;
}
}
logChannel = webhookExecutor.GetLogChannel(guild, LogChannelType.MessageDelete, ev.ChannelID, msg.UserId); logChannel = webhookExecutor.GetLogChannel(guild, LogChannelType.MessageDelete, ev.ChannelID, msg.UserId);
if (logChannel == null) return Result.Success; if (logChannel == null) return Result.Success;

View file

@ -20,7 +20,8 @@ public class MessageUpdateResponder(
ChannelCacheService channelCache, ChannelCacheService channelCache,
UserCacheService userCache, UserCacheService userCache,
MessageRepository messageRepository, MessageRepository messageRepository,
WebhookExecutorService webhookExecutor) : IResponder<IMessageUpdate> WebhookExecutorService webhookExecutor,
PluralkitApiService pluralkitApi) : IResponder<IMessageUpdate>
{ {
private readonly ILogger _logger = logger.ForContext<MessageUpdateResponder>(); private readonly ILogger _logger = logger.ForContext<MessageUpdateResponder>();
@ -76,8 +77,7 @@ public class MessageUpdateResponder(
.WithFooter($"ID: {msg.ID}") .WithFooter($"ID: {msg.ID}")
.WithTimestamp(msg.ID.Timestamp); .WithTimestamp(msg.ID.Timestamp);
var fields = Enumerable.Range(0, msg.Content.Length / 1000) var fields = ChunksUpTo(msg.Content, 1000)
.Select(i => msg.Content.Substring(i * 1000, 1000))
.Select<string, IEmbedField>((s, i) => .Select<string, IEmbedField>((s, i) =>
new EmbedField($"New content{(i != 0 ? " (cont.)" : "")}", s, false)) new EmbedField($"New content{(i != 0 ? " (cont.)" : "")}", s, false))
.ToList(); .ToList();
@ -110,7 +110,16 @@ public class MessageUpdateResponder(
} }
finally finally
{ {
await messageRepository.UpdateMessageAsync(msg, ct); if (!await messageRepository.UpdateMessageAsync(msg, ct) && msg.ApplicationID.Is(DiscordUtils.PkUserId))
{
_logger.Debug(
"Message {MessageId} wasn't stored yet and was proxied by PluralKit, fetching proxy information from its API",
msg.ID);
var pkMsg = await pluralkitApi.GetPluralKitMessageAsync(msg.ID.Value, ct);
if (pkMsg != null)
await messageRepository.SetProxiedMessageDataAsync(msg.ID.Value, pkMsg.Original, pkMsg.Sender,
pkMsg.System?.Id, pkMsg.Member?.Id);
}
} }
} }
@ -122,4 +131,10 @@ public class MessageUpdateResponder(
evt.WebhookID, evt.Type.GetOrThrow(), evt.Activity, evt.Application, evt.ApplicationID, evt.MessageReference, evt.WebhookID, evt.Type.GetOrThrow(), evt.Activity, evt.Application, evt.ApplicationID, evt.MessageReference,
evt.Flags, evt.ReferencedMessage, evt.Interaction, evt.Thread, evt.Components, evt.StickerItems, evt.Position, evt.Flags, evt.ReferencedMessage, evt.Interaction, evt.Thread, evt.Components, evt.StickerItems, evt.Position,
evt.Resolved, evt.InteractionMetadata, evt.Poll); evt.Resolved, evt.InteractionMetadata, evt.Poll);
private static IEnumerable<string> ChunksUpTo(string str, int maxChunkSize)
{
for (var i = 0; i < str.Length; i += maxChunkSize)
yield return str.Substring(i, Math.Min(maxChunkSize, str.Length - i));
}
} }

View file

@ -36,7 +36,12 @@ public class MessageRepository(ILogger logger, DatabaseContext db, IEncryptionSe
await db.SaveChangesAsync(ct); await db.SaveChangesAsync(ct);
} }
public async Task UpdateMessageAsync(IMessageCreate msg, CancellationToken ct = default) /// <summary>
/// Updates an edited message.
/// </summary>
/// <returns>true if the message was already stored and got updated,
/// false if the message wasn't stored and was newly inserted.</returns>
public async Task<bool> UpdateMessageAsync(IMessageCreate msg, CancellationToken ct = default)
{ {
_logger.Debug("Updating message {MessageId}", msg.ID); _logger.Debug("Updating message {MessageId}", msg.ID);
@ -44,13 +49,16 @@ public class MessageRepository(ILogger logger, DatabaseContext db, IEncryptionSe
var (isStored, _) = await HasProxyInfoAsync(msg.ID.Value); var (isStored, _) = await HasProxyInfoAsync(msg.ID.Value);
if (!isStored) if (!isStored)
{ {
_logger.Debug("Edited message {MessageId} is not stored yet, storing it", msg.ID);
await SaveMessageAsync(msg, ct); await SaveMessageAsync(msg, ct);
await tx.CommitAsync(ct);
return false;
} }
else else
{ {
var metadata = new Metadata(IsWebhook: msg.WebhookID.HasValue, var metadata = new Metadata(IsWebhook: msg.WebhookID.HasValue,
msg.Attachments.Select(a => new Attachment(a.Filename, a.Size, a.ContentType.Value))); msg.Attachments.Select(a => new Attachment(a.Filename, a.Size, a.ContentType.Value)));
var dbMsg = await db.Messages.FindAsync(msg.ID.Value); var dbMsg = await db.Messages.FindAsync(msg.ID.Value);
if (dbMsg == null) throw new CataloggerError("Message was null despite HasProxyInfoAsync returning true"); if (dbMsg == null) throw new CataloggerError("Message was null despite HasProxyInfoAsync returning true");
@ -62,9 +70,9 @@ public class MessageRepository(ILogger logger, DatabaseContext db, IEncryptionSe
db.Update(dbMsg); db.Update(dbMsg);
await db.SaveChangesAsync(ct); await db.SaveChangesAsync(ct);
await tx.CommitAsync(ct);
return true;
} }
await tx.CommitAsync(ct);
} }
public async Task<Message?> GetMessageAsync(ulong id, CancellationToken ct = default) public async Task<Message?> GetMessageAsync(ulong id, CancellationToken ct = default)

View file

@ -26,7 +26,7 @@ public static class DiscordExtensions
var avatarIndex = user.Discriminator == 0 ? (int)((user.ID.Value >> 22) % 6) : user.Discriminator % 5; var avatarIndex = user.Discriminator == 0 ? (int)((user.ID.Value >> 22) % 6) : user.Discriminator % 5;
return $"https://cdn.discordapp.com/embed/avatars/{avatarIndex}.png?size={size}"; return $"https://cdn.discordapp.com/embed/avatars/{avatarIndex}.png?size={size}";
} }
public static string? IconUrl(this IGuild guild, int size = 256) public static string? IconUrl(this IGuild guild, int size = 256)
{ {
if (guild.Icon == null) return null; if (guild.Icon == null) return null;
@ -44,6 +44,9 @@ public static class DiscordExtensions
return snowflake.Value.Value; return snowflake.Value.Value;
} }
public static bool Is(this Optional<Snowflake> s1, Snowflake s2) => s1.IsDefined(out var value) && value == s2;
public static bool Is(this Optional<Snowflake> s1, ulong s2) => s1.IsDefined(out var value) && value == s2;
public static T GetOrThrow<T>(this Result<T> result) public static T GetOrThrow<T>(this Result<T> result)
{ {
if (result.Error != null) throw new DiscordRestException(result.Error.Message); if (result.Error != null) throw new DiscordRestException(result.Error.Message);