diff --git a/DiscordChatExporter.Cli/Commands/Base/ExportCommandBase.cs b/DiscordChatExporter.Cli/Commands/Base/ExportCommandBase.cs
index e99fc98e..92378682 100644
--- a/DiscordChatExporter.Cli/Commands/Base/ExportCommandBase.cs
+++ b/DiscordChatExporter.Cli/Commands/Base/ExportCommandBase.cs
@@ -33,11 +33,11 @@ namespace DiscordChatExporter.Cli.Commands.Base
[CommandOption("before", Description = "Only include messages sent before this date or message ID.")]
public Snowflake? Before { get; init; }
- [CommandOption("partition", 'p', Description = "Split output into partitions, each limited to this number of messages (e.g. 100) or file size (e.g. 10mb).")]
- public PartitionLimit PartitionLimit { get; init; } = NullPartitionLimit.Instance;
+ [CommandOption("partition", 'p', Description = "Split output into partitions, each limited to this number of messages (e.g. '100') or file size (e.g. '10mb').")]
+ public PartitionLimit PartitionLimit { get; init; } = PartitionLimit.Null;
- [CommandOption("filter", Description = "Only include messages that satisfy this filter (e.g. from:foo#1234).")]
- public MessageFilter MessageFilter { get; init; } = NullMessageFilter.Instance;
+ [CommandOption("filter", Description = "Only include messages that satisfy this filter (e.g. 'from:foo#1234' or 'has:image').")]
+ public MessageFilter MessageFilter { get; init; } = MessageFilter.Null;
[CommandOption("parallel", Description = "Limits how many channels can be exported in parallel.")]
public int ParallelLimit { get; init; } = 1;
@@ -133,8 +133,6 @@ namespace DiscordChatExporter.Cli.Commands.Base
{
throw new CommandException("Export failed.");
}
-
- await console.Output.WriteLineAsync("Done.");
}
public override ValueTask ExecuteAsync(IConsole console)
diff --git a/DiscordChatExporter.Cli/DiscordChatExporter.Cli.csproj b/DiscordChatExporter.Cli/DiscordChatExporter.Cli.csproj
index 587735ba..3a85ea1e 100644
--- a/DiscordChatExporter.Cli/DiscordChatExporter.Cli.csproj
+++ b/DiscordChatExporter.Cli/DiscordChatExporter.Cli.csproj
@@ -6,8 +6,8 @@
-
-
+
+
diff --git a/DiscordChatExporter.Core/Exporting/Filtering/BinaryExpressionKind.cs b/DiscordChatExporter.Core/Exporting/Filtering/BinaryExpressionKind.cs
index 962ee062..16bd5d72 100644
--- a/DiscordChatExporter.Core/Exporting/Filtering/BinaryExpressionKind.cs
+++ b/DiscordChatExporter.Core/Exporting/Filtering/BinaryExpressionKind.cs
@@ -1,6 +1,6 @@
namespace DiscordChatExporter.Core.Exporting.Filtering
{
- public enum BinaryExpressionKind
+ internal enum BinaryExpressionKind
{
Or,
And
diff --git a/DiscordChatExporter.Core/Exporting/Filtering/BinaryExpressionMessageFilter.cs b/DiscordChatExporter.Core/Exporting/Filtering/BinaryExpressionMessageFilter.cs
index 19467266..6a187cd6 100644
--- a/DiscordChatExporter.Core/Exporting/Filtering/BinaryExpressionMessageFilter.cs
+++ b/DiscordChatExporter.Core/Exporting/Filtering/BinaryExpressionMessageFilter.cs
@@ -1,9 +1,9 @@
-using DiscordChatExporter.Core.Discord.Data;
-using System;
+using System;
+using DiscordChatExporter.Core.Discord.Data;
namespace DiscordChatExporter.Core.Exporting.Filtering
{
- public class BinaryExpressionMessageFilter : MessageFilter
+ internal class BinaryExpressionMessageFilter : MessageFilter
{
private readonly MessageFilter _first;
private readonly MessageFilter _second;
@@ -19,8 +19,8 @@ namespace DiscordChatExporter.Core.Exporting.Filtering
public override bool Filter(Message message) => _kind switch
{
BinaryExpressionKind.Or => _first.Filter(message) || _second.Filter(message),
- BinaryExpressionKind.And => _first.Filter(message) && _second.Filter(message),
+ BinaryExpressionKind.And => _first.Filter(message) && _second.Filter(message),
_ => throw new InvalidOperationException($"Unknown binary expression kind '{_kind}'.")
};
}
-}
+}
\ No newline at end of file
diff --git a/DiscordChatExporter.Core/Exporting/Filtering/ContainsMessageFilter.cs b/DiscordChatExporter.Core/Exporting/Filtering/ContainsMessageFilter.cs
index 9b59460b..d956d607 100644
--- a/DiscordChatExporter.Core/Exporting/Filtering/ContainsMessageFilter.cs
+++ b/DiscordChatExporter.Core/Exporting/Filtering/ContainsMessageFilter.cs
@@ -1,15 +1,18 @@
-using DiscordChatExporter.Core.Discord.Data;
-using System.Text.RegularExpressions;
+using System.Text.RegularExpressions;
+using DiscordChatExporter.Core.Discord.Data;
namespace DiscordChatExporter.Core.Exporting.Filtering
{
- public class ContainsMessageFilter : MessageFilter
+ internal class ContainsMessageFilter : MessageFilter
{
- private readonly string _value;
+ private readonly string _text;
- public ContainsMessageFilter(string value) => _value = value;
+ public ContainsMessageFilter(string text) => _text = text;
- public override bool Filter(Message message) =>
- Regex.IsMatch(message.Content, $@"\b{Regex.Escape(_value)}\b", RegexOptions.IgnoreCase | DefaultRegexOptions);
+ public override bool Filter(Message message) => Regex.IsMatch(
+ message.Content,
+ "\\b" + _text + "\\b",
+ RegexOptions.IgnoreCase | RegexOptions.CultureInvariant
+ );
}
}
\ No newline at end of file
diff --git a/DiscordChatExporter.Core/Exporting/Filtering/FromMessageFilter.cs b/DiscordChatExporter.Core/Exporting/Filtering/FromMessageFilter.cs
index fb2f863a..f6da2c70 100644
--- a/DiscordChatExporter.Core/Exporting/Filtering/FromMessageFilter.cs
+++ b/DiscordChatExporter.Core/Exporting/Filtering/FromMessageFilter.cs
@@ -1,9 +1,9 @@
-using DiscordChatExporter.Core.Discord.Data;
-using System;
+using System;
+using DiscordChatExporter.Core.Discord.Data;
namespace DiscordChatExporter.Core.Exporting.Filtering
{
- public class FromMessageFilter : MessageFilter
+ internal class FromMessageFilter : MessageFilter
{
private readonly string _value;
@@ -14,4 +14,4 @@ namespace DiscordChatExporter.Core.Exporting.Filtering
string.Equals(_value, message.Author.FullName, StringComparison.OrdinalIgnoreCase) ||
string.Equals(_value, message.Author.Id.ToString(), StringComparison.OrdinalIgnoreCase);
}
-}
+}
\ No newline at end of file
diff --git a/DiscordChatExporter.Core/Exporting/Filtering/HasMessageFilter.cs b/DiscordChatExporter.Core/Exporting/Filtering/HasMessageFilter.cs
index 53c8fcab..4ef94f52 100644
--- a/DiscordChatExporter.Core/Exporting/Filtering/HasMessageFilter.cs
+++ b/DiscordChatExporter.Core/Exporting/Filtering/HasMessageFilter.cs
@@ -1,26 +1,25 @@
-using DiscordChatExporter.Core.Discord.Data;
-using System;
+using System;
using System.Linq;
using System.Text.RegularExpressions;
+using DiscordChatExporter.Core.Discord.Data;
namespace DiscordChatExporter.Core.Exporting.Filtering
{
- public class HasMessageFilter : MessageFilter
+ internal class HasMessageFilter : MessageFilter
{
- private readonly string _value;
+ private readonly MessageContentMatchKind _kind;
- public HasMessageFilter(string value) => _value = value;
+ public HasMessageFilter(MessageContentMatchKind kind) => _kind = kind;
- public override bool Filter(Message message) =>
- _value switch
- {
- "link" => Regex.IsMatch(message.Content, "https?://\\S*[^\\.,:;\"\'\\s]", DefaultRegexOptions),
- "embed" => message.Embeds.Any(),
- "file" => message.Attachments.Any(),
- "video" => message.Attachments.Any(file => file.IsVideo),
- "image" => message.Attachments.Any(file => file.IsImage),
- "sound" => message.Attachments.Any(file => file.IsAudio),
- _ => throw new InvalidOperationException($"Invalid value provided for the 'has' message filter: '{_value}'")
- };
+ public override bool Filter(Message message) => _kind switch
+ {
+ MessageContentMatchKind.Link => Regex.IsMatch(message.Content, "https?://\\S*[^\\.,:;\"\'\\s]"),
+ 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),
+ _ => throw new InvalidOperationException($"Unknown message content match kind '{_kind}'.")
+ };
}
-}
+}
\ No newline at end of file
diff --git a/DiscordChatExporter.Core/Exporting/Filtering/MentionsMessageFilter.cs b/DiscordChatExporter.Core/Exporting/Filtering/MentionsMessageFilter.cs
index 9bc21794..f96bc88a 100644
--- a/DiscordChatExporter.Core/Exporting/Filtering/MentionsMessageFilter.cs
+++ b/DiscordChatExporter.Core/Exporting/Filtering/MentionsMessageFilter.cs
@@ -1,19 +1,19 @@
-using DiscordChatExporter.Core.Discord.Data;
-using System;
+using System;
using System.Linq;
+using DiscordChatExporter.Core.Discord.Data;
namespace DiscordChatExporter.Core.Exporting.Filtering
{
- public class MentionsMessageFilter : MessageFilter
+ internal class MentionsMessageFilter : MessageFilter
{
private readonly string _value;
public MentionsMessageFilter(string value) => _value = value;
- public override bool Filter(Message message) =>
- message.MentionedUsers.Any(user =>
- string.Equals(_value, user.Name, StringComparison.OrdinalIgnoreCase) ||
- string.Equals(_value, user.FullName, StringComparison.OrdinalIgnoreCase) ||
- string.Equals(_value, user.Id.ToString(), StringComparison.OrdinalIgnoreCase));
+ public override bool Filter(Message message) => message.MentionedUsers.Any(user =>
+ string.Equals(_value, user.Name, StringComparison.OrdinalIgnoreCase) ||
+ string.Equals(_value, user.FullName, StringComparison.OrdinalIgnoreCase) ||
+ string.Equals(_value, user.Id.ToString(), StringComparison.OrdinalIgnoreCase)
+ );
}
}
\ No newline at end of file
diff --git a/DiscordChatExporter.Core/Exporting/Filtering/MessageContentMatchKind.cs b/DiscordChatExporter.Core/Exporting/Filtering/MessageContentMatchKind.cs
new file mode 100644
index 00000000..549a38f0
--- /dev/null
+++ b/DiscordChatExporter.Core/Exporting/Filtering/MessageContentMatchKind.cs
@@ -0,0 +1,12 @@
+namespace DiscordChatExporter.Core.Exporting.Filtering
+{
+ internal enum MessageContentMatchKind
+ {
+ Link,
+ Embed,
+ File,
+ Video,
+ Image,
+ Sound
+ }
+}
\ No newline at end of file
diff --git a/DiscordChatExporter.Core/Exporting/Filtering/MessageFilter.cs b/DiscordChatExporter.Core/Exporting/Filtering/MessageFilter.cs
index 6593f05e..a7bbb093 100644
--- a/DiscordChatExporter.Core/Exporting/Filtering/MessageFilter.cs
+++ b/DiscordChatExporter.Core/Exporting/Filtering/MessageFilter.cs
@@ -1,6 +1,4 @@
-using System;
-using System.Text.RegularExpressions;
-using DiscordChatExporter.Core.Discord.Data;
+using DiscordChatExporter.Core.Discord.Data;
using DiscordChatExporter.Core.Exporting.Filtering.Parsing;
using Superpower;
@@ -13,26 +11,8 @@ namespace DiscordChatExporter.Core.Exporting.Filtering
public partial class MessageFilter
{
- protected const RegexOptions DefaultRegexOptions = RegexOptions.Compiled | RegexOptions.CultureInvariant | RegexOptions.Multiline;
+ public static MessageFilter Null { get; } = new NullMessageFilter();
- internal static MessageFilter CreateFilter(string text) => new ContainsMessageFilter(text);
-
- internal static MessageFilter CreateFilter(string key, string value)
- {
- return key.ToLowerInvariant() switch
- {
- "from" => new FromMessageFilter(value),
- "has" => new HasMessageFilter(value),
- "mentions" => new MentionsMessageFilter(value),
- _ => throw new ArgumentException($"Invalid filter type '{key}'.", nameof(key))
- };
- }
-
- public static MessageFilter Parse(string value, IFormatProvider? formatProvider = null)
- {
- var tokens = FilterTokenizer.Instance.Tokenize(value);
- var parsed = FilterParser.Instance.Parse(tokens);
- return parsed;
- }
+ public static MessageFilter Parse(string value) => FilterGrammar.Filter.Parse(value);
}
-}
+}
\ No newline at end of file
diff --git a/DiscordChatExporter.Core/Exporting/Filtering/NegatedMessageFilter.cs b/DiscordChatExporter.Core/Exporting/Filtering/NegatedMessageFilter.cs
index e2a554c9..2c144d0a 100644
--- a/DiscordChatExporter.Core/Exporting/Filtering/NegatedMessageFilter.cs
+++ b/DiscordChatExporter.Core/Exporting/Filtering/NegatedMessageFilter.cs
@@ -2,7 +2,7 @@
namespace DiscordChatExporter.Core.Exporting.Filtering
{
- public class NegatedMessageFilter : MessageFilter
+ internal class NegatedMessageFilter : MessageFilter
{
private readonly MessageFilter _filter;
diff --git a/DiscordChatExporter.Core/Exporting/Filtering/NullMessageFilter.cs b/DiscordChatExporter.Core/Exporting/Filtering/NullMessageFilter.cs
index d213d194..d68b6b64 100644
--- a/DiscordChatExporter.Core/Exporting/Filtering/NullMessageFilter.cs
+++ b/DiscordChatExporter.Core/Exporting/Filtering/NullMessageFilter.cs
@@ -2,10 +2,8 @@
namespace DiscordChatExporter.Core.Exporting.Filtering
{
- public class NullMessageFilter : MessageFilter
+ internal class NullMessageFilter : MessageFilter
{
- public static NullMessageFilter Instance { get; } = new();
-
public override bool Filter(Message message) => true;
}
}
\ No newline at end of file
diff --git a/DiscordChatExporter.Core/Exporting/Filtering/Parsing/FilterGrammar.cs b/DiscordChatExporter.Core/Exporting/Filtering/Parsing/FilterGrammar.cs
new file mode 100644
index 00000000..d1d186bf
--- /dev/null
+++ b/DiscordChatExporter.Core/Exporting/Filtering/Parsing/FilterGrammar.cs
@@ -0,0 +1,106 @@
+using System.Linq;
+using DiscordChatExporter.Core.Utils.Extensions;
+using Superpower;
+using Superpower.Parsers;
+
+namespace DiscordChatExporter.Core.Exporting.Filtering.Parsing
+{
+ internal static class FilterGrammar
+ {
+ // Choice(a, b) looks cleaner than a.Or(b)
+ private static TextParser Choice(params TextParser[] parsers) =>
+ parsers.Aggregate((current, next) => current.Or(next));
+
+ private static readonly TextParser EscapedCharacter =
+ Character.EqualTo('\\').IgnoreThen(Character.AnyChar);
+
+ private static readonly TextParser QuotedString =
+ from open in Character.In('"', '\'')
+ from value in Choice(EscapedCharacter, Character.Except(open)).Many().Text()
+ from close in Character.EqualTo(open)
+ select value;
+
+ private static readonly TextParser FreeCharacter =
+ Character.Matching(c =>
+ !char.IsWhiteSpace(c) &&
+ // Avoid all special tokens used by the grammar
+ c is not ('(' or ')' or '"' or '\'' or '-' or '|' or '&'),
+ "any character except whitespace or `(`, `)`, `\"`, `'`, `-`, `|`, `&`"
+ );
+
+ private static readonly TextParser UnquotedString =
+ Choice(EscapedCharacter, FreeCharacter).AtLeastOnce().Text();
+
+ private static readonly TextParser String =
+ Choice(QuotedString, UnquotedString).Named("text string");
+
+ private static readonly TextParser ContainsFilter =
+ String.Select(v => (MessageFilter) new ContainsMessageFilter(v));
+
+ private static readonly TextParser FromFilter = Span
+ .EqualToIgnoreCase("from:")
+ .IgnoreThen(String)
+ .Select(v => (MessageFilter) new FromMessageFilter(v))
+ .Named("from:");
+
+ private static readonly TextParser MentionsFilter = Span
+ .EqualToIgnoreCase("mentions:")
+ .IgnoreThen(String)
+ .Select(v => (MessageFilter) new MentionsMessageFilter(v))
+ .Named("mentions:");
+
+ private static readonly TextParser HasFilter = Span
+ .EqualToIgnoreCase("has:")
+ .IgnoreThen(Choice(
+ Span.EqualToIgnoreCase("link").IgnoreThen(Parse.Return(MessageContentMatchKind.Link)),
+ Span.EqualToIgnoreCase("embed").IgnoreThen(Parse.Return(MessageContentMatchKind.Embed)),
+ Span.EqualToIgnoreCase("video").IgnoreThen(Parse.Return(MessageContentMatchKind.Video)),
+ Span.EqualToIgnoreCase("image").IgnoreThen(Parse.Return(MessageContentMatchKind.Image)),
+ Span.EqualToIgnoreCase("sound").IgnoreThen(Parse.Return(MessageContentMatchKind.Sound))
+ ))
+ .Select(k => (MessageFilter) new HasMessageFilter(k))
+ .Named("has:");
+
+ private static readonly TextParser NegatedFilter = Character
+ .EqualTo('-')
+ .IgnoreThen(Parse.Ref(() => StandaloneFilter))
+ .Select(f => (MessageFilter) new NegatedMessageFilter(f));
+
+ private static readonly TextParser GroupedFilter =
+ from open in Character.EqualTo('(')
+ from content in Parse.Ref(() => BinaryExpressionFilter).Token()
+ from close in Character.EqualTo(')')
+ select content;
+
+ private static readonly TextParser StandaloneFilter = Choice(
+ GroupedFilter,
+ FromFilter,
+ MentionsFilter,
+ HasFilter,
+ ContainsFilter
+ );
+
+ private static readonly TextParser UnaryExpressionFilter = Choice(
+ NegatedFilter,
+ StandaloneFilter
+ );
+
+ private static readonly TextParser BinaryExpressionFilter = Parse.Chain(
+ Choice(
+ // Explicit operator
+ Character.In('|', '&').Token().Try(),
+ // Implicit operator (resolves to 'and')
+ Character.WhiteSpace.AtLeastOnce().IgnoreThen(Parse.Return(' '))
+ ),
+ UnaryExpressionFilter,
+ (op, left, right) => op switch
+ {
+ '|' => new BinaryExpressionMessageFilter(left, right, BinaryExpressionKind.Or),
+ _ => new BinaryExpressionMessageFilter(left, right, BinaryExpressionKind.And)
+ }
+ );
+
+ public static readonly TextParser Filter =
+ BinaryExpressionFilter.Token().AtEnd();
+ }
+}
\ No newline at end of file
diff --git a/DiscordChatExporter.Core/Exporting/Filtering/Parsing/FilterParser.cs b/DiscordChatExporter.Core/Exporting/Filtering/Parsing/FilterParser.cs
deleted file mode 100644
index 79b57859..00000000
--- a/DiscordChatExporter.Core/Exporting/Filtering/Parsing/FilterParser.cs
+++ /dev/null
@@ -1,68 +0,0 @@
-using Superpower;
-using Superpower.Model;
-using Superpower.Parsers;
-using System;
-using System.Collections.Generic;
-using System.Diagnostics.CodeAnalysis;
-using System.Linq;
-using System.Text;
-
-namespace DiscordChatExporter.Core.Exporting.Filtering.Parsing
-{
- public static class FilterParser
- {
- public static TextParser QuotedString { get; } =
- from open in Character.EqualTo('"')
- from content in Character.EqualTo('\\').IgnoreThen(Character.AnyChar).Try()
- .Or(Character.Except('"'))
- .Many()
- from close in Character.EqualTo('"')
- select new string(content);
-
- public static TextParser UnquotedString { get; } =
- from content in Character.EqualTo('\\').IgnoreThen(Character.In('"', '/')).Try()
- .Or(Character.Except(c => char.IsWhiteSpace(c) || "():-|\"".Contains(c), "non-whitespace character except for (, ), :, -, |, and \""))
- .AtLeastOnce()
- select new string(content);
-
- public static TokenListParser AnyString { get; } =
- Token.EqualTo(FilterToken.QuotedString).Apply(QuotedString)
- .Or(Token.EqualTo(FilterToken.UnquotedString).Apply(UnquotedString));
-
- public static TokenListParser AnyFilter { get; } =
- from minus in Token.EqualTo(FilterToken.Minus).Optional()
- from content in KeyValueFilter.Or(TextFilter).Or(GroupedFilter)
- select minus.HasValue ? new NegatedMessageFilter(content) : content;
-
- public static TokenListParser TextFilter { get; } =
- from value in AnyString
- select MessageFilter.CreateFilter(value);
-
- public static TokenListParser KeyValueFilter { get; } =
- from key in AnyString.Try()
- from colon in Token.EqualTo(FilterToken.Colon).Try()
- from value in AnyString
- select MessageFilter.CreateFilter(key, value);
-
- public static TokenListParser GroupedFilter { get; } =
- from open in Token.EqualTo(FilterToken.LParen)
- from content in BinaryExpression
- from close in Token.EqualTo(FilterToken.RParen)
- select content;
-
- public static TokenListParser OrBinaryExpression { get; } =
- from first in AnyFilter
- from vbar in Token.EqualTo(FilterToken.VBar)
- from rest in BinaryExpression
- select (MessageFilter)new BinaryExpressionMessageFilter(first, rest, BinaryExpressionKind.Or);
-
- public static TokenListParser AndBinaryExpression { get; } =
- from first in AnyFilter
- from rest in BinaryExpression
- select (MessageFilter)new BinaryExpressionMessageFilter(first, rest, BinaryExpressionKind.And);
-
- public static TokenListParser BinaryExpression { get; } = OrBinaryExpression.Try().Or(AndBinaryExpression.Try()).Or(AnyFilter);
-
- public static TokenListParser Instance { get; } = BinaryExpression.AtEnd();
- }
-}
diff --git a/DiscordChatExporter.Core/Exporting/Filtering/Parsing/FilterToken.cs b/DiscordChatExporter.Core/Exporting/Filtering/Parsing/FilterToken.cs
deleted file mode 100644
index 222525ad..00000000
--- a/DiscordChatExporter.Core/Exporting/Filtering/Parsing/FilterToken.cs
+++ /dev/null
@@ -1,18 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Text;
-
-namespace DiscordChatExporter.Core.Exporting.Filtering.Parsing
-{
- public enum FilterToken
- {
- None,
- LParen,
- RParen,
- Colon,
- Minus,
- VBar,
- UnquotedString,
- QuotedString
- }
-}
diff --git a/DiscordChatExporter.Core/Exporting/Filtering/Parsing/FilterTokenizer.cs b/DiscordChatExporter.Core/Exporting/Filtering/Parsing/FilterTokenizer.cs
deleted file mode 100644
index 11bd3e3d..00000000
--- a/DiscordChatExporter.Core/Exporting/Filtering/Parsing/FilterTokenizer.cs
+++ /dev/null
@@ -1,23 +0,0 @@
-using Superpower;
-using Superpower.Parsers;
-using Superpower.Tokenizers;
-using System;
-using System.Collections.Generic;
-using System.Text;
-
-namespace DiscordChatExporter.Core.Exporting.Filtering.Parsing
-{
- public static class FilterTokenizer
- {
- public static Tokenizer Instance { get; } = new TokenizerBuilder()
- .Ignore(Span.WhiteSpace)
- .Match(Character.EqualTo('('), FilterToken.LParen)
- .Match(Character.EqualTo(')'), FilterToken.RParen)
- .Match(Character.EqualTo(':'), FilterToken.Colon)
- .Match(Character.EqualTo('-'), FilterToken.Minus)
- .Match(Character.EqualTo('|'), FilterToken.VBar)
- .Match(FilterParser.QuotedString, FilterToken.QuotedString)
- .Match(FilterParser.UnquotedString, FilterToken.UnquotedString)
- .Build();
- }
-}
diff --git a/DiscordChatExporter.Core/Exporting/Partitioning/FileSizePartitionLimit.cs b/DiscordChatExporter.Core/Exporting/Partitioning/FileSizePartitionLimit.cs
index 1baffbb0..a2e6bacc 100644
--- a/DiscordChatExporter.Core/Exporting/Partitioning/FileSizePartitionLimit.cs
+++ b/DiscordChatExporter.Core/Exporting/Partitioning/FileSizePartitionLimit.cs
@@ -1,6 +1,6 @@
namespace DiscordChatExporter.Core.Exporting.Partitioning
{
- public class FileSizePartitionLimit : PartitionLimit
+ internal class FileSizePartitionLimit : PartitionLimit
{
private readonly long _limit;
diff --git a/DiscordChatExporter.Core/Exporting/Partitioning/MessageCountPartitionLimit.cs b/DiscordChatExporter.Core/Exporting/Partitioning/MessageCountPartitionLimit.cs
index 318ffc6e..83f3379f 100644
--- a/DiscordChatExporter.Core/Exporting/Partitioning/MessageCountPartitionLimit.cs
+++ b/DiscordChatExporter.Core/Exporting/Partitioning/MessageCountPartitionLimit.cs
@@ -1,6 +1,6 @@
namespace DiscordChatExporter.Core.Exporting.Partitioning
{
- public class MessageCountPartitionLimit : PartitionLimit
+ internal class MessageCountPartitionLimit : PartitionLimit
{
private readonly long _limit;
diff --git a/DiscordChatExporter.Core/Exporting/Partitioning/NullPartitionLimit.cs b/DiscordChatExporter.Core/Exporting/Partitioning/NullPartitionLimit.cs
index d38546ff..4017a371 100644
--- a/DiscordChatExporter.Core/Exporting/Partitioning/NullPartitionLimit.cs
+++ b/DiscordChatExporter.Core/Exporting/Partitioning/NullPartitionLimit.cs
@@ -1,9 +1,7 @@
namespace DiscordChatExporter.Core.Exporting.Partitioning
{
- public class NullPartitionLimit : PartitionLimit
+ internal class NullPartitionLimit : PartitionLimit
{
- public static NullPartitionLimit Instance { get; } = new();
-
public override bool IsReached(long messagesWritten, long bytesWritten) => false;
}
}
\ No newline at end of file
diff --git a/DiscordChatExporter.Core/Exporting/Partitioning/PartitionLimit.cs b/DiscordChatExporter.Core/Exporting/Partitioning/PartitionLimit.cs
index 1a82259e..f1336d68 100644
--- a/DiscordChatExporter.Core/Exporting/Partitioning/PartitionLimit.cs
+++ b/DiscordChatExporter.Core/Exporting/Partitioning/PartitionLimit.cs
@@ -11,6 +11,8 @@ namespace DiscordChatExporter.Core.Exporting.Partitioning
public partial class PartitionLimit
{
+ public static PartitionLimit Null { get; } = new NullPartitionLimit();
+
private static long? TryParseFileSizeBytes(string value, IFormatProvider? formatProvider = null)
{
var match = Regex.Match(value, @"^\s*(\d+[\.,]?\d*)\s*(\w)?b\s*$", RegexOptions.IgnoreCase);
diff --git a/DiscordChatExporter.Core/Exporting/Writers/MarkdownVisitors/HtmlMarkdownVisitor.cs b/DiscordChatExporter.Core/Exporting/Writers/MarkdownVisitors/HtmlMarkdownVisitor.cs
index f6e17767..4b565d45 100644
--- a/DiscordChatExporter.Core/Exporting/Writers/MarkdownVisitors/HtmlMarkdownVisitor.cs
+++ b/DiscordChatExporter.Core/Exporting/Writers/MarkdownVisitors/HtmlMarkdownVisitor.cs
@@ -6,7 +6,7 @@ using System.Text.RegularExpressions;
using DiscordChatExporter.Core.Discord;
using DiscordChatExporter.Core.Discord.Data;
using DiscordChatExporter.Core.Markdown;
-using DiscordChatExporter.Core.Markdown.Ast;
+using DiscordChatExporter.Core.Markdown.Parsing;
using DiscordChatExporter.Core.Utils.Extensions;
namespace DiscordChatExporter.Core.Exporting.Writers.MarkdownVisitors
@@ -78,14 +78,14 @@ namespace DiscordChatExporter.Core.Exporting.Writers.MarkdownVisitors
protected override MarkdownNode VisitMention(MentionNode mention)
{
var mentionId = Snowflake.TryParse(mention.Id);
- if (mention.Type == MentionType.Meta)
+ if (mention.Kind == MentionKind.Meta)
{
_buffer
.Append("")
.Append("@").Append(HtmlEncode(mention.Id))
.Append("");
}
- else if (mention.Type == MentionType.User)
+ else if (mention.Kind == MentionKind.User)
{
var member = mentionId?.Pipe(_context.TryGetMember);
var fullName = member?.User.FullName ?? "Unknown";
@@ -96,7 +96,7 @@ namespace DiscordChatExporter.Core.Exporting.Writers.MarkdownVisitors
.Append("@").Append(HtmlEncode(nick))
.Append("");
}
- else if (mention.Type == MentionType.Channel)
+ else if (mention.Kind == MentionKind.Channel)
{
var channel = mentionId?.Pipe(_context.TryGetChannel);
var name = channel?.Name ?? "deleted-channel";
@@ -106,7 +106,7 @@ namespace DiscordChatExporter.Core.Exporting.Writers.MarkdownVisitors
.Append("#").Append(HtmlEncode(name))
.Append("");
}
- else if (mention.Type == MentionType.Role)
+ else if (mention.Kind == MentionKind.Role)
{
var role = mentionId?.Pipe(_context.TryGetRole);
var name = role?.Name ?? "deleted-role";
diff --git a/DiscordChatExporter.Core/Exporting/Writers/MarkdownVisitors/PlainTextMarkdownVisitor.cs b/DiscordChatExporter.Core/Exporting/Writers/MarkdownVisitors/PlainTextMarkdownVisitor.cs
index 5b9bcd29..f5aa61e9 100644
--- a/DiscordChatExporter.Core/Exporting/Writers/MarkdownVisitors/PlainTextMarkdownVisitor.cs
+++ b/DiscordChatExporter.Core/Exporting/Writers/MarkdownVisitors/PlainTextMarkdownVisitor.cs
@@ -1,7 +1,7 @@
using System.Text;
using DiscordChatExporter.Core.Discord;
using DiscordChatExporter.Core.Markdown;
-using DiscordChatExporter.Core.Markdown.Ast;
+using DiscordChatExporter.Core.Markdown.Parsing;
using DiscordChatExporter.Core.Utils.Extensions;
namespace DiscordChatExporter.Core.Exporting.Writers.MarkdownVisitors
@@ -26,25 +26,25 @@ namespace DiscordChatExporter.Core.Exporting.Writers.MarkdownVisitors
protected override MarkdownNode VisitMention(MentionNode mention)
{
var mentionId = Snowflake.TryParse(mention.Id);
- if (mention.Type == MentionType.Meta)
+ if (mention.Kind == MentionKind.Meta)
{
_buffer.Append($"@{mention.Id}");
}
- else if (mention.Type == MentionType.User)
+ else if (mention.Kind == MentionKind.User)
{
var member = mentionId?.Pipe(_context.TryGetMember);
var name = member?.User.Name ?? "Unknown";
_buffer.Append($"@{name}");
}
- else if (mention.Type == MentionType.Channel)
+ else if (mention.Kind == MentionKind.Channel)
{
var channel = mentionId?.Pipe(_context.TryGetChannel);
var name = channel?.Name ?? "deleted-channel";
_buffer.Append($"#{name}");
}
- else if (mention.Type == MentionType.Role)
+ else if (mention.Kind == MentionKind.Role)
{
var role = mentionId?.Pipe(_context.TryGetRole);
var name = role?.Name ?? "deleted-role";
diff --git a/DiscordChatExporter.Core/Markdown/Ast/MentionNode.cs b/DiscordChatExporter.Core/Markdown/Ast/MentionNode.cs
deleted file mode 100644
index 900198f9..00000000
--- a/DiscordChatExporter.Core/Markdown/Ast/MentionNode.cs
+++ /dev/null
@@ -1,17 +0,0 @@
-namespace DiscordChatExporter.Core.Markdown.Ast
-{
- internal class MentionNode : MarkdownNode
- {
- public string Id { get; }
-
- public MentionType Type { get; }
-
- public MentionNode(string id, MentionType type)
- {
- Id = id;
- Type = type;
- }
-
- public override string ToString() => $"<{Type} mention> {Id}";
- }
-}
\ No newline at end of file
diff --git a/DiscordChatExporter.Core/Markdown/Ast/MentionType.cs b/DiscordChatExporter.Core/Markdown/Ast/MentionType.cs
deleted file mode 100644
index ef3a5f0b..00000000
--- a/DiscordChatExporter.Core/Markdown/Ast/MentionType.cs
+++ /dev/null
@@ -1,10 +0,0 @@
-namespace DiscordChatExporter.Core.Markdown.Ast
-{
- internal enum MentionType
- {
- Meta,
- User,
- Channel,
- Role
- }
-}
\ No newline at end of file
diff --git a/DiscordChatExporter.Core/Markdown/Ast/EmojiNode.cs b/DiscordChatExporter.Core/Markdown/EmojiNode.cs
similarity index 95%
rename from DiscordChatExporter.Core/Markdown/Ast/EmojiNode.cs
rename to DiscordChatExporter.Core/Markdown/EmojiNode.cs
index a36a9c80..9e4aebcf 100644
--- a/DiscordChatExporter.Core/Markdown/Ast/EmojiNode.cs
+++ b/DiscordChatExporter.Core/Markdown/EmojiNode.cs
@@ -1,6 +1,6 @@
using DiscordChatExporter.Core.Utils;
-namespace DiscordChatExporter.Core.Markdown.Ast
+namespace DiscordChatExporter.Core.Markdown
{
internal class EmojiNode : MarkdownNode
{
diff --git a/DiscordChatExporter.Core/Markdown/Ast/FormattedNode.cs b/DiscordChatExporter.Core/Markdown/FormattedNode.cs
similarity index 90%
rename from DiscordChatExporter.Core/Markdown/Ast/FormattedNode.cs
rename to DiscordChatExporter.Core/Markdown/FormattedNode.cs
index 0a78a074..c30cad3c 100644
--- a/DiscordChatExporter.Core/Markdown/Ast/FormattedNode.cs
+++ b/DiscordChatExporter.Core/Markdown/FormattedNode.cs
@@ -1,6 +1,6 @@
using System.Collections.Generic;
-namespace DiscordChatExporter.Core.Markdown.Ast
+namespace DiscordChatExporter.Core.Markdown
{
internal class FormattedNode : MarkdownNode
{
diff --git a/DiscordChatExporter.Core/Markdown/Ast/InlineCodeBlockNode.cs b/DiscordChatExporter.Core/Markdown/InlineCodeBlockNode.cs
similarity index 83%
rename from DiscordChatExporter.Core/Markdown/Ast/InlineCodeBlockNode.cs
rename to DiscordChatExporter.Core/Markdown/InlineCodeBlockNode.cs
index ebea1776..dbb7637d 100644
--- a/DiscordChatExporter.Core/Markdown/Ast/InlineCodeBlockNode.cs
+++ b/DiscordChatExporter.Core/Markdown/InlineCodeBlockNode.cs
@@ -1,4 +1,4 @@
-namespace DiscordChatExporter.Core.Markdown.Ast
+namespace DiscordChatExporter.Core.Markdown
{
internal class InlineCodeBlockNode : MarkdownNode
{
diff --git a/DiscordChatExporter.Core/Markdown/Ast/LinkNode.cs b/DiscordChatExporter.Core/Markdown/LinkNode.cs
similarity index 88%
rename from DiscordChatExporter.Core/Markdown/Ast/LinkNode.cs
rename to DiscordChatExporter.Core/Markdown/LinkNode.cs
index 2c9f481f..1b364d1d 100644
--- a/DiscordChatExporter.Core/Markdown/Ast/LinkNode.cs
+++ b/DiscordChatExporter.Core/Markdown/LinkNode.cs
@@ -1,4 +1,4 @@
-namespace DiscordChatExporter.Core.Markdown.Ast
+namespace DiscordChatExporter.Core.Markdown
{
internal class LinkNode : MarkdownNode
{
diff --git a/DiscordChatExporter.Core/Markdown/Ast/MarkdownNode.cs b/DiscordChatExporter.Core/Markdown/MarkdownNode.cs
similarity index 51%
rename from DiscordChatExporter.Core/Markdown/Ast/MarkdownNode.cs
rename to DiscordChatExporter.Core/Markdown/MarkdownNode.cs
index 6bf3baf7..b6ca6a25 100644
--- a/DiscordChatExporter.Core/Markdown/Ast/MarkdownNode.cs
+++ b/DiscordChatExporter.Core/Markdown/MarkdownNode.cs
@@ -1,4 +1,4 @@
-namespace DiscordChatExporter.Core.Markdown.Ast
+namespace DiscordChatExporter.Core.Markdown
{
internal abstract class MarkdownNode
{
diff --git a/DiscordChatExporter.Core/Markdown/MentionKind.cs b/DiscordChatExporter.Core/Markdown/MentionKind.cs
new file mode 100644
index 00000000..824a1cd3
--- /dev/null
+++ b/DiscordChatExporter.Core/Markdown/MentionKind.cs
@@ -0,0 +1,10 @@
+namespace DiscordChatExporter.Core.Markdown
+{
+ internal enum MentionKind
+ {
+ Meta,
+ User,
+ Channel,
+ Role
+ }
+}
\ No newline at end of file
diff --git a/DiscordChatExporter.Core/Markdown/MentionNode.cs b/DiscordChatExporter.Core/Markdown/MentionNode.cs
new file mode 100644
index 00000000..646d1c80
--- /dev/null
+++ b/DiscordChatExporter.Core/Markdown/MentionNode.cs
@@ -0,0 +1,17 @@
+namespace DiscordChatExporter.Core.Markdown
+{
+ internal class MentionNode : MarkdownNode
+ {
+ public string Id { get; }
+
+ public MentionKind Kind { get; }
+
+ public MentionNode(string id, MentionKind kind)
+ {
+ Id = id;
+ Kind = kind;
+ }
+
+ public override string ToString() => $"<{Kind} mention> {Id}";
+ }
+}
\ No newline at end of file
diff --git a/DiscordChatExporter.Core/Markdown/Ast/MultiLineCodeBlockNode.cs b/DiscordChatExporter.Core/Markdown/MultiLineCodeBlockNode.cs
similarity index 87%
rename from DiscordChatExporter.Core/Markdown/Ast/MultiLineCodeBlockNode.cs
rename to DiscordChatExporter.Core/Markdown/MultiLineCodeBlockNode.cs
index d53406d5..afa3e0df 100644
--- a/DiscordChatExporter.Core/Markdown/Ast/MultiLineCodeBlockNode.cs
+++ b/DiscordChatExporter.Core/Markdown/MultiLineCodeBlockNode.cs
@@ -1,4 +1,4 @@
-namespace DiscordChatExporter.Core.Markdown.Ast
+namespace DiscordChatExporter.Core.Markdown
{
internal class MultiLineCodeBlockNode : MarkdownNode
{
diff --git a/DiscordChatExporter.Core/Markdown/Matching/AggregateMatcher.cs b/DiscordChatExporter.Core/Markdown/Parsing/AggregateMatcher.cs
similarity index 96%
rename from DiscordChatExporter.Core/Markdown/Matching/AggregateMatcher.cs
rename to DiscordChatExporter.Core/Markdown/Parsing/AggregateMatcher.cs
index a507e0d1..07b30bc2 100644
--- a/DiscordChatExporter.Core/Markdown/Matching/AggregateMatcher.cs
+++ b/DiscordChatExporter.Core/Markdown/Parsing/AggregateMatcher.cs
@@ -1,6 +1,6 @@
using System.Collections.Generic;
-namespace DiscordChatExporter.Core.Markdown.Matching
+namespace DiscordChatExporter.Core.Markdown.Parsing
{
internal class AggregateMatcher : IMatcher
{
diff --git a/DiscordChatExporter.Core/Markdown/Matching/IMatcher.cs b/DiscordChatExporter.Core/Markdown/Parsing/IMatcher.cs
similarity index 97%
rename from DiscordChatExporter.Core/Markdown/Matching/IMatcher.cs
rename to DiscordChatExporter.Core/Markdown/Parsing/IMatcher.cs
index 0958c18f..0e3d1ec6 100644
--- a/DiscordChatExporter.Core/Markdown/Matching/IMatcher.cs
+++ b/DiscordChatExporter.Core/Markdown/Parsing/IMatcher.cs
@@ -1,7 +1,7 @@
using System;
using System.Collections.Generic;
-namespace DiscordChatExporter.Core.Markdown.Matching
+namespace DiscordChatExporter.Core.Markdown.Parsing
{
internal interface IMatcher
{
diff --git a/DiscordChatExporter.Core/Markdown/MarkdownParser.cs b/DiscordChatExporter.Core/Markdown/Parsing/MarkdownParser.cs
similarity index 96%
rename from DiscordChatExporter.Core/Markdown/MarkdownParser.cs
rename to DiscordChatExporter.Core/Markdown/Parsing/MarkdownParser.cs
index 05f9f57f..ca8cd4d5 100644
--- a/DiscordChatExporter.Core/Markdown/MarkdownParser.cs
+++ b/DiscordChatExporter.Core/Markdown/Parsing/MarkdownParser.cs
@@ -1,11 +1,9 @@
using System.Collections.Generic;
using System.Linq;
using System.Text.RegularExpressions;
-using DiscordChatExporter.Core.Markdown.Ast;
-using DiscordChatExporter.Core.Markdown.Matching;
using DiscordChatExporter.Core.Utils;
-namespace DiscordChatExporter.Core.Markdown
+namespace DiscordChatExporter.Core.Markdown.Parsing
{
// The following parsing logic is meant to replicate Discord's markdown grammar as close as possible
internal static partial class MarkdownParser
@@ -120,31 +118,31 @@ namespace DiscordChatExporter.Core.Markdown
// Capture @everyone
private static readonly IMatcher EveryoneMentionNodeMatcher = new StringMatcher(
"@everyone",
- _ => new MentionNode("everyone", MentionType.Meta)
+ _ => new MentionNode("everyone", MentionKind.Meta)
);
// Capture @here
private static readonly IMatcher HereMentionNodeMatcher = new StringMatcher(
"@here",
- _ => new MentionNode("here", MentionType.Meta)
+ _ => new MentionNode("here", MentionKind.Meta)
);
// Capture <@123456> or <@!123456>
private static readonly IMatcher UserMentionNodeMatcher = new RegexMatcher(
new Regex("<@!?(\\d+)>", DefaultRegexOptions),
- (_, m) => new MentionNode(m.Groups[1].Value, MentionType.User)
+ (_, m) => new MentionNode(m.Groups[1].Value, MentionKind.User)
);
// Capture <#123456>
private static readonly IMatcher ChannelMentionNodeMatcher = new RegexMatcher(
new Regex("<#(\\d+)>", DefaultRegexOptions),
- (_, m) => new MentionNode(m.Groups[1].Value, MentionType.Channel)
+ (_, m) => new MentionNode(m.Groups[1].Value, MentionKind.Channel)
);
// Capture <@&123456>
private static readonly IMatcher RoleMentionNodeMatcher = new RegexMatcher(
new Regex("<@&(\\d+)>", DefaultRegexOptions),
- (_, m) => new MentionNode(m.Groups[1].Value, MentionType.Role)
+ (_, m) => new MentionNode(m.Groups[1].Value, MentionKind.Role)
);
/* Emojis */
@@ -293,12 +291,16 @@ namespace DiscordChatExporter.Core.Markdown
internal static partial class MarkdownParser
{
- private static IReadOnlyList Parse(StringPart stringPart) => Parse(stringPart, AggregateNodeMatcher);
+ private static IReadOnlyList Parse(StringPart stringPart) =>
+ Parse(stringPart, AggregateNodeMatcher);
- private static IReadOnlyList ParseMinimal(StringPart stringPart) => Parse(stringPart, MinimalAggregateNodeMatcher);
+ private static IReadOnlyList ParseMinimal(StringPart stringPart) =>
+ Parse(stringPart, MinimalAggregateNodeMatcher);
- public static IReadOnlyList Parse(string input) => Parse(new StringPart(input));
+ public static IReadOnlyList Parse(string input) =>
+ Parse(new StringPart(input));
- public static IReadOnlyList ParseMinimal(string input) => ParseMinimal(new StringPart(input));
+ public static IReadOnlyList ParseMinimal(string input) =>
+ ParseMinimal(new StringPart(input));
}
}
\ No newline at end of file
diff --git a/DiscordChatExporter.Core/Markdown/MarkdownVisitor.cs b/DiscordChatExporter.Core/Markdown/Parsing/MarkdownVisitor.cs
similarity index 94%
rename from DiscordChatExporter.Core/Markdown/MarkdownVisitor.cs
rename to DiscordChatExporter.Core/Markdown/Parsing/MarkdownVisitor.cs
index 56100ff7..ad953ab4 100644
--- a/DiscordChatExporter.Core/Markdown/MarkdownVisitor.cs
+++ b/DiscordChatExporter.Core/Markdown/Parsing/MarkdownVisitor.cs
@@ -1,8 +1,7 @@
using System;
using System.Collections.Generic;
-using DiscordChatExporter.Core.Markdown.Ast;
-namespace DiscordChatExporter.Core.Markdown
+namespace DiscordChatExporter.Core.Markdown.Parsing
{
internal abstract class MarkdownVisitor
{
diff --git a/DiscordChatExporter.Core/Markdown/Matching/ParsedMatch.cs b/DiscordChatExporter.Core/Markdown/Parsing/ParsedMatch.cs
similarity index 82%
rename from DiscordChatExporter.Core/Markdown/Matching/ParsedMatch.cs
rename to DiscordChatExporter.Core/Markdown/Parsing/ParsedMatch.cs
index a144e032..07133358 100644
--- a/DiscordChatExporter.Core/Markdown/Matching/ParsedMatch.cs
+++ b/DiscordChatExporter.Core/Markdown/Parsing/ParsedMatch.cs
@@ -1,4 +1,4 @@
-namespace DiscordChatExporter.Core.Markdown.Matching
+namespace DiscordChatExporter.Core.Markdown.Parsing
{
internal class ParsedMatch
{
diff --git a/DiscordChatExporter.Core/Markdown/Matching/RegexMatcher.cs b/DiscordChatExporter.Core/Markdown/Parsing/RegexMatcher.cs
similarity index 96%
rename from DiscordChatExporter.Core/Markdown/Matching/RegexMatcher.cs
rename to DiscordChatExporter.Core/Markdown/Parsing/RegexMatcher.cs
index a141113f..7a9ffb91 100644
--- a/DiscordChatExporter.Core/Markdown/Matching/RegexMatcher.cs
+++ b/DiscordChatExporter.Core/Markdown/Parsing/RegexMatcher.cs
@@ -1,7 +1,7 @@
using System;
using System.Text.RegularExpressions;
-namespace DiscordChatExporter.Core.Markdown.Matching
+namespace DiscordChatExporter.Core.Markdown.Parsing
{
internal class RegexMatcher : IMatcher
{
diff --git a/DiscordChatExporter.Core/Markdown/Matching/StringMatcher.cs b/DiscordChatExporter.Core/Markdown/Parsing/StringMatcher.cs
similarity index 95%
rename from DiscordChatExporter.Core/Markdown/Matching/StringMatcher.cs
rename to DiscordChatExporter.Core/Markdown/Parsing/StringMatcher.cs
index 842ae9d1..380819ba 100644
--- a/DiscordChatExporter.Core/Markdown/Matching/StringMatcher.cs
+++ b/DiscordChatExporter.Core/Markdown/Parsing/StringMatcher.cs
@@ -1,6 +1,6 @@
using System;
-namespace DiscordChatExporter.Core.Markdown.Matching
+namespace DiscordChatExporter.Core.Markdown.Parsing
{
internal class StringMatcher : IMatcher
{
diff --git a/DiscordChatExporter.Core/Markdown/Matching/StringPart.cs b/DiscordChatExporter.Core/Markdown/Parsing/StringPart.cs
similarity index 94%
rename from DiscordChatExporter.Core/Markdown/Matching/StringPart.cs
rename to DiscordChatExporter.Core/Markdown/Parsing/StringPart.cs
index c38c7a48..487d5918 100644
--- a/DiscordChatExporter.Core/Markdown/Matching/StringPart.cs
+++ b/DiscordChatExporter.Core/Markdown/Parsing/StringPart.cs
@@ -1,6 +1,6 @@
using System.Text.RegularExpressions;
-namespace DiscordChatExporter.Core.Markdown.Matching
+namespace DiscordChatExporter.Core.Markdown.Parsing
{
internal readonly struct StringPart
{
diff --git a/DiscordChatExporter.Core/Markdown/Ast/TextFormatting.cs b/DiscordChatExporter.Core/Markdown/TextFormatting.cs
similarity index 74%
rename from DiscordChatExporter.Core/Markdown/Ast/TextFormatting.cs
rename to DiscordChatExporter.Core/Markdown/TextFormatting.cs
index b272277d..f6f30b76 100644
--- a/DiscordChatExporter.Core/Markdown/Ast/TextFormatting.cs
+++ b/DiscordChatExporter.Core/Markdown/TextFormatting.cs
@@ -1,4 +1,4 @@
-namespace DiscordChatExporter.Core.Markdown.Ast
+namespace DiscordChatExporter.Core.Markdown
{
internal enum TextFormatting
{
diff --git a/DiscordChatExporter.Core/Markdown/Ast/TextNode.cs b/DiscordChatExporter.Core/Markdown/TextNode.cs
similarity index 81%
rename from DiscordChatExporter.Core/Markdown/Ast/TextNode.cs
rename to DiscordChatExporter.Core/Markdown/TextNode.cs
index 66bad0eb..95e7a710 100644
--- a/DiscordChatExporter.Core/Markdown/Ast/TextNode.cs
+++ b/DiscordChatExporter.Core/Markdown/TextNode.cs
@@ -1,4 +1,4 @@
-namespace DiscordChatExporter.Core.Markdown.Ast
+namespace DiscordChatExporter.Core.Markdown
{
internal class TextNode : MarkdownNode
{
diff --git a/DiscordChatExporter.Core/Utils/Extensions/SuperpowerExtensions.cs b/DiscordChatExporter.Core/Utils/Extensions/SuperpowerExtensions.cs
new file mode 100644
index 00000000..a70296f6
--- /dev/null
+++ b/DiscordChatExporter.Core/Utils/Extensions/SuperpowerExtensions.cs
@@ -0,0 +1,24 @@
+using System;
+using Superpower;
+using Superpower.Parsers;
+
+namespace DiscordChatExporter.Core.Utils.Extensions
+{
+ public static class SuperpowerExtensions
+ {
+ public static TextParser Text(this TextParser parser) =>
+ parser.Select(chars => new string(chars));
+
+ public static TextParser Token(this TextParser parser) =>
+ parser.Between(Character.WhiteSpace.IgnoreMany(), Character.WhiteSpace.IgnoreMany());
+
+ // From: https://twitter.com/nblumhardt/status/1389349059786264578
+ public static TextParser Log(this TextParser parser, string description) => i =>
+ {
+ Console.WriteLine($"Trying {description} ->");
+ var r = parser(i);
+ Console.WriteLine($"Result was {r}");
+ return r;
+ };
+ }
+}
\ No newline at end of file
diff --git a/DiscordChatExporter.Core/Utils/Http.cs b/DiscordChatExporter.Core/Utils/Http.cs
index dd4e2a1b..fec0cbfa 100644
--- a/DiscordChatExporter.Core/Utils/Http.cs
+++ b/DiscordChatExporter.Core/Utils/Http.cs
@@ -21,14 +21,15 @@ namespace DiscordChatExporter.Core.Utils
.OrResult(m => m.StatusCode == HttpStatusCode.TooManyRequests)
.OrResult(m => m.StatusCode == HttpStatusCode.RequestTimeout)
.OrResult(m => m.StatusCode >= HttpStatusCode.InternalServerError)
- .WaitAndRetryAsync(8,
+ .WaitAndRetryAsync(
+ 8,
(i, result, _) =>
{
// If rate-limited, use retry-after as a guide
if (result.Result?.StatusCode == HttpStatusCode.TooManyRequests)
{
- // Only start respecting retry-after after a few attempts.
- // The reason is that Discord often sends unreasonable (20+ minutes) retry-after
+ // Only start respecting retry-after after a few attempts, because
+ // Discord often sends unreasonable (20+ minutes) retry-after
// on the very first request.
if (i > 3)
{
@@ -40,7 +41,8 @@ namespace DiscordChatExporter.Core.Utils
return TimeSpan.FromSeconds(Math.Pow(2, i) + 1);
},
- (_, _, _, _) => Task.CompletedTask);
+ (_, _, _, _) => Task.CompletedTask
+ );
private static HttpStatusCode? TryGetStatusCodeFromException(HttpRequestException ex) =>
// This is extremely frail, but there's no other way
diff --git a/DiscordChatExporter.Gui/ViewModels/Dialogs/ExportSetupViewModel.cs b/DiscordChatExporter.Gui/ViewModels/Dialogs/ExportSetupViewModel.cs
index a1d5cc5a..9c6a2df3 100644
--- a/DiscordChatExporter.Gui/ViewModels/Dialogs/ExportSetupViewModel.cs
+++ b/DiscordChatExporter.Gui/ViewModels/Dialogs/ExportSetupViewModel.cs
@@ -52,13 +52,13 @@ namespace DiscordChatExporter.Gui.ViewModels.Dialogs
public PartitionLimit PartitionLimit => !string.IsNullOrWhiteSpace(PartitionLimitValue)
? PartitionLimit.Parse(PartitionLimitValue)
- : NullPartitionLimit.Instance;
+ : PartitionLimit.Null;
public string? MessageFilterValue { get; set; }
public MessageFilter MessageFilter => !string.IsNullOrWhiteSpace(MessageFilterValue)
? MessageFilter.Parse(MessageFilterValue)
- : NullMessageFilter.Instance;
+ : MessageFilter.Null;
public bool ShouldDownloadMedia { get; set; }
diff --git a/DiscordChatExporter.Gui/Views/Dialogs/ExportSetupView.xaml b/DiscordChatExporter.Gui/Views/Dialogs/ExportSetupView.xaml
index 6a0d7eb1..98f4415e 100644
--- a/DiscordChatExporter.Gui/Views/Dialogs/ExportSetupView.xaml
+++ b/DiscordChatExporter.Gui/Views/Dialogs/ExportSetupView.xaml
@@ -131,7 +131,7 @@
materialDesign:HintAssist.Hint="Partition limit"
materialDesign:HintAssist.IsFloating="True"
Text="{Binding PartitionLimitValue}"
- ToolTip="Split output into partitions, each limited to this number of messages (e.g. 100) or file size (e.g. 10mb)" />
+ ToolTip="Split output into partitions, each limited to this number of messages (e.g. '100') or file size (e.g. '10mb')" />
+ ToolTip="Only include messages that satisfy this filter (e.g. 'from:foo#1234' or 'has:image')." />
diff --git a/DiscordChatExporter.Gui/Views/RootView.xaml b/DiscordChatExporter.Gui/Views/RootView.xaml
index d5660d49..c26f5bad 100644
--- a/DiscordChatExporter.Gui/Views/RootView.xaml
+++ b/DiscordChatExporter.Gui/Views/RootView.xaml
@@ -1,4 +1,4 @@
-