Clean up after last PR

This commit is contained in:
Tyrrrz 2026-02-27 17:30:06 +02:00
parent 532470e3db
commit b660edfe78
7 changed files with 69 additions and 72 deletions

View file

@ -27,10 +27,16 @@ public partial record Message(
IReadOnlyList<User> MentionedUsers, IReadOnlyList<User> MentionedUsers,
MessageReference? Reference, MessageReference? Reference,
Message? ReferencedMessage, Message? ReferencedMessage,
Interaction? Interaction, MessageSnapshot? ForwardedMessage,
MessageSnapshot? ForwardedMessage Interaction? Interaction
) : IHasId ) : IHasId
{ {
public bool IsEmpty { get; } =
string.IsNullOrWhiteSpace(Content)
&& !Attachments.Any()
&& !Embeds.Any()
&& !Stickers.Any();
public bool IsSystemNotification { get; } = public bool IsSystemNotification { get; } =
Kind is >= MessageKind.RecipientAdd and <= MessageKind.ThreadCreated; 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 // App interactions are rendered as replies in the Discord client, but they are not actually replies
public bool IsReplyLike => IsReply || Interaction is not null; 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 IsForwarded { get; } = Reference?.Kind == MessageReferenceKind.Forward;
public bool IsEmpty { get; } =
string.IsNullOrWhiteSpace(Content)
&& !Attachments.Any()
&& !Embeds.Any()
&& !Stickers.Any();
public IEnumerable<User> GetReferencedUsers() public IEnumerable<User> GetReferencedUsers()
{ {
yield return Author; yield return Author;
@ -175,16 +174,19 @@ public partial record Message
var messageReference = json.GetPropertyOrNull("message_reference") var messageReference = json.GetPropertyOrNull("message_reference")
?.Pipe(MessageReference.Parse); ?.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 // Currently Discord only supports 1 snapshot per forward
var forwardedMessage = json.GetPropertyOrNull("message_snapshots") var forwardedMessage = json.GetPropertyOrNull("message_snapshots")
?.EnumerateArrayOrNull() ?.EnumerateArrayOrNull()
?.Select(MessageSnapshot.Parse) ?.Select(j => j.GetPropertyOrNull("message"))
.WhereNotNull()
.Select(MessageSnapshot.Parse)
.FirstOrDefault(); .FirstOrDefault();
var interaction = json.GetPropertyOrNull("interaction")?.Pipe(Interaction.Parse);
return new Message( return new Message(
id, id,
kind, kind,
@ -202,8 +204,8 @@ public partial record Message
mentionedUsers, mentionedUsers,
messageReference, messageReference,
referencedMessage, referencedMessage,
interaction, forwardedMessage,
forwardedMessage interaction
); );
} }
} }

View file

