diff --git a/Directory.Packages.props b/Directory.Packages.props
index 8c144c22..a3a98c65 100644
--- a/Directory.Packages.props
+++ b/Directory.Packages.props
@@ -34,6 +34,7 @@
+
diff --git a/DiscordChatExporter.Cli.Tests/DiscordChatExporter.Cli.Tests.csproj b/DiscordChatExporter.Cli.Tests/DiscordChatExporter.Cli.Tests.csproj
index 8cba1b16..a8becf93 100644
--- a/DiscordChatExporter.Cli.Tests/DiscordChatExporter.Cli.Tests.csproj
+++ b/DiscordChatExporter.Cli.Tests/DiscordChatExporter.Cli.Tests.csproj
@@ -20,6 +20,7 @@
+
diff --git a/DiscordChatExporter.Cli.Tests/Specs/DateRangeSpecs.cs b/DiscordChatExporter.Cli.Tests/Specs/DateRangeSpecs.cs
index 25e0cec9..58bf0788 100644
--- a/DiscordChatExporter.Cli.Tests/Specs/DateRangeSpecs.cs
+++ b/DiscordChatExporter.Cli.Tests/Specs/DateRangeSpecs.cs
@@ -5,11 +5,11 @@ using System.Threading.Tasks;
using CliFx.Infrastructure;
using DiscordChatExporter.Cli.Commands;
using DiscordChatExporter.Cli.Tests.Infra;
-using DiscordChatExporter.Cli.Tests.Utils;
using DiscordChatExporter.Core.Discord;
using DiscordChatExporter.Core.Exporting;
using FluentAssertions;
using JsonExtensions;
+using PowerKit;
using Xunit;
namespace DiscordChatExporter.Cli.Tests.Specs;
diff --git a/DiscordChatExporter.Cli.Tests/Specs/FilterSpecs.cs b/DiscordChatExporter.Cli.Tests/Specs/FilterSpecs.cs
index 3b09786a..324278b6 100644
--- a/DiscordChatExporter.Cli.Tests/Specs/FilterSpecs.cs
+++ b/DiscordChatExporter.Cli.Tests/Specs/FilterSpecs.cs
@@ -5,11 +5,11 @@ using System.Threading.Tasks;
using CliFx.Infrastructure;
using DiscordChatExporter.Cli.Commands;
using DiscordChatExporter.Cli.Tests.Infra;
-using DiscordChatExporter.Cli.Tests.Utils;
using DiscordChatExporter.Core.Exporting;
using DiscordChatExporter.Core.Exporting.Filtering;
using FluentAssertions;
using JsonExtensions;
+using PowerKit;
using Xunit;
namespace DiscordChatExporter.Cli.Tests.Specs;
diff --git a/DiscordChatExporter.Cli.Tests/Specs/HtmlContentSpecs.cs b/DiscordChatExporter.Cli.Tests/Specs/HtmlContentSpecs.cs
index 601cfdd3..4b1e474d 100644
--- a/DiscordChatExporter.Cli.Tests/Specs/HtmlContentSpecs.cs
+++ b/DiscordChatExporter.Cli.Tests/Specs/HtmlContentSpecs.cs
@@ -8,6 +8,7 @@ using DiscordChatExporter.Cli.Tests.Infra;
using DiscordChatExporter.Cli.Tests.Utils;
using DiscordChatExporter.Core.Exporting;
using FluentAssertions;
+using PowerKit;
using Xunit;
namespace DiscordChatExporter.Cli.Tests.Specs;
diff --git a/DiscordChatExporter.Cli.Tests/Specs/HtmlEmbedSpecs.cs b/DiscordChatExporter.Cli.Tests/Specs/HtmlEmbedSpecs.cs
index a959d462..d204b8c0 100644
--- a/DiscordChatExporter.Cli.Tests/Specs/HtmlEmbedSpecs.cs
+++ b/DiscordChatExporter.Cli.Tests/Specs/HtmlEmbedSpecs.cs
@@ -4,8 +4,8 @@ using System.Threading.Tasks;
using AngleSharp.Dom;
using DiscordChatExporter.Cli.Tests.Infra;
using DiscordChatExporter.Core.Discord;
-using DiscordChatExporter.Core.Utils.Extensions;
using FluentAssertions;
+using PowerKit.Extensions;
using Xunit;
namespace DiscordChatExporter.Cli.Tests.Specs;
diff --git a/DiscordChatExporter.Cli.Tests/Specs/HtmlForwardSpecs.cs b/DiscordChatExporter.Cli.Tests/Specs/HtmlForwardSpecs.cs
index 823c900e..24929c89 100644
--- a/DiscordChatExporter.Cli.Tests/Specs/HtmlForwardSpecs.cs
+++ b/DiscordChatExporter.Cli.Tests/Specs/HtmlForwardSpecs.cs
@@ -1,9 +1,9 @@
using System.Threading.Tasks;
using AngleSharp.Dom;
using DiscordChatExporter.Cli.Tests.Infra;
-using DiscordChatExporter.Cli.Tests.Utils.Extensions;
using DiscordChatExporter.Core.Discord;
using FluentAssertions;
+using PowerKit.Extensions;
using Xunit;
namespace DiscordChatExporter.Cli.Tests.Specs;
@@ -22,7 +22,7 @@ public class HtmlForwardSpecs
// Assert
message
.Text()
- .ReplaceWhiteSpace()
+ .ReplaceWhiteSpace(' ')
.Should()
.ContainAll("Forwarded", @"¯\_(ツ)_/¯", "12/29/2025 2:14 PM");
}
diff --git a/DiscordChatExporter.Cli.Tests/Specs/HtmlGroupingSpecs.cs b/DiscordChatExporter.Cli.Tests/Specs/HtmlGroupingSpecs.cs
index 627a2468..c3bee420 100644
--- a/DiscordChatExporter.Cli.Tests/Specs/HtmlGroupingSpecs.cs
+++ b/DiscordChatExporter.Cli.Tests/Specs/HtmlGroupingSpecs.cs
@@ -8,6 +8,7 @@ using DiscordChatExporter.Cli.Tests.Infra;
using DiscordChatExporter.Cli.Tests.Utils;
using DiscordChatExporter.Core.Exporting;
using FluentAssertions;
+using PowerKit;
using Xunit;
namespace DiscordChatExporter.Cli.Tests.Specs;
diff --git a/DiscordChatExporter.Cli.Tests/Specs/HtmlMarkdownSpecs.cs b/DiscordChatExporter.Cli.Tests/Specs/HtmlMarkdownSpecs.cs
index c9623efb..c8496ada 100644
--- a/DiscordChatExporter.Cli.Tests/Specs/HtmlMarkdownSpecs.cs
+++ b/DiscordChatExporter.Cli.Tests/Specs/HtmlMarkdownSpecs.cs
@@ -1,9 +1,9 @@
using System.Threading.Tasks;
using AngleSharp.Dom;
using DiscordChatExporter.Cli.Tests.Infra;
-using DiscordChatExporter.Cli.Tests.Utils.Extensions;
using DiscordChatExporter.Core.Discord;
using FluentAssertions;
+using PowerKit.Extensions;
using Xunit;
namespace DiscordChatExporter.Cli.Tests.Specs;
@@ -22,11 +22,14 @@ public class HtmlMarkdownSpecs
// Assert
message
.Text()
- .ReplaceWhiteSpace()
+ .ReplaceWhiteSpace(' ')
.Should()
.Contain("Default timestamp: 2/12/2023 1:36 PM");
- message.InnerHtml.ReplaceWhiteSpace().Should().Contain("Sunday, February 12, 2023 1:36 PM");
+ message
+ .InnerHtml.ReplaceWhiteSpace(' ')
+ .Should()
+ .Contain("Sunday, February 12, 2023 1:36 PM");
}
[Fact]
@@ -39,8 +42,11 @@ public class HtmlMarkdownSpecs
);
// Assert
- message.Text().ReplaceWhiteSpace().Should().Contain("Short time timestamp: 1:36 PM");
- message.InnerHtml.ReplaceWhiteSpace().Should().Contain("Sunday, February 12, 2023 1:36 PM");
+ message.Text().ReplaceWhiteSpace(' ').Should().Contain("Short time timestamp: 1:36 PM");
+ message
+ .InnerHtml.ReplaceWhiteSpace(' ')
+ .Should()
+ .Contain("Sunday, February 12, 2023 1:36 PM");
}
[Fact]
@@ -53,8 +59,11 @@ public class HtmlMarkdownSpecs
);
// Assert
- message.Text().ReplaceWhiteSpace().Should().Contain("Long time timestamp: 1:36:12 PM");
- message.InnerHtml.ReplaceWhiteSpace().Should().Contain("Sunday, February 12, 2023 1:36 PM");
+ message.Text().ReplaceWhiteSpace(' ').Should().Contain("Long time timestamp: 1:36:12 PM");
+ message
+ .InnerHtml.ReplaceWhiteSpace(' ')
+ .Should()
+ .Contain("Sunday, February 12, 2023 1:36 PM");
}
[Fact]
@@ -67,8 +76,11 @@ public class HtmlMarkdownSpecs
);
// Assert
- message.Text().ReplaceWhiteSpace().Should().Contain("Short date timestamp: 2/12/2023");
- message.InnerHtml.ReplaceWhiteSpace().Should().Contain("Sunday, February 12, 2023 1:36 PM");
+ message.Text().ReplaceWhiteSpace(' ').Should().Contain("Short date timestamp: 2/12/2023");
+ message
+ .InnerHtml.ReplaceWhiteSpace(' ')
+ .Should()
+ .Contain("Sunday, February 12, 2023 1:36 PM");
}
[Fact]
@@ -83,11 +95,14 @@ public class HtmlMarkdownSpecs
// Assert
message
.Text()
- .ReplaceWhiteSpace()
+ .ReplaceWhiteSpace(' ')
.Should()
.Contain("Long date timestamp: Sunday, February 12, 2023");
- message.InnerHtml.ReplaceWhiteSpace().Should().Contain("Sunday, February 12, 2023 1:36 PM");
+ message
+ .InnerHtml.ReplaceWhiteSpace(' ')
+ .Should()
+ .Contain("Sunday, February 12, 2023 1:36 PM");
}
[Fact]
@@ -102,11 +117,14 @@ public class HtmlMarkdownSpecs
// Assert
message
.Text()
- .ReplaceWhiteSpace()
+ .ReplaceWhiteSpace(' ')
.Should()
.Contain("Full timestamp: Sunday, February 12, 2023 1:36 PM");
- message.InnerHtml.ReplaceWhiteSpace().Should().Contain("Sunday, February 12, 2023 1:36 PM");
+ message
+ .InnerHtml.ReplaceWhiteSpace(' ')
+ .Should()
+ .Contain("Sunday, February 12, 2023 1:36 PM");
}
[Fact]
@@ -121,11 +139,14 @@ public class HtmlMarkdownSpecs
// Assert
message
.Text()
- .ReplaceWhiteSpace()
+ .ReplaceWhiteSpace(' ')
.Should()
.Contain("Full long timestamp: Sunday, February 12, 2023 1:36:12 PM");
- message.InnerHtml.ReplaceWhiteSpace().Should().Contain("Sunday, February 12, 2023 1:36 PM");
+ message
+ .InnerHtml.ReplaceWhiteSpace(' ')
+ .Should()
+ .Contain("Sunday, February 12, 2023 1:36 PM");
}
[Fact]
@@ -140,11 +161,14 @@ public class HtmlMarkdownSpecs
// Assert
message
.Text()
- .ReplaceWhiteSpace()
+ .ReplaceWhiteSpace(' ')
.Should()
.Contain("Relative timestamp: 2/12/2023 1:36 PM");
- message.InnerHtml.ReplaceWhiteSpace().Should().Contain("Sunday, February 12, 2023 1:36 PM");
+ message
+ .InnerHtml.ReplaceWhiteSpace(' ')
+ .Should()
+ .Contain("Sunday, February 12, 2023 1:36 PM");
}
[Fact]
diff --git a/DiscordChatExporter.Cli.Tests/Specs/JsonContentSpecs.cs b/DiscordChatExporter.Cli.Tests/Specs/JsonContentSpecs.cs
index ec1e7f1f..dbe413c5 100644
--- a/DiscordChatExporter.Cli.Tests/Specs/JsonContentSpecs.cs
+++ b/DiscordChatExporter.Cli.Tests/Specs/JsonContentSpecs.cs
@@ -4,10 +4,10 @@ using System.Threading.Tasks;
using CliFx.Infrastructure;
using DiscordChatExporter.Cli.Commands;
using DiscordChatExporter.Cli.Tests.Infra;
-using DiscordChatExporter.Cli.Tests.Utils;
using DiscordChatExporter.Core.Exporting;
using FluentAssertions;
using JsonExtensions;
+using PowerKit;
using Xunit;
namespace DiscordChatExporter.Cli.Tests.Specs;
diff --git a/DiscordChatExporter.Cli.Tests/Specs/PartitioningSpecs.cs b/DiscordChatExporter.Cli.Tests/Specs/PartitioningSpecs.cs
index a763c9aa..89f27696 100644
--- a/DiscordChatExporter.Cli.Tests/Specs/PartitioningSpecs.cs
+++ b/DiscordChatExporter.Cli.Tests/Specs/PartitioningSpecs.cs
@@ -3,10 +3,10 @@ using System.Threading.Tasks;
using CliFx.Infrastructure;
using DiscordChatExporter.Cli.Commands;
using DiscordChatExporter.Cli.Tests.Infra;
-using DiscordChatExporter.Cli.Tests.Utils;
using DiscordChatExporter.Core.Exporting;
using DiscordChatExporter.Core.Exporting.Partitioning;
using FluentAssertions;
+using PowerKit;
using Xunit;
namespace DiscordChatExporter.Cli.Tests.Specs;
@@ -17,7 +17,7 @@ public class PartitioningSpecs
public async Task I_can_export_a_channel_with_partitioning_based_on_message_count()
{
// Arrange
- using var dir = TempDir.Create();
+ using var dir = TempDirectory.Create();
var filePath = Path.Combine(dir.Path, "output.html");
// Act
@@ -38,7 +38,7 @@ public class PartitioningSpecs
public async Task I_can_export_a_channel_with_partitioning_based_on_file_size()
{
// Arrange
- using var dir = TempDir.Create();
+ using var dir = TempDirectory.Create();
var filePath = Path.Combine(dir.Path, "output.html");
// Act
diff --git a/DiscordChatExporter.Cli.Tests/Specs/PlainTextForwardSpecs.cs b/DiscordChatExporter.Cli.Tests/Specs/PlainTextForwardSpecs.cs
index 8d42a36e..2f8ef73c 100644
--- a/DiscordChatExporter.Cli.Tests/Specs/PlainTextForwardSpecs.cs
+++ b/DiscordChatExporter.Cli.Tests/Specs/PlainTextForwardSpecs.cs
@@ -1,7 +1,7 @@
using System.Threading.Tasks;
using DiscordChatExporter.Cli.Tests.Infra;
-using DiscordChatExporter.Cli.Tests.Utils.Extensions;
using FluentAssertions;
+using PowerKit.Extensions;
using Xunit;
namespace DiscordChatExporter.Cli.Tests.Specs;
@@ -16,7 +16,7 @@ public class PlainTextForwardSpecs
// Assert
document
- .ReplaceWhiteSpace()
+ .ReplaceWhiteSpace(' ')
.Should()
.ContainAll("{Forwarded Message}", @"¯\_(ツ)_/¯", "12/28/2025 10:52 PM");
}
diff --git a/DiscordChatExporter.Cli.Tests/Specs/SelfContainedSpecs.cs b/DiscordChatExporter.Cli.Tests/Specs/SelfContainedSpecs.cs
index b8a2d677..58865701 100644
--- a/DiscordChatExporter.Cli.Tests/Specs/SelfContainedSpecs.cs
+++ b/DiscordChatExporter.Cli.Tests/Specs/SelfContainedSpecs.cs
@@ -7,6 +7,7 @@ using DiscordChatExporter.Cli.Tests.Infra;
using DiscordChatExporter.Cli.Tests.Utils;
using DiscordChatExporter.Core.Exporting;
using FluentAssertions;
+using PowerKit;
using Xunit;
namespace DiscordChatExporter.Cli.Tests.Specs;
@@ -17,7 +18,7 @@ public class SelfContainedSpecs
public async Task I_can_export_a_channel_and_download_all_referenced_assets()
{
// Arrange
- using var dir = TempDir.Create();
+ using var dir = TempDirectory.Create();
var filePath = Path.Combine(dir.Path, "output.html");
// Act
diff --git a/DiscordChatExporter.Cli.Tests/Utils/Extensions/StringExtensions.cs b/DiscordChatExporter.Cli.Tests/Utils/Extensions/StringExtensions.cs
deleted file mode 100644
index 4db07483..00000000
--- a/DiscordChatExporter.Cli.Tests/Utils/Extensions/StringExtensions.cs
+++ /dev/null
@@ -1,19 +0,0 @@
-using System.Text;
-
-namespace DiscordChatExporter.Cli.Tests.Utils.Extensions;
-
-internal static class StringExtensions
-{
- extension(string str)
- {
- public string ReplaceWhiteSpace(string replacement = " ")
- {
- var buffer = new StringBuilder(str.Length);
-
- foreach (var ch in str)
- buffer.Append(char.IsWhiteSpace(ch) ? replacement : ch);
-
- return buffer.ToString();
- }
- }
-}
diff --git a/DiscordChatExporter.Cli.Tests/Utils/TempDir.cs b/DiscordChatExporter.Cli.Tests/Utils/TempDir.cs
deleted file mode 100644
index e907755d..00000000
--- a/DiscordChatExporter.Cli.Tests/Utils/TempDir.cs
+++ /dev/null
@@ -1,36 +0,0 @@
-using System;
-using System.IO;
-using System.Reflection;
-
-namespace DiscordChatExporter.Cli.Tests.Utils;
-
-internal partial class TempDir(string path) : IDisposable
-{
- public string Path { get; } = path;
-
- public void Dispose()
- {
- try
- {
- Directory.Delete(Path, true);
- }
- catch (DirectoryNotFoundException) { }
- }
-}
-
-internal partial class TempDir
-{
- public static TempDir Create()
- {
- var dirPath = System.IO.Path.Combine(
- System.IO.Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location)
- ?? Directory.GetCurrentDirectory(),
- "Temp",
- Guid.NewGuid().ToString()
- );
-
- Directory.CreateDirectory(dirPath);
-
- return new TempDir(dirPath);
- }
-}
diff --git a/DiscordChatExporter.Cli.Tests/Utils/TempFile.cs b/DiscordChatExporter.Cli.Tests/Utils/TempFile.cs
deleted file mode 100644
index 0a686a65..00000000
--- a/DiscordChatExporter.Cli.Tests/Utils/TempFile.cs
+++ /dev/null
@@ -1,37 +0,0 @@
-using System;
-using System.IO;
-using System.Reflection;
-
-namespace DiscordChatExporter.Cli.Tests.Utils;
-
-internal partial class TempFile(string path) : IDisposable
-{
- public string Path { get; } = path;
-
- public void Dispose()
- {
- try
- {
- File.Delete(Path);
- }
- catch (FileNotFoundException) { }
- }
-}
-
-internal partial class TempFile
-{
- public static TempFile Create()
- {
- var dirPath = System.IO.Path.Combine(
- System.IO.Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location)
- ?? Directory.GetCurrentDirectory(),
- "Temp"
- );
-
- Directory.CreateDirectory(dirPath);
-
- var filePath = System.IO.Path.Combine(dirPath, Guid.NewGuid() + ".tmp");
-
- return new TempFile(filePath);
- }
-}
diff --git a/DiscordChatExporter.Cli/Commands/ExportChannelsCommand.cs b/DiscordChatExporter.Cli/Commands/ExportChannelsCommand.cs
index 3e963cad..832ae536 100644
--- a/DiscordChatExporter.Cli/Commands/ExportChannelsCommand.cs
+++ b/DiscordChatExporter.Cli/Commands/ExportChannelsCommand.cs
@@ -5,7 +5,7 @@ using CliFx.Infrastructure;
using DiscordChatExporter.Cli.Commands.Base;
using DiscordChatExporter.Core.Discord;
using DiscordChatExporter.Core.Discord.Data;
-using DiscordChatExporter.Core.Utils.Extensions;
+using PowerKit.Extensions;
namespace DiscordChatExporter.Cli.Commands;
diff --git a/DiscordChatExporter.Cli/Commands/ExportDirectMessagesCommand.cs b/DiscordChatExporter.Cli/Commands/ExportDirectMessagesCommand.cs
index 39f41f40..5fba63a7 100644
--- a/DiscordChatExporter.Cli/Commands/ExportDirectMessagesCommand.cs
+++ b/DiscordChatExporter.Cli/Commands/ExportDirectMessagesCommand.cs
@@ -3,7 +3,7 @@ using CliFx.Binding;
using CliFx.Infrastructure;
using DiscordChatExporter.Cli.Commands.Base;
using DiscordChatExporter.Core.Discord.Data;
-using DiscordChatExporter.Core.Utils.Extensions;
+using PowerKit.Extensions;
namespace DiscordChatExporter.Cli.Commands;
diff --git a/DiscordChatExporter.Cli/Commands/GetChannelsCommand.cs b/DiscordChatExporter.Cli/Commands/GetChannelsCommand.cs
index b0116ed7..fb63a36a 100644
--- a/DiscordChatExporter.Cli/Commands/GetChannelsCommand.cs
+++ b/DiscordChatExporter.Cli/Commands/GetChannelsCommand.cs
@@ -7,7 +7,7 @@ using DiscordChatExporter.Cli.Commands.Base;
using DiscordChatExporter.Cli.Commands.Converters;
using DiscordChatExporter.Cli.Commands.Shared;
using DiscordChatExporter.Core.Discord;
-using DiscordChatExporter.Core.Utils.Extensions;
+using PowerKit.Extensions;
namespace DiscordChatExporter.Cli.Commands;
diff --git a/DiscordChatExporter.Cli/Commands/GetDirectChannelsCommand.cs b/DiscordChatExporter.Cli/Commands/GetDirectChannelsCommand.cs
index 06ea69b1..b1c8586e 100644
--- a/DiscordChatExporter.Cli/Commands/GetDirectChannelsCommand.cs
+++ b/DiscordChatExporter.Cli/Commands/GetDirectChannelsCommand.cs
@@ -5,7 +5,7 @@ using CliFx.Binding;
using CliFx.Infrastructure;
using DiscordChatExporter.Cli.Commands.Base;
using DiscordChatExporter.Core.Discord.Data;
-using DiscordChatExporter.Core.Utils.Extensions;
+using PowerKit.Extensions;
namespace DiscordChatExporter.Cli.Commands;
diff --git a/DiscordChatExporter.Cli/Commands/GetGuildsCommand.cs b/DiscordChatExporter.Cli/Commands/GetGuildsCommand.cs
index 6d8b5410..626d05bb 100644
--- a/DiscordChatExporter.Cli/Commands/GetGuildsCommand.cs
+++ b/DiscordChatExporter.Cli/Commands/GetGuildsCommand.cs
@@ -5,7 +5,7 @@ using CliFx.Binding;
using CliFx.Infrastructure;
using DiscordChatExporter.Cli.Commands.Base;
using DiscordChatExporter.Core.Discord.Data;
-using DiscordChatExporter.Core.Utils.Extensions;
+using PowerKit.Extensions;
namespace DiscordChatExporter.Cli.Commands;
diff --git a/DiscordChatExporter.Cli/DiscordChatExporter.Cli.csproj b/DiscordChatExporter.Cli/DiscordChatExporter.Cli.csproj
index 2945ffa4..eed56c41 100644
--- a/DiscordChatExporter.Cli/DiscordChatExporter.Cli.csproj
+++ b/DiscordChatExporter.Cli/DiscordChatExporter.Cli.csproj
@@ -11,6 +11,7 @@
+
diff --git a/DiscordChatExporter.Core/Discord/Data/Application.cs b/DiscordChatExporter.Core/Discord/Data/Application.cs
index 05309e64..2a18fb02 100644
--- a/DiscordChatExporter.Core/Discord/Data/Application.cs
+++ b/DiscordChatExporter.Core/Discord/Data/Application.cs
@@ -1,6 +1,6 @@
using System.Text.Json;
-using DiscordChatExporter.Core.Utils.Extensions;
using JsonExtensions.Reading;
+using PowerKit.Extensions;
namespace DiscordChatExporter.Core.Discord.Data;
diff --git a/DiscordChatExporter.Core/Discord/Data/Attachment.cs b/DiscordChatExporter.Core/Discord/Data/Attachment.cs
index 7d0a7ca6..f47776cb 100644
--- a/DiscordChatExporter.Core/Discord/Data/Attachment.cs
+++ b/DiscordChatExporter.Core/Discord/Data/Attachment.cs
@@ -2,8 +2,8 @@
using System.IO;
using System.Text.Json;
using DiscordChatExporter.Core.Discord.Data.Common;
-using DiscordChatExporter.Core.Utils.Extensions;
using JsonExtensions.Reading;
+using PowerKit.Extensions;
namespace DiscordChatExporter.Core.Discord.Data;
diff --git a/DiscordChatExporter.Core/Discord/Data/Channel.cs b/DiscordChatExporter.Core/Discord/Data/Channel.cs
index 8511b47d..ef137170 100644
--- a/DiscordChatExporter.Core/Discord/Data/Channel.cs
+++ b/DiscordChatExporter.Core/Discord/Data/Channel.cs
@@ -2,8 +2,8 @@
using System.Linq;
using System.Text.Json;
using DiscordChatExporter.Core.Discord.Data.Common;
-using DiscordChatExporter.Core.Utils.Extensions;
using JsonExtensions.Reading;
+using PowerKit.Extensions;
namespace DiscordChatExporter.Core.Discord.Data;
diff --git a/DiscordChatExporter.Core/Discord/Data/Embeds/Embed.cs b/DiscordChatExporter.Core/Discord/Data/Embeds/Embed.cs
index caa474e1..b9a29e83 100644
--- a/DiscordChatExporter.Core/Discord/Data/Embeds/Embed.cs
+++ b/DiscordChatExporter.Core/Discord/Data/Embeds/Embed.cs
@@ -3,8 +3,8 @@ using System.Collections.Generic;
using System.Drawing;
using System.Linq;
using System.Text.Json;
-using DiscordChatExporter.Core.Utils.Extensions;
using JsonExtensions.Reading;
+using PowerKit.Extensions;
namespace DiscordChatExporter.Core.Discord.Data.Embeds;
@@ -47,9 +47,7 @@ public partial record Embed
var kind =
json.GetPropertyOrNull("type")
?.GetStringOrNull()
- ?.Pipe(s =>
- Enum.TryParse(s, true, out var result) ? result : (EmbedKind?)null
- )
+ .Pipe(s => Enum.ParseOrNull(s, true))
?? EmbedKind.Rich;
var url = json.GetPropertyOrNull("url")?.GetNonWhiteSpaceStringOrNull();
@@ -58,7 +56,7 @@ public partial record Embed
var color = json.GetPropertyOrNull("color")
?.GetInt32OrNull()
?.Pipe(System.Drawing.Color.FromArgb)
- .ResetAlpha();
+ .WithFullAlpha();
var author = json.GetPropertyOrNull("author")?.Pipe(EmbedAuthor.Parse);
var description = json.GetPropertyOrNull("description")?.GetStringOrNull();
diff --git a/DiscordChatExporter.Core/Discord/Data/Emoji.cs b/DiscordChatExporter.Core/Discord/Data/Emoji.cs
index 31aafff7..b0bbb2e3 100644
--- a/DiscordChatExporter.Core/Discord/Data/Emoji.cs
+++ b/DiscordChatExporter.Core/Discord/Data/Emoji.cs
@@ -1,8 +1,8 @@
using System.Text.Json;
using DiscordChatExporter.Core.Discord.Data.Common;
using DiscordChatExporter.Core.Utils;
-using DiscordChatExporter.Core.Utils.Extensions;
using JsonExtensions.Reading;
+using PowerKit.Extensions;
namespace DiscordChatExporter.Core.Discord.Data;
diff --git a/DiscordChatExporter.Core/Discord/Data/Guild.cs b/DiscordChatExporter.Core/Discord/Data/Guild.cs
index fa2ade07..b9efdc7b 100644
--- a/DiscordChatExporter.Core/Discord/Data/Guild.cs
+++ b/DiscordChatExporter.Core/Discord/Data/Guild.cs
@@ -1,7 +1,7 @@
using System.Text.Json;
using DiscordChatExporter.Core.Discord.Data.Common;
-using DiscordChatExporter.Core.Utils.Extensions;
using JsonExtensions.Reading;
+using PowerKit.Extensions;
namespace DiscordChatExporter.Core.Discord.Data;
diff --git a/DiscordChatExporter.Core/Discord/Data/Interaction.cs b/DiscordChatExporter.Core/Discord/Data/Interaction.cs
index 3a020d40..6b964df4 100644
--- a/DiscordChatExporter.Core/Discord/Data/Interaction.cs
+++ b/DiscordChatExporter.Core/Discord/Data/Interaction.cs
@@ -1,6 +1,6 @@
using System.Text.Json;
-using DiscordChatExporter.Core.Utils.Extensions;
using JsonExtensions.Reading;
+using PowerKit.Extensions;
namespace DiscordChatExporter.Core.Discord.Data;
diff --git a/DiscordChatExporter.Core/Discord/Data/Invite.cs b/DiscordChatExporter.Core/Discord/Data/Invite.cs
index 17d077f4..1c87a6d6 100644
--- a/DiscordChatExporter.Core/Discord/Data/Invite.cs
+++ b/DiscordChatExporter.Core/Discord/Data/Invite.cs
@@ -1,7 +1,7 @@
using System.Text.Json;
using System.Text.RegularExpressions;
-using DiscordChatExporter.Core.Utils.Extensions;
using JsonExtensions.Reading;
+using PowerKit.Extensions;
namespace DiscordChatExporter.Core.Discord.Data;
diff --git a/DiscordChatExporter.Core/Discord/Data/Member.cs b/DiscordChatExporter.Core/Discord/Data/Member.cs
index ec9487f4..4beb40b4 100644
--- a/DiscordChatExporter.Core/Discord/Data/Member.cs
+++ b/DiscordChatExporter.Core/Discord/Data/Member.cs
@@ -2,8 +2,8 @@
using System.Linq;
using System.Text.Json;
using DiscordChatExporter.Core.Discord.Data.Common;
-using DiscordChatExporter.Core.Utils.Extensions;
using JsonExtensions.Reading;
+using PowerKit.Extensions;
namespace DiscordChatExporter.Core.Discord.Data;
diff --git a/DiscordChatExporter.Core/Discord/Data/Message.cs b/DiscordChatExporter.Core/Discord/Data/Message.cs
index 9b1c4f92..c35c7690 100644
--- a/DiscordChatExporter.Core/Discord/Data/Message.cs
+++ b/DiscordChatExporter.Core/Discord/Data/Message.cs
@@ -4,8 +4,8 @@ using System.Linq;
using System.Text.Json;
using DiscordChatExporter.Core.Discord.Data.Common;
using DiscordChatExporter.Core.Discord.Data.Embeds;
-using DiscordChatExporter.Core.Utils.Extensions;
using JsonExtensions.Reading;
+using PowerKit.Extensions;
namespace DiscordChatExporter.Core.Discord.Data;
diff --git a/DiscordChatExporter.Core/Discord/Data/MessageReference.cs b/DiscordChatExporter.Core/Discord/Data/MessageReference.cs
index 48c68d5f..b6f2bf56 100644
--- a/DiscordChatExporter.Core/Discord/Data/MessageReference.cs
+++ b/DiscordChatExporter.Core/Discord/Data/MessageReference.cs
@@ -1,6 +1,6 @@
using System.Text.Json;
-using DiscordChatExporter.Core.Utils.Extensions;
using JsonExtensions.Reading;
+using PowerKit.Extensions;
namespace DiscordChatExporter.Core.Discord.Data;
diff --git a/DiscordChatExporter.Core/Discord/Data/Reaction.cs b/DiscordChatExporter.Core/Discord/Data/Reaction.cs
index 21e9c712..17117b47 100644
--- a/DiscordChatExporter.Core/Discord/Data/Reaction.cs
+++ b/DiscordChatExporter.Core/Discord/Data/Reaction.cs
@@ -1,5 +1,5 @@
using System.Text.Json;
-using DiscordChatExporter.Core.Utils.Extensions;
+using PowerKit.Extensions;
namespace DiscordChatExporter.Core.Discord.Data;
diff --git a/DiscordChatExporter.Core/Discord/Data/Role.cs b/DiscordChatExporter.Core/Discord/Data/Role.cs
index 86616eff..999da66e 100644
--- a/DiscordChatExporter.Core/Discord/Data/Role.cs
+++ b/DiscordChatExporter.Core/Discord/Data/Role.cs
@@ -1,8 +1,8 @@
using System.Drawing;
using System.Text.Json;
using DiscordChatExporter.Core.Discord.Data.Common;
-using DiscordChatExporter.Core.Utils.Extensions;
using JsonExtensions.Reading;
+using PowerKit.Extensions;
namespace DiscordChatExporter.Core.Discord.Data;
@@ -18,7 +18,7 @@ public record Role(Snowflake Id, string Name, int Position, Color? Color) : IHas
var color = json.GetPropertyOrNull("color")
?.GetInt32OrNull()
?.Pipe(System.Drawing.Color.FromArgb)
- .ResetAlpha()
+ .WithFullAlpha()
.NullIf(c => c.ToRgb() <= 0);
return new Role(id, name, position, color);
diff --git a/DiscordChatExporter.Core/Discord/Data/Sticker.cs b/DiscordChatExporter.Core/Discord/Data/Sticker.cs
index 76ea2486..79314455 100644
--- a/DiscordChatExporter.Core/Discord/Data/Sticker.cs
+++ b/DiscordChatExporter.Core/Discord/Data/Sticker.cs
@@ -1,8 +1,8 @@
using System;
using System.Text.Json;
using DiscordChatExporter.Core.Discord.Data.Common;
-using DiscordChatExporter.Core.Utils.Extensions;
using JsonExtensions.Reading;
+using PowerKit.Extensions;
namespace DiscordChatExporter.Core.Discord.Data;
diff --git a/DiscordChatExporter.Core/Discord/Data/User.cs b/DiscordChatExporter.Core/Discord/Data/User.cs
index ba1ec8fc..30e6992a 100644
--- a/DiscordChatExporter.Core/Discord/Data/User.cs
+++ b/DiscordChatExporter.Core/Discord/Data/User.cs
@@ -1,7 +1,7 @@
using System.Text.Json;
using DiscordChatExporter.Core.Discord.Data.Common;
-using DiscordChatExporter.Core.Utils.Extensions;
using JsonExtensions.Reading;
+using PowerKit.Extensions;
namespace DiscordChatExporter.Core.Discord.Data;
diff --git a/DiscordChatExporter.Core/Discord/DiscordClient.cs b/DiscordChatExporter.Core/Discord/DiscordClient.cs
index c3846136..b021b790 100644
--- a/DiscordChatExporter.Core/Discord/DiscordClient.cs
+++ b/DiscordChatExporter.Core/Discord/DiscordClient.cs
@@ -11,10 +11,10 @@ using System.Threading.Tasks;
using DiscordChatExporter.Core.Discord.Data;
using DiscordChatExporter.Core.Exceptions;
using DiscordChatExporter.Core.Utils;
-using DiscordChatExporter.Core.Utils.Extensions;
using Gress;
using JsonExtensions.Http;
using JsonExtensions.Reading;
+using PowerKit.Extensions;
namespace DiscordChatExporter.Core.Discord;
@@ -59,12 +59,12 @@ public class DiscordClient(
{
var remainingRequestCount = response
.Headers.TryGetValue("X-RateLimit-Remaining")
- ?.Pipe(s => int.Parse(s, CultureInfo.InvariantCulture));
+ ?.Pipe(s => int.ParseOrNull(s, CultureInfo.InvariantCulture));
var resetAfterDelay = response
.Headers.TryGetValue("X-RateLimit-Reset-After")
- ?.Pipe(s => double.Parse(s, CultureInfo.InvariantCulture))
- .Pipe(TimeSpan.FromSeconds);
+ ?.Pipe(s => double.ParseOrNull(s, CultureInfo.InvariantCulture))
+ ?.Pipe(TimeSpan.FromSeconds);
// If this was the last request available before hitting the rate limit,
// wait out the reset time so that future requests can succeed.
@@ -161,7 +161,7 @@ public class DiscordClient(
$"""
Request to '{url}' failed: {response
.StatusCode.ToString()
- .ToSpaceSeparatedWords()
+ .SeparateWords(' ')
.ToLowerInvariant()}.
Response content: {await response.Content.ReadAsStringAsync(
cancellationToken
diff --git a/DiscordChatExporter.Core/DiscordChatExporter.Core.csproj b/DiscordChatExporter.Core/DiscordChatExporter.Core.csproj
index 041356e5..efd2b93a 100644
--- a/DiscordChatExporter.Core/DiscordChatExporter.Core.csproj
+++ b/DiscordChatExporter.Core/DiscordChatExporter.Core.csproj
@@ -6,6 +6,7 @@
+
diff --git a/DiscordChatExporter.Core/Exporting/CsvMessageWriter.cs b/DiscordChatExporter.Core/Exporting/CsvMessageWriter.cs
index f6911ce3..fee4c387 100644
--- a/DiscordChatExporter.Core/Exporting/CsvMessageWriter.cs
+++ b/DiscordChatExporter.Core/Exporting/CsvMessageWriter.cs
@@ -5,7 +5,7 @@ using System.Text;
using System.Threading;
using System.Threading.Tasks;
using DiscordChatExporter.Core.Discord.Data;
-using DiscordChatExporter.Core.Utils.Extensions;
+using PowerKit.Extensions;
namespace DiscordChatExporter.Core.Exporting;
diff --git a/DiscordChatExporter.Core/Exporting/ExportAssetDownloader.cs b/DiscordChatExporter.Core/Exporting/ExportAssetDownloader.cs
index 84cf4223..0260a20f 100644
--- a/DiscordChatExporter.Core/Exporting/ExportAssetDownloader.cs
+++ b/DiscordChatExporter.Core/Exporting/ExportAssetDownloader.cs
@@ -9,7 +9,7 @@ using System.Threading.Tasks;
using System.Web;
using AsyncKeyedLock;
using DiscordChatExporter.Core.Utils;
-using DiscordChatExporter.Core.Utils.Extensions;
+using PowerKit.Extensions;
namespace DiscordChatExporter.Core.Exporting;
diff --git a/DiscordChatExporter.Core/Exporting/ExportContext.cs b/DiscordChatExporter.Core/Exporting/ExportContext.cs
index 4b78e41a..b16fd6be 100644
--- a/DiscordChatExporter.Core/Exporting/ExportContext.cs
+++ b/DiscordChatExporter.Core/Exporting/ExportContext.cs
@@ -9,7 +9,7 @@ using System.Threading.Tasks;
using DiscordChatExporter.Core.Discord;
using DiscordChatExporter.Core.Discord.Data;
using DiscordChatExporter.Core.Utils;
-using DiscordChatExporter.Core.Utils.Extensions;
+using PowerKit.Extensions;
namespace DiscordChatExporter.Core.Exporting;
diff --git a/DiscordChatExporter.Core/Exporting/ExportRequest.cs b/DiscordChatExporter.Core/Exporting/ExportRequest.cs
index aae3a921..0bbaa76a 100644
--- a/DiscordChatExporter.Core/Exporting/ExportRequest.cs
+++ b/DiscordChatExporter.Core/Exporting/ExportRequest.cs
@@ -7,7 +7,7 @@ using DiscordChatExporter.Core.Discord;
using DiscordChatExporter.Core.Discord.Data;
using DiscordChatExporter.Core.Exporting.Filtering;
using DiscordChatExporter.Core.Exporting.Partitioning;
-using DiscordChatExporter.Core.Utils.Extensions;
+using PowerKit.Extensions;
namespace DiscordChatExporter.Core.Exporting;
diff --git a/DiscordChatExporter.Core/Exporting/HtmlMarkdownVisitor.cs b/DiscordChatExporter.Core/Exporting/HtmlMarkdownVisitor.cs
index 407913f6..ba4fa389 100644
--- a/DiscordChatExporter.Core/Exporting/HtmlMarkdownVisitor.cs
+++ b/DiscordChatExporter.Core/Exporting/HtmlMarkdownVisitor.cs
@@ -7,7 +7,7 @@ using System.Threading;
using System.Threading.Tasks;
using DiscordChatExporter.Core.Markdown;
using DiscordChatExporter.Core.Markdown.Parsing;
-using DiscordChatExporter.Core.Utils.Extensions;
+using PowerKit.Extensions;
namespace DiscordChatExporter.Core.Exporting;
diff --git a/DiscordChatExporter.Core/Exporting/JsonMessageWriter.cs b/DiscordChatExporter.Core/Exporting/JsonMessageWriter.cs
index 6bb8cc11..43f820d3 100644
--- a/DiscordChatExporter.Core/Exporting/JsonMessageWriter.cs
+++ b/DiscordChatExporter.Core/Exporting/JsonMessageWriter.cs
@@ -9,8 +9,8 @@ using System.Threading.Tasks;
using DiscordChatExporter.Core.Discord.Data;
using DiscordChatExporter.Core.Discord.Data.Embeds;
using DiscordChatExporter.Core.Markdown.Parsing;
-using DiscordChatExporter.Core.Utils.Extensions;
using JsonExtensions.Writing;
+using PowerKit.Extensions;
namespace DiscordChatExporter.Core.Exporting;
@@ -55,7 +55,7 @@ internal class JsonMessageWriter(Stream stream, ExportContext context)
Context.TryGetMember(user.Id)?.DisplayName ?? user.DisplayName
);
- _writer.WriteString("color", Context.TryGetUserColor(user.Id)?.ToHex());
+ _writer.WriteString("color", Context.TryGetUserColor(user.Id)?.ToHexString());
_writer.WriteBoolean("isBot", user.IsBot);
if (includeRoles)
@@ -109,7 +109,7 @@ internal class JsonMessageWriter(Stream stream, ExportContext context)
_writer.WriteString("id", role.Id.ToString());
_writer.WriteString("name", role.Name);
- _writer.WriteString("color", role.Color?.ToHex());
+ _writer.WriteString("color", role.Color?.ToHexString());
_writer.WriteNumber("position", role.Position);
_writer.WriteEndObject();
@@ -281,7 +281,7 @@ internal class JsonMessageWriter(Stream stream, ExportContext context)
);
if (embed.Color is not null)
- _writer.WriteString("color", embed.Color.Value.ToHex());
+ _writer.WriteString("color", embed.Color.Value.ToHexString());
if (embed.Author is not null)
{
diff --git a/DiscordChatExporter.Core/Exporting/MessageGroupTemplate.cshtml b/DiscordChatExporter.Core/Exporting/MessageGroupTemplate.cshtml
index cefb8bfb..f0dba0de 100644
--- a/DiscordChatExporter.Core/Exporting/MessageGroupTemplate.cshtml
+++ b/DiscordChatExporter.Core/Exporting/MessageGroupTemplate.cshtml
@@ -6,6 +6,7 @@
@using DiscordChatExporter.Core.Discord.Data.Embeds
@using DiscordChatExporter.Core.Markdown.Parsing
@using DiscordChatExporter.Core.Utils.Extensions
+@using PowerKit.Extensions
@inherits RazorBlade.HtmlTemplate
diff --git a/DiscordChatExporter.Core/Exporting/PlainTextMarkdownVisitor.cs b/DiscordChatExporter.Core/Exporting/PlainTextMarkdownVisitor.cs
index 95513ba4..d59bd102 100644
--- a/DiscordChatExporter.Core/Exporting/PlainTextMarkdownVisitor.cs
+++ b/DiscordChatExporter.Core/Exporting/PlainTextMarkdownVisitor.cs
@@ -3,7 +3,7 @@ using System.Threading;
using System.Threading.Tasks;
using DiscordChatExporter.Core.Markdown;
using DiscordChatExporter.Core.Markdown.Parsing;
-using DiscordChatExporter.Core.Utils.Extensions;
+using PowerKit.Extensions;
namespace DiscordChatExporter.Core.Exporting;
diff --git a/DiscordChatExporter.Core/Exporting/PlainTextMessageExtensions.cs b/DiscordChatExporter.Core/Exporting/PlainTextMessageExtensions.cs
index 69a81d49..ab1aa23f 100644
--- a/DiscordChatExporter.Core/Exporting/PlainTextMessageExtensions.cs
+++ b/DiscordChatExporter.Core/Exporting/PlainTextMessageExtensions.cs
@@ -1,7 +1,7 @@
using System.Globalization;
using System.Linq;
using DiscordChatExporter.Core.Discord.Data;
-using DiscordChatExporter.Core.Utils.Extensions;
+using PowerKit.Extensions;
namespace DiscordChatExporter.Core.Exporting;
diff --git a/DiscordChatExporter.Core/Markdown/Parsing/MarkdownParser.cs b/DiscordChatExporter.Core/Markdown/Parsing/MarkdownParser.cs
index 4bb53ed6..e3362e66 100644
--- a/DiscordChatExporter.Core/Markdown/Parsing/MarkdownParser.cs
+++ b/DiscordChatExporter.Core/Markdown/Parsing/MarkdownParser.cs
@@ -5,7 +5,7 @@ using System.Linq;
using System.Text.RegularExpressions;
using DiscordChatExporter.Core.Discord;
using DiscordChatExporter.Core.Utils;
-using DiscordChatExporter.Core.Utils.Extensions;
+using PowerKit.Extensions;
namespace DiscordChatExporter.Core.Markdown.Parsing;
diff --git a/DiscordChatExporter.Core/Utils/Extensions/AsyncCollectionExtensions.cs b/DiscordChatExporter.Core/Utils/Extensions/AsyncCollectionExtensions.cs
deleted file mode 100644
index 2af5e685..00000000
--- a/DiscordChatExporter.Core/Utils/Extensions/AsyncCollectionExtensions.cs
+++ /dev/null
@@ -1,24 +0,0 @@
-using System.Collections.Generic;
-using System.Runtime.CompilerServices;
-using System.Threading.Tasks;
-
-namespace DiscordChatExporter.Core.Utils.Extensions;
-
-public static class AsyncCollectionExtensions
-{
- extension(IAsyncEnumerable asyncEnumerable)
- {
- private async ValueTask> CollectAsync()
- {
- var list = new List();
-
- await foreach (var i in asyncEnumerable)
- list.Add(i);
-
- return list;
- }
-
- public ValueTaskAwaiter> GetAwaiter() =>
- asyncEnumerable.CollectAsync().GetAwaiter();
- }
-}
diff --git a/DiscordChatExporter.Core/Utils/Extensions/CollectionExtensions.cs b/DiscordChatExporter.Core/Utils/Extensions/CollectionExtensions.cs
deleted file mode 100644
index abb1dd9a..00000000
--- a/DiscordChatExporter.Core/Utils/Extensions/CollectionExtensions.cs
+++ /dev/null
@@ -1,40 +0,0 @@
-using System.Collections.Generic;
-
-namespace DiscordChatExporter.Core.Utils.Extensions;
-
-public static class CollectionExtensions
-{
- extension(T obj)
- {
- public IEnumerable ToSingletonEnumerable()
- {
- yield return obj;
- }
- }
-
- extension(IEnumerable source)
- where T : class
- {
- public IEnumerable WhereNotNull()
- {
- foreach (var o in source)
- {
- if (o is not null)
- yield return o;
- }
- }
- }
-
- extension(IEnumerable source)
- where T : struct
- {
- public IEnumerable WhereNotNull()
- {
- foreach (var o in source)
- {
- if (o is not null)
- yield return o.Value;
- }
- }
- }
-}
diff --git a/DiscordChatExporter.Core/Utils/Extensions/ColorExtensions.cs b/DiscordChatExporter.Core/Utils/Extensions/ColorExtensions.cs
deleted file mode 100644
index a3ec6486..00000000
--- a/DiscordChatExporter.Core/Utils/Extensions/ColorExtensions.cs
+++ /dev/null
@@ -1,17 +0,0 @@
-using System.Drawing;
-
-namespace DiscordChatExporter.Core.Utils.Extensions;
-
-public static class ColorExtensions
-{
- extension(Color color)
- {
- public Color WithAlpha(int alpha) => Color.FromArgb(alpha, color);
-
- public Color ResetAlpha() => color.WithAlpha(255);
-
- public int ToRgb() => color.ToArgb() & 0xffffff;
-
- public string ToHex() => $"#{color.R:X2}{color.G:X2}{color.B:X2}";
- }
-}
diff --git a/DiscordChatExporter.Core/Utils/Extensions/ExceptionExtensions.cs b/DiscordChatExporter.Core/Utils/Extensions/ExceptionExtensions.cs
deleted file mode 100644
index e3d67f1f..00000000
--- a/DiscordChatExporter.Core/Utils/Extensions/ExceptionExtensions.cs
+++ /dev/null
@@ -1,34 +0,0 @@
-using System;
-using System.Collections.Generic;
-
-namespace DiscordChatExporter.Core.Utils.Extensions;
-
-public static class ExceptionExtensions
-{
- extension(Exception exception)
- {
- private void PopulateChildren(ICollection children)
- {
- if (exception is AggregateException aggregateException)
- {
- foreach (var innerException in aggregateException.InnerExceptions)
- {
- children.Add(innerException);
- PopulateChildren(innerException, children);
- }
- }
- else if (exception.InnerException is not null)
- {
- children.Add(exception.InnerException);
- PopulateChildren(exception.InnerException, children);
- }
- }
-
- public IReadOnlyList GetSelfAndChildren()
- {
- var children = new List { exception };
- PopulateChildren(exception, children);
- return children;
- }
- }
-}
diff --git a/DiscordChatExporter.Core/Utils/Extensions/GenericExtensions.cs b/DiscordChatExporter.Core/Utils/Extensions/GenericExtensions.cs
deleted file mode 100644
index 2c47995d..00000000
--- a/DiscordChatExporter.Core/Utils/Extensions/GenericExtensions.cs
+++ /dev/null
@@ -1,21 +0,0 @@
-using System;
-using System.Collections.Generic;
-
-namespace DiscordChatExporter.Core.Utils.Extensions;
-
-public static class GenericExtensions
-{
- extension(TIn input)
- {
- public TOut Pipe(Func transform) => transform(input);
- }
-
- extension(T value)
- where T : struct
- {
- public T? NullIf(Func predicate) => !predicate(value) ? value : null;
-
- public T? NullIfDefault() =>
- value.NullIf(v => EqualityComparer.Default.Equals(v, default));
- }
-}
diff --git a/DiscordChatExporter.Core/Utils/Extensions/HttpExtensions.cs b/DiscordChatExporter.Core/Utils/Extensions/HttpExtensions.cs
deleted file mode 100644
index 19841afc..00000000
--- a/DiscordChatExporter.Core/Utils/Extensions/HttpExtensions.cs
+++ /dev/null
@@ -1,12 +0,0 @@
-using System.Net.Http.Headers;
-
-namespace DiscordChatExporter.Core.Utils.Extensions;
-
-public static class HttpExtensions
-{
- extension(HttpHeaders headers)
- {
- public string? TryGetValue(string name) =>
- headers.TryGetValues(name, out var values) ? string.Concat(values) : null;
- }
-}
diff --git a/DiscordChatExporter.Core/Utils/Extensions/PathExtensions.cs b/DiscordChatExporter.Core/Utils/Extensions/PathExtensions.cs
deleted file mode 100644
index 183fc13c..00000000
--- a/DiscordChatExporter.Core/Utils/Extensions/PathExtensions.cs
+++ /dev/null
@@ -1,48 +0,0 @@
-using System;
-using System.IO;
-using System.Text;
-
-namespace DiscordChatExporter.Core.Utils.Extensions;
-
-public static class PathExtensions
-{
- // This is a union of invalid characters from Windows (NTFS/FAT32), Linux (ext4/XFS), and macOS (HFS+/APFS).
- // We use this instead of Path.GetInvalidFileNameChars() because that only returns OS-specific characters,
- // not filesystem-specific characters. It's possible to use, for example, an NTFS drive on Linux,
- // which would make some additional characters invalid that are otherwise valid on Linux.
- // https://github.com/Tyrrrz/DiscordChatExporter/issues/1452
- private static readonly char[] InvalidFileNameChars =
- [
- '\0', // Null character - invalid on all filesystems
- '/', // Path separator on Unix and Windows
- '\\', // Path separator on Windows
- ':', // Reserved on Windows (drive letters, NTFS streams)
- '*', // Wildcard on Windows
- '?', // Wildcard on Windows
- '"', // Reserved on Windows
- '<', // Redirection on Windows
- '>', // Redirection on Windows
- '|', // Pipe on Windows
- ];
-
- extension(Path)
- {
- public static string EscapeFileName(string path)
- {
- var buffer = new StringBuilder(path.Length);
-
- foreach (var c in path)
- buffer.Append(!InvalidFileNameChars.Contains(c) ? c : '_');
-
- // File names cannot end with a dot on Windows
- // https://github.com/Tyrrrz/DiscordChatExporter/issues/977
- if (OperatingSystem.IsWindows())
- {
- while (buffer.Length > 0 && buffer[^1] == '.')
- buffer.Remove(buffer.Length - 1, 1);
- }
-
- return buffer.ToString();
- }
- }
-}
diff --git a/DiscordChatExporter.Core/Utils/Extensions/StringExtensions.cs b/DiscordChatExporter.Core/Utils/Extensions/StringExtensions.cs
deleted file mode 100644
index cc84267a..00000000
--- a/DiscordChatExporter.Core/Utils/Extensions/StringExtensions.cs
+++ /dev/null
@@ -1,34 +0,0 @@
-using System.Text;
-
-namespace DiscordChatExporter.Core.Utils.Extensions;
-
-public static class StringExtensions
-{
- extension(string str)
- {
- public string? NullIfWhiteSpace() => !string.IsNullOrWhiteSpace(str) ? str : null;
-
- public string Truncate(int charCount) => str.Length > charCount ? str[..charCount] : str;
-
- public string ToSpaceSeparatedWords()
- {
- 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();
- }
- }
-
- extension(StringBuilder builder)
- {
- public StringBuilder AppendIfNotEmpty(char value) =>
- builder.Length > 0 ? builder.Append(value) : builder;
- }
-}
diff --git a/DiscordChatExporter.Core/Utils/Extensions/TimeSpanExtensions.cs b/DiscordChatExporter.Core/Utils/Extensions/TimeSpanExtensions.cs
deleted file mode 100644
index 9d838b09..00000000
--- a/DiscordChatExporter.Core/Utils/Extensions/TimeSpanExtensions.cs
+++ /dev/null
@@ -1,20 +0,0 @@
-using System;
-
-namespace DiscordChatExporter.Core.Utils.Extensions;
-
-public static class TimeSpanExtensions
-{
- extension(TimeSpan value)
- {
- public TimeSpan Clamp(TimeSpan min, TimeSpan max)
- {
- if (value < min)
- return min;
-
- if (value > max)
- return max;
-
- return value;
- }
- }
-}
diff --git a/DiscordChatExporter.Core/Utils/Http.cs b/DiscordChatExporter.Core/Utils/Http.cs
index 7ed0b47c..49c6d9ab 100644
--- a/DiscordChatExporter.Core/Utils/Http.cs
+++ b/DiscordChatExporter.Core/Utils/Http.cs
@@ -5,9 +5,9 @@ using System.Net.Http;
using System.Net.Sockets;
using System.Security.Authentication;
using System.Threading.Tasks;
-using DiscordChatExporter.Core.Utils.Extensions;
using Polly;
using Polly.Retry;
+using PowerKit.Extensions;
namespace DiscordChatExporter.Core.Utils;
@@ -24,7 +24,7 @@ public static class Http
private static bool IsRetryableException(Exception exception) =>
exception
- .GetSelfAndChildren()
+ .GetSelfAndDescendants()
.Any(ex =>
ex is TimeoutException or SocketException or AuthenticationException
|| ex is HttpRequestException hrex
diff --git a/DiscordChatExporter.Core/Utils/UrlBuilder.cs b/DiscordChatExporter.Core/Utils/UrlBuilder.cs
index 2622bf25..f1c3b064 100644
--- a/DiscordChatExporter.Core/Utils/UrlBuilder.cs
+++ b/DiscordChatExporter.Core/Utils/UrlBuilder.cs
@@ -2,7 +2,7 @@
using System.Collections.Generic;
using System.Linq;
using System.Text;
-using DiscordChatExporter.Core.Utils.Extensions;
+using PowerKit.Extensions;
namespace DiscordChatExporter.Core.Utils;
diff --git a/DiscordChatExporter.Gui/App.axaml.cs b/DiscordChatExporter.Gui/App.axaml.cs
index 975b34ae..af71f607 100644
--- a/DiscordChatExporter.Gui/App.axaml.cs
+++ b/DiscordChatExporter.Gui/App.axaml.cs
@@ -7,13 +7,13 @@ using Avalonia.Platform;
using DiscordChatExporter.Gui.Framework;
using DiscordChatExporter.Gui.Localization;
using DiscordChatExporter.Gui.Services;
-using DiscordChatExporter.Gui.Utils;
-using DiscordChatExporter.Gui.Utils.Extensions;
using DiscordChatExporter.Gui.ViewModels;
using DiscordChatExporter.Gui.ViewModels.Components;
using DiscordChatExporter.Gui.ViewModels.Dialogs;
using Material.Styles.Themes;
using Microsoft.Extensions.DependencyInjection;
+using PowerKit;
+using PowerKit.Extensions;
namespace DiscordChatExporter.Gui;
@@ -22,7 +22,7 @@ public class App : Application, IDisposable
private readonly ServiceProvider _services;
private readonly SettingsService _settingsService;
- private readonly DisposableCollector _eventRoot = new();
+ private readonly IDisposable _eventSubscription;
private bool _isDisposed;
@@ -54,7 +54,7 @@ public class App : Application, IDisposable
_settingsService = _services.GetRequiredService();
// Re-initialize the theme when the user changes it
- _eventRoot.Add(
+ _eventSubscription = Disposable.Merge(
_settingsService.WatchProperty(
o => o.Theme,
v =>
@@ -131,7 +131,7 @@ public class App : Application, IDisposable
_isDisposed = true;
- _eventRoot.Dispose();
+ _eventSubscription.Dispose();
_services.Dispose();
}
}
diff --git a/DiscordChatExporter.Gui/DiscordChatExporter.Gui.csproj b/DiscordChatExporter.Gui/DiscordChatExporter.Gui.csproj
index b9b2baff..94ed5edf 100644
--- a/DiscordChatExporter.Gui/DiscordChatExporter.Gui.csproj
+++ b/DiscordChatExporter.Gui/DiscordChatExporter.Gui.csproj
@@ -41,6 +41,7 @@
+
diff --git a/DiscordChatExporter.Gui/Localization/LocalizationManager.cs b/DiscordChatExporter.Gui/Localization/LocalizationManager.cs
index de917565..9b8fdfba 100644
--- a/DiscordChatExporter.Gui/Localization/LocalizationManager.cs
+++ b/DiscordChatExporter.Gui/Localization/LocalizationManager.cs
@@ -3,20 +3,19 @@ using System.Globalization;
using System.Runtime.CompilerServices;
using CommunityToolkit.Mvvm.ComponentModel;
using DiscordChatExporter.Gui.Services;
-using DiscordChatExporter.Gui.Utils;
-using DiscordChatExporter.Gui.Utils.Extensions;
+using PowerKit;
+using PowerKit.Extensions;
namespace DiscordChatExporter.Gui.Localization;
public partial class LocalizationManager : ObservableObject, IDisposable
{
- private readonly DisposableCollector _eventRoot = new();
+ private readonly IDisposable _eventSubscription;
public LocalizationManager(SettingsService settingsService)
{
- _eventRoot.Add(settingsService.WatchProperty(o => o.Language, v => Language = v, true));
-
- _eventRoot.Add(
+ _eventSubscription = Disposable.Merge(
+ settingsService.WatchProperty(o => o.Language, v => Language = v, true),
this.WatchProperty(
o => o.Language,
_ =>
@@ -66,7 +65,7 @@ public partial class LocalizationManager : ObservableObject, IDisposable
return $"Missing localization for '{key}'";
}
- public void Dispose() => _eventRoot.Dispose();
+ public void Dispose() => _eventSubscription.Dispose();
}
public partial class LocalizationManager
diff --git a/DiscordChatExporter.Gui/Utils/Disposable.cs b/DiscordChatExporter.Gui/Utils/Disposable.cs
deleted file mode 100644
index e7bdfb63..00000000
--- a/DiscordChatExporter.Gui/Utils/Disposable.cs
+++ /dev/null
@@ -1,10 +0,0 @@
-using System;
-
-namespace DiscordChatExporter.Gui.Utils;
-
-internal class Disposable(Action dispose) : IDisposable
-{
- public static IDisposable Create(Action dispose) => new Disposable(dispose);
-
- public void Dispose() => dispose();
-}
diff --git a/DiscordChatExporter.Gui/Utils/DisposableCollector.cs b/DiscordChatExporter.Gui/Utils/DisposableCollector.cs
deleted file mode 100644
index e96761b9..00000000
--- a/DiscordChatExporter.Gui/Utils/DisposableCollector.cs
+++ /dev/null
@@ -1,28 +0,0 @@
-using System;
-using System.Collections.Generic;
-using DiscordChatExporter.Gui.Utils.Extensions;
-
-namespace DiscordChatExporter.Gui.Utils;
-
-internal class DisposableCollector : IDisposable
-{
- private readonly object _lock = new();
- private readonly List _items = [];
-
- public void Add(IDisposable item)
- {
- lock (_lock)
- {
- _items.Add(item);
- }
- }
-
- public void Dispose()
- {
- lock (_lock)
- {
- _items.DisposeAll();
- _items.Clear();
- }
- }
-}
diff --git a/DiscordChatExporter.Gui/Utils/Extensions/CommandExtensions.cs b/DiscordChatExporter.Gui/Utils/Extensions/CommandExtensions.cs
deleted file mode 100644
index fc0008cb..00000000
--- a/DiscordChatExporter.Gui/Utils/Extensions/CommandExtensions.cs
+++ /dev/null
@@ -1,15 +0,0 @@
-using System.Windows.Input;
-
-namespace DiscordChatExporter.Gui.Utils.Extensions;
-
-internal static class CommandExtensions
-{
- extension(ICommand command)
- {
- public void ExecuteIfCan(object? parameter = null)
- {
- if (command.CanExecute(parameter))
- command.Execute(parameter);
- }
- }
-}
diff --git a/DiscordChatExporter.Gui/Utils/Extensions/DisposableExtensions.cs b/DiscordChatExporter.Gui/Utils/Extensions/DisposableExtensions.cs
deleted file mode 100644
index 553526bd..00000000
--- a/DiscordChatExporter.Gui/Utils/Extensions/DisposableExtensions.cs
+++ /dev/null
@@ -1,31 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Linq;
-
-namespace DiscordChatExporter.Gui.Utils.Extensions;
-
-internal static class DisposableExtensions
-{
- extension(IEnumerable disposables)
- {
- public void DisposeAll()
- {
- var exceptions = default(List);
-
- foreach (var disposable in disposables)
- {
- try
- {
- disposable.Dispose();
- }
- catch (Exception ex)
- {
- (exceptions ??= []).Add(ex);
- }
- }
-
- if (exceptions?.Any() == true)
- throw new AggregateException(exceptions);
- }
- }
-}
diff --git a/DiscordChatExporter.Gui/Utils/Extensions/NotifyPropertyChangedExtensions.cs b/DiscordChatExporter.Gui/Utils/Extensions/NotifyPropertyChangedExtensions.cs
deleted file mode 100644
index eef1583f..00000000
--- a/DiscordChatExporter.Gui/Utils/Extensions/NotifyPropertyChangedExtensions.cs
+++ /dev/null
@@ -1,55 +0,0 @@
-using System;
-using System.ComponentModel;
-using System.Linq.Expressions;
-using System.Reflection;
-
-namespace DiscordChatExporter.Gui.Utils.Extensions;
-
-internal static class NotifyPropertyChangedExtensions
-{
- extension(TOwner owner)
- where TOwner : INotifyPropertyChanged
- {
- public IDisposable WatchProperty(
- Expression> propertyExpression,
- Action callback,
- bool watchInitialValue = false
- )
- {
- var memberExpression = propertyExpression.Body as MemberExpression;
- if (memberExpression?.Member is not PropertyInfo property)
- throw new ArgumentException("Provided expression must reference a property.");
-
- var getValue = propertyExpression.Compile();
-
- void OnPropertyChanged(object? sender, PropertyChangedEventArgs args)
- {
- if (
- string.IsNullOrWhiteSpace(args.PropertyName)
- || string.Equals(args.PropertyName, property.Name, StringComparison.Ordinal)
- )
- {
- callback(getValue(owner));
- }
- }
-
- owner.PropertyChanged += OnPropertyChanged;
-
- if (watchInitialValue)
- callback(getValue(owner));
-
- return Disposable.Create(() => owner.PropertyChanged -= OnPropertyChanged);
- }
-
- public IDisposable WatchAllProperties(Action callback, bool watchInitialValues = false)
- {
- void OnPropertyChanged(object? sender, PropertyChangedEventArgs args) => callback();
- owner.PropertyChanged += OnPropertyChanged;
-
- if (watchInitialValues)
- callback();
-
- return Disposable.Create(() => owner.PropertyChanged -= OnPropertyChanged);
- }
- }
-}
diff --git a/DiscordChatExporter.Gui/Utils/Extensions/ProcessExtensions.cs b/DiscordChatExporter.Gui/Utils/Extensions/ProcessExtensions.cs
deleted file mode 100644
index 07317d21..00000000
--- a/DiscordChatExporter.Gui/Utils/Extensions/ProcessExtensions.cs
+++ /dev/null
@@ -1,17 +0,0 @@
-using System.Diagnostics;
-
-namespace DiscordChatExporter.Gui.Utils.Extensions;
-
-internal static class ProcessExtensions
-{
- extension(Process)
- {
- public static void StartShellExecute(string path)
- {
- using var process = new Process();
- process.StartInfo = new ProcessStartInfo(path) { UseShellExecute = true };
-
- process.Start();
- }
- }
-}
diff --git a/DiscordChatExporter.Gui/ViewModels/Components/DashboardViewModel.cs b/DiscordChatExporter.Gui/ViewModels/Components/DashboardViewModel.cs
index dd7a8454..1d1866cd 100644
--- a/DiscordChatExporter.Gui/ViewModels/Components/DashboardViewModel.cs
+++ b/DiscordChatExporter.Gui/ViewModels/Components/DashboardViewModel.cs
@@ -10,15 +10,14 @@ using DiscordChatExporter.Core.Discord;
using DiscordChatExporter.Core.Discord.Data;
using DiscordChatExporter.Core.Exceptions;
using DiscordChatExporter.Core.Exporting;
-using DiscordChatExporter.Core.Utils.Extensions;
using DiscordChatExporter.Gui.Framework;
using DiscordChatExporter.Gui.Localization;
using DiscordChatExporter.Gui.Models;
using DiscordChatExporter.Gui.Services;
-using DiscordChatExporter.Gui.Utils;
-using DiscordChatExporter.Gui.Utils.Extensions;
using Gress;
using Gress.Completable;
+using PowerKit;
+using PowerKit.Extensions;
namespace DiscordChatExporter.Gui.ViewModels.Components;
@@ -29,7 +28,7 @@ public partial class DashboardViewModel : ViewModelBase
private readonly DialogManager _dialogManager;
private readonly SettingsService _settingsService;
- private readonly DisposableCollector _eventRoot = new();
+ private readonly IDisposable _eventSubscription;
private readonly AutoResetProgressMuxer _progressMuxer;
private DiscordClient? _discord;
@@ -50,14 +49,11 @@ public partial class DashboardViewModel : ViewModelBase
_progressMuxer = Progress.CreateMuxer().WithAutoReset();
- _eventRoot.Add(
+ _eventSubscription = Disposable.Merge(
Progress.WatchProperty(
o => o.Current,
_ => OnPropertyChanged(nameof(IsProgressIndeterminate))
- )
- );
-
- _eventRoot.Add(
+ ),
SelectedChannels.WatchProperty(
o => o.Count,
_ => ExportCommand.NotifyCanExecuteChanged()
@@ -332,7 +328,7 @@ public partial class DashboardViewModel : ViewModelBase
{
if (disposing)
{
- _eventRoot.Dispose();
+ _eventSubscription.Dispose();
}
base.Dispose(disposing);
diff --git a/DiscordChatExporter.Gui/ViewModels/Dialogs/ExportSetupViewModel.cs b/DiscordChatExporter.Gui/ViewModels/Dialogs/ExportSetupViewModel.cs
index 5a43e646..e1c8705a 100644
--- a/DiscordChatExporter.Gui/ViewModels/Dialogs/ExportSetupViewModel.cs
+++ b/DiscordChatExporter.Gui/ViewModels/Dialogs/ExportSetupViewModel.cs
@@ -10,10 +10,10 @@ using DiscordChatExporter.Core.Discord.Data;
using DiscordChatExporter.Core.Exporting;
using DiscordChatExporter.Core.Exporting.Filtering;
using DiscordChatExporter.Core.Exporting.Partitioning;
-using DiscordChatExporter.Core.Utils.Extensions;
using DiscordChatExporter.Gui.Framework;
using DiscordChatExporter.Gui.Localization;
using DiscordChatExporter.Gui.Services;
+using PowerKit.Extensions;
namespace DiscordChatExporter.Gui.ViewModels.Dialogs;
diff --git a/DiscordChatExporter.Gui/ViewModels/Dialogs/SettingsViewModel.cs b/DiscordChatExporter.Gui/ViewModels/Dialogs/SettingsViewModel.cs
index 143246ca..c1c19543 100644
--- a/DiscordChatExporter.Gui/ViewModels/Dialogs/SettingsViewModel.cs
+++ b/DiscordChatExporter.Gui/ViewModels/Dialogs/SettingsViewModel.cs
@@ -1,13 +1,12 @@
using System;
using System.Collections.Generic;
using DiscordChatExporter.Core.Discord;
-using DiscordChatExporter.Core.Utils.Extensions;
using DiscordChatExporter.Gui.Framework;
using DiscordChatExporter.Gui.Localization;
using DiscordChatExporter.Gui.Models;
using DiscordChatExporter.Gui.Services;
-using DiscordChatExporter.Gui.Utils;
-using DiscordChatExporter.Gui.Utils.Extensions;
+using PowerKit;
+using PowerKit.Extensions;
namespace DiscordChatExporter.Gui.ViewModels.Dialogs;
@@ -15,7 +14,7 @@ public class SettingsViewModel : DialogViewModelBase
{
private readonly SettingsService _settingsService;
- private readonly DisposableCollector _eventRoot = new();
+ private readonly IDisposable _eventSubscription;
public SettingsViewModel(
SettingsService settingsService,
@@ -25,7 +24,9 @@ public class SettingsViewModel : DialogViewModelBase
_settingsService = settingsService;
LocalizationManager = localizationManager;
- _eventRoot.Add(_settingsService.WatchAllProperties(OnAllPropertiesChanged));
+ _eventSubscription = Disposable.Merge(
+ _settingsService.WatchAllProperties(OnAllPropertiesChanged)
+ );
}
public LocalizationManager LocalizationManager { get; }
@@ -140,7 +141,7 @@ public class SettingsViewModel : DialogViewModelBase
{
if (disposing)
{
- _eventRoot.Dispose();
+ _eventSubscription.Dispose();
}
base.Dispose(disposing);
diff --git a/DiscordChatExporter.Gui/ViewModels/MainViewModel.cs b/DiscordChatExporter.Gui/ViewModels/MainViewModel.cs
index ca428c63..b4715d8e 100644
--- a/DiscordChatExporter.Gui/ViewModels/MainViewModel.cs
+++ b/DiscordChatExporter.Gui/ViewModels/MainViewModel.cs
@@ -7,6 +7,7 @@ using DiscordChatExporter.Gui.Localization;
using DiscordChatExporter.Gui.Services;
using DiscordChatExporter.Gui.Utils.Extensions;
using DiscordChatExporter.Gui.ViewModels.Components;
+using PowerKit.Extensions;
namespace DiscordChatExporter.Gui.ViewModels;
diff --git a/DiscordChatExporter.Gui/Views/Components/DashboardView.axaml.cs b/DiscordChatExporter.Gui/Views/Components/DashboardView.axaml.cs
index 6155a55d..4bd49841 100644
--- a/DiscordChatExporter.Gui/Views/Components/DashboardView.axaml.cs
+++ b/DiscordChatExporter.Gui/Views/Components/DashboardView.axaml.cs
@@ -4,8 +4,8 @@ using Avalonia.Input;
using Avalonia.Interactivity;
using DiscordChatExporter.Core.Discord.Data;
using DiscordChatExporter.Gui.Framework;
-using DiscordChatExporter.Gui.Utils.Extensions;
using DiscordChatExporter.Gui.ViewModels.Components;
+using PowerKit.Extensions;
namespace DiscordChatExporter.Gui.Views.Components;
diff --git a/DiscordChatExporter.Gui/Views/Controls/HyperLink.axaml.cs b/DiscordChatExporter.Gui/Views/Controls/HyperLink.axaml.cs
index 72ed47a8..d8d94dc6 100644
--- a/DiscordChatExporter.Gui/Views/Controls/HyperLink.axaml.cs
+++ b/DiscordChatExporter.Gui/Views/Controls/HyperLink.axaml.cs
@@ -3,7 +3,7 @@ using System.Windows.Input;
using Avalonia;
using Avalonia.Controls;
using Avalonia.Input;
-using DiscordChatExporter.Gui.Utils.Extensions;
+using PowerKit.Extensions;
namespace DiscordChatExporter.Gui.Views.Controls;