From b660edfe78e0202137b178f0c3f4a91e473a868b Mon Sep 17 00:00:00 2001 From: Tyrrrz <1935960+Tyrrrz@users.noreply.github.com> Date: Fri, 27 Feb 2026 17:30:06 +0200 Subject: [PATCH] Clean up after last PR --- .../Discord/Data/Message.cs | 32 ++++++------- .../Discord/Data/MessageReference.cs | 15 +++---- .../Discord/Data/MessageSnapshot.cs | 45 +++++++------------ .../Exporting/JsonMessageWriter.cs | 14 +++--- .../Exporting/MessageGroupTemplate.cshtml | 19 ++++---- .../Exporting/PlainTextMessageWriter.cs | 3 -- .../Utils/Extensions/CollectionExtensions.cs | 13 ++++++ 7 files changed, 69 insertions(+), 72 deletions(-) diff --git a/DiscordChatExporter.Core/Discord/Data/Message.cs b/DiscordChatExporter.Core/Discord/Data/Message.cs index 81496ef4..ad568695 100644 --- a/DiscordChatExporter.Core/Discord/Data/Message.cs +++ b/DiscordChatExporter.Core/Discord/Data/Message.cs @@ -27,10 +27,16 @@ public partial record Message( IReadOnlyList MentionedUsers, MessageReference? Reference, Message? ReferencedMessage, - Interaction? Interaction, - MessageSnapshot? ForwardedMessage + MessageSnapshot? ForwardedMessage, + Interaction? Interaction ) : IHasId { + public bool IsEmpty { get; } = + string.IsNullOrWhiteSpace(Content) + && !Attachments.Any() + && !Embeds.Any() + && !Stickers.Any(); + public bool IsSystemNotification { get; } = Kind is >= MessageKind.RecipientAdd and <= MessageKind.ThreadCreated; @@ -39,15 +45,8 @@ public partial record Message( // App interactions are rendered as replies in the Discord client, but they are not actually replies public bool IsReplyLike => IsReply || Interaction is not null; - // A message is a forward if its reference type is Forward public bool IsForwarded { get; } = Reference?.Kind == MessageReferenceKind.Forward; - public bool IsEmpty { get; } = - string.IsNullOrWhiteSpace(Content) - && !Attachments.Any() - && !Embeds.Any() - && !Stickers.Any(); - public IEnumerable GetReferencedUsers() { yield return Author; @@ -175,16 +174,19 @@ public partial record Message var messageReference = json.GetPropertyOrNull("message_reference") ?.Pipe(MessageReference.Parse); - var referencedMessage = json.GetPropertyOrNull("referenced_message")?.Pipe(Parse); - var interaction = json.GetPropertyOrNull("interaction")?.Pipe(Interaction.Parse); - // Parse message snapshots for forwarded messages + var referencedMessage = json.GetPropertyOrNull("referenced_message")?.Pipe(Parse); + // Currently Discord only supports 1 snapshot per forward var forwardedMessage = json.GetPropertyOrNull("message_snapshots") ?.EnumerateArrayOrNull() - ?.Select(MessageSnapshot.Parse) + ?.Select(j => j.GetPropertyOrNull("message")) + .WhereNotNull() + .Select(MessageSnapshot.Parse) .FirstOrDefault(); + var interaction = json.GetPropertyOrNull("interaction")?.Pipe(Interaction.Parse); + return new Message( id, kind, @@ -202,8 +204,8 @@ public partial record Message mentionedUsers, messageReference, referencedMessage, - interaction, - forwardedMessage + forwardedMessage, + interaction ); } } diff --git a/DiscordChatExporter.Core/Discord/Data/MessageReference.cs b/DiscordChatExporter.Core/Discord/Data/MessageReference.cs index af024f4c..73374d6a 100644 --- a/DiscordChatExporter.Core/Discord/Data/MessageReference.cs +++ b/DiscordChatExporter.Core/Discord/Data/MessageReference.cs @@ -6,14 +6,17 @@ namespace DiscordChatExporter.Core.Discord.Data; // https://discord.com/developers/docs/resources/channel#message-object-message-reference-structure public record MessageReference( + MessageReferenceKind Kind, Snowflake? MessageId, Snowflake? ChannelId, - Snowflake? GuildId, - MessageReferenceKind Kind -) + Snowflake? GuildId) { public static MessageReference Parse(JsonElement json) { + var kind = + json.GetPropertyOrNull("type")?.GetInt32OrNull()?.Pipe(t => (MessageReferenceKind)t) + ?? MessageReferenceKind.Default; + var messageId = json.GetPropertyOrNull("message_id") ?.GetNonWhiteSpaceStringOrNull() ?.Pipe(Snowflake.Parse); @@ -26,10 +29,6 @@ public record MessageReference( ?.GetNonWhiteSpaceStringOrNull() ?.Pipe(Snowflake.Parse); - var kind = - json.GetPropertyOrNull("type")?.GetInt32OrNull()?.Pipe(t => (MessageReferenceKind)t) - ?? MessageReferenceKind.Default; - - return new MessageReference(messageId, channelId, guildId, kind); + return new MessageReference(kind, messageId, channelId, guildId); } } diff --git a/DiscordChatExporter.Core/Discord/Data/MessageSnapshot.cs b/DiscordChatExporter.Core/Discord/Data/MessageSnapshot.cs index c56b703c..db0d1722 100644 --- a/DiscordChatExporter.Core/Discord/Data/MessageSnapshot.cs +++ b/DiscordChatExporter.Core/Discord/Data/MessageSnapshot.cs @@ -3,31 +3,34 @@ using System.Collections.Generic; using System.Linq; using System.Text.Json; using DiscordChatExporter.Core.Discord.Data.Embeds; -using DiscordChatExporter.Core.Utils.Extensions; using JsonExtensions.Reading; namespace DiscordChatExporter.Core.Discord.Data; -// https://discord.com/developers/docs/resources/channel#message-snapshot-object +// https://docs.discord.com/developers/resources/message#message-snapshot-object // Message snapshots contain a subset of message fields for forwarded messages public record MessageSnapshot( + DateTimeOffset Timestamp, + DateTimeOffset? EditedTimestamp, string Content, IReadOnlyList Attachments, IReadOnlyList Embeds, - IReadOnlyList Stickers, - DateTimeOffset Timestamp, - DateTimeOffset? EditedTimestamp -) + IReadOnlyList Stickers) { public static MessageSnapshot Parse(JsonElement json) { - // The message snapshot has a "message" property containing the actual message data - var messageJson = json.GetPropertyOrNull("message") ?? json; + var timestamp = + json.GetPropertyOrNull("timestamp")?.GetDateTimeOffsetOrNull() + ?? DateTimeOffset.MinValue; - var content = messageJson.GetPropertyOrNull("content")?.GetStringOrNull() ?? ""; + var editedTimestamp = json + .GetPropertyOrNull("edited_timestamp") + ?.GetDateTimeOffsetOrNull(); + + var content = json.GetPropertyOrNull("content")?.GetStringOrNull() ?? ""; var attachments = - messageJson + json .GetPropertyOrNull("attachments") ?.EnumerateArrayOrNull() ?.Select(Attachment.Parse) @@ -35,7 +38,7 @@ public record MessageSnapshot( ?? []; var embeds = - messageJson + json .GetPropertyOrNull("embeds") ?.EnumerateArrayOrNull() ?.Select(Embed.Parse) @@ -43,28 +46,14 @@ public record MessageSnapshot( ?? []; var stickers = - messageJson + json .GetPropertyOrNull("sticker_items") ?.EnumerateArrayOrNull() ?.Select(Sticker.Parse) .ToArray() ?? []; - var timestamp = - messageJson.GetPropertyOrNull("timestamp")?.GetDateTimeOffsetOrNull() - ?? DateTimeOffset.MinValue; - - var editedTimestamp = messageJson - .GetPropertyOrNull("edited_timestamp") - ?.GetDateTimeOffsetOrNull(); - - return new MessageSnapshot( - content, - attachments, - embeds, - stickers, - timestamp, - editedTimestamp - ); + return new MessageSnapshot(timestamp, + editedTimestamp, content, attachments, embeds, stickers); } } diff --git a/DiscordChatExporter.Core/Exporting/JsonMessageWriter.cs b/DiscordChatExporter.Core/Exporting/JsonMessageWriter.cs index 5b959da7..2c0618c4 100644 --- a/DiscordChatExporter.Core/Exporting/JsonMessageWriter.cs +++ b/DiscordChatExporter.Core/Exporting/JsonMessageWriter.cs @@ -539,22 +539,20 @@ internal class JsonMessageWriter(Stream stream, ExportContext context) { _writer.WriteStartObject("forwardedMessage"); - _writer.WriteString( - "content", - await FormatMarkdownAsync(message.ForwardedMessage.Content, cancellationToken) - ); - _writer.WriteString( "timestamp", - message.ForwardedMessage.Timestamp != DateTimeOffset.MinValue - ? Context.NormalizeDate(message.ForwardedMessage.Timestamp) - : null + Context.NormalizeDate(message.ForwardedMessage.Timestamp) ); _writer.WriteString( "timestampEdited", message.ForwardedMessage.EditedTimestamp?.Pipe(Context.NormalizeDate) ); + + _writer.WriteString( + "content", + await FormatMarkdownAsync(message.ForwardedMessage.Content, cancellationToken) + ); // Forwarded attachments _writer.WriteStartArray("attachments"); diff --git a/DiscordChatExporter.Core/Exporting/MessageGroupTemplate.cshtml b/DiscordChatExporter.Core/Exporting/MessageGroupTemplate.cshtml index 9ec95032..10bab26e 100644 --- a/DiscordChatExporter.Core/Exporting/MessageGroupTemplate.cshtml +++ b/DiscordChatExporter.Core/Exporting/MessageGroupTemplate.cshtml @@ -263,9 +263,8 @@ } @* Forwarded message content *@ - @if (message.IsForwarded && message.ForwardedMessage is not null) + @if (message is { IsForwarded: true, ForwardedMessage: not null }) { - var fwd = message.ForwardedMessage;
@@ -275,18 +274,18 @@
@* Forwarded text content *@ - @if (!string.IsNullOrWhiteSpace(fwd.Content)) + @if (!string.IsNullOrWhiteSpace(message.ForwardedMessage.Content)) {
- @Html.Raw(await FormatMarkdownAsync(fwd.Content)) + @Html.Raw(await FormatMarkdownAsync(message.ForwardedMessage.Content))
} @* Forwarded attachments *@ - @if (fwd.Attachments.Any()) + @if (message.ForwardedMessage.Attachments.Any()) {
- @foreach (var attachment in fwd.Attachments) + @foreach (var attachment in message.ForwardedMessage.Attachments) { @if (attachment.IsImage) { @@ -327,7 +326,7 @@ } @* Forwarded stickers *@ - @foreach (var sticker in fwd.Stickers) + @foreach (var sticker in message.ForwardedMessage.Stickers) {
@if (sticker.IsImage) @@ -343,10 +342,10 @@ @* Forwarded timestamp *@
- Originally sent: @FormatDate(fwd.Timestamp) - @if (fwd.EditedTimestamp is not null) + Originally sent: @FormatDate(message.ForwardedMessage.Timestamp) + @if (message.ForwardedMessage.EditedTimestamp is not null) { - (edited) + (edited) }
diff --git a/DiscordChatExporter.Core/Exporting/PlainTextMessageWriter.cs b/DiscordChatExporter.Core/Exporting/PlainTextMessageWriter.cs index 6c31bbf0..6ddfcc1d 100644 --- a/DiscordChatExporter.Core/Exporting/PlainTextMessageWriter.cs +++ b/DiscordChatExporter.Core/Exporting/PlainTextMessageWriter.cs @@ -239,12 +239,9 @@ internal class PlainTextMessageWriter(Stream stream, ExportContext context) ); } - if (forwardedMessage.Timestamp != DateTimeOffset.MinValue) - { await _writer.WriteLineAsync( $"Originally sent: {Context.FormatDate(forwardedMessage.Timestamp)}" ); - } await WriteAttachmentsAsync(forwardedMessage.Attachments, cancellationToken); await WriteEmbedsAsync(forwardedMessage.Embeds, cancellationToken); diff --git a/DiscordChatExporter.Core/Utils/Extensions/CollectionExtensions.cs b/DiscordChatExporter.Core/Utils/Extensions/CollectionExtensions.cs index 03e34721..72a8f249 100644 --- a/DiscordChatExporter.Core/Utils/Extensions/CollectionExtensions.cs +++ b/DiscordChatExporter.Core/Utils/Extensions/CollectionExtensions.cs @@ -24,4 +24,17 @@ public static class CollectionExtensions } } } + + extension(IEnumerable source) + where T : struct + { + public IEnumerable WhereNotNull() + { + foreach (var o in source) + { + if (o is not null) + yield return o.Value; + } + } + } }