This commit is contained in:
Tyrrrz 2023-05-20 07:09:19 +03:00
parent 03c5c1bc5e
commit 31c7ae9312
50 changed files with 181 additions and 198 deletions

View file

@ -1,7 +1,6 @@
namespace DiscordChatExporter.Core.Discord.Data;
// https://discord.com/developers/docs/resources/channel#channel-object-channel-types
// Order of enum fields needs to match the order in the docs.
public enum ChannelKind
{
GuildTextChat = 0,

View file

@ -1,5 +1,4 @@
using System.Linq;
using System.Text.Json;
using System.Text.Json;
using DiscordChatExporter.Core.Discord.Data.Common;
using DiscordChatExporter.Core.Utils.Extensions;
using JsonExtensions.Reading;

View file

@ -1,12 +0,0 @@
using System.Collections.Generic;
namespace DiscordChatExporter.Core.Discord.Data.Common;
public class IdBasedEqualityComparer : IEqualityComparer<IHasId>
{
public static IdBasedEqualityComparer Instance { get; } = new();
public bool Equals(IHasId? x, IHasId? y) => x?.Id == y?.Id;
public int GetHashCode(IHasId obj) => obj.Id.GetHashCode();
}

View file

@ -34,8 +34,7 @@ public partial record Emoji
if (!string.IsNullOrWhiteSpace(name))
return ImageCdn.GetStandardEmojiUrl(name);
// Either ID or name should be set
throw new ApplicationException("Emoji has neither ID nor name set.");
throw new InvalidOperationException("Either the emoji ID or name should be provided.");
}
public static Emoji Parse(JsonElement json)

View file

@ -17,5 +17,5 @@ public enum MessageKind
public static class MessageKindExtensions
{
public static bool IsSystemNotification(this MessageKind c) => (int)c is >= 1 and <= 18;
public static bool IsSystemNotification(this MessageKind kind) => (int)kind is >= 1 and <= 18;
}

View file

