From 8f40acdda7120ac45fba6a15ea5cc19bb0d23091 Mon Sep 17 00:00:00 2001 From: Tyrrrz <1935960+Tyrrrz@users.noreply.github.com> Date: Wed, 8 Mar 2023 12:32:44 +0200 Subject: [PATCH] Load mentions for users that left the guild --- .../Discord/Data/Channel.cs | 15 +------------ .../Discord/Data/ChannelCategory.cs | 13 +++++++++++ .../Discord/Data/Member.cs | 2 ++ .../Discord/DiscordClient.cs | 8 +++++++ .../Exporting/ChannelExporter.cs | 2 +- .../Exporting/ExportContext.cs | 22 +++++++++++++++++-- 6 files changed, 45 insertions(+), 17 deletions(-) diff --git a/DiscordChatExporter.Core/Discord/Data/Channel.cs b/DiscordChatExporter.Core/Discord/Data/Channel.cs index 0a21c21b..02ceb4e0 100644 --- a/DiscordChatExporter.Core/Discord/Data/Channel.cs +++ b/DiscordChatExporter.Core/Discord/Data/Channel.cs @@ -23,19 +23,6 @@ public partial record Channel( public partial record Channel { - private static ChannelCategory GetFallbackCategory(ChannelKind channelKind) => new( - Snowflake.Zero, - channelKind switch - { - ChannelKind.GuildTextChat => "Text", - ChannelKind.DirectTextChat => "Private", - ChannelKind.DirectGroupTextChat => "Group", - ChannelKind.GuildNews => "News", - _ => "Default" - }, - null - ); - public static Channel Parse(JsonElement json, ChannelCategory? categoryHint = null, int? positionHint = null) { var id = json.GetProperty("id").GetNonWhiteSpaceString().Pipe(Snowflake.Parse); @@ -45,7 +32,7 @@ public partial record Channel json.GetPropertyOrNull("guild_id")?.GetNonWhiteSpaceStringOrNull()?.Pipe(Snowflake.Parse) ?? Guild.DirectMessages.Id; - var category = categoryHint ?? GetFallbackCategory(kind); + var category = categoryHint ?? ChannelCategory.CreateDefault(kind); var name = // Guild channel diff --git a/DiscordChatExporter.Core/Discord/Data/ChannelCategory.cs b/DiscordChatExporter.Core/Discord/Data/ChannelCategory.cs index f7a9087f..56bfe651 100644 --- a/DiscordChatExporter.Core/Discord/Data/ChannelCategory.cs +++ b/DiscordChatExporter.Core/Discord/Data/ChannelCategory.cs @@ -7,6 +7,19 @@ namespace DiscordChatExporter.Core.Discord.Data; public record ChannelCategory(Snowflake Id, string Name, int? Position) : IHasId { + public static ChannelCategory CreateDefault(ChannelKind channelKind) => new( + Snowflake.Zero, + channelKind switch + { + ChannelKind.GuildTextChat => "Text", + ChannelKind.DirectTextChat => "Private", + ChannelKind.DirectGroupTextChat => "Group", + ChannelKind.GuildNews => "News", + _ => "Default" + }, + null + ); + public static ChannelCategory Parse(JsonElement json, int? positionHint = null) { var id = json.GetProperty("id").GetNonWhiteSpaceString().Pipe(Snowflake.Parse); diff --git a/DiscordChatExporter.Core/Discord/Data/Member.cs b/DiscordChatExporter.Core/Discord/Data/Member.cs index e65fd3cb..b84547fd 100644 --- a/DiscordChatExporter.Core/Discord/Data/Member.cs +++ b/DiscordChatExporter.Core/Discord/Data/Member.cs @@ -20,6 +20,8 @@ public partial record Member( public partial record Member { + public static Member CreateDefault(User user) => new(user, null, null, Array.Empty()); + public static Member Parse(JsonElement json, Snowflake? guildId = null) { var user = json.GetProperty("user").Pipe(User.Parse); diff --git a/DiscordChatExporter.Core/Discord/DiscordClient.cs b/DiscordChatExporter.Core/Discord/DiscordClient.cs index 5bc0a1af..ef573e22 100644 --- a/DiscordChatExporter.Core/Discord/DiscordClient.cs +++ b/DiscordChatExporter.Core/Discord/DiscordClient.cs @@ -150,6 +150,14 @@ public class DiscordClient : null; } + public async ValueTask TryGetUserAsync( + Snowflake userId, + CancellationToken cancellationToken = default) + { + var response = await TryGetJsonResponseAsync($"users/{userId}", cancellationToken); + return response?.Pipe(User.Parse); + } + public async IAsyncEnumerable GetUserGuildsAsync( [EnumeratorCancellation] CancellationToken cancellationToken = default) { diff --git a/DiscordChatExporter.Core/Exporting/ChannelExporter.cs b/DiscordChatExporter.Core/Exporting/ChannelExporter.cs index 527445b0..75c319ea 100644 --- a/DiscordChatExporter.Core/Exporting/ChannelExporter.cs +++ b/DiscordChatExporter.Core/Exporting/ChannelExporter.cs @@ -34,7 +34,7 @@ public class ChannelExporter { // Resolve members for referenced users foreach (var user in message.GetReferencedUsers()) - await context.PopulateMemberAsync(user.Id, cancellationToken); + await context.PopulateMemberAsync(user, cancellationToken); // Export the message if (request.MessageFilter.IsMatch(message)) diff --git a/DiscordChatExporter.Core/Exporting/ExportContext.cs b/DiscordChatExporter.Core/Exporting/ExportContext.cs index 94166277..1b359a49 100644 --- a/DiscordChatExporter.Core/Exporting/ExportContext.cs +++ b/DiscordChatExporter.Core/Exporting/ExportContext.cs @@ -20,6 +20,7 @@ internal class ExportContext private readonly ExportAssetDownloader _assetDownloader; public DiscordClient Discord { get; } + public ExportRequest Request { get; } public ExportContext(DiscordClient discord, @@ -43,18 +44,35 @@ internal class ExportContext _roles[role.Id] = role; } - // Because members are not pulled in bulk, we need to populate them on demand - public async ValueTask PopulateMemberAsync(Snowflake id, CancellationToken cancellationToken = default) + // Because members cannot be pulled in bulk, we need to populate them on demand + private async ValueTask PopulateMemberAsync( + Snowflake id, + User? fallbackUser, + CancellationToken cancellationToken = default) { if (_members.ContainsKey(id)) return; var member = await Discord.TryGetGuildMemberAsync(Request.Guild.Id, id, cancellationToken); + // User may have left the guild since they were mentioned + if (member is null) + { + var user = fallbackUser ?? await Discord.TryGetUserAsync(id, cancellationToken); + if (user is not null) + member = Member.CreateDefault(user); + } + // Store the result even if it's null, to avoid re-fetching non-existing members _members[id] = member; } + public async ValueTask PopulateMemberAsync(Snowflake id, CancellationToken cancellationToken = default) => + await PopulateMemberAsync(id, null, cancellationToken); + + public async ValueTask PopulateMemberAsync(User user, CancellationToken cancellationToken = default) => + await PopulateMemberAsync(user.Id, user, cancellationToken); + public string FormatDate(DateTimeOffset instant) => Request.DateFormat switch { "unix" => instant.ToUnixTimeSeconds().ToString(),