mirror of
https://github.com/Tyrrrz/DiscordChatExporter.git
synced 2026-03-14 02:42:30 +00:00
Refactor
This commit is contained in:
@@ -5,7 +5,6 @@ using System.Text.Json;
|
||||
using DiscordChatExporter.Core.Discord.Data.Common;
|
||||
using DiscordChatExporter.Core.Utils.Extensions;
|
||||
using JsonExtensions.Reading;
|
||||
using FileSize = DiscordChatExporter.Core.Discord.Data.Common.FileSize;
|
||||
|
||||
namespace DiscordChatExporter.Core.Discord.Data
|
||||
{
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
using System;
|
||||
using System.Globalization;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
namespace DiscordChatExporter.Core.Discord.Data.Common
|
||||
{
|
||||
@@ -61,5 +63,37 @@ namespace DiscordChatExporter.Core.Discord.Data.Common
|
||||
public partial struct FileSize
|
||||
{
|
||||
public static FileSize FromBytes(long bytes) => new(bytes);
|
||||
|
||||
public static FileSize? TryParse(string value)
|
||||
{
|
||||
var match = Regex.Match(value, @"^(\d+[\.,]?\d*)\s*(\w)?b$", RegexOptions.IgnoreCase);
|
||||
|
||||
// Number part
|
||||
if (!double.TryParse(
|
||||
match.Groups[1].Value,
|
||||
NumberStyles.Float,
|
||||
CultureInfo.InvariantCulture,
|
||||
out var number))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
// Magnitude part
|
||||
var magnitude = match.Groups[2].Value.ToUpperInvariant() switch
|
||||
{
|
||||
"G" => 1_000_000_000,
|
||||
"M" => 1_000_000,
|
||||
"K" => 1_000,
|
||||
"" => 1,
|
||||
_ => -1
|
||||
};
|
||||
|
||||
if (magnitude < 0)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return FromBytes((long) (number * magnitude));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -48,7 +48,7 @@ namespace DiscordChatExporter.Core.Discord
|
||||
}
|
||||
|
||||
public static Snowflake Parse(string str, IFormatProvider? formatProvider) =>
|
||||
TryParse(str, formatProvider) ?? throw new FormatException($"Invalid snowflake: {str}.");
|
||||
TryParse(str, formatProvider) ?? throw new FormatException($"Invalid snowflake '{str}'.");
|
||||
|
||||
public static Snowflake Parse(string str) => Parse(str, null);
|
||||
}
|
||||
|
||||
@@ -5,7 +5,6 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="ByteSize" Version="2.0.0" />
|
||||
<PackageReference Include="JsonExtensions" Version="1.0.1" />
|
||||
<PackageReference Include="MiniRazor.CodeGen" Version="2.1.2" />
|
||||
<PackageReference Include="Polly" Version="7.2.1" />
|
||||
|
||||
@@ -4,6 +4,7 @@ using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
using DiscordChatExporter.Core.Discord;
|
||||
using DiscordChatExporter.Core.Discord.Data;
|
||||
using DiscordChatExporter.Core.Exporting.Partitioning;
|
||||
using DiscordChatExporter.Core.Utils;
|
||||
|
||||
namespace DiscordChatExporter.Core.Exporting
|
||||
@@ -28,7 +29,7 @@ namespace DiscordChatExporter.Core.Exporting
|
||||
|
||||
public Snowflake? Before { get; }
|
||||
|
||||
public IPartitioner Partitoner { get; }
|
||||
public PartitionLimit PartitionLimit { get; }
|
||||
|
||||
public bool ShouldDownloadMedia { get; }
|
||||
|
||||
@@ -43,7 +44,7 @@ namespace DiscordChatExporter.Core.Exporting
|
||||
ExportFormat format,
|
||||
Snowflake? after,
|
||||
Snowflake? before,
|
||||
IPartitioner partitioner,
|
||||
PartitionLimit partitionLimit,
|
||||
bool shouldDownloadMedia,
|
||||
bool shouldReuseMedia,
|
||||
string dateFormat)
|
||||
@@ -54,7 +55,7 @@ namespace DiscordChatExporter.Core.Exporting
|
||||
Format = format;
|
||||
After = after;
|
||||
Before = before;
|
||||
Partitoner = partitioner;
|
||||
PartitionLimit = partitionLimit;
|
||||
ShouldDownloadMedia = shouldDownloadMedia;
|
||||
ShouldReuseMedia = shouldReuseMedia;
|
||||
DateFormat = dateFormat;
|
||||
|
||||
@@ -1,21 +1,15 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading.Tasks;
|
||||
using ByteSizeLib;
|
||||
using DiscordChatExporter.Core.Discord.Data;
|
||||
using DiscordChatExporter.Core.Exporting;
|
||||
using DiscordChatExporter.Core.Exporting.Partitioners;
|
||||
using DiscordChatExporter.Core.Exporting.Writers;
|
||||
|
||||
namespace DiscordChatExporter.Core.Exporting
|
||||
{
|
||||
internal partial class MessageExporter : IAsyncDisposable
|
||||
{
|
||||
|
||||
private readonly ExportContext _context;
|
||||
|
||||
private long _messageCount;
|
||||
private int _partitionIndex;
|
||||
private MessageWriter? _writer;
|
||||
|
||||
@@ -24,17 +18,6 @@ namespace DiscordChatExporter.Core.Exporting
|
||||
_context = context;
|
||||
}
|
||||
|
||||
private bool IsPartitionLimitReached()
|
||||
{
|
||||
if (_writer is null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return _context.Request.Partitoner.IsLimitReached(
|
||||
new ExportPartitioningContext(_messageCount, _writer.SizeInBytes));
|
||||
}
|
||||
|
||||
private async ValueTask ResetWriterAsync()
|
||||
{
|
||||
if (_writer is not null)
|
||||
@@ -48,7 +31,8 @@ namespace DiscordChatExporter.Core.Exporting
|
||||
private async ValueTask<MessageWriter> GetWriterAsync()
|
||||
{
|
||||
// Ensure partition limit has not been exceeded
|
||||
if (_writer != null && IsPartitionLimitReached())
|
||||
if (_writer is not null &&
|
||||
_context.Request.PartitionLimit.IsReached(_writer.MessagesWritten, _writer.BytesWritten))
|
||||
{
|
||||
await ResetWriterAsync();
|
||||
_partitionIndex++;
|
||||
@@ -74,7 +58,6 @@ namespace DiscordChatExporter.Core.Exporting
|
||||
{
|
||||
var writer = await GetWriterAsync();
|
||||
await writer.WriteMessageAsync(message);
|
||||
_messageCount++;
|
||||
}
|
||||
|
||||
public async ValueTask DisposeAsync() => await ResetWriterAsync();
|
||||
@@ -82,9 +65,7 @@ namespace DiscordChatExporter.Core.Exporting
|
||||
|
||||
internal partial class MessageExporter
|
||||
{
|
||||
private static string GetPartitionFilePath(
|
||||
string baseFilePath,
|
||||
int partitionIndex)
|
||||
private static string GetPartitionFilePath(string baseFilePath, int partitionIndex)
|
||||
{
|
||||
// First partition - don't change file name
|
||||
if (partitionIndex <= 0)
|
||||
|
||||
@@ -1,18 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace DiscordChatExporter.Core.Exporting.Partitioners
|
||||
{
|
||||
public class ExportPartitioningContext
|
||||
{
|
||||
public long MessageCount { get; }
|
||||
public long SizeInBytes { get; }
|
||||
|
||||
public ExportPartitioningContext(long messageCount, long sizeInBytes)
|
||||
{
|
||||
MessageCount = messageCount;
|
||||
SizeInBytes = sizeInBytes;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,21 +0,0 @@
|
||||
using DiscordChatExporter.Core.Exporting.Partitioners;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace DiscordChatExporter.Core.Exporting
|
||||
{
|
||||
public class FileSizePartitioner : IPartitioner
|
||||
{
|
||||
private long _bytesPerFile;
|
||||
|
||||
public FileSizePartitioner(long bytesPerFile)
|
||||
{
|
||||
_bytesPerFile = bytesPerFile;
|
||||
}
|
||||
public bool IsLimitReached(ExportPartitioningContext context)
|
||||
{
|
||||
return context.SizeInBytes >= _bytesPerFile;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,12 +0,0 @@
|
||||
using DiscordChatExporter.Core.Exporting.Partitioners;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace DiscordChatExporter.Core.Exporting
|
||||
{
|
||||
public interface IPartitioner
|
||||
{
|
||||
bool IsLimitReached(ExportPartitioningContext context);
|
||||
}
|
||||
}
|
||||
@@ -1,25 +0,0 @@
|
||||
using DiscordChatExporter.Core.Exporting.Partitioners;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace DiscordChatExporter.Core.Exporting
|
||||
{
|
||||
public class MessageCountPartitioner : IPartitioner
|
||||
{
|
||||
|
||||
private int _messagesPerPartition;
|
||||
|
||||
public MessageCountPartitioner(int messagesPerPartition)
|
||||
{
|
||||
_messagesPerPartition = messagesPerPartition;
|
||||
}
|
||||
|
||||
public bool IsLimitReached(ExportPartitioningContext context)
|
||||
{
|
||||
return context.MessageCount > 0 &&
|
||||
_messagesPerPartition != 0 &&
|
||||
context.MessageCount % _messagesPerPartition == 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
using DiscordChatExporter.Core.Exporting;
|
||||
using DiscordChatExporter.Core.Exporting.Partitioners;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace DiscordChatExporter.Core.Exporting
|
||||
{
|
||||
public class NullPartitioner : IPartitioner
|
||||
{
|
||||
public bool IsLimitReached(ExportPartitioningContext context)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
namespace DiscordChatExporter.Core.Exporting.Partitioning
|
||||
{
|
||||
public class FileSizePartitionLimit : PartitionLimit
|
||||
{
|
||||
private readonly long _limit;
|
||||
|
||||
public FileSizePartitionLimit(long limit) => _limit = limit;
|
||||
|
||||
public override bool IsReached(long messagesWritten, long bytesWritten) =>
|
||||
bytesWritten >= _limit;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
namespace DiscordChatExporter.Core.Exporting.Partitioning
|
||||
{
|
||||
public class MessageCountPartitionLimit : PartitionLimit
|
||||
{
|
||||
private readonly long _limit;
|
||||
|
||||
public MessageCountPartitionLimit(long limit) => _limit = limit;
|
||||
|
||||
public override bool IsReached(long messagesWritten, long bytesWritten) =>
|
||||
messagesWritten >= _limit;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
namespace DiscordChatExporter.Core.Exporting.Partitioning
|
||||
{
|
||||
public class NullPartitionLimit : PartitionLimit
|
||||
{
|
||||
public static NullPartitionLimit Instance { get; } = new();
|
||||
|
||||
public override bool IsReached(long messagesWritten, long bytesWritten) => false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
using DiscordChatExporter.Core.Discord.Data.Common;
|
||||
|
||||
namespace DiscordChatExporter.Core.Exporting.Partitioning
|
||||
{
|
||||
public abstract partial class PartitionLimit
|
||||
{
|
||||
public abstract bool IsReached(long messagesWritten, long bytesWritten);
|
||||
}
|
||||
|
||||
public partial class PartitionLimit
|
||||
{
|
||||
public static PartitionLimit Parse(string value)
|
||||
{
|
||||
var fileSize = FileSize.TryParse(value);
|
||||
if (fileSize is not null)
|
||||
return new FileSizePartitionLimit(fileSize.Value.TotalBytes);
|
||||
|
||||
var messageCount = int.Parse(value);
|
||||
return new MessageCountPartitionLimit(messageCount);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -58,6 +58,8 @@ namespace DiscordChatExporter.Core.Exporting.Writers
|
||||
|
||||
public override async ValueTask WriteMessageAsync(Message message)
|
||||
{
|
||||
await base.WriteMessageAsync(message);
|
||||
|
||||
// Author ID
|
||||
await _writer.WriteAsync(CsvEncode(message.Author.Id.ToString()));
|
||||
await _writer.WriteAsync(',');
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
@namespace DiscordChatExporter.Core.Exporting.Writers.Html
|
||||
@inherits MiniRazor.TemplateBase<DiscordChatExporter.Core.Exporting.Writers.Html.LayoutTemplateContext>
|
||||
@inherits MiniRazor.TemplateBase<DiscordChatExporter.Core.Exporting.Writers.Html.PostambleTemplateContext>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="postamble">
|
||||
<div class="postamble__entry">Exported @Model.MessageCount.ToString("N0") message(s)</div>
|
||||
<div class="postamble__entry">Exported @Model.MessagesWritten.ToString("N0") message(s)</div>
|
||||
</div>
|
||||
|
||||
</body>
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
namespace DiscordChatExporter.Core.Exporting.Writers.Html
|
||||
{
|
||||
internal class PostambleTemplateContext
|
||||
{
|
||||
public ExportContext ExportContext { get; }
|
||||
|
||||
public long MessagesWritten { get; }
|
||||
|
||||
public PostambleTemplateContext(ExportContext exportContext, long messagesWritten)
|
||||
{
|
||||
ExportContext = exportContext;
|
||||
MessagesWritten = messagesWritten;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2,7 +2,7 @@
|
||||
@using System.Threading.Tasks
|
||||
@using Tyrrrz.Extensions
|
||||
@namespace DiscordChatExporter.Core.Exporting.Writers.Html
|
||||
@inherits MiniRazor.TemplateBase<DiscordChatExporter.Core.Exporting.Writers.Html.LayoutTemplateContext>
|
||||
@inherits MiniRazor.TemplateBase<DiscordChatExporter.Core.Exporting.Writers.Html.PreambleTemplateContext>
|
||||
|
||||
@{
|
||||
string FormatDate(DateTimeOffset date) => Model.ExportContext.FormatDate(date);
|
||||
|
||||
@@ -1,18 +1,15 @@
|
||||
namespace DiscordChatExporter.Core.Exporting.Writers.Html
|
||||
{
|
||||
internal class LayoutTemplateContext
|
||||
internal class PreambleTemplateContext
|
||||
{
|
||||
public ExportContext ExportContext { get; }
|
||||
|
||||
public string ThemeName { get; }
|
||||
|
||||
public long MessageCount { get; }
|
||||
|
||||
public LayoutTemplateContext(ExportContext exportContext, string themeName, long messageCount)
|
||||
public PreambleTemplateContext(ExportContext exportContext, string themeName)
|
||||
{
|
||||
ExportContext = exportContext;
|
||||
ThemeName = themeName;
|
||||
MessageCount = messageCount;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -14,8 +14,6 @@ namespace DiscordChatExporter.Core.Exporting.Writers
|
||||
|
||||
private readonly List<Message> _messageGroupBuffer = new();
|
||||
|
||||
private long _messageCount;
|
||||
|
||||
public HtmlMessageWriter(Stream stream, ExportContext context, string themeName)
|
||||
: base(stream, context)
|
||||
{
|
||||
@@ -25,7 +23,7 @@ namespace DiscordChatExporter.Core.Exporting.Writers
|
||||
|
||||
public override async ValueTask WritePreambleAsync()
|
||||
{
|
||||
var templateContext = new LayoutTemplateContext(Context, _themeName, _messageCount);
|
||||
var templateContext = new PreambleTemplateContext(Context, _themeName);
|
||||
|
||||
await _writer.WriteLineAsync(
|
||||
await PreambleTemplate.RenderAsync(templateContext)
|
||||
@@ -43,6 +41,8 @@ namespace DiscordChatExporter.Core.Exporting.Writers
|
||||
|
||||
public override async ValueTask WriteMessageAsync(Message message)
|
||||
{
|
||||
await base.WriteMessageAsync(message);
|
||||
|
||||
// If message group is empty or the given message can be grouped, buffer the given message
|
||||
if (!_messageGroupBuffer.Any() || MessageGroup.CanJoin(_messageGroupBuffer.Last(), message))
|
||||
{
|
||||
@@ -56,9 +56,6 @@ namespace DiscordChatExporter.Core.Exporting.Writers
|
||||
_messageGroupBuffer.Clear();
|
||||
_messageGroupBuffer.Add(message);
|
||||
}
|
||||
|
||||
// Increment message count
|
||||
_messageCount++;
|
||||
}
|
||||
|
||||
public override async ValueTask WritePostambleAsync()
|
||||
@@ -67,7 +64,7 @@ namespace DiscordChatExporter.Core.Exporting.Writers
|
||||
if (_messageGroupBuffer.Any())
|
||||
await WriteMessageGroupAsync(MessageGroup.Join(_messageGroupBuffer));
|
||||
|
||||
var templateContext = new LayoutTemplateContext(Context, _themeName, _messageCount);
|
||||
var templateContext = new PostambleTemplateContext(Context, MessagesWritten);
|
||||
|
||||
await _writer.WriteLineAsync(
|
||||
await PostambleTemplate.RenderAsync(templateContext)
|
||||
|
||||
@@ -13,8 +13,6 @@ namespace DiscordChatExporter.Core.Exporting.Writers
|
||||
{
|
||||
private readonly Utf8JsonWriter _writer;
|
||||
|
||||
private long _messageCount;
|
||||
|
||||
public JsonMessageWriter(Stream stream, ExportContext context)
|
||||
: base(stream, context)
|
||||
{
|
||||
@@ -211,6 +209,8 @@ namespace DiscordChatExporter.Core.Exporting.Writers
|
||||
|
||||
public override async ValueTask WriteMessageAsync(Message message)
|
||||
{
|
||||
await base.WriteMessageAsync(message);
|
||||
|
||||
_writer.WriteStartObject();
|
||||
|
||||
// Metadata
|
||||
@@ -279,8 +279,6 @@ namespace DiscordChatExporter.Core.Exporting.Writers
|
||||
|
||||
_writer.WriteEndObject();
|
||||
await _writer.FlushAsync();
|
||||
|
||||
_messageCount++;
|
||||
}
|
||||
|
||||
public override async ValueTask WritePostambleAsync()
|
||||
@@ -288,7 +286,7 @@ namespace DiscordChatExporter.Core.Exporting.Writers
|
||||
// Message array (end)
|
||||
_writer.WriteEndArray();
|
||||
|
||||
_writer.WriteNumber("messageCount", _messageCount);
|
||||
_writer.WriteNumber("messageCount", MessagesWritten);
|
||||
|
||||
// Root object (end)
|
||||
_writer.WriteEndObject();
|
||||
|
||||
@@ -11,17 +11,23 @@ namespace DiscordChatExporter.Core.Exporting.Writers
|
||||
|
||||
protected ExportContext Context { get; }
|
||||
|
||||
public long MessagesWritten { get; private set; }
|
||||
|
||||
public long BytesWritten => Stream.Length;
|
||||
|
||||
protected MessageWriter(Stream stream, ExportContext context)
|
||||
{
|
||||
Stream = stream;
|
||||
Context = context;
|
||||
}
|
||||
|
||||
public long SizeInBytes => Stream.Length;
|
||||
|
||||
public virtual ValueTask WritePreambleAsync() => default;
|
||||
|
||||
public abstract ValueTask WriteMessageAsync(Message message);
|
||||
public virtual ValueTask WriteMessageAsync(Message message)
|
||||
{
|
||||
MessagesWritten++;
|
||||
return default;
|
||||
}
|
||||
|
||||
public virtual ValueTask WritePostambleAsync() => default;
|
||||
|
||||
|
||||
@@ -12,8 +12,6 @@ namespace DiscordChatExporter.Core.Exporting.Writers
|
||||
{
|
||||
private readonly TextWriter _writer;
|
||||
|
||||
private long _messageCount;
|
||||
|
||||
public PlainTextMessageWriter(Stream stream, ExportContext context)
|
||||
: base(stream, context)
|
||||
{
|
||||
@@ -130,26 +128,29 @@ namespace DiscordChatExporter.Core.Exporting.Writers
|
||||
|
||||
public override async ValueTask WriteMessageAsync(Message message)
|
||||
{
|
||||
await base.WriteMessageAsync(message);
|
||||
|
||||
// Header
|
||||
await WriteMessageHeaderAsync(message);
|
||||
|
||||
// Content
|
||||
if (!string.IsNullOrWhiteSpace(message.Content))
|
||||
await _writer.WriteLineAsync(FormatMarkdown(message.Content));
|
||||
|
||||
await _writer.WriteLineAsync();
|
||||
|
||||
// Attachments, embeds, reactions
|
||||
await WriteAttachmentsAsync(message.Attachments);
|
||||
await WriteEmbedsAsync(message.Embeds);
|
||||
await WriteReactionsAsync(message.Reactions);
|
||||
|
||||
await _writer.WriteLineAsync();
|
||||
|
||||
_messageCount++;
|
||||
}
|
||||
|
||||
public override async ValueTask WritePostambleAsync()
|
||||
{
|
||||
await _writer.WriteLineAsync('='.Repeat(62));
|
||||
await _writer.WriteLineAsync($"Exported {_messageCount:N0} message(s)");
|
||||
await _writer.WriteLineAsync($"Exported {MessagesWritten:N0} message(s)");
|
||||
await _writer.WriteLineAsync('='.Repeat(62));
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user