@ -6,14 +6,17 @@ namespace DiscordChatExporter.Core.Discord.Data;
// https://discord.com/developers/docs/resources/channel#message-object-message-reference-structure // https://discord.com/developers/docs/resources/channel#message-object-message-reference-structure
public record MessageReference( public record MessageReference(
MessageReferenceKind Kind,
Snowflake? MessageId, Snowflake? MessageId,
Snowflake? ChannelId, Snowflake? ChannelId,
Snowflake? GuildId, Snowflake? GuildId)
MessageReferenceKind Kind
)
{ {
public static MessageReference Parse(JsonElement json) public static MessageReference Parse(JsonElement json)
{ {
var kind =
json.GetPropertyOrNull("type")?.GetInt32OrNull()?.Pipe(t => (MessageReferenceKind)t)
?? MessageReferenceKind.Default;
var messageId = json.GetPropertyOrNull("message_id") var messageId = json.GetPropertyOrNull("message_id")
?.GetNonWhiteSpaceStringOrNull() ?.GetNonWhiteSpaceStringOrNull()
?.Pipe(Snowflake.Parse); ?.Pipe(Snowflake.Parse);
@ -26,10 +29,6 @@ public record MessageReference(
?.GetNonWhiteSpaceStringOrNull() ?.GetNonWhiteSpaceStringOrNull()
?.Pipe(Snowflake.Parse); ?.Pipe(Snowflake.Parse);
var kind = return new MessageReference(kind, messageId, channelId, guildId);
json.GetPropertyOrNull("type")?.GetInt32OrNull()?.Pipe(t => (MessageReferenceKind)t)
?? MessageReferenceKind.Default;
return new MessageReference(messageId, channelId, guildId, kind);
} }
} }

View file

@ -3,31 +3,34 @@ using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Text.Json; using System.Text.Json;
using DiscordChatExporter.Core.Discord.Data.Embeds; using DiscordChatExporter.Core.Discord.Data.Embeds;
using DiscordChatExporter.Core.Utils.Extensions;
using JsonExtensions.Reading; using JsonExtensions.Reading;
namespace DiscordChatExporter.Core.Discord.Data; 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 // Message snapshots contain a subset of message fields for forwarded messages
public record MessageSnapshot( public record MessageSnapshot(
DateTimeOffset Timestamp,
DateTimeOffset? EditedTimestamp,
string Content, string Content,
IReadOnlyList<Attachment> Attachments, IReadOnlyList<Attachment> Attachments,
IReadOnlyList<Embed> Embeds, IReadOnlyList<Embed> Embeds,
IReadOnlyList<Sticker> Stickers, IReadOnlyList<Sticker> Stickers)
DateTimeOffset Timestamp,
DateTimeOffset? EditedTimestamp
)
{ {
public static MessageSnapshot Parse(JsonElement json) public static MessageSnapshot Parse(JsonElement json)
{ {
// The message snapshot has a "message" property containing the actual message data var timestamp =
var messageJson = json.GetPropertyOrNull("message") ?? json; 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 = var attachments =
messageJson json
.GetPropertyOrNull("attachments") .GetPropertyOrNull("attachments")
?.EnumerateArrayOrNull() ?.EnumerateArrayOrNull()
?.Select(Attachment.Parse) ?.Select(Attachment.Parse)
@ -35,7 +38,7 @@ public record MessageSnapshot(
?? []; ?? [];
var embeds = var embeds =
messageJson json
.GetPropertyOrNull("embeds") .GetPropertyOrNull("embeds")
?.EnumerateArrayOrNull() ?.EnumerateArrayOrNull()
?.Select(Embed.Parse) ?.Select(Embed.Parse)
@ -43,28 +46,14 @@ public record MessageSnapshot(
?? []; ?? [];
var stickers = var stickers =
messageJson json
.GetPropertyOrNull("sticker_items") .GetPropertyOrNull("sticker_items")
?.EnumerateArrayOrNull() ?.EnumerateArrayOrNull()
?.Select(Sticker.Parse) ?.Select(Sticker.Parse)
.ToArray() .ToArray()
?? []; ?? [];
var timestamp = return new MessageSnapshot(timestamp,
messageJson.GetPropertyOrNull("timestamp")?.GetDateTimeOffsetOrNull() editedTimestamp, content, attachments, embeds, stickers);
?? DateTimeOffset.MinValue;
var editedTimestamp = messageJson
.GetPropertyOrNull("edited_timestamp")
?.GetDateTimeOffsetOrNull();
return new MessageSnapshot(
content,
attachments,
embeds,
stickers,
timestamp,
editedTimestamp
);
} }
} }

View file

@ -539,22 +539,20 @@ internal class JsonMessageWriter(Stream stream, ExportContext context)
{ {
_writer.WriteStartObject("forwardedMessage"); _writer.WriteStartObject("forwardedMessage");
_writer.WriteString(
"content",
await FormatMarkdownAsync(message.ForwardedMessage.Content, cancellationToken)
);
_writer.WriteString( _writer.WriteString(
"timestamp", "timestamp",
message.ForwardedMessage.Timestamp != DateTimeOffset.MinValue Context.NormalizeDate(message.ForwardedMessage.Timestamp)
? Context.NormalizeDate(message.ForwardedMessage.Timestamp)
: null
); );
_writer.WriteString( _writer.WriteString(
"timestampEdited", "timestampEdited",
message.ForwardedMessage.EditedTimestamp?.Pipe(Context.NormalizeDate) message.ForwardedMessage.EditedTimestamp?.Pipe(Context.NormalizeDate)
); );
_writer.WriteString(
"content",
await FormatMarkdownAsync(message.ForwardedMessage.Content, cancellationToken)
);
// Forwarded attachments // Forwarded attachments
_writer.WriteStartArray("attachments"); _writer.WriteStartArray("attachments");

View file

@ -263,9 +263,8 @@
} }
@* Forwarded message content *@ @* Forwarded message content *@
@if (message.IsForwarded && message.ForwardedMessage is not null) @if (message is { IsForwarded: true, ForwardedMessage: not null })
{ {
var fwd = message.ForwardedMessage;
<div class="chatlog__forwarded"> <div class="chatlog__forwarded">
<div class="chatlog__forwarded-header"> <div class="chatlog__forwarded-header">
<svg class="chatlog__forwarded-icon"> <svg class="chatlog__forwarded-icon">
@ -275,18 +274,18 @@
</div> </div>
@* Forwarded text content *@ @* Forwarded text content *@
@if (!string.IsNullOrWhiteSpace(fwd.Content)) @if (!string.IsNullOrWhiteSpace(message.ForwardedMessage.Content))
{ {
<div class="chatlog__forwarded-content chatlog__markdown"> <div class="chatlog__forwarded-content chatlog__markdown">
<span class="chatlog__markdown-preserve"><!--wmm:ignore-->@Html.Raw(await FormatMarkdownAsync(fwd.Content))<!--/wmm:ignore--></span> <span class="chatlog__markdown-preserve"><!--wmm:ignore-->@Html.Raw(await FormatMarkdownAsync(message.ForwardedMessage.Content))<!--/wmm:ignore--></span>
</div> </div>
} }
@* Forwarded attachments *@ @* Forwarded attachments *@
@if (fwd.Attachments.Any()) @if (message.ForwardedMessage.Attachments.Any())
{ {
<div class="chatlog__forwarded-attachments"> <div class="chatlog__forwarded-attachments">
@foreach (var attachment in fwd.Attachments) @foreach (var attachment in message.ForwardedMessage.Attachments)
{ {
@if (attachment.IsImage) @if (attachment.IsImage)
{ {
@ -327,7 +326,7 @@
} }
@* Forwarded stickers *@ @* Forwarded stickers *@
@foreach (var sticker in fwd.Stickers) @foreach (var sticker in message.ForwardedMessage.Stickers)
{ {
<div class="chatlog__sticker" title="@sticker.Name"> <div class="chatlog__sticker" title="@sticker.Name">
@if (sticker.IsImage) @if (sticker.IsImage)
@ -343,10 +342,10 @@
@* Forwarded timestamp *@ @* Forwarded timestamp *@
<div class="chatlog__forwarded-timestamp"> <div class="chatlog__forwarded-timestamp">
<span title="@FormatDate(fwd.Timestamp, "f")">Originally sent: @FormatDate(fwd.Timestamp)</span> <span title="@FormatDate(message.ForwardedMessage.Timestamp, "f")">Originally sent: @FormatDate(message.ForwardedMessage.Timestamp)</span>
@if (fwd.EditedTimestamp is not null) @if (message.ForwardedMessage.EditedTimestamp is not null)
{ {
<span title="@FormatDate(fwd.EditedTimestamp.Value, "f")"> (edited)</span> <span title="@FormatDate(message.ForwardedMessage.EditedTimestamp.Value, "f")"> (edited)</span>
} }
</div> </div>
</div> </div>

View file

@ -239,12 +239,9 @@ internal class PlainTextMessageWriter(Stream stream, ExportContext context)
); );
} }
if (forwardedMessage.Timestamp != DateTimeOffset.MinValue)
{
await _writer.WriteLineAsync( await _writer.WriteLineAsync(
$"Originally sent: {Context.FormatDate(forwardedMessage.Timestamp)}" $"Originally sent: {Context.FormatDate(forwardedMessage.Timestamp)}"
); );
}
await WriteAttachmentsAsync(forwardedMessage.Attachments, cancellationToken); await WriteAttachmentsAsync(forwardedMessage.Attachments, cancellationToken);
await WriteEmbedsAsync(forwardedMessage.Embeds, cancellationToken); await WriteEmbedsAsync(forwardedMessage.Embeds, cancellationToken);

View file

@ -24,4 +24,17 @@ public static class CollectionExtensions
} }
} }
} }
extension<T>(IEnumerable<T?> source)
where T : struct
{
public IEnumerable<T> WhereNotNull()
{
foreach (var o in source)
{
if (o is not null)
yield return o.Value;
}
}
}
} }