pull/1349/merge
Leonardo Mosquera 1 month ago committed by GitHub
commit 225e529bdc
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

@ -179,6 +179,7 @@ public abstract class ExportCommandBase : DiscordCommandBase
// Export
var cancellationToken = console.RegisterCancellationHandler();
var errorsByChannel = new ConcurrentDictionary<Channel, string>();
var warningsByChannel = new ConcurrentDictionary<Channel, string>();
await console.Output.WriteLineAsync($"Exporting {channels.Count} channel(s)...");
await console
@ -236,6 +237,9 @@ public abstract class ExportCommandBase : DiscordCommandBase
}
);
}
catch (ChannelNotExportedException ex) {
warningsByChannel[channel] = ex.Message;
}
catch (DiscordChatExporterException ex) when (!ex.IsFatal)
{
errorsByChannel[channel] = ex.Message;
@ -252,26 +256,33 @@ public abstract class ExportCommandBase : DiscordCommandBase
);
}
// Print errors
if (errorsByChannel.Any())
// Print errors and warnings
if (errorsByChannel.Any() || warningsByChannel.Any())
{
await console.Output.WriteLineAsync();
using (console.WithForegroundColor(ConsoleColor.Red))
var tuples = new List<(ConcurrentDictionary<Channel, string>, ConsoleColor, string)>
{
await console.Error.WriteLineAsync(
$"Failed to export {errorsByChannel.Count} the following channel(s):"
);
}
(errorsByChannel, ConsoleColor.Red, "Failed to export the following channels:"),
(warningsByChannel, ConsoleColor.Yellow, "No messages exported for the following channels:")
};
foreach (var (channel, error) in errorsByChannel)
{
await console.Error.WriteAsync($"{channel.GetHierarchicalName()}: ");
using (console.WithForegroundColor(ConsoleColor.Red))
await console.Error.WriteLineAsync(error);
}
foreach (var (messages, color, title) in tuples) {
if (! messages.Any())
continue;
await console.Error.WriteLineAsync();
await console.Output.WriteLineAsync();
using (console.WithForegroundColor(color))
await console.Error.WriteLineAsync(title);
foreach (var (channel, message) in messages)
{
await console.Error.WriteAsync($"{channel.GetHierarchicalName()}: ");
using (console.WithForegroundColor(color))
await console.Error.WriteLineAsync(message);
}
await console.Error.WriteLineAsync();
}
}
// Fail the command only if ALL channels failed to export.

@ -0,0 +1,9 @@
using System;
namespace DiscordChatExporter.Core.Exceptions;
// Thrown when there is circumstancially no message to export with given parameters,
// though it should not be treated as a runtime error; simply warn instead
public class ChannelNotExportedException(string message)
: DiscordChatExporterException(message, false, null) {
}

@ -27,45 +27,35 @@ public class ChannelExporter(DiscordClient discord)
);
}
// Build context
var context = new ExportContext(discord, request);
await context.PopulateChannelsAndRolesAsync(cancellationToken);
// Export messages
await using var messageExporter = new MessageExporter(context);
await messageExporter.EnsureFileIsCreated(cancellationToken);
// Check if the channel is empty
if (request.Channel.IsEmpty)
{
throw new DiscordChatExporterException(
$"Channel '{request.Channel.Name}' "
+ $"of guild '{request.Guild.Name}' "
+ $"does not contain any messages."
);
}
// Check if the 'after' boundary is valid
if (request.After is not null && !request.Channel.MayHaveMessagesAfter(request.After.Value))
{
throw new DiscordChatExporterException(
throw new ChannelNotExportedException(
$"Channel '{request.Channel.Name}' "
+ $"of guild '{request.Guild.Name}' "
+ $"does not contain any messages within the specified period."
+ $"does not contain any messages; an empty file will be created."
);
}
// Check if the 'before' boundary is valid
if (
request.Before is not null
&& !request.Channel.MayHaveMessagesBefore(request.Before.Value)
)
// Check if the 'before' and 'after' boundaries are valid
if ((request.Before is not null && !request.Channel.MayHaveMessagesBefore(request.Before.Value)) ||
(request.After is not null && !request.Channel.MayHaveMessagesAfter(request.After.Value)))
{
throw new DiscordChatExporterException(
throw new ChannelNotExportedException(
$"Channel '{request.Channel.Name}' "
+ $"of guild '{request.Guild.Name}' "
+ $"does not contain any messages within the specified period."
+ $"does not contain any messages within the specified period; an empty file will be created."
);
}
// Build context
var context = new ExportContext(discord, request);
await context.PopulateChannelsAndRolesAsync(cancellationToken);
// Export messages
await using var messageExporter = new MessageExporter(context);
await foreach (
var message in discord.GetMessagesAsync(
request.Channel.Id,
@ -98,15 +88,5 @@ public class ChannelExporter(DiscordClient discord)
);
}
}
// Throw if no messages were exported
if (messageExporter.MessagesExported <= 0)
{
throw new DiscordChatExporterException(
$"Channel '{request.Channel.Name}' (#{request.Channel.Id}) "
+ $"of guild '{request.Guild.Name}' (#{request.Guild.Id}) "
+ $"does not contain any matching messages within the specified period."
);
}
}
}

@ -30,6 +30,11 @@ internal partial class MessageExporter(ExportContext context) : IAsyncDisposable
}
}
// Unless explicitly called, file will be created before writing first message
public async ValueTask EnsureFileIsCreated(CancellationToken cancellationToken = default) {
await GetWriterAsync(cancellationToken);
}
private async ValueTask<MessageWriter> GetWriterAsync(
CancellationToken cancellationToken = default
)

@ -283,6 +283,12 @@ public partial class DashboardViewModel : ViewModelBase
Interlocked.Increment(ref successfulExportCount);
}
catch (ChannelNotExportedException ex) {
_snackbarManager.Notify(ex.Message.TrimEnd('.'));
// FIXME: not exactly successful, but not a failure either. Not ideal to duplicate the line
Interlocked.Increment(ref successfulExportCount);
}
catch (DiscordChatExporterException ex) when (!ex.IsFatal)
{
_snackbarManager.Notify(ex.Message.TrimEnd('.'));

Loading…
Cancel
Save