More refactoring

This commit is contained in:
Alexey Golub
2020-04-24 14:18:41 +03:00
parent 9d0d7cd5dd
commit d03be8b1dd
43 changed files with 617 additions and 655 deletions

View File

@@ -1,13 +1,14 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using DiscordChatExporter.Domain.Discord;
using DiscordChatExporter.Domain.Discord.Models;
using DiscordChatExporter.Domain.Discord.Models.Common;
using DiscordChatExporter.Domain.Exceptions;
using Tyrrrz.Extensions;
using DiscordChatExporter.Domain.Utilities;
namespace DiscordChatExporter.Domain.Exporting
{
@@ -36,37 +37,35 @@ namespace DiscordChatExporter.Domain.Exporting
var options = new ExportOptions(baseFilePath, format, partitionLimit);
// Context
var mentionableUsers = new HashSet<User>(IdBasedEqualityComparer.Instance);
var mentionableChannels = await _discord.GetGuildChannelsAsync(guild.Id);
var mentionableRoles = guild.Roles;
var contextMembers = new HashSet<Member>(IdBasedEqualityComparer.Instance);
var contextChannels = await _discord.GetGuildChannelsAsync(guild.Id);
var contextRoles = await _discord.GetGuildRolesAsync(guild.Id);
var context = new ExportContext(
guild, channel, after, before, dateFormat,
mentionableUsers, mentionableChannels, mentionableRoles
contextMembers, contextChannels, contextRoles
);
await using var messageExporter = new MessageExporter(options, context);
var exportedAnything = false;
var encounteredUsers = new HashSet<User>(IdBasedEqualityComparer.Instance);
await foreach (var message in _discord.GetMessagesAsync(channel.Id, after, before, progress))
{
// Add encountered users to the list of mentionable users
var encounteredUsers = new List<User>();
encounteredUsers.Add(message.Author);
encounteredUsers.AddRange(message.MentionedUsers);
mentionableUsers.AddRange(encounteredUsers);
foreach (User u in encounteredUsers)
// Resolve members for referenced users
foreach (var referencedUser in message.MentionedUsers.Prepend(message.Author))
{
if (!guild.Members.ContainsKey(u.Id))
if (encounteredUsers.Add(referencedUser))
{
var member = await _discord.GetGuildMemberAsync(guild.Id, u.Id);
guild.Members[u.Id] = member;
var member =
await _discord.TryGetGuildMemberAsync(guild.Id, referencedUser) ??
Member.CreateForUser(referencedUser);
contextMembers.Add(member);
}
}
// Render message
// Export message
await messageExporter.ExportMessageAsync(message);
exportedAnything = true;
}

View File

@@ -1,5 +1,7 @@
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Linq;
using DiscordChatExporter.Domain.Discord.Models;
namespace DiscordChatExporter.Domain.Exporting
@@ -16,11 +18,11 @@ namespace DiscordChatExporter.Domain.Exporting
public string DateFormat { get; }
public IReadOnlyCollection<User> MentionableUsers { get; }
public IReadOnlyCollection<Member> Members { get; }
public IReadOnlyCollection<Channel> MentionableChannels { get; }
public IReadOnlyCollection<Channel> Channels { get; }
public IReadOnlyCollection<Role> MentionableRoles { get; }
public IReadOnlyCollection<Role> Roles { get; }
public ExportContext(
Guild guild,
@@ -28,19 +30,41 @@ namespace DiscordChatExporter.Domain.Exporting
DateTimeOffset? after,
DateTimeOffset? before,
string dateFormat,
IReadOnlyCollection<User> mentionableUsers,
IReadOnlyCollection<Channel> mentionableChannels,
IReadOnlyCollection<Role> mentionableRoles)
IReadOnlyCollection<Member> members,
IReadOnlyCollection<Channel> channels,
IReadOnlyCollection<Role> roles)
{
Guild = guild;
Channel = channel;
After = after;
Before = before;
DateFormat = dateFormat;
MentionableUsers = mentionableUsers;
MentionableChannels = mentionableChannels;
MentionableRoles = mentionableRoles;
Members = members;
Channels = channels;
Roles = roles;
}
public Member? TryGetMentionedMember(string id) =>
Members.FirstOrDefault(m => m.Id == id);
public Channel? TryGetMentionedChannel(string id) =>
Channels.FirstOrDefault(c => c.Id == id);
public Role? TryGetMentionedRole(string id) =>
Roles.FirstOrDefault(r => r.Id == id);
public Member? TryGetUserMember(User user) => Members
.FirstOrDefault(m => m.Id == user.Id);
public Color? TryGetUserColor(User user)
{
var member = TryGetUserMember(user);
var roles = member?.RoleIds.Join(Roles, i => i, r => r.Id, (_, role) => role);
return roles?
.OrderByDescending(r => r.Position)
.Select(r => r.Color)
.FirstOrDefault(c => c != null);
}
}
}

