Skip to content

Commit

Permalink
Merge
Browse files Browse the repository at this point in the history
  • Loading branch information
nulldg committed Aug 28, 2023
2 parents feb95ad + d430f77 commit a3c1071
Show file tree
Hide file tree
Showing 17 changed files with 192 additions and 129 deletions.
103 changes: 55 additions & 48 deletions DiscordChatExporter.Cli/Commands/Base/ExportCommandBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -170,56 +170,61 @@ protected async ValueTask ExportAsync(IConsole console, IReadOnlyList<Channel> c
var errorsByChannel = new ConcurrentDictionary<Channel, string>();

await console.Output.WriteLineAsync($"Exporting {channels.Count} channel(s)...");
await console.CreateProgressTicker().StartAsync(async progressContext =>
{
await Parallel.ForEachAsync(
channels,
new ParallelOptions
{
MaxDegreeOfParallelism = Math.Max(1, ParallelLimit),
CancellationToken = cancellationToken
},
async (channel, innerCancellationToken) =>
{
try
await console
.CreateProgressTicker()
.StartAsync(async progressContext =>
{
await Parallel.ForEachAsync(
channels,
new ParallelOptions
{
await progressContext.StartTaskAsync(
$"{channel.Category} / {channel.Name}",
async progress =>
{
var guild = await Discord.GetGuildAsync(channel.GuildId, innerCancellationToken);
var request = new ExportRequest(
guild,
channel,
OutputPath,
AssetsDirPath,
ExportFormat,
After,
Before,
PartitionLimit,
MessageFilter,
ShouldFormatMarkdown,
ShouldDownloadAssets,
ShouldReuseAssets,
DateFormat
);
await Exporter.ExportChannelAsync(
request,
progress.ToPercentageBased(),
innerCancellationToken
);
}
);
}
catch (DiscordChatExporterException ex) when (!ex.IsFatal)
MaxDegreeOfParallelism = Math.Max(1, ParallelLimit),
CancellationToken = cancellationToken
},
async (channel, innerCancellationToken) =>
{
errorsByChannel[channel] = ex.Message;
try
{
await progressContext.StartTaskAsync(
$"{channel.ParentNameWithFallback} / {channel.Name}",
async progress =>
{
var guild = await Discord.GetGuildAsync(
channel.GuildId,
innerCancellationToken
);
var request = new ExportRequest(
guild,
channel,
OutputPath,
AssetsDirPath,
ExportFormat,
After,
Before,
PartitionLimit,
MessageFilter,
ShouldFormatMarkdown,
ShouldDownloadAssets,
ShouldReuseAssets,
DateFormat
);
await Exporter.ExportChannelAsync(
request,
progress.ToPercentageBased(),
innerCancellationToken
);
}
);
}
catch (DiscordChatExporterException ex) when (!ex.IsFatal)
{
errorsByChannel[channel] = ex.Message;
}
}
}
);
});
);
});

