Add option to specify download directory for assets (#989)

Co-authored-by: Oleksii Holub <1935960+Tyrrrz@users.noreply.github.com>
pull/1003/head
Lucas LaBuff 2 years ago committed by GitHub
parent 53ff8ba430
commit 95115f3e99
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -98,6 +98,20 @@ public abstract class ExportCommandBase : TokenCommandBase
)] )]
public bool ShouldReuseAssets { get; init; } public bool ShouldReuseAssets { get; init; }
private readonly string? _assetsPath;
[CommandOption(
"media-dir",
Description = "Download assets to this directory."
)]
public string? AssetsPath
{
get => _assetsPath;
// Handle ~/ in paths on Unix systems
// https://github.com/Tyrrrz/DiscordChatExporter/pull/903
init => _assetsPath = value is not null ? Path.GetFullPath(value) : null;
}
[CommandOption( [CommandOption(
"dateformat", "dateformat",
Description = "Format used when writing dates." Description = "Format used when writing dates."
@ -124,6 +138,14 @@ public abstract class ExportCommandBase : TokenCommandBase
); );
} }
// Assets directory should only be specified when the download assets option is set
if (!string.IsNullOrWhiteSpace(AssetsPath) && !ShouldDownloadAssets)
{
throw new CommandException(
"Option --media-dir cannot be used without --media."
);
}
// Make sure the user does not try to export all channels into a single file. // Make sure the user does not try to export all channels into a single file.
// Output path must either be a directory, or contain template tokens. // Output path must either be a directory, or contain template tokens.
// https://github.com/Tyrrrz/DiscordChatExporter/issues/799 // https://github.com/Tyrrrz/DiscordChatExporter/issues/799
@ -172,6 +194,7 @@ public abstract class ExportCommandBase : TokenCommandBase
guild, guild,
channel, channel,
OutputPath, OutputPath,
AssetsPath,
ExportFormat, ExportFormat,
After, After,
Before, Before,

@ -92,13 +92,19 @@ internal class ExportContext
try try
{ {
var filePath = await _assetDownloader.DownloadAsync(url, cancellationToken); var absoluteFilePath = await _assetDownloader.DownloadAsync(url, cancellationToken);
// We want relative path so that the output files can be copied around without breaking. // We want relative path so that the output files can be copied around without breaking.
// Base directory path may be null if the file is stored at the root or relative to working directory. // Base directory path may be null if the file is stored at the root or relative to working directory.
var relativeFilePath = !string.IsNullOrWhiteSpace(Request.OutputBaseDirPath) var relativeFilePath = !string.IsNullOrWhiteSpace(Request.OutputBaseDirPath)
? Path.GetRelativePath(Request.OutputBaseDirPath, filePath) ? Path.GetRelativePath(Request.OutputBaseDirPath, absoluteFilePath)
: filePath; : absoluteFilePath;
// If the assets path is outside of the export directory, fall back to absolute path
var filePath = relativeFilePath.StartsWith("..")
? absoluteFilePath
: relativeFilePath;
// HACK: for HTML, we need to format the URL properly // HACK: for HTML, we need to format the URL properly
if (Request.Format is ExportFormat.HtmlDark or ExportFormat.HtmlLight) if (Request.Format is ExportFormat.HtmlDark or ExportFormat.HtmlLight)
@ -106,13 +112,14 @@ internal class ExportContext
// Need to escape each path segment while keeping the directory separators intact // Need to escape each path segment while keeping the directory separators intact
return string.Join( return string.Join(
Path.AltDirectorySeparatorChar, Path.AltDirectorySeparatorChar,
relativeFilePath filePath
.Split(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar) .Split(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar)
.Select(Uri.EscapeDataString) .Select(Uri.EscapeDataString)
.Select(x => x.Replace("%3A", ":"))
); );
} }
return relativeFilePath; return filePath;
} }
// Try to catch only exceptions related to failed HTTP requests // Try to catch only exceptions related to failed HTTP requests
// https://github.com/Tyrrrz/DiscordChatExporter/issues/332 // https://github.com/Tyrrrz/DiscordChatExporter/issues/332

@ -14,6 +14,7 @@ public partial record ExportRequest(
Guild Guild, Guild Guild,
Channel Channel, Channel Channel,
string OutputPath, string OutputPath,
string? AssetsPath,
ExportFormat Format, ExportFormat Format,
Snowflake? After, Snowflake? After,
Snowflake? Before, Snowflake? Before,
@ -36,7 +37,18 @@ public partial record ExportRequest(
public string OutputBaseDirPath => Path.GetDirectoryName(OutputBaseFilePath) ?? OutputPath; public string OutputBaseDirPath => Path.GetDirectoryName(OutputBaseFilePath) ?? OutputPath;
public string OutputAssetsDirPath => $"{OutputBaseFilePath}_Files{Path.DirectorySeparatorChar}"; private string? _outputAssetsDirPath;
public string OutputAssetsDirPath => _outputAssetsDirPath ??= (
AssetsPath is not null
? EvaluateTemplateTokens(
AssetsPath,
Guild,
Channel,
After,
Before
)
: $"{OutputBaseFilePath}_Files{Path.DirectorySeparatorChar}"
);
} }
public partial record ExportRequest public partial record ExportRequest
@ -83,17 +95,15 @@ public partial record ExportRequest
return PathEx.EscapeFileName(buffer.ToString()); return PathEx.EscapeFileName(buffer.ToString());
} }
private static string GetOutputBaseFilePath( private static string EvaluateTemplateTokens(
string path,
Guild guild, Guild guild,
Channel channel, Channel channel,
string outputPath, Snowflake? after,
ExportFormat format, Snowflake? before)
Snowflake? after = null,
Snowflake? before = null)
{ {
// Format path return Regex.Replace(
var actualOutputPath = Regex.Replace( path,
outputPath,
"%.", "%.",
m => PathEx.EscapeFileName(m.Value switch m => PathEx.EscapeFileName(m.Value switch
{ {
@ -110,8 +120,18 @@ public partial record ExportRequest
"%d" => DateTimeOffset.Now.ToString("yyyy-MM-dd"), "%d" => DateTimeOffset.Now.ToString("yyyy-MM-dd"),
"%%" => "%", "%%" => "%",
_ => m.Value _ => m.Value
}) }));
); }
private static string GetOutputBaseFilePath(
Guild guild,
Channel channel,
string outputPath,
ExportFormat format,
Snowflake? after = null,
Snowflake? before = null)
{
var actualOutputPath = EvaluateTemplateTokens(outputPath, guild, channel, after, before);
// Output is a directory // Output is a directory
if (Directory.Exists(actualOutputPath) || string.IsNullOrWhiteSpace(Path.GetExtension(actualOutputPath))) if (Directory.Exists(actualOutputPath) || string.IsNullOrWhiteSpace(Path.GetExtension(actualOutputPath)))

@ -186,6 +186,7 @@ public class DashboardViewModel : PropertyChangedBase
dialog.Guild!, dialog.Guild!,
channel, channel,
dialog.OutputPath!, dialog.OutputPath!,
null,
dialog.SelectedFormat, dialog.SelectedFormat,
dialog.After?.Pipe(Snowflake.FromDate), dialog.After?.Pipe(Snowflake.FromDate),
dialog.Before?.Pipe(Snowflake.FromDate), dialog.Before?.Pipe(Snowflake.FromDate),

Loading…
Cancel
Save