@ -238,11 +238,8 @@ public class DiscordClient
? categories.GetValueOrDefault(parentId)
: null;
var channel = Channel.Parse(channelJson, category, position);
yield return Channel.Parse(channelJson, category, position);
position++;
yield return channel;
}
}
}
@ -288,8 +285,8 @@ public class DiscordClient
var response = await GetJsonResponseAsync($"channels/{channelId}", cancellationToken);
return ChannelCategory.Parse(response);
}
// In some cases, the Discord API returns an empty body when requesting a channel.
// Return an empty channel category as fallback in these cases.
// In some cases, Discord API returns an empty body when requesting a channel.
// Use an empty channel category as fallback for these cases.
catch (DiscordChatExporterException)
{
return new ChannelCategory(channelId, "Unknown Category", 0);
@ -371,8 +368,9 @@ public class DiscordClient
if (lastMessage is null || lastMessage.Timestamp < after?.ToDate())
yield break;
// Keep track of the first message in range in order to calculate progress
// Keep track of the first message in range in order to calculate the progress
var firstMessage = default(Message);
var currentAfter = after ?? Snowflake.Zero;
while (true)
{

View file

@ -29,15 +29,11 @@ public partial record struct Snowflake
// As number
if (ulong.TryParse(str, NumberStyles.None, formatProvider, out var value))
{
return new Snowflake(value);
}
// As date
if (DateTimeOffset.TryParse(str, formatProvider, DateTimeStyles.None, out var instant))
{
return FromDate(instant);
}
return null;
}

View file

@ -89,11 +89,15 @@ internal partial class CsvMessageWriter : MessageWriter
// Message content
if (message.Kind.IsSystemNotification())
{
await _writer.WriteAsync(CsvEncode(message.GetFallbackContent()));
await _writer.WriteAsync(CsvEncode(
message.GetFallbackContent()
));
}
else
{
await _writer.WriteAsync(CsvEncode(await FormatMarkdownAsync(message.Content, cancellationToken)));
await _writer.WriteAsync(CsvEncode(
await FormatMarkdownAsync(message.Content, cancellationToken)
));
}
await _writer.WriteAsync(',');

View file

@ -75,9 +75,8 @@ internal partial class ExportAssetDownloader
catch
{
// This can apparently fail for some reason.
// Updating the file date is not a critical task, so we'll just ignore exceptions thrown here.
// https://github.com/Tyrrrz/DiscordChatExporter/issues/585
// Updating file dates is not a critical task, so we'll just
// ignore exceptions thrown here.
}
});
@ -98,7 +97,7 @@ internal partial class ExportAssetDownloader
{
var urlHash = GetUrlHash(url);
// Try to extract file name from URL
// Try to extract the file name from URL
var fileName = Regex.Match(url, @".+/([^?]*)").Groups[1].Value;
// If it's not there, just use the URL hash as the file name
@ -110,7 +109,7 @@ internal partial class ExportAssetDownloader
var fileExtension = Path.GetExtension(fileName);
// Probably not a file extension, just a dot in a long file name
// https://github.com/Tyrrrz/DiscordChatExporter/issues/708
// https://github.com/Tyrrrz/DiscordChatExporter/pull/812
if (fileExtension.Length > 41)
{
fileNameWithoutExtension = fileName;

View file

@ -55,10 +55,13 @@ internal class ExportContext
var member = await Discord.TryGetGuildMemberAsync(Request.Guild.Id, id, cancellationToken);
// User may have left the guild since they were mentioned
// User may have left the guild since they were mentioned.
// Create a dummy member object based on the user info.
if (member is null)
{
var user = fallbackUser ?? await Discord.TryGetUserAsync(id, cancellationToken);
// User may have been deleted since they were mentioned
if (user is not null)
member = Member.CreateDefault(user);
}
@ -114,7 +117,7 @@ internal class ExportContext
var relativeFilePath = Path.GetRelativePath(Request.OutputDirPath, filePath);
// Prefer relative paths so that the output files can be copied around without breaking references.
// If the assets path is outside of the export directory, use the absolute path instead.
// If the assets path is outside of the export directory, use an absolute path instead.
var optimalFilePath =
relativeFilePath.StartsWith(".." + Path.DirectorySeparatorChar, StringComparison.Ordinal) ||
relativeFilePath.StartsWith(".." + Path.AltDirectorySeparatorChar, StringComparison.Ordinal)
@ -135,8 +138,8 @@ internal class ExportContext
// https://github.com/Tyrrrz/DiscordChatExporter/issues/372
catch (Exception ex) when (ex is HttpRequestException or OperationCanceledException)
{
// TODO: add logging so we can be more liberal with catching exceptions
// We don't want this to crash the exporting process in case of failure
// We don't want this to crash the exporting process in case of failure.
// TODO: add logging so we can be more liberal with catching exceptions.
return url;
}
}

View file

@ -52,12 +52,12 @@ internal class HtmlMessageWriter : MessageWriter
if ((message.Timestamp - lastMessage.Timestamp).Duration().TotalMinutes > 7)
return false;
// Messages must be from the same author
// Messages must be sent by the same author
if (message.Author.Id != lastMessage.Author.Id)
return false;
// If the user changed their name after the last message, their new messages
// cannot join an existing group.
// If the author changed their name after the last message, their new messages
// cannot join the existing group.
if (!string.Equals(message.Author.FullName, lastMessage.Author.FullName, StringComparison.Ordinal))
return false;
}

View file

@ -39,7 +39,7 @@ internal partial class MessageExporter : IAsyncDisposable
private async ValueTask<MessageWriter> GetWriterAsync(CancellationToken cancellationToken = default)
{
// Ensure partition limit has not been reached
// Ensure that the partition limit has not been reached
if (_writer is not null &&
_context.Request.PartitionLimit.IsReached(_writer.MessagesWritten, _writer.BytesWritten))
{
@ -74,11 +74,11 @@ internal partial class MessageExporter
{
private static string GetPartitionFilePath(string baseFilePath, int partitionIndex)
{
// First partition, don't change file name
// First partition, don't change the file name
if (partitionIndex <= 0)
return baseFilePath;
// Inject partition index into file name
// Inject partition index into the file name
var fileNameWithoutExt = Path.GetFileNameWithoutExtension(baseFilePath);
var fileExt = Path.GetExtension(baseFilePath);
var fileName = $"{fileNameWithoutExt} [part {partitionIndex + 1}]{fileExt}";

View file

@ -16,7 +16,7 @@ public static class Http
private static bool IsRetryableStatusCode(HttpStatusCode statusCode) =>
statusCode is HttpStatusCode.TooManyRequests or HttpStatusCode.RequestTimeout ||
// Treat all server-side errors as retryable.
// Treat all server-side errors as retryable
// https://github.com/Tyrrrz/DiscordChatExporter/issues/908
(int)statusCode >= 500;