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.
143 lines
4.4 KiB
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();
|
|
}
|
|
} |