From 619fe9ccf7e829fd9aae2d763373dcf344af6be7 Mon Sep 17 00:00:00 2001 From: Tyrrrz <1935960+Tyrrrz@users.noreply.github.com> Date: Sun, 10 Dec 2023 22:32:45 +0200 Subject: [PATCH] Refactor using c# 12 features --- .../Utils/TempDir.cs | 6 +- .../Utils/TempFile.cs | 6 +- .../Commands/ExportGuildCommand.cs | 1 - .../Discord/DiscordClient.cs | 8 +-- .../Discord/Dump/DataDump.cs | 6 +- .../DiscordChatExporterException.cs | 18 ++---- .../Exporting/ChannelExporter.cs | 6 +- .../Exporting/ExportAssetDownloader.cs | 9 +-- .../BinaryExpressionMessageFilter.cs | 29 +++------ .../Filtering/ContainsMessageFilter.cs | 8 +-- .../Exporting/Filtering/FromMessageFilter.cs | 14 ++--- .../Exporting/Filtering/HasMessageFilter.cs | 10 +--- .../Filtering/MentionsMessageFilter.cs | 14 ++--- .../Filtering/NegatedMessageFilter.cs | 8 +-- .../Filtering/ReactionMessageFilter.cs | 16 ++--- .../Exporting/HtmlMarkdownVisitor.cs | 60 +++++++++---------- .../Exporting/HtmlMessageWriter.cs | 9 +-- .../Exporting/JsonMessageWriter.cs | 25 ++++---- .../Exporting/MessageExporter.cs | 10 ++-- .../Partitioning/FileSizePartitionLimit.cs | 8 +-- .../MessageCountPartitionLimit.cs | 8 +-- .../Exporting/PlainTextMarkdownVisitor.cs | 32 +++++----- .../Markdown/Parsing/AggregateMatcher.cs | 11 +--- .../Markdown/Parsing/ParsedMatch.cs | 12 +--- .../Markdown/Parsing/RegexMatcher.cs | 17 ++---- .../Markdown/Parsing/StringMatcher.cs | 29 +++------ .../Services/SettingsService.cs | 6 +- .../Services/UpdateService.cs | 15 ++--- .../ViewModels/Dialogs/SettingsViewModel.cs | 34 +++++------ .../ViewModels/Framework/DialogManager.cs | 10 +--- 30 files changed, 155 insertions(+), 290 deletions(-) diff --git a/DiscordChatExporter.Cli.Tests/Utils/TempDir.cs b/DiscordChatExporter.Cli.Tests/Utils/TempDir.cs index 28d6f58f..b65888e9 100644 --- a/DiscordChatExporter.Cli.Tests/Utils/TempDir.cs +++ b/DiscordChatExporter.Cli.Tests/Utils/TempDir.cs @@ -5,11 +5,9 @@ using PathEx = System.IO.Path; namespace DiscordChatExporter.Cli.Tests.Utils; -internal partial class TempDir : IDisposable +internal partial class TempDir(string path) : IDisposable { - public string Path { get; } - - public TempDir(string path) => Path = path; + public string Path { get; } = path; public void Dispose() { diff --git a/DiscordChatExporter.Cli.Tests/Utils/TempFile.cs b/DiscordChatExporter.Cli.Tests/Utils/TempFile.cs index ca66739f..e42ccbe8 100644 --- a/DiscordChatExporter.Cli.Tests/Utils/TempFile.cs +++ b/DiscordChatExporter.Cli.Tests/Utils/TempFile.cs @@ -5,11 +5,9 @@ using PathEx = System.IO.Path; namespace DiscordChatExporter.Cli.Tests.Utils; -internal partial class TempFile : IDisposable +internal partial class TempFile(string path) : IDisposable { - public string Path { get; } - - public TempFile(string path) => Path = path; + public string Path { get; } = path; public void Dispose() { diff --git a/DiscordChatExporter.Cli/Commands/ExportGuildCommand.cs b/DiscordChatExporter.Cli/Commands/ExportGuildCommand.cs index 24e41ad5..8721a98d 100644 --- a/DiscordChatExporter.Cli/Commands/ExportGuildCommand.cs +++ b/DiscordChatExporter.Cli/Commands/ExportGuildCommand.cs @@ -1,5 +1,4 @@ using System.Collections.Generic; -using System.Linq; using System.Threading.Tasks; using CliFx.Attributes; using CliFx.Infrastructure; diff --git a/DiscordChatExporter.Core/Discord/DiscordClient.cs b/DiscordChatExporter.Core/Discord/DiscordClient.cs index a89ec22d..5ad9862f 100644 --- a/DiscordChatExporter.Core/Discord/DiscordClient.cs +++ b/DiscordChatExporter.Core/Discord/DiscordClient.cs @@ -18,15 +18,11 @@ using JsonExtensions.Reading; namespace DiscordChatExporter.Core.Discord; -public class DiscordClient +public class DiscordClient(string token) { - private readonly string _token; private readonly Uri _baseUri = new("https://discord.com/api/v10/", UriKind.Absolute); - private TokenKind? _resolvedTokenKind; - public DiscordClient(string token) => _token = token; - private async ValueTask GetResponseAsync( string url, TokenKind tokenKind, @@ -44,7 +40,7 @@ public class DiscordClient .Headers .TryAddWithoutValidation( "Authorization", - tokenKind == TokenKind.Bot ? $"Bot {_token}" : _token + tokenKind == TokenKind.Bot ? $"Bot {token}" : token ); var response = await Http.Client.SendAsync( diff --git a/DiscordChatExporter.Core/Discord/Dump/DataDump.cs b/DiscordChatExporter.Core/Discord/Dump/DataDump.cs index 28ae9f53..d0605bed 100644 --- a/DiscordChatExporter.Core/Discord/Dump/DataDump.cs +++ b/DiscordChatExporter.Core/Discord/Dump/DataDump.cs @@ -8,11 +8,9 @@ using JsonExtensions.Reading; namespace DiscordChatExporter.Core.Discord.Dump; -public partial class DataDump +public partial class DataDump(IReadOnlyList channels) { - public IReadOnlyList Channels { get; } - - public DataDump(IReadOnlyList channels) => Channels = channels; + public IReadOnlyList Channels { get; } = channels; } public partial class DataDump diff --git a/DiscordChatExporter.Core/Exceptions/DiscordChatExporterException.cs b/DiscordChatExporter.Core/Exceptions/DiscordChatExporterException.cs index 0f60a95c..67b081ac 100644 --- a/DiscordChatExporter.Core/Exceptions/DiscordChatExporterException.cs +++ b/DiscordChatExporter.Core/Exceptions/DiscordChatExporterException.cs @@ -2,17 +2,11 @@ namespace DiscordChatExporter.Core.Exceptions; -public class DiscordChatExporterException : Exception +public class DiscordChatExporterException( + string message, + bool isFatal = false, + Exception? innerException = null +) : Exception(message, innerException) { - public bool IsFatal { get; } - - public DiscordChatExporterException( - string message, - bool isFatal = false, - Exception? innerException = null - ) - : base(message, innerException) - { - IsFatal = isFatal; - } + public bool IsFatal { get; } = isFatal; } diff --git a/DiscordChatExporter.Core/Exporting/ChannelExporter.cs b/DiscordChatExporter.Core/Exporting/ChannelExporter.cs index fa6e8951..dc60c1dc 100644 --- a/DiscordChatExporter.Core/Exporting/ChannelExporter.cs +++ b/DiscordChatExporter.Core/Exporting/ChannelExporter.cs @@ -10,8 +10,6 @@ namespace DiscordChatExporter.Core.Exporting; public class ChannelExporter(DiscordClient discord) { - private readonly DiscordClient _discord = discord; - public async ValueTask ExportChannelAsync( ExportRequest request, IProgress? progress = null, @@ -63,13 +61,13 @@ public class ChannelExporter(DiscordClient discord) } // Build context - var context = new ExportContext(_discord, request); + var context = new ExportContext(discord, request); await context.PopulateChannelsAndRolesAsync(cancellationToken); // Export messages await using var messageExporter = new MessageExporter(context); await foreach ( - var message in _discord.GetMessagesAsync( + var message in discord.GetMessagesAsync( request.Channel.Id, request.After, request.Before, diff --git a/DiscordChatExporter.Core/Exporting/ExportAssetDownloader.cs b/DiscordChatExporter.Core/Exporting/ExportAssetDownloader.cs index 075ce6b5..3f37923f 100644 --- a/DiscordChatExporter.Core/Exporting/ExportAssetDownloader.cs +++ b/DiscordChatExporter.Core/Exporting/ExportAssetDownloader.cs @@ -23,9 +23,6 @@ internal partial class ExportAssetDownloader(string workingDirPath, bool reuse) o.PoolInitialFill = 1; }); - private readonly string _workingDirPath = workingDirPath; - private readonly bool _reuse = reuse; - // File paths of the previously downloaded assets private readonly Dictionary _previousPathsByUrl = new(StringComparer.Ordinal); @@ -35,7 +32,7 @@ internal partial class ExportAssetDownloader(string workingDirPath, bool reuse) ) { var fileName = GetFileNameFromUrl(url); - var filePath = Path.Combine(_workingDirPath, fileName); + var filePath = Path.Combine(workingDirPath, fileName); using var _ = await Locker.LockAsync(filePath, cancellationToken); @@ -43,10 +40,10 @@ internal partial class ExportAssetDownloader(string workingDirPath, bool reuse) return cachedFilePath; // Reuse existing files if we're allowed to - if (_reuse && File.Exists(filePath)) + if (reuse && File.Exists(filePath)) return _previousPathsByUrl[url] = filePath; - Directory.CreateDirectory(_workingDirPath); + Directory.CreateDirectory(workingDirPath); await Http.ResiliencePipeline.ExecuteAsync( async innerCancellationToken => diff --git a/DiscordChatExporter.Core/Exporting/Filtering/BinaryExpressionMessageFilter.cs b/DiscordChatExporter.Core/Exporting/Filtering/BinaryExpressionMessageFilter.cs index d6b56b12..73ab4839 100644 --- a/DiscordChatExporter.Core/Exporting/Filtering/BinaryExpressionMessageFilter.cs +++ b/DiscordChatExporter.Core/Exporting/Filtering/BinaryExpressionMessageFilter.cs @@ -3,28 +3,17 @@ using DiscordChatExporter.Core.Discord.Data; namespace DiscordChatExporter.Core.Exporting.Filtering; -internal class BinaryExpressionMessageFilter : MessageFilter +internal class BinaryExpressionMessageFilter( + MessageFilter first, + MessageFilter second, + BinaryExpressionKind kind +) : MessageFilter { - private readonly MessageFilter _first; - private readonly MessageFilter _second; - private readonly BinaryExpressionKind _kind; - - public BinaryExpressionMessageFilter( - MessageFilter first, - MessageFilter second, - BinaryExpressionKind kind - ) - { - _first = first; - _second = second; - _kind = kind; - } - public override bool IsMatch(Message message) => - _kind switch + kind switch { - BinaryExpressionKind.Or => _first.IsMatch(message) || _second.IsMatch(message), - BinaryExpressionKind.And => _first.IsMatch(message) && _second.IsMatch(message), - _ => throw new InvalidOperationException($"Unknown binary expression kind '{_kind}'.") + BinaryExpressionKind.Or => first.IsMatch(message) || second.IsMatch(message), + BinaryExpressionKind.And => first.IsMatch(message) && second.IsMatch(message), + _ => throw new InvalidOperationException($"Unknown binary expression kind '{kind}'.") }; } diff --git a/DiscordChatExporter.Core/Exporting/Filtering/ContainsMessageFilter.cs b/DiscordChatExporter.Core/Exporting/Filtering/ContainsMessageFilter.cs index e9755ab0..ccf83a92 100644 --- a/DiscordChatExporter.Core/Exporting/Filtering/ContainsMessageFilter.cs +++ b/DiscordChatExporter.Core/Exporting/Filtering/ContainsMessageFilter.cs @@ -4,12 +4,8 @@ using DiscordChatExporter.Core.Discord.Data; namespace DiscordChatExporter.Core.Exporting.Filtering; -internal class ContainsMessageFilter : MessageFilter +internal class ContainsMessageFilter(string text) : MessageFilter { - private readonly string _text; - - public ContainsMessageFilter(string text) => _text = text; - // Match content within word boundaries, between spaces, or as the whole input. // For example, "max" shouldn't match on content "our maximum effort", // but should match on content "our max effort". @@ -20,7 +16,7 @@ internal class ContainsMessageFilter : MessageFilter !string.IsNullOrWhiteSpace(content) && Regex.IsMatch( content, - @"(?:\b|\s|^)" + Regex.Escape(_text) + @"(?:\b|\s|$)", + @"(?:\b|\s|^)" + Regex.Escape(text) + @"(?:\b|\s|$)", RegexOptions.IgnoreCase | RegexOptions.CultureInvariant ); diff --git a/DiscordChatExporter.Core/Exporting/Filtering/FromMessageFilter.cs b/DiscordChatExporter.Core/Exporting/Filtering/FromMessageFilter.cs index 56f02a1e..15c4c337 100644 --- a/DiscordChatExporter.Core/Exporting/Filtering/FromMessageFilter.cs +++ b/DiscordChatExporter.Core/Exporting/Filtering/FromMessageFilter.cs @@ -3,15 +3,11 @@ using DiscordChatExporter.Core.Discord.Data; namespace DiscordChatExporter.Core.Exporting.Filtering; -internal class FromMessageFilter : MessageFilter +internal class FromMessageFilter(string value) : MessageFilter { - private readonly string _value; - - public FromMessageFilter(string value) => _value = value; - public override bool IsMatch(Message message) => - string.Equals(_value, message.Author.Name, StringComparison.OrdinalIgnoreCase) - || string.Equals(_value, message.Author.DisplayName, StringComparison.OrdinalIgnoreCase) - || string.Equals(_value, message.Author.FullName, StringComparison.OrdinalIgnoreCase) - || string.Equals(_value, message.Author.Id.ToString(), StringComparison.OrdinalIgnoreCase); + string.Equals(value, message.Author.Name, StringComparison.OrdinalIgnoreCase) + || string.Equals(value, message.Author.DisplayName, StringComparison.OrdinalIgnoreCase) + || string.Equals(value, message.Author.FullName, StringComparison.OrdinalIgnoreCase) + || string.Equals(value, message.Author.Id.ToString(), StringComparison.OrdinalIgnoreCase); } diff --git a/DiscordChatExporter.Core/Exporting/Filtering/HasMessageFilter.cs b/DiscordChatExporter.Core/Exporting/Filtering/HasMessageFilter.cs index 52600d90..00653963 100644 --- a/DiscordChatExporter.Core/Exporting/Filtering/HasMessageFilter.cs +++ b/DiscordChatExporter.Core/Exporting/Filtering/HasMessageFilter.cs @@ -5,14 +5,10 @@ using DiscordChatExporter.Core.Discord.Data; namespace DiscordChatExporter.Core.Exporting.Filtering; -internal class HasMessageFilter : MessageFilter +internal class HasMessageFilter(MessageContentMatchKind kind) : MessageFilter { - private readonly MessageContentMatchKind _kind; - - public HasMessageFilter(MessageContentMatchKind kind) => _kind = kind; - public override bool IsMatch(Message message) => - _kind switch + kind switch { MessageContentMatchKind.Link => Regex.IsMatch(message.Content, "https?://\\S*[^\\.,:;\"\'\\s]"), @@ -24,7 +20,7 @@ internal class HasMessageFilter : MessageFilter MessageContentMatchKind.Pin => message.IsPinned, _ => throw new InvalidOperationException( - $"Unknown message content match kind '{_kind}'." + $"Unknown message content match kind '{kind}'." ) }; } diff --git a/DiscordChatExporter.Core/Exporting/Filtering/MentionsMessageFilter.cs b/DiscordChatExporter.Core/Exporting/Filtering/MentionsMessageFilter.cs index 94b75823..21d2a4d0 100644 --- a/DiscordChatExporter.Core/Exporting/Filtering/MentionsMessageFilter.cs +++ b/DiscordChatExporter.Core/Exporting/Filtering/MentionsMessageFilter.cs @@ -4,20 +4,16 @@ using DiscordChatExporter.Core.Discord.Data; namespace DiscordChatExporter.Core.Exporting.Filtering; -internal class MentionsMessageFilter : MessageFilter +internal class MentionsMessageFilter(string value) : MessageFilter { - private readonly string _value; - - public MentionsMessageFilter(string value) => _value = value; - public override bool IsMatch(Message message) => message .MentionedUsers .Any( user => - string.Equals(_value, user.Name, StringComparison.OrdinalIgnoreCase) - || string.Equals(_value, user.DisplayName, StringComparison.OrdinalIgnoreCase) - || string.Equals(_value, user.FullName, StringComparison.OrdinalIgnoreCase) - || string.Equals(_value, user.Id.ToString(), StringComparison.OrdinalIgnoreCase) + string.Equals(value, user.Name, StringComparison.OrdinalIgnoreCase) + || string.Equals(value, user.DisplayName, StringComparison.OrdinalIgnoreCase) + || string.Equals(value, user.FullName, StringComparison.OrdinalIgnoreCase) + || string.Equals(value, user.Id.ToString(), StringComparison.OrdinalIgnoreCase) ); } diff --git a/DiscordChatExporter.Core/Exporting/Filtering/NegatedMessageFilter.cs b/DiscordChatExporter.Core/Exporting/Filtering/NegatedMessageFilter.cs index 37364392..e58ff9ce 100644 --- a/DiscordChatExporter.Core/Exporting/Filtering/NegatedMessageFilter.cs +++ b/DiscordChatExporter.Core/Exporting/Filtering/NegatedMessageFilter.cs @@ -2,11 +2,7 @@ namespace DiscordChatExporter.Core.Exporting.Filtering; -internal class NegatedMessageFilter : MessageFilter +internal class NegatedMessageFilter(MessageFilter filter) : MessageFilter { - private readonly MessageFilter _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); } diff --git a/DiscordChatExporter.Core/Exporting/Filtering/ReactionMessageFilter.cs b/DiscordChatExporter.Core/Exporting/Filtering/ReactionMessageFilter.cs index 087a84ad..e4f490b3 100644 --- a/DiscordChatExporter.Core/Exporting/Filtering/ReactionMessageFilter.cs +++ b/DiscordChatExporter.Core/Exporting/Filtering/ReactionMessageFilter.cs @@ -4,23 +4,15 @@ using DiscordChatExporter.Core.Discord.Data; namespace DiscordChatExporter.Core.Exporting.Filtering; -internal class ReactionMessageFilter : MessageFilter +internal class ReactionMessageFilter(string value) : MessageFilter { - private readonly string _value; - - public ReactionMessageFilter(string value) => _value = value; - public override bool IsMatch(Message message) => message .Reactions .Any( r => - string.Equals( - _value, - r.Emoji.Id?.ToString(), - StringComparison.OrdinalIgnoreCase - ) - || string.Equals(_value, r.Emoji.Name, StringComparison.OrdinalIgnoreCase) - || string.Equals(_value, r.Emoji.Code, StringComparison.OrdinalIgnoreCase) + string.Equals(value, r.Emoji.Id?.ToString(), StringComparison.OrdinalIgnoreCase) + || string.Equals(value, r.Emoji.Name, StringComparison.OrdinalIgnoreCase) + || string.Equals(value, r.Emoji.Code, StringComparison.OrdinalIgnoreCase) ); } diff --git a/DiscordChatExporter.Core/Exporting/HtmlMarkdownVisitor.cs b/DiscordChatExporter.Core/Exporting/HtmlMarkdownVisitor.cs index b37835bc..29435db4 100644 --- a/DiscordChatExporter.Core/Exporting/HtmlMarkdownVisitor.cs +++ b/DiscordChatExporter.Core/Exporting/HtmlMarkdownVisitor.cs @@ -18,16 +18,12 @@ internal partial class HtmlMarkdownVisitor( bool isJumbo ) : MarkdownVisitor { - private readonly ExportContext _context = context; - private readonly StringBuilder _buffer = buffer; - private readonly bool _isJumbo = isJumbo; - protected override ValueTask VisitTextAsync( TextNode text, CancellationToken cancellationToken = default ) { - _buffer.Append(HtmlEncode(text.Text)); + buffer.Append(HtmlEncode(text.Text)); return default; } @@ -92,9 +88,9 @@ internal partial class HtmlMarkdownVisitor( ) }; - _buffer.Append(openingTag); + buffer.Append(openingTag); await VisitAsync(formatting.Children, cancellationToken); - _buffer.Append(closingTag); + buffer.Append(closingTag); } protected override async ValueTask VisitHeadingAsync( @@ -102,14 +98,14 @@ internal partial class HtmlMarkdownVisitor( CancellationToken cancellationToken = default ) { - _buffer.Append( + buffer.Append( // lang=html $"" ); await VisitAsync(heading.Children, cancellationToken); - _buffer.Append( + buffer.Append( // lang=html $"" ); @@ -120,14 +116,14 @@ internal partial class HtmlMarkdownVisitor( CancellationToken cancellationToken = default ) { - _buffer.Append( + buffer.Append( // lang=html "
    " ); await VisitAsync(list.Items, cancellationToken); - _buffer.Append( + buffer.Append( // lang=html "
