You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
DiscordChatExporter/DiscordChatExporter.Core/Exporting/HtmlMessageWriter.cs

143 lines
4.4 KiB

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using DiscordChatExporter.Core.Discord.Data;
using WebMarkupMin.Core;
namespace DiscordChatExporter.Core.Exporting;
internal class HtmlMessageWriter : MessageWriter
{
private readonly TextWriter _writer;
private readonly string _themeName;
private readonly HtmlMinifier _minifier = new();
private readonly List<Message> _messageGroup = new();
public HtmlMessageWriter(Stream stream, ExportContext context, string themeName)
: base(stream, context)
{
_writer = new StreamWriter(stream);
_themeName = themeName;
}
private bool CanJoinGroup(Message message)
{
// If the group is empty, any message can join it
if (_messageGroup.LastOrDefault() is not { } lastMessage)
return true;
// Reply messages cannot join existing groups because they need to appear first
if (message.Kind == MessageKind.Reply)
return false;
// Grouping for system notifications
if (message.Kind.IsSystemNotification())
{
// Can only be grouped with other system notifications
if (!lastMessage.Kind.IsSystemNotification())
return false;
}
// Grouping for normal messages
else
{
// Can only be grouped with other normal messages
if (lastMessage.Kind.IsSystemNotification())
return false;
// Messages must be within 7 minutes of each other
if ((message.Timestamp - lastMessage.Timestamp).Duration().TotalMinutes > 7)
return false;
// Messages must be sent by the same author
if (message.Author.Id != lastMessage.Author.Id)
return false;
// If the author changed their name after the last message, their new messages
// cannot join the existing group.
if (!string.Equals(message.Author.FullName, lastMessage.Author.FullName, StringComparison.Ordinal))
return false;
}
return true;
}
// Use <!--wmm:ignore--> to preserve blocks of code inside the templates
private string Minify(string html) => _minifier.Minify(html, false).MinifiedContent;
public override async ValueTask WritePreambleAsync(
CancellationToken cancellationToken = default)
{
await _writer.WriteLineAsync(
Minify(
await new PreambleTemplate
{
Context = Context,
ThemeName = _themeName
}.RenderAsync(cancellationToken)
)
);
}
private async ValueTask WriteMessageGroupAsync(
IReadOnlyList<Message> messages,
CancellationToken cancellationToken = default)
{
await _writer.WriteLineAsync(
Minify(
await new MessageGroupTemplate
{
Context = Context,
Messages = messages
}.RenderAsync(cancellationToken)
)
);
}
public override async ValueTask WriteMessageAsync(
Message message,
CancellationToken cancellationToken = default)
{
await base.WriteMessageAsync(message, cancellationToken);
// If the message can be grouped, buffer it for now
if (CanJoinGroup(message))
{
_messageGroup.Add(message);
}
// Otherwise, flush the group and render messages
else
{
await WriteMessageGroupAsync(_messageGroup, cancellationToken);
_messageGroup.Clear();
_messageGroup.Add(message);
}
}
public override async ValueTask WritePostambleAsync(CancellationToken cancellationToken = default)
{
// Flush current message group
if (_messageGroup.Any())
await WriteMessageGroupAsync(_messageGroup, cancellationToken);
await _writer.WriteLineAsync(
Minify(
await new PostambleTemplate
{
ExportContext = Context,
MessagesWritten = MessagesWritten
}.RenderAsync(cancellationToken)
)
);
}
public override async ValueTask DisposeAsync()
{
await _writer.DisposeAsync();
await base.DisposeAsync();
}
}