// Print the result
using (console.WithForegroundColor(ConsoleColor.White))
Expand All @@ -243,7 +248,9 @@ await console.Error.WriteLineAsync(

foreach (var (channel, error) in errorsByChannel)
{
await console.Error.WriteAsync($"{channel.Category} / {channel.Name}: ");
await console.Error.WriteAsync(
$"{channel.ParentNameWithFallback} / {channel.Name}: "
);

using (console.WithForegroundColor(ConsoleColor.Red))
await console.Error.WriteLineAsync(error);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
using System;
using CliFx.Extensibility;
using DiscordChatExporter.Cli.Commands.Shared;

namespace DiscordChatExporter.Cli.Commands.Converters;

internal class ThreadInclusionBindingConverter : BindingConverter<ThreadInclusion>
{
public override ThreadInclusion Convert(string? rawValue)
{
// Empty or unset value is treated as 'active' to match the previous behavior
if (string.IsNullOrWhiteSpace(rawValue))
return ThreadInclusion.Active;

// Boolean 'true' is treated as 'active', boolean 'false' is treated as 'none'
if (bool.TryParse(rawValue, out var boolValue))
return boolValue ? ThreadInclusion.Active : ThreadInclusion.None;

// Otherwise, fall back to regular enum parsing
return Enum.Parse<ThreadInclusion>(rawValue, true);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,19 +7,15 @@ internal class TruthyBooleanBindingConverter : BindingConverter<bool>
{
public override bool Convert(string? rawValue)
{
// Null is still considered true, to match the base behavior
if (rawValue is null)
return true;

// Empty or unset value is treated as 'true', to match the regular boolean behavior
if (string.IsNullOrWhiteSpace(rawValue))
return false;

if (bool.TryParse(rawValue, out var boolValue))
return boolValue;
return true;

if (int.TryParse(rawValue, CultureInfo.InvariantCulture, out var intValue) && intValue == 0)
return false;
// Number '1' is treated as 'true', other numbers are treated as 'false'
if (int.TryParse(rawValue, CultureInfo.InvariantCulture, out var intValue))
return intValue == 1;

return true;
// Otherwise, fall back to regular boolean parsing
return bool.Parse(rawValue);
}
}
23 changes: 8 additions & 15 deletions DiscordChatExporter.Cli/Commands/ExportAllCommand.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@
using CliFx.Exceptions;
using CliFx.Infrastructure;
using DiscordChatExporter.Cli.Commands.Base;
using DiscordChatExporter.Cli.Commands.Converters;
using DiscordChatExporter.Cli.Commands.Shared;
using DiscordChatExporter.Core.Discord;
using DiscordChatExporter.Core.Discord.Data;
using DiscordChatExporter.Core.Exceptions;
Expand Down Expand Up @@ -36,15 +38,14 @@ public class ExportAllCommand : ExportCommandBase

[CommandOption(
"include-threads",
Description = "Include threads."
Description = "Specifies which types of threads should be included.",
Converter = typeof(ThreadInclusionBindingConverter)
)]
public bool IncludeThreads { get; init; } = false;
public ThreadInclusion ThreadInclusion { get; init; } = ThreadInclusion.None;

[CommandOption(
"include-archived-threads",
Description = "Include archived threads."
)]
public bool IncludeArchivedThreads { get; init; } = false;
private bool IncludeThreads => ThreadInclusion != ThreadInclusion.None;

private bool IncludeArchivedThreads => ThreadInclusion.HasFlag(ThreadInclusion.Archived);

[CommandOption(
"data-package",
Expand All @@ -58,14 +59,6 @@ public override async ValueTask ExecuteAsync(IConsole console)
{
await base.ExecuteAsync(console);

// Cannot include archived threads without including active threads as well
if (IncludeArchivedThreads && !IncludeThreads)
{
throw new CommandException(
"Option --include-archived-threads can only be used when --include-threads is also specified."
);
}

var cancellationToken = console.RegisterCancellationHandler();
var channels = new List<Channel>();

Expand Down
24 changes: 8 additions & 16 deletions DiscordChatExporter.Cli/Commands/ExportGuildCommand.cs
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
using System.Collections.Generic;
using System.Threading.Tasks;
using CliFx.Attributes;
using CliFx.Exceptions;
using CliFx.Infrastructure;
using DiscordChatExporter.Cli.Commands.Base;
using DiscordChatExporter.Cli.Commands.Converters;
using DiscordChatExporter.Cli.Commands.Shared;
using DiscordChatExporter.Core.Discord;
using DiscordChatExporter.Core.Discord.Data;

Expand All @@ -27,28 +28,19 @@ public class ExportGuildCommand : ExportCommandBase

[CommandOption(
"include-threads",
Description = "Include threads."
Description = "Specifies which types of threads should be included.",
Converter = typeof(ThreadInclusionBindingConverter)
)]
public bool IncludeThreads { get; init; } = false;
public ThreadInclusion ThreadInclusion { get; init; } = ThreadInclusion.None;

[CommandOption(
"include-archived-threads",
Description = "Include archived threads."
)]
public bool IncludeArchivedThreads { get; init; } = false;
private bool IncludeThreads => ThreadInclusion != ThreadInclusion.None;

private bool IncludeArchivedThreads => ThreadInclusion.HasFlag(ThreadInclusion.Archived);

public override async ValueTask ExecuteAsync(IConsole console)
{
await base.ExecuteAsync(console);

// Cannot include archived threads without including active threads as well
if (IncludeArchivedThreads && !IncludeThreads)
{
throw new CommandException(
"Option --include-archived-threads can only be used when --include-threads is also specified."
);
}

var cancellationToken = console.RegisterCancellationHandler();
var channels = new List<Channel>();

Expand Down
28 changes: 11 additions & 17 deletions DiscordChatExporter.Cli/Commands/GetChannelsCommand.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,10 @@
using System.Linq;
using System.Threading.Tasks;
using CliFx.Attributes;
using CliFx.Exceptions;
using CliFx.Infrastructure;
using DiscordChatExporter.Cli.Commands.Base;
using DiscordChatExporter.Cli.Commands.Converters;
using DiscordChatExporter.Cli.Commands.Shared;
using DiscordChatExporter.Core.Discord;
using DiscordChatExporter.Core.Discord.Data;
using DiscordChatExporter.Core.Utils.Extensions;
Expand All @@ -29,28 +30,19 @@ public class GetChannelsCommand : DiscordCommandBase

[CommandOption(
"include-threads",
Description = "Include threads."
Description = "Specifies which types of threads should be included.",
Converter = typeof(ThreadInclusionBindingConverter)
)]
public bool IncludeThreads { get; init; } = false;
public ThreadInclusion ThreadInclusion { get; init; } = ThreadInclusion.None;

[CommandOption(
"include-archived-threads",
Description = "Include archived threads."
)]
public bool IncludeArchivedThreads { get; init; } = false;
private bool IncludeThreads => ThreadInclusion != ThreadInclusion.None;

private bool IncludeArchivedThreads => ThreadInclusion.HasFlag(ThreadInclusion.Archived);

public override async ValueTask ExecuteAsync(IConsole console)
{
await base.ExecuteAsync(console);

// Cannot include archived threads without including active threads as well
if (IncludeArchivedThreads && !IncludeThreads)
{
throw new CommandException(
"Option --include-archived-threads can only be used when --include-threads is also specified."
);
}

var cancellationToken = console.RegisterCancellationHandler();

var channels = (await Discord.GetGuildChannelsAsync(GuildId, cancellationToken))
Expand Down Expand Up @@ -84,7 +76,9 @@ await console.Output.WriteAsync(

// Channel category / name
using (console.WithForegroundColor(ConsoleColor.White))
await console.Output.WriteLineAsync($"{channel.Category} / {channel.Name}");
await console.Output.WriteLineAsync(
$"{channel.ParentNameWithFallback} / {channel.Name}"
);

var channelThreads = threads.Where(t => t.Parent?.Id == channel.Id).ToArray();
var channelThreadIdMaxLength = channelThreads
Expand Down
4 changes: 3 additions & 1 deletion DiscordChatExporter.Cli/Commands/GetDirectChannelsCommand.cs
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,9 @@ await console.Output.WriteAsync(

// Channel category / name
using (console.WithForegroundColor(ConsoleColor.White))
await console.Output.WriteLineAsync($"{channel.Category} / {channel.Name}");
await console.Output.WriteLineAsync(
$"{channel.ParentNameWithFallback} / {channel.Name}"
);
}
}
}
12 changes: 12 additions & 0 deletions DiscordChatExporter.Cli/Commands/Shared/ThreadInclusion.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
using System;

namespace DiscordChatExporter.Cli.Commands.Shared;

[Flags]
public enum ThreadInclusion
{
None = 0,
Active = 1,
Archived = 2,
All = Active | Archived
}
29 changes: 17 additions & 12 deletions DiscordChatExporter.Core/Discord/Data/Channel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -23,21 +23,26 @@ public partial record Channel(
// channels without a parent (i.e. mostly DM channels) or channels
// with an inaccessible parent (i.e. inside private categories) had
// a fallback category created for them.
public string Category => Parent?.Name ?? Kind switch
{
ChannelKind.GuildCategory => "Category",
ChannelKind.GuildTextChat => "Text",
ChannelKind.DirectTextChat => "Private",
ChannelKind.DirectGroupTextChat => "Group",
ChannelKind.GuildPrivateThread => "Private Thread",
ChannelKind.GuildPublicThread => "Public Thread",
ChannelKind.GuildNews => "News",
ChannelKind.GuildNewsThread => "News Thread",
_ => "Default"
};
public string ParentNameWithFallback =>
Parent?.Name
?? Kind switch
{
ChannelKind.GuildCategory => "Category",
ChannelKind.GuildTextChat => "Text",
ChannelKind.DirectTextChat => "Private",
ChannelKind.DirectGroupTextChat => "Group",
ChannelKind.GuildPrivateThread => "Private Thread",
ChannelKind.GuildPublicThread => "Public Thread",
ChannelKind.GuildNews => "News",
ChannelKind.GuildNewsThread => "News Thread",
_ => "Default"
};

// Only needed for WPF data binding. Don't use anywhere else.
public bool IsVoice => Kind.IsVoice();

// Only needed for WPF data binding. Don't use anywhere else.
public bool IsThread => Kind.IsThread();
}

public partial record Channel
Expand Down
4 changes: 3 additions & 1 deletion DiscordChatExporter.Core/Exporting/ExportRequest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,9 @@ public static string GetDefaultOutputFileName(
var buffer = new StringBuilder();

// Guild and channel names
buffer.Append($"{guild.Name} - {channel.Category} - {channel.Name} [{channel.Id}]");
buffer.Append(
$"{guild.Name} - {channel.ParentNameWithFallback} - {channel.Name} [{channel.Id}]"
);

// Date range
if (after is not null || before is not null)
Expand Down
Loading

0 comments on commit a3c1071

Please sign in to comment.