" ); @@ -138,14 +134,14 @@ internal partial class HtmlMarkdownVisitor( CancellationToken cancellationToken = default ) { - _buffer.Append( + buffer.Append( // lang=html "
  • " ); await VisitAsync(listItem.Children, cancellationToken); - _buffer.Append( + buffer.Append( // lang=html "
  • " ); @@ -156,7 +152,7 @@ internal partial class HtmlMarkdownVisitor( CancellationToken cancellationToken = default ) { - _buffer.Append( + buffer.Append( // lang=html $""" {HtmlEncode(inlineCodeBlock.Code)} @@ -175,7 +171,7 @@ internal partial class HtmlMarkdownVisitor( ? $"language-{multiLineCodeBlock.Language}" : "nohighlight"; - _buffer.Append( + buffer.Append( // lang=html $""" {HtmlEncode(multiLineCodeBlock.Code)} @@ -196,7 +192,7 @@ internal partial class HtmlMarkdownVisitor( .Groups[1] .Value; - _buffer.Append( + buffer.Append( !string.IsNullOrWhiteSpace(linkedMessageId) // lang=html ? $"""""" @@ -206,7 +202,7 @@ internal partial class HtmlMarkdownVisitor( await VisitAsync(link.Children, cancellationToken); - _buffer.Append( + buffer.Append( // lang=html "" ); @@ -218,9 +214,9 @@ internal partial class HtmlMarkdownVisitor( ) { var emojiImageUrl = Emoji.GetImageUrl(emoji.Id, emoji.Name, emoji.IsAnimated); - var jumboClass = _isJumbo ? "chatlog__emoji--large" : ""; + var jumboClass = isJumbo ? "chatlog__emoji--large" : ""; - _buffer.Append( + buffer.Append( // lang=html $""" {emoji.Name} + src="{await context.ResolveAssetUrlAsync(emojiImageUrl, cancellationToken)}"> """ ); } @@ -240,7 +236,7 @@ internal partial class HtmlMarkdownVisitor( { if (mention.Kind == MentionKind.Everyone) { - _buffer.Append( + buffer.Append( // lang=html """ @everyone @@ -249,7 +245,7 @@ internal partial class HtmlMarkdownVisitor( } else if (mention.Kind == MentionKind.Here) { - _buffer.Append( + buffer.Append( // lang=html """ @here @@ -262,13 +258,13 @@ internal partial class HtmlMarkdownVisitor( // which means they need to be populated on demand. // https://github.com/Tyrrrz/DiscordChatExporter/issues/304 if (mention.TargetId is not null) - await _context.PopulateMemberAsync(mention.TargetId.Value, cancellationToken); + await context.PopulateMemberAsync(mention.TargetId.Value, cancellationToken); - var member = mention.TargetId?.Pipe(_context.TryGetMember); + var member = mention.TargetId?.Pipe(context.TryGetMember); var fullName = member?.User.FullName ?? "Unknown"; var displayName = member?.DisplayName ?? member?.User.DisplayName ?? "Unknown"; - _buffer.Append( + buffer.Append( // lang=html $""" @{HtmlEncode(displayName)} @@ -277,11 +273,11 @@ internal partial class HtmlMarkdownVisitor( } else if (mention.Kind == MentionKind.Channel) { - var channel = mention.TargetId?.Pipe(_context.TryGetChannel); + var channel = mention.TargetId?.Pipe(context.TryGetChannel); var symbol = channel?.IsVoice == true ? "🔊" : "#"; var name = channel?.Name ?? "deleted-channel"; - _buffer.Append( + buffer.Append( // lang=html $""" {symbol}{HtmlEncode(name)} @@ -290,7 +286,7 @@ internal partial class HtmlMarkdownVisitor( } else if (mention.Kind == MentionKind.Role) { - var role = mention.TargetId?.Pipe(_context.TryGetRole); + var role = mention.TargetId?.Pipe(context.TryGetRole); var name = role?.Name ?? "deleted-role"; var color = role?.Color; @@ -300,7 +296,7 @@ internal partial class HtmlMarkdownVisitor( """ : null; - _buffer.Append( + buffer.Append( // lang=html $""" @{HtmlEncode(name)} @@ -315,14 +311,14 @@ internal partial class HtmlMarkdownVisitor( ) { var formatted = timestamp.Instant is not null - ? _context.FormatDate(timestamp.Instant.Value, timestamp.Format ?? "g") + ? context.FormatDate(timestamp.Instant.Value, timestamp.Format ?? "g") : "Invalid date"; var formattedLong = timestamp.Instant is not null - ? _context.FormatDate(timestamp.Instant.Value, "f") + ? context.FormatDate(timestamp.Instant.Value, "f") : ""; - _buffer.Append( + buffer.Append( // lang=html $""" {HtmlEncode(formatted)} diff --git a/DiscordChatExporter.Core/Exporting/HtmlMessageWriter.cs b/DiscordChatExporter.Core/Exporting/HtmlMessageWriter.cs index a8a06d3f..5d071b49 100644 --- a/DiscordChatExporter.Core/Exporting/HtmlMessageWriter.cs +++ b/DiscordChatExporter.Core/Exporting/HtmlMessageWriter.cs @@ -13,7 +13,6 @@ internal class HtmlMessageWriter(Stream stream, ExportContext context, string th : MessageWriter(stream, context) { private readonly TextWriter _writer = new StreamWriter(stream); - private readonly string _themeName = themeName; private readonly HtmlMinifier _minifier = new(); private readonly List _messageGroup = new(); @@ -74,11 +73,9 @@ internal class HtmlMessageWriter(Stream stream, ExportContext context, string th { await _writer.WriteLineAsync( Minify( - await new PreambleTemplate - { - Context = Context, - ThemeName = _themeName - }.RenderAsync(cancellationToken) + await new PreambleTemplate { Context = Context, ThemeName = themeName }.RenderAsync( + cancellationToken + ) ) ); } diff --git a/DiscordChatExporter.Core/Exporting/JsonMessageWriter.cs b/DiscordChatExporter.Core/Exporting/JsonMessageWriter.cs index 6f7424eb..8aa33ddc 100644 --- a/DiscordChatExporter.Core/Exporting/JsonMessageWriter.cs +++ b/DiscordChatExporter.Core/Exporting/JsonMessageWriter.cs @@ -15,18 +15,19 @@ namespace DiscordChatExporter.Core.Exporting; internal class JsonMessageWriter(Stream stream, ExportContext context) : MessageWriter(stream, context) { - private readonly Utf8JsonWriter _writer = new Utf8JsonWriter( - stream, - new JsonWriterOptions - { - // https://github.com/Tyrrrz/DiscordChatExporter/issues/450 - Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping, - Indented = true, - // Validation errors may mask actual failures - // https://github.com/Tyrrrz/DiscordChatExporter/issues/413 - SkipValidation = true - } - ); + private readonly Utf8JsonWriter _writer = + new( + stream, + new JsonWriterOptions + { + // https://github.com/Tyrrrz/DiscordChatExporter/issues/450 + Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping, + Indented = true, + // Validation errors may mask actual failures + // https://github.com/Tyrrrz/DiscordChatExporter/issues/413 + SkipValidation = true + } + ); private async ValueTask FormatMarkdownAsync( string markdown, diff --git a/DiscordChatExporter.Core/Exporting/MessageExporter.cs b/DiscordChatExporter.Core/Exporting/MessageExporter.cs index 5bc47afb..87dae680 100644 --- a/DiscordChatExporter.Core/Exporting/MessageExporter.cs +++ b/DiscordChatExporter.Core/Exporting/MessageExporter.cs @@ -8,8 +8,6 @@ namespace DiscordChatExporter.Core.Exporting; internal partial class MessageExporter(ExportContext context) : IAsyncDisposable { - private readonly ExportContext _context = context; - private int _partitionIndex; private MessageWriter? _writer; @@ -39,7 +37,7 @@ internal partial class MessageExporter(ExportContext context) : IAsyncDisposable // Ensure that the partition limit has not been reached if ( _writer is not null - && _context + && context .Request .PartitionLimit .IsReached(_writer.MessagesWritten, _writer.BytesWritten) @@ -53,10 +51,10 @@ internal partial class MessageExporter(ExportContext context) : IAsyncDisposable if (_writer is not null) return _writer; - Directory.CreateDirectory(_context.Request.OutputDirPath); - var filePath = GetPartitionFilePath(_context.Request.OutputFilePath, _partitionIndex); + Directory.CreateDirectory(context.Request.OutputDirPath); + var filePath = GetPartitionFilePath(context.Request.OutputFilePath, _partitionIndex); - var writer = CreateMessageWriter(filePath, _context.Request.Format, _context); + var writer = CreateMessageWriter(filePath, context.Request.Format, context); await writer.WritePreambleAsync(cancellationToken); return _writer = writer; diff --git a/DiscordChatExporter.Core/Exporting/Partitioning/FileSizePartitionLimit.cs b/DiscordChatExporter.Core/Exporting/Partitioning/FileSizePartitionLimit.cs index 916f2356..520894e4 100644 --- a/DiscordChatExporter.Core/Exporting/Partitioning/FileSizePartitionLimit.cs +++ b/DiscordChatExporter.Core/Exporting/Partitioning/FileSizePartitionLimit.cs @@ -1,11 +1,7 @@ namespace DiscordChatExporter.Core.Exporting.Partitioning; -internal class FileSizePartitionLimit : PartitionLimit +internal class FileSizePartitionLimit(long limit) : PartitionLimit { - private readonly long _limit; - - public FileSizePartitionLimit(long limit) => _limit = limit; - public override bool IsReached(long messagesWritten, long bytesWritten) => - bytesWritten >= _limit; + bytesWritten >= limit; } diff --git a/DiscordChatExporter.Core/Exporting/Partitioning/MessageCountPartitionLimit.cs b/DiscordChatExporter.Core/Exporting/Partitioning/MessageCountPartitionLimit.cs index 221a6531..d97e6ead 100644 --- a/DiscordChatExporter.Core/Exporting/Partitioning/MessageCountPartitionLimit.cs +++ b/DiscordChatExporter.Core/Exporting/Partitioning/MessageCountPartitionLimit.cs @@ -1,11 +1,7 @@ namespace DiscordChatExporter.Core.Exporting.Partitioning; -internal class MessageCountPartitionLimit : PartitionLimit +internal class MessageCountPartitionLimit(long limit) : PartitionLimit { - private readonly long _limit; - - public MessageCountPartitionLimit(long limit) => _limit = limit; - public override bool IsReached(long messagesWritten, long bytesWritten) => - messagesWritten >= _limit; + messagesWritten >= limit; } diff --git a/DiscordChatExporter.Core/Exporting/PlainTextMarkdownVisitor.cs b/DiscordChatExporter.Core/Exporting/PlainTextMarkdownVisitor.cs index 86282ecd..5e7a0ca8 100644 --- a/DiscordChatExporter.Core/Exporting/PlainTextMarkdownVisitor.cs +++ b/DiscordChatExporter.Core/Exporting/PlainTextMarkdownVisitor.cs @@ -1,7 +1,6 @@ using System.Text; using System.Threading; using System.Threading.Tasks; -using DiscordChatExporter.Core.Discord.Data; using DiscordChatExporter.Core.Markdown; using DiscordChatExporter.Core.Markdown.Parsing; using DiscordChatExporter.Core.Utils.Extensions; @@ -11,15 +10,12 @@ namespace DiscordChatExporter.Core.Exporting; internal partial class PlainTextMarkdownVisitor(ExportContext context, StringBuilder buffer) : MarkdownVisitor { - private readonly ExportContext _context = context; - private readonly StringBuilder _buffer = buffer; - protected override ValueTask VisitTextAsync( TextNode text, CancellationToken cancellationToken = default ) { - _buffer.Append(text.Text); + buffer.Append(text.Text); return default; } @@ -28,7 +24,7 @@ internal partial class PlainTextMarkdownVisitor(ExportContext context, StringBui CancellationToken cancellationToken = default ) { - _buffer.Append(emoji.IsCustomEmoji ? $":{emoji.Name}:" : emoji.Name); + buffer.Append(emoji.IsCustomEmoji ? $":{emoji.Name}:" : emoji.Name); return default; } @@ -40,11 +36,11 @@ internal partial class PlainTextMarkdownVisitor(ExportContext context, StringBui { if (mention.Kind == MentionKind.Everyone) { - _buffer.Append("@everyone"); + buffer.Append("@everyone"); } else if (mention.Kind == MentionKind.Here) { - _buffer.Append("@here"); + buffer.Append("@here"); } else if (mention.Kind == MentionKind.User) { @@ -52,30 +48,30 @@ internal partial class PlainTextMarkdownVisitor(ExportContext context, StringBui // which means they need to be populated on demand. // https://github.com/Tyrrrz/DiscordChatExporter/issues/304 if (mention.TargetId is not null) - await _context.PopulateMemberAsync(mention.TargetId.Value, cancellationToken); + await context.PopulateMemberAsync(mention.TargetId.Value, cancellationToken); - var member = mention.TargetId?.Pipe(_context.TryGetMember); + var member = mention.TargetId?.Pipe(context.TryGetMember); var displayName = member?.DisplayName ?? member?.User.DisplayName ?? "Unknown"; - _buffer.Append($"@{displayName}"); + buffer.Append($"@{displayName}"); } else if (mention.Kind == MentionKind.Channel) { - var channel = mention.TargetId?.Pipe(_context.TryGetChannel); + var channel = mention.TargetId?.Pipe(context.TryGetChannel); var name = channel?.Name ?? "deleted-channel"; - _buffer.Append($"#{name}"); + buffer.Append($"#{name}"); // Voice channel marker if (channel?.IsVoice == true) - _buffer.Append(" [voice]"); + buffer.Append(" [voice]"); } else if (mention.Kind == MentionKind.Role) { - var role = mention.TargetId?.Pipe(_context.TryGetRole); + var role = mention.TargetId?.Pipe(context.TryGetRole); var name = role?.Name ?? "deleted-role"; - _buffer.Append($"@{name}"); + buffer.Append($"@{name}"); } } @@ -84,9 +80,9 @@ internal partial class PlainTextMarkdownVisitor(ExportContext context, StringBui CancellationToken cancellationToken = default ) { - _buffer.Append( + buffer.Append( timestamp.Instant is not null - ? _context.FormatDate(timestamp.Instant.Value, timestamp.Format ?? "g") + ? context.FormatDate(timestamp.Instant.Value, timestamp.Format ?? "g") : "Invalid date" ); diff --git a/DiscordChatExporter.Core/Markdown/Parsing/AggregateMatcher.cs b/DiscordChatExporter.Core/Markdown/Parsing/AggregateMatcher.cs index 3e1656cb..6a3e6e7c 100644 --- a/DiscordChatExporter.Core/Markdown/Parsing/AggregateMatcher.cs +++ b/DiscordChatExporter.Core/Markdown/Parsing/AggregateMatcher.cs @@ -2,15 +2,8 @@ namespace DiscordChatExporter.Core.Markdown.Parsing; -internal class AggregateMatcher : IMatcher +internal class AggregateMatcher(IReadOnlyList> matchers) : IMatcher { - private readonly IReadOnlyList> _matchers; - - public AggregateMatcher(IReadOnlyList> matchers) - { - _matchers = matchers; - } - public AggregateMatcher(params IMatcher[] matchers) : this((IReadOnlyList>)matchers) { } @@ -19,7 +12,7 @@ internal class AggregateMatcher : IMatcher ParsedMatch? earliestMatch = null; // Try to match the input with each matcher and get the match with the lowest start index - foreach (var matcher in _matchers) + foreach (var matcher in matchers) { // Try to match var match = matcher.TryMatch(segment); diff --git a/DiscordChatExporter.Core/Markdown/Parsing/ParsedMatch.cs b/DiscordChatExporter.Core/Markdown/Parsing/ParsedMatch.cs index 439ccb5b..a7471f19 100644 --- a/DiscordChatExporter.Core/Markdown/Parsing/ParsedMatch.cs +++ b/DiscordChatExporter.Core/Markdown/Parsing/ParsedMatch.cs @@ -1,14 +1,8 @@ namespace DiscordChatExporter.Core.Markdown.Parsing; -internal class ParsedMatch +internal class ParsedMatch(StringSegment segment, T value) { - public StringSegment Segment { get; } + public StringSegment Segment { get; } = segment; - public T Value { get; } - - public ParsedMatch(StringSegment segment, T value) - { - Segment = segment; - Value = value; - } + public T Value { get; } = value; } diff --git a/DiscordChatExporter.Core/Markdown/Parsing/RegexMatcher.cs b/DiscordChatExporter.Core/Markdown/Parsing/RegexMatcher.cs index 1c7721a2..1fa6288b 100644 --- a/DiscordChatExporter.Core/Markdown/Parsing/RegexMatcher.cs +++ b/DiscordChatExporter.Core/Markdown/Parsing/RegexMatcher.cs @@ -3,20 +3,11 @@ using System.Text.RegularExpressions; namespace DiscordChatExporter.Core.Markdown.Parsing; -internal class RegexMatcher : IMatcher +internal class RegexMatcher(Regex regex, Func transform) : IMatcher { - private readonly Regex _regex; - private readonly Func _transform; - - public RegexMatcher(Regex regex, Func transform) - { - _regex = regex; - _transform = transform; - } - public ParsedMatch? TryMatch(StringSegment segment) { - var match = _regex.Match(segment.Source, segment.StartIndex, segment.Length); + var match = regex.Match(segment.Source, segment.StartIndex, segment.Length); if (!match.Success) return null; @@ -25,11 +16,11 @@ internal class RegexMatcher : IMatcher // Which is super weird because regex.Match(string, int) takes the whole input in context. // So in order to properly account for ^/$ regex tokens, we need to make sure that // the expression also matches on the bigger part of the input. - if (!_regex.IsMatch(segment.Source[..segment.EndIndex], segment.StartIndex)) + if (!regex.IsMatch(segment.Source[..segment.EndIndex], segment.StartIndex)) return null; var segmentMatch = segment.Relocate(match); - var value = _transform(segmentMatch, match); + var value = transform(segmentMatch, match); return value is not null ? new ParsedMatch(segmentMatch, value) : null; } diff --git a/DiscordChatExporter.Core/Markdown/Parsing/StringMatcher.cs b/DiscordChatExporter.Core/Markdown/Parsing/StringMatcher.cs index 7f30735f..881046c9 100644 --- a/DiscordChatExporter.Core/Markdown/Parsing/StringMatcher.cs +++ b/DiscordChatExporter.Core/Markdown/Parsing/StringMatcher.cs @@ -2,37 +2,24 @@ namespace DiscordChatExporter.Core.Markdown.Parsing; -internal class StringMatcher : IMatcher +internal class StringMatcher( + string needle, + StringComparison comparison, + Func transform +) : IMatcher { - private readonly string _needle; - private readonly StringComparison _comparison; - private readonly Func _transform; - - public StringMatcher( - string needle, - StringComparison comparison, - Func transform - ) - { - _needle = needle; - _comparison = comparison; - _transform = transform; - } - public StringMatcher(string needle, Func transform) : this(needle, StringComparison.Ordinal, transform) { } public ParsedMatch? TryMatch(StringSegment segment) { - var index = segment - .Source - .IndexOf(_needle, segment.StartIndex, segment.Length, _comparison); + var index = segment.Source.IndexOf(needle, segment.StartIndex, segment.Length, comparison); if (index < 0) return null; - var segmentMatch = segment.Relocate(index, _needle.Length); - var value = _transform(segmentMatch); + var segmentMatch = segment.Relocate(index, needle.Length); + var value = transform(segmentMatch); return value is not null ? new ParsedMatch(segmentMatch, value) : null; } diff --git a/DiscordChatExporter.Gui/Services/SettingsService.cs b/DiscordChatExporter.Gui/Services/SettingsService.cs index 3e90c33b..7353d0f4 100644 --- a/DiscordChatExporter.Gui/Services/SettingsService.cs +++ b/DiscordChatExporter.Gui/Services/SettingsService.cs @@ -8,7 +8,8 @@ using Microsoft.Win32; namespace DiscordChatExporter.Gui.Services; -public partial class SettingsService : SettingsBase +public partial class SettingsService() + : SettingsBase(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Settings.dat")) { public bool IsUkraineSupportMessageEnabled { get; set; } = true; @@ -44,9 +45,6 @@ public partial class SettingsService : SettingsBase public string? LastAssetsDirPath { get; set; } - public SettingsService() - : base(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Settings.dat")) { } - public override void Save() { // Clear the token if it's not supposed to be persisted diff --git a/DiscordChatExporter.Gui/Services/UpdateService.cs b/DiscordChatExporter.Gui/Services/UpdateService.cs index ca68a924..738ba216 100644 --- a/DiscordChatExporter.Gui/Services/UpdateService.cs +++ b/DiscordChatExporter.Gui/Services/UpdateService.cs @@ -6,27 +6,20 @@ using Onova.Services; namespace DiscordChatExporter.Gui.Services; -public class UpdateService : IDisposable +public class UpdateService(SettingsService settingsService) : IDisposable { private readonly IUpdateManager _updateManager = new UpdateManager( new GithubPackageResolver("Tyrrrz", "DiscordChatExporter", "DiscordChatExporter.zip"), new ZipPackageExtractor() ); - private readonly SettingsService _settingsService; - private Version? _updateVersion; private bool _updatePrepared; private bool _updaterLaunched; - public UpdateService(SettingsService settingsService) - { - _settingsService = settingsService; - } - public async ValueTask CheckForUpdatesAsync() { - if (!_settingsService.IsAutoUpdateEnabled) + if (!settingsService.IsAutoUpdateEnabled) return null; var check = await _updateManager.CheckForUpdatesAsync(); @@ -35,7 +28,7 @@ public class UpdateService : IDisposable public async ValueTask PrepareUpdateAsync(Version version) { - if (!_settingsService.IsAutoUpdateEnabled) + if (!settingsService.IsAutoUpdateEnabled) return; try @@ -55,7 +48,7 @@ public class UpdateService : IDisposable public void FinalizeUpdate(bool needRestart) { - if (!_settingsService.IsAutoUpdateEnabled) + if (!settingsService.IsAutoUpdateEnabled) return; if (_updateVersion is null || !_updatePrepared || _updaterLaunched) diff --git a/DiscordChatExporter.Gui/ViewModels/Dialogs/SettingsViewModel.cs b/DiscordChatExporter.Gui/ViewModels/Dialogs/SettingsViewModel.cs index d24d60bd..10370b1b 100644 --- a/DiscordChatExporter.Gui/ViewModels/Dialogs/SettingsViewModel.cs +++ b/DiscordChatExporter.Gui/ViewModels/Dialogs/SettingsViewModel.cs @@ -8,26 +8,24 @@ using DiscordChatExporter.Gui.ViewModels.Framework; namespace DiscordChatExporter.Gui.ViewModels.Dialogs; -public class SettingsViewModel : DialogScreen +public class SettingsViewModel(SettingsService settingsService) : DialogScreen { - private readonly SettingsService _settingsService; - public bool IsAutoUpdateEnabled { - get => _settingsService.IsAutoUpdateEnabled; - set => _settingsService.IsAutoUpdateEnabled = value; + get => settingsService.IsAutoUpdateEnabled; + set => settingsService.IsAutoUpdateEnabled = value; } public bool IsDarkModeEnabled { - get => _settingsService.IsDarkModeEnabled; - set => _settingsService.IsDarkModeEnabled = value; + get => settingsService.IsDarkModeEnabled; + set => settingsService.IsDarkModeEnabled = value; } public bool IsTokenPersisted { - get => _settingsService.IsTokenPersisted; - set => _settingsService.IsTokenPersisted = value; + get => settingsService.IsTokenPersisted; + set => settingsService.IsTokenPersisted = value; } public IReadOnlyList AvailableThreadInclusions { get; } = @@ -35,8 +33,8 @@ public class SettingsViewModel : DialogScreen public ThreadInclusionMode ThreadInclusionMode { - get => _settingsService.ThreadInclusionMode; - set => _settingsService.ThreadInclusionMode = value; + get => settingsService.ThreadInclusionMode; + set => settingsService.ThreadInclusionMode = value; } public IReadOnlyList AvailableLocales { get; } = new[] @@ -77,21 +75,19 @@ public class SettingsViewModel : DialogScreen public string Locale { - get => _settingsService.Locale; - set => _settingsService.Locale = value; + get => settingsService.Locale; + set => settingsService.Locale = value; } public bool IsUtcNormalizationEnabled { - get => _settingsService.IsUtcNormalizationEnabled; - set => _settingsService.IsUtcNormalizationEnabled = value; + get => settingsService.IsUtcNormalizationEnabled; + set => settingsService.IsUtcNormalizationEnabled = value; } public int ParallelLimit { - get => _settingsService.ParallelLimit; - set => _settingsService.ParallelLimit = Math.Clamp(value, 1, 10); + get => settingsService.ParallelLimit; + set => settingsService.ParallelLimit = Math.Clamp(value, 1, 10); } - - public SettingsViewModel(SettingsService settingsService) => _settingsService = settingsService; } diff --git a/DiscordChatExporter.Gui/ViewModels/Framework/DialogManager.cs b/DiscordChatExporter.Gui/ViewModels/Framework/DialogManager.cs index c4b76faa..15958817 100644 --- a/DiscordChatExporter.Gui/ViewModels/Framework/DialogManager.cs +++ b/DiscordChatExporter.Gui/ViewModels/Framework/DialogManager.cs @@ -8,19 +8,13 @@ using Stylet; namespace DiscordChatExporter.Gui.ViewModels.Framework; -public class DialogManager : IDisposable +public class DialogManager(IViewManager viewManager) : IDisposable { - private readonly IViewManager _viewManager; private readonly SemaphoreSlim _dialogLock = new(1, 1); - public DialogManager(IViewManager viewManager) - { - _viewManager = viewManager; - } - public async ValueTask ShowDialogAsync(DialogScreen dialogScreen) { - var view = _viewManager.CreateAndBindViewForModelIfNecessary(dialogScreen); + var view = viewManager.CreateAndBindViewForModelIfNecessary(dialogScreen); void OnDialogOpened(object? openSender, DialogOpenedEventArgs openArgs) {