From 026927265a2b6eb234be20f7eb699ebf1ef2de01 Mon Sep 17 00:00:00 2001 From: Tyrrrz Date: Wed, 24 Mar 2021 03:04:46 +0200 Subject: [PATCH] [CLI] Remove ExportMultipleCommandBase and allow multiple channel IDs in ExportChannelCommand --- .../Commands/Base/ExportCommandBase.cs | 118 ++++++++++++++---- .../Base/ExportMultipleCommandBase.cs | 78 ------------ .../Commands/ExportAllCommand.cs | 4 +- .../Commands/ExportChannelCommand.cs | 34 ----- .../Commands/ExportChannelsCommand.cs | 38 ++++++ .../Commands/ExportDirectMessagesCommand.cs | 4 +- .../Commands/ExportGuildCommand.cs | 4 +- 7 files changed, 138 insertions(+), 142 deletions(-) delete mode 100644 DiscordChatExporter.Cli/Commands/Base/ExportMultipleCommandBase.cs delete mode 100644 DiscordChatExporter.Cli/Commands/ExportChannelCommand.cs create mode 100644 DiscordChatExporter.Cli/Commands/ExportChannelsCommand.cs diff --git a/DiscordChatExporter.Cli/Commands/Base/ExportCommandBase.cs b/DiscordChatExporter.Cli/Commands/Base/ExportCommandBase.cs index 6fecab3..3c1b109 100644 --- a/DiscordChatExporter.Cli/Commands/Base/ExportCommandBase.cs +++ b/DiscordChatExporter.Cli/Commands/Base/ExportCommandBase.cs @@ -1,12 +1,20 @@ -using System.IO; +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.IO; +using System.Linq; using System.Threading.Tasks; using CliFx.Attributes; using CliFx.Exceptions; using CliFx.Infrastructure; +using DiscordChatExporter.Cli.Utils.Extensions; using DiscordChatExporter.Core.Discord; using DiscordChatExporter.Core.Discord.Data; +using DiscordChatExporter.Core.Exceptions; using DiscordChatExporter.Core.Exporting; +using DiscordChatExporter.Core.Utils.Extensions; using Spectre.Console; +using Tyrrrz.Extensions; namespace DiscordChatExporter.Cli.Commands.Base { @@ -27,6 +35,9 @@ namespace DiscordChatExporter.Cli.Commands.Base [CommandOption("partition", 'p', Description = "Split output into partitions limited to this number of messages.")] public int? PartitionLimit { get; init; } + [CommandOption("parallel", Description = "Limits how many channels can be exported in parallel.")] + public int ParallelLimit { get; init; } = 1; + [CommandOption("media", Description = "Download referenced media content.")] public bool ShouldDownloadMedia { get; init; } @@ -39,34 +50,93 @@ namespace DiscordChatExporter.Cli.Commands.Base private ChannelExporter? _channelExporter; protected ChannelExporter Exporter => _channelExporter ??= new ChannelExporter(Discord); - protected async ValueTask ExportChannelAsync(Guild guild, Channel channel, ProgressContext progressContext) + protected async ValueTask ExportAsync(IConsole console, IReadOnlyList channels) { - var request = new ExportRequest( - guild, - channel, - OutputPath, - ExportFormat, - After, - Before, - PartitionLimit, - ShouldDownloadMedia, - ShouldReuseMedia, - DateFormat - ); - - var progress = progressContext.AddTask( - $"{channel.Category} / {channel.Name}", - new ProgressTaskSettings {MaxValue = 1} - ); - - try + await console.Output.WriteLineAsync($"Exporting {channels.Count} channel(s)..."); + + var errors = new ConcurrentDictionary(); + + // Wrap everything in a progress ticker + await console.CreateProgressTicker().StartAsync(async progressContext => + { + await channels.ParallelForEachAsync(async channel => + { + // Export + try + { + var guild = await Discord.GetGuildAsync(channel.GuildId); + + var request = new ExportRequest( + guild, + channel, + OutputPath, + ExportFormat, + After, + Before, + PartitionLimit, + ShouldDownloadMedia, + ShouldReuseMedia, + DateFormat + ); + + var progress = progressContext.AddTask( + $"{channel.Category} / {channel.Name}", + new ProgressTaskSettings {MaxValue = 1} + ); + + try + { + await Exporter.ExportChannelAsync(request, progress); + } + finally + { + progress.StopTask(); + } + } + catch (DiscordChatExporterException ex) when (!ex.IsCritical) + { + errors[channel] = ex.Message; + } + }, ParallelLimit.ClampMin(1)); + + await console.Output.WriteLineAsync(); + }); + + // Print result + using (console.WithForegroundColor(ConsoleColor.Green)) { - await Exporter.ExportChannelAsync(request, progress); + await console.Output.WriteLineAsync( + $"Successfully exported {channels.Count - errors.Count} channel(s)." + ); } - finally + + // Print errors + if (errors.Any()) { - progress.StopTask(); + await console.Output.WriteLineAsync(); + + using (console.WithForegroundColor(ConsoleColor.Red)) + await console.Output.WriteLineAsync($"Failed to export {errors.Count} channel(s):"); + + foreach (var (channel, error) in errors) + { + await console.Output.WriteAsync($"{channel.Category} / {channel.Name}: "); + + using (console.WithForegroundColor(ConsoleColor.Red)) + await console.Output.WriteLineAsync(error); + } + + 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. + if (errors.Count >= channels.Count) + { + throw new CommandException("Export failed."); + } + + await console.Output.WriteLineAsync("Done."); } public override ValueTask ExecuteAsync(IConsole console) diff --git a/DiscordChatExporter.Cli/Commands/Base/ExportMultipleCommandBase.cs b/DiscordChatExporter.Cli/Commands/Base/ExportMultipleCommandBase.cs deleted file mode 100644 index ce46897..0000000 --- a/DiscordChatExporter.Cli/Commands/Base/ExportMultipleCommandBase.cs +++ /dev/null @@ -1,78 +0,0 @@ -using System; -using System.Collections.Concurrent; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; -using CliFx.Attributes; -using CliFx.Exceptions; -using CliFx.Infrastructure; -using DiscordChatExporter.Cli.Utils.Extensions; -using DiscordChatExporter.Core.Discord.Data; -using DiscordChatExporter.Core.Exceptions; -using DiscordChatExporter.Core.Utils.Extensions; -using Tyrrrz.Extensions; - -namespace DiscordChatExporter.Cli.Commands.Base -{ - public abstract class ExportMultipleCommandBase : ExportCommandBase - { - [CommandOption("parallel", Description = "Limits how many channels can be exported in parallel.")] - public int ParallelLimit { get; init; } = 1; - - protected async ValueTask ExportChannelsAsync(IConsole console, IReadOnlyList channels) - { - await console.Output.WriteLineAsync($"Exporting {channels.Count} channel(s)..."); - - var errors = new ConcurrentDictionary(); - - await console.CreateProgressTicker().StartAsync(async progressContext => - { - await channels.ParallelForEachAsync(async channel => - { - try - { - var guild = await Discord.GetGuildAsync(channel.GuildId); - await ExportChannelAsync(guild, channel, progressContext); - } - catch (DiscordChatExporterException ex) when (!ex.IsCritical) - { - errors[channel] = ex.Message; - } - }, ParallelLimit.ClampMin(1)); - - await console.Output.WriteLineAsync(); - }); - - // Print result - await console.Output.WriteLineAsync( - $"Successfully exported {channels.Count - errors.Count} channel(s)." - ); - - // Print errors - if (errors.Any()) - { - using (console.WithForegroundColor(ConsoleColor.Red)) - await console.Output.WriteLineAsync($"Failed to export {errors.Count} channel(s):"); - - foreach (var (channel, error) in errors) - { - await console.Output.WriteAsync($"{channel.Category} / {channel.Name}: "); - - using (console.WithForegroundColor(ConsoleColor.Red)) - await console.Output.WriteLineAsync(error); - } - - 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. - if (errors.Count >= channels.Count) - { - throw new CommandException("Export failed."); - } - - await console.Output.WriteLineAsync("Done."); - } - } -} \ No newline at end of file diff --git a/DiscordChatExporter.Cli/Commands/ExportAllCommand.cs b/DiscordChatExporter.Cli/Commands/ExportAllCommand.cs index 714e240..ed196da 100644 --- a/DiscordChatExporter.Cli/Commands/ExportAllCommand.cs +++ b/DiscordChatExporter.Cli/Commands/ExportAllCommand.cs @@ -8,7 +8,7 @@ using DiscordChatExporter.Core.Discord.Data; namespace DiscordChatExporter.Cli.Commands { [Command("exportall", Description = "Export all accessible channels.")] - public class ExportAllCommand : ExportMultipleCommandBase + public class ExportAllCommand : ExportCommandBase { [CommandOption("include-dm", Description = "Include direct message channels.")] public bool IncludeDirectMessages { get; init; } = true; @@ -36,7 +36,7 @@ namespace DiscordChatExporter.Cli.Commands } // Export - await ExportChannelsAsync(console, channels); + await ExportAsync(console, channels); } } } \ No newline at end of file diff --git a/DiscordChatExporter.Cli/Commands/ExportChannelCommand.cs b/DiscordChatExporter.Cli/Commands/ExportChannelCommand.cs deleted file mode 100644 index 68f78c8..0000000 --- a/DiscordChatExporter.Cli/Commands/ExportChannelCommand.cs +++ /dev/null @@ -1,34 +0,0 @@ -using System.Threading.Tasks; -using CliFx.Attributes; -using CliFx.Infrastructure; -using DiscordChatExporter.Cli.Commands.Base; -using DiscordChatExporter.Cli.Utils.Extensions; -using DiscordChatExporter.Core.Discord; - -namespace DiscordChatExporter.Cli.Commands -{ - [Command("export", Description = "Export a channel.")] - public class ExportChannelCommand : ExportCommandBase - { - [CommandOption("channel", 'c', IsRequired = true, Description = "Channel ID.")] - public Snowflake ChannelId { get; init; } - - public override async ValueTask ExecuteAsync(IConsole console) - { - await base.ExecuteAsync(console); - - // Get channel metadata - await console.Output.WriteLineAsync("Fetching channel..."); - var channel = await Discord.GetChannelAsync(ChannelId); - var guild = await Discord.GetGuildAsync(channel.GuildId); - - // Export - await console.Output.WriteLineAsync("Exporting..."); - await console.CreateProgressTicker().StartAsync(async progressContext => - { - await ExportChannelAsync(guild, channel, progressContext); - }); - await console.Output.WriteLineAsync("Done."); - } - } -} \ No newline at end of file diff --git a/DiscordChatExporter.Cli/Commands/ExportChannelsCommand.cs b/DiscordChatExporter.Cli/Commands/ExportChannelsCommand.cs new file mode 100644 index 0000000..5c219b8 --- /dev/null +++ b/DiscordChatExporter.Cli/Commands/ExportChannelsCommand.cs @@ -0,0 +1,38 @@ +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using CliFx.Attributes; +using CliFx.Infrastructure; +using DiscordChatExporter.Cli.Commands.Base; +using DiscordChatExporter.Core.Discord; +using DiscordChatExporter.Core.Discord.Data; + +namespace DiscordChatExporter.Cli.Commands +{ + [Command("export", Description = "Export one or multiple channels.")] + public class ExportChannelsCommand : ExportCommandBase + { + // TODO: change this to plural with a breaking change + [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); + } + } +} \ No newline at end of file diff --git a/DiscordChatExporter.Cli/Commands/ExportDirectMessagesCommand.cs b/DiscordChatExporter.Cli/Commands/ExportDirectMessagesCommand.cs index 785c377..b2db56a 100644 --- a/DiscordChatExporter.Cli/Commands/ExportDirectMessagesCommand.cs +++ b/DiscordChatExporter.Cli/Commands/ExportDirectMessagesCommand.cs @@ -8,7 +8,7 @@ using DiscordChatExporter.Core.Utils.Extensions; namespace DiscordChatExporter.Cli.Commands { [Command("exportdm", Description = "Export all direct message channels.")] - public class ExportDirectMessagesCommand : ExportMultipleCommandBase + public class ExportDirectMessagesCommand : ExportCommandBase { public override async ValueTask ExecuteAsync(IConsole console) { @@ -19,7 +19,7 @@ namespace DiscordChatExporter.Cli.Commands var channels = await Discord.GetGuildChannelsAsync(Guild.DirectMessages.Id); // Export - await ExportChannelsAsync(console, channels); + await ExportAsync(console, channels); } } } \ No newline at end of file diff --git a/DiscordChatExporter.Cli/Commands/ExportGuildCommand.cs b/DiscordChatExporter.Cli/Commands/ExportGuildCommand.cs index e18188b..fb6230d 100644 --- a/DiscordChatExporter.Cli/Commands/ExportGuildCommand.cs +++ b/DiscordChatExporter.Cli/Commands/ExportGuildCommand.cs @@ -8,7 +8,7 @@ using DiscordChatExporter.Core.Utils.Extensions; namespace DiscordChatExporter.Cli.Commands { [Command("exportguild", Description = "Export all channels within specified guild.")] - public class ExportGuildCommand : ExportMultipleCommandBase + public class ExportGuildCommand : ExportCommandBase { [CommandOption("guild", 'g', IsRequired = true, Description = "Guild ID.")] public Snowflake GuildId { get; init; } @@ -22,7 +22,7 @@ namespace DiscordChatExporter.Cli.Commands var channels = await Discord.GetGuildChannelsAsync(GuildId); // Export - await ExportChannelsAsync(console, channels); + await ExportAsync(console, channels); } } } \ No newline at end of file