mirror of
https://github.com/Tyrrrz/DiscordChatExporter.git
synced 2025-05-11 10:26:57 +02:00
Format stuff
This commit is contained in:
parent
e8192b2b53
commit
09e0b3f133
45 changed files with 156 additions and 174 deletions
|
@ -56,7 +56,7 @@ public static class ExportWrapper
|
||||||
ExportFormat = format,
|
ExportFormat = format,
|
||||||
OutputPath = filePath,
|
OutputPath = filePath,
|
||||||
Locale = "en-US",
|
Locale = "en-US",
|
||||||
IsUtcNormalizationEnabled = true
|
IsUtcNormalizationEnabled = true,
|
||||||
}.ExecuteAsync(console);
|
}.ExecuteAsync(console);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -30,7 +30,7 @@ public class DateRangeSpecs
|
||||||
ChannelIds = [ChannelIds.DateRangeTestCases],
|
ChannelIds = [ChannelIds.DateRangeTestCases],
|
||||||
ExportFormat = ExportFormat.Json,
|
ExportFormat = ExportFormat.Json,
|
||||||
OutputPath = file.Path,
|
OutputPath = file.Path,
|
||||||
After = Snowflake.FromDate(after)
|
After = Snowflake.FromDate(after),
|
||||||
}.ExecuteAsync(new FakeConsole());
|
}.ExecuteAsync(new FakeConsole());
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
|
@ -50,7 +50,7 @@ public class DateRangeSpecs
|
||||||
new DateTimeOffset(2021, 07, 24, 14, 52, 38, TimeSpan.Zero),
|
new DateTimeOffset(2021, 07, 24, 14, 52, 38, TimeSpan.Zero),
|
||||||
new DateTimeOffset(2021, 07, 24, 14, 52, 39, TimeSpan.Zero),
|
new DateTimeOffset(2021, 07, 24, 14, 52, 39, TimeSpan.Zero),
|
||||||
new DateTimeOffset(2021, 07, 24, 14, 52, 40, TimeSpan.Zero),
|
new DateTimeOffset(2021, 07, 24, 14, 52, 40, TimeSpan.Zero),
|
||||||
new DateTimeOffset(2021, 09, 08, 14, 26, 35, TimeSpan.Zero)
|
new DateTimeOffset(2021, 09, 08, 14, 26, 35, TimeSpan.Zero),
|
||||||
],
|
],
|
||||||
o =>
|
o =>
|
||||||
o.Using<DateTimeOffset>(ctx =>
|
o.Using<DateTimeOffset>(ctx =>
|
||||||
|
@ -74,7 +74,7 @@ public class DateRangeSpecs
|
||||||
ChannelIds = [ChannelIds.DateRangeTestCases],
|
ChannelIds = [ChannelIds.DateRangeTestCases],
|
||||||
ExportFormat = ExportFormat.Json,
|
ExportFormat = ExportFormat.Json,
|
||||||
OutputPath = file.Path,
|
OutputPath = file.Path,
|
||||||
Before = Snowflake.FromDate(before)
|
Before = Snowflake.FromDate(before),
|
||||||
}.ExecuteAsync(new FakeConsole());
|
}.ExecuteAsync(new FakeConsole());
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
|
@ -92,7 +92,7 @@ public class DateRangeSpecs
|
||||||
[
|
[
|
||||||
new DateTimeOffset(2021, 07, 19, 13, 34, 18, TimeSpan.Zero),
|
new DateTimeOffset(2021, 07, 19, 13, 34, 18, TimeSpan.Zero),
|
||||||
new DateTimeOffset(2021, 07, 19, 15, 58, 48, TimeSpan.Zero),
|
new DateTimeOffset(2021, 07, 19, 15, 58, 48, TimeSpan.Zero),
|
||||||
new DateTimeOffset(2021, 07, 19, 17, 23, 58, TimeSpan.Zero)
|
new DateTimeOffset(2021, 07, 19, 17, 23, 58, TimeSpan.Zero),
|
||||||
],
|
],
|
||||||
o =>
|
o =>
|
||||||
o.Using<DateTimeOffset>(ctx =>
|
o.Using<DateTimeOffset>(ctx =>
|
||||||
|
@ -118,7 +118,7 @@ public class DateRangeSpecs
|
||||||
ExportFormat = ExportFormat.Json,
|
ExportFormat = ExportFormat.Json,
|
||||||
OutputPath = file.Path,
|
OutputPath = file.Path,
|
||||||
Before = Snowflake.FromDate(before),
|
Before = Snowflake.FromDate(before),
|
||||||
After = Snowflake.FromDate(after)
|
After = Snowflake.FromDate(after),
|
||||||
}.ExecuteAsync(new FakeConsole());
|
}.ExecuteAsync(new FakeConsole());
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
|
@ -137,7 +137,7 @@ public class DateRangeSpecs
|
||||||
new DateTimeOffset(2021, 07, 24, 13, 49, 13, TimeSpan.Zero),
|
new DateTimeOffset(2021, 07, 24, 13, 49, 13, TimeSpan.Zero),
|
||||||
new DateTimeOffset(2021, 07, 24, 14, 52, 38, TimeSpan.Zero),
|
new DateTimeOffset(2021, 07, 24, 14, 52, 38, TimeSpan.Zero),
|
||||||
new DateTimeOffset(2021, 07, 24, 14, 52, 39, TimeSpan.Zero),
|
new DateTimeOffset(2021, 07, 24, 14, 52, 39, TimeSpan.Zero),
|
||||||
new DateTimeOffset(2021, 07, 24, 14, 52, 40, TimeSpan.Zero)
|
new DateTimeOffset(2021, 07, 24, 14, 52, 40, TimeSpan.Zero),
|
||||||
],
|
],
|
||||||
o =>
|
o =>
|
||||||
o.Using<DateTimeOffset>(ctx =>
|
o.Using<DateTimeOffset>(ctx =>
|
||||||
|
|
|
@ -29,7 +29,7 @@ public class FilterSpecs
|
||||||
ChannelIds = [ChannelIds.FilterTestCases],
|
ChannelIds = [ChannelIds.FilterTestCases],
|
||||||
ExportFormat = ExportFormat.Json,
|
ExportFormat = ExportFormat.Json,
|
||||||
OutputPath = file.Path,
|
OutputPath = file.Path,
|
||||||
MessageFilter = MessageFilter.Parse("some text")
|
MessageFilter = MessageFilter.Parse("some text"),
|
||||||
}.ExecuteAsync(new FakeConsole());
|
}.ExecuteAsync(new FakeConsole());
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
|
@ -54,7 +54,7 @@ public class FilterSpecs
|
||||||
ChannelIds = [ChannelIds.FilterTestCases],
|
ChannelIds = [ChannelIds.FilterTestCases],
|
||||||
ExportFormat = ExportFormat.Json,
|
ExportFormat = ExportFormat.Json,
|
||||||
OutputPath = file.Path,
|
OutputPath = file.Path,
|
||||||
MessageFilter = MessageFilter.Parse("from:Tyrrrz")
|
MessageFilter = MessageFilter.Parse("from:Tyrrrz"),
|
||||||
}.ExecuteAsync(new FakeConsole());
|
}.ExecuteAsync(new FakeConsole());
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
|
@ -79,7 +79,7 @@ public class FilterSpecs
|
||||||
ChannelIds = [ChannelIds.FilterTestCases],
|
ChannelIds = [ChannelIds.FilterTestCases],
|
||||||
ExportFormat = ExportFormat.Json,
|
ExportFormat = ExportFormat.Json,
|
||||||
OutputPath = file.Path,
|
OutputPath = file.Path,
|
||||||
MessageFilter = MessageFilter.Parse("has:image")
|
MessageFilter = MessageFilter.Parse("has:image"),
|
||||||
}.ExecuteAsync(new FakeConsole());
|
}.ExecuteAsync(new FakeConsole());
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
|
@ -104,7 +104,7 @@ public class FilterSpecs
|
||||||
ChannelIds = [ChannelIds.FilterTestCases],
|
ChannelIds = [ChannelIds.FilterTestCases],
|
||||||
ExportFormat = ExportFormat.Json,
|
ExportFormat = ExportFormat.Json,
|
||||||
OutputPath = file.Path,
|
OutputPath = file.Path,
|
||||||
MessageFilter = MessageFilter.Parse("has:pin")
|
MessageFilter = MessageFilter.Parse("has:pin"),
|
||||||
}.ExecuteAsync(new FakeConsole());
|
}.ExecuteAsync(new FakeConsole());
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
|
@ -129,7 +129,7 @@ public class FilterSpecs
|
||||||
ChannelIds = [ChannelIds.FilterTestCases],
|
ChannelIds = [ChannelIds.FilterTestCases],
|
||||||
ExportFormat = ExportFormat.Json,
|
ExportFormat = ExportFormat.Json,
|
||||||
OutputPath = file.Path,
|
OutputPath = file.Path,
|
||||||
MessageFilter = MessageFilter.Parse("has:invite")
|
MessageFilter = MessageFilter.Parse("has:invite"),
|
||||||
}.ExecuteAsync(new FakeConsole());
|
}.ExecuteAsync(new FakeConsole());
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
|
@ -154,7 +154,7 @@ public class FilterSpecs
|
||||||
ChannelIds = [ChannelIds.FilterTestCases],
|
ChannelIds = [ChannelIds.FilterTestCases],
|
||||||
ExportFormat = ExportFormat.Json,
|
ExportFormat = ExportFormat.Json,
|
||||||
OutputPath = file.Path,
|
OutputPath = file.Path,
|
||||||
MessageFilter = MessageFilter.Parse("mentions:Tyrrrz")
|
MessageFilter = MessageFilter.Parse("mentions:Tyrrrz"),
|
||||||
}.ExecuteAsync(new FakeConsole());
|
}.ExecuteAsync(new FakeConsole());
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
|
|
|
@ -28,7 +28,7 @@ public class HtmlGroupingSpecs
|
||||||
Token = Secrets.DiscordToken,
|
Token = Secrets.DiscordToken,
|
||||||
ChannelIds = [ChannelIds.GroupingTestCases],
|
ChannelIds = [ChannelIds.GroupingTestCases],
|
||||||
ExportFormat = ExportFormat.HtmlDark,
|
ExportFormat = ExportFormat.HtmlDark,
|
||||||
OutputPath = file.Path
|
OutputPath = file.Path,
|
||||||
}.ExecuteAsync(new FakeConsole());
|
}.ExecuteAsync(new FakeConsole());
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
|
|
|
@ -27,7 +27,7 @@ public class PartitioningSpecs
|
||||||
ChannelIds = [ChannelIds.DateRangeTestCases],
|
ChannelIds = [ChannelIds.DateRangeTestCases],
|
||||||
ExportFormat = ExportFormat.HtmlDark,
|
ExportFormat = ExportFormat.HtmlDark,
|
||||||
OutputPath = filePath,
|
OutputPath = filePath,
|
||||||
PartitionLimit = PartitionLimit.Parse("3")
|
PartitionLimit = PartitionLimit.Parse("3"),
|
||||||
}.ExecuteAsync(new FakeConsole());
|
}.ExecuteAsync(new FakeConsole());
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
|
@ -48,7 +48,7 @@ public class PartitioningSpecs
|
||||||
ChannelIds = [ChannelIds.DateRangeTestCases],
|
ChannelIds = [ChannelIds.DateRangeTestCases],
|
||||||
ExportFormat = ExportFormat.HtmlDark,
|
ExportFormat = ExportFormat.HtmlDark,
|
||||||
OutputPath = filePath,
|
OutputPath = filePath,
|
||||||
PartitionLimit = PartitionLimit.Parse("1kb")
|
PartitionLimit = PartitionLimit.Parse("1kb"),
|
||||||
}.ExecuteAsync(new FakeConsole());
|
}.ExecuteAsync(new FakeConsole());
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
|
|
|
@ -27,7 +27,7 @@ public class SelfContainedSpecs
|
||||||
ChannelIds = [ChannelIds.SelfContainedTestCases],
|
ChannelIds = [ChannelIds.SelfContainedTestCases],
|
||||||
ExportFormat = ExportFormat.HtmlDark,
|
ExportFormat = ExportFormat.HtmlDark,
|
||||||
OutputPath = filePath,
|
OutputPath = filePath,
|
||||||
ShouldDownloadAssets = true
|
ShouldDownloadAssets = true,
|
||||||
}.ExecuteAsync(new FakeConsole());
|
}.ExecuteAsync(new FakeConsole());
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
|
|
|
@ -196,7 +196,7 @@ public abstract class ExportCommandBase : DiscordCommandBase
|
||||||
new ParallelOptions
|
new ParallelOptions
|
||||||
{
|
{
|
||||||
MaxDegreeOfParallelism = Math.Max(1, ParallelLimit),
|
MaxDegreeOfParallelism = Math.Max(1, ParallelLimit),
|
||||||
CancellationToken = cancellationToken
|
CancellationToken = cancellationToken,
|
||||||
},
|
},
|
||||||
async (channel, innerCancellationToken) =>
|
async (channel, innerCancellationToken) =>
|
||||||
{
|
{
|
||||||
|
|
|
@ -4,5 +4,5 @@ public enum ThreadInclusionMode
|
||||||
{
|
{
|
||||||
None,
|
None,
|
||||||
Active,
|
Active,
|
||||||
All
|
All,
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,7 +13,7 @@ internal static class ConsoleExtensions
|
||||||
{
|
{
|
||||||
Ansi = AnsiSupport.Detect,
|
Ansi = AnsiSupport.Detect,
|
||||||
ColorSystem = ColorSystemSupport.Detect,
|
ColorSystem = ColorSystemSupport.Detect,
|
||||||
Out = new AnsiConsoleOutput(console.Output)
|
Out = new AnsiConsoleOutput(console.Output),
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
@ -16,5 +16,5 @@ public enum ApplicationFlags
|
||||||
Embedded = 131072,
|
Embedded = 131072,
|
||||||
GatewayMessageContent = 262144,
|
GatewayMessageContent = 262144,
|
||||||
GatewayMessageContentLimited = 524288,
|
GatewayMessageContentLimited = 524288,
|
||||||
ApplicationCommandBadge = 8388608
|
ApplicationCommandBadge = 8388608,
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,5 +14,5 @@ public enum ChannelKind
|
||||||
GuildPrivateThread = 12,
|
GuildPrivateThread = 12,
|
||||||
GuildStageVoice = 13,
|
GuildStageVoice = 13,
|
||||||
GuildDirectory = 14,
|
GuildDirectory = 14,
|
||||||
GuildForum = 15
|
GuildForum = 15,
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,5 +7,5 @@ public enum EmbedKind
|
||||||
Image,
|
Image,
|
||||||
Video,
|
Video,
|
||||||
Gifv,
|
Gifv,
|
||||||
Link
|
Link,
|
||||||
}
|
}
|
||||||
|
|
|
@ -3548,7 +3548,7 @@ internal static class EmojiIndex
|
||||||
["🇸🇯"] = "flag_sj",
|
["🇸🇯"] = "flag_sj",
|
||||||
["🇹🇦"] = "flag_ta",
|
["🇹🇦"] = "flag_ta",
|
||||||
["🇺🇲"] = "flag_um",
|
["🇺🇲"] = "flag_um",
|
||||||
["🇺🇳"] = "united_nations"
|
["🇺🇳"] = "united_nations",
|
||||||
};
|
};
|
||||||
|
|
||||||
private static Dictionary<string, string> _fromCodes =
|
private static Dictionary<string, string> _fromCodes =
|
||||||
|
@ -8880,7 +8880,7 @@ internal static class EmojiIndex
|
||||||
["flag_sj"] = "🇸🇯",
|
["flag_sj"] = "🇸🇯",
|
||||||
["flag_ta"] = "🇹🇦",
|
["flag_ta"] = "🇹🇦",
|
||||||
["flag_um"] = "🇺🇲",
|
["flag_um"] = "🇺🇲",
|
||||||
["united_nations"] = "🇺🇳"
|
["united_nations"] = "🇺🇳",
|
||||||
};
|
};
|
||||||
|
|
||||||
public static string? TryGetCode(string name) => _toCodes.GetValueOrDefault(name);
|
public static string? TryGetCode(string name) => _toCodes.GetValueOrDefault(name);
|
||||||
|
|
|
@ -14,5 +14,5 @@ public enum MessageFlags
|
||||||
Urgent = 16,
|
Urgent = 16,
|
||||||
HasThread = 32,
|
HasThread = 32,
|
||||||
Ephemeral = 64,
|
Ephemeral = 64,
|
||||||
Loading = 128
|
Loading = 128,
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,5 +12,5 @@ public enum MessageKind
|
||||||
ChannelPinnedMessage = 6,
|
ChannelPinnedMessage = 6,
|
||||||
GuildMemberJoin = 7,
|
GuildMemberJoin = 7,
|
||||||
ThreadCreated = 18,
|
ThreadCreated = 18,
|
||||||
Reply = 19
|
Reply = 19,
|
||||||
}
|
}
|
||||||
|
|
|
@ -28,7 +28,7 @@ public partial record Sticker
|
||||||
StickerFormat.Apng => "png",
|
StickerFormat.Apng => "png",
|
||||||
StickerFormat.Lottie => "json",
|
StickerFormat.Lottie => "json",
|
||||||
StickerFormat.Gif => "gif",
|
StickerFormat.Gif => "gif",
|
||||||
_ => throw new InvalidOperationException($"Unknown sticker format '{format}'.")
|
_ => throw new InvalidOperationException($"Unknown sticker format '{format}'."),
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
@ -5,5 +5,5 @@ public enum StickerFormat
|
||||||
Png = 1,
|
Png = 1,
|
||||||
Apng = 2,
|
Apng = 2,
|
||||||
Lottie = 3,
|
Lottie = 3,
|
||||||
Gif = 4
|
Gif = 4,
|
||||||
}
|
}
|
||||||
|
|
|
@ -133,25 +133,21 @@ public class DiscordClient(string token)
|
||||||
{
|
{
|
||||||
throw response.StatusCode switch
|
throw response.StatusCode switch
|
||||||
{
|
{
|
||||||
HttpStatusCode.Unauthorized
|
HttpStatusCode.Unauthorized => throw new DiscordChatExporterException(
|
||||||
=> throw new DiscordChatExporterException(
|
"Authentication token is invalid.",
|
||||||
"Authentication token is invalid.",
|
true
|
||||||
true
|
),
|
||||||
),
|
|
||||||
|
|
||||||
HttpStatusCode.Forbidden
|
HttpStatusCode.Forbidden => throw new DiscordChatExporterException(
|
||||||
=> throw new DiscordChatExporterException(
|
$"Request to '{url}' failed: forbidden."
|
||||||
$"Request to '{url}' failed: forbidden."
|
),
|
||||||
),
|
|
||||||
|
|
||||||
HttpStatusCode.NotFound
|
HttpStatusCode.NotFound => throw new DiscordChatExporterException(
|
||||||
=> throw new DiscordChatExporterException(
|
$"Request to '{url}' failed: not found."
|
||||||
$"Request to '{url}' failed: not found."
|
),
|
||||||
),
|
|
||||||
|
|
||||||
_
|
_ => throw new DiscordChatExporterException(
|
||||||
=> throw new DiscordChatExporterException(
|
$"""
|
||||||
$"""
|
|
||||||
Request to '{url}' failed: {response
|
Request to '{url}' failed: {response
|
||||||
.StatusCode.ToString()
|
.StatusCode.ToString()
|
||||||
.ToSpaceSeparatedWords()
|
.ToSpaceSeparatedWords()
|
||||||
|
@ -160,8 +156,8 @@ public class DiscordClient(string token)
|
||||||
cancellationToken
|
cancellationToken
|
||||||
)}
|
)}
|
||||||
""",
|
""",
|
||||||
true
|
true
|
||||||
)
|
),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -3,5 +3,5 @@
|
||||||
public enum TokenKind
|
public enum TokenKind
|
||||||
{
|
{
|
||||||
User,
|
User,
|
||||||
Bot
|
Bot,
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,7 +8,7 @@ public enum ExportFormat
|
||||||
HtmlDark,
|
HtmlDark,
|
||||||
HtmlLight,
|
HtmlLight,
|
||||||
Csv,
|
Csv,
|
||||||
Json
|
Json,
|
||||||
}
|
}
|
||||||
|
|
||||||
public static class ExportFormatExtensions
|
public static class ExportFormatExtensions
|
||||||
|
@ -21,7 +21,7 @@ public static class ExportFormatExtensions
|
||||||
ExportFormat.HtmlLight => "html",
|
ExportFormat.HtmlLight => "html",
|
||||||
ExportFormat.Csv => "csv",
|
ExportFormat.Csv => "csv",
|
||||||
ExportFormat.Json => "json",
|
ExportFormat.Json => "json",
|
||||||
_ => throw new ArgumentOutOfRangeException(nameof(format))
|
_ => throw new ArgumentOutOfRangeException(nameof(format)),
|
||||||
};
|
};
|
||||||
|
|
||||||
public static string GetDisplayName(this ExportFormat format) =>
|
public static string GetDisplayName(this ExportFormat format) =>
|
||||||
|
@ -32,6 +32,6 @@ public static class ExportFormatExtensions
|
||||||
ExportFormat.HtmlLight => "HTML (Light)",
|
ExportFormat.HtmlLight => "HTML (Light)",
|
||||||
ExportFormat.Csv => "CSV",
|
ExportFormat.Csv => "CSV",
|
||||||
ExportFormat.Json => "JSON",
|
ExportFormat.Json => "JSON",
|
||||||
_ => throw new ArgumentOutOfRangeException(nameof(format))
|
_ => throw new ArgumentOutOfRangeException(nameof(format)),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -172,24 +172,21 @@ public partial class ExportRequest
|
||||||
"%C" => channel.Name,
|
"%C" => channel.Name,
|
||||||
|
|
||||||
"%p" => channel.Position?.ToString(CultureInfo.InvariantCulture) ?? "0",
|
"%p" => channel.Position?.ToString(CultureInfo.InvariantCulture) ?? "0",
|
||||||
"%P"
|
"%P" => channel.Parent?.Position?.ToString(CultureInfo.InvariantCulture)
|
||||||
=> channel.Parent?.Position?.ToString(CultureInfo.InvariantCulture)
|
?? "0",
|
||||||
?? "0",
|
|
||||||
|
|
||||||
"%a"
|
"%a" => after?.ToDate().ToString("yyyy-MM-dd", CultureInfo.InvariantCulture)
|
||||||
=> after?.ToDate().ToString("yyyy-MM-dd", CultureInfo.InvariantCulture)
|
?? "",
|
||||||
?? "",
|
"%b" => before
|
||||||
"%b"
|
?.ToDate()
|
||||||
=> before?.ToDate().ToString("yyyy-MM-dd", CultureInfo.InvariantCulture)
|
.ToString("yyyy-MM-dd", CultureInfo.InvariantCulture) ?? "",
|
||||||
?? "",
|
"%d" => DateTimeOffset.Now.ToString(
|
||||||
"%d"
|
"yyyy-MM-dd",
|
||||||
=> DateTimeOffset.Now.ToString(
|
CultureInfo.InvariantCulture
|
||||||
"yyyy-MM-dd",
|
),
|
||||||
CultureInfo.InvariantCulture
|
|
||||||
),
|
|
||||||
|
|
||||||
"%%" => "%",
|
"%%" => "%",
|
||||||
_ => m.Value
|
_ => m.Value,
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
|
@ -3,5 +3,5 @@
|
||||||
internal enum BinaryExpressionKind
|
internal enum BinaryExpressionKind
|
||||||
{
|
{
|
||||||
Or,
|
Or,
|
||||||
And
|
And,
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,6 +14,6 @@ internal class BinaryExpressionMessageFilter(
|
||||||
{
|
{
|
||||||
BinaryExpressionKind.Or => first.IsMatch(message) || second.IsMatch(message),
|
BinaryExpressionKind.Or => first.IsMatch(message) || second.IsMatch(message),
|
||||||
BinaryExpressionKind.And => first.IsMatch(message) && second.IsMatch(message),
|
BinaryExpressionKind.And => first.IsMatch(message) && second.IsMatch(message),
|
||||||
_ => throw new InvalidOperationException($"Unknown binary expression kind '{kind}'.")
|
_ => throw new InvalidOperationException($"Unknown binary expression kind '{kind}'."),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,15 +17,13 @@ internal class HasMessageFilter(MessageContentMatchKind kind) : MessageFilter
|
||||||
MessageContentMatchKind.Image => message.Attachments.Any(file => file.IsImage),
|
MessageContentMatchKind.Image => message.Attachments.Any(file => file.IsImage),
|
||||||
MessageContentMatchKind.Sound => message.Attachments.Any(file => file.IsAudio),
|
MessageContentMatchKind.Sound => message.Attachments.Any(file => file.IsAudio),
|
||||||
MessageContentMatchKind.Pin => message.IsPinned,
|
MessageContentMatchKind.Pin => message.IsPinned,
|
||||||
MessageContentMatchKind.Invite
|
MessageContentMatchKind.Invite => MarkdownParser
|
||||||
=> MarkdownParser
|
.ExtractLinks(message.Content)
|
||||||
.ExtractLinks(message.Content)
|
.Select(l => l.Url)
|
||||||
.Select(l => l.Url)
|
.Select(Invite.TryGetCodeFromUrl)
|
||||||
.Select(Invite.TryGetCodeFromUrl)
|
.Any(c => !string.IsNullOrWhiteSpace(c)),
|
||||||
.Any(c => !string.IsNullOrWhiteSpace(c)),
|
_ => throw new InvalidOperationException(
|
||||||
_
|
$"Unknown message content match kind '{kind}'."
|
||||||
=> throw new InvalidOperationException(
|
),
|
||||||
$"Unknown message content match kind '{kind}'."
|
|
||||||
)
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,5 +9,5 @@ internal enum MessageContentMatchKind
|
||||||
Image,
|
Image,
|
||||||
Sound,
|
Sound,
|
||||||
Pin,
|
Pin,
|
||||||
Invite
|
Invite,
|
||||||
}
|
}
|
||||||
|
|
|
@ -125,7 +125,7 @@ internal static class FilterGrammar
|
||||||
op switch
|
op switch
|
||||||
{
|
{
|
||||||
'|' => new BinaryExpressionMessageFilter(left, right, BinaryExpressionKind.Or),
|
'|' => new BinaryExpressionMessageFilter(left, right, BinaryExpressionKind.Or),
|
||||||
_ => new BinaryExpressionMessageFilter(left, right, BinaryExpressionKind.And)
|
_ => new BinaryExpressionMessageFilter(left, right, BinaryExpressionKind.And),
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
@ -34,58 +34,51 @@ internal partial class HtmlMarkdownVisitor(
|
||||||
{
|
{
|
||||||
var (openingTag, closingTag) = formatting.Kind switch
|
var (openingTag, closingTag) = formatting.Kind switch
|
||||||
{
|
{
|
||||||
FormattingKind.Bold
|
FormattingKind.Bold => (
|
||||||
=> (
|
// lang=html
|
||||||
// lang=html
|
"<strong>",
|
||||||
"<strong>",
|
// lang=html
|
||||||
// lang=html
|
"</strong>"
|
||||||
"</strong>"
|
),
|
||||||
),
|
|
||||||
|
|
||||||
FormattingKind.Italic
|
FormattingKind.Italic => (
|
||||||
=> (
|
// lang=html
|
||||||
// lang=html
|
"<em>",
|
||||||
"<em>",
|
// lang=html
|
||||||
// lang=html
|
"</em>"
|
||||||
"</em>"
|
),
|
||||||
),
|
|
||||||
|
|
||||||
FormattingKind.Underline
|
FormattingKind.Underline => (
|
||||||
=> (
|
// lang=html
|
||||||
// lang=html
|
"<u>",
|
||||||
"<u>",
|
// lang=html
|
||||||
// lang=html
|
"</u>"
|
||||||
"</u>"
|
),
|
||||||
),
|
|
||||||
|
|
||||||
FormattingKind.Strikethrough
|
FormattingKind.Strikethrough => (
|
||||||
=> (
|
// lang=html
|
||||||
// lang=html
|
"<s>",
|
||||||
"<s>",
|
// lang=html
|
||||||
// lang=html
|
"</s>"
|
||||||
"</s>"
|
),
|
||||||
),
|
|
||||||
|
|
||||||
FormattingKind.Spoiler
|
FormattingKind.Spoiler => (
|
||||||
=> (
|
// lang=html
|
||||||
// lang=html
|
"""<span class="chatlog__markdown-spoiler chatlog__markdown-spoiler--hidden" onclick="showSpoiler(event, this)">""",
|
||||||
"""<span class="chatlog__markdown-spoiler chatlog__markdown-spoiler--hidden" onclick="showSpoiler(event, this)">""",
|
// lang=html
|
||||||
// lang=html
|
"""</span>"""
|
||||||
"""</span>"""
|
),
|
||||||
),
|
|
||||||
|
|
||||||
FormattingKind.Quote
|
FormattingKind.Quote => (
|
||||||
=> (
|
// lang=html
|
||||||
// lang=html
|
"""<div class="chatlog__markdown-quote"><div class="chatlog__markdown-quote-border"></div><div class="chatlog__markdown-quote-content">""",
|
||||||
"""<div class="chatlog__markdown-quote"><div class="chatlog__markdown-quote-border"></div><div class="chatlog__markdown-quote-content">""",
|
// lang=html
|
||||||
// lang=html
|
"""</div></div>"""
|
||||||
"""</div></div>"""
|
),
|
||||||
),
|
|
||||||
|
|
||||||
_
|
_ => throw new InvalidOperationException(
|
||||||
=> throw new InvalidOperationException(
|
$"Unknown formatting kind '{formatting.Kind}'."
|
||||||
$"Unknown formatting kind '{formatting.Kind}'."
|
),
|
||||||
)
|
|
||||||
};
|
};
|
||||||
|
|
||||||
buffer.Append(openingTag);
|
buffer.Append(openingTag);
|
||||||
|
|
|
@ -90,7 +90,7 @@ internal class HtmlMessageWriter(Stream stream, ExportContext context, string th
|
||||||
await new MessageGroupTemplate
|
await new MessageGroupTemplate
|
||||||
{
|
{
|
||||||
Context = Context,
|
Context = Context,
|
||||||
Messages = messages
|
Messages = messages,
|
||||||
}.RenderAsync(cancellationToken)
|
}.RenderAsync(cancellationToken)
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
@ -131,7 +131,7 @@ internal class HtmlMessageWriter(Stream stream, ExportContext context, string th
|
||||||
await new PostambleTemplate
|
await new PostambleTemplate
|
||||||
{
|
{
|
||||||
Context = Context,
|
Context = Context,
|
||||||
MessagesWritten = MessagesWritten
|
MessagesWritten = MessagesWritten,
|
||||||
}.RenderAsync(cancellationToken)
|
}.RenderAsync(cancellationToken)
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
|
@ -25,7 +25,7 @@ internal class JsonMessageWriter(Stream stream, ExportContext context)
|
||||||
Indented = true,
|
Indented = true,
|
||||||
// Validation errors may mask actual failures
|
// Validation errors may mask actual failures
|
||||||
// https://github.com/Tyrrrz/DiscordChatExporter/issues/413
|
// https://github.com/Tyrrrz/DiscordChatExporter/issues/413
|
||||||
SkipValidation = true
|
SkipValidation = true,
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
@ -100,13 +100,15 @@ internal partial class MessageExporter
|
||||||
ExportFormat.PlainText => new PlainTextMessageWriter(File.Create(filePath), context),
|
ExportFormat.PlainText => new PlainTextMessageWriter(File.Create(filePath), context),
|
||||||
ExportFormat.Csv => new CsvMessageWriter(File.Create(filePath), context),
|
ExportFormat.Csv => new CsvMessageWriter(File.Create(filePath), context),
|
||||||
ExportFormat.HtmlDark => new HtmlMessageWriter(File.Create(filePath), context, "Dark"),
|
ExportFormat.HtmlDark => new HtmlMessageWriter(File.Create(filePath), context, "Dark"),
|
||||||
ExportFormat.HtmlLight
|
ExportFormat.HtmlLight => new HtmlMessageWriter(
|
||||||
=> new HtmlMessageWriter(File.Create(filePath), context, "Light"),
|
File.Create(filePath),
|
||||||
|
context,
|
||||||
|
"Light"
|
||||||
|
),
|
||||||
ExportFormat.Json => new JsonMessageWriter(File.Create(filePath), context),
|
ExportFormat.Json => new JsonMessageWriter(File.Create(filePath), context),
|
||||||
_
|
_ => throw new ArgumentOutOfRangeException(
|
||||||
=> throw new ArgumentOutOfRangeException(
|
nameof(format),
|
||||||
nameof(format),
|
$"Unknown export format '{format}'."
|
||||||
$"Unknown export format '{format}'."
|
),
|
||||||
)
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -37,7 +37,7 @@ public partial class PartitionLimit
|
||||||
"M" => 1_000_000,
|
"M" => 1_000_000,
|
||||||
"K" => 1_000,
|
"K" => 1_000,
|
||||||
"" => 1,
|
"" => 1,
|
||||||
_ => -1
|
_ => -1,
|
||||||
};
|
};
|
||||||
|
|
||||||
if (magnitude < 0)
|
if (magnitude < 0)
|
||||||
|
|
|
@ -10,20 +10,18 @@ internal static class PlainTextMessageExtensions
|
||||||
public static string GetFallbackContent(this Message message) =>
|
public static string GetFallbackContent(this Message message) =>
|
||||||
message.Kind switch
|
message.Kind switch
|
||||||
{
|
{
|
||||||
MessageKind.RecipientAdd
|
MessageKind.RecipientAdd => message.MentionedUsers.Any()
|
||||||
=> message.MentionedUsers.Any()
|
? $"Added {message.MentionedUsers.First().DisplayName} to the group."
|
||||||
? $"Added {message.MentionedUsers.First().DisplayName} to the group."
|
: "Added a recipient.",
|
||||||
: "Added a recipient.",
|
|
||||||
|
|
||||||
MessageKind.RecipientRemove
|
MessageKind.RecipientRemove => message.MentionedUsers.Any()
|
||||||
=> message.MentionedUsers.Any()
|
? message.Author.Id == message.MentionedUsers.First().Id
|
||||||
? message.Author.Id == message.MentionedUsers.First().Id
|
? "Left the group."
|
||||||
? "Left the group."
|
: $"Removed {message.MentionedUsers.First().DisplayName} from the group."
|
||||||
: $"Removed {message.MentionedUsers.First().DisplayName} from the group."
|
: "Removed a recipient.",
|
||||||
: "Removed a recipient.",
|
|
||||||
|
|
||||||
MessageKind.Call
|
MessageKind.Call =>
|
||||||
=> $"Started a call that lasted {
|
$"Started a call that lasted {
|
||||||
message
|
message
|
||||||
.CallEndedTimestamp?
|
.CallEndedTimestamp?
|
||||||
.Pipe(t => t - message.Timestamp)
|
.Pipe(t => t - message.Timestamp)
|
||||||
|
@ -31,16 +29,15 @@ internal static class PlainTextMessageExtensions
|
||||||
.ToString("n0", CultureInfo.InvariantCulture) ?? "0"
|
.ToString("n0", CultureInfo.InvariantCulture) ?? "0"
|
||||||
} minutes.",
|
} minutes.",
|
||||||
|
|
||||||
MessageKind.ChannelNameChange
|
MessageKind.ChannelNameChange => !string.IsNullOrWhiteSpace(message.Content)
|
||||||
=> !string.IsNullOrWhiteSpace(message.Content)
|
? $"Changed the channel name: {message.Content}"
|
||||||
? $"Changed the channel name: {message.Content}"
|
: "Changed the channel name.",
|
||||||
: "Changed the channel name.",
|
|
||||||
|
|
||||||
MessageKind.ChannelIconChange => "Changed the channel icon.",
|
MessageKind.ChannelIconChange => "Changed the channel icon.",
|
||||||
MessageKind.ChannelPinnedMessage => "Pinned a message.",
|
MessageKind.ChannelPinnedMessage => "Pinned a message.",
|
||||||
MessageKind.ThreadCreated => "Started a thread.",
|
MessageKind.ThreadCreated => "Started a thread.",
|
||||||
MessageKind.GuildMemberJoin => "Joined the server.",
|
MessageKind.GuildMemberJoin => "Joined the server.",
|
||||||
|
|
||||||
_ => message.Content
|
_ => message.Content,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,5 +7,5 @@ internal enum FormattingKind
|
||||||
Underline,
|
Underline,
|
||||||
Strikethrough,
|
Strikethrough,
|
||||||
Spoiler,
|
Spoiler,
|
||||||
Quote
|
Quote,
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,5 +6,5 @@ internal enum MentionKind
|
||||||
Here,
|
Here,
|
||||||
User,
|
User,
|
||||||
Channel,
|
Channel,
|
||||||
Role
|
Role,
|
||||||
}
|
}
|
||||||
|
|
|
@ -377,10 +377,9 @@ internal static partial class MarkdownParser
|
||||||
null => null,
|
null => null,
|
||||||
// Unknown format: throw an exception to consider this timestamp invalid
|
// Unknown format: throw an exception to consider this timestamp invalid
|
||||||
// https://github.com/Tyrrrz/DiscordChatExporter/issues/1156
|
// https://github.com/Tyrrrz/DiscordChatExporter/issues/1156
|
||||||
var f
|
var f => throw new InvalidOperationException(
|
||||||
=> throw new InvalidOperationException(
|
$"Unknown timestamp format '{f}'."
|
||||||
$"Unknown timestamp format '{f}'."
|
),
|
||||||
)
|
|
||||||
};
|
};
|
||||||
|
|
||||||
return new TimestampNode(instant, format);
|
return new TimestampNode(instant, format);
|
||||||
|
|
|
@ -39,7 +39,7 @@ public static class Http
|
||||||
ShouldHandle = new PredicateBuilder().Handle<Exception>(IsRetryableException),
|
ShouldHandle = new PredicateBuilder().Handle<Exception>(IsRetryableException),
|
||||||
MaxRetryAttempts = 4,
|
MaxRetryAttempts = 4,
|
||||||
BackoffType = DelayBackoffType.Exponential,
|
BackoffType = DelayBackoffType.Exponential,
|
||||||
Delay = TimeSpan.FromSeconds(1)
|
Delay = TimeSpan.FromSeconds(1),
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
.Build();
|
.Build();
|
||||||
|
@ -68,7 +68,7 @@ public static class Http
|
||||||
return ValueTask.FromResult<TimeSpan?>(
|
return ValueTask.FromResult<TimeSpan?>(
|
||||||
TimeSpan.FromSeconds(Math.Pow(2, args.AttemptNumber) + 1)
|
TimeSpan.FromSeconds(Math.Pow(2, args.AttemptNumber) + 1)
|
||||||
);
|
);
|
||||||
}
|
},
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
.Build();
|
.Build();
|
||||||
|
|
|
@ -9,7 +9,7 @@ public static class PathEx
|
||||||
{
|
{
|
||||||
private static readonly HashSet<char> InvalidFileNameChars =
|
private static readonly HashSet<char> InvalidFileNameChars =
|
||||||
[
|
[
|
||||||
.. Path.GetInvalidFileNameChars()
|
.. Path.GetInvalidFileNameChars(),
|
||||||
];
|
];
|
||||||
|
|
||||||
public static string EscapeFileName(string path)
|
public static string EscapeFileName(string path)
|
||||||
|
|
|
@ -61,7 +61,7 @@ public class App : Application, IDisposable
|
||||||
{
|
{
|
||||||
ThemeVariant.Light => Avalonia.Styling.ThemeVariant.Light,
|
ThemeVariant.Light => Avalonia.Styling.ThemeVariant.Light,
|
||||||
ThemeVariant.Dark => Avalonia.Styling.ThemeVariant.Dark,
|
ThemeVariant.Dark => Avalonia.Styling.ThemeVariant.Dark,
|
||||||
_ => Avalonia.Styling.ThemeVariant.Default
|
_ => Avalonia.Styling.ThemeVariant.Default,
|
||||||
};
|
};
|
||||||
|
|
||||||
InitializeTheme();
|
InitializeTheme();
|
||||||
|
@ -86,7 +86,7 @@ public class App : Application, IDisposable
|
||||||
{
|
{
|
||||||
"Light" => PlatformThemeVariant.Light,
|
"Light" => PlatformThemeVariant.Light,
|
||||||
"Dark" => PlatformThemeVariant.Dark,
|
"Dark" => PlatformThemeVariant.Dark,
|
||||||
_ => PlatformSettings?.GetColorValues().ThemeVariant ?? PlatformThemeVariant.Light
|
_ => PlatformSettings?.GetColorValues().ThemeVariant ?? PlatformThemeVariant.Light,
|
||||||
};
|
};
|
||||||
|
|
||||||
this.LocateMaterialTheme<MaterialThemeBase>().CurrentTheme =
|
this.LocateMaterialTheme<MaterialThemeBase>().CurrentTheme =
|
||||||
|
|
|
@ -56,7 +56,7 @@ public class DialogManager : IDisposable
|
||||||
{
|
{
|
||||||
FileTypeChoices = fileTypes,
|
FileTypeChoices = fileTypes,
|
||||||
SuggestedFileName = defaultFilePath,
|
SuggestedFileName = defaultFilePath,
|
||||||
DefaultExtension = Path.GetExtension(defaultFilePath).TrimStart('.')
|
DefaultExtension = Path.GetExtension(defaultFilePath).TrimStart('.'),
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -77,7 +77,7 @@ public class DialogManager : IDisposable
|
||||||
new FolderPickerOpenOptions
|
new FolderPickerOpenOptions
|
||||||
{
|
{
|
||||||
AllowMultiple = false,
|
AllowMultiple = false,
|
||||||
SuggestedStartLocation = startLocation
|
SuggestedStartLocation = startLocation,
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
@ -4,5 +4,5 @@ public enum ThemeVariant
|
||||||
{
|
{
|
||||||
System,
|
System,
|
||||||
Light,
|
Light,
|
||||||
Dark
|
Dark,
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,7 +19,7 @@ public partial class ViewManager
|
||||||
ExportSetupViewModel => new ExportSetupView(),
|
ExportSetupViewModel => new ExportSetupView(),
|
||||||
MessageBoxViewModel => new MessageBoxView(),
|
MessageBoxViewModel => new MessageBoxView(),
|
||||||
SettingsViewModel => new SettingsView(),
|
SettingsViewModel => new SettingsView(),
|
||||||
_ => null
|
_ => null,
|
||||||
};
|
};
|
||||||
|
|
||||||
public Control? TryBindView(ViewModelBase viewModel)
|
public Control? TryBindView(ViewModelBase viewModel)
|
||||||
|
|
|
@ -4,5 +4,5 @@ public enum ThreadInclusionMode
|
||||||
{
|
{
|
||||||
None,
|
None,
|
||||||
Active,
|
Active,
|
||||||
All
|
All,
|
||||||
}
|
}
|
||||||
|
|
|
@ -253,7 +253,7 @@ public partial class DashboardViewModel : ViewModelBase
|
||||||
channelProgressPairs,
|
channelProgressPairs,
|
||||||
new ParallelOptions
|
new ParallelOptions
|
||||||
{
|
{
|
||||||
MaxDegreeOfParallelism = Math.Max(1, _settingsService.ParallelLimit)
|
MaxDegreeOfParallelism = Math.Max(1, _settingsService.ParallelLimit),
|
||||||
},
|
},
|
||||||
async (pair, cancellationToken) =>
|
async (pair, cancellationToken) =>
|
||||||
{
|
{
|
||||||
|
|
|
@ -138,8 +138,8 @@ public partial class ExportSetupViewModel(
|
||||||
[
|
[
|
||||||
new FilePickerFileType($"{extension.ToUpperInvariant()} file")
|
new FilePickerFileType($"{extension.ToUpperInvariant()} file")
|
||||||
{
|
{
|
||||||
Patterns = [$"*.{extension}"]
|
Patterns = [$"*.{extension}"],
|
||||||
}
|
},
|
||||||
],
|
],
|
||||||
defaultFileName
|
defaultFileName
|
||||||
);
|
);
|
||||||
|
|
|
@ -85,7 +85,7 @@ public class SettingsViewModel : DialogViewModelBase
|
||||||
"zh-CN",
|
"zh-CN",
|
||||||
"ja-JP",
|
"ja-JP",
|
||||||
"zh-TW",
|
"zh-TW",
|
||||||
"ko-KR"
|
"ko-KR",
|
||||||
];
|
];
|
||||||
|
|
||||||
// This has to be non-nullable because Avalonia ComboBox doesn't allow a null value to be selected
|
// This has to be non-nullable because Avalonia ComboBox doesn't allow a null value to be selected
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue