pull/947/head
Tyrrrz 2 years ago
parent 8612d2c84a
commit a80ee2943f

@ -1,6 +1,4 @@
using System.Text.RegularExpressions; namespace DiscordChatExporter.Core.Discord.Data;
namespace DiscordChatExporter.Core.Discord.Data;
// https://discord.com/developers/docs/resources/channel#message-object-message-types // https://discord.com/developers/docs/resources/channel#message-object-message-types
public enum MessageKind public enum MessageKind
@ -18,10 +16,6 @@ public enum MessageKind
public static class MessageKindExtensions public static class MessageKindExtensions
{ {
public static bool IsSystemMessage(this MessageKind c) => public static bool IsSystemNotification(this MessageKind c) =>
c is not MessageKind.Default and not MessageKind.Reply; c is not MessageKind.Default and not MessageKind.Reply;
public static string ToCssIdFormat(this MessageKind c) =>
string.Join("-", Regex.Split(c.ToString(), @"(?<!^)(?=[A-Z])")).ToLowerInvariant();
} }

@ -7,7 +7,7 @@
<ItemGroup> <ItemGroup>
<PackageReference Include="Gress" Version="2.0.1" /> <PackageReference Include="Gress" Version="2.0.1" />
<PackageReference Include="JsonExtensions" Version="1.2.0" /> <PackageReference Include="JsonExtensions" Version="1.2.0" />
<PackageReference Include="MiniRazor.CodeGen" Version="2.2.1" /> <PackageReference Include="MiniRazor.CodeGen" Version="2.2.2" />
<PackageReference Include="Polly" Version="7.2.3" /> <PackageReference Include="Polly" Version="7.2.3" />
<PackageReference Include="Superpower" Version="3.0.0" /> <PackageReference Include="Superpower" Version="3.0.0" />
<PackageReference Include="WebMarkupMin.Core" Version="2.12.0" /> <PackageReference Include="WebMarkupMin.Core" Version="2.12.0" />

