mirror of
https://github.com/Tyrrrz/DiscordChatExporter.git
synced 2026-05-06 04:23:36 +00:00
Add PowerKit and replace custom utility extensions (#1525)
Co-authored-by: Tyrrrz <1935960+Tyrrrz@users.noreply.github.com> Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
This commit is contained in:
@@ -34,6 +34,7 @@
|
||||
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="18.3.0" />
|
||||
<PackageVersion Include="Onova" Version="2.6.13" />
|
||||
<PackageVersion Include="Polly" Version="8.6.6" />
|
||||
<PackageVersion Include="PowerKit" Version="1.1.0" />
|
||||
<PackageVersion Include="RazorBlade" Version="0.11.0" />
|
||||
<PackageVersion Include="Spectre.Console" Version="0.54.0" />
|
||||
<PackageVersion Include="Superpower" Version="3.1.0" />
|
||||
|
||||
@@ -20,6 +20,7 @@
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration.EnvironmentVariables" />
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration.UserSecrets" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" />
|
||||
<PackageReference Include="PowerKit" PrivateAssets="all" />
|
||||
<PackageReference Include="xunit" />
|
||||
<PackageReference Include="xunit.runner.visualstudio" PrivateAssets="all" />
|
||||
</ItemGroup>
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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");
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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]
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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");
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
<PackageReference Include="CSharpier.MsBuild" PrivateAssets="all" />
|
||||
<PackageReference Include="Deorcify" PrivateAssets="all" />
|
||||
<PackageReference Include="Gress" />
|
||||
<PackageReference Include="PowerKit" PrivateAssets="all" />
|
||||
<PackageReference Include="Spectre.Console" />
|
||||
</ItemGroup>
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
using System.Text.Json;
|
||||
using DiscordChatExporter.Core.Utils.Extensions;
|
||||
using JsonExtensions.Reading;
|
||||
using PowerKit.Extensions;
|
||||
|
||||
namespace DiscordChatExporter.Core.Discord.Data;
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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<EmbedKind>(s, true, out var result) ? result : (EmbedKind?)null
|
||||
)
|
||||
.Pipe(s => Enum.ParseOrNull<EmbedKind>(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();
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
using System.Text.Json;
|
||||
using DiscordChatExporter.Core.Utils.Extensions;
|
||||
using JsonExtensions.Reading;
|
||||
using PowerKit.Extensions;
|
||||
|
||||
namespace DiscordChatExporter.Core.Discord.Data;
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
using System.Text.Json;
|
||||
using DiscordChatExporter.Core.Utils.Extensions;
|
||||
using JsonExtensions.Reading;
|
||||
using PowerKit.Extensions;
|
||||
|
||||
namespace DiscordChatExporter.Core.Discord.Data;
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
using System.Text.Json;
|
||||
using DiscordChatExporter.Core.Utils.Extensions;
|
||||
using PowerKit.Extensions;
|
||||
|
||||
namespace DiscordChatExporter.Core.Discord.Data;
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
<PackageReference Include="Gress" />
|
||||
<PackageReference Include="JsonExtensions" />
|
||||
<PackageReference Include="Polly" />
|
||||
<PackageReference Include="PowerKit" PrivateAssets="all" />
|
||||
<PackageReference Include="RazorBlade" />
|
||||
<PackageReference Include="Superpower" />
|
||||
<PackageReference Include="WebMarkupMin.Core" />
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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<T>(IAsyncEnumerable<T> asyncEnumerable)
|
||||
{
|
||||
private async ValueTask<IReadOnlyList<T>> CollectAsync()
|
||||
{
|
||||
var list = new List<T>();
|
||||
|
||||
await foreach (var i in asyncEnumerable)
|
||||
list.Add(i);
|
||||
|
||||
return list;
|
||||
}
|
||||
|
||||
public ValueTaskAwaiter<IReadOnlyList<T>> GetAwaiter() =>
|
||||
asyncEnumerable.CollectAsync().GetAwaiter();
|
||||
}
|
||||
}
|
||||
@@ -1,40 +0,0 @@
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace DiscordChatExporter.Core.Utils.Extensions;
|
||||
|
||||
public static class CollectionExtensions
|
||||
{
|
||||
extension<T>(T obj)
|
||||
{
|
||||
public IEnumerable<T> ToSingletonEnumerable()
|
||||
{
|
||||
yield return obj;
|
||||
}
|
||||
}
|
||||
|
||||
extension<T>(IEnumerable<T?> source)
|
||||
where T : class
|
||||
{
|
||||
public IEnumerable<T> WhereNotNull()
|
||||
{
|
||||
foreach (var o in source)
|
||||
{
|
||||
if (o is not null)
|
||||
yield return o;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension<T>(IEnumerable<T?> source)
|
||||
where T : struct
|
||||
{
|
||||
public IEnumerable<T> WhereNotNull()
|
||||
{
|
||||
foreach (var o in source)
|
||||
{
|
||||
if (o is not null)
|
||||
yield return o.Value;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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}";
|
||||
}
|
||||
}
|
||||
@@ -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<Exception> 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<Exception> GetSelfAndChildren()
|
||||
{
|
||||
var children = new List<Exception> { exception };
|
||||
PopulateChildren(exception, children);
|
||||
return children;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,21 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace DiscordChatExporter.Core.Utils.Extensions;
|
||||
|
||||
public static class GenericExtensions
|
||||
{
|
||||
extension<TIn>(TIn input)
|
||||
{
|
||||
public TOut Pipe<TOut>(Func<TIn, TOut> transform) => transform(input);
|
||||
}
|
||||
|
||||
extension<T>(T value)
|
||||
where T : struct
|
||||
{
|
||||
public T? NullIf(Func<T, bool> predicate) => !predicate(value) ? value : null;
|
||||
|
||||
public T? NullIfDefault() =>
|
||||
value.NullIf(v => EqualityComparer<T>.Default.Equals(v, default));
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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<SettingsService>();
|
||||
|
||||
// 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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -41,6 +41,7 @@
|
||||
<PackageReference Include="Material.Icons.Avalonia" />
|
||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection" />
|
||||
<PackageReference Include="Onova" />
|
||||
<PackageReference Include="PowerKit" PrivateAssets="all" />
|
||||
<PackageReference Include="ThisAssembly.Project" PrivateAssets="all" />
|
||||
</ItemGroup>
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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<IDisposable> _items = [];
|
||||
|
||||
public void Add(IDisposable item)
|
||||
{
|
||||
lock (_lock)
|
||||
{
|
||||
_items.Add(item);
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
lock (_lock)
|
||||
{
|
||||
_items.DisposeAll();
|
||||
_items.Clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,31 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace DiscordChatExporter.Gui.Utils.Extensions;
|
||||
|
||||
internal static class DisposableExtensions
|
||||
{
|
||||
extension(IEnumerable<IDisposable> disposables)
|
||||
{
|
||||
public void DisposeAll()
|
||||
{
|
||||
var exceptions = default(List<Exception>);
|
||||
|
||||
foreach (var disposable in disposables)
|
||||
{
|
||||
try
|
||||
{
|
||||
disposable.Dispose();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
(exceptions ??= []).Add(ex);
|
||||
}
|
||||
}
|
||||
|
||||
if (exceptions?.Any() == true)
|
||||
throw new AggregateException(exceptions);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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>(TOwner owner)
|
||||
where TOwner : INotifyPropertyChanged
|
||||
{
|
||||
public IDisposable WatchProperty<TProperty>(
|
||||
Expression<Func<TOwner, TProperty>> propertyExpression,
|
||||
Action<TProperty> 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user