diff --git a/DiscordChatExporter.Cli.Tests/Specs/FilterSpecs.cs b/DiscordChatExporter.Cli.Tests/Specs/FilterSpecs.cs index d5b7675..023b7bf 100644 --- a/DiscordChatExporter.Cli.Tests/Specs/FilterSpecs.cs +++ b/DiscordChatExporter.Cli.Tests/Specs/FilterSpecs.cs @@ -37,7 +37,7 @@ public class FilterSpecs .EnumerateArray() .Select(j => j.GetProperty("content").GetString()) .Should() - .ContainSingle("Some random text"); + .AllBe("Some random text"); } [Fact] @@ -66,7 +66,7 @@ public class FilterSpecs } [Fact] - public async Task I_can_filter_the_export_to_only_include_messages_that_contain_the_specified_content() + public async Task I_can_filter_the_export_to_only_include_messages_that_contain_images() { // Arrange using var file = TempFile.Create(); @@ -87,7 +87,32 @@ public class FilterSpecs .EnumerateArray() .Select(j => j.GetProperty("content").GetString()) .Should() - .ContainSingle("This has image"); + .AllBe("This has image"); + } + + [Fact] + public async Task I_can_filter_the_export_to_only_include_messages_that_contain_guild_invites() + { + // Arrange + using var file = TempFile.Create(); + + // Act + await new ExportChannelsCommand + { + Token = Secrets.DiscordToken, + ChannelIds = [ChannelIds.FilterTestCases], + ExportFormat = ExportFormat.Json, + OutputPath = file.Path, + MessageFilter = MessageFilter.Parse("has:invite") + }.ExecuteAsync(new FakeConsole()); + + // Assert + Json.Parse(await File.ReadAllTextAsync(file.Path)) + .GetProperty("messages") + .EnumerateArray() + .Select(j => j.GetProperty("content").GetString()) + .Should() + .AllBe("This has invite"); } [Fact] @@ -112,7 +137,7 @@ public class FilterSpecs .EnumerateArray() .Select(j => j.GetProperty("content").GetString()) .Should() - .ContainSingle("This is pinned"); + .AllBe("This is pinned"); } [Fact] @@ -137,6 +162,6 @@ public class FilterSpecs .EnumerateArray() .Select(j => j.GetProperty("content").GetString()) .Should() - .ContainSingle("This has mention"); + .AllBe("This has mention"); } } diff --git a/DiscordChatExporter.Core/Exporting/Filtering/HasMessageFilter.cs b/DiscordChatExporter.Core/Exporting/Filtering/HasMessageFilter.cs index 0065396..c061ad3 100644 --- a/DiscordChatExporter.Core/Exporting/Filtering/HasMessageFilter.cs +++ b/DiscordChatExporter.Core/Exporting/Filtering/HasMessageFilter.cs @@ -1,7 +1,7 @@ using System; using System.Linq; -using System.Text.RegularExpressions; using DiscordChatExporter.Core.Discord.Data; +using DiscordChatExporter.Core.Markdown.Parsing; namespace DiscordChatExporter.Core.Exporting.Filtering; @@ -10,14 +10,19 @@ internal class HasMessageFilter(MessageContentMatchKind kind) : MessageFilter public override bool IsMatch(Message message) => kind switch { - MessageContentMatchKind.Link - => Regex.IsMatch(message.Content, "https?://\\S*[^\\.,:;\"\'\\s]"), + MessageContentMatchKind.Link => MarkdownParser.ExtractLinks(message.Content).Any(), MessageContentMatchKind.Embed => message.Embeds.Any(), MessageContentMatchKind.File => message.Attachments.Any(), MessageContentMatchKind.Video => message.Attachments.Any(file => file.IsVideo), MessageContentMatchKind.Image => message.Attachments.Any(file => file.IsImage), MessageContentMatchKind.Sound => message.Attachments.Any(file => file.IsAudio), MessageContentMatchKind.Pin => message.IsPinned, + MessageContentMatchKind.Invite + => MarkdownParser + .ExtractLinks(message.Content) + .Select(l => l.Url) + .Select(Invite.TryGetCodeFromUrl) + .Any(c => !string.IsNullOrWhiteSpace(c)), _ => throw new InvalidOperationException( $"Unknown message content match kind '{kind}'." diff --git a/DiscordChatExporter.Core/Exporting/Filtering/MessageContentMatchKind.cs b/DiscordChatExporter.Core/Exporting/Filtering/MessageContentMatchKind.cs index b5f10f1..d649661 100644 --- a/DiscordChatExporter.Core/Exporting/Filtering/MessageContentMatchKind.cs +++ b/DiscordChatExporter.Core/Exporting/Filtering/MessageContentMatchKind.cs @@ -8,5 +8,6 @@ internal enum MessageContentMatchKind Video, Image, Sound, - Pin + Pin, + Invite } diff --git a/DiscordChatExporter.Core/Exporting/Filtering/Parsing/FilterGrammar.cs b/DiscordChatExporter.Core/Exporting/Filtering/Parsing/FilterGrammar.cs index e05a8ae..8aaac42 100644 --- a/DiscordChatExporter.Core/Exporting/Filtering/Parsing/FilterGrammar.cs +++ b/DiscordChatExporter.Core/Exporting/Filtering/Parsing/FilterGrammar.cs @@ -61,18 +61,28 @@ internal static class FilterGrammar .IgnoreThen( Parse.OneOf( Span.EqualToIgnoreCase("link") - .IgnoreThen(Parse.Return(MessageContentMatchKind.Link)), + .IgnoreThen(Parse.Return(MessageContentMatchKind.Link)) + .Try(), Span.EqualToIgnoreCase("embed") - .IgnoreThen(Parse.Return(MessageContentMatchKind.Embed)), + .IgnoreThen(Parse.Return(MessageContentMatchKind.Embed)) + .Try(), Span.EqualToIgnoreCase("file") - .IgnoreThen(Parse.Return(MessageContentMatchKind.File)), + .IgnoreThen(Parse.Return(MessageContentMatchKind.File)) + .Try(), Span.EqualToIgnoreCase("video") - .IgnoreThen(Parse.Return(MessageContentMatchKind.Video)), + .IgnoreThen(Parse.Return(MessageContentMatchKind.Video)) + .Try(), Span.EqualToIgnoreCase("image") - .IgnoreThen(Parse.Return(MessageContentMatchKind.Image)), + .IgnoreThen(Parse.Return(MessageContentMatchKind.Image)) + .Try(), Span.EqualToIgnoreCase("sound") .IgnoreThen(Parse.Return(MessageContentMatchKind.Sound)), - Span.EqualToIgnoreCase("pin").IgnoreThen(Parse.Return(MessageContentMatchKind.Pin)) + Span.EqualToIgnoreCase("pin") + .IgnoreThen(Parse.Return(MessageContentMatchKind.Pin)) + .Try(), + Span.EqualToIgnoreCase("invite") + .IgnoreThen(Parse.Return(MessageContentMatchKind.Invite)) + .Try() ) ) .Select(k => (MessageFilter)new HasMessageFilter(k))