@ -46,69 +46,68 @@
<div class="chatlog__message-group"> <div class="chatlog__message-group">
@foreach (var (message, i) in Model.Messages.WithIndex()) @foreach (var (message, i) in Model.Messages.WithIndex())
{ {
var isSystemMessage = message.Kind.IsSystemMessage();
var isFirst = i == 0; var isFirst = i == 0;
<div id="chatlog__message-container-@message.Id" class="chatlog__message-container @(message.IsPinned ? "chatlog__message-container--pinned" : null)" data-message-id="@message.Id"> <div id="chatlog__message-container-@message.Id" class="chatlog__message-container @(message.IsPinned ? "chatlog__message-container--pinned" : null)" data-message-id="@message.Id">
<div class="chatlog__message"> <div class="chatlog__message">
@{/* Left side */} @{/* System notification */}
<div class="chatlog__message-aside"> @if (message.Kind.IsSystemNotification())
@if (isSystemMessage)
{
<svg class=chatlog__system-message-icon><use href="#@message.Kind.ToCssIdFormat()-icon"></use></svg>
}
else if(isFirst)
{
// Reference symbol
if (message.Reference is not null)
{
<div class="chatlog__reference-symbol"></div>
}
// Avatar
<img class="chatlog__avatar" src="@await ResolveUrlAsync(message.Author.AvatarUrl)" alt="Avatar" loading="lazy">
}
else
{ {
<div class="chatlog__short-timestamp" title="@FormatDate(message.Timestamp)">@message.Timestamp.ToLocalString("t")</div> // System notifications are grouped even if the message author is different.
} // That's why we have to update the user values with the author of the current message.
</div>
@{/* Right side */}
<div class="chatlog__message-primary">
@if (isSystemMessage)
{
// system messages are grouped even if the message author is different
// that's why we have to update the user values with the author of the current message
userMember = Model.ExportContext.TryGetMember(message.Author.Id); userMember = Model.ExportContext.TryGetMember(message.Author.Id);
userColor = Model.ExportContext.TryGetUserColor(message.Author.Id); userColor = Model.ExportContext.TryGetUserColor(message.Author.Id);
userNick = message.Author.IsBot userNick = message.Author.IsBot
? message.Author.Name ? message.Author.Name
: userMember?.Nick ?? message.Author.Name; : userMember?.Nick ?? message.Author.Name;
<div class="chatlog__message-aside">
<svg class=chatlog__system-notification-icon><use href="#@message.Kind.ToString().ToDashCase().ToLowerInvariant()-icon"></use></svg>
</div>
<div class="chatlog__message-primary">
<div class="chatlog__header"> <div class="chatlog__header">
@{/* Author name */} @{/* Author name */}
<span class="chatlog__author" style="@(userColor is not null ? $"color: rgb({userColor.Value.R}, {userColor.Value.G}, {userColor.Value.B})" : null)" title="@message.Author.FullName" data-user-id="@message.Author.Id">@userNick</span> <span class="chatlog__author" style="@(userColor is not null ? $"color: rgb({userColor.Value.R}, {userColor.Value.G}, {userColor.Value.B})" : null)" title="@message.Author.FullName" data-user-id="@message.Author.Id">@userNick</span>
@{/* System message content */} @{/* System notification content */}
@if(message.Kind == MessageKind.ChannelPinnedMessage) @if(message.Kind == MessageKind.ChannelPinnedMessage)
{ {
<span class="chatlog__system-message"> pinned</span><span class="chatlog__system-message-reference-link chatlog__reference-link" onclick="scrollToMessage(event, @message.Reference.MessageId)"> a message</span><span class="chatlog__system-message"> to this channel.</span> <span class="chatlog__system-notification"> pinned</span><span class="chatlog__system-message-reference-link chatlog__reference-link" onclick="scrollToMessage(event, '@message.Reference?.MessageId')"> a message</span><span class="chatlog__system-message"> to this channel.</span>
} }
else else
{ {
<span class="chatlog__system-message"> <!--wmm:ignore-->@Raw(FormatMarkdown(Char.ToLowerInvariant(message.Content[0]) + message.Content.Substring(1)))<!--/wmm:ignore--></span> <span class="chatlog__system-notification">@(char.ToLowerInvariant(message.Content[0]) + message.Content[1..])</span>
} }
@{/* Timestamp */} @{/* Timestamp */}
<span class="chatlog__timestamp"><a href="#chatlog__message-container-@message.Id">@FormatDate(message.Timestamp)</a></span> <span class="chatlog__timestamp"><a href="#chatlog__message-container-@message.Id">@FormatDate(message.Timestamp)</a></span>
</div> </div>
</div>
}
// Regular message
else
{
<div class="chatlog__message-aside">
@if (isFirst)
{
// Reference symbol
if (message.Reference is not null)
{
<div class="chatlog__reference-symbol"></div>
}
// Avatar
<img class="chatlog__avatar" src="@await ResolveUrlAsync(message.Author.AvatarUrl)" alt="Avatar" loading="lazy">
} }
else if (isFirst) else
{
<div class="chatlog__short-timestamp" title="@FormatDate(message.Timestamp)">@message.Timestamp.ToLocalString("t")</div>
}
</div>
<div class="chatlog__message-primary">
@if (isFirst)
{ {
// Reference // Reference
if (message.Reference is not null) if (message.Reference is not null)
@ -167,8 +166,6 @@
} }
@{/* Content */} @{/* Content */}
@if (!isSystemMessage)
{
@if (!string.IsNullOrWhiteSpace(message.Content) || message.EditedTimestamp is not null) @if (!string.IsNullOrWhiteSpace(message.Content) || message.EditedTimestamp is not null)
{ {
<div class="chatlog__content chatlog__markdown"> <div class="chatlog__content chatlog__markdown">
@ -185,7 +182,6 @@
} }
</div> </div>
} }
}
@{/* Attachments */} @{/* Attachments */}
@foreach (var attachment in message.Attachments) @foreach (var attachment in message.Attachments)
@ -519,6 +515,7 @@
</div> </div>
} }
</div> </div>
}
</div> </div>
</div> </div>
} }

