mirror of
https://github.com/Tyrrrz/DiscordChatExporter.git
synced 2026-02-15 00:03:38 -07:00
C#10ify
This commit is contained in:
parent
8e7baee8a5
commit
880f400e2c
|
|
@ -14,10 +14,10 @@ using DiscordChatExporter.Core.Discord;
|
||||||
using DiscordChatExporter.Core.Exporting;
|
using DiscordChatExporter.Core.Exporting;
|
||||||
using JsonExtensions;
|
using JsonExtensions;
|
||||||
|
|
||||||
namespace DiscordChatExporter.Cli.Tests.Fixtures
|
namespace DiscordChatExporter.Cli.Tests.Fixtures;
|
||||||
|
|
||||||
|
public class ExportWrapperFixture : IDisposable
|
||||||
{
|
{
|
||||||
public class ExportWrapperFixture : IDisposable
|
|
||||||
{
|
|
||||||
private string DirPath { get; } = Path.Combine(
|
private string DirPath { get; } = Path.Combine(
|
||||||
Path.GetDirectoryName(typeof(ExportWrapperFixture).Assembly.Location) ?? Directory.GetCurrentDirectory(),
|
Path.GetDirectoryName(typeof(ExportWrapperFixture).Assembly.Location) ?? Directory.GetCurrentDirectory(),
|
||||||
"ExportCache",
|
"ExportCache",
|
||||||
|
|
@ -128,5 +128,4 @@ namespace DiscordChatExporter.Cli.Tests.Fixtures
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Dispose() => DirectoryEx.DeleteIfExists(DirPath);
|
public void Dispose() => DirectoryEx.DeleteIfExists(DirPath);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
@ -2,10 +2,10 @@
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using DiscordChatExporter.Cli.Tests.Utils;
|
using DiscordChatExporter.Cli.Tests.Utils;
|
||||||
|
|
||||||
namespace DiscordChatExporter.Cli.Tests.Fixtures
|
namespace DiscordChatExporter.Cli.Tests.Fixtures;
|
||||||
|
|
||||||
|
public class TempOutputFixture : IDisposable
|
||||||
{
|
{
|
||||||
public class TempOutputFixture : IDisposable
|
|
||||||
{
|
|
||||||
public string DirPath { get; } = Path.Combine(
|
public string DirPath { get; } = Path.Combine(
|
||||||
Path.GetDirectoryName(typeof(TempOutputFixture).Assembly.Location) ?? Directory.GetCurrentDirectory(),
|
Path.GetDirectoryName(typeof(TempOutputFixture).Assembly.Location) ?? Directory.GetCurrentDirectory(),
|
||||||
"Temp",
|
"Temp",
|
||||||
|
|
@ -19,5 +19,4 @@ namespace DiscordChatExporter.Cli.Tests.Fixtures
|
||||||
public string GetTempFilePath() => GetTempFilePath(Guid.NewGuid() + ".tmp");
|
public string GetTempFilePath() => GetTempFilePath(Guid.NewGuid() + ".tmp");
|
||||||
|
|
||||||
public void Dispose() => DirectoryEx.DeleteIfExists(DirPath);
|
public void Dispose() => DirectoryEx.DeleteIfExists(DirPath);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
@ -1,10 +1,10 @@
|
||||||
using System;
|
using System;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
|
|
||||||
namespace DiscordChatExporter.Cli.Tests.Infra
|
namespace DiscordChatExporter.Cli.Tests.Infra;
|
||||||
|
|
||||||
|
internal static class Secrets
|
||||||
{
|
{
|
||||||
internal static class Secrets
|
|
||||||
{
|
|
||||||
private static readonly Lazy<string> DiscordTokenLazy = new(() =>
|
private static readonly Lazy<string> DiscordTokenLazy = new(() =>
|
||||||
{
|
{
|
||||||
var fromEnvironment = Environment.GetEnvironmentVariable("DISCORD_TOKEN");
|
var fromEnvironment = Environment.GetEnvironmentVariable("DISCORD_TOKEN");
|
||||||
|
|
@ -42,5 +42,4 @@ namespace DiscordChatExporter.Cli.Tests.Infra
|
||||||
public static string DiscordToken => DiscordTokenLazy.Value;
|
public static string DiscordToken => DiscordTokenLazy.Value;
|
||||||
|
|
||||||
public static bool IsDiscordTokenBot => IsDiscordTokenBotLazy.Value;
|
public static bool IsDiscordTokenBot => IsDiscordTokenBotLazy.Value;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
@ -4,10 +4,10 @@ using DiscordChatExporter.Cli.Tests.TestData;
|
||||||
using FluentAssertions;
|
using FluentAssertions;
|
||||||
using Xunit;
|
using Xunit;
|
||||||
|
|
||||||
namespace DiscordChatExporter.Cli.Tests.Specs.CsvWriting
|
namespace DiscordChatExporter.Cli.Tests.Specs.CsvWriting;
|
||||||
|
|
||||||
|
public record ContentSpecs(ExportWrapperFixture ExportWrapper) : IClassFixture<ExportWrapperFixture>
|
||||||
{
|
{
|
||||||
public record ContentSpecs(ExportWrapperFixture ExportWrapper) : IClassFixture<ExportWrapperFixture>
|
|
||||||
{
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public async Task Messages_are_exported_correctly()
|
public async Task Messages_are_exported_correctly()
|
||||||
{
|
{
|
||||||
|
|
@ -27,5 +27,4 @@ namespace DiscordChatExporter.Cli.Tests.Specs.CsvWriting
|
||||||
"Yeet"
|
"Yeet"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
@ -13,10 +13,10 @@ using FluentAssertions;
|
||||||
using JsonExtensions;
|
using JsonExtensions;
|
||||||
using Xunit;
|
using Xunit;
|
||||||
|
|
||||||
namespace DiscordChatExporter.Cli.Tests.Specs
|
namespace DiscordChatExporter.Cli.Tests.Specs;
|
||||||
|
|
||||||
|
public record DateRangeSpecs(TempOutputFixture TempOutput) : IClassFixture<TempOutputFixture>
|
||||||
{
|
{
|
||||||
public record DateRangeSpecs(TempOutputFixture TempOutput) : IClassFixture<TempOutputFixture>
|
|
||||||
{
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public async Task Messages_filtered_after_specific_date_only_include_messages_sent_after_that_date()
|
public async Task Messages_filtered_after_specific_date_only_include_messages_sent_after_that_date()
|
||||||
{
|
{
|
||||||
|
|
@ -156,5 +156,4 @@ namespace DiscordChatExporter.Cli.Tests.Specs
|
||||||
.WhenTypeIs<DateTimeOffset>();
|
.WhenTypeIs<DateTimeOffset>();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
@ -12,10 +12,10 @@ using FluentAssertions;
|
||||||
using JsonExtensions;
|
using JsonExtensions;
|
||||||
using Xunit;
|
using Xunit;
|
||||||
|
|
||||||
namespace DiscordChatExporter.Cli.Tests.Specs
|
namespace DiscordChatExporter.Cli.Tests.Specs;
|
||||||
|
|
||||||
|
public record FilterSpecs(TempOutputFixture TempOutput) : IClassFixture<TempOutputFixture>
|
||||||
{
|
{
|
||||||
public record FilterSpecs(TempOutputFixture TempOutput) : IClassFixture<TempOutputFixture>
|
|
||||||
{
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public async Task Messages_filtered_by_text_only_include_messages_that_contain_that_text()
|
public async Task Messages_filtered_by_text_only_include_messages_that_contain_that_text()
|
||||||
{
|
{
|
||||||
|
|
@ -131,5 +131,4 @@ namespace DiscordChatExporter.Cli.Tests.Specs
|
||||||
.Should()
|
.Should()
|
||||||
.ContainSingle("This has mention");
|
.ContainSingle("This has mention");
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
@ -6,10 +6,10 @@ using DiscordChatExporter.Core.Discord;
|
||||||
using FluentAssertions;
|
using FluentAssertions;
|
||||||
using Xunit;
|
using Xunit;
|
||||||
|
|
||||||
namespace DiscordChatExporter.Cli.Tests.Specs.HtmlWriting
|
namespace DiscordChatExporter.Cli.Tests.Specs.HtmlWriting;
|
||||||
|
|
||||||
|
public record AttachmentSpecs(ExportWrapperFixture ExportWrapper) : IClassFixture<ExportWrapperFixture>
|
||||||
{
|
{
|
||||||
public record AttachmentSpecs(ExportWrapperFixture ExportWrapper) : IClassFixture<ExportWrapperFixture>
|
|
||||||
{
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public async Task Message_with_a_generic_attachment_is_rendered_correctly()
|
public async Task Message_with_a_generic_attachment_is_rendered_correctly()
|
||||||
{
|
{
|
||||||
|
|
@ -89,5 +89,4 @@ namespace DiscordChatExporter.Cli.Tests.Specs.HtmlWriting
|
||||||
"https://cdn.discordapp.com/attachments/885587741654536192/885656175348187146/file_example_MP3_1MG.mp3"
|
"https://cdn.discordapp.com/attachments/885587741654536192/885656175348187146/file_example_MP3_1MG.mp3"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
@ -6,10 +6,10 @@ using DiscordChatExporter.Cli.Tests.TestData;
|
||||||
using FluentAssertions;
|
using FluentAssertions;
|
||||||
using Xunit;
|
using Xunit;
|
||||||
|
|
||||||
namespace DiscordChatExporter.Cli.Tests.Specs.HtmlWriting
|
namespace DiscordChatExporter.Cli.Tests.Specs.HtmlWriting;
|
||||||
|
|
||||||
|
public record ContentSpecs(ExportWrapperFixture ExportWrapper) : IClassFixture<ExportWrapperFixture>
|
||||||
{
|
{
|
||||||
public record ContentSpecs(ExportWrapperFixture ExportWrapper) : IClassFixture<ExportWrapperFixture>
|
|
||||||
{
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public async Task Messages_are_exported_correctly()
|
public async Task Messages_are_exported_correctly()
|
||||||
{
|
{
|
||||||
|
|
@ -39,5 +39,4 @@ namespace DiscordChatExporter.Cli.Tests.Specs.HtmlWriting
|
||||||
"Yeet"
|
"Yeet"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
@ -6,10 +6,10 @@ using DiscordChatExporter.Core.Discord;
|
||||||
using FluentAssertions;
|
using FluentAssertions;
|
||||||
using Xunit;
|
using Xunit;
|
||||||
|
|
||||||
namespace DiscordChatExporter.Cli.Tests.Specs.HtmlWriting
|
namespace DiscordChatExporter.Cli.Tests.Specs.HtmlWriting;
|
||||||
|
|
||||||
|
public record EmbedSpecs(ExportWrapperFixture ExportWrapper) : IClassFixture<ExportWrapperFixture>
|
||||||
{
|
{
|
||||||
public record EmbedSpecs(ExportWrapperFixture ExportWrapper) : IClassFixture<ExportWrapperFixture>
|
|
||||||
{
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public async Task Message_with_an_embed_is_rendered_correctly()
|
public async Task Message_with_an_embed_is_rendered_correctly()
|
||||||
{
|
{
|
||||||
|
|
@ -60,5 +60,4 @@ namespace DiscordChatExporter.Cli.Tests.Specs.HtmlWriting
|
||||||
// Assert
|
// Assert
|
||||||
iframeSrc.Should().StartWithEquivalentOf("https://www.youtube.com/embed/qOWW4OlgbvE");
|
iframeSrc.Should().StartWithEquivalentOf("https://www.youtube.com/embed/qOWW4OlgbvE");
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
@ -6,10 +6,10 @@ using DiscordChatExporter.Core.Discord;
|
||||||
using FluentAssertions;
|
using FluentAssertions;
|
||||||
using Xunit;
|
using Xunit;
|
||||||
|
|
||||||
namespace DiscordChatExporter.Cli.Tests.Specs.HtmlWriting
|
namespace DiscordChatExporter.Cli.Tests.Specs.HtmlWriting;
|
||||||
|
|
||||||
|
public record MentionSpecs(ExportWrapperFixture ExportWrapper) : IClassFixture<ExportWrapperFixture>
|
||||||
{
|
{
|
||||||
public record MentionSpecs(ExportWrapperFixture ExportWrapper) : IClassFixture<ExportWrapperFixture>
|
|
||||||
{
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public async Task User_mention_is_rendered_correctly()
|
public async Task User_mention_is_rendered_correctly()
|
||||||
{
|
{
|
||||||
|
|
@ -62,5 +62,4 @@ namespace DiscordChatExporter.Cli.Tests.Specs.HtmlWriting
|
||||||
// Assert
|
// Assert
|
||||||
message.Text().Trim().Should().Be("Role mention: @Role 1");
|
message.Text().Trim().Should().Be("Role mention: @Role 1");
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
@ -6,10 +6,10 @@ using DiscordChatExporter.Core.Discord;
|
||||||
using FluentAssertions;
|
using FluentAssertions;
|
||||||
using Xunit;
|
using Xunit;
|
||||||
|
|
||||||
namespace DiscordChatExporter.Cli.Tests.Specs.HtmlWriting
|
namespace DiscordChatExporter.Cli.Tests.Specs.HtmlWriting;
|
||||||
|
|
||||||
|
public record ReplySpecs(ExportWrapperFixture ExportWrapper) : IClassFixture<ExportWrapperFixture>
|
||||||
{
|
{
|
||||||
public record ReplySpecs(ExportWrapperFixture ExportWrapper) : IClassFixture<ExportWrapperFixture>
|
|
||||||
{
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public async Task Reply_to_a_normal_message_is_rendered_correctly()
|
public async Task Reply_to_a_normal_message_is_rendered_correctly()
|
||||||
{
|
{
|
||||||
|
|
@ -53,5 +53,4 @@ namespace DiscordChatExporter.Cli.Tests.Specs.HtmlWriting
|
||||||
message.QuerySelector(".chatlog__reference-link")?.Text().Trim().Should()
|
message.QuerySelector(".chatlog__reference-link")?.Text().Trim().Should()
|
||||||
.Be("Click to see attachment 🖼️");
|
.Be("Click to see attachment 🖼️");
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
@ -6,10 +6,10 @@ using DiscordChatExporter.Core.Discord;
|
||||||
using FluentAssertions;
|
using FluentAssertions;
|
||||||
using Xunit;
|
using Xunit;
|
||||||
|
|
||||||
namespace DiscordChatExporter.Cli.Tests.Specs.JsonWriting
|
namespace DiscordChatExporter.Cli.Tests.Specs.JsonWriting;
|
||||||
|
|
||||||
|
public record AttachmentSpecs(ExportWrapperFixture ExportWrapper) : IClassFixture<ExportWrapperFixture>
|
||||||
{
|
{
|
||||||
public record AttachmentSpecs(ExportWrapperFixture ExportWrapper) : IClassFixture<ExportWrapperFixture>
|
|
||||||
{
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public async Task Message_with_a_generic_attachment_is_rendered_correctly()
|
public async Task Message_with_a_generic_attachment_is_rendered_correctly()
|
||||||
{
|
{
|
||||||
|
|
@ -97,5 +97,4 @@ namespace DiscordChatExporter.Cli.Tests.Specs.JsonWriting
|
||||||
attachments.Single().GetProperty("fileName").GetString().Should().Be("file_example_MP3_1MG.mp3");
|
attachments.Single().GetProperty("fileName").GetString().Should().Be("file_example_MP3_1MG.mp3");
|
||||||
attachments.Single().GetProperty("fileSizeBytes").GetInt64().Should().Be(1087849);
|
attachments.Single().GetProperty("fileSizeBytes").GetInt64().Should().Be(1087849);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
@ -5,10 +5,10 @@ using DiscordChatExporter.Cli.Tests.TestData;
|
||||||
using FluentAssertions;
|
using FluentAssertions;
|
||||||
using Xunit;
|
using Xunit;
|
||||||
|
|
||||||
namespace DiscordChatExporter.Cli.Tests.Specs.JsonWriting
|
namespace DiscordChatExporter.Cli.Tests.Specs.JsonWriting;
|
||||||
|
|
||||||
|
public record ContentSpecs(ExportWrapperFixture ExportWrapper) : IClassFixture<ExportWrapperFixture>
|
||||||
{
|
{
|
||||||
public record ContentSpecs(ExportWrapperFixture ExportWrapper) : IClassFixture<ExportWrapperFixture>
|
|
||||||
{
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public async Task Messages_are_exported_correctly()
|
public async Task Messages_are_exported_correctly()
|
||||||
{
|
{
|
||||||
|
|
@ -38,5 +38,4 @@ namespace DiscordChatExporter.Cli.Tests.Specs.JsonWriting
|
||||||
"Yeet"
|
"Yeet"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
@ -6,10 +6,10 @@ using DiscordChatExporter.Core.Discord;
|
||||||
using FluentAssertions;
|
using FluentAssertions;
|
||||||
using Xunit;
|
using Xunit;
|
||||||
|
|
||||||
namespace DiscordChatExporter.Cli.Tests.Specs.JsonWriting
|
namespace DiscordChatExporter.Cli.Tests.Specs.JsonWriting;
|
||||||
|
|
||||||
|
public record EmbedSpecs(ExportWrapperFixture ExportWrapper) : IClassFixture<ExportWrapperFixture>
|
||||||
{
|
{
|
||||||
public record EmbedSpecs(ExportWrapperFixture ExportWrapper) : IClassFixture<ExportWrapperFixture>
|
|
||||||
{
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public async Task Message_with_an_embed_is_rendered_correctly()
|
public async Task Message_with_an_embed_is_rendered_correctly()
|
||||||
{
|
{
|
||||||
|
|
@ -54,5 +54,4 @@ namespace DiscordChatExporter.Cli.Tests.Specs.JsonWriting
|
||||||
embedFields[2].GetProperty("value").GetString().Should().Be("Value 3");
|
embedFields[2].GetProperty("value").GetString().Should().Be("Value 3");
|
||||||
embedFields[2].GetProperty("isInline").GetBoolean().Should().BeTrue();
|
embedFields[2].GetProperty("isInline").GetBoolean().Should().BeTrue();
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
@ -6,10 +6,10 @@ using DiscordChatExporter.Core.Discord;
|
||||||
using FluentAssertions;
|
using FluentAssertions;
|
||||||
using Xunit;
|
using Xunit;
|
||||||
|
|
||||||
namespace DiscordChatExporter.Cli.Tests.Specs.JsonWriting
|
namespace DiscordChatExporter.Cli.Tests.Specs.JsonWriting;
|
||||||
|
|
||||||
|
public record MentionSpecs(ExportWrapperFixture ExportWrapper) : IClassFixture<ExportWrapperFixture>
|
||||||
{
|
{
|
||||||
public record MentionSpecs(ExportWrapperFixture ExportWrapper) : IClassFixture<ExportWrapperFixture>
|
|
||||||
{
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public async Task User_mention_is_rendered_correctly()
|
public async Task User_mention_is_rendered_correctly()
|
||||||
{
|
{
|
||||||
|
|
@ -67,5 +67,4 @@ namespace DiscordChatExporter.Cli.Tests.Specs.JsonWriting
|
||||||
// Assert
|
// Assert
|
||||||
message.GetProperty("content").GetString().Should().Be("Role mention: @Role 1");
|
message.GetProperty("content").GetString().Should().Be("Role mention: @Role 1");
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
@ -10,10 +10,10 @@ using DiscordChatExporter.Core.Exporting.Partitioning;
|
||||||
using FluentAssertions;
|
using FluentAssertions;
|
||||||
using Xunit;
|
using Xunit;
|
||||||
|
|
||||||
namespace DiscordChatExporter.Cli.Tests.Specs
|
namespace DiscordChatExporter.Cli.Tests.Specs;
|
||||||
|
|
||||||
|
public record PartitioningSpecs(TempOutputFixture TempOutput) : IClassFixture<TempOutputFixture>
|
||||||
{
|
{
|
||||||
public record PartitioningSpecs(TempOutputFixture TempOutput) : IClassFixture<TempOutputFixture>
|
|
||||||
{
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public async Task Messages_partitioned_by_count_are_split_into_multiple_files_correctly()
|
public async Task Messages_partitioned_by_count_are_split_into_multiple_files_correctly()
|
||||||
{
|
{
|
||||||
|
|
@ -63,5 +63,4 @@ namespace DiscordChatExporter.Cli.Tests.Specs
|
||||||
.Should()
|
.Should()
|
||||||
.HaveCount(2);
|
.HaveCount(2);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
@ -4,10 +4,10 @@ using DiscordChatExporter.Cli.Tests.TestData;
|
||||||
using FluentAssertions;
|
using FluentAssertions;
|
||||||
using Xunit;
|
using Xunit;
|
||||||
|
|
||||||
namespace DiscordChatExporter.Cli.Tests.Specs.PlainTextWriting
|
namespace DiscordChatExporter.Cli.Tests.Specs.PlainTextWriting;
|
||||||
|
|
||||||
|
public record ContentSpecs(ExportWrapperFixture ExportWrapper) : IClassFixture<ExportWrapperFixture>
|
||||||
{
|
{
|
||||||
public record ContentSpecs(ExportWrapperFixture ExportWrapper) : IClassFixture<ExportWrapperFixture>
|
|
||||||
{
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public async Task Messages_are_exported_correctly()
|
public async Task Messages_are_exported_correctly()
|
||||||
{
|
{
|
||||||
|
|
@ -27,5 +27,4 @@ namespace DiscordChatExporter.Cli.Tests.Specs.PlainTextWriting
|
||||||
"Yeet"
|
"Yeet"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
@ -11,10 +11,10 @@ using DiscordChatExporter.Core.Exporting;
|
||||||
using FluentAssertions;
|
using FluentAssertions;
|
||||||
using Xunit;
|
using Xunit;
|
||||||
|
|
||||||
namespace DiscordChatExporter.Cli.Tests.Specs
|
namespace DiscordChatExporter.Cli.Tests.Specs;
|
||||||
|
|
||||||
|
public record SelfContainedSpecs(TempOutputFixture TempOutput) : IClassFixture<TempOutputFixture>
|
||||||
{
|
{
|
||||||
public record SelfContainedSpecs(TempOutputFixture TempOutput) : IClassFixture<TempOutputFixture>
|
|
||||||
{
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public async Task Messages_in_self_contained_export_only_reference_local_file_resources()
|
public async Task Messages_in_self_contained_export_only_reference_local_file_resources()
|
||||||
{
|
{
|
||||||
|
|
@ -45,5 +45,4 @@ namespace DiscordChatExporter.Cli.Tests.Specs
|
||||||
.Should()
|
.Should()
|
||||||
.BeTrue();
|
.BeTrue();
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
@ -1,9 +1,9 @@
|
||||||
using DiscordChatExporter.Core.Discord;
|
using DiscordChatExporter.Core.Discord;
|
||||||
|
|
||||||
namespace DiscordChatExporter.Cli.Tests.TestData
|
namespace DiscordChatExporter.Cli.Tests.TestData;
|
||||||
|
|
||||||
|
public static class ChannelIds
|
||||||
{
|
{
|
||||||
public static class ChannelIds
|
|
||||||
{
|
|
||||||
public static Snowflake AttachmentTestCases { get; } = Snowflake.Parse("885587741654536192");
|
public static Snowflake AttachmentTestCases { get; } = Snowflake.Parse("885587741654536192");
|
||||||
|
|
||||||
public static Snowflake DateRangeTestCases { get; } = Snowflake.Parse("866674248747319326");
|
public static Snowflake DateRangeTestCases { get; } = Snowflake.Parse("866674248747319326");
|
||||||
|
|
@ -17,5 +17,4 @@ namespace DiscordChatExporter.Cli.Tests.TestData
|
||||||
public static Snowflake ReplyTestCases { get; } = Snowflake.Parse("866459871934677052");
|
public static Snowflake ReplyTestCases { get; } = Snowflake.Parse("866459871934677052");
|
||||||
|
|
||||||
public static Snowflake SelfContainedTestCases { get; } = Snowflake.Parse("887441432678379560");
|
public static Snowflake SelfContainedTestCases { get; } = Snowflake.Parse("887441432678379560");
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
@ -1,9 +1,9 @@
|
||||||
using System.IO;
|
using System.IO;
|
||||||
|
|
||||||
namespace DiscordChatExporter.Cli.Tests.Utils
|
namespace DiscordChatExporter.Cli.Tests.Utils;
|
||||||
|
|
||||||
|
internal static class DirectoryEx
|
||||||
{
|
{
|
||||||
internal static class DirectoryEx
|
|
||||||
{
|
|
||||||
public static void DeleteIfExists(string dirPath, bool recursive = true)
|
public static void DeleteIfExists(string dirPath, bool recursive = true)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
|
|
@ -20,5 +20,4 @@ namespace DiscordChatExporter.Cli.Tests.Utils
|
||||||
DeleteIfExists(dirPath);
|
DeleteIfExists(dirPath);
|
||||||
Directory.CreateDirectory(dirPath);
|
Directory.CreateDirectory(dirPath);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
@ -1,12 +1,11 @@
|
||||||
using AngleSharp.Html.Dom;
|
using AngleSharp.Html.Dom;
|
||||||
using AngleSharp.Html.Parser;
|
using AngleSharp.Html.Parser;
|
||||||
|
|
||||||
namespace DiscordChatExporter.Cli.Tests.Utils
|
namespace DiscordChatExporter.Cli.Tests.Utils;
|
||||||
|
|
||||||
|
internal static class Html
|
||||||
{
|
{
|
||||||
internal static class Html
|
|
||||||
{
|
|
||||||
private static readonly IHtmlParser Parser = new HtmlParser();
|
private static readonly IHtmlParser Parser = new HtmlParser();
|
||||||
|
|
||||||
public static IHtmlDocument Parse(string source) => Parser.ParseDocument(source);
|
public static IHtmlDocument Parse(string source) => Parser.ParseDocument(source);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
@ -16,10 +16,10 @@ using DiscordChatExporter.Core.Exporting.Filtering;
|
||||||
using DiscordChatExporter.Core.Exporting.Partitioning;
|
using DiscordChatExporter.Core.Exporting.Partitioning;
|
||||||
using DiscordChatExporter.Core.Utils.Extensions;
|
using DiscordChatExporter.Core.Utils.Extensions;
|
||||||
|
|
||||||
namespace DiscordChatExporter.Cli.Commands.Base
|
namespace DiscordChatExporter.Cli.Commands.Base;
|
||||||
|
|
||||||
|
public abstract class ExportCommandBase : TokenCommandBase
|
||||||
{
|
{
|
||||||
public abstract class ExportCommandBase : TokenCommandBase
|
|
||||||
{
|
|
||||||
[CommandOption("output", 'o', Description = "Output file or directory path.")]
|
[CommandOption("output", 'o', Description = "Output file or directory path.")]
|
||||||
public string OutputPath { get; init; } = Directory.GetCurrentDirectory();
|
public string OutputPath { get; init; } = Directory.GetCurrentDirectory();
|
||||||
|
|
||||||
|
|
@ -152,5 +152,4 @@ namespace DiscordChatExporter.Cli.Commands.Base
|
||||||
|
|
||||||
await ExecuteAsync(console, channels);
|
await ExecuteAsync(console, channels);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
@ -4,10 +4,10 @@ using CliFx.Attributes;
|
||||||
using CliFx.Infrastructure;
|
using CliFx.Infrastructure;
|
||||||
using DiscordChatExporter.Core.Discord;
|
using DiscordChatExporter.Core.Discord;
|
||||||
|
|
||||||
namespace DiscordChatExporter.Cli.Commands.Base
|
namespace DiscordChatExporter.Cli.Commands.Base;
|
||||||
|
|
||||||
|
public abstract class TokenCommandBase : ICommand
|
||||||
{
|
{
|
||||||
public abstract class TokenCommandBase : ICommand
|
|
||||||
{
|
|
||||||
[CommandOption("token", 't', IsRequired = true, EnvironmentVariable = "DISCORD_TOKEN", Description = "Authentication token.")]
|
[CommandOption("token", 't', IsRequired = true, EnvironmentVariable = "DISCORD_TOKEN", Description = "Authentication token.")]
|
||||||
public string TokenValue { get; init; } = "";
|
public string TokenValue { get; init; } = "";
|
||||||
|
|
||||||
|
|
@ -26,5 +26,4 @@ namespace DiscordChatExporter.Cli.Commands.Base
|
||||||
protected DiscordClient Discord => _discordClient ??= new DiscordClient(AuthToken);
|
protected DiscordClient Discord => _discordClient ??= new DiscordClient(AuthToken);
|
||||||
|
|
||||||
public abstract ValueTask ExecuteAsync(IConsole console);
|
public abstract ValueTask ExecuteAsync(IConsole console);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
@ -5,11 +5,11 @@ using CliFx.Infrastructure;
|
||||||
using DiscordChatExporter.Cli.Commands.Base;
|
using DiscordChatExporter.Cli.Commands.Base;
|
||||||
using DiscordChatExporter.Core.Discord.Data;
|
using DiscordChatExporter.Core.Discord.Data;
|
||||||
|
|
||||||
namespace DiscordChatExporter.Cli.Commands
|
namespace DiscordChatExporter.Cli.Commands;
|
||||||
|
|
||||||
|
[Command("exportall", Description = "Export all accessible channels.")]
|
||||||
|
public class ExportAllCommand : ExportCommandBase
|
||||||
{
|
{
|
||||||
[Command("exportall", Description = "Export all accessible channels.")]
|
|
||||||
public class ExportAllCommand : ExportCommandBase
|
|
||||||
{
|
|
||||||
[CommandOption("include-dm", Description = "Include direct message channels.")]
|
[CommandOption("include-dm", Description = "Include direct message channels.")]
|
||||||
public bool IncludeDirectMessages { get; init; } = true;
|
public bool IncludeDirectMessages { get; init; } = true;
|
||||||
|
|
||||||
|
|
@ -37,5 +37,4 @@ namespace DiscordChatExporter.Cli.Commands
|
||||||
|
|
||||||
await base.ExecuteAsync(console, channels);
|
await base.ExecuteAsync(console, channels);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
@ -6,16 +6,15 @@ using CliFx.Infrastructure;
|
||||||
using DiscordChatExporter.Cli.Commands.Base;
|
using DiscordChatExporter.Cli.Commands.Base;
|
||||||
using DiscordChatExporter.Core.Discord;
|
using DiscordChatExporter.Core.Discord;
|
||||||
|
|
||||||
namespace DiscordChatExporter.Cli.Commands
|
namespace DiscordChatExporter.Cli.Commands;
|
||||||
|
|
||||||
|
[Command("export", Description = "Export one or multiple channels.")]
|
||||||
|
public class ExportChannelsCommand : ExportCommandBase
|
||||||
{
|
{
|
||||||
[Command("export", Description = "Export one or multiple channels.")]
|
|
||||||
public class ExportChannelsCommand : ExportCommandBase
|
|
||||||
{
|
|
||||||
// TODO: change this to plural (breaking change)
|
// TODO: change this to plural (breaking change)
|
||||||
[CommandOption("channel", 'c', IsRequired = true, Description = "Channel ID(s).")]
|
[CommandOption("channel", 'c', IsRequired = true, Description = "Channel ID(s).")]
|
||||||
public IReadOnlyList<Snowflake> ChannelIds { get; init; } = Array.Empty<Snowflake>();
|
public IReadOnlyList<Snowflake> ChannelIds { get; init; } = Array.Empty<Snowflake>();
|
||||||
|
|
||||||
public override async ValueTask ExecuteAsync(IConsole console) =>
|
public override async ValueTask ExecuteAsync(IConsole console) =>
|
||||||
await base.ExecuteAsync(console, ChannelIds);
|
await base.ExecuteAsync(console, ChannelIds);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
@ -6,11 +6,11 @@ using DiscordChatExporter.Cli.Commands.Base;
|
||||||
using DiscordChatExporter.Core.Discord.Data;
|
using DiscordChatExporter.Core.Discord.Data;
|
||||||
using DiscordChatExporter.Core.Utils.Extensions;
|
using DiscordChatExporter.Core.Utils.Extensions;
|
||||||
|
|
||||||
namespace DiscordChatExporter.Cli.Commands
|
namespace DiscordChatExporter.Cli.Commands;
|
||||||
|
|
||||||
|
[Command("exportdm", Description = "Export all direct message channels.")]
|
||||||
|
public class ExportDirectMessagesCommand : ExportCommandBase
|
||||||
{
|
{
|
||||||
[Command("exportdm", Description = "Export all direct message channels.")]
|
|
||||||
public class ExportDirectMessagesCommand : ExportCommandBase
|
|
||||||
{
|
|
||||||
public override async ValueTask ExecuteAsync(IConsole console)
|
public override async ValueTask ExecuteAsync(IConsole console)
|
||||||
{
|
{
|
||||||
var cancellationToken = console.RegisterCancellationHandler();
|
var cancellationToken = console.RegisterCancellationHandler();
|
||||||
|
|
@ -21,5 +21,4 @@ namespace DiscordChatExporter.Cli.Commands
|
||||||
|
|
||||||
await base.ExecuteAsync(console, textChannels);
|
await base.ExecuteAsync(console, textChannels);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
@ -6,11 +6,11 @@ using DiscordChatExporter.Cli.Commands.Base;
|
||||||
using DiscordChatExporter.Core.Discord;
|
using DiscordChatExporter.Core.Discord;
|
||||||
using DiscordChatExporter.Core.Utils.Extensions;
|
using DiscordChatExporter.Core.Utils.Extensions;
|
||||||
|
|
||||||
namespace DiscordChatExporter.Cli.Commands
|
namespace DiscordChatExporter.Cli.Commands;
|
||||||
|
|
||||||
|
[Command("exportguild", Description = "Export all channels within specified guild.")]
|
||||||
|
public class ExportGuildCommand : ExportCommandBase
|
||||||
{
|
{
|
||||||
[Command("exportguild", Description = "Export all channels within specified guild.")]
|
|
||||||
public class ExportGuildCommand : ExportCommandBase
|
|
||||||
{
|
|
||||||
[CommandOption("guild", 'g', IsRequired = true, Description = "Guild ID.")]
|
[CommandOption("guild", 'g', IsRequired = true, Description = "Guild ID.")]
|
||||||
public Snowflake GuildId { get; init; }
|
public Snowflake GuildId { get; init; }
|
||||||
|
|
||||||
|
|
@ -24,5 +24,4 @@ namespace DiscordChatExporter.Cli.Commands
|
||||||
|
|
||||||
await base.ExecuteAsync(console, textChannels);
|
await base.ExecuteAsync(console, textChannels);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
@ -7,11 +7,11 @@ using DiscordChatExporter.Cli.Commands.Base;
|
||||||
using DiscordChatExporter.Core.Discord;
|
using DiscordChatExporter.Core.Discord;
|
||||||
using DiscordChatExporter.Core.Utils.Extensions;
|
using DiscordChatExporter.Core.Utils.Extensions;
|
||||||
|
|
||||||
namespace DiscordChatExporter.Cli.Commands
|
namespace DiscordChatExporter.Cli.Commands;
|
||||||
|
|
||||||
|
[Command("channels", Description = "Get the list of channels in a guild.")]
|
||||||
|
public class GetChannelsCommand : TokenCommandBase
|
||||||
{
|
{
|
||||||
[Command("channels", Description = "Get the list of channels in a guild.")]
|
|
||||||
public class GetChannelsCommand : TokenCommandBase
|
|
||||||
{
|
|
||||||
[CommandOption("guild", 'g', IsRequired = true, Description = "Guild ID.")]
|
[CommandOption("guild", 'g', IsRequired = true, Description = "Guild ID.")]
|
||||||
public Snowflake GuildId { get; init; }
|
public Snowflake GuildId { get; init; }
|
||||||
|
|
||||||
|
|
@ -41,5 +41,4 @@ namespace DiscordChatExporter.Cli.Commands
|
||||||
await console.Output.WriteLineAsync($"{channel.Category.Name} / {channel.Name}");
|
await console.Output.WriteLineAsync($"{channel.Category.Name} / {channel.Name}");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
@ -7,11 +7,11 @@ using DiscordChatExporter.Cli.Commands.Base;
|
||||||
using DiscordChatExporter.Core.Discord.Data;
|
using DiscordChatExporter.Core.Discord.Data;
|
||||||
using DiscordChatExporter.Core.Utils.Extensions;
|
using DiscordChatExporter.Core.Utils.Extensions;
|
||||||
|
|
||||||
namespace DiscordChatExporter.Cli.Commands
|
namespace DiscordChatExporter.Cli.Commands;
|
||||||
|
|
||||||
|
[Command("dm", Description = "Get the list of direct message channels.")]
|
||||||
|
public class GetDirectMessageChannelsCommand : TokenCommandBase
|
||||||
{
|
{
|
||||||
[Command("dm", Description = "Get the list of direct message channels.")]
|
|
||||||
public class GetDirectMessageChannelsCommand : TokenCommandBase
|
|
||||||
{
|
|
||||||
public override async ValueTask ExecuteAsync(IConsole console)
|
public override async ValueTask ExecuteAsync(IConsole console)
|
||||||
{
|
{
|
||||||
var cancellationToken = console.RegisterCancellationHandler();
|
var cancellationToken = console.RegisterCancellationHandler();
|
||||||
|
|
@ -38,5 +38,4 @@ namespace DiscordChatExporter.Cli.Commands
|
||||||
await console.Output.WriteLineAsync($"{channel.Category.Name} / {channel.Name}");
|
await console.Output.WriteLineAsync($"{channel.Category.Name} / {channel.Name}");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
@ -6,11 +6,11 @@ using CliFx.Infrastructure;
|
||||||
using DiscordChatExporter.Cli.Commands.Base;
|
using DiscordChatExporter.Cli.Commands.Base;
|
||||||
using DiscordChatExporter.Core.Utils.Extensions;
|
using DiscordChatExporter.Core.Utils.Extensions;
|
||||||
|
|
||||||
namespace DiscordChatExporter.Cli.Commands
|
namespace DiscordChatExporter.Cli.Commands;
|
||||||
|
|
||||||
|
[Command("guilds", Description = "Get the list of accessible guilds.")]
|
||||||
|
public class GetGuildsCommand : TokenCommandBase
|
||||||
{
|
{
|
||||||
[Command("guilds", Description = "Get the list of accessible guilds.")]
|
|
||||||
public class GetGuildsCommand : TokenCommandBase
|
|
||||||
{
|
|
||||||
public override async ValueTask ExecuteAsync(IConsole console)
|
public override async ValueTask ExecuteAsync(IConsole console)
|
||||||
{
|
{
|
||||||
var cancellationToken = console.RegisterCancellationHandler();
|
var cancellationToken = console.RegisterCancellationHandler();
|
||||||
|
|
@ -31,5 +31,4 @@ namespace DiscordChatExporter.Cli.Commands
|
||||||
await console.Output.WriteLineAsync(guild.Name);
|
await console.Output.WriteLineAsync(guild.Name);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
@ -4,11 +4,11 @@ using CliFx;
|
||||||
using CliFx.Attributes;
|
using CliFx.Attributes;
|
||||||
using CliFx.Infrastructure;
|
using CliFx.Infrastructure;
|
||||||
|
|
||||||
namespace DiscordChatExporter.Cli.Commands
|
namespace DiscordChatExporter.Cli.Commands;
|
||||||
|
|
||||||
|
[Command("guide", Description = "Explains how to obtain token, guild or channel ID.")]
|
||||||
|
public class GuideCommand : ICommand
|
||||||
{
|
{
|
||||||
[Command("guide", Description = "Explains how to obtain token, guild or channel ID.")]
|
|
||||||
public class GuideCommand : ICommand
|
|
||||||
{
|
|
||||||
public ValueTask ExecuteAsync(IConsole console)
|
public ValueTask ExecuteAsync(IConsole console)
|
||||||
{
|
{
|
||||||
// User token
|
// User token
|
||||||
|
|
@ -67,5 +67,4 @@ namespace DiscordChatExporter.Cli.Commands
|
||||||
|
|
||||||
return default;
|
return default;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
@ -1,14 +1,6 @@
|
||||||
using System.Threading.Tasks;
|
using CliFx;
|
||||||
using CliFx;
|
|
||||||
|
|
||||||
namespace DiscordChatExporter.Cli
|
return await new CliApplicationBuilder()
|
||||||
{
|
|
||||||
public static class Program
|
|
||||||
{
|
|
||||||
public static async Task<int> Main(string[] args) =>
|
|
||||||
await new CliApplicationBuilder()
|
|
||||||
.AddCommandsFromThisAssembly()
|
.AddCommandsFromThisAssembly()
|
||||||
.Build()
|
.Build()
|
||||||
.RunAsync(args);
|
.RunAsync(args);
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -3,10 +3,10 @@ using System.Threading.Tasks;
|
||||||
using CliFx.Infrastructure;
|
using CliFx.Infrastructure;
|
||||||
using Spectre.Console;
|
using Spectre.Console;
|
||||||
|
|
||||||
namespace DiscordChatExporter.Cli.Utils.Extensions
|
namespace DiscordChatExporter.Cli.Utils.Extensions;
|
||||||
|
|
||||||
|
internal static class ConsoleExtensions
|
||||||
{
|
{
|
||||||
internal static class ConsoleExtensions
|
|
||||||
{
|
|
||||||
public static IAnsiConsole CreateAnsiConsole(this IConsole console) =>
|
public static IAnsiConsole CreateAnsiConsole(this IConsole console) =>
|
||||||
AnsiConsole.Create(new AnsiConsoleSettings
|
AnsiConsole.Create(new AnsiConsoleSettings
|
||||||
{
|
{
|
||||||
|
|
@ -47,5 +47,4 @@ namespace DiscordChatExporter.Cli.Utils.Extensions
|
||||||
progressTask.StopTask();
|
progressTask.StopTask();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
@ -1,13 +1,12 @@
|
||||||
using System.Net.Http.Headers;
|
using System.Net.Http.Headers;
|
||||||
|
|
||||||
namespace DiscordChatExporter.Core.Discord
|
namespace DiscordChatExporter.Core.Discord;
|
||||||
|
|
||||||
|
public record AuthToken(AuthTokenKind Kind, string Value)
|
||||||
{
|
{
|
||||||
public record AuthToken(AuthTokenKind Kind, string Value)
|
|
||||||
{
|
|
||||||
public AuthenticationHeaderValue GetAuthenticationHeader() => Kind switch
|
public AuthenticationHeaderValue GetAuthenticationHeader() => Kind switch
|
||||||
{
|
{
|
||||||
AuthTokenKind.Bot => new AuthenticationHeaderValue("Bot", Value),
|
AuthTokenKind.Bot => new AuthenticationHeaderValue("Bot", Value),
|
||||||
_ => new AuthenticationHeaderValue(Value)
|
_ => new AuthenticationHeaderValue(Value)
|
||||||
};
|
};
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
@ -1,8 +1,7 @@
|
||||||
namespace DiscordChatExporter.Core.Discord
|
namespace DiscordChatExporter.Core.Discord;
|
||||||
|
|
||||||
|
public enum AuthTokenKind
|
||||||
{
|
{
|
||||||
public enum AuthTokenKind
|
|
||||||
{
|
|
||||||
User,
|
User,
|
||||||
Bot
|
Bot
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
@ -6,17 +6,17 @@ using DiscordChatExporter.Core.Utils;
|
||||||
using DiscordChatExporter.Core.Utils.Extensions;
|
using DiscordChatExporter.Core.Utils.Extensions;
|
||||||
using JsonExtensions.Reading;
|
using JsonExtensions.Reading;
|
||||||
|
|
||||||
namespace DiscordChatExporter.Core.Discord.Data
|
namespace DiscordChatExporter.Core.Discord.Data;
|
||||||
{
|
|
||||||
// https://discord.com/developers/docs/resources/channel#attachment-object
|
// https://discord.com/developers/docs/resources/channel#attachment-object
|
||||||
public partial record Attachment(
|
public partial record Attachment(
|
||||||
Snowflake Id,
|
Snowflake Id,
|
||||||
string Url,
|
string Url,
|
||||||
string FileName,
|
string FileName,
|
||||||
int? Width,
|
int? Width,
|
||||||
int? Height,
|
int? Height,
|
||||||
FileSize FileSize) : IHasId
|
FileSize FileSize) : IHasId
|
||||||
{
|
{
|
||||||
public string FileExtension => Path.GetExtension(FileName);
|
public string FileExtension => Path.GetExtension(FileName);
|
||||||
|
|
||||||
public bool IsImage => FileFormat.IsImage(FileExtension);
|
public bool IsImage => FileFormat.IsImage(FileExtension);
|
||||||
|
|
@ -26,10 +26,10 @@ namespace DiscordChatExporter.Core.Discord.Data
|
||||||
public bool IsAudio => FileFormat.IsAudio(FileExtension);
|
public bool IsAudio => FileFormat.IsAudio(FileExtension);
|
||||||
|
|
||||||
public bool IsSpoiler => FileName.StartsWith("SPOILER_", StringComparison.Ordinal);
|
public bool IsSpoiler => FileName.StartsWith("SPOILER_", StringComparison.Ordinal);
|
||||||
}
|
}
|
||||||
|
|
||||||
public partial record Attachment
|
public partial record Attachment
|
||||||
{
|
{
|
||||||
public static Attachment Parse(JsonElement json)
|
public static Attachment Parse(JsonElement json)
|
||||||
{
|
{
|
||||||
var id = json.GetProperty("id").GetNonWhiteSpaceString().Pipe(Snowflake.Parse);
|
var id = json.GetProperty("id").GetNonWhiteSpaceString().Pipe(Snowflake.Parse);
|
||||||
|
|
@ -41,5 +41,4 @@ namespace DiscordChatExporter.Core.Discord.Data
|
||||||
|
|
||||||
return new Attachment(id, url, fileName, width, height, fileSize);
|
return new Attachment(id, url, fileName, width, height, fileSize);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
@ -4,10 +4,10 @@ using DiscordChatExporter.Core.Discord.Data.Common;
|
||||||
using DiscordChatExporter.Core.Utils.Extensions;
|
using DiscordChatExporter.Core.Utils.Extensions;
|
||||||
using JsonExtensions.Reading;
|
using JsonExtensions.Reading;
|
||||||
|
|
||||||
namespace DiscordChatExporter.Core.Discord.Data
|
namespace DiscordChatExporter.Core.Discord.Data;
|
||||||
{
|
|
||||||
// https://discord.com/developers/docs/resources/channel#channel-object
|
// https://discord.com/developers/docs/resources/channel#channel-object
|
||||||
public partial record Channel(
|
public partial record Channel(
|
||||||
Snowflake Id,
|
Snowflake Id,
|
||||||
ChannelKind Kind,
|
ChannelKind Kind,
|
||||||
Snowflake GuildId,
|
Snowflake GuildId,
|
||||||
|
|
@ -15,7 +15,7 @@ namespace DiscordChatExporter.Core.Discord.Data
|
||||||
string Name,
|
string Name,
|
||||||
int? Position,
|
int? Position,
|
||||||
string? Topic) : IHasId
|
string? Topic) : IHasId
|
||||||
{
|
{
|
||||||
public bool IsTextChannel => Kind is
|
public bool IsTextChannel => Kind is
|
||||||
ChannelKind.GuildTextChat or
|
ChannelKind.GuildTextChat or
|
||||||
ChannelKind.DirectTextChat or
|
ChannelKind.DirectTextChat or
|
||||||
|
|
@ -24,10 +24,10 @@ namespace DiscordChatExporter.Core.Discord.Data
|
||||||
ChannelKind.GuildStore;
|
ChannelKind.GuildStore;
|
||||||
|
|
||||||
public bool IsVoiceChannel => !IsTextChannel;
|
public bool IsVoiceChannel => !IsTextChannel;
|
||||||
}
|
}
|
||||||
|
|
||||||
public partial record Channel
|
public partial record Channel
|
||||||
{
|
{
|
||||||
private static ChannelCategory GetFallbackCategory(ChannelKind channelKind) => new(
|
private static ChannelCategory GetFallbackCategory(ChannelKind channelKind) => new(
|
||||||
Snowflake.Zero,
|
Snowflake.Zero,
|
||||||
channelKind switch
|
channelKind switch
|
||||||
|
|
@ -68,5 +68,4 @@ namespace DiscordChatExporter.Core.Discord.Data
|
||||||
topic
|
topic
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
@ -3,10 +3,10 @@ using DiscordChatExporter.Core.Discord.Data.Common;
|
||||||
using DiscordChatExporter.Core.Utils.Extensions;
|
using DiscordChatExporter.Core.Utils.Extensions;
|
||||||
using JsonExtensions.Reading;
|
using JsonExtensions.Reading;
|
||||||
|
|
||||||
namespace DiscordChatExporter.Core.Discord.Data
|
namespace DiscordChatExporter.Core.Discord.Data;
|
||||||
|
|
||||||
|
public record ChannelCategory(Snowflake Id, string Name, int? Position) : IHasId
|
||||||
{
|
{
|
||||||
public record ChannelCategory(Snowflake Id, string Name, int? Position) : IHasId
|
|
||||||
{
|
|
||||||
public static ChannelCategory Unknown { get; } = new(Snowflake.Zero, "<unknown category>", 0);
|
public static ChannelCategory Unknown { get; } = new(Snowflake.Zero, "<unknown category>", 0);
|
||||||
|
|
||||||
public static ChannelCategory Parse(JsonElement json, int? position = null)
|
public static ChannelCategory Parse(JsonElement json, int? position = null)
|
||||||
|
|
@ -23,5 +23,4 @@ namespace DiscordChatExporter.Core.Discord.Data
|
||||||
position ?? json.GetPropertyOrNull("position")?.GetInt32()
|
position ?? json.GetPropertyOrNull("position")?.GetInt32()
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
@ -1,9 +1,9 @@
|
||||||
namespace DiscordChatExporter.Core.Discord.Data
|
namespace DiscordChatExporter.Core.Discord.Data;
|
||||||
|
|
||||||
|
// https://discord.com/developers/docs/resources/channel#channel-object-channel-types
|
||||||
|
// Order of enum fields needs to match the order in the docs.
|
||||||
|
public enum ChannelKind
|
||||||
{
|
{
|
||||||
// https://discord.com/developers/docs/resources/channel#channel-object-channel-types
|
|
||||||
// Order of enum fields needs to match the order in the docs.
|
|
||||||
public enum ChannelKind
|
|
||||||
{
|
|
||||||
GuildTextChat = 0,
|
GuildTextChat = 0,
|
||||||
DirectTextChat,
|
DirectTextChat,
|
||||||
GuildVoiceChat,
|
GuildVoiceChat,
|
||||||
|
|
@ -11,5 +11,4 @@
|
||||||
GuildCategory,
|
GuildCategory,
|
||||||
GuildNews,
|
GuildNews,
|
||||||
GuildStore
|
GuildStore
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
@ -1,11 +1,11 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Diagnostics.CodeAnalysis;
|
using System.Diagnostics.CodeAnalysis;
|
||||||
|
|
||||||
namespace DiscordChatExporter.Core.Discord.Data.Common
|
namespace DiscordChatExporter.Core.Discord.Data.Common;
|
||||||
|
|
||||||
|
// Loosely based on https://github.com/omar/ByteSize (MIT license)
|
||||||
|
public readonly partial record struct FileSize(long TotalBytes)
|
||||||
{
|
{
|
||||||
// Loosely based on https://github.com/omar/ByteSize (MIT license)
|
|
||||||
public readonly partial record struct FileSize(long TotalBytes)
|
|
||||||
{
|
|
||||||
public double TotalKiloBytes => TotalBytes / 1024.0;
|
public double TotalKiloBytes => TotalBytes / 1024.0;
|
||||||
public double TotalMegaBytes => TotalKiloBytes / 1024.0;
|
public double TotalMegaBytes => TotalKiloBytes / 1024.0;
|
||||||
public double TotalGigaBytes => TotalMegaBytes / 1024.0;
|
public double TotalGigaBytes => TotalMegaBytes / 1024.0;
|
||||||
|
|
@ -40,10 +40,9 @@ namespace DiscordChatExporter.Core.Discord.Data.Common
|
||||||
|
|
||||||
[ExcludeFromCodeCoverage]
|
[ExcludeFromCodeCoverage]
|
||||||
public override string ToString() => $"{GetLargestWholeNumberValue():0.##} {GetLargestWholeNumberSymbol()}";
|
public override string ToString() => $"{GetLargestWholeNumberValue():0.##} {GetLargestWholeNumberSymbol()}";
|
||||||
}
|
}
|
||||||
|
|
||||||
public partial record struct FileSize
|
public partial record struct FileSize
|
||||||
{
|
{
|
||||||
public static FileSize FromBytes(long bytes) => new(bytes);
|
public static FileSize FromBytes(long bytes) => new(bytes);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
@ -1,7 +1,6 @@
|
||||||
namespace DiscordChatExporter.Core.Discord.Data.Common
|
namespace DiscordChatExporter.Core.Discord.Data.Common;
|
||||||
|
|
||||||
|
public interface IHasId
|
||||||
{
|
{
|
||||||
public interface IHasId
|
|
||||||
{
|
|
||||||
Snowflake Id { get; }
|
Snowflake Id { get; }
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
@ -1,13 +1,12 @@
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
|
||||||
namespace DiscordChatExporter.Core.Discord.Data.Common
|
namespace DiscordChatExporter.Core.Discord.Data.Common;
|
||||||
|
|
||||||
|
public class IdBasedEqualityComparer : IEqualityComparer<IHasId>
|
||||||
{
|
{
|
||||||
public class IdBasedEqualityComparer : IEqualityComparer<IHasId>
|
|
||||||
{
|
|
||||||
public static IdBasedEqualityComparer Instance { get; } = new();
|
public static IdBasedEqualityComparer Instance { get; } = new();
|
||||||
|
|
||||||
public bool Equals(IHasId? x, IHasId? y) => x?.Id == y?.Id;
|
public bool Equals(IHasId? x, IHasId? y) => x?.Id == y?.Id;
|
||||||
|
|
||||||
public int GetHashCode(IHasId obj) => obj.Id.GetHashCode();
|
public int GetHashCode(IHasId obj) => obj.Id.GetHashCode();
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
@ -6,10 +6,10 @@ using System.Text.Json;
|
||||||
using DiscordChatExporter.Core.Utils.Extensions;
|
using DiscordChatExporter.Core.Utils.Extensions;
|
||||||
using JsonExtensions.Reading;
|
using JsonExtensions.Reading;
|
||||||
|
|
||||||
namespace DiscordChatExporter.Core.Discord.Data.Embeds
|
namespace DiscordChatExporter.Core.Discord.Data.Embeds;
|
||||||
{
|
|
||||||
// https://discord.com/developers/docs/resources/channel#embed-object
|
// https://discord.com/developers/docs/resources/channel#embed-object
|
||||||
public partial record Embed(
|
public partial record Embed(
|
||||||
string? Title,
|
string? Title,
|
||||||
string? Url,
|
string? Url,
|
||||||
DateTimeOffset? Timestamp,
|
DateTimeOffset? Timestamp,
|
||||||
|
|
@ -20,7 +20,7 @@ namespace DiscordChatExporter.Core.Discord.Data.Embeds
|
||||||
EmbedImage? Thumbnail,
|
EmbedImage? Thumbnail,
|
||||||
EmbedImage? Image,
|
EmbedImage? Image,
|
||||||
EmbedFooter? Footer)
|
EmbedFooter? Footer)
|
||||||
{
|
{
|
||||||
public PlainImageEmbedProjection? TryGetPlainImage() =>
|
public PlainImageEmbedProjection? TryGetPlainImage() =>
|
||||||
PlainImageEmbedProjection.TryResolve(this);
|
PlainImageEmbedProjection.TryResolve(this);
|
||||||
|
|
||||||
|
|
@ -29,10 +29,10 @@ namespace DiscordChatExporter.Core.Discord.Data.Embeds
|
||||||
|
|
||||||
public YouTubeVideoEmbedProjection? TryGetYouTubeVideo() =>
|
public YouTubeVideoEmbedProjection? TryGetYouTubeVideo() =>
|
||||||
YouTubeVideoEmbedProjection.TryResolve(this);
|
YouTubeVideoEmbedProjection.TryResolve(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
public partial record Embed
|
public partial record Embed
|
||||||
{
|
{
|
||||||
public static Embed Parse(JsonElement json)
|
public static Embed Parse(JsonElement json)
|
||||||
{
|
{
|
||||||
var title = json.GetPropertyOrNull("title")?.GetStringOrNull();
|
var title = json.GetPropertyOrNull("title")?.GetStringOrNull();
|
||||||
|
|
@ -63,5 +63,4 @@ namespace DiscordChatExporter.Core.Discord.Data.Embeds
|
||||||
footer
|
footer
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
@ -1,15 +1,15 @@
|
||||||
using System.Text.Json;
|
using System.Text.Json;
|
||||||
using JsonExtensions.Reading;
|
using JsonExtensions.Reading;
|
||||||
|
|
||||||
namespace DiscordChatExporter.Core.Discord.Data.Embeds
|
namespace DiscordChatExporter.Core.Discord.Data.Embeds;
|
||||||
{
|
|
||||||
// https://discord.com/developers/docs/resources/channel#embed-object-embed-author-structure
|
// https://discord.com/developers/docs/resources/channel#embed-object-embed-author-structure
|
||||||
public record EmbedAuthor(
|
public record EmbedAuthor(
|
||||||
string? Name,
|
string? Name,
|
||||||
string? Url,
|
string? Url,
|
||||||
string? IconUrl,
|
string? IconUrl,
|
||||||
string? IconProxyUrl)
|
string? IconProxyUrl)
|
||||||
{
|
{
|
||||||
public static EmbedAuthor Parse(JsonElement json)
|
public static EmbedAuthor Parse(JsonElement json)
|
||||||
{
|
{
|
||||||
var name = json.GetPropertyOrNull("name")?.GetStringOrNull();
|
var name = json.GetPropertyOrNull("name")?.GetStringOrNull();
|
||||||
|
|
@ -19,5 +19,4 @@ namespace DiscordChatExporter.Core.Discord.Data.Embeds
|
||||||
|
|
||||||
return new EmbedAuthor(name, url, iconUrl, iconProxyUrl);
|
return new EmbedAuthor(name, url, iconUrl, iconProxyUrl);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
@ -2,14 +2,14 @@ using System.Text.Json;
|
||||||
using DiscordChatExporter.Core.Utils.Extensions;
|
using DiscordChatExporter.Core.Utils.Extensions;
|
||||||
using JsonExtensions.Reading;
|
using JsonExtensions.Reading;
|
||||||
|
|
||||||
namespace DiscordChatExporter.Core.Discord.Data.Embeds
|
namespace DiscordChatExporter.Core.Discord.Data.Embeds;
|
||||||
{
|
|
||||||
// https://discord.com/developers/docs/resources/channel#embed-object-embed-field-structure
|
// https://discord.com/developers/docs/resources/channel#embed-object-embed-field-structure
|
||||||
public record EmbedField(
|
public record EmbedField(
|
||||||
string Name,
|
string Name,
|
||||||
string Value,
|
string Value,
|
||||||
bool IsInline)
|
bool IsInline)
|
||||||
{
|
{
|
||||||
public static EmbedField Parse(JsonElement json)
|
public static EmbedField Parse(JsonElement json)
|
||||||
{
|
{
|
||||||
var name = json.GetProperty("name").GetNonWhiteSpaceString();
|
var name = json.GetProperty("name").GetNonWhiteSpaceString();
|
||||||
|
|
@ -18,5 +18,4 @@ namespace DiscordChatExporter.Core.Discord.Data.Embeds
|
||||||
|
|
||||||
return new EmbedField(name, value, isInline);
|
return new EmbedField(name, value, isInline);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
@ -2,14 +2,14 @@ using System.Text.Json;
|
||||||
using DiscordChatExporter.Core.Utils.Extensions;
|
using DiscordChatExporter.Core.Utils.Extensions;
|
||||||
using JsonExtensions.Reading;
|
using JsonExtensions.Reading;
|
||||||
|
|
||||||
namespace DiscordChatExporter.Core.Discord.Data.Embeds
|
namespace DiscordChatExporter.Core.Discord.Data.Embeds;
|
||||||
{
|
|
||||||
// https://discord.com/developers/docs/resources/channel#embed-object-embed-footer-structure
|
// https://discord.com/developers/docs/resources/channel#embed-object-embed-footer-structure
|
||||||
public record EmbedFooter(
|
public record EmbedFooter(
|
||||||
string Text,
|
string Text,
|
||||||
string? IconUrl,
|
string? IconUrl,
|
||||||
string? IconProxyUrl)
|
string? IconProxyUrl)
|
||||||
{
|
{
|
||||||
public static EmbedFooter Parse(JsonElement json)
|
public static EmbedFooter Parse(JsonElement json)
|
||||||
{
|
{
|
||||||
var text = json.GetProperty("text").GetNonWhiteSpaceString();
|
var text = json.GetProperty("text").GetNonWhiteSpaceString();
|
||||||
|
|
@ -18,5 +18,4 @@ namespace DiscordChatExporter.Core.Discord.Data.Embeds
|
||||||
|
|
||||||
return new EmbedFooter(text, iconUrl, iconProxyUrl);
|
return new EmbedFooter(text, iconUrl, iconProxyUrl);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
@ -1,15 +1,15 @@
|
||||||
using System.Text.Json;
|
using System.Text.Json;
|
||||||
using JsonExtensions.Reading;
|
using JsonExtensions.Reading;
|
||||||
|
|
||||||
namespace DiscordChatExporter.Core.Discord.Data.Embeds
|
namespace DiscordChatExporter.Core.Discord.Data.Embeds;
|
||||||
{
|
|
||||||
// https://discord.com/developers/docs/resources/channel#embed-object-embed-image-structure
|
// https://discord.com/developers/docs/resources/channel#embed-object-embed-image-structure
|
||||||
public record EmbedImage(
|
public record EmbedImage(
|
||||||
string? Url,
|
string? Url,
|
||||||
string? ProxyUrl,
|
string? ProxyUrl,
|
||||||
int? Width,
|
int? Width,
|
||||||
int? Height)
|
int? Height)
|
||||||
{
|
{
|
||||||
public static EmbedImage Parse(JsonElement json)
|
public static EmbedImage Parse(JsonElement json)
|
||||||
{
|
{
|
||||||
var url = json.GetPropertyOrNull("url")?.GetStringOrNull();
|
var url = json.GetPropertyOrNull("url")?.GetStringOrNull();
|
||||||
|
|
@ -19,5 +19,4 @@ namespace DiscordChatExporter.Core.Discord.Data.Embeds
|
||||||
|
|
||||||
return new EmbedImage(url, proxyUrl, width, height);
|
return new EmbedImage(url, proxyUrl, width, height);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
@ -3,10 +3,10 @@ using System.Linq;
|
||||||
using System.Text.RegularExpressions;
|
using System.Text.RegularExpressions;
|
||||||
using DiscordChatExporter.Core.Utils;
|
using DiscordChatExporter.Core.Utils;
|
||||||
|
|
||||||
namespace DiscordChatExporter.Core.Discord.Data.Embeds
|
namespace DiscordChatExporter.Core.Discord.Data.Embeds;
|
||||||
|
|
||||||
|
public record PlainImageEmbedProjection(string Url)
|
||||||
{
|
{
|
||||||
public record PlainImageEmbedProjection(string Url)
|
|
||||||
{
|
|
||||||
public static PlainImageEmbedProjection? TryResolve(Embed embed)
|
public static PlainImageEmbedProjection? TryResolve(Embed embed)
|
||||||
{
|
{
|
||||||
if (string.IsNullOrWhiteSpace(embed.Url))
|
if (string.IsNullOrWhiteSpace(embed.Url))
|
||||||
|
|
@ -30,5 +30,4 @@ namespace DiscordChatExporter.Core.Discord.Data.Embeds
|
||||||
|
|
||||||
return new PlainImageEmbedProjection(embed.Url);
|
return new PlainImageEmbedProjection(embed.Url);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
@ -1,14 +1,14 @@
|
||||||
using System.Text.RegularExpressions;
|
using System.Text.RegularExpressions;
|
||||||
|
|
||||||
namespace DiscordChatExporter.Core.Discord.Data.Embeds
|
namespace DiscordChatExporter.Core.Discord.Data.Embeds;
|
||||||
{
|
|
||||||
public partial record SpotifyTrackEmbedProjection(string TrackId)
|
|
||||||
{
|
|
||||||
public string Url => $"https://open.spotify.com/embed/track/{TrackId}";
|
|
||||||
}
|
|
||||||
|
|
||||||
public partial record SpotifyTrackEmbedProjection
|
public partial record SpotifyTrackEmbedProjection(string TrackId)
|
||||||
{
|
{
|
||||||
|
public string Url => $"https://open.spotify.com/embed/track/{TrackId}";
|
||||||
|
}
|
||||||
|
|
||||||
|
public partial record SpotifyTrackEmbedProjection
|
||||||
|
{
|
||||||
private static string? TryParseTrackId(string embedUrl)
|
private static string? TryParseTrackId(string embedUrl)
|
||||||
{
|
{
|
||||||
// https://open.spotify.com/track/1LHZMWefF9502NPfArRfvP?si=3efac6ce9be04f0a
|
// https://open.spotify.com/track/1LHZMWefF9502NPfArRfvP?si=3efac6ce9be04f0a
|
||||||
|
|
@ -30,5 +30,4 @@ namespace DiscordChatExporter.Core.Discord.Data.Embeds
|
||||||
|
|
||||||
return new SpotifyTrackEmbedProjection(trackId);
|
return new SpotifyTrackEmbedProjection(trackId);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
@ -1,14 +1,14 @@
|
||||||
using System.Text.RegularExpressions;
|
using System.Text.RegularExpressions;
|
||||||
|
|
||||||
namespace DiscordChatExporter.Core.Discord.Data.Embeds
|
namespace DiscordChatExporter.Core.Discord.Data.Embeds;
|
||||||
{
|
|
||||||
public partial record YouTubeVideoEmbedProjection(string VideoId)
|
|
||||||
{
|
|
||||||
public string Url => $"https://www.youtube.com/embed/{VideoId}";
|
|
||||||
}
|
|
||||||
|
|
||||||
public partial record YouTubeVideoEmbedProjection
|
public partial record YouTubeVideoEmbedProjection(string VideoId)
|
||||||
{
|
{
|
||||||
|
public string Url => $"https://www.youtube.com/embed/{VideoId}";
|
||||||
|
}
|
||||||
|
|
||||||
|
public partial record YouTubeVideoEmbedProjection
|
||||||
|
{
|
||||||
// Adapted from YoutubeExplode
|
// Adapted from YoutubeExplode
|
||||||
// https://github.com/Tyrrrz/YoutubeExplode/blob/5be164be20019783913f76fcc98f18c65aebe9f0/YoutubeExplode/Videos/VideoId.cs#L34-L64
|
// https://github.com/Tyrrrz/YoutubeExplode/blob/5be164be20019783913f76fcc98f18c65aebe9f0/YoutubeExplode/Videos/VideoId.cs#L34-L64
|
||||||
private static string? TryParseVideoId(string embedUrl)
|
private static string? TryParseVideoId(string embedUrl)
|
||||||
|
|
@ -45,5 +45,4 @@ namespace DiscordChatExporter.Core.Discord.Data.Embeds
|
||||||
|
|
||||||
return new YouTubeVideoEmbedProjection(videoId);
|
return new YouTubeVideoEmbedProjection(videoId);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
@ -4,25 +4,25 @@ using DiscordChatExporter.Core.Utils;
|
||||||
using DiscordChatExporter.Core.Utils.Extensions;
|
using DiscordChatExporter.Core.Utils.Extensions;
|
||||||
using JsonExtensions.Reading;
|
using JsonExtensions.Reading;
|
||||||
|
|
||||||
namespace DiscordChatExporter.Core.Discord.Data
|
namespace DiscordChatExporter.Core.Discord.Data;
|
||||||
{
|
|
||||||
// https://discord.com/developers/docs/resources/emoji#emoji-object
|
// https://discord.com/developers/docs/resources/emoji#emoji-object
|
||||||
public partial record Emoji(
|
public partial record Emoji(
|
||||||
// Only present on custom emoji
|
// Only present on custom emoji
|
||||||
string? Id,
|
string? Id,
|
||||||
// Name of custom emoji (e.g. LUL) or actual representation of standard emoji (e.g. 🙂)
|
// Name of custom emoji (e.g. LUL) or actual representation of standard emoji (e.g. 🙂)
|
||||||
string Name,
|
string Name,
|
||||||
bool IsAnimated,
|
bool IsAnimated,
|
||||||
string ImageUrl)
|
string ImageUrl)
|
||||||
{
|
{
|
||||||
// Name of custom emoji (e.g. LUL) or name of standard emoji (e.g. slight_smile)
|
// Name of custom emoji (e.g. LUL) or name of standard emoji (e.g. slight_smile)
|
||||||
public string Code => !string.IsNullOrWhiteSpace(Id)
|
public string Code => !string.IsNullOrWhiteSpace(Id)
|
||||||
? Name
|
? Name
|
||||||
: EmojiIndex.TryGetCode(Name) ?? Name;
|
: EmojiIndex.TryGetCode(Name) ?? Name;
|
||||||
}
|
}
|
||||||
|
|
||||||
public partial record Emoji
|
public partial record Emoji
|
||||||
{
|
{
|
||||||
private static string GetTwemojiName(string name) => string.Join("-",
|
private static string GetTwemojiName(string name) => string.Join("-",
|
||||||
name
|
name
|
||||||
.GetRunes()
|
.GetRunes()
|
||||||
|
|
@ -56,5 +56,4 @@ namespace DiscordChatExporter.Core.Discord.Data
|
||||||
|
|
||||||
return new Emoji(id, name, isAnimated, imageUrl);
|
return new Emoji(id, name, isAnimated, imageUrl);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
@ -3,11 +3,11 @@ using DiscordChatExporter.Core.Discord.Data.Common;
|
||||||
using DiscordChatExporter.Core.Utils.Extensions;
|
using DiscordChatExporter.Core.Utils.Extensions;
|
||||||
using JsonExtensions.Reading;
|
using JsonExtensions.Reading;
|
||||||
|
|
||||||
namespace DiscordChatExporter.Core.Discord.Data
|
namespace DiscordChatExporter.Core.Discord.Data;
|
||||||
|
|
||||||
|
// https://discord.com/developers/docs/resources/guild#guild-object
|
||||||
|
public record Guild(Snowflake Id, string Name, string IconUrl) : IHasId
|
||||||
{
|
{
|
||||||
// https://discord.com/developers/docs/resources/guild#guild-object
|
|
||||||
public record Guild(Snowflake Id, string Name, string IconUrl) : IHasId
|
|
||||||
{
|
|
||||||
public static Guild DirectMessages { get; } = new(
|
public static Guild DirectMessages { get; } = new(
|
||||||
Snowflake.Zero,
|
Snowflake.Zero,
|
||||||
"Direct Messages",
|
"Direct Messages",
|
||||||
|
|
@ -32,5 +32,4 @@ namespace DiscordChatExporter.Core.Discord.Data
|
||||||
|
|
||||||
return new Guild(id, name, iconUrl);
|
return new Guild(id, name, iconUrl);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
@ -6,19 +6,19 @@ using DiscordChatExporter.Core.Discord.Data.Common;
|
||||||
using DiscordChatExporter.Core.Utils.Extensions;
|
using DiscordChatExporter.Core.Utils.Extensions;
|
||||||
using JsonExtensions.Reading;
|
using JsonExtensions.Reading;
|
||||||
|
|
||||||
namespace DiscordChatExporter.Core.Discord.Data
|
namespace DiscordChatExporter.Core.Discord.Data;
|
||||||
{
|
|
||||||
// https://discord.com/developers/docs/resources/guild#guild-member-object
|
// https://discord.com/developers/docs/resources/guild#guild-member-object
|
||||||
public partial record Member(
|
public partial record Member(
|
||||||
User User,
|
User User,
|
||||||
string Nick,
|
string Nick,
|
||||||
IReadOnlyList<Snowflake> RoleIds) : IHasId
|
IReadOnlyList<Snowflake> RoleIds) : IHasId
|
||||||
{
|
{
|
||||||
public Snowflake Id => User.Id;
|
public Snowflake Id => User.Id;
|
||||||
}
|
}
|
||||||
|
|
||||||
public partial record Member
|
public partial record Member
|
||||||
{
|
{
|
||||||
public static Member CreateForUser(User user) => new(
|
public static Member CreateForUser(User user) => new(
|
||||||
user,
|
user,
|
||||||
user.Name,
|
user.Name,
|
||||||
|
|
@ -43,5 +43,4 @@ namespace DiscordChatExporter.Core.Discord.Data
|
||||||
roleIds
|
roleIds
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
@ -7,10 +7,10 @@ using DiscordChatExporter.Core.Discord.Data.Embeds;
|
||||||
using DiscordChatExporter.Core.Utils.Extensions;
|
using DiscordChatExporter.Core.Utils.Extensions;
|
||||||
using JsonExtensions.Reading;
|
using JsonExtensions.Reading;
|
||||||
|
|
||||||
namespace DiscordChatExporter.Core.Discord.Data
|
namespace DiscordChatExporter.Core.Discord.Data;
|
||||||
{
|
|
||||||
// https://discord.com/developers/docs/resources/channel#message-object
|
// https://discord.com/developers/docs/resources/channel#message-object
|
||||||
public record Message(
|
public record Message(
|
||||||
Snowflake Id,
|
Snowflake Id,
|
||||||
MessageKind Kind,
|
MessageKind Kind,
|
||||||
User Author,
|
User Author,
|
||||||
|
|
@ -25,7 +25,7 @@ namespace DiscordChatExporter.Core.Discord.Data
|
||||||
IReadOnlyList<User> MentionedUsers,
|
IReadOnlyList<User> MentionedUsers,
|
||||||
MessageReference? Reference,
|
MessageReference? Reference,
|
||||||
Message? ReferencedMessage) : IHasId
|
Message? ReferencedMessage) : IHasId
|
||||||
{
|
{
|
||||||
public static Message Parse(JsonElement json)
|
public static Message Parse(JsonElement json)
|
||||||
{
|
{
|
||||||
var id = json.GetProperty("id").GetNonWhiteSpaceString().Pipe(Snowflake.Parse);
|
var id = json.GetProperty("id").GetNonWhiteSpaceString().Pipe(Snowflake.Parse);
|
||||||
|
|
@ -85,5 +85,4 @@ namespace DiscordChatExporter.Core.Discord.Data
|
||||||
referencedMessage
|
referencedMessage
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
@ -1,8 +1,8 @@
|
||||||
namespace DiscordChatExporter.Core.Discord.Data
|
namespace DiscordChatExporter.Core.Discord.Data;
|
||||||
|
|
||||||
|
// https://discord.com/developers/docs/resources/channel#message-object-message-types
|
||||||
|
public enum MessageKind
|
||||||
{
|
{
|
||||||
// https://discord.com/developers/docs/resources/channel#message-object-message-types
|
|
||||||
public enum MessageKind
|
|
||||||
{
|
|
||||||
Default = 0,
|
Default = 0,
|
||||||
RecipientAdd = 1,
|
RecipientAdd = 1,
|
||||||
RecipientRemove = 2,
|
RecipientRemove = 2,
|
||||||
|
|
@ -12,5 +12,4 @@
|
||||||
ChannelPinnedMessage = 6,
|
ChannelPinnedMessage = 6,
|
||||||
GuildMemberJoin = 7,
|
GuildMemberJoin = 7,
|
||||||
Reply = 19
|
Reply = 19
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
@ -2,11 +2,11 @@ using System.Text.Json;
|
||||||
using DiscordChatExporter.Core.Utils.Extensions;
|
using DiscordChatExporter.Core.Utils.Extensions;
|
||||||
using JsonExtensions.Reading;
|
using JsonExtensions.Reading;
|
||||||
|
|
||||||
namespace DiscordChatExporter.Core.Discord.Data
|
namespace DiscordChatExporter.Core.Discord.Data;
|
||||||
|
|
||||||
|
// https://discord.com/developers/docs/resources/channel#message-object-message-reference-structure
|
||||||
|
public record MessageReference(Snowflake? MessageId, Snowflake? ChannelId, Snowflake? GuildId)
|
||||||
{
|
{
|
||||||
// https://discord.com/developers/docs/resources/channel#message-object-message-reference-structure
|
|
||||||
public record MessageReference(Snowflake? MessageId, Snowflake? ChannelId, Snowflake? GuildId)
|
|
||||||
{
|
|
||||||
public static MessageReference Parse(JsonElement json)
|
public static MessageReference Parse(JsonElement json)
|
||||||
{
|
{
|
||||||
var messageId = json.GetPropertyOrNull("message_id")?.GetStringOrNull()?.Pipe(Snowflake.Parse);
|
var messageId = json.GetPropertyOrNull("message_id")?.GetStringOrNull()?.Pipe(Snowflake.Parse);
|
||||||
|
|
@ -15,5 +15,4 @@ namespace DiscordChatExporter.Core.Discord.Data
|
||||||
|
|
||||||
return new MessageReference(messageId, channelId, guildId);
|
return new MessageReference(messageId, channelId, guildId);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
@ -1,11 +1,11 @@
|
||||||
using System.Text.Json;
|
using System.Text.Json;
|
||||||
using DiscordChatExporter.Core.Utils.Extensions;
|
using DiscordChatExporter.Core.Utils.Extensions;
|
||||||
|
|
||||||
namespace DiscordChatExporter.Core.Discord.Data
|
namespace DiscordChatExporter.Core.Discord.Data;
|
||||||
|
|
||||||
|
// https://discord.com/developers/docs/resources/channel#reaction-object
|
||||||
|
public record Reaction(Emoji Emoji, int Count)
|
||||||
{
|
{
|
||||||
// https://discord.com/developers/docs/resources/channel#reaction-object
|
|
||||||
public record Reaction(Emoji Emoji, int Count)
|
|
||||||
{
|
|
||||||
public static Reaction Parse(JsonElement json)
|
public static Reaction Parse(JsonElement json)
|
||||||
{
|
{
|
||||||
var emoji = json.GetProperty("emoji").Pipe(Emoji.Parse);
|
var emoji = json.GetProperty("emoji").Pipe(Emoji.Parse);
|
||||||
|
|
@ -13,5 +13,4 @@ namespace DiscordChatExporter.Core.Discord.Data
|
||||||
|
|
||||||
return new Reaction(emoji, count);
|
return new Reaction(emoji, count);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
@ -4,11 +4,11 @@ using DiscordChatExporter.Core.Discord.Data.Common;
|
||||||
using DiscordChatExporter.Core.Utils.Extensions;
|
using DiscordChatExporter.Core.Utils.Extensions;
|
||||||
using JsonExtensions.Reading;
|
using JsonExtensions.Reading;
|
||||||
|
|
||||||
namespace DiscordChatExporter.Core.Discord.Data
|
namespace DiscordChatExporter.Core.Discord.Data;
|
||||||
|
|
||||||
|
// https://discord.com/developers/docs/topics/permissions#role-object
|
||||||
|
public record Role(Snowflake Id, string Name, int Position, Color? Color) : IHasId
|
||||||
{
|
{
|
||||||
// https://discord.com/developers/docs/topics/permissions#role-object
|
|
||||||
public record Role(Snowflake Id, string Name, int Position, Color? Color) : IHasId
|
|
||||||
{
|
|
||||||
public static Role Parse(JsonElement json)
|
public static Role Parse(JsonElement json)
|
||||||
{
|
{
|
||||||
var id = json.GetProperty("id").GetNonWhiteSpaceString().Pipe(Snowflake.Parse);
|
var id = json.GetProperty("id").GetNonWhiteSpaceString().Pipe(Snowflake.Parse);
|
||||||
|
|
@ -24,5 +24,4 @@ namespace DiscordChatExporter.Core.Discord.Data
|
||||||
|
|
||||||
return new Role(id, name, position, color);
|
return new Role(id, name, position, color);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
@ -4,23 +4,23 @@ using DiscordChatExporter.Core.Discord.Data.Common;
|
||||||
using DiscordChatExporter.Core.Utils.Extensions;
|
using DiscordChatExporter.Core.Utils.Extensions;
|
||||||
using JsonExtensions.Reading;
|
using JsonExtensions.Reading;
|
||||||
|
|
||||||
namespace DiscordChatExporter.Core.Discord.Data
|
namespace DiscordChatExporter.Core.Discord.Data;
|
||||||
{
|
|
||||||
// https://discord.com/developers/docs/resources/user#user-object
|
// https://discord.com/developers/docs/resources/user#user-object
|
||||||
public partial record User(
|
public partial record User(
|
||||||
Snowflake Id,
|
Snowflake Id,
|
||||||
bool IsBot,
|
bool IsBot,
|
||||||
int Discriminator,
|
int Discriminator,
|
||||||
string Name,
|
string Name,
|
||||||
string AvatarUrl) : IHasId
|
string AvatarUrl) : IHasId
|
||||||
{
|
{
|
||||||
public string DiscriminatorFormatted => $"{Discriminator:0000}";
|
public string DiscriminatorFormatted => $"{Discriminator:0000}";
|
||||||
|
|
||||||
public string FullName => $"{Name}#{DiscriminatorFormatted}";
|
public string FullName => $"{Name}#{DiscriminatorFormatted}";
|
||||||
}
|
}
|
||||||
|
|
||||||
public partial record User
|
public partial record User
|
||||||
{
|
{
|
||||||
private static string GetDefaultAvatarUrl(int discriminator) =>
|
private static string GetDefaultAvatarUrl(int discriminator) =>
|
||||||
$"https://cdn.discordapp.com/embed/avatars/{discriminator % 5}.png";
|
$"https://cdn.discordapp.com/embed/avatars/{discriminator % 5}.png";
|
||||||
|
|
||||||
|
|
@ -47,5 +47,4 @@ namespace DiscordChatExporter.Core.Discord.Data
|
||||||
|
|
||||||
return new User(id, isBot, discriminator, name, avatarUrl);
|
return new User(id, isBot, discriminator, name, avatarUrl);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
@ -14,10 +14,10 @@ using DiscordChatExporter.Core.Utils.Extensions;
|
||||||
using JsonExtensions.Http;
|
using JsonExtensions.Http;
|
||||||
using JsonExtensions.Reading;
|
using JsonExtensions.Reading;
|
||||||
|
|
||||||
namespace DiscordChatExporter.Core.Discord
|
namespace DiscordChatExporter.Core.Discord;
|
||||||
|
|
||||||
|
public class DiscordClient
|
||||||
{
|
{
|
||||||
public class DiscordClient
|
|
||||||
{
|
|
||||||
private readonly AuthToken _token;
|
private readonly AuthToken _token;
|
||||||
private readonly Uri _baseUri = new("https://discord.com/api/v8/", UriKind.Absolute);
|
private readonly Uri _baseUri = new("https://discord.com/api/v8/", UriKind.Absolute);
|
||||||
|
|
||||||
|
|
@ -299,5 +299,4 @@ namespace DiscordChatExporter.Core.Discord
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
@ -3,20 +3,20 @@ using System.Diagnostics.CodeAnalysis;
|
||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
using System.Text.RegularExpressions;
|
using System.Text.RegularExpressions;
|
||||||
|
|
||||||
namespace DiscordChatExporter.Core.Discord
|
namespace DiscordChatExporter.Core.Discord;
|
||||||
|
|
||||||
|
public readonly partial record struct Snowflake(ulong Value)
|
||||||
{
|
{
|
||||||
public readonly partial record struct Snowflake(ulong Value)
|
|
||||||
{
|
|
||||||
public DateTimeOffset ToDate() => DateTimeOffset.FromUnixTimeMilliseconds(
|
public DateTimeOffset ToDate() => DateTimeOffset.FromUnixTimeMilliseconds(
|
||||||
(long)((Value >> 22) + 1420070400000UL)
|
(long)((Value >> 22) + 1420070400000UL)
|
||||||
).ToLocalTime();
|
).ToLocalTime();
|
||||||
|
|
||||||
[ExcludeFromCodeCoverage]
|
[ExcludeFromCodeCoverage]
|
||||||
public override string ToString() => Value.ToString(CultureInfo.InvariantCulture);
|
public override string ToString() => Value.ToString(CultureInfo.InvariantCulture);
|
||||||
}
|
}
|
||||||
|
|
||||||
public partial record struct Snowflake
|
public partial record struct Snowflake
|
||||||
{
|
{
|
||||||
public static Snowflake Zero { get; } = new(0);
|
public static Snowflake Zero { get; } = new(0);
|
||||||
|
|
||||||
public static Snowflake FromDate(DateTimeOffset date) => new(
|
public static Snowflake FromDate(DateTimeOffset date) => new(
|
||||||
|
|
@ -47,10 +47,9 @@ namespace DiscordChatExporter.Core.Discord
|
||||||
TryParse(str, formatProvider) ?? throw new FormatException($"Invalid snowflake '{str}'.");
|
TryParse(str, formatProvider) ?? throw new FormatException($"Invalid snowflake '{str}'.");
|
||||||
|
|
||||||
public static Snowflake Parse(string str) => Parse(str, null);
|
public static Snowflake Parse(string str) => Parse(str, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
public partial record struct Snowflake : IComparable<Snowflake>
|
public partial record struct Snowflake : IComparable<Snowflake>
|
||||||
{
|
{
|
||||||
public int CompareTo(Snowflake other) => Value.CompareTo(other.Value);
|
public int CompareTo(Snowflake other) => Value.CompareTo(other.Value);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
@ -1,10 +1,10 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Net.Http;
|
using System.Net.Http;
|
||||||
|
|
||||||
namespace DiscordChatExporter.Core.Exceptions
|
namespace DiscordChatExporter.Core.Exceptions;
|
||||||
|
|
||||||
|
public partial class DiscordChatExporterException : Exception
|
||||||
{
|
{
|
||||||
public partial class DiscordChatExporterException : Exception
|
|
||||||
{
|
|
||||||
public bool IsFatal { get; }
|
public bool IsFatal { get; }
|
||||||
|
|
||||||
public DiscordChatExporterException(string message, bool isFatal = false)
|
public DiscordChatExporterException(string message, bool isFatal = false)
|
||||||
|
|
@ -12,10 +12,10 @@ namespace DiscordChatExporter.Core.Exceptions
|
||||||
{
|
{
|
||||||
IsFatal = isFatal;
|
IsFatal = isFatal;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public partial class DiscordChatExporterException
|
public partial class DiscordChatExporterException
|
||||||
{
|
{
|
||||||
internal static DiscordChatExporterException FailedHttpRequest(HttpResponseMessage response)
|
internal static DiscordChatExporterException FailedHttpRequest(HttpResponseMessage response)
|
||||||
{
|
{
|
||||||
var message = $@"
|
var message = $@"
|
||||||
|
|
@ -41,5 +41,4 @@ Failed to perform an HTTP request.
|
||||||
|
|
||||||
internal static DiscordChatExporterException ChannelIsEmpty() =>
|
internal static DiscordChatExporterException ChannelIsEmpty() =>
|
||||||
new("No messages found for the specified period.");
|
new("No messages found for the specified period.");
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
@ -9,10 +9,10 @@ using DiscordChatExporter.Core.Discord.Data.Common;
|
||||||
using DiscordChatExporter.Core.Exceptions;
|
using DiscordChatExporter.Core.Exceptions;
|
||||||
using DiscordChatExporter.Core.Utils.Extensions;
|
using DiscordChatExporter.Core.Utils.Extensions;
|
||||||
|
|
||||||
namespace DiscordChatExporter.Core.Exporting
|
namespace DiscordChatExporter.Core.Exporting;
|
||||||
|
|
||||||
|
public class ChannelExporter
|
||||||
{
|
{
|
||||||
public class ChannelExporter
|
|
||||||
{
|
|
||||||
private readonly DiscordClient _discord;
|
private readonly DiscordClient _discord;
|
||||||
|
|
||||||
public ChannelExporter(DiscordClient discord) => _discord = discord;
|
public ChannelExporter(DiscordClient discord) => _discord = discord;
|
||||||
|
|
@ -79,5 +79,4 @@ namespace DiscordChatExporter.Core.Exporting
|
||||||
if (!exportedAnything)
|
if (!exportedAnything)
|
||||||
throw DiscordChatExporterException.ChannelIsEmpty();
|
throw DiscordChatExporterException.ChannelIsEmpty();
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
@ -10,10 +10,10 @@ using DiscordChatExporter.Core.Discord;
|
||||||
using DiscordChatExporter.Core.Discord.Data;
|
using DiscordChatExporter.Core.Discord.Data;
|
||||||
using DiscordChatExporter.Core.Utils.Extensions;
|
using DiscordChatExporter.Core.Utils.Extensions;
|
||||||
|
|
||||||
namespace DiscordChatExporter.Core.Exporting
|
namespace DiscordChatExporter.Core.Exporting;
|
||||||
|
|
||||||
|
internal class ExportContext
|
||||||
{
|
{
|
||||||
internal class ExportContext
|
|
||||||
{
|
|
||||||
private readonly MediaDownloader _mediaDownloader;
|
private readonly MediaDownloader _mediaDownloader;
|
||||||
|
|
||||||
public ExportRequest Request { get; }
|
public ExportRequest Request { get; }
|
||||||
|
|
@ -102,5 +102,4 @@ namespace DiscordChatExporter.Core.Exporting
|
||||||
return url;
|
return url;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
@ -1,18 +1,18 @@
|
||||||
using System;
|
using System;
|
||||||
|
|
||||||
namespace DiscordChatExporter.Core.Exporting
|
namespace DiscordChatExporter.Core.Exporting;
|
||||||
|
|
||||||
|
public enum ExportFormat
|
||||||
{
|
{
|
||||||
public enum ExportFormat
|
|
||||||
{
|
|
||||||
PlainText,
|
PlainText,
|
||||||
HtmlDark,
|
HtmlDark,
|
||||||
HtmlLight,
|
HtmlLight,
|
||||||
Csv,
|
Csv,
|
||||||
Json
|
Json
|
||||||
}
|
}
|
||||||
|
|
||||||
public static class ExportFormatExtensions
|
public static class ExportFormatExtensions
|
||||||
{
|
{
|
||||||
public static string GetFileExtension(this ExportFormat format) => format switch
|
public static string GetFileExtension(this ExportFormat format) => format switch
|
||||||
{
|
{
|
||||||
ExportFormat.PlainText => "txt",
|
ExportFormat.PlainText => "txt",
|
||||||
|
|
@ -32,5 +32,4 @@ namespace DiscordChatExporter.Core.Exporting
|
||||||
ExportFormat.Json => "JSON",
|
ExportFormat.Json => "JSON",
|
||||||
_ => throw new ArgumentOutOfRangeException(nameof(format))
|
_ => throw new ArgumentOutOfRangeException(nameof(format))
|
||||||
};
|
};
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
@ -8,9 +8,9 @@ using DiscordChatExporter.Core.Exporting.Filtering;
|
||||||
using DiscordChatExporter.Core.Exporting.Partitioning;
|
using DiscordChatExporter.Core.Exporting.Partitioning;
|
||||||
using DiscordChatExporter.Core.Utils;
|
using DiscordChatExporter.Core.Utils;
|
||||||
|
|
||||||
namespace DiscordChatExporter.Core.Exporting
|
namespace DiscordChatExporter.Core.Exporting;
|
||||||
{
|
|
||||||
public partial record ExportRequest(
|
public partial record ExportRequest(
|
||||||
Guild Guild,
|
Guild Guild,
|
||||||
Channel Channel,
|
Channel Channel,
|
||||||
string OutputPath,
|
string OutputPath,
|
||||||
|
|
@ -22,7 +22,7 @@ namespace DiscordChatExporter.Core.Exporting
|
||||||
bool ShouldDownloadMedia,
|
bool ShouldDownloadMedia,
|
||||||
bool ShouldReuseMedia,
|
bool ShouldReuseMedia,
|
||||||
string DateFormat)
|
string DateFormat)
|
||||||
{
|
{
|
||||||
private string? _outputBaseFilePath;
|
private string? _outputBaseFilePath;
|
||||||
public string OutputBaseFilePath => _outputBaseFilePath ??= GetOutputBaseFilePath(
|
public string OutputBaseFilePath => _outputBaseFilePath ??= GetOutputBaseFilePath(
|
||||||
Guild,
|
Guild,
|
||||||
|
|
@ -36,10 +36,10 @@ namespace DiscordChatExporter.Core.Exporting
|
||||||
public string OutputBaseDirPath => Path.GetDirectoryName(OutputBaseFilePath) ?? OutputPath;
|
public string OutputBaseDirPath => Path.GetDirectoryName(OutputBaseFilePath) ?? OutputPath;
|
||||||
|
|
||||||
public string OutputMediaDirPath => $"{OutputBaseFilePath}_Files{Path.DirectorySeparatorChar}";
|
public string OutputMediaDirPath => $"{OutputBaseFilePath}_Files{Path.DirectorySeparatorChar}";
|
||||||
}
|
}
|
||||||
|
|
||||||
public partial record ExportRequest
|
public partial record ExportRequest
|
||||||
{
|
{
|
||||||
private static string GetOutputBaseFilePath(
|
private static string GetOutputBaseFilePath(
|
||||||
Guild guild,
|
Guild guild,
|
||||||
Channel channel,
|
Channel channel,
|
||||||
|
|
@ -123,5 +123,4 @@ namespace DiscordChatExporter.Core.Exporting
|
||||||
|
|
||||||
return buffer.ToString();
|
return buffer.ToString();
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
@ -1,8 +1,7 @@
|
||||||
namespace DiscordChatExporter.Core.Exporting.Filtering
|
namespace DiscordChatExporter.Core.Exporting.Filtering;
|
||||||
|
|
||||||
|
internal enum BinaryExpressionKind
|
||||||
{
|
{
|
||||||
internal enum BinaryExpressionKind
|
|
||||||
{
|
|
||||||
Or,
|
Or,
|
||||||
And
|
And
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
@ -1,10 +1,10 @@
|
||||||
using System;
|
using System;
|
||||||
using DiscordChatExporter.Core.Discord.Data;
|
using DiscordChatExporter.Core.Discord.Data;
|
||||||
|
|
||||||
namespace DiscordChatExporter.Core.Exporting.Filtering
|
namespace DiscordChatExporter.Core.Exporting.Filtering;
|
||||||
|
|
||||||
|
internal class BinaryExpressionMessageFilter : MessageFilter
|
||||||
{
|
{
|
||||||
internal class BinaryExpressionMessageFilter : MessageFilter
|
|
||||||
{
|
|
||||||
private readonly MessageFilter _first;
|
private readonly MessageFilter _first;
|
||||||
private readonly MessageFilter _second;
|
private readonly MessageFilter _second;
|
||||||
private readonly BinaryExpressionKind _kind;
|
private readonly BinaryExpressionKind _kind;
|
||||||
|
|
@ -22,5 +22,4 @@ namespace DiscordChatExporter.Core.Exporting.Filtering
|
||||||
BinaryExpressionKind.And => _first.IsMatch(message) && _second.IsMatch(message),
|
BinaryExpressionKind.And => _first.IsMatch(message) && _second.IsMatch(message),
|
||||||
_ => throw new InvalidOperationException($"Unknown binary expression kind '{_kind}'.")
|
_ => throw new InvalidOperationException($"Unknown binary expression kind '{_kind}'.")
|
||||||
};
|
};
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
@ -2,10 +2,10 @@
|
||||||
using System.Text.RegularExpressions;
|
using System.Text.RegularExpressions;
|
||||||
using DiscordChatExporter.Core.Discord.Data;
|
using DiscordChatExporter.Core.Discord.Data;
|
||||||
|
|
||||||
namespace DiscordChatExporter.Core.Exporting.Filtering
|
namespace DiscordChatExporter.Core.Exporting.Filtering;
|
||||||
|
|
||||||
|
internal class ContainsMessageFilter : MessageFilter
|
||||||
{
|
{
|
||||||
internal class ContainsMessageFilter : MessageFilter
|
|
||||||
{
|
|
||||||
private readonly string _text;
|
private readonly string _text;
|
||||||
|
|
||||||
public ContainsMessageFilter(string text) => _text = text;
|
public ContainsMessageFilter(string text) => _text = text;
|
||||||
|
|
@ -30,5 +30,4 @@ namespace DiscordChatExporter.Core.Exporting.Filtering
|
||||||
IsMatch(f.Value)
|
IsMatch(f.Value)
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
@ -1,10 +1,10 @@
|
||||||
using System;
|
using System;
|
||||||
using DiscordChatExporter.Core.Discord.Data;
|
using DiscordChatExporter.Core.Discord.Data;
|
||||||
|
|
||||||
namespace DiscordChatExporter.Core.Exporting.Filtering
|
namespace DiscordChatExporter.Core.Exporting.Filtering;
|
||||||
|
|
||||||
|
internal class FromMessageFilter : MessageFilter
|
||||||
{
|
{
|
||||||
internal class FromMessageFilter : MessageFilter
|
|
||||||
{
|
|
||||||
private readonly string _value;
|
private readonly string _value;
|
||||||
|
|
||||||
public FromMessageFilter(string value) => _value = value;
|
public FromMessageFilter(string value) => _value = value;
|
||||||
|
|
@ -13,5 +13,4 @@ namespace DiscordChatExporter.Core.Exporting.Filtering
|
||||||
string.Equals(_value, message.Author.Name, StringComparison.OrdinalIgnoreCase) ||
|
string.Equals(_value, message.Author.Name, StringComparison.OrdinalIgnoreCase) ||
|
||||||
string.Equals(_value, message.Author.FullName, StringComparison.OrdinalIgnoreCase) ||
|
string.Equals(_value, message.Author.FullName, StringComparison.OrdinalIgnoreCase) ||
|
||||||
string.Equals(_value, message.Author.Id.ToString(), StringComparison.OrdinalIgnoreCase);
|
string.Equals(_value, message.Author.Id.ToString(), StringComparison.OrdinalIgnoreCase);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
@ -3,10 +3,10 @@ using System.Linq;
|
||||||
using System.Text.RegularExpressions;
|
using System.Text.RegularExpressions;
|
||||||
using DiscordChatExporter.Core.Discord.Data;
|
using DiscordChatExporter.Core.Discord.Data;
|
||||||
|
|
||||||
namespace DiscordChatExporter.Core.Exporting.Filtering
|
namespace DiscordChatExporter.Core.Exporting.Filtering;
|
||||||
|
|
||||||
|
internal class HasMessageFilter : MessageFilter
|
||||||
{
|
{
|
||||||
internal class HasMessageFilter : MessageFilter
|
|
||||||
{
|
|
||||||
private readonly MessageContentMatchKind _kind;
|
private readonly MessageContentMatchKind _kind;
|
||||||
|
|
||||||
public HasMessageFilter(MessageContentMatchKind kind) => _kind = kind;
|
public HasMessageFilter(MessageContentMatchKind kind) => _kind = kind;
|
||||||
|
|
@ -21,5 +21,4 @@ namespace DiscordChatExporter.Core.Exporting.Filtering
|
||||||
MessageContentMatchKind.Sound => message.Attachments.Any(file => file.IsAudio),
|
MessageContentMatchKind.Sound => message.Attachments.Any(file => file.IsAudio),
|
||||||
_ => throw new InvalidOperationException($"Unknown message content match kind '{_kind}'.")
|
_ => throw new InvalidOperationException($"Unknown message content match kind '{_kind}'.")
|
||||||
};
|
};
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
@ -2,10 +2,10 @@
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using DiscordChatExporter.Core.Discord.Data;
|
using DiscordChatExporter.Core.Discord.Data;
|
||||||
|
|
||||||
namespace DiscordChatExporter.Core.Exporting.Filtering
|
namespace DiscordChatExporter.Core.Exporting.Filtering;
|
||||||
|
|
||||||
|
internal class MentionsMessageFilter : MessageFilter
|
||||||
{
|
{
|
||||||
internal class MentionsMessageFilter : MessageFilter
|
|
||||||
{
|
|
||||||
private readonly string _value;
|
private readonly string _value;
|
||||||
|
|
||||||
public MentionsMessageFilter(string value) => _value = value;
|
public MentionsMessageFilter(string value) => _value = value;
|
||||||
|
|
@ -15,5 +15,4 @@ namespace DiscordChatExporter.Core.Exporting.Filtering
|
||||||
string.Equals(_value, user.FullName, StringComparison.OrdinalIgnoreCase) ||
|
string.Equals(_value, user.FullName, StringComparison.OrdinalIgnoreCase) ||
|
||||||
string.Equals(_value, user.Id.ToString(), StringComparison.OrdinalIgnoreCase)
|
string.Equals(_value, user.Id.ToString(), StringComparison.OrdinalIgnoreCase)
|
||||||
);
|
);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
@ -1,12 +1,11 @@
|
||||||
namespace DiscordChatExporter.Core.Exporting.Filtering
|
namespace DiscordChatExporter.Core.Exporting.Filtering;
|
||||||
|
|
||||||
|
internal enum MessageContentMatchKind
|
||||||
{
|
{
|
||||||
internal enum MessageContentMatchKind
|
|
||||||
{
|
|
||||||
Link,
|
Link,
|
||||||
Embed,
|
Embed,
|
||||||
File,
|
File,
|
||||||
Video,
|
Video,
|
||||||
Image,
|
Image,
|
||||||
Sound
|
Sound
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
@ -2,17 +2,16 @@
|
||||||
using DiscordChatExporter.Core.Exporting.Filtering.Parsing;
|
using DiscordChatExporter.Core.Exporting.Filtering.Parsing;
|
||||||
using Superpower;
|
using Superpower;
|
||||||
|
|
||||||
namespace DiscordChatExporter.Core.Exporting.Filtering
|
namespace DiscordChatExporter.Core.Exporting.Filtering;
|
||||||
{
|
|
||||||
public abstract partial class MessageFilter
|
|
||||||
{
|
|
||||||
public abstract bool IsMatch(Message message);
|
|
||||||
}
|
|
||||||
|
|
||||||
public partial class MessageFilter
|
public abstract partial class MessageFilter
|
||||||
{
|
{
|
||||||
|
public abstract bool IsMatch(Message message);
|
||||||
|
}
|
||||||
|
|
||||||
|
public partial class MessageFilter
|
||||||
|
{
|
||||||
public static MessageFilter Null { get; } = new NullMessageFilter();
|
public static MessageFilter Null { get; } = new NullMessageFilter();
|
||||||
|
|
||||||
public static MessageFilter Parse(string value) => FilterGrammar.Filter.Parse(value);
|
public static MessageFilter Parse(string value) => FilterGrammar.Filter.Parse(value);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
@ -1,13 +1,12 @@
|
||||||
using DiscordChatExporter.Core.Discord.Data;
|
using DiscordChatExporter.Core.Discord.Data;
|
||||||
|
|
||||||
namespace DiscordChatExporter.Core.Exporting.Filtering
|
namespace DiscordChatExporter.Core.Exporting.Filtering;
|
||||||
|
|
||||||
|
internal class NegatedMessageFilter : MessageFilter
|
||||||
{
|
{
|
||||||
internal class NegatedMessageFilter : MessageFilter
|
|
||||||
{
|
|
||||||
private readonly MessageFilter _filter;
|
private readonly MessageFilter _filter;
|
||||||
|
|
||||||
public NegatedMessageFilter(MessageFilter filter) => _filter = filter;
|
public NegatedMessageFilter(MessageFilter filter) => _filter = filter;
|
||||||
|
|
||||||
public override bool IsMatch(Message message) => !_filter.IsMatch(message);
|
public override bool IsMatch(Message message) => !_filter.IsMatch(message);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
@ -1,9 +1,8 @@
|
||||||
using DiscordChatExporter.Core.Discord.Data;
|
using DiscordChatExporter.Core.Discord.Data;
|
||||||
|
|
||||||
namespace DiscordChatExporter.Core.Exporting.Filtering
|
namespace DiscordChatExporter.Core.Exporting.Filtering;
|
||||||
|
|
||||||
|
internal class NullMessageFilter : MessageFilter
|
||||||
{
|
{
|
||||||
internal class NullMessageFilter : MessageFilter
|
|
||||||
{
|
|
||||||
public override bool IsMatch(Message message) => true;
|
public override bool IsMatch(Message message) => true;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
@ -2,10 +2,10 @@
|
||||||
using Superpower;
|
using Superpower;
|
||||||
using Superpower.Parsers;
|
using Superpower.Parsers;
|
||||||
|
|
||||||
namespace DiscordChatExporter.Core.Exporting.Filtering.Parsing
|
namespace DiscordChatExporter.Core.Exporting.Filtering.Parsing;
|
||||||
|
|
||||||
|
internal static class FilterGrammar
|
||||||
{
|
{
|
||||||
internal static class FilterGrammar
|
|
||||||
{
|
|
||||||
private static readonly TextParser<char> EscapedCharacter =
|
private static readonly TextParser<char> EscapedCharacter =
|
||||||
Character.EqualTo('\\').IgnoreThen(Character.AnyChar);
|
Character.EqualTo('\\').IgnoreThen(Character.AnyChar);
|
||||||
|
|
||||||
|
|
@ -98,5 +98,4 @@ namespace DiscordChatExporter.Core.Exporting.Filtering.Parsing
|
||||||
|
|
||||||
public static readonly TextParser<MessageFilter> Filter =
|
public static readonly TextParser<MessageFilter> Filter =
|
||||||
BinaryExpressionFilter.Token().AtEnd();
|
BinaryExpressionFilter.Token().AtEnd();
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
@ -10,10 +10,10 @@ using System.Threading.Tasks;
|
||||||
using DiscordChatExporter.Core.Utils;
|
using DiscordChatExporter.Core.Utils;
|
||||||
using DiscordChatExporter.Core.Utils.Extensions;
|
using DiscordChatExporter.Core.Utils.Extensions;
|
||||||
|
|
||||||
namespace DiscordChatExporter.Core.Exporting
|
namespace DiscordChatExporter.Core.Exporting;
|
||||||
|
|
||||||
|
internal partial class MediaDownloader
|
||||||
{
|
{
|
||||||
internal partial class MediaDownloader
|
|
||||||
{
|
|
||||||
private readonly string _workingDirPath;
|
private readonly string _workingDirPath;
|
||||||
private readonly bool _reuseMedia;
|
private readonly bool _reuseMedia;
|
||||||
|
|
||||||
|
|
@ -47,7 +47,7 @@ namespace DiscordChatExporter.Core.Exporting
|
||||||
using var response = await Http.Client.GetAsync(url, cancellationToken);
|
using var response = await Http.Client.GetAsync(url, cancellationToken);
|
||||||
await using (var output = File.Create(filePath))
|
await using (var output = File.Create(filePath))
|
||||||
{
|
{
|
||||||
await response.Content.CopyToAsync(output);
|
await response.Content.CopyToAsync(output, cancellationToken);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Try to set the file date according to the last-modified header
|
// Try to set the file date according to the last-modified header
|
||||||
|
|
@ -77,10 +77,10 @@ namespace DiscordChatExporter.Core.Exporting
|
||||||
|
|
||||||
return _pathCache[url] = filePath;
|
return _pathCache[url] = filePath;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
internal partial class MediaDownloader
|
internal partial class MediaDownloader
|
||||||
{
|
{
|
||||||
private static string GetUrlHash(string url)
|
private static string GetUrlHash(string url)
|
||||||
{
|
{
|
||||||
using var hash = SHA256.Create();
|
using var hash = SHA256.Create();
|
||||||
|
|
@ -106,5 +106,4 @@ namespace DiscordChatExporter.Core.Exporting
|
||||||
|
|
||||||
return PathEx.EscapePath(fileNameWithoutExtension.Truncate(42) + '-' + urlHash + fileExtension);
|
return PathEx.EscapePath(fileNameWithoutExtension.Truncate(42) + '-' + urlHash + fileExtension);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
@ -5,10 +5,10 @@ using System.Threading.Tasks;
|
||||||
using DiscordChatExporter.Core.Discord.Data;
|
using DiscordChatExporter.Core.Discord.Data;
|
||||||
using DiscordChatExporter.Core.Exporting.Writers;
|
using DiscordChatExporter.Core.Exporting.Writers;
|
||||||
|
|
||||||
namespace DiscordChatExporter.Core.Exporting
|
namespace DiscordChatExporter.Core.Exporting;
|
||||||
|
|
||||||
|
internal partial class MessageExporter : IAsyncDisposable
|
||||||
{
|
{
|
||||||
internal partial class MessageExporter : IAsyncDisposable
|
|
||||||
{
|
|
||||||
private readonly ExportContext _context;
|
private readonly ExportContext _context;
|
||||||
|
|
||||||
private int _partitionIndex;
|
private int _partitionIndex;
|
||||||
|
|
@ -62,10 +62,10 @@ namespace DiscordChatExporter.Core.Exporting
|
||||||
}
|
}
|
||||||
|
|
||||||
public async ValueTask DisposeAsync() => await ResetWriterAsync();
|
public async ValueTask DisposeAsync() => await ResetWriterAsync();
|
||||||
}
|
}
|
||||||
|
|
||||||
internal partial class MessageExporter
|
internal partial class MessageExporter
|
||||||
{
|
{
|
||||||
private static string GetPartitionFilePath(string baseFilePath, int partitionIndex)
|
private static string GetPartitionFilePath(string baseFilePath, int partitionIndex)
|
||||||
{
|
{
|
||||||
// First partition - don't change file name
|
// First partition - don't change file name
|
||||||
|
|
@ -101,5 +101,4 @@ namespace DiscordChatExporter.Core.Exporting
|
||||||
_ => throw new ArgumentOutOfRangeException(nameof(format), $"Unknown export format '{format}'.")
|
_ => throw new ArgumentOutOfRangeException(nameof(format), $"Unknown export format '{format}'.")
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
@ -1,12 +1,11 @@
|
||||||
namespace DiscordChatExporter.Core.Exporting.Partitioning
|
namespace DiscordChatExporter.Core.Exporting.Partitioning;
|
||||||
|
|
||||||
|
internal class FileSizePartitionLimit : PartitionLimit
|
||||||
{
|
{
|
||||||
internal class FileSizePartitionLimit : PartitionLimit
|
|
||||||
{
|
|
||||||
private readonly long _limit;
|
private readonly long _limit;
|
||||||
|
|
||||||
public FileSizePartitionLimit(long limit) => _limit = limit;
|
public FileSizePartitionLimit(long limit) => _limit = limit;
|
||||||
|
|
||||||
public override bool IsReached(long messagesWritten, long bytesWritten) =>
|
public override bool IsReached(long messagesWritten, long bytesWritten) =>
|
||||||
bytesWritten >= _limit;
|
bytesWritten >= _limit;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
@ -1,12 +1,11 @@
|
||||||
namespace DiscordChatExporter.Core.Exporting.Partitioning
|
namespace DiscordChatExporter.Core.Exporting.Partitioning;
|
||||||
|
|
||||||
|
internal class MessageCountPartitionLimit : PartitionLimit
|
||||||
{
|
{
|
||||||
internal class MessageCountPartitionLimit : PartitionLimit
|
|
||||||
{
|
|
||||||
private readonly long _limit;
|
private readonly long _limit;
|
||||||
|
|
||||||
public MessageCountPartitionLimit(long limit) => _limit = limit;
|
public MessageCountPartitionLimit(long limit) => _limit = limit;
|
||||||
|
|
||||||
public override bool IsReached(long messagesWritten, long bytesWritten) =>
|
public override bool IsReached(long messagesWritten, long bytesWritten) =>
|
||||||
messagesWritten >= _limit;
|
messagesWritten >= _limit;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
@ -1,7 +1,6 @@
|
||||||
namespace DiscordChatExporter.Core.Exporting.Partitioning
|
namespace DiscordChatExporter.Core.Exporting.Partitioning;
|
||||||
|
|
||||||
|
internal class NullPartitionLimit : PartitionLimit
|
||||||
{
|
{
|
||||||
internal class NullPartitionLimit : PartitionLimit
|
|
||||||
{
|
|
||||||
public override bool IsReached(long messagesWritten, long bytesWritten) => false;
|
public override bool IsReached(long messagesWritten, long bytesWritten) => false;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
@ -2,15 +2,15 @@
|
||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
using System.Text.RegularExpressions;
|
using System.Text.RegularExpressions;
|
||||||
|
|
||||||
namespace DiscordChatExporter.Core.Exporting.Partitioning
|
namespace DiscordChatExporter.Core.Exporting.Partitioning;
|
||||||
{
|
|
||||||
public abstract partial class PartitionLimit
|
|
||||||
{
|
|
||||||
public abstract bool IsReached(long messagesWritten, long bytesWritten);
|
|
||||||
}
|
|
||||||
|
|
||||||
public partial class PartitionLimit
|
public abstract partial class PartitionLimit
|
||||||
{
|
{
|
||||||
|
public abstract bool IsReached(long messagesWritten, long bytesWritten);
|
||||||
|
}
|
||||||
|
|
||||||
|
public partial class PartitionLimit
|
||||||
|
{
|
||||||
public static PartitionLimit Null { get; } = new NullPartitionLimit();
|
public static PartitionLimit Null { get; } = new NullPartitionLimit();
|
||||||
|
|
||||||
private static long? TryParseFileSizeBytes(string value, IFormatProvider? formatProvider = null)
|
private static long? TryParseFileSizeBytes(string value, IFormatProvider? formatProvider = null)
|
||||||
|
|
@ -59,5 +59,4 @@ namespace DiscordChatExporter.Core.Exporting.Partitioning
|
||||||
|
|
||||||
public static PartitionLimit Parse(string value, IFormatProvider? formatProvider = null) =>
|
public static PartitionLimit Parse(string value, IFormatProvider? formatProvider = null) =>
|
||||||
TryParse(value, formatProvider) ?? throw new FormatException($"Invalid partition limit '{value}'.");
|
TryParse(value, formatProvider) ?? throw new FormatException($"Invalid partition limit '{value}'.");
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
@ -7,10 +7,10 @@ using DiscordChatExporter.Core.Discord.Data;
|
||||||
using DiscordChatExporter.Core.Exporting.Writers.MarkdownVisitors;
|
using DiscordChatExporter.Core.Exporting.Writers.MarkdownVisitors;
|
||||||
using DiscordChatExporter.Core.Utils.Extensions;
|
using DiscordChatExporter.Core.Utils.Extensions;
|
||||||
|
|
||||||
namespace DiscordChatExporter.Core.Exporting.Writers
|
namespace DiscordChatExporter.Core.Exporting.Writers;
|
||||||
|
|
||||||
|
internal partial class CsvMessageWriter : MessageWriter
|
||||||
{
|
{
|
||||||
internal partial class CsvMessageWriter : MessageWriter
|
|
||||||
{
|
|
||||||
private readonly TextWriter _writer;
|
private readonly TextWriter _writer;
|
||||||
|
|
||||||
public CsvMessageWriter(Stream stream, ExportContext context)
|
public CsvMessageWriter(Stream stream, ExportContext context)
|
||||||
|
|
@ -103,14 +103,13 @@ namespace DiscordChatExporter.Core.Exporting.Writers
|
||||||
await _writer.DisposeAsync();
|
await _writer.DisposeAsync();
|
||||||
await base.DisposeAsync();
|
await base.DisposeAsync();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
internal partial class CsvMessageWriter
|
internal partial class CsvMessageWriter
|
||||||
{
|
{
|
||||||
private static string CsvEncode(string value)
|
private static string CsvEncode(string value)
|
||||||
{
|
{
|
||||||
value = value.Replace("\"", "\"\"");
|
value = value.Replace("\"", "\"\"");
|
||||||
return $"\"{value}\"";
|
return $"\"{value}\"";
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
@ -3,11 +3,11 @@ using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using DiscordChatExporter.Core.Discord.Data;
|
using DiscordChatExporter.Core.Discord.Data;
|
||||||
|
|
||||||
namespace DiscordChatExporter.Core.Exporting.Writers.Html
|
namespace DiscordChatExporter.Core.Exporting.Writers.Html;
|
||||||
|
|
||||||
|
// Used for grouping contiguous messages in HTML export
|
||||||
|
internal partial class MessageGroup
|
||||||
{
|
{
|
||||||
// Used for grouping contiguous messages in HTML export
|
|
||||||
internal partial class MessageGroup
|
|
||||||
{
|
|
||||||
public User Author { get; }
|
public User Author { get; }
|
||||||
|
|
||||||
public DateTimeOffset Timestamp { get; }
|
public DateTimeOffset Timestamp { get; }
|
||||||
|
|
@ -31,10 +31,10 @@ namespace DiscordChatExporter.Core.Exporting.Writers.Html
|
||||||
ReferencedMessage = referencedMessage;
|
ReferencedMessage = referencedMessage;
|
||||||
Messages = messages;
|
Messages = messages;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
internal partial class MessageGroup
|
internal partial class MessageGroup
|
||||||
{
|
{
|
||||||
public static bool CanJoin(Message message1, Message message2) =>
|
public static bool CanJoin(Message message1, Message message2) =>
|
||||||
// Must be from the same author
|
// Must be from the same author
|
||||||
message1.Author.Id == message2.Author.Id &&
|
message1.Author.Id == message2.Author.Id &&
|
||||||
|
|
@ -57,5 +57,4 @@ namespace DiscordChatExporter.Core.Exporting.Writers.Html
|
||||||
messages
|
messages
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
@ -1,9 +1,9 @@
|
||||||
using DiscordChatExporter.Core.Exporting.Writers.MarkdownVisitors;
|
using DiscordChatExporter.Core.Exporting.Writers.MarkdownVisitors;
|
||||||
|
|
||||||
namespace DiscordChatExporter.Core.Exporting.Writers.Html
|
namespace DiscordChatExporter.Core.Exporting.Writers.Html;
|
||||||
|
|
||||||
|
internal class MessageGroupTemplateContext
|
||||||
{
|
{
|
||||||
internal class MessageGroupTemplateContext
|
|
||||||
{
|
|
||||||
public ExportContext ExportContext { get; }
|
public ExportContext ExportContext { get; }
|
||||||
|
|
||||||
public MessageGroup MessageGroup { get; }
|
public MessageGroup MessageGroup { get; }
|
||||||
|
|
@ -16,5 +16,4 @@ namespace DiscordChatExporter.Core.Exporting.Writers.Html
|
||||||
|
|
||||||
public string FormatMarkdown(string? markdown, bool isJumboAllowed = true) =>
|
public string FormatMarkdown(string? markdown, bool isJumboAllowed = true) =>
|
||||||
HtmlMarkdownVisitor.Format(ExportContext, markdown ?? "", isJumboAllowed);
|
HtmlMarkdownVisitor.Format(ExportContext, markdown ?? "", isJumboAllowed);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
namespace DiscordChatExporter.Core.Exporting.Writers.Html
|
namespace DiscordChatExporter.Core.Exporting.Writers.Html;
|
||||||
|
|
||||||
|
internal class PostambleTemplateContext
|
||||||
{
|
{
|
||||||
internal class PostambleTemplateContext
|
|
||||||
{
|
|
||||||
public ExportContext ExportContext { get; }
|
public ExportContext ExportContext { get; }
|
||||||
|
|
||||||
public long MessagesWritten { get; }
|
public long MessagesWritten { get; }
|
||||||
|
|
@ -11,5 +11,4 @@
|
||||||
ExportContext = exportContext;
|
ExportContext = exportContext;
|
||||||
MessagesWritten = messagesWritten;
|
MessagesWritten = messagesWritten;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
@ -1,9 +1,9 @@
|
||||||
using DiscordChatExporter.Core.Exporting.Writers.MarkdownVisitors;
|
using DiscordChatExporter.Core.Exporting.Writers.MarkdownVisitors;
|
||||||
|
|
||||||
namespace DiscordChatExporter.Core.Exporting.Writers.Html
|
namespace DiscordChatExporter.Core.Exporting.Writers.Html;
|
||||||
|
|
||||||
|
internal class PreambleTemplateContext
|
||||||
{
|
{
|
||||||
internal class PreambleTemplateContext
|
|
||||||
{
|
|
||||||
public ExportContext ExportContext { get; }
|
public ExportContext ExportContext { get; }
|
||||||
|
|
||||||
public string ThemeName { get; }
|
public string ThemeName { get; }
|
||||||
|
|
@ -16,5 +16,4 @@ namespace DiscordChatExporter.Core.Exporting.Writers.Html
|
||||||
|
|
||||||
public string FormatMarkdown(string? markdown, bool isJumboAllowed = true) =>
|
public string FormatMarkdown(string? markdown, bool isJumboAllowed = true) =>
|
||||||
HtmlMarkdownVisitor.Format(ExportContext, markdown ?? "", isJumboAllowed);
|
HtmlMarkdownVisitor.Format(ExportContext, markdown ?? "", isJumboAllowed);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
@ -6,10 +6,10 @@ using System.Threading.Tasks;
|
||||||
using DiscordChatExporter.Core.Discord.Data;
|
using DiscordChatExporter.Core.Discord.Data;
|
||||||
using DiscordChatExporter.Core.Exporting.Writers.Html;
|
using DiscordChatExporter.Core.Exporting.Writers.Html;
|
||||||
|
|
||||||
namespace DiscordChatExporter.Core.Exporting.Writers
|
namespace DiscordChatExporter.Core.Exporting.Writers;
|
||||||
|
|
||||||
|
internal class HtmlMessageWriter : MessageWriter
|
||||||
{
|
{
|
||||||
internal class HtmlMessageWriter : MessageWriter
|
|
||||||
{
|
|
||||||
private readonly TextWriter _writer;
|
private readonly TextWriter _writer;
|
||||||
private readonly string _themeName;
|
private readonly string _themeName;
|
||||||
|
|
||||||
|
|
@ -92,5 +92,4 @@ namespace DiscordChatExporter.Core.Exporting.Writers
|
||||||
await _writer.DisposeAsync();
|
await _writer.DisposeAsync();
|
||||||
await base.DisposeAsync();
|
await base.DisposeAsync();
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
@ -9,10 +9,10 @@ using DiscordChatExporter.Core.Exporting.Writers.MarkdownVisitors;
|
||||||
using DiscordChatExporter.Core.Utils.Extensions;
|
using DiscordChatExporter.Core.Utils.Extensions;
|
||||||
using JsonExtensions.Writing;
|
using JsonExtensions.Writing;
|
||||||
|
|
||||||
namespace DiscordChatExporter.Core.Exporting.Writers
|
namespace DiscordChatExporter.Core.Exporting.Writers;
|
||||||
|
|
||||||
|
internal class JsonMessageWriter : MessageWriter
|
||||||
{
|
{
|
||||||
internal class JsonMessageWriter : MessageWriter
|
|
||||||
{
|
|
||||||
private readonly Utf8JsonWriter _writer;
|
private readonly Utf8JsonWriter _writer;
|
||||||
|
|
||||||
public JsonMessageWriter(Stream stream, ExportContext context)
|
public JsonMessageWriter(Stream stream, ExportContext context)
|
||||||
|
|
@ -323,5 +323,4 @@ namespace DiscordChatExporter.Core.Exporting.Writers
|
||||||
await _writer.DisposeAsync();
|
await _writer.DisposeAsync();
|
||||||
await base.DisposeAsync();
|
await base.DisposeAsync();
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
@ -9,10 +9,10 @@ using DiscordChatExporter.Core.Markdown;
|
||||||
using DiscordChatExporter.Core.Markdown.Parsing;
|
using DiscordChatExporter.Core.Markdown.Parsing;
|
||||||
using DiscordChatExporter.Core.Utils.Extensions;
|
using DiscordChatExporter.Core.Utils.Extensions;
|
||||||
|
|
||||||
namespace DiscordChatExporter.Core.Exporting.Writers.MarkdownVisitors
|
namespace DiscordChatExporter.Core.Exporting.Writers.MarkdownVisitors;
|
||||||
|
|
||||||
|
internal partial class HtmlMarkdownVisitor : MarkdownVisitor
|
||||||
{
|
{
|
||||||
internal partial class HtmlMarkdownVisitor : MarkdownVisitor
|
|
||||||
{
|
|
||||||
private readonly ExportContext _context;
|
private readonly ExportContext _context;
|
||||||
private readonly StringBuilder _buffer;
|
private readonly StringBuilder _buffer;
|
||||||
private readonly bool _isJumbo;
|
private readonly bool _isJumbo;
|
||||||
|
|
@ -169,10 +169,10 @@ namespace DiscordChatExporter.Core.Exporting.Writers.MarkdownVisitors
|
||||||
|
|
||||||
return base.VisitUnixTimestamp(timestamp);
|
return base.VisitUnixTimestamp(timestamp);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
internal partial class HtmlMarkdownVisitor
|
internal partial class HtmlMarkdownVisitor
|
||||||
{
|
{
|
||||||
private static string HtmlEncode(string text) => WebUtility.HtmlEncode(text);
|
private static string HtmlEncode(string text) => WebUtility.HtmlEncode(text);
|
||||||
|
|
||||||
public static string Format(ExportContext context, string markdown, bool isJumboAllowed = true)
|
public static string Format(ExportContext context, string markdown, bool isJumboAllowed = true)
|
||||||
|
|
@ -189,5 +189,4 @@ namespace DiscordChatExporter.Core.Exporting.Writers.MarkdownVisitors
|
||||||
|
|
||||||
return buffer.ToString();
|
return buffer.ToString();
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
@ -4,10 +4,10 @@ using DiscordChatExporter.Core.Markdown;
|
||||||
using DiscordChatExporter.Core.Markdown.Parsing;
|
using DiscordChatExporter.Core.Markdown.Parsing;
|
||||||
using DiscordChatExporter.Core.Utils.Extensions;
|
using DiscordChatExporter.Core.Utils.Extensions;
|
||||||
|
|
||||||
namespace DiscordChatExporter.Core.Exporting.Writers.MarkdownVisitors
|
namespace DiscordChatExporter.Core.Exporting.Writers.MarkdownVisitors;
|
||||||
|
|
||||||
|
internal partial class PlainTextMarkdownVisitor : MarkdownVisitor
|
||||||
{
|
{
|
||||||
internal partial class PlainTextMarkdownVisitor : MarkdownVisitor
|
|
||||||
{
|
|
||||||
private readonly ExportContext _context;
|
private readonly ExportContext _context;
|
||||||
private readonly StringBuilder _buffer;
|
private readonly StringBuilder _buffer;
|
||||||
|
|
||||||
|
|
@ -78,10 +78,10 @@ namespace DiscordChatExporter.Core.Exporting.Writers.MarkdownVisitors
|
||||||
|
|
||||||
return base.VisitUnixTimestamp(timestamp);
|
return base.VisitUnixTimestamp(timestamp);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
internal partial class PlainTextMarkdownVisitor
|
internal partial class PlainTextMarkdownVisitor
|
||||||
{
|
{
|
||||||
public static string Format(ExportContext context, string markdown)
|
public static string Format(ExportContext context, string markdown)
|
||||||
{
|
{
|
||||||
var nodes = MarkdownParser.ParseMinimal(markdown);
|
var nodes = MarkdownParser.ParseMinimal(markdown);
|
||||||
|
|
@ -91,5 +91,4 @@ namespace DiscordChatExporter.Core.Exporting.Writers.MarkdownVisitors
|
||||||
|
|
||||||
return buffer.ToString();
|
return buffer.ToString();
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
@ -4,10 +4,10 @@ using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using DiscordChatExporter.Core.Discord.Data;
|
using DiscordChatExporter.Core.Discord.Data;
|
||||||
|
|
||||||
namespace DiscordChatExporter.Core.Exporting.Writers
|
namespace DiscordChatExporter.Core.Exporting.Writers;
|
||||||
|
|
||||||
|
internal abstract class MessageWriter : IAsyncDisposable
|
||||||
{
|
{
|
||||||
internal abstract class MessageWriter : IAsyncDisposable
|
|
||||||
{
|
|
||||||
protected Stream Stream { get; }
|
protected Stream Stream { get; }
|
||||||
|
|
||||||
protected ExportContext Context { get; }
|
protected ExportContext Context { get; }
|
||||||
|
|
@ -33,5 +33,4 @@ namespace DiscordChatExporter.Core.Exporting.Writers
|
||||||
public virtual ValueTask WritePostambleAsync(CancellationToken cancellationToken = default) => default;
|
public virtual ValueTask WritePostambleAsync(CancellationToken cancellationToken = default) => default;
|
||||||
|
|
||||||
public virtual async ValueTask DisposeAsync() => await Stream.DisposeAsync();
|
public virtual async ValueTask DisposeAsync() => await Stream.DisposeAsync();
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
@ -7,10 +7,10 @@ using DiscordChatExporter.Core.Discord.Data;
|
||||||
using DiscordChatExporter.Core.Discord.Data.Embeds;
|
using DiscordChatExporter.Core.Discord.Data.Embeds;
|
||||||
using DiscordChatExporter.Core.Exporting.Writers.MarkdownVisitors;
|
using DiscordChatExporter.Core.Exporting.Writers.MarkdownVisitors;
|
||||||
|
|
||||||
namespace DiscordChatExporter.Core.Exporting.Writers
|
namespace DiscordChatExporter.Core.Exporting.Writers;
|
||||||
|
|
||||||
|
internal class PlainTextMessageWriter : MessageWriter
|
||||||
{
|
{
|
||||||
internal class PlainTextMessageWriter : MessageWriter
|
|
||||||
{
|
|
||||||
private readonly TextWriter _writer;
|
private readonly TextWriter _writer;
|
||||||
|
|
||||||
public PlainTextMessageWriter(Stream stream, ExportContext context)
|
public PlainTextMessageWriter(Stream stream, ExportContext context)
|
||||||
|
|
@ -176,5 +176,4 @@ namespace DiscordChatExporter.Core.Exporting.Writers
|
||||||
await _writer.DisposeAsync();
|
await _writer.DisposeAsync();
|
||||||
await base.DisposeAsync();
|
await base.DisposeAsync();
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
@ -1,14 +1,14 @@
|
||||||
using DiscordChatExporter.Core.Utils;
|
using DiscordChatExporter.Core.Utils;
|
||||||
|
|
||||||
namespace DiscordChatExporter.Core.Markdown
|
namespace DiscordChatExporter.Core.Markdown;
|
||||||
{
|
|
||||||
internal record EmojiNode(
|
internal record EmojiNode(
|
||||||
// Only present on custom emoji
|
// Only present on custom emoji
|
||||||
string? Id,
|
string? Id,
|
||||||
// Name of custom emoji (e.g. LUL) or actual representation of standard emoji (e.g. 🙂)
|
// Name of custom emoji (e.g. LUL) or actual representation of standard emoji (e.g. 🙂)
|
||||||
string Name,
|
string Name,
|
||||||
bool IsAnimated) : MarkdownNode
|
bool IsAnimated) : MarkdownNode
|
||||||
{
|
{
|
||||||
// Name of custom emoji (e.g. LUL) or name of standard emoji (e.g. slight_smile)
|
// Name of custom emoji (e.g. LUL) or name of standard emoji (e.g. slight_smile)
|
||||||
public string Code => !string.IsNullOrWhiteSpace(Id)
|
public string Code => !string.IsNullOrWhiteSpace(Id)
|
||||||
? Name
|
? Name
|
||||||
|
|
@ -20,5 +20,4 @@ namespace DiscordChatExporter.Core.Markdown
|
||||||
: this(null, name, false)
|
: this(null, name, false)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
@ -1,12 +1,11 @@
|
||||||
namespace DiscordChatExporter.Core.Markdown
|
namespace DiscordChatExporter.Core.Markdown;
|
||||||
|
|
||||||
|
internal enum FormattingKind
|
||||||
{
|
{
|
||||||
internal enum FormattingKind
|
|
||||||
{
|
|
||||||
Bold,
|
Bold,
|
||||||
Italic,
|
Italic,
|
||||||
Underline,
|
Underline,
|
||||||
Strikethrough,
|
Strikethrough,
|
||||||
Spoiler,
|
Spoiler,
|
||||||
Quote
|
Quote
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
@ -1,6 +1,5 @@
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
|
||||||
namespace DiscordChatExporter.Core.Markdown
|
namespace DiscordChatExporter.Core.Markdown;
|
||||||
{
|
|
||||||
internal record FormattingNode(FormattingKind Kind, IReadOnlyList<MarkdownNode> Children) : MarkdownNode;
|
internal record FormattingNode(FormattingKind Kind, IReadOnlyList<MarkdownNode> Children) : MarkdownNode;
|
||||||
}
|
|
||||||
|
|
@ -1,4 +1,3 @@
|
||||||
namespace DiscordChatExporter.Core.Markdown
|
namespace DiscordChatExporter.Core.Markdown;
|
||||||
{
|
|
||||||
internal record InlineCodeBlockNode(string Code) : MarkdownNode;
|
internal record InlineCodeBlockNode(string Code) : MarkdownNode;
|
||||||
}
|
|
||||||
|
|
@ -1,14 +1,13 @@
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
|
||||||
namespace DiscordChatExporter.Core.Markdown
|
namespace DiscordChatExporter.Core.Markdown;
|
||||||
{
|
|
||||||
internal record LinkNode(
|
internal record LinkNode(
|
||||||
string Url,
|
string Url,
|
||||||
IReadOnlyList<MarkdownNode> Children) : MarkdownNode
|
IReadOnlyList<MarkdownNode> Children) : MarkdownNode
|
||||||
{
|
{
|
||||||
public LinkNode(string url)
|
public LinkNode(string url)
|
||||||
: this(url, new[] { new TextNode(url) })
|
: this(url, new[] { new TextNode(url) })
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
@ -1,4 +1,3 @@
|
||||||
namespace DiscordChatExporter.Core.Markdown
|
namespace DiscordChatExporter.Core.Markdown;
|
||||||
{
|
|
||||||
internal abstract record MarkdownNode;
|
internal abstract record MarkdownNode;
|
||||||
}
|
|
||||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue