From ea31b1b270bfe77fdca6ad6eb5aecf98ae1b0780 Mon Sep 17 00:00:00 2001 From: Tyrrrz <1935960+Tyrrrz@users.noreply.github.com> Date: Thu, 16 Sep 2021 01:08:14 +0300 Subject: [PATCH] Refactor --- .../Specs/HtmlWriting/EmbedSpecs.cs | 12 +++----- .../Commands/Base/ExportCommandBase.cs | 28 +++++++++++-------- .../Commands/Base/TokenCommandBase.cs | 5 ++-- .../Commands/ExportAllCommand.cs | 10 ++----- .../Commands/ExportChannelsCommand.cs | 21 ++------------ .../Commands/ExportDirectMessagesCommand.cs | 6 +--- .../Commands/ExportGuildCommand.cs | 6 +--- .../Commands/GetChannelsCommand.cs | 4 +-- .../GetDirectMessageChannelsCommand.cs | 4 +-- .../Commands/GetGuildsCommand.cs | 4 +-- .../Commands/GuideCommand.cs | 6 +++- .../Discord/Data/ChannelCategory.cs | 4 +-- .../Discord/Data/Message.cs | 14 ---------- .../Discord/Data/MessageKind.cs | 16 +++++++++++ .../Discord/DiscordClient.cs | 6 ++-- .../DiscordChatExporterException.cs | 8 +++--- .../Exporting/ChannelExporter.cs | 5 +--- .../Exporting/MessageExporter.cs | 2 +- .../ViewModels/RootViewModel.cs | 4 +-- 19 files changed, 67 insertions(+), 98 deletions(-) create mode 100644 DiscordChatExporter.Core/Discord/Data/MessageKind.cs diff --git a/DiscordChatExporter.Cli.Tests/Specs/HtmlWriting/EmbedSpecs.cs b/DiscordChatExporter.Cli.Tests/Specs/HtmlWriting/EmbedSpecs.cs index b3b6a19..670368e 100644 --- a/DiscordChatExporter.Cli.Tests/Specs/HtmlWriting/EmbedSpecs.cs +++ b/DiscordChatExporter.Cli.Tests/Specs/HtmlWriting/EmbedSpecs.cs @@ -40,12 +40,10 @@ namespace DiscordChatExporter.Cli.Tests.Specs.HtmlWriting Snowflake.Parse("867886632203976775") ); - var iframe = message.QuerySelector("iframe"); + var iframeSrc = message.QuerySelector("iframe")?.GetAttribute("src"); // Assert - iframe.Should().NotBeNull(); - iframe?.GetAttribute("src").Should() - .StartWithEquivalentOf("https://open.spotify.com/embed/track/1LHZMWefF9502NPfArRfvP"); + iframeSrc.Should().StartWithEquivalentOf("https://open.spotify.com/embed/track/1LHZMWefF9502NPfArRfvP"); } [Fact] @@ -57,12 +55,10 @@ namespace DiscordChatExporter.Cli.Tests.Specs.HtmlWriting Snowflake.Parse("866472508588294165") ); - var iframe = message.QuerySelector("iframe"); + var iframeSrc = message.QuerySelector("iframe")?.GetAttribute("src"); // Assert - iframe.Should().NotBeNull(); - iframe?.GetAttribute("src").Should() - .StartWithEquivalentOf("https://www.youtube.com/embed/qOWW4OlgbvE"); + iframeSrc.Should().StartWithEquivalentOf("https://www.youtube.com/embed/qOWW4OlgbvE"); } } } \ No newline at end of file diff --git a/DiscordChatExporter.Cli/Commands/Base/ExportCommandBase.cs b/DiscordChatExporter.Cli/Commands/Base/ExportCommandBase.cs index 9237868..507f71a 100644 --- a/DiscordChatExporter.Cli/Commands/Base/ExportCommandBase.cs +++ b/DiscordChatExporter.Cli/Commands/Base/ExportCommandBase.cs @@ -54,18 +54,21 @@ namespace DiscordChatExporter.Cli.Commands.Base private ChannelExporter? _channelExporter; protected ChannelExporter Exporter => _channelExporter ??= new ChannelExporter(Discord); - protected async ValueTask ExportAsync(IConsole console, IReadOnlyList channels) + protected async ValueTask ExecuteAsync(IConsole console, IReadOnlyList channels) { - await console.Output.WriteLineAsync($"Exporting {channels.Count} channel(s)..."); + if (ShouldReuseMedia && !ShouldDownloadMedia) + { + throw new CommandException("Option --reuse-media cannot be used without --media."); + } var errors = new ConcurrentDictionary(); - // Wrap everything in a progress ticker + // Export + await console.Output.WriteLineAsync($"Exporting {channels.Count} channel(s)..."); await console.CreateProgressTicker().StartAsync(async progressContext => { await channels.ParallelForEachAsync(async channel => { - // Export try { await progressContext.StartTaskAsync($"{channel.Category} / {channel.Name}", async progress => @@ -89,7 +92,7 @@ namespace DiscordChatExporter.Cli.Commands.Base await Exporter.ExportChannelAsync(request, progress); }); } - catch (DiscordChatExporterException ex) when (!ex.IsCritical) + catch (DiscordChatExporterException ex) when (!ex.IsFatal) { errors[channel] = ex.Message; } @@ -127,22 +130,25 @@ namespace DiscordChatExporter.Cli.Commands.Base await console.Output.WriteLineAsync(); } - // Fail the command if ALL channels failed to export. - // Having some of the channels fail to export is fine and expected. + // Fail the command only if ALL channels failed to export. + // Having some of the channels fail to export is expected. if (errors.Count >= channels.Count) { throw new CommandException("Export failed."); } } - public override ValueTask ExecuteAsync(IConsole console) + protected async ValueTask ExecuteAsync(IConsole console, IReadOnlyList channelIds) { - if (ShouldReuseMedia && !ShouldDownloadMedia) + var channels = new List(); + + foreach (var channelId in channelIds) { - throw new CommandException("Option --reuse-media cannot be used without --media."); + var channel = await Discord.GetChannelAsync(channelId); + channels.Add(channel); } - return default; + await ExecuteAsync(console, channels); } } } \ No newline at end of file diff --git a/DiscordChatExporter.Cli/Commands/Base/TokenCommandBase.cs b/DiscordChatExporter.Cli/Commands/Base/TokenCommandBase.cs index e75826b..b68808e 100644 --- a/DiscordChatExporter.Cli/Commands/Base/TokenCommandBase.cs +++ b/DiscordChatExporter.Cli/Commands/Base/TokenCommandBase.cs @@ -14,7 +14,8 @@ namespace DiscordChatExporter.Cli.Commands.Base [CommandOption("bot", 'b', EnvironmentVariable = "DISCORD_TOKEN_BOT", Description = "Authenticate as a bot.")] public bool IsBotToken { get; init; } - private AuthToken GetAuthToken() => new( + private AuthToken? _authToken; + private AuthToken AuthToken => _authToken ??= new AuthToken( IsBotToken ? AuthTokenKind.Bot : AuthTokenKind.User, @@ -22,7 +23,7 @@ namespace DiscordChatExporter.Cli.Commands.Base ); private DiscordClient? _discordClient; - protected DiscordClient Discord => _discordClient ??= new DiscordClient(GetAuthToken()); + protected DiscordClient Discord => _discordClient ??= new DiscordClient(AuthToken); public abstract ValueTask ExecuteAsync(IConsole console); } diff --git a/DiscordChatExporter.Cli/Commands/ExportAllCommand.cs b/DiscordChatExporter.Cli/Commands/ExportAllCommand.cs index 7e81f3a..efbbb85 100644 --- a/DiscordChatExporter.Cli/Commands/ExportAllCommand.cs +++ b/DiscordChatExporter.Cli/Commands/ExportAllCommand.cs @@ -15,14 +15,9 @@ namespace DiscordChatExporter.Cli.Commands public override async ValueTask ExecuteAsync(IConsole console) { - await base.ExecuteAsync(console); - - // Get channel metadata - await console.Output.WriteLineAsync("Fetching channels..."); - var channels = new List(); - // Aggregate channels from all guilds + await console.Output.WriteLineAsync("Fetching channels..."); await foreach (var guild in Discord.GetUserGuildsAsync()) { // Skip DMs if instructed to @@ -39,8 +34,7 @@ namespace DiscordChatExporter.Cli.Commands } } - // Export - await ExportAsync(console, channels); + await base.ExecuteAsync(console, channels); } } } \ No newline at end of file diff --git a/DiscordChatExporter.Cli/Commands/ExportChannelsCommand.cs b/DiscordChatExporter.Cli/Commands/ExportChannelsCommand.cs index 33c71b4..3fe02b2 100644 --- a/DiscordChatExporter.Cli/Commands/ExportChannelsCommand.cs +++ b/DiscordChatExporter.Cli/Commands/ExportChannelsCommand.cs @@ -5,7 +5,6 @@ using CliFx.Attributes; using CliFx.Infrastructure; using DiscordChatExporter.Cli.Commands.Base; using DiscordChatExporter.Core.Discord; -using DiscordChatExporter.Core.Discord.Data; namespace DiscordChatExporter.Cli.Commands { @@ -16,23 +15,7 @@ namespace DiscordChatExporter.Cli.Commands [CommandOption("channel", 'c', IsRequired = true, Description = "Channel ID(s).")] public IReadOnlyList ChannelIds { get; init; } = Array.Empty(); - public override async ValueTask ExecuteAsync(IConsole console) - { - await base.ExecuteAsync(console); - - // Get channel metadata - await console.Output.WriteLineAsync("Fetching channel(s)..."); - - var channels = new List(); - - foreach (var channelId in ChannelIds) - { - var channel = await Discord.GetChannelAsync(channelId); - channels.Add(channel); - } - - // Export - await ExportAsync(console, channels); - } + public override async ValueTask ExecuteAsync(IConsole console) => + await base.ExecuteAsync(console, ChannelIds); } } \ No newline at end of file diff --git a/DiscordChatExporter.Cli/Commands/ExportDirectMessagesCommand.cs b/DiscordChatExporter.Cli/Commands/ExportDirectMessagesCommand.cs index 4b1fee5..6b9f72c 100644 --- a/DiscordChatExporter.Cli/Commands/ExportDirectMessagesCommand.cs +++ b/DiscordChatExporter.Cli/Commands/ExportDirectMessagesCommand.cs @@ -13,15 +13,11 @@ namespace DiscordChatExporter.Cli.Commands { public override async ValueTask ExecuteAsync(IConsole console) { - await base.ExecuteAsync(console); - - // Get channel metadata await console.Output.WriteLineAsync("Fetching channels..."); var channels = await Discord.GetGuildChannelsAsync(Guild.DirectMessages.Id); var textChannels = channels.Where(c => c.IsTextChannel).ToArray(); - // Export - await ExportAsync(console, textChannels); + await base.ExecuteAsync(console, textChannels); } } } \ No newline at end of file diff --git a/DiscordChatExporter.Cli/Commands/ExportGuildCommand.cs b/DiscordChatExporter.Cli/Commands/ExportGuildCommand.cs index 8a4daf6..14f02d1 100644 --- a/DiscordChatExporter.Cli/Commands/ExportGuildCommand.cs +++ b/DiscordChatExporter.Cli/Commands/ExportGuildCommand.cs @@ -16,15 +16,11 @@ namespace DiscordChatExporter.Cli.Commands public override async ValueTask ExecuteAsync(IConsole console) { - await base.ExecuteAsync(console); - - // Get channel metadata await console.Output.WriteLineAsync("Fetching channels..."); var channels = await Discord.GetGuildChannelsAsync(GuildId); var textChannels = channels.Where(c => c.IsTextChannel).ToArray(); - // Export - await ExportAsync(console, textChannels); + await base.ExecuteAsync(console, textChannels); } } } \ No newline at end of file diff --git a/DiscordChatExporter.Cli/Commands/GetChannelsCommand.cs b/DiscordChatExporter.Cli/Commands/GetChannelsCommand.cs index dc691c3..fa59d96 100644 --- a/DiscordChatExporter.Cli/Commands/GetChannelsCommand.cs +++ b/DiscordChatExporter.Cli/Commands/GetChannelsCommand.cs @@ -36,9 +36,7 @@ namespace DiscordChatExporter.Cli.Commands // Channel category / name using (console.WithForegroundColor(ConsoleColor.White)) - await console.Output.WriteAsync($"{channel.Category} / {channel.Name}"); - - await console.Output.WriteLineAsync(); + await console.Output.WriteLineAsync($"{channel.Category} / {channel.Name}"); } } } diff --git a/DiscordChatExporter.Cli/Commands/GetDirectMessageChannelsCommand.cs b/DiscordChatExporter.Cli/Commands/GetDirectMessageChannelsCommand.cs index 4845be5..d5ba3e4 100644 --- a/DiscordChatExporter.Cli/Commands/GetDirectMessageChannelsCommand.cs +++ b/DiscordChatExporter.Cli/Commands/GetDirectMessageChannelsCommand.cs @@ -33,9 +33,7 @@ namespace DiscordChatExporter.Cli.Commands // Channel category / name using (console.WithForegroundColor(ConsoleColor.White)) - await console.Output.WriteAsync($"{channel.Category} / {channel.Name}"); - - await console.Output.WriteLineAsync(); + await console.Output.WriteLineAsync($"{channel.Category} / {channel.Name}"); } } } diff --git a/DiscordChatExporter.Cli/Commands/GetGuildsCommand.cs b/DiscordChatExporter.Cli/Commands/GetGuildsCommand.cs index 76f93cf..b2a32f6 100644 --- a/DiscordChatExporter.Cli/Commands/GetGuildsCommand.cs +++ b/DiscordChatExporter.Cli/Commands/GetGuildsCommand.cs @@ -26,9 +26,7 @@ namespace DiscordChatExporter.Cli.Commands // Guild name using (console.WithForegroundColor(ConsoleColor.White)) - await console.Output.WriteAsync(guild.Name); - - await console.Output.WriteLineAsync(); + await console.Output.WriteLineAsync(guild.Name); } } } diff --git a/DiscordChatExporter.Cli/Commands/GuideCommand.cs b/DiscordChatExporter.Cli/Commands/GuideCommand.cs index 8c6a7a6..5b49359 100644 --- a/DiscordChatExporter.Cli/Commands/GuideCommand.cs +++ b/DiscordChatExporter.Cli/Commands/GuideCommand.cs @@ -11,6 +11,7 @@ namespace DiscordChatExporter.Cli.Commands { public ValueTask ExecuteAsync(IConsole console) { + // User token using (console.WithForegroundColor(ConsoleColor.White)) console.Output.WriteLine("To get user token:"); @@ -25,6 +26,7 @@ namespace DiscordChatExporter.Cli.Commands console.Output.WriteLine(" * Automating user accounts is technically against TOS, use at your own risk."); console.Output.WriteLine(); + // Bot token using (console.WithForegroundColor(ConsoleColor.White)) console.Output.WriteLine("To get bot token:"); @@ -34,6 +36,7 @@ namespace DiscordChatExporter.Cli.Commands console.Output.WriteLine(" 4. Under Token click Copy"); console.Output.WriteLine(); + // Guild or channel ID using (console.WithForegroundColor(ConsoleColor.White)) console.Output.WriteLine("To get guild ID or guild channel ID:"); @@ -44,6 +47,7 @@ namespace DiscordChatExporter.Cli.Commands console.Output.WriteLine(" 5. Right click on the desired guild or channel and click Copy ID"); console.Output.WriteLine(); + // Direct message channel ID using (console.WithForegroundColor(ConsoleColor.White)) console.Output.WriteLine("To get direct message channel ID:"); @@ -55,9 +59,9 @@ namespace DiscordChatExporter.Cli.Commands console.Output.WriteLine(" 6. Copy the first long sequence of numbers inside the URL"); console.Output.WriteLine(); + // Wiki link using (console.WithForegroundColor(ConsoleColor.White)) console.Output.WriteLine("For more information, check out the wiki:"); - using (console.WithForegroundColor(ConsoleColor.DarkCyan)) console.Output.WriteLine("https://github.com/Tyrrrz/DiscordChatExporter/wiki"); diff --git a/DiscordChatExporter.Core/Discord/Data/ChannelCategory.cs b/DiscordChatExporter.Core/Discord/Data/ChannelCategory.cs index 1bdf86c..159adc2 100644 --- a/DiscordChatExporter.Core/Discord/Data/ChannelCategory.cs +++ b/DiscordChatExporter.Core/Discord/Data/ChannelCategory.cs @@ -27,6 +27,8 @@ namespace DiscordChatExporter.Core.Discord.Data public partial class ChannelCategory { + public static ChannelCategory Unknown { get; } = new(Snowflake.Zero, "", 0); + public static ChannelCategory Parse(JsonElement json, int? position = null) { var id = json.GetProperty("id").GetString().Pipe(Snowflake.Parse); @@ -41,7 +43,5 @@ namespace DiscordChatExporter.Core.Discord.Data position ?? json.GetPropertyOrNull("position")?.GetInt32() ); } - - public static ChannelCategory Empty { get; } = new(Snowflake.Zero, "", 0); } } \ No newline at end of file diff --git a/DiscordChatExporter.Core/Discord/Data/Message.cs b/DiscordChatExporter.Core/Discord/Data/Message.cs index 5504271..10adbf7 100644 --- a/DiscordChatExporter.Core/Discord/Data/Message.cs +++ b/DiscordChatExporter.Core/Discord/Data/Message.cs @@ -10,20 +10,6 @@ using JsonExtensions.Reading; namespace DiscordChatExporter.Core.Discord.Data { - // https://discord.com/developers/docs/resources/channel#message-object-message-types - public enum MessageKind - { - Default = 0, - RecipientAdd = 1, - RecipientRemove = 2, - Call = 3, - ChannelNameChange = 4, - ChannelIconChange = 5, - ChannelPinnedMessage = 6, - GuildMemberJoin = 7, - Reply = 19 - } - // https://discord.com/developers/docs/resources/channel#message-object public partial class Message : IHasId { diff --git a/DiscordChatExporter.Core/Discord/Data/MessageKind.cs b/DiscordChatExporter.Core/Discord/Data/MessageKind.cs new file mode 100644 index 0000000..78e632c --- /dev/null +++ b/DiscordChatExporter.Core/Discord/Data/MessageKind.cs @@ -0,0 +1,16 @@ +namespace DiscordChatExporter.Core.Discord.Data +{ + // https://discord.com/developers/docs/resources/channel#message-object-message-types + public enum MessageKind + { + Default = 0, + RecipientAdd = 1, + RecipientRemove = 2, + Call = 3, + ChannelNameChange = 4, + ChannelIconChange = 5, + ChannelPinnedMessage = 6, + GuildMemberJoin = 7, + Reply = 19 + } +} \ No newline at end of file diff --git a/DiscordChatExporter.Core/Discord/DiscordClient.cs b/DiscordChatExporter.Core/Discord/DiscordClient.cs index 3b4ddb3..abf93ef 100644 --- a/DiscordChatExporter.Core/Discord/DiscordClient.cs +++ b/DiscordChatExporter.Core/Discord/DiscordClient.cs @@ -160,13 +160,13 @@ namespace DiscordChatExporter.Core.Discord yield return Role.Parse(roleJson); } - public async ValueTask TryGetGuildMemberAsync(Snowflake guildId, User user) + public async ValueTask GetGuildMemberAsync(Snowflake guildId, User user) { if (guildId == Guild.DirectMessages.Id) return Member.CreateForUser(user); var response = await TryGetJsonResponseAsync($"guilds/{guildId}/members/{user.Id}"); - return response?.Pipe(Member.Parse); + return response?.Pipe(Member.Parse) ?? Member.CreateForUser(user); } public async ValueTask GetChannelCategoryAsync(Snowflake channelId) @@ -180,7 +180,7 @@ namespace DiscordChatExporter.Core.Discord // Instead, we use an empty channel category as a fallback. catch (DiscordChatExporterException) { - return ChannelCategory.Empty; + return ChannelCategory.Unknown; } } diff --git a/DiscordChatExporter.Core/Exceptions/DiscordChatExporterException.cs b/DiscordChatExporter.Core/Exceptions/DiscordChatExporterException.cs index d6b4d53..530da2f 100644 --- a/DiscordChatExporter.Core/Exceptions/DiscordChatExporterException.cs +++ b/DiscordChatExporter.Core/Exceptions/DiscordChatExporterException.cs @@ -5,12 +5,12 @@ namespace DiscordChatExporter.Core.Exceptions { public partial class DiscordChatExporterException : Exception { - public bool IsCritical { get; } + public bool IsFatal { get; } - public DiscordChatExporterException(string message, bool isCritical = false) + public DiscordChatExporterException(string message, bool isFatal = false) : base(message) { - IsCritical = isCritical; + IsFatal = isFatal; } } @@ -31,7 +31,7 @@ Failed to perform an HTTP request. } internal static DiscordChatExporterException Unauthorized() => - new("Authentication token is invalid."); + new("Authentication token is invalid.", true); internal static DiscordChatExporterException Forbidden() => new("Access is forbidden."); diff --git a/DiscordChatExporter.Core/Exporting/ChannelExporter.cs b/DiscordChatExporter.Core/Exporting/ChannelExporter.cs index 1d04d39..c959f39 100644 --- a/DiscordChatExporter.Core/Exporting/ChannelExporter.cs +++ b/DiscordChatExporter.Core/Exporting/ChannelExporter.cs @@ -49,10 +49,7 @@ namespace DiscordChatExporter.Core.Exporting if (!encounteredUsers.Add(referencedUser)) continue; - var member = - await _discord.TryGetGuildMemberAsync(request.Guild.Id, referencedUser) ?? - Member.CreateForUser(referencedUser); - + var member = await _discord.GetGuildMemberAsync(request.Guild.Id, referencedUser); contextMembers.Add(member); } diff --git a/DiscordChatExporter.Core/Exporting/MessageExporter.cs b/DiscordChatExporter.Core/Exporting/MessageExporter.cs index 4743a45..cb0643d 100644 --- a/DiscordChatExporter.Core/Exporting/MessageExporter.cs +++ b/DiscordChatExporter.Core/Exporting/MessageExporter.cs @@ -30,7 +30,7 @@ namespace DiscordChatExporter.Core.Exporting private async ValueTask GetWriterAsync() { - // Ensure partition limit has not been exceeded + // Ensure partition limit has not been reached if (_writer is not null && _context.Request.PartitionLimit.IsReached(_writer.MessagesWritten, _writer.BytesWritten)) { diff --git a/DiscordChatExporter.Gui/ViewModels/RootViewModel.cs b/DiscordChatExporter.Gui/ViewModels/RootViewModel.cs index 57968e5..9712c17 100644 --- a/DiscordChatExporter.Gui/ViewModels/RootViewModel.cs +++ b/DiscordChatExporter.Gui/ViewModels/RootViewModel.cs @@ -175,7 +175,7 @@ namespace DiscordChatExporter.Gui.ViewModels GuildChannelMap = guildChannelMap; SelectedGuild = guildChannelMap.Keys.FirstOrDefault(); } - catch (DiscordChatExporterException ex) when (!ex.IsCritical) + catch (DiscordChatExporterException ex) when (!ex.IsFatal) { Notifications.Enqueue(ex.Message.TrimEnd('.')); } @@ -234,7 +234,7 @@ namespace DiscordChatExporter.Gui.ViewModels Interlocked.Increment(ref successfulExportCount); } - catch (DiscordChatExporterException ex) when (!ex.IsCritical) + catch (DiscordChatExporterException ex) when (!ex.IsFatal) { Notifications.Enqueue(ex.Message.TrimEnd('.')); }