From 9601e0aceaff67c46373daff856221db0a60ca9a Mon Sep 17 00:00:00 2001 From: Tyrrrz <1935960+Tyrrrz@users.noreply.github.com> Date: Sun, 3 Sep 2023 21:43:55 +0300 Subject: [PATCH] Refactor --- .../Discord/Data/Channel.cs | 6 +++ .../Discord/DiscordClient.cs | 48 ++++++++++--------- .../Exporting/ChannelExporter.cs | 21 ++++---- 3 files changed, 41 insertions(+), 34 deletions(-) diff --git a/DiscordChatExporter.Core/Discord/Data/Channel.cs b/DiscordChatExporter.Core/Discord/Data/Channel.cs index 431f675d..97f01296 100644 --- a/DiscordChatExporter.Core/Discord/Data/Channel.cs +++ b/DiscordChatExporter.Core/Discord/Data/Channel.cs @@ -39,11 +39,17 @@ public partial record Channel( _ => "Default" }; + public bool IsEmpty => LastMessageId is null; + // Only needed for WPF data binding. Don't use anywhere else. public bool IsVoice => Kind.IsVoice(); // Only needed for WPF data binding. Don't use anywhere else. public bool IsThread => Kind.IsThread(); + + public bool MayHaveMessagesAfter(Snowflake messageId) => !IsEmpty && messageId < LastMessageId; + + public bool MayHaveMessagesBefore(Snowflake messageId) => !IsEmpty && messageId > Id; } public partial record Channel diff --git a/DiscordChatExporter.Core/Discord/DiscordClient.cs b/DiscordChatExporter.Core/Discord/DiscordClient.cs index 593d0f39..7fdc7d53 100644 --- a/DiscordChatExporter.Core/Discord/DiscordClient.cs +++ b/DiscordChatExporter.Core/Discord/DiscordClient.cs @@ -286,23 +286,25 @@ public class DiscordClient yield break; var tokenKind = _resolvedTokenKind ??= await GetTokenKindAsync(cancellationToken); - var channels = await GetGuildChannelsAsync(guildId, cancellationToken); - - var filteredChannels = channels + var channels = (await GetGuildChannelsAsync(guildId, cancellationToken)) // Categories cannot have threads .Where(c => c.Kind != ChannelKind.GuildCategory) // Voice channels cannot have threads .Where(c => !c.Kind.IsVoice()) - // Ordinary channel or forum channel without LastMessageId cannot have threads - .Where(c => c.LastMessageId != null) - // Ff --before is specified, skip channels created after the specified date - .Where(c => before == null || before > c.Id); + // Empty channels cannot have threads + .Where(c => !c.IsEmpty) + // If the 'before' boundary is specified, skip channels that don't have messages + // for that range, because thread-start event should always be accompanied by a message. + // Note that we don't perform a similar check for the 'after' boundary, because + // threads may have messages in range, even if the parent channel doesn't. + .Where(c => before is null || c.MayHaveMessagesBefore(before.Value)) + .ToArray(); // User accounts can only fetch threads using the search endpoint if (tokenKind == TokenKind.User) { // Active threads - foreach (var channel in filteredChannels) + foreach (var channel in channels) { var currentOffset = 0; while (true) @@ -320,7 +322,7 @@ public class DiscordClient if (response is null) break; - var containsOlder = false; + var breakOuter = false; foreach ( var threadJson in response.Value.GetProperty("threads").EnumerateArray() @@ -328,19 +330,19 @@ public class DiscordClient { var thread = Channel.Parse(threadJson, channel); - // if --after is specified, we can break early, because the threads are sorted by last message time - if (after is not null && after > thread.LastMessageId) + // If the 'after' boundary is specified, we can break early, + // because threads are sorted by last message time. + if (after is not null && !thread.MayHaveMessagesAfter(after.Value)) { - containsOlder = true; + breakOuter = true; break; } yield return thread; - currentOffset++; } - if (containsOlder) + if (breakOuter) break; if (!response.Value.GetProperty("has_more").GetBoolean()) @@ -351,7 +353,7 @@ public class DiscordClient // Archived threads if (includeArchived) { - foreach (var channel in filteredChannels) + foreach (var channel in channels) { var currentOffset = 0; while (true) @@ -369,7 +371,7 @@ public class DiscordClient if (response is null) break; - var containsOlder = false; + var breakOuter = false; foreach ( var threadJson in response.Value.GetProperty("threads").EnumerateArray() @@ -377,19 +379,19 @@ public class DiscordClient { var thread = Channel.Parse(threadJson, channel); - // if --after is specified, we can break early, because the threads are sorted by last message time - if (after is not null && after > thread.LastMessageId) + // If the 'after' boundary is specified, we can break early, + // because threads are sorted by last message time. + if (after is not null && !thread.MayHaveMessagesAfter(after.Value)) { - containsOlder = true; + breakOuter = true; break; } yield return thread; - currentOffset++; } - if (containsOlder) + if (breakOuter) break; if (!response.Value.GetProperty("has_more").GetBoolean()) @@ -403,7 +405,7 @@ public class DiscordClient { // Active threads { - var parentsById = filteredChannels.ToDictionary(c => c.Id); + var parentsById = channels.ToDictionary(c => c.Id); var response = await GetJsonResponseAsync( $"guilds/{guildId}/threads/active", @@ -425,7 +427,7 @@ public class DiscordClient // Archived threads if (includeArchived) { - foreach (var channel in filteredChannels) + foreach (var channel in channels) { // Public archived threads { diff --git a/DiscordChatExporter.Core/Exporting/ChannelExporter.cs b/DiscordChatExporter.Core/Exporting/ChannelExporter.cs index 7edbdbf0..8bd53b0c 100644 --- a/DiscordChatExporter.Core/Exporting/ChannelExporter.cs +++ b/DiscordChatExporter.Core/Exporting/ChannelExporter.cs @@ -20,14 +20,16 @@ public class ChannelExporter CancellationToken cancellationToken = default ) { + // Forum channels don't have messages, they are just a list of threads + if (request.Channel.Kind == ChannelKind.GuildForum) + throw new DiscordChatExporterException("Channel is a forum."); + // Check if the channel is empty - if (request.Channel.LastMessageId is null) - { + if (request.Channel.IsEmpty) throw new DiscordChatExporterException("Channel does not contain any messages."); - } // Check if the 'after' boundary is valid - if (request.After is not null && request.Channel.LastMessageId < request.After) + if (request.After is not null && !request.Channel.MayHaveMessagesAfter(request.After.Value)) { throw new DiscordChatExporterException( "Channel does not contain any messages within the specified period." @@ -35,19 +37,16 @@ public class ChannelExporter } // Check if the 'before' boundary is valid - if (request.Before is not null && request.Channel.Id > request.Before) + if ( + request.Before is not null + && !request.Channel.MayHaveMessagesBefore(request.Before.Value) + ) { throw new DiscordChatExporterException( "Channel does not contain any messages within the specified period." ); } - // Skip forum channels, they are exported as threads - if (request.Channel.Kind == ChannelKind.GuildForum) - { - throw new DiscordChatExporterException("Channel is a forum."); - } - // Build context var context = new ExportContext(_discord, request); await context.PopulateChannelsAndRolesAsync(cancellationToken);