@ -254,19 +254,18 @@
unicode-bidi: bidi-override; unicode-bidi: bidi-override;
} }
.chatlog__system-message{ .chatlog__system-notification {
color: #96989D color: @Themed("rgb(150, 152, 157)", "rgb(94, 103, 114)")
} }
.chatlog__system-message-reference-link{ .chatlog__system-notification-reference-link {
font-weight: 500; font-weight: 500;
color: #ffffff color: #ffffff
} }
.chatlog__system-message-icon{ .chatlog__system-notification-icon {
width: 18px; width: 18px;
height: 18px; height: 18px;
margin-right: 0.25rem;
} }
.chatlog__header { .chatlog__header {
@ -809,17 +808,14 @@
<path fill="#f4f5fb" d="M530,215a25,25,0,0,1-25-25V50a25,25,0,0,1,16.23-23.41L693.41,198.77A25,25,0,0,1,670,215Z" /> <path fill="#f4f5fb" d="M530,215a25,25,0,0,1-25-25V50a25,25,0,0,1,16.23-23.41L693.41,198.77A25,25,0,0,1,670,215Z" />
<path fill="#7789c4" d="M530,70.71,649.29,190H530V70.71M530,0a50,50,0,0,0-50,50V190a50,50,0,0,0,50,50H670a50,50,0,0,0,50-50Z" /> <path fill="#7789c4" d="M530,70.71,649.29,190H530V70.71M530,0a50,50,0,0,0-50,50V190a50,50,0,0,0,50,50H670a50,50,0,0,0,50-50Z" />
</symbol> </symbol>
<symbol id="channel-pinned-message-icon" fill="none" viewBox="0 0 17 17"> <symbol id="channel-pinned-message-icon" viewBox="0 0 18 18">
<path d="m16.908 8.39684-8.29587-8.295827-1.18584 1.184157 1.18584 1.18584-4.14834 4.1475v.00167l-1.18583-1.18583-1.185 1.18583 3.55583 3.55502-4.740831 4.74 1.185001 1.185 4.74083-4.74 3.55581 3.555 1.185-1.185-1.185-1.185 4.1475-4.14836h.0009l1.185 1.185z" fill="#b9bbbe"/> <path fill="#b9bbbe" d="m16.908 8.39684-8.29587-8.295827-1.18584 1.184157 1.18584 1.18584-4.14834 4.1475v.00167l-1.18583-1.18583-1.185 1.18583 3.55583 3.55502-4.740831 4.74 1.185001 1.185 4.74083-4.74 3.55581 3.555 1.185-1.185-1.185-1.185 4.1475-4.14836h.0009l1.185 1.185z" />
</symbol> </symbol>
<symbol id="call-icon" viewBox="0 0 18 18"> <symbol id="call-icon" viewBox="0 0 18 18">
<path fill="#3ba55c" fill-rule="evenodd" d="M17.7163041 15.36645368c-.0190957.02699568-1.9039523 2.6680735-2.9957762 2.63320406-3.0676659-.09785935-6.6733809-3.07188394-9.15694343-5.548738C3.08002193 9.9740657.09772497 6.3791404 0 3.3061316v-.024746C0 2.2060575 2.61386252.3152347 2.64082114.2972376c.7110335-.4971705 1.4917101-.3149497 1.80959713.1372281.19320342.2744561 2.19712724 3.2811005 2.42290565 3.6489167.09884826.1608492.14714912.3554431.14714912.5702838 0 .2744561-.07975258.5770327-.23701117.8751101-.1527655.2902036-.65262318 1.1664385-.89862055 1.594995.2673396.3768148.94804468 1.26429792 2.351016 2.66357424 1.39173858 1.39027775 2.28923588 2.07641807 2.67002628 2.34187563.4302146-.2452108 1.3086162-.74238132 1.5972981-.89423205.5447887-.28682915 1.0907006-.31944893 1.4568885-.08661115.3459689.2182151 3.3383754 2.21027167 3.6225641 2.41611376.2695862.19234426.4144887.5399137.4144887.91672846 0 .2969525-.089862.61190215-.2808189.88523346" /> <path fill="#3ba55c" fill-rule="evenodd" d="M17.7163041 15.36645368c-.0190957.02699568-1.9039523 2.6680735-2.9957762 2.63320406-3.0676659-.09785935-6.6733809-3.07188394-9.15694343-5.548738C3.08002193 9.9740657.09772497 6.3791404 0 3.3061316v-.024746C0 2.2060575 2.61386252.3152347 2.64082114.2972376c.7110335-.4971705 1.4917101-.3149497 1.80959713.1372281.19320342.2744561 2.19712724 3.2811005 2.42290565 3.6489167.09884826.1608492.14714912.3554431.14714912.5702838 0 .2744561-.07975258.5770327-.23701117.8751101-.1527655.2902036-.65262318 1.1664385-.89862055 1.594995.2673396.3768148.94804468 1.26429792 2.351016 2.66357424 1.39173858 1.39027775 2.28923588 2.07641807 2.67002628 2.34187563.4302146-.2452108 1.3086162-.74238132 1.5972981-.89423205.5447887-.28682915 1.0907006-.31944893 1.4568885-.08661115.3459689.2182151 3.3383754 2.21027167 3.6225641 2.41611376.2695862.19234426.4144887.5399137.4144887.91672846 0 .2969525-.089862.61190215-.2808189.88523346" />
</symbol> </symbol>
<symbol id="guild-member-join-icon" viewBox="0 0 18 18"> <symbol id="guild-member-join-icon" viewBox="0 0 18 18">
<g fill="none" fill-rule="evenodd"> <path fill="#3ba55c" d="m0 8h14.2l-3.6-3.6 1.4-1.4 6 6-6 6-1.4-1.4 3.6-3.6h-14.2" />
<path d="m18 0h-18v18h18z"/>
<path d="m0 8h14.2l-3.6-3.6 1.4-1.4 6 6-6 6-1.4-1.4 3.6-3.6h-14.2" fill="#3ba55c"/>
</g>
</symbol> </symbol>
</defs> </defs>
</svg> </svg>

@ -27,29 +27,43 @@ internal class HtmlMessageWriter : MessageWriter
private bool CanJoinGroup(Message message) private bool CanJoinGroup(Message message)
{ {
// First message in the group can always join // If the group is empty, any message can join it
if (_messageGroup.LastOrDefault() is not { } lastMessage) if (_messageGroup.LastOrDefault() is not { } lastMessage)
{
return true; return true;
}
// Group system messages with other system messages, regardless of author // Reply messages cannot join existing groups because they need to appear first
if (message.Kind.IsSystemMessage()) if (message.Kind == MessageKind.Reply)
return false;
// Grouping for system notifications
if (message.Kind.IsSystemNotification())
{ {
return lastMessage.Kind.IsSystemMessage(); // 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 from the same author
if (message.Author.Id != lastMessage.Author.Id)
return false;
// If the user changed their name after the last message, their new messages
// cannot join an existing group.
if (!string.Equals(message.Author.FullName, lastMessage.Author.FullName, StringComparison.Ordinal))
return false;
} }
return return true;
// Must be a non system message
!message.Kind.IsSystemMessage() &&
// Must be from the same author
lastMessage.Author.Id == message.Author.Id &&
// Author's name must not have changed between messages
string.Equals(lastMessage.Author.FullName, message.Author.FullName, StringComparison.Ordinal) &&
// Duration between messages must be 7 minutes or less
(message.Timestamp - lastMessage.Timestamp).Duration().TotalMinutes <= 7 &&
// Other message must not be a reply
message.Reference is null;
} }
// Use <!--wmm:ignore--> to preserve blocks of code inside the templates // Use <!--wmm:ignore--> to preserve blocks of code inside the templates

@ -1,5 +1,6 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.Text; using System.Text;
using System.Text.RegularExpressions;
namespace DiscordChatExporter.Core.Utils.Extensions; namespace DiscordChatExporter.Core.Utils.Extensions;
@ -25,6 +26,9 @@ public static class StringExtensions
} }
} }
public static string ToDashCase(this string str) =>
Regex.Replace(str, @"(\p{Ll})(\p{Lu})", "$1-$2");
public static StringBuilder AppendIfNotEmpty(this StringBuilder builder, char value) => public static StringBuilder AppendIfNotEmpty(this StringBuilder builder, char value) =>
builder.Length > 0 builder.Length > 0
? builder.Append(value) ? builder.Append(value)

Loading…
Cancel
Save