mirror of
https://github.com/Tyrrrz/DiscordChatExporter.git
synced 2026-08-13 14:03:01 -06:00
Move thread starter extraction logic into DiscordClient
This commit is contained in:
parent
38f9b9c676
commit
857da7857b
|
|
@ -656,6 +656,33 @@ public class DiscordClient(
|
|||
.FirstOrDefault(m => m.Id == messageId);
|
||||
}
|
||||
|
||||
private async ValueTask<Message?> ResolveThreadStarterMessageAsync(
|
||||
Message message,
|
||||
CancellationToken cancellationToken = default
|
||||
)
|
||||
{
|
||||
// Threads created from a message contain an empty THREAD_STARTER_MESSAGE placeholder at
|
||||
// the top of their history (in place of the actual starter message) that merely points
|
||||
// back to the originating message in the parent channel. Resolve the placeholder to that
|
||||
// actual message so the thread's starter message appears in the output, in its correct
|
||||
// chronological position, with its real content.
|
||||
// This doesn't apply to forum/media posts, whose starter message is already a regular
|
||||
// message in the thread's own history (i.e. not a placeholder).
|
||||
// https://github.com/Tyrrrz/DiscordChatExporter/issues/1265
|
||||
if (message.Kind != MessageKind.ThreadStarterMessage)
|
||||
return message;
|
||||
|
||||
// The placeholder references the parent channel and the original message it points to.
|
||||
if (message.Reference?.ChannelId is not { } channelId)
|
||||
return null;
|
||||
if (message.Reference?.MessageId is not { } messageId)
|
||||
return null;
|
||||
|
||||
// The original message may no longer be accessible (e.g. deleted), in which case the
|
||||
// empty placeholder is dropped as well.
|
||||
return await TryGetMessageAsync(channelId, messageId, cancellationToken);
|
||||
}
|
||||
|
||||
public async IAsyncEnumerable<Message> GetMessagesAsync(
|
||||
Snowflake channelId,
|
||||
Snowflake? after = null,
|
||||
|
|
@ -728,7 +755,15 @@ public class DiscordClient(
|
|||
);
|
||||
}
|
||||
|
||||
yield return message;
|
||||
// Thread starter messages are returned as empty placeholders; resolve them to
|
||||
// the actual message they reference before yielding (or skip if unavailable).
|
||||
var resolvedMessage = await ResolveThreadStarterMessageAsync(
|
||||
message,
|
||||
cancellationToken
|
||||
);
|
||||
if (resolvedMessage is not null)
|
||||
yield return resolvedMessage;
|
||||
|
||||
currentAfter = message.Id;
|
||||
}
|
||||
}
|
||||
|
|
@ -796,7 +831,14 @@ public class DiscordClient(
|
|||
);
|
||||
}
|
||||
|
||||
yield return message;
|
||||
// Thread starter messages are returned as empty placeholders; resolve them to
|
||||
// the actual message they reference before yielding (or skip if unavailable).
|
||||
var resolvedMessage = await ResolveThreadStarterMessageAsync(
|
||||
message,
|
||||
cancellationToken
|
||||
);
|
||||
if (resolvedMessage is not null)
|
||||
yield return resolvedMessage;
|
||||
}
|
||||
|
||||
currentBefore = messages.Last().Id;
|
||||
|
|
|
|||
|
|
@ -1,6 +1,4 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using DiscordChatExporter.Core.Discord;
|
||||
|
|
@ -82,63 +80,7 @@ public class ChannelExporter(DiscordClient discord)
|
|||
cancellationToken
|
||||
);
|
||||
|
||||
// Threads created from a message don't include that message in their own history, even
|
||||
// though it logically belongs to the thread as its very first message. Because the thread
|
||||
// shares its ID with that starter message, we can fetch it from the parent channel and
|
||||
// export it together with the rest of the thread's messages.
|
||||
// This doesn't apply to forum/media posts, whose starter message is already part of the
|
||||
// thread's own history.
|
||||
// https://github.com/Tyrrrz/DiscordChatExporter/issues/1265
|
||||
var starterMessage =
|
||||
request.Channel.IsThread
|
||||
&& request.Channel.Parent is { Kind: not ChannelKind.GuildForum } parent
|
||||
? await discord.TryGetMessageAsync(parent.Id, request.Channel.Id, cancellationToken)
|
||||
: null;
|
||||
|
||||
// Only include the starter message if it falls within the requested range
|
||||
if (
|
||||
starterMessage is not null
|
||||
&& (
|
||||
(request.After is not null && starterMessage.Id < request.After.Value)
|
||||
|| (request.Before is not null && starterMessage.Id > request.Before.Value)
|
||||
)
|
||||
)
|
||||
{
|
||||
starterMessage = null;
|
||||
}
|
||||
|
||||
// Prepend the starter message to the thread's history, or append it when exporting in
|
||||
// reverse order, so that it remains the oldest message in the output either way.
|
||||
async IAsyncEnumerable<Message> GetMessagesWithStarter(
|
||||
[EnumeratorCancellation] CancellationToken innerCancellationToken
|
||||
)
|
||||
{
|
||||
// If the message order is not reversed, the starter message should be the first one in the output, so we yield it before fetching the rest of the thread's history.
|
||||
if (starterMessage is not null && !request.IsReverseMessageOrder)
|
||||
yield return starterMessage;
|
||||
|
||||
// Fetch the rest of the thread's history and yield messages one by one
|
||||
await foreach (var message in messages.WithCancellation(innerCancellationToken))
|
||||
{
|
||||
// A thread created from a message has an empty THREAD_STARTER_MESSAGE placeholder
|
||||
// at the top of its history that merely points back to the originating message.
|
||||
// It never carries any renderable content of its own and is superseded by the
|
||||
// actual starter message fetched above, so skip it.
|
||||
if (message.Kind == MessageKind.ThreadStarterMessage)
|
||||
continue;
|
||||
|
||||
// Avoid exporting the starter message twice in case it's also returned as part
|
||||
// of the thread's own history.
|
||||
if (message.Id != starterMessage?.Id)
|
||||
yield return message;
|
||||
}
|
||||
|
||||
// If the message order is reversed, the starter message should be the last one in the output, so we yield it after fetching the rest of the thread's history.
|
||||
if (starterMessage is not null && request.IsReverseMessageOrder)
|
||||
yield return starterMessage;
|
||||
}
|
||||
|
||||
await foreach (var message in GetMessagesWithStarter(cancellationToken))
|
||||
await foreach (var message in messages)
|
||||
{
|
||||
try
|
||||
{
|
||||
|
|
|
|||
Loading…
Reference in a new issue