mirror of
https://github.com/Tyrrrz/DiscordChatExporter.git
synced 2025-05-10 18:06:37 +02:00
Don't consider it an error if there is nothing to export (#1349)
This commit is contained in:
parent
cf7580014c
commit
7add81a472
6 changed files with 110 additions and 49 deletions
|
@ -146,4 +146,33 @@ public class DateRangeSpecs
|
|||
.WhenTypeIs<DateTimeOffset>()
|
||||
);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Export_file_is_created_even_when_nothing_to_export()
|
||||
{
|
||||
var long_in_the_past = new DateTimeOffset(1921, 08, 01, 0, 0, 0, TimeSpan.Zero);
|
||||
|
||||
// Arrange
|
||||
var before = long_in_the_past;
|
||||
using var file = TempFile.Create();
|
||||
|
||||
// Act
|
||||
await new ExportChannelsCommand
|
||||
{
|
||||
Token = Secrets.DiscordToken,
|
||||
ChannelIds = [ChannelIds.DateRangeTestCases],
|
||||
ExportFormat = ExportFormat.Json,
|
||||
OutputPath = file.Path,
|
||||
Before = Snowflake.FromDate(before),
|
||||
}.ExecuteAsync(new FakeConsole());
|
||||
|
||||
// Assert
|
||||
var timestamps = Json.Parse(await File.ReadAllTextAsync(file.Path))
|
||||
.GetProperty("messages")
|
||||
.EnumerateArray()
|
||||
.Select(j => j.GetProperty("timestamp").GetDateTimeOffset())
|
||||
.ToArray();
|
||||
|
||||
timestamps.Should().BeEmpty();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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,10 @@ public abstract class ExportCommandBase : DiscordCommandBase
|
|||
}
|
||||
);
|
||||
}
|
||||
catch (ChannelEmptyException ex)
|
||||
{
|
||||
warningsByChannel[channel] = ex.Message;
|
||||
}
|
||||
catch (DiscordChatExporterException ex) when (!ex.IsFatal)
|
||||
{
|
||||
errorsByChannel[channel] = ex.Message;
|
||||
|
@ -252,6 +257,28 @@ public abstract class ExportCommandBase : DiscordCommandBase
|
|||
);
|
||||
}
|
||||
|
||||
// Print warnings
|
||||
if (warningsByChannel.Any())
|
||||
{
|
||||
await console.Output.WriteLineAsync();
|
||||
|
||||
using (console.WithForegroundColor(ConsoleColor.Yellow))
|
||||
{
|
||||
await console.Error.WriteLineAsync(
|
||||
$"Warnings reported for the following channel(s):"
|
||||
);
|
||||
}
|
||||
|
||||
foreach (var (channel, message) in warningsByChannel)
|
||||
{
|
||||
await console.Error.WriteAsync($"{channel.GetHierarchicalName()}: ");
|
||||
using (console.WithForegroundColor(ConsoleColor.Yellow))
|
||||
await console.Error.WriteLineAsync(message);
|
||||
}
|
||||
|
||||
await console.Error.WriteLineAsync();
|
||||
}
|
||||
|
||||
// Print errors
|
||||
if (errorsByChannel.Any())
|
||||
{
|
||||
|
@ -259,16 +286,14 @@ public abstract class ExportCommandBase : DiscordCommandBase
|
|||
|
||||
using (console.WithForegroundColor(ConsoleColor.Red))
|
||||
{
|
||||
await console.Error.WriteLineAsync(
|
||||
$"Failed to export {errorsByChannel.Count} the following channel(s):"
|
||||
);
|
||||
await console.Error.WriteLineAsync($"Failed to export the following channel(s):");
|
||||
}
|
||||
|
||||
foreach (var (channel, error) in errorsByChannel)
|
||||
foreach (var (channel, message) in errorsByChannel)
|
||||
{
|
||||
await console.Error.WriteAsync($"{channel.GetHierarchicalName()}: ");
|
||||
using (console.WithForegroundColor(ConsoleColor.Red))
|
||||
await console.Error.WriteLineAsync(error);
|
||||
await console.Error.WriteLineAsync(message);
|
||||
}
|
||||
|
||||
await console.Error.WriteLineAsync();
|
||||
|
|
|
@ -0,0 +1,8 @@
|
|||
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 ChannelEmptyException(string message)
|
||||
: DiscordChatExporterException(message, false, null) { }
|
|
@ -27,45 +27,42 @@ public class ChannelExporter(DiscordClient discord)
|
|||
);
|
||||
}
|
||||
|
||||
// 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(
|
||||
$"Channel '{request.Channel.Name}' "
|
||||
+ $"of guild '{request.Guild.Name}' "
|
||||
+ $"does not contain any messages within the specified period."
|
||||
);
|
||||
}
|
||||
|
||||
// Check if the 'before' boundary is valid
|
||||
if (
|
||||
request.Before is not null
|
||||
&& !request.Channel.MayHaveMessagesBefore(request.Before.Value)
|
||||
)
|
||||
{
|
||||
throw new DiscordChatExporterException(
|
||||
$"Channel '{request.Channel.Name}' "
|
||||
+ $"of guild '{request.Guild.Name}' "
|
||||
+ $"does not contain any messages within the specified period."
|
||||
);
|
||||
}
|
||||
|
||||
// Build context
|
||||
var context = new ExportContext(discord, request);
|
||||
await context.PopulateChannelsAndRolesAsync(cancellationToken);
|
||||
|
||||
// Export messages
|
||||
await using var messageExporter = new MessageExporter(context);
|
||||
|
||||
// Check if the channel is empty
|
||||
if (request.Channel.IsEmpty)
|
||||
{
|
||||
throw new ChannelEmptyException(
|
||||
$"Channel '{request.Channel.Name}' "
|
||||
+ $"of guild '{request.Guild.Name}' "
|
||||
+ $"does not contain any messages; an empty file will be created."
|
||||
);
|
||||
}
|
||||
|
||||
// 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 ChannelEmptyException(
|
||||
$"Channel '{request.Channel.Name}' "
|
||||
+ $"of guild '{request.Guild.Name}' "
|
||||
+ $"does not contain any messages within the specified period; an empty file will be created."
|
||||
);
|
||||
}
|
||||
|
||||
await foreach (
|
||||
var message in discord.GetMessagesAsync(
|
||||
request.Channel.Id,
|
||||
|
@ -98,15 +95,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."
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -70,7 +70,12 @@ internal partial class MessageExporter(ExportContext context) : IAsyncDisposable
|
|||
MessagesExported++;
|
||||
}
|
||||
|
||||
public async ValueTask DisposeAsync() => await ResetWriterAsync();
|
||||
public async ValueTask DisposeAsync()
|
||||
{
|
||||
// causes the file to be created whether there were messages written or not
|
||||
await GetWriterAsync();
|
||||
await ResetWriterAsync();
|
||||
}
|
||||
}
|
||||
|
||||
internal partial class MessageExporter
|
||||
|
|
|
@ -283,6 +283,13 @@ public partial class DashboardViewModel : ViewModelBase
|
|||
|
||||
Interlocked.Increment(ref successfulExportCount);
|
||||
}
|
||||
catch (ChannelEmptyException 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…
Add table
Add a link
Reference in a new issue