diff --git a/PluralKit.Bot/Commands/MessageEdit.cs b/PluralKit.Bot/Commands/MessageEdit.cs index 146aaee3..298021c8 100644 --- a/PluralKit.Bot/Commands/MessageEdit.cs +++ b/PluralKit.Bot/Commands/MessageEdit.cs @@ -51,10 +51,12 @@ namespace PluralKit.Bot var newContent = ctx.RemainderOrNull(); var originalMsg = await _rest.GetMessage(msg.Message.Channel, msg.Message.Mid); + if (originalMsg == null) + throw new PKError("Could not edit message."); try { - await _webhookExecutor.EditWebhookMessage(msg.Message.Channel, msg.Message.Mid, newContent); + var editedMsg = await _webhookExecutor.EditWebhookMessage(msg.Message.Channel, msg.Message.Mid, newContent); if (ctx.Guild == null) await _rest.CreateReaction(ctx.Channel.Id, ctx.Message.Id, new() { Name = Emojis.Success }); @@ -62,7 +64,7 @@ namespace PluralKit.Bot if (ctx.BotPermissions.HasFlag(PermissionSet.ManageMessages)) await _rest.DeleteMessage(ctx.Channel.Id, ctx.Message.Id); - await _logChannel.LogEditedMessage(ctx.MessageContext, msg, ctx.Message, originalMsg!, newContent); + await _logChannel.LogMessage(ctx.MessageContext, msg.Message, ctx.Message, editedMsg, originalMsg!.Content!); } catch (NotFoundException) { diff --git a/PluralKit.Bot/Proxy/ProxyService.cs b/PluralKit.Bot/Proxy/ProxyService.cs index 9ab3c995..b9a570ef 100644 --- a/PluralKit.Bot/Proxy/ProxyService.cs +++ b/PluralKit.Bot/Proxy/ProxyService.cs @@ -289,7 +289,7 @@ namespace PluralKit.Bot Message triggerMessage, Message proxyMessage, ProxyMatch match) { - Task SaveMessageInDatabase() => _repo.AddMessage(conn, new PKMessage + var sentMessage = new PKMessage { Channel = triggerMessage.ChannelId, Guild = triggerMessage.GuildId, @@ -297,9 +297,11 @@ namespace PluralKit.Bot Mid = proxyMessage.Id, OriginalMid = triggerMessage.Id, Sender = triggerMessage.Author.Id - }); + }; + + Task SaveMessageInDatabase() => _repo.AddMessage(conn, sentMessage); - Task LogMessageToChannel() => _logChannel.LogMessage(ctx, match, triggerMessage, proxyMessage.Id).AsTask(); + Task LogMessageToChannel() => _logChannel.LogMessage(ctx, sentMessage, triggerMessage, proxyMessage).AsTask(); async Task DeleteProxyTriggerMessage() { diff --git a/PluralKit.Bot/Services/EmbedService.cs b/PluralKit.Bot/Services/EmbedService.cs index 6cd8c177..9a13ec47 100644 --- a/PluralKit.Bot/Services/EmbedService.cs +++ b/PluralKit.Bot/Services/EmbedService.cs @@ -108,30 +108,23 @@ namespace PluralKit.Bot { return eb.Build(); } - public Embed CreateLoggedMessageEmbed(PKSystem system, PKMember member, ulong messageId, ulong originalMsgId, User sender, string content, Channel channel) { + public Embed CreateLoggedMessageEmbed(Message triggerMessage, Message proxiedMessage, string systemHid, PKMember member, string channelName, string oldContent = null) { // TODO: pronouns in ?-reacted response using this card - var timestamp = DiscordUtils.SnowflakeToInstant(messageId); - var name = member.NameFor(LookupContext.ByNonOwner); - return new EmbedBuilder() - .Author(new($"#{channel.Name}: {name}", IconUrl: DiscordUtils.WorkaroundForUrlBug(member.AvatarFor(LookupContext.ByNonOwner).TryGetCleanCdnUrl()))) - .Thumbnail(new(member.AvatarFor(LookupContext.ByNonOwner).TryGetCleanCdnUrl())) - .Description(content?.NormalizeLineEndSpacing()) - .Footer(new($"System ID: {system.Hid} | Member ID: {member.Hid} | Sender: {sender.Username}#{sender.Discriminator} ({sender.Id}) | Message ID: {messageId} | Original Message ID: {originalMsgId}")) - .Timestamp(timestamp.ToDateTimeOffset().ToString("O")) - .Build(); - } + var timestamp = DiscordUtils.SnowflakeToInstant(proxiedMessage.Id); + var name = proxiedMessage.Author.Username; + // sometimes Discord will just... not return the avatar hash with webhook messages + var avatar = proxiedMessage.Author.Avatar != null ? proxiedMessage.Author.AvatarUrl() : member.AvatarFor(LookupContext.ByNonOwner); + var embed = new EmbedBuilder() + .Author(new($"#{channelName}: {name}", IconUrl: avatar)) + .Thumbnail(new(avatar)) + .Description(proxiedMessage.Content?.NormalizeLineEndSpacing()) + .Footer(new($"System ID: {systemHid} | Member ID: {member.Hid} | Sender: {triggerMessage.Author.Username}#{triggerMessage.Author.Discriminator} ({triggerMessage.Author.Id}) | Message ID: {proxiedMessage.Id} | Original Message ID: {triggerMessage.Id}")) + .Timestamp(timestamp.ToDateTimeOffset().ToString("O")); - public Embed CreateEditedMessageEmbed(PKSystem system, PKMember member, ulong messageId, ulong originalMsgId, User sender, string content, string oldContent, Channel channel) { - var timestamp = DiscordUtils.SnowflakeToInstant(messageId); - var name = member.NameFor(LookupContext.ByNonOwner); - return new EmbedBuilder() - .Author(new($"[Edited] #{channel.Name}: {name}", IconUrl: DiscordUtils.WorkaroundForUrlBug(member.AvatarFor(LookupContext.ByNonOwner).TryGetCleanCdnUrl()))) - .Thumbnail(new(member.AvatarFor(LookupContext.ByNonOwner).TryGetCleanCdnUrl())) - .Field(new("Old message", oldContent?.NormalizeLineEndSpacing().Truncate(1000))) - .Description(content?.NormalizeLineEndSpacing()) - .Footer(new($"System ID: {system.Hid} | Member ID: {member.Hid} | Sender: {sender.Username}#{sender.Discriminator} ({sender.Id}) | Message ID: {messageId} | Original Message ID: {originalMsgId}")) - .Timestamp(timestamp.ToDateTimeOffset().ToString("O")) - .Build(); + if (oldContent != null) + embed.Field(new("Old message", oldContent?.NormalizeLineEndSpacing().Truncate(1000))); + + return embed.Build(); } public async Task CreateMemberEmbed(PKSystem system, PKMember member, Guild guild, LookupContext ctx) diff --git a/PluralKit.Bot/Services/LogChannelService.cs b/PluralKit.Bot/Services/LogChannelService.cs index b2880024..64b9039e 100644 --- a/PluralKit.Bot/Services/LogChannelService.cs +++ b/PluralKit.Bot/Services/LogChannelService.cs @@ -33,54 +33,45 @@ namespace PluralKit.Bot { _logger = logger.ForContext(); } - public async ValueTask LogMessage(MessageContext ctx, ProxyMatch proxy, Message trigger, ulong hookMessage) + public async ValueTask LogMessage(MessageContext ctx, PKMessage proxiedMessage, Message trigger, Message hookMessage, string oldContent = null) { - var logChannel = await GetAndCheckLogChannel(ctx, trigger); - if (logChannel == null) return; + var logChannel = await GetAndCheckLogChannel(ctx, trigger, proxiedMessage); + if (logChannel == null) + return; - var triggerChannel = _cache.GetChannel(trigger.ChannelId); + var triggerChannel = _cache.GetChannel(proxiedMessage.Channel); - // Send embed! await using var conn = await _db.Obtain(); - var embed = _embed.CreateLoggedMessageEmbed(await _repo.GetSystem(conn, ctx.SystemId.Value), - await _repo.GetMember(conn, proxy.Member.Id), hookMessage, trigger.Id, trigger.Author, proxy.Content, - triggerChannel); - var url = $"https://discord.com/channels/{trigger.GuildId}/{trigger.ChannelId}/{hookMessage}"; + var system = await _repo.GetSystem(conn, ctx.SystemId.Value); + var member = await _repo.GetMember(conn, proxiedMessage.Member); + + // Send embed! + var embed = _embed.CreateLoggedMessageEmbed(trigger, hookMessage, system.Hid, member, triggerChannel.Name, oldContent); + var url = $"https://discord.com/channels/{proxiedMessage.Guild.Value}/{proxiedMessage.Channel}/{proxiedMessage.Mid}"; await _rest.CreateMessage(logChannel.Id, new() {Content = url, Embed = embed}); } - public async ValueTask LogEditedMessage(MessageContext ctx, FullMessage proxy, Message trigger, Message originalMessage, string newContent) + private async Task GetAndCheckLogChannel(MessageContext ctx, Message trigger, PKMessage proxiedMessage) { - var logChannel = await GetAndCheckLogChannel(ctx, trigger, proxy.Message); - if (logChannel == null) return; + if (proxiedMessage.Guild == null && proxiedMessage.Channel != trigger.ChannelId) + // a very old message is being edited outside of its original channel + // we can't know if we're in the correct guild, so skip fetching a log channel + return null; - var triggerChannel = _cache.GetChannel(proxy.Message.Channel); - - // Send embed! - await using var conn = await _db.Obtain(); - var embed = _embed.CreateEditedMessageEmbed(proxy.System, proxy.Member, originalMessage.Id, trigger.Id, trigger.Author, newContent, originalMessage.Content, - triggerChannel); - var url = $"https://discord.com/channels/{proxy.Message.Guild.Value}/{proxy.Message.Channel}/{proxy.Message.Mid}"; - await _rest.CreateMessage(logChannel.Id, new() {Content = url, Embed = embed}); - } - - private async Task GetAndCheckLogChannel(MessageContext ctx, Message trigger, PKMessage original = null) - { - var guildId = trigger.GuildId != null ? trigger.GuildId!.Value : original.Guild.Value; + var guildId = proxiedMessage.Guild ?? trigger.GuildId.Value; var logChannelId = ctx.LogChannel; var isBlacklisted = ctx.InLogBlacklist; - if (original != null) + if (proxiedMessage.Guild != trigger.GuildId) { - // we're editing a message, get log channel info from the database - var guild = await _db.Execute(c => _repo.GetGuild(c, original.Guild.Value)); + // we're editing a message from a different server, get log channel info from the database + var guild = await _db.Execute(c => _repo.GetGuild(c, proxiedMessage.Guild.Value)); logChannelId = guild.LogChannel; isBlacklisted = guild.Blacklist.Any(x => x == logChannelId); } if (ctx.SystemId == null || logChannelId == null || isBlacklisted) return null; - // Find log channel and check if valid var logChannel = await FindLogChannel(guildId, logChannelId.Value); if (logChannel == null || logChannel.Type != Channel.ChannelType.GuildText) return null; @@ -90,7 +81,7 @@ namespace PluralKit.Bot { if (!perms.HasFlag(PermissionSet.SendMessages | PermissionSet.EmbedLinks)) { _logger.Information( - "Does not have permission to proxy log, ignoring (channel: {ChannelId}, guild: {GuildId}, bot permissions: {BotPermissions})", + "Does not have permission to log proxy, ignoring (channel: {ChannelId}, guild: {GuildId}, bot permissions: {BotPermissions})", ctx.LogChannel.Value, trigger.GuildId!.Value, perms); return null; }