mirror of
https://github.com/Tyrrrz/DiscordChatExporter.git
synced 2026-04-28 00:36:00 +00:00
C#10ify
This commit is contained in:
@@ -16,141 +16,140 @@ using DiscordChatExporter.Core.Exporting.Filtering;
|
||||
using DiscordChatExporter.Core.Exporting.Partitioning;
|
||||
using DiscordChatExporter.Core.Utils.Extensions;
|
||||
|
||||
namespace DiscordChatExporter.Cli.Commands.Base
|
||||
namespace DiscordChatExporter.Cli.Commands.Base;
|
||||
|
||||
public abstract class ExportCommandBase : TokenCommandBase
|
||||
{
|
||||
public abstract class ExportCommandBase : TokenCommandBase
|
||||
[CommandOption("output", 'o', Description = "Output file or directory path.")]
|
||||
public string OutputPath { get; init; } = Directory.GetCurrentDirectory();
|
||||
|
||||
[CommandOption("format", 'f', Description = "Export format.")]
|
||||
public ExportFormat ExportFormat { get; init; } = ExportFormat.HtmlDark;
|
||||
|
||||
[CommandOption("after", Description = "Only include messages sent after this date or message ID.")]
|
||||
public Snowflake? After { get; init; }
|
||||
|
||||
[CommandOption("before", Description = "Only include messages sent before this date or message ID.")]
|
||||
public Snowflake? Before { get; init; }
|
||||
|
||||
[CommandOption("partition", 'p', Description = "Split output into partitions, each limited to this number of messages (e.g. '100') or file size (e.g. '10mb').")]
|
||||
public PartitionLimit PartitionLimit { get; init; } = PartitionLimit.Null;
|
||||
|
||||
[CommandOption("filter", Description = "Only include messages that satisfy this filter (e.g. 'from:foo#1234' or 'has:image').")]
|
||||
public MessageFilter MessageFilter { get; init; } = MessageFilter.Null;
|
||||
|
||||
[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; }
|
||||
|
||||
[CommandOption("reuse-media", Description = "Reuse already existing media content to skip redundant downloads.")]
|
||||
public bool ShouldReuseMedia { get; init; }
|
||||
|
||||
[CommandOption("dateformat", Description = "Format used when writing dates.")]
|
||||
public string DateFormat { get; init; } = "dd-MMM-yy hh:mm tt";
|
||||
|
||||
private ChannelExporter? _channelExporter;
|
||||
protected ChannelExporter Exporter => _channelExporter ??= new ChannelExporter(Discord);
|
||||
|
||||
protected async ValueTask ExecuteAsync(IConsole console, IReadOnlyList<Channel> channels)
|
||||
{
|
||||
[CommandOption("output", 'o', Description = "Output file or directory path.")]
|
||||
public string OutputPath { get; init; } = Directory.GetCurrentDirectory();
|
||||
var cancellationToken = console.RegisterCancellationHandler();
|
||||
|
||||
[CommandOption("format", 'f', Description = "Export format.")]
|
||||
public ExportFormat ExportFormat { get; init; } = ExportFormat.HtmlDark;
|
||||
|
||||
[CommandOption("after", Description = "Only include messages sent after this date or message ID.")]
|
||||
public Snowflake? After { get; init; }
|
||||
|
||||
[CommandOption("before", Description = "Only include messages sent before this date or message ID.")]
|
||||
public Snowflake? Before { get; init; }
|
||||
|
||||
[CommandOption("partition", 'p', Description = "Split output into partitions, each limited to this number of messages (e.g. '100') or file size (e.g. '10mb').")]
|
||||
public PartitionLimit PartitionLimit { get; init; } = PartitionLimit.Null;
|
||||
|
||||
[CommandOption("filter", Description = "Only include messages that satisfy this filter (e.g. 'from:foo#1234' or 'has:image').")]
|
||||
public MessageFilter MessageFilter { get; init; } = MessageFilter.Null;
|
||||
|
||||
[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; }
|
||||
|
||||
[CommandOption("reuse-media", Description = "Reuse already existing media content to skip redundant downloads.")]
|
||||
public bool ShouldReuseMedia { get; init; }
|
||||
|
||||
[CommandOption("dateformat", Description = "Format used when writing dates.")]
|
||||
public string DateFormat { get; init; } = "dd-MMM-yy hh:mm tt";
|
||||
|
||||
private ChannelExporter? _channelExporter;
|
||||
protected ChannelExporter Exporter => _channelExporter ??= new ChannelExporter(Discord);
|
||||
|
||||
protected async ValueTask ExecuteAsync(IConsole console, IReadOnlyList<Channel> channels)
|
||||
if (ShouldReuseMedia && !ShouldDownloadMedia)
|
||||
{
|
||||
var cancellationToken = console.RegisterCancellationHandler();
|
||||
throw new CommandException("Option --reuse-media cannot be used without --media.");
|
||||
}
|
||||
|
||||
if (ShouldReuseMedia && !ShouldDownloadMedia)
|
||||
var errors = new ConcurrentDictionary<Channel, string>();
|
||||
|
||||
// Export
|
||||
await console.Output.WriteLineAsync($"Exporting {channels.Count} channel(s)...");
|
||||
await console.CreateProgressTicker().StartAsync(async progressContext =>
|
||||
{
|
||||
await channels.ParallelForEachAsync(async channel =>
|
||||
{
|
||||
throw new CommandException("Option --reuse-media cannot be used without --media.");
|
||||
}
|
||||
|
||||
var errors = new ConcurrentDictionary<Channel, string>();
|
||||
|
||||
// Export
|
||||
await console.Output.WriteLineAsync($"Exporting {channels.Count} channel(s)...");
|
||||
await console.CreateProgressTicker().StartAsync(async progressContext =>
|
||||
{
|
||||
await channels.ParallelForEachAsync(async channel =>
|
||||
try
|
||||
{
|
||||
try
|
||||
await progressContext.StartTaskAsync($"{channel.Category.Name} / {channel.Name}", async progress =>
|
||||
{
|
||||
await progressContext.StartTaskAsync($"{channel.Category.Name} / {channel.Name}", async progress =>
|
||||
{
|
||||
var guild = await Discord.GetGuildAsync(channel.GuildId, cancellationToken);
|
||||
var guild = await Discord.GetGuildAsync(channel.GuildId, cancellationToken);
|
||||
|
||||
var request = new ExportRequest(
|
||||
guild,
|
||||
channel,
|
||||
OutputPath,
|
||||
ExportFormat,
|
||||
After,
|
||||
Before,
|
||||
PartitionLimit,
|
||||
MessageFilter,
|
||||
ShouldDownloadMedia,
|
||||
ShouldReuseMedia,
|
||||
DateFormat
|
||||
);
|
||||
var request = new ExportRequest(
|
||||
guild,
|
||||
channel,
|
||||
OutputPath,
|
||||
ExportFormat,
|
||||
After,
|
||||
Before,
|
||||
PartitionLimit,
|
||||
MessageFilter,
|
||||
ShouldDownloadMedia,
|
||||
ShouldReuseMedia,
|
||||
DateFormat
|
||||
);
|
||||
|
||||
await Exporter.ExportChannelAsync(request, progress, cancellationToken);
|
||||
});
|
||||
}
|
||||
catch (DiscordChatExporterException ex) when (!ex.IsFatal)
|
||||
{
|
||||
errors[channel] = ex.Message;
|
||||
}
|
||||
}, Math.Max(ParallelLimit, 1), cancellationToken);
|
||||
});
|
||||
await Exporter.ExportChannelAsync(request, progress, cancellationToken);
|
||||
});
|
||||
}
|
||||
catch (DiscordChatExporterException ex) when (!ex.IsFatal)
|
||||
{
|
||||
errors[channel] = ex.Message;
|
||||
}
|
||||
}, Math.Max(ParallelLimit, 1), cancellationToken);
|
||||
});
|
||||
|
||||
// Print result
|
||||
using (console.WithForegroundColor(ConsoleColor.White))
|
||||
// Print result
|
||||
using (console.WithForegroundColor(ConsoleColor.White))
|
||||
{
|
||||
await console.Output.WriteLineAsync(
|
||||
$"Successfully exported {channels.Count - errors.Count} channel(s)."
|
||||
);
|
||||
}
|
||||
|
||||
// Print errors
|
||||
if (errors.Any())
|
||||
{
|
||||
await console.Output.WriteLineAsync();
|
||||
|
||||
using (console.WithForegroundColor(ConsoleColor.Red))
|
||||
{
|
||||
await console.Output.WriteLineAsync(
|
||||
$"Successfully exported {channels.Count - errors.Count} channel(s)."
|
||||
$"Failed to export {errors.Count} channel(s):"
|
||||
);
|
||||
}
|
||||
|
||||
// Print errors
|
||||
if (errors.Any())
|
||||
foreach (var (channel, error) in errors)
|
||||
{
|
||||
await console.Output.WriteLineAsync();
|
||||
await console.Output.WriteAsync($"{channel.Category.Name} / {channel.Name}: ");
|
||||
|
||||
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.Name} / {channel.Name}: ");
|
||||
|
||||
using (console.WithForegroundColor(ConsoleColor.Red))
|
||||
await console.Output.WriteLineAsync(error);
|
||||
}
|
||||
|
||||
await console.Output.WriteLineAsync();
|
||||
await console.Output.WriteLineAsync(error);
|
||||
}
|
||||
|
||||
// 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.");
|
||||
}
|
||||
await console.Output.WriteLineAsync();
|
||||
}
|
||||
|
||||
protected async ValueTask ExecuteAsync(IConsole console, IReadOnlyList<Snowflake> channelIds)
|
||||
// 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)
|
||||
{
|
||||
var cancellationToken = console.RegisterCancellationHandler();
|
||||
var channels = new List<Channel>();
|
||||
|
||||
foreach (var channelId in channelIds)
|
||||
{
|
||||
var channel = await Discord.GetChannelAsync(channelId, cancellationToken);
|
||||
channels.Add(channel);
|
||||
}
|
||||
|
||||
await ExecuteAsync(console, channels);
|
||||
throw new CommandException("Export failed.");
|
||||
}
|
||||
}
|
||||
|
||||
protected async ValueTask ExecuteAsync(IConsole console, IReadOnlyList<Snowflake> channelIds)
|
||||
{
|
||||
var cancellationToken = console.RegisterCancellationHandler();
|
||||
var channels = new List<Channel>();
|
||||
|
||||
foreach (var channelId in channelIds)
|
||||
{
|
||||
var channel = await Discord.GetChannelAsync(channelId, cancellationToken);
|
||||
channels.Add(channel);
|
||||
}
|
||||
|
||||
await ExecuteAsync(console, channels);
|
||||
}
|
||||
}
|
||||
@@ -4,27 +4,26 @@ using CliFx.Attributes;
|
||||
using CliFx.Infrastructure;
|
||||
using DiscordChatExporter.Core.Discord;
|
||||
|
||||
namespace DiscordChatExporter.Cli.Commands.Base
|
||||
namespace DiscordChatExporter.Cli.Commands.Base;
|
||||
|
||||
public abstract class TokenCommandBase : ICommand
|
||||
{
|
||||
public abstract class TokenCommandBase : ICommand
|
||||
{
|
||||
[CommandOption("token", 't', IsRequired = true, EnvironmentVariable = "DISCORD_TOKEN", Description = "Authentication token.")]
|
||||
public string TokenValue { get; init; } = "";
|
||||
[CommandOption("token", 't', IsRequired = true, EnvironmentVariable = "DISCORD_TOKEN", Description = "Authentication token.")]
|
||||
public string TokenValue { get; init; } = "";
|
||||
|
||||
[CommandOption("bot", 'b', EnvironmentVariable = "DISCORD_TOKEN_BOT", Description = "Authenticate as a bot.")]
|
||||
public bool IsBotToken { get; init; }
|
||||
[CommandOption("bot", 'b', EnvironmentVariable = "DISCORD_TOKEN_BOT", Description = "Authenticate as a bot.")]
|
||||
public bool IsBotToken { get; init; }
|
||||
|
||||
private AuthToken? _authToken;
|
||||
private AuthToken AuthToken => _authToken ??= new AuthToken(
|
||||
IsBotToken
|
||||
? AuthTokenKind.Bot
|
||||
: AuthTokenKind.User,
|
||||
TokenValue
|
||||
);
|
||||
private AuthToken? _authToken;
|
||||
private AuthToken AuthToken => _authToken ??= new AuthToken(
|
||||
IsBotToken
|
||||
? AuthTokenKind.Bot
|
||||
: AuthTokenKind.User,
|
||||
TokenValue
|
||||
);
|
||||
|
||||
private DiscordClient? _discordClient;
|
||||
protected DiscordClient Discord => _discordClient ??= new DiscordClient(AuthToken);
|
||||
private DiscordClient? _discordClient;
|
||||
protected DiscordClient Discord => _discordClient ??= new DiscordClient(AuthToken);
|
||||
|
||||
public abstract ValueTask ExecuteAsync(IConsole console);
|
||||
}
|
||||
public abstract ValueTask ExecuteAsync(IConsole console);
|
||||
}
|
||||
Reference in New Issue
Block a user