View File

@@ -48,7 +48,7 @@ img {
}
.markdown {
display: inline-block;
max-width: 100%;
white-space: pre-wrap;
line-height: 1.3;
overflow-wrap: break-word;

View File

@@ -62,22 +62,22 @@
</div>
<div class="preamble__entries-container">
<div class="preamble__entry">{{ Context.Guild.Name | html.escape }}</div>
<div class="preamble__entry">{{ Context.Channel.Name | html.escape }}</div>
<div class="preamble__entry">{{ Context.Channel.Category | html.escape }} / {{ Context.Channel.Name | html.escape }}</div>
{{~ if Context.Channel.Topic ~}}
<div class="preamble__entry preamble__entry--small">{{ Context.Channel.Topic | html.escape }}</div>
<div class="preamble__entry preamble__entry--small">{{ Context.Channel.Topic | html.escape }}</div>
{{~ end ~}}
{{~ if Context.After || Context.Before ~}}
<div class="preamble__entry preamble__entry--small">
{{~ if Context.After && Context.Before ~}}
Between {{ Context.After | FormatDate | html.escape }} and {{ Context.Before | FormatDate | html.escape }}
{{~ else if Context.After ~}}
After {{ Context.After | FormatDate | html.escape }}
{{~ else if Context.Before ~}}
Before {{ Context.Before | FormatDate | html.escape }}
{{~ end ~}}
</div>
<div class="preamble__entry preamble__entry--small">
{{~ if Context.After && Context.Before ~}}
Between {{ Context.After | FormatDate | html.escape }} and {{ Context.Before | FormatDate | html.escape }}
{{~ else if Context.After ~}}
After {{ Context.After | FormatDate | html.escape }}
{{~ else if Context.Before ~}}
Before {{ Context.Before | FormatDate | html.escape }}
{{~ end ~}}
</div>
{{~ end ~}}
</div>
</div>

View File

@@ -5,27 +5,30 @@
</div>
<div class="chatlog__messages">
{{~ # Author name and timestamp ~}}
<span class="chatlog__author-name" title="{{ MessageGroup.Author.FullName | html.escape }}" data-user-id="{{ MessageGroup.Author.Id | html.escape }}" {{ if GetUserColor Context.Guild MessageGroup.Author }} style="color: {{ GetUserColor Context.Guild MessageGroup.Author }}" {{ end }}>{{ GetUserNick Context.Guild MessageGroup.Author | html.escape }}</span>
{{~ userColor = TryGetUserColor MessageGroup.Author | FormatColorRgb ~}}
<span class="chatlog__author-name" title="{{ MessageGroup.Author.FullName | html.escape }}" data-user-id="{{ MessageGroup.Author.Id | html.escape }}" {{ if userColor }} style="color: {{ userColor }}" {{ end }}>{{ (TryGetUserNick MessageGroup.Author ?? MessageGroup.Author.Name) | html.escape }}</span>
{{~ # Bot tag ~}}
{{~ if MessageGroup.Author.IsBot ~}}
<span class="chatlog__bot-tag">BOT</span>
<span class="chatlog__bot-tag">BOT</span>
{{~ end ~}}
<span class="chatlog__timestamp">{{ MessageGroup.Timestamp | FormatDate | html.escape }}</span>
{{~ # Messages ~}}
{{~ for message in MessageGroup.Messages ~}}
<div class="chatlog__message {{if message.IsPinned }}chatlog__message--pinned{{ end }}" data-message-id="{{ message.Id }}" id="message-{{ message.Id }}">
<div class="chatlog__message {{ if message.IsPinned }}chatlog__message--pinned{{ end }}" data-message-id="{{ message.Id }}" id="message-{{ message.Id }}">
{{~ # Content ~}}
{{~ if message.Content ~}}
<div class="chatlog__content">
<div class="markdown">{{ message.Content | FormatMarkdown }}</div>
<div class="markdown">
{{- message.Content | FormatMarkdown -}}
{{~ # Edited timestamp ~}}
{{~ if message.EditedTimestamp ~}}
<span class="chatlog__edited-timestamp" title="{{ message.EditedTimestamp | FormatDate | html.escape }}">(edited)</span>
{{~ end ~}}
{{- # Edited timestamp -}}
{{- if message.EditedTimestamp -}}
{{-}}<span class="chatlog__edited-timestamp" title="{{ message.EditedTimestamp | FormatDate | html.escape }}">(edited)</span>{{-}}
{{- end -}}
</div>
</div>
{{~ end ~}}
@@ -89,16 +92,16 @@
{{~ if embed.Title ~}}
<div class="chatlog__embed-title">
{{~ if embed.Url ~}}
<a class="chatlog__embed-title-link" href="{{ embed.Url }}"><span class="markdown">{{ embed.Title | FormatMarkdown }}</span></a>
<a class="chatlog__embed-title-link" href="{{ embed.Url }}"><div class="markdown">{{ embed.Title | FormatEmbedMarkdown }}</div></a>
{{~ else ~}}
<span class="markdown">{{ embed.Title | FormatMarkdown }}</span>
<div class="markdown">{{ embed.Title | FormatEmbedMarkdown }}</div>
{{~ end ~}}
</div>
{{~ end ~}}
{{~ # Description ~}}
{{~ if embed.Description ~}}
<div class="chatlog__embed-description"><span class="markdown">{{ embed.Description | FormatMarkdown }}</span></div>
<div class="chatlog__embed-description"><div class="markdown">{{ embed.Description | FormatEmbedMarkdown }}</div></div>
{{~ end ~}}
{{~ # Fields ~}}
@@ -107,10 +110,10 @@
{{~ for field in embed.Fields ~}}
<div class="chatlog__embed-field {{ if field.IsInline }} chatlog__embed-field--inline {{ end }}">
{{~ if field.Name ~}}
<div class="chatlog__embed-field-name"><span class="markdown">{{ field.Name | FormatMarkdown }}</span></div>
<div class="chatlog__embed-field-name"><div class="markdown">{{ field.Name | FormatEmbedMarkdown }}</div></div>
{{~ end ~}}
{{~ if field.Value ~}}
<div class="chatlog__embed-field-value"><span class="markdown">{{ field.Value | FormatMarkdown }}</span></div>
<div class="chatlog__embed-field-value"><div class="markdown">{{ field.Value | FormatEmbedMarkdown }}</div></div>
{{~ end ~}}
</div>
{{~ end ~}}

View File

@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.Drawing;
using System.IO;
using System.Linq;
using System.Reflection;
@@ -73,12 +74,20 @@ namespace DiscordChatExporter.Domain.Exporting.Writers
scriptObject.Import("FormatDate",
new Func<DateTimeOffset, string>(d => d.ToLocalString(Context.DateFormat)));
scriptObject.Import("FormatColorRgb",
new Func<Color?, string?>(c => c != null ? $"rgb({c?.R}, {c?.G}, {c?.B})" : null));
scriptObject.Import("TryGetUserColor",
new Func<User, Color?>(Context.TryGetUserColor));
scriptObject.Import("TryGetUserNick",
new Func<User, string?>(u => Context.TryGetUserMember(u)?.Nick));
scriptObject.Import("FormatMarkdown",
new Func<string?, string>(FormatMarkdown));
new Func<string?, string>(m => FormatMarkdown(m)));
scriptObject.Import("GetUserColor", new Func<Guild, User, string>(Guild.GetUserColor));
scriptObject.Import("GetUserNick", new Func<Guild, User, string>(Guild.GetUserNick));
scriptObject.Import("FormatEmbedMarkdown",
new Func<string?, string>(m => FormatMarkdown(m, false)));
// Push model
templateContext.PushGlobal(scriptObject);
@@ -89,8 +98,8 @@ namespace DiscordChatExporter.Domain.Exporting.Writers
return templateContext;
}
private string FormatMarkdown(string? markdown) =>
HtmlMarkdownVisitor.Format(Context, markdown ?? "");
private string FormatMarkdown(string? markdown, bool isJumboAllowed = true) =>
HtmlMarkdownVisitor.Format(Context, markdown ?? "", isJumboAllowed);
private async Task RenderCurrentMessageGroupAsync()
{

View File

@@ -84,8 +84,8 @@ namespace DiscordChatExporter.Domain.Exporting.Writers
{
_writer.WriteStartObject();
_writer.WriteString("name", embedField.Name);
_writer.WriteString("value", embedField.Value);
_writer.WriteString("name", FormatMarkdown(embedField.Name));
_writer.WriteString("value", FormatMarkdown(embedField.Value));
_writer.WriteBoolean("isInline", embedField.IsInline);
_writer.WriteEndObject();
@@ -156,6 +156,7 @@ namespace DiscordChatExporter.Domain.Exporting.Writers
_writer.WriteStartObject("channel");
_writer.WriteString("id", Context.Channel.Id);
_writer.WriteString("type", Context.Channel.Type.ToString());
_writer.WriteString("category", Context.Channel.Category);
_writer.WriteString("name", Context.Channel.Name);
_writer.WriteString("topic", Context.Channel.Topic);
_writer.WriteEndObject();

View File

@@ -85,38 +85,38 @@ namespace DiscordChatExporter.Domain.Exporting.Writers.MarkdownVisitors
}
else if (mention.Type == MentionType.User)
{
var user = _context.MentionableUsers.FirstOrDefault(u => u.Id == mention.Id) ??
User.CreateUnknownUser(mention.Id);
var nick = Guild.GetUserNick(_context.Guild, user);
var member = _context.TryGetMentionedMember(mention.Id);
var fullName = member?.User.FullName ?? "Unknown";
var nick = member?.Nick ?? "Unknown";
_buffer
.Append($"<span class=\"mention\" title=\"{HtmlEncode(user.FullName)}\">")
.Append($"<span class=\"mention\" title=\"{HtmlEncode(fullName)}\">")
.Append("@").Append(HtmlEncode(nick))
.Append("</span>");
}
else if (mention.Type == MentionType.Channel)
{
var channel = _context.MentionableChannels.FirstOrDefault(c => c.Id == mention.Id) ??
Channel.CreateDeletedChannel(mention.Id);
var channel = _context.TryGetMentionedChannel(mention.Id);
var name = channel?.Name ?? "deleted-channel";
_buffer
.Append("<span class=\"mention\">")
.Append("#").Append(HtmlEncode(channel.Name))
.Append("#").Append(HtmlEncode(name))
.Append("</span>");
}
else if (mention.Type == MentionType.Role)
{
var role = _context.MentionableRoles.FirstOrDefault(r => r.Id == mention.Id) ??
Role.CreateDeletedRole(mention.Id);
var role = _context.TryGetMentionedRole(mention.Id);
var name = role?.Name ?? "deleted-role";
var color = role?.Color;
var style = role.Color != null
? $"color: {role.Color.Value.ToHexString()}; background-color: rgba({role.Color.Value.ToRgbString()}, 0.1);"
var style = color != null
? $"color: rgb({color?.R}, {color?.G}, {color?.B}); background-color: rgba({color?.R}, {color?.G}, {color?.B}, 0.1);"
: "";
_buffer
.Append($"<span class=\"mention\" style=\"{style}\">")
.Append("@").Append(HtmlEncode(role.Name))
.Append("@").Append(HtmlEncode(name))
.Append("</span>");
}
@@ -162,10 +162,13 @@ namespace DiscordChatExporter.Domain.Exporting.Writers.MarkdownVisitors
{
private static string HtmlEncode(string text) => WebUtility.HtmlEncode(text);
public static string Format(ExportContext context, string markdown)
public static string Format(ExportContext context, string markdown, bool isJumboAllowed = true)
{
var nodes = MarkdownParser.Parse(markdown);
var isJumbo = nodes.All(n => n is EmojiNode || n is TextNode textNode && string.IsNullOrWhiteSpace(textNode.Text));
var isJumbo =
isJumboAllowed &&
nodes.All(n => n is EmojiNode || n is TextNode textNode && string.IsNullOrWhiteSpace(textNode.Text));
var buffer = new StringBuilder();

View File

@@ -1,6 +1,4 @@
using System.Linq;
using System.Text;
using DiscordChatExporter.Domain.Discord.Models;
using System.Text;
using DiscordChatExporter.Domain.Markdown;
using DiscordChatExporter.Domain.Markdown.Ast;
@@ -27,24 +25,24 @@ namespace DiscordChatExporter.Domain.Exporting.Writers.MarkdownVisitors
{
if (mention.Type == MentionType.User)
{
var user = _context.MentionableUsers.FirstOrDefault(u => u.Id == mention.Id) ??
User.CreateUnknownUser(mention.Id);
var member = _context.TryGetMentionedMember(mention.Id);
var name = member?.User.Name ?? "Unknown";
_buffer.Append($"@{user.Name}");
_buffer.Append($"@{name}");
}
else if (mention.Type == MentionType.Channel)
{
var channel = _context.MentionableChannels.FirstOrDefault(c => c.Id == mention.Id) ??
Channel.CreateDeletedChannel(mention.Id);
var channel = _context.TryGetMentionedChannel(mention.Id);
var name = channel?.Name ?? "deleted-channel";
_buffer.Append($"#{channel.Name}");
_buffer.Append($"#{name}");
}
else if (mention.Type == MentionType.Role)
{
var role = _context.MentionableRoles.FirstOrDefault(r => r.Id == mention.Id) ??
Role.CreateDeletedRole(mention.Id);
var role = _context.TryGetMentionedRole(mention.Id);
var name = role?.Name ?? "deleted-role";
_buffer.Append($"@{role.Name}");
_buffer.Append($"@{name}");
}
return base.VisitMention(mention);

View File

@@ -76,8 +76,8 @@ namespace DiscordChatExporter.Domain.Exporting.Writers
foreach (var field in embed.Fields)
{
buffer
.AppendLineIfNotNullOrWhiteSpace(field.Name)
.AppendLineIfNotNullOrWhiteSpace(field.Value);
.AppendLineIfNotNullOrWhiteSpace(FormatMarkdown(field.Name))
.AppendLineIfNotNullOrWhiteSpace(FormatMarkdown(field.Value));
}
buffer
@@ -135,7 +135,7 @@ namespace DiscordChatExporter.Domain.Exporting.Writers
buffer.Append('=', 62).AppendLine();
buffer.AppendLine($"Guild: {Context.Guild.Name}");
buffer.AppendLine($"Channel: {Context.Channel.Name}");
buffer.AppendLine($"Channel: {Context.Channel.Category} / {Context.Channel.Name}");
if (!string.IsNullOrWhiteSpace(Context.Channel.Topic))
buffer.AppendLine($"Topic: {Context.Channel.Topic}");