From 5a57c4202d13a70ae64f67dfa1c7bb49c1a37805 Mon Sep 17 00:00:00 2001 From: Jacob Pfundstein <81419612+CanePlayz@users.noreply.github.com> Date: Thu, 18 Jun 2026 14:37:58 +0200 Subject: [PATCH 1/2] Validate output path early to prevent errors when exporting multiple channels (#1555) Co-authored-by: Oleksii Holub <1935960+Tyrrrz@users.noreply.github.com> --- .../Commands/Base/ExportCommandBase.cs | 53 +++++++++++-------- 1 file changed, 31 insertions(+), 22 deletions(-) diff --git a/DiscordChatExporter.Cli/Commands/Base/ExportCommandBase.cs b/DiscordChatExporter.Cli/Commands/Base/ExportCommandBase.cs index 9476acbb..57b4ee49 100644 --- a/DiscordChatExporter.Cli/Commands/Base/ExportCommandBase.cs +++ b/DiscordChatExporter.Cli/Commands/Base/ExportCommandBase.cs @@ -165,6 +165,37 @@ public abstract class ExportCommandBase : DiscordCommandBase throw new CommandException("Option --media-dir cannot be used without --media."); } + // Make sure the user does not try to export multiple channels into one file. + // Output path must either be a directory or contain template tokens for this to work. + // Validate this up-front, before fetching threads, because thread fetching can take a + // long time and it's frustrating to fail only after it completes. + // https://github.com/Tyrrrz/DiscordChatExporter/issues/799 + // https://github.com/Tyrrrz/DiscordChatExporter/issues/917 + // https://github.com/Tyrrrz/DiscordChatExporter/issues/1549 + var mayExportMultipleChannels = + // Multiple channels were provided explicitly + channels.Count > 1 + // Thread inclusion can add more channels to the export + || ThreadInclusionMode != ThreadInclusionMode.None; + + var isValidOutputPath = + // Anything is valid when exporting a single channel + !mayExportMultipleChannels + // When using template tokens, assume the user knows what they're doing + || OutputPath.Contains('%') + // Otherwise, require an existing directory or an unambiguous directory path + || Directory.Exists(OutputPath) + || Path.EndsInDirectorySeparator(OutputPath); + + if (!isValidOutputPath) + { + throw new CommandException( + "Attempted to export multiple channels, but the output path is neither a directory nor a template. " + + "If the provided output path is meant to be treated as a directory, make sure it ends with a slash. " + + $"Provided output path: '{OutputPath}'." + ); + } + var unwrappedChannels = new List(channels); // Unwrap threads @@ -205,28 +236,6 @@ public abstract class ExportCommandBase : DiscordCommandBase await console.Output.WriteLineAsync($"Fetched {fetchedThreadsCount} thread(s)."); } - // Make sure the user does not try to export multiple channels into one file. - // Output path must either be a directory or contain template tokens for this to work. - // https://github.com/Tyrrrz/DiscordChatExporter/issues/799 - // https://github.com/Tyrrrz/DiscordChatExporter/issues/917 - var isValidOutputPath = - // Anything is valid when exporting a single channel - unwrappedChannels.Count <= 1 - // When using template tokens, assume the user knows what they're doing - || OutputPath.Contains('%') - // Otherwise, require an existing directory or an unambiguous directory path - || Directory.Exists(OutputPath) - || Path.EndsInDirectorySeparator(OutputPath); - - if (!isValidOutputPath) - { - throw new CommandException( - "Attempted to export multiple channels, but the output path is neither a directory nor a template. " - + "If the provided output path is meant to be treated as a directory, make sure it ends with a slash. " - + $"Provided output path: '{OutputPath}'." - ); - } - // Export var errorsByChannel = new ConcurrentDictionary(); var warningsByChannel = new ConcurrentDictionary(); From 97485c280be3a68970f528f7f5c8e521dfdff41e Mon Sep 17 00:00:00 2001 From: Jacob Pfundstein <81419612+CanePlayz@users.noreply.github.com> Date: Sat, 20 Jun 2026 12:55:49 +0200 Subject: [PATCH 2/2] Fix output path detection to correctly identify directories and files (#1556) --- DiscordChatExporter.Core/Exporting/ExportRequest.cs | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/DiscordChatExporter.Core/Exporting/ExportRequest.cs b/DiscordChatExporter.Core/Exporting/ExportRequest.cs index db83d98a..1285b246 100644 --- a/DiscordChatExporter.Core/Exporting/ExportRequest.cs +++ b/DiscordChatExporter.Core/Exporting/ExportRequest.cs @@ -206,10 +206,15 @@ public partial class ExportRequest { var actualOutputPath = FormatPath(outputPath, guild, channel, after, before); - // Output is a directory + // Determine whether the output path refers to a directory or a file. + // The extension-based heuristic is evaluated on the original, unsubstituted path, + // because the value of a template token (e.g. a guild or channel name) may contain + // a period that would otherwise be mistaken for a file extension, incorrectly causing + // a directory path to be treated as a file. + // https://github.com/Tyrrrz/DiscordChatExporter/issues/1502 if ( Directory.Exists(actualOutputPath) - || string.IsNullOrWhiteSpace(Path.GetExtension(actualOutputPath)) + || string.IsNullOrWhiteSpace(Path.GetExtension(outputPath)) ) { var fileName = GetDefaultOutputFileName(guild, channel, format, after, before);