Compare commits

...

3 Commits

Author SHA1 Message Date
Tyrrrz 5fc2fae28a Update version 2023-07-27 23:59:17 +03:00
Tyrrrz ddfbe51cfa Improve error reporting on unexpected HTTP status code 2023-07-25 18:59:05 +03:00
Tyrrrz bcf652edbe Don't throw when parent channel cannot be retrieved
Closes #1108
2023-07-23 16:04:53 +03:00
7 changed files with 77 additions and 46 deletions
+7
View File
@@ -1,5 +1,12 @@
# Changelog
## v2.40.2 (27-Jul-2023)
- General changes:
- Improved error reporting on unexpected HTTP errors.
- CLI changes:
- Fixed an issue which caused a crash when trying to export a private (but accessible) channel inside a private (but not accessible) category.
## v2.40.1 (21-Jul-2023)
- General changes:
+1 -1
View File
@@ -2,7 +2,7 @@
<PropertyGroup>
<TargetFramework>net7.0</TargetFramework>
<Version>2.40.1</Version>
<Version>2.40.2</Version>
<Company>Tyrrrz</Company>
<Copyright>Copyright (c) Oleksii Holub</Copyright>
<LangVersion>preview</LangVersion>
@@ -20,8 +20,9 @@ public partial record Channel(
Snowflake? LastMessageId) : IHasId
{
// Used for visual backwards-compatibility with old exports, where
// channels without a parent (i.e. mostly DM channels) had a fallback
// category created for them.
// 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",
@@ -108,7 +108,7 @@ public class DiscordClient
if (botResponse.StatusCode != HttpStatusCode.Unauthorized)
return TokenKind.Bot;
throw DiscordChatExporterException.Unauthorized();
throw new DiscordChatExporterException("Authentication token is invalid.", true);
}
private async ValueTask<HttpResponseMessage> GetResponseAsync(
@@ -129,10 +129,26 @@ public class DiscordClient
{
throw response.StatusCode switch
{
HttpStatusCode.Unauthorized => DiscordChatExporterException.Unauthorized(),
HttpStatusCode.Forbidden => DiscordChatExporterException.Forbidden(),
HttpStatusCode.NotFound => DiscordChatExporterException.NotFound(url),
_ => DiscordChatExporterException.FailedHttpRequest(response)
HttpStatusCode.Unauthorized => throw new DiscordChatExporterException(
"Authentication token is invalid.",
true
),
HttpStatusCode.Forbidden => throw new DiscordChatExporterException(
$"Request to '{url}' failed: forbidden."
),
HttpStatusCode.NotFound => throw new DiscordChatExporterException(
$"Request to '{url}' failed: not found."
),
_ => throw new DiscordChatExporterException(
$"""
Request to '{url}' failed: {response.StatusCode.ToString().ToSpaceSeparatedWords().ToLowerInvariant()}.
Response content: {await response.Content.ReadAsStringAsync(cancellationToken)}
""",
true
)
};
}
@@ -368,11 +384,21 @@ public class DiscordClient
.GetNonWhiteSpaceStringOrNull()?
.Pipe(Snowflake.Parse);
var parent = parentId is not null
? await GetChannelAsync(parentId.Value, cancellationToken)
: null;
try
{
var parent = parentId is not null
? await GetChannelAsync(parentId.Value, cancellationToken)
: null;
return Channel.Parse(response, parent);
return Channel.Parse(response, parent);
}
// It's possible for the parent channel to be inaccessible, despite the
// child channel being accessible.
// https://github.com/Tyrrrz/DiscordChatExporter/issues/1108
catch (DiscordChatExporterException)
{
return Channel.Parse(response);
}
}
private async ValueTask<Message?> TryGetLastMessageAsync(
@@ -1,9 +1,8 @@
using System;
using System.Net.Http;
namespace DiscordChatExporter.Core.Exceptions;
public partial class DiscordChatExporterException : Exception
public class DiscordChatExporterException : Exception
{
public bool IsFatal { get; }
@@ -12,33 +11,4 @@ public partial class DiscordChatExporterException : Exception
{
IsFatal = isFatal;
}
}
public partial class DiscordChatExporterException
{
internal static DiscordChatExporterException FailedHttpRequest(HttpResponseMessage response)
{
var message = $@"
Failed to perform an HTTP request.
[Request]
{response.RequestMessage}
[Response]
{response}";
return new DiscordChatExporterException(message.Trim(), true);
}
internal static DiscordChatExporterException Unauthorized() =>
new("Authentication token is invalid.", true);
internal static DiscordChatExporterException Forbidden() =>
new("Access is forbidden.");
internal static DiscordChatExporterException NotFound(string resourceId) =>
new($"Requested resource ({resourceId}) does not exist.");
internal static DiscordChatExporterException ChannelIsEmpty() =>
new("Channel is empty or contains no messages for the specified period.");
}
@@ -20,11 +20,19 @@ public class ChannelExporter
{
// Check if the channel is empty
if (request.Channel.LastMessageId is null)
throw DiscordChatExporterException.ChannelIsEmpty();
{
throw new DiscordChatExporterException(
"Channel does not contain any messages."
);
}
// Check if the 'after' boundary is valid
if (request.After is not null && request.Channel.LastMessageId < request.After)
throw DiscordChatExporterException.ChannelIsEmpty();
{
throw new DiscordChatExporterException(
"Channel does not contain any messages within the specified period."
);
}
// Build context
var context = new ExportContext(_discord, request);
@@ -50,6 +58,10 @@ public class ChannelExporter
// Throw if no messages were exported
if (messageExporter.MessagesExported <= 0)
throw DiscordChatExporterException.ChannelIsEmpty();
{
throw new DiscordChatExporterException(
"Channel does not contain any matching messages within the specified period."
);
}
}
}
@@ -16,6 +16,21 @@ public static class StringExtensions
? str[..charCount]
: str;
public static string ToSpaceSeparatedWords(this string str)
{
var builder = new StringBuilder(str.Length * 2);
foreach (var c in str)
{
if (char.IsUpper(c) && builder.Length > 0)
builder.Append(' ');
builder.Append(c);
}
return builder.ToString();
}
public static IEnumerable<Rune> GetRunes(this string str)
{
var lastIndex = 0;