mirror of
https://github.com/Tyrrrz/DiscordChatExporter.git
synced 2025-05-25 04:04:22 -04:00
Refactor
This commit is contained in:
parent
6a430cdc76
commit
dc79813ad7
13 changed files with 227 additions and 263 deletions
|
@ -3,7 +3,7 @@ using System.Threading.Tasks;
|
||||||
using CliFx;
|
using CliFx;
|
||||||
using CliFx.Attributes;
|
using CliFx.Attributes;
|
||||||
using DiscordChatExporter.Cli.Commands.Base;
|
using DiscordChatExporter.Cli.Commands.Base;
|
||||||
using DiscordChatExporter.Domain.Discord;
|
using DiscordChatExporter.Domain.Utilities;
|
||||||
|
|
||||||
namespace DiscordChatExporter.Cli.Commands
|
namespace DiscordChatExporter.Cli.Commands
|
||||||
{
|
{
|
||||||
|
|
|
@ -1,22 +0,0 @@
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Runtime.CompilerServices;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace DiscordChatExporter.Domain.Discord
|
|
||||||
{
|
|
||||||
public static class AccessibilityExtensions
|
|
||||||
{
|
|
||||||
private static async ValueTask<IReadOnlyList<T>> AggregateAsync<T>(this IAsyncEnumerable<T> asyncEnumerable)
|
|
||||||
{
|
|
||||||
var list = new List<T>();
|
|
||||||
|
|
||||||
await foreach (var i in asyncEnumerable)
|
|
||||||
list.Add(i);
|
|
||||||
|
|
||||||
return list;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static ValueTaskAwaiter<IReadOnlyList<T>> GetAwaiter<T>(this IAsyncEnumerable<T> asyncEnumerable) =>
|
|
||||||
asyncEnumerable.AggregateAsync().GetAwaiter();
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -2,11 +2,7 @@
|
||||||
|
|
||||||
namespace DiscordChatExporter.Domain.Discord
|
namespace DiscordChatExporter.Domain.Discord
|
||||||
{
|
{
|
||||||
public enum AuthTokenType
|
public enum AuthTokenType { User, Bot }
|
||||||
{
|
|
||||||
User,
|
|
||||||
Bot
|
|
||||||
}
|
|
||||||
|
|
||||||
public class AuthToken
|
public class AuthToken
|
||||||
{
|
{
|
||||||
|
|
|
@ -23,7 +23,9 @@ namespace DiscordChatExporter.Domain.Discord
|
||||||
_token = token;
|
_token = token;
|
||||||
_httpClient = httpClient;
|
_httpClient = httpClient;
|
||||||
|
|
||||||
// Discord seems to always respond 429 on our first request with unreasonable wait time (10+ minutes).
|
_httpClient.BaseAddress = new Uri("https://discordapp.com/api/v6");
|
||||||
|
|
||||||
|
// Discord seems to always respond with 429 on the first request with unreasonable wait time (10+ minutes).
|
||||||
// For that reason the policy will start respecting their retry-after header only after Nth failed response.
|
// For that reason the policy will start respecting their retry-after header only after Nth failed response.
|
||||||
_httpRequestPolicy = Policy
|
_httpRequestPolicy = Policy
|
||||||
.HandleResult<HttpResponseMessage>(m => m.StatusCode == HttpStatusCode.TooManyRequests)
|
.HandleResult<HttpResponseMessage>(m => m.StatusCode == HttpStatusCode.TooManyRequests)
|
||||||
|
@ -240,10 +242,7 @@ namespace DiscordChatExporter.Domain.Discord
|
||||||
|
|
||||||
handler.UseCookies = false;
|
handler.UseCookies = false;
|
||||||
|
|
||||||
return new HttpClient(handler, true)
|
return new HttpClient(handler, true);
|
||||||
{
|
|
||||||
BaseAddress = new Uri("https://discordapp.com/api/v6")
|
|
||||||
};
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -31,8 +31,7 @@ namespace DiscordChatExporter.Domain.Exporting
|
||||||
var mentionableChannels = await _discord.GetGuildChannelsAsync(guild.Id);
|
var mentionableChannels = await _discord.GetGuildChannelsAsync(guild.Id);
|
||||||
var mentionableRoles = guild.Roles;
|
var mentionableRoles = guild.Roles;
|
||||||
|
|
||||||
var context = new RenderContext
|
var context = new RenderContext(
|
||||||
(
|
|
||||||
guild, channel, after, before, dateFormat,
|
guild, channel, after, before, dateFormat,
|
||||||
mentionableUsers, mentionableChannels, mentionableRoles
|
mentionableUsers, mentionableChannels, mentionableRoles
|
||||||
);
|
);
|
||||||
|
|
|
@ -9,7 +9,7 @@ using Tyrrrz.Extensions;
|
||||||
|
|
||||||
namespace DiscordChatExporter.Domain.Exporting.Writers
|
namespace DiscordChatExporter.Domain.Exporting.Writers
|
||||||
{
|
{
|
||||||
internal class CsvMessageWriter : MessageWriterBase
|
internal partial class CsvMessageWriter : MessageWriterBase
|
||||||
{
|
{
|
||||||
private readonly TextWriter _writer;
|
private readonly TextWriter _writer;
|
||||||
|
|
||||||
|
@ -19,35 +19,26 @@ namespace DiscordChatExporter.Domain.Exporting.Writers
|
||||||
_writer = new StreamWriter(stream);
|
_writer = new StreamWriter(stream);
|
||||||
}
|
}
|
||||||
|
|
||||||
private string EncodeValue(string value)
|
private string FormatMarkdown(string? markdown) =>
|
||||||
{
|
PlainTextMarkdownVisitor.Format(Context, markdown ?? "");
|
||||||
value = value.Replace("\"", "\"\"");
|
|
||||||
return $"\"{value}\"";
|
|
||||||
}
|
|
||||||
|
|
||||||
private string FormatMarkdown(string markdown) =>
|
|
||||||
PlainTextMarkdownVisitor.Format(Context, markdown);
|
|
||||||
|
|
||||||
private string FormatMessage(Message message)
|
|
||||||
{
|
|
||||||
var buffer = new StringBuilder();
|
|
||||||
|
|
||||||
buffer
|
|
||||||
.Append(EncodeValue(message.Author.Id)).Append(',')
|
|
||||||
.Append(EncodeValue(message.Author.FullName)).Append(',')
|
|
||||||
.Append(EncodeValue(message.Timestamp.ToLocalString(Context.DateFormat))).Append(',')
|
|
||||||
.Append(EncodeValue(FormatMarkdown(message.Content))).Append(',')
|
|
||||||
.Append(EncodeValue(message.Attachments.Select(a => a.Url).JoinToString(","))).Append(',')
|
|
||||||
.Append(EncodeValue(message.Reactions.Select(r => $"{r.Emoji.Name} ({r.Count})").JoinToString(",")));
|
|
||||||
|
|
||||||
return buffer.ToString();
|
|
||||||
}
|
|
||||||
|
|
||||||
public override async Task WritePreambleAsync() =>
|
public override async Task WritePreambleAsync() =>
|
||||||
await _writer.WriteLineAsync("AuthorID,Author,Date,Content,Attachments,Reactions");
|
await _writer.WriteLineAsync("AuthorID,Author,Date,Content,Attachments,Reactions");
|
||||||
|
|
||||||
public override async Task WriteMessageAsync(Message message) =>
|
public override async Task WriteMessageAsync(Message message)
|
||||||
await _writer.WriteLineAsync(FormatMessage(message));
|
{
|
||||||
|
var buffer = new StringBuilder();
|
||||||
|
|
||||||
|
buffer
|
||||||
|
.Append(CsvEncode(message.Author.Id)).Append(',')
|
||||||
|
.Append(CsvEncode(message.Author.FullName)).Append(',')
|
||||||
|
.Append(CsvEncode(message.Timestamp.ToLocalString(Context.DateFormat))).Append(',')
|
||||||
|
.Append(CsvEncode(FormatMarkdown(message.Content))).Append(',')
|
||||||
|
.Append(CsvEncode(message.Attachments.Select(a => a.Url).JoinToString(","))).Append(',')
|
||||||
|
.Append(CsvEncode(message.Reactions.Select(r => $"{r.Emoji.Name} ({r.Count})").JoinToString(",")));
|
||||||
|
|
||||||
|
await _writer.WriteLineAsync(buffer.ToString());
|
||||||
|
}
|
||||||
|
|
||||||
public override async ValueTask DisposeAsync()
|
public override async ValueTask DisposeAsync()
|
||||||
{
|
{
|
||||||
|
@ -55,4 +46,13 @@ namespace DiscordChatExporter.Domain.Exporting.Writers
|
||||||
await base.DisposeAsync();
|
await base.DisposeAsync();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
internal partial class CsvMessageWriter
|
||||||
|
{
|
||||||
|
private static string CsvEncode(string value)
|
||||||
|
{
|
||||||
|
value = value.Replace("\"", "\"\"");
|
||||||
|
return $"\"{value}\"";
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
|
@ -1,17 +1,12 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Drawing;
|
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Net;
|
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
using System.Text.RegularExpressions;
|
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using DiscordChatExporter.Domain.Discord.Models;
|
using DiscordChatExporter.Domain.Discord.Models;
|
||||||
using DiscordChatExporter.Domain.Exporting.Writers.MarkdownVisitors;
|
using DiscordChatExporter.Domain.Exporting.Writers.MarkdownVisitors;
|
||||||
using DiscordChatExporter.Domain.Internal;
|
using DiscordChatExporter.Domain.Internal;
|
||||||
using DiscordChatExporter.Domain.Markdown;
|
|
||||||
using DiscordChatExporter.Domain.Markdown.Ast;
|
|
||||||
using Scriban;
|
using Scriban;
|
||||||
using Scriban.Runtime;
|
using Scriban.Runtime;
|
||||||
using Tyrrrz.Extensions;
|
using Tyrrrz.Extensions;
|
||||||
|
@ -79,7 +74,7 @@ namespace DiscordChatExporter.Domain.Exporting.Writers
|
||||||
new Func<DateTimeOffset, string>(d => d.ToLocalString(Context.DateFormat)));
|
new Func<DateTimeOffset, string>(d => d.ToLocalString(Context.DateFormat)));
|
||||||
|
|
||||||
scriptObject.Import("FormatMarkdown",
|
scriptObject.Import("FormatMarkdown",
|
||||||
new Func<string, string>(FormatMarkdown));
|
new Func<string?, string>(FormatMarkdown));
|
||||||
|
|
||||||
scriptObject.Import("GetUserColor", new Func<Guild, User, string>(Guild.GetUserColor));
|
scriptObject.Import("GetUserColor", new Func<Guild, User, string>(Guild.GetUserColor));
|
||||||
|
|
||||||
|
@ -94,8 +89,8 @@ namespace DiscordChatExporter.Domain.Exporting.Writers
|
||||||
return templateContext;
|
return templateContext;
|
||||||
}
|
}
|
||||||
|
|
||||||
private string FormatMarkdown(string markdown) =>
|
private string FormatMarkdown(string? markdown) =>
|
||||||
HtmlMarkdownVisitor.Format(Context, markdown);
|
HtmlMarkdownVisitor.Format(Context, markdown ?? "");
|
||||||
|
|
||||||
private async Task RenderCurrentMessageGroupAsync()
|
private async Task RenderCurrentMessageGroupAsync()
|
||||||
{
|
{
|
||||||
|
@ -180,7 +175,5 @@ namespace DiscordChatExporter.Domain.Exporting.Writers
|
||||||
ResourcesAssembly
|
ResourcesAssembly
|
||||||
.GetManifestResourceString($"{ResourcesNamespace}.HtmlLayoutTemplate.html")
|
.GetManifestResourceString($"{ResourcesNamespace}.HtmlLayoutTemplate.html")
|
||||||
.SubstringAfter("{{~ %SPLIT% ~}}");
|
.SubstringAfter("{{~ %SPLIT% ~}}");
|
||||||
|
|
||||||
private static string HtmlEncode(string s) => WebUtility.HtmlEncode(s);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -22,6 +22,124 @@ namespace DiscordChatExporter.Domain.Exporting.Writers
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private string FormatMarkdown(string? markdown) =>
|
||||||
|
PlainTextMarkdownVisitor.Format(Context, markdown ?? "");
|
||||||
|
|
||||||
|
private void WriteAttachment(Attachment attachment)
|
||||||
|
{
|
||||||
|
_writer.WriteStartObject();
|
||||||
|
|
||||||
|
_writer.WriteString("id", attachment.Id);
|
||||||
|
_writer.WriteString("url", attachment.Url);
|
||||||
|
_writer.WriteString("fileName", attachment.FileName);
|
||||||
|
_writer.WriteNumber("fileSizeBytes", attachment.FileSize.TotalBytes);
|
||||||
|
|
||||||
|
_writer.WriteEndObject();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void WriteEmbedAuthor(EmbedAuthor embedAuthor)
|
||||||
|
{
|
||||||
|
_writer.WriteStartObject("author");
|
||||||
|
|
||||||
|
_writer.WriteString("name", embedAuthor.Name);
|
||||||
|
_writer.WriteString("url", embedAuthor.Url);
|
||||||
|
_writer.WriteString("iconUrl", embedAuthor.IconUrl);
|
||||||
|
|
||||||
|
_writer.WriteEndObject();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void WriteEmbedThumbnail(EmbedImage embedThumbnail)
|
||||||
|
{
|
||||||
|
_writer.WriteStartObject("thumbnail");
|
||||||
|
|
||||||
|
_writer.WriteString("url", embedThumbnail.Url);
|
||||||
|
_writer.WriteNumber("width", embedThumbnail.Width);
|
||||||
|
_writer.WriteNumber("height", embedThumbnail.Height);
|
||||||
|
|
||||||
|
_writer.WriteEndObject();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void WriteEmbedImage(EmbedImage embedImage)
|
||||||
|
{
|
||||||
|
_writer.WriteStartObject("image");
|
||||||
|
|
||||||
|
_writer.WriteString("url", embedImage.Url);
|
||||||
|
_writer.WriteNumber("width", embedImage.Width);
|
||||||
|
_writer.WriteNumber("height", embedImage.Height);
|
||||||
|
|
||||||
|
_writer.WriteEndObject();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void WriteEmbedFooter(EmbedFooter embedFooter)
|
||||||
|
{
|
||||||
|
_writer.WriteStartObject("footer");
|
||||||
|
|
||||||
|
_writer.WriteString("text", embedFooter.Text);
|
||||||
|
_writer.WriteString("iconUrl", embedFooter.IconUrl);
|
||||||
|
|
||||||
|
_writer.WriteEndObject();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void WriteEmbedField(EmbedField embedField)
|
||||||
|
{
|
||||||
|
_writer.WriteStartObject();
|
||||||
|
|
||||||
|
_writer.WriteString("name", embedField.Name);
|
||||||
|
_writer.WriteString("value", embedField.Value);
|
||||||
|
_writer.WriteBoolean("isInline", embedField.IsInline);
|
||||||
|
|
||||||
|
_writer.WriteEndObject();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void WriteEmbed(Embed embed)
|
||||||
|
{
|
||||||
|
_writer.WriteStartObject();
|
||||||
|
|
||||||
|
_writer.WriteString("title", FormatMarkdown(embed.Title));
|
||||||
|
_writer.WriteString("url", embed.Url);
|
||||||
|
_writer.WriteString("timestamp", embed.Timestamp);
|
||||||
|
_writer.WriteString("description", FormatMarkdown(embed.Description));
|
||||||
|
|
||||||
|
if (embed.Author != null)
|
||||||
|
WriteEmbedAuthor(embed.Author);
|
||||||
|
|
||||||
|
if (embed.Thumbnail != null)
|
||||||
|
WriteEmbedThumbnail(embed.Thumbnail);
|
||||||
|
|
||||||
|
if (embed.Image != null)
|
||||||
|
WriteEmbedImage(embed.Image);
|
||||||
|
|
||||||
|
if (embed.Footer != null)
|
||||||
|
WriteEmbedFooter(embed.Footer);
|
||||||
|
|
||||||
|
// Fields
|
||||||
|
_writer.WriteStartArray("fields");
|
||||||
|
|
||||||
|
foreach (var field in embed.Fields)
|
||||||
|
WriteEmbedField(field);
|
||||||
|
|
||||||
|
_writer.WriteEndArray();
|
||||||
|
|
||||||
|
_writer.WriteEndObject();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void WriteReaction(Reaction reaction)
|
||||||
|
{
|
||||||
|
_writer.WriteStartObject();
|
||||||
|
|
||||||
|
// Emoji
|
||||||
|
_writer.WriteStartObject("emoji");
|
||||||
|
_writer.WriteString("id", reaction.Emoji.Id);
|
||||||
|
_writer.WriteString("name", reaction.Emoji.Name);
|
||||||
|
_writer.WriteBoolean("isAnimated", reaction.Emoji.IsAnimated);
|
||||||
|
_writer.WriteString("imageUrl", reaction.Emoji.ImageUrl);
|
||||||
|
_writer.WriteEndObject();
|
||||||
|
|
||||||
|
_writer.WriteNumber("count", reaction.Count);
|
||||||
|
|
||||||
|
_writer.WriteEndObject();
|
||||||
|
}
|
||||||
|
|
||||||
public override async Task WritePreambleAsync()
|
public override async Task WritePreambleAsync()
|
||||||
{
|
{
|
||||||
// Root object (start)
|
// Root object (start)
|
||||||
|
@ -66,8 +184,7 @@ namespace DiscordChatExporter.Domain.Exporting.Writers
|
||||||
_writer.WriteBoolean("isPinned", message.IsPinned);
|
_writer.WriteBoolean("isPinned", message.IsPinned);
|
||||||
|
|
||||||
// Content
|
// Content
|
||||||
var content = PlainTextMarkdownVisitor.Format(Context, message.Content);
|
_writer.WriteString("content", FormatMarkdown(message.Content));
|
||||||
_writer.WriteString("content", content);
|
|
||||||
|
|
||||||
// Author
|
// Author
|
||||||
_writer.WriteStartObject("author");
|
_writer.WriteStartObject("author");
|
||||||
|
@ -82,16 +199,7 @@ namespace DiscordChatExporter.Domain.Exporting.Writers
|
||||||
_writer.WriteStartArray("attachments");
|
_writer.WriteStartArray("attachments");
|
||||||
|
|
||||||
foreach (var attachment in message.Attachments)
|
foreach (var attachment in message.Attachments)
|
||||||
{
|
WriteAttachment(attachment);
|
||||||
_writer.WriteStartObject();
|
|
||||||
|
|
||||||
_writer.WriteString("id", attachment.Id);
|
|
||||||
_writer.WriteString("url", attachment.Url);
|
|
||||||
_writer.WriteString("fileName", attachment.FileName);
|
|
||||||
_writer.WriteNumber("fileSizeBytes", attachment.FileSize.TotalBytes);
|
|
||||||
|
|
||||||
_writer.WriteEndObject();
|
|
||||||
}
|
|
||||||
|
|
||||||
_writer.WriteEndArray();
|
_writer.WriteEndArray();
|
||||||
|
|
||||||
|
@ -99,71 +207,7 @@ namespace DiscordChatExporter.Domain.Exporting.Writers
|
||||||
_writer.WriteStartArray("embeds");
|
_writer.WriteStartArray("embeds");
|
||||||
|
|
||||||
foreach (var embed in message.Embeds)
|
foreach (var embed in message.Embeds)
|
||||||
{
|
WriteEmbed(embed);
|
||||||
_writer.WriteStartObject();
|
|
||||||
|
|
||||||
_writer.WriteString("title", embed.Title);
|
|
||||||
_writer.WriteString("url", embed.Url);
|
|
||||||
_writer.WriteString("timestamp", embed.Timestamp);
|
|
||||||
_writer.WriteString("description", embed.Description);
|
|
||||||
|
|
||||||
// Author
|
|
||||||
if (embed.Author != null)
|
|
||||||
{
|
|
||||||
_writer.WriteStartObject("author");
|
|
||||||
_writer.WriteString("name", embed.Author.Name);
|
|
||||||
_writer.WriteString("url", embed.Author.Url);
|
|
||||||
_writer.WriteString("iconUrl", embed.Author.IconUrl);
|
|
||||||
_writer.WriteEndObject();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Thumbnail
|
|
||||||
if (embed.Thumbnail != null)
|
|
||||||
{
|
|
||||||
_writer.WriteStartObject("thumbnail");
|
|
||||||
_writer.WriteString("url", embed.Thumbnail.Url);
|
|
||||||
_writer.WriteNumber("width", embed.Thumbnail.Width);
|
|
||||||
_writer.WriteNumber("height", embed.Thumbnail.Height);
|
|
||||||
_writer.WriteEndObject();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Image
|
|
||||||
if (embed.Image != null)
|
|
||||||
{
|
|
||||||
_writer.WriteStartObject("image");
|
|
||||||
_writer.WriteString("url", embed.Image.Url);
|
|
||||||
_writer.WriteNumber("width", embed.Image.Width);
|
|
||||||
_writer.WriteNumber("height", embed.Image.Height);
|
|
||||||
_writer.WriteEndObject();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Footer
|
|
||||||
if (embed.Footer != null)
|
|
||||||
{
|
|
||||||
_writer.WriteStartObject("footer");
|
|
||||||
_writer.WriteString("text", embed.Footer.Text);
|
|
||||||
_writer.WriteString("iconUrl", embed.Footer.IconUrl);
|
|
||||||
_writer.WriteEndObject();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Fields
|
|
||||||
_writer.WriteStartArray("fields");
|
|
||||||
|
|
||||||
foreach (var field in embed.Fields)
|
|
||||||
{
|
|
||||||
_writer.WriteStartObject();
|
|
||||||
|
|
||||||
_writer.WriteString("name", field.Name);
|
|
||||||
_writer.WriteString("value", field.Value);
|
|
||||||
_writer.WriteBoolean("isInline", field.IsInline);
|
|
||||||
|
|
||||||
_writer.WriteEndObject();
|
|
||||||
}
|
|
||||||
|
|
||||||
_writer.WriteEndArray();
|
|
||||||
|
|
||||||
_writer.WriteEndObject();
|
|
||||||
}
|
|
||||||
|
|
||||||
_writer.WriteEndArray();
|
_writer.WriteEndArray();
|
||||||
|
|
||||||
|
@ -171,31 +215,14 @@ namespace DiscordChatExporter.Domain.Exporting.Writers
|
||||||
_writer.WriteStartArray("reactions");
|
_writer.WriteStartArray("reactions");
|
||||||
|
|
||||||
foreach (var reaction in message.Reactions)
|
foreach (var reaction in message.Reactions)
|
||||||
{
|
WriteReaction(reaction);
|
||||||
_writer.WriteStartObject();
|
|
||||||
|
|
||||||
// Emoji
|
|
||||||
_writer.WriteStartObject("emoji");
|
|
||||||
_writer.WriteString("id", reaction.Emoji.Id);
|
|
||||||
_writer.WriteString("name", reaction.Emoji.Name);
|
|
||||||
_writer.WriteBoolean("isAnimated", reaction.Emoji.IsAnimated);
|
|
||||||
_writer.WriteString("imageUrl", reaction.Emoji.ImageUrl);
|
|
||||||
_writer.WriteEndObject();
|
|
||||||
|
|
||||||
// Count
|
|
||||||
_writer.WriteNumber("count", reaction.Count);
|
|
||||||
|
|
||||||
_writer.WriteEndObject();
|
|
||||||
}
|
|
||||||
|
|
||||||
_writer.WriteEndArray();
|
_writer.WriteEndArray();
|
||||||
|
|
||||||
_writer.WriteEndObject();
|
_writer.WriteEndObject();
|
||||||
|
|
||||||
_messageCount++;
|
|
||||||
|
|
||||||
// Flush every 100 messages
|
// Flush every 100 messages
|
||||||
if (_messageCount % 100 == 0)
|
if (_messageCount++ % 100 == 0)
|
||||||
await _writer.FlushAsync();
|
await _writer.FlushAsync();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -204,7 +231,6 @@ namespace DiscordChatExporter.Domain.Exporting.Writers
|
||||||
// Message array (end)
|
// Message array (end)
|
||||||
_writer.WriteEndArray();
|
_writer.WriteEndArray();
|
||||||
|
|
||||||
// Message count
|
|
||||||
_writer.WriteNumber("messageCount", _messageCount);
|
_writer.WriteNumber("messageCount", _messageCount);
|
||||||
|
|
||||||
// Root object (end)
|
// Root object (end)
|
||||||
|
|
|
@ -115,7 +115,7 @@ namespace DiscordChatExporter.Domain.Exporting.Writers.MarkdownVisitors
|
||||||
: "";
|
: "";
|
||||||
|
|
||||||
_buffer
|
_buffer
|
||||||
.Append($"<span class=\"mention\" style=\"{style}>\"")
|
.Append($"<span class=\"mention\" style=\"{style}\">")
|
||||||
.Append("@").Append(HtmlEncode(role.Name))
|
.Append("@").Append(HtmlEncode(role.Name))
|
||||||
.Append("</span>");
|
.Append("</span>");
|
||||||
}
|
}
|
||||||
|
|
|
@ -52,9 +52,11 @@ namespace DiscordChatExporter.Domain.Exporting.Writers.MarkdownVisitors
|
||||||
|
|
||||||
public override MarkdownNode VisitEmoji(EmojiNode emoji)
|
public override MarkdownNode VisitEmoji(EmojiNode emoji)
|
||||||
{
|
{
|
||||||
_buffer.Append(emoji.IsCustomEmoji
|
_buffer.Append(
|
||||||
? $":{emoji.Name}:"
|
emoji.IsCustomEmoji
|
||||||
: emoji.Name);
|
? $":{emoji.Name}:"
|
||||||
|
: emoji.Name
|
||||||
|
);
|
||||||
|
|
||||||
return base.VisitEmoji(emoji);
|
return base.VisitEmoji(emoji);
|
||||||
}
|
}
|
||||||
|
|
|
@ -22,41 +22,8 @@ namespace DiscordChatExporter.Domain.Exporting.Writers
|
||||||
_writer = new StreamWriter(stream);
|
_writer = new StreamWriter(stream);
|
||||||
}
|
}
|
||||||
|
|
||||||
private string FormatPreamble()
|
private string FormatMarkdown(string? markdown) =>
|
||||||
{
|
PlainTextMarkdownVisitor.Format(Context, markdown ?? "");
|
||||||
var buffer = new StringBuilder();
|
|
||||||
|
|
||||||
buffer.Append('=', 62).AppendLine();
|
|
||||||
buffer.AppendLine($"Guild: {Context.Guild.Name}");
|
|
||||||
buffer.AppendLine($"Channel: {Context.Channel.Name}");
|
|
||||||
|
|
||||||
if (!string.IsNullOrWhiteSpace(Context.Channel.Topic))
|
|
||||||
buffer.AppendLine($"Topic: {Context.Channel.Topic}");
|
|
||||||
|
|
||||||
if (Context.After != null)
|
|
||||||
buffer.AppendLine($"After: {Context.After.Value.ToLocalString(Context.DateFormat)}");
|
|
||||||
|
|
||||||
if (Context.Before != null)
|
|
||||||
buffer.AppendLine($"Before: {Context.Before.Value.ToLocalString(Context.DateFormat)}");
|
|
||||||
|
|
||||||
buffer.Append('=', 62).AppendLine();
|
|
||||||
|
|
||||||
return buffer.ToString();
|
|
||||||
}
|
|
||||||
|
|
||||||
private string FormatPostamble()
|
|
||||||
{
|
|
||||||
var buffer = new StringBuilder();
|
|
||||||
|
|
||||||
buffer.Append('=', 62).AppendLine();
|
|
||||||
buffer.AppendLine($"Exported {_messageCount:N0} message(s)");
|
|
||||||
buffer.Append('=', 62).AppendLine();
|
|
||||||
|
|
||||||
return buffer.ToString();
|
|
||||||
}
|
|
||||||
|
|
||||||
private string FormatMarkdown(string markdown) =>
|
|
||||||
PlainTextMarkdownVisitor.Format(Context, markdown);
|
|
||||||
|
|
||||||
private string FormatMessageHeader(Message message)
|
private string FormatMessageHeader(Message message)
|
||||||
{
|
{
|
||||||
|
@ -70,21 +37,11 @@ namespace DiscordChatExporter.Domain.Exporting.Writers
|
||||||
|
|
||||||
// Whether the message is pinned
|
// Whether the message is pinned
|
||||||
if (message.IsPinned)
|
if (message.IsPinned)
|
||||||
{
|
|
||||||
buffer.Append(' ').Append("(pinned)");
|
buffer.Append(' ').Append("(pinned)");
|
||||||
}
|
|
||||||
|
|
||||||
return buffer.ToString();
|
return buffer.ToString();
|
||||||
}
|
}
|
||||||
|
|
||||||
private string FormatMessageContent(Message message)
|
|
||||||
{
|
|
||||||
if (string.IsNullOrWhiteSpace(message.Content))
|
|
||||||
return "";
|
|
||||||
|
|
||||||
return FormatMarkdown(message.Content);
|
|
||||||
}
|
|
||||||
|
|
||||||
private string FormatAttachments(IReadOnlyList<Attachment> attachments)
|
private string FormatAttachments(IReadOnlyList<Attachment> attachments)
|
||||||
{
|
{
|
||||||
if (!attachments.Any())
|
if (!attachments.Any())
|
||||||
|
@ -109,49 +66,25 @@ namespace DiscordChatExporter.Domain.Exporting.Writers
|
||||||
|
|
||||||
foreach (var embed in embeds)
|
foreach (var embed in embeds)
|
||||||
{
|
{
|
||||||
buffer.AppendLine("{Embed}");
|
buffer
|
||||||
|
.AppendLine("{Embed}")
|
||||||
|
.AppendLineIfNotNullOrWhiteSpace(embed.Author?.Name)
|
||||||
|
.AppendLineIfNotNullOrWhiteSpace(embed.Url)
|
||||||
|
.AppendLineIfNotNullOrWhiteSpace(FormatMarkdown(embed.Title))
|
||||||
|
.AppendLineIfNotNullOrWhiteSpace(FormatMarkdown(embed.Description));
|
||||||
|
|
||||||
// Author name
|
|
||||||
if (!string.IsNullOrWhiteSpace(embed.Author?.Name))
|
|
||||||
buffer.AppendLine(embed.Author.Name);
|
|
||||||
|
|
||||||
// URL
|
|
||||||
if (!string.IsNullOrWhiteSpace(embed.Url))
|
|
||||||
buffer.AppendLine(embed.Url);
|
|
||||||
|
|
||||||
// Title
|
|
||||||
if (!string.IsNullOrWhiteSpace(embed.Title))
|
|
||||||
buffer.AppendLine(FormatMarkdown(embed.Title));
|
|
||||||
|
|
||||||
// Description
|
|
||||||
if (!string.IsNullOrWhiteSpace(embed.Description))
|
|
||||||
buffer.AppendLine(FormatMarkdown(embed.Description));
|
|
||||||
|
|
||||||
// Fields
|
|
||||||
foreach (var field in embed.Fields)
|
foreach (var field in embed.Fields)
|
||||||
{
|
{
|
||||||
// Name
|
buffer
|
||||||
if (!string.IsNullOrWhiteSpace(field.Name))
|
.AppendLineIfNotNullOrWhiteSpace(field.Name)
|
||||||
buffer.AppendLine(field.Name);
|
.AppendLineIfNotNullOrWhiteSpace(field.Value);
|
||||||
|
|
||||||
// Value
|
|
||||||
if (!string.IsNullOrWhiteSpace(field.Value))
|
|
||||||
buffer.AppendLine(field.Value);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Thumbnail URL
|
buffer
|
||||||
if (!string.IsNullOrWhiteSpace(embed.Thumbnail?.Url))
|
.AppendLineIfNotNullOrWhiteSpace(embed.Thumbnail?.Url)
|
||||||
buffer.AppendLine(embed.Thumbnail?.Url);
|
.AppendLineIfNotNullOrWhiteSpace(embed.Image?.Url)
|
||||||
|
.AppendLineIfNotNullOrWhiteSpace(embed.Footer?.Text)
|
||||||
// Image URL
|
.AppendLine();
|
||||||
if (!string.IsNullOrWhiteSpace(embed.Image?.Url))
|
|
||||||
buffer.AppendLine(embed.Image?.Url);
|
|
||||||
|
|
||||||
// Footer text
|
|
||||||
if (!string.IsNullOrWhiteSpace(embed.Footer?.Text))
|
|
||||||
buffer.AppendLine(embed.Footer?.Text);
|
|
||||||
|
|
||||||
buffer.AppendLine();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return buffer.ToString();
|
return buffer.ToString();
|
||||||
|
@ -187,18 +120,35 @@ namespace DiscordChatExporter.Domain.Exporting.Writers
|
||||||
|
|
||||||
buffer
|
buffer
|
||||||
.AppendLine(FormatMessageHeader(message))
|
.AppendLine(FormatMessageHeader(message))
|
||||||
.AppendLineIfNotEmpty(FormatMessageContent(message))
|
.AppendLineIfNotNullOrWhiteSpace(FormatMarkdown(message.Content))
|
||||||
.AppendLine()
|
.AppendLine()
|
||||||
.AppendLineIfNotEmpty(FormatAttachments(message.Attachments))
|
.AppendLineIfNotNullOrWhiteSpace(FormatAttachments(message.Attachments))
|
||||||
.AppendLineIfNotEmpty(FormatEmbeds(message.Embeds))
|
.AppendLineIfNotNullOrWhiteSpace(FormatEmbeds(message.Embeds))
|
||||||
.AppendLineIfNotEmpty(FormatReactions(message.Reactions));
|
.AppendLineIfNotNullOrWhiteSpace(FormatReactions(message.Reactions));
|
||||||
|
|
||||||
return buffer.Trim().ToString();
|
return buffer.Trim().ToString();
|
||||||
}
|
}
|
||||||
|
|
||||||
public override async Task WritePreambleAsync()
|
public override async Task WritePreambleAsync()
|
||||||
{
|
{
|
||||||
await _writer.WriteLineAsync(FormatPreamble());
|
var buffer = new StringBuilder();
|
||||||
|
|
||||||
|
buffer.Append('=', 62).AppendLine();
|
||||||
|
buffer.AppendLine($"Guild: {Context.Guild.Name}");
|
||||||
|
buffer.AppendLine($"Channel: {Context.Channel.Name}");
|
||||||
|
|
||||||
|
if (!string.IsNullOrWhiteSpace(Context.Channel.Topic))
|
||||||
|
buffer.AppendLine($"Topic: {Context.Channel.Topic}");
|
||||||
|
|
||||||
|
if (Context.After != null)
|
||||||
|
buffer.AppendLine($"After: {Context.After.Value.ToLocalString(Context.DateFormat)}");
|
||||||
|
|
||||||
|
if (Context.Before != null)
|
||||||
|
buffer.AppendLine($"Before: {Context.Before.Value.ToLocalString(Context.DateFormat)}");
|
||||||
|
|
||||||
|
buffer.Append('=', 62).AppendLine();
|
||||||
|
|
||||||
|
await _writer.WriteLineAsync(buffer.ToString());
|
||||||
}
|
}
|
||||||
|
|
||||||
public override async Task WriteMessageAsync(Message message)
|
public override async Task WriteMessageAsync(Message message)
|
||||||
|
@ -211,8 +161,15 @@ namespace DiscordChatExporter.Domain.Exporting.Writers
|
||||||
|
|
||||||
public override async Task WritePostambleAsync()
|
public override async Task WritePostambleAsync()
|
||||||
{
|
{
|
||||||
await _writer.WriteLineAsync();
|
var buffer = new StringBuilder();
|
||||||
await _writer.WriteLineAsync(FormatPostamble());
|
|
||||||
|
buffer
|
||||||
|
.Append('=', 62).AppendLine()
|
||||||
|
.AppendLine($"Exported {_messageCount:N0} message(s)")
|
||||||
|
.Append('=', 62).AppendLine()
|
||||||
|
.AppendLine();
|
||||||
|
|
||||||
|
await _writer.WriteLineAsync(buffer.ToString());
|
||||||
}
|
}
|
||||||
|
|
||||||
public override async ValueTask DisposeAsync()
|
public override async ValueTask DisposeAsync()
|
||||||
|
|
|
@ -4,7 +4,7 @@ namespace DiscordChatExporter.Domain.Internal
|
||||||
{
|
{
|
||||||
internal static class StringExtensions
|
internal static class StringExtensions
|
||||||
{
|
{
|
||||||
public static StringBuilder AppendLineIfNotEmpty(this StringBuilder builder, string value) =>
|
public static StringBuilder AppendLineIfNotNullOrWhiteSpace(this StringBuilder builder, string? value) =>
|
||||||
!string.IsNullOrWhiteSpace(value) ? builder.AppendLine(value) : builder;
|
!string.IsNullOrWhiteSpace(value) ? builder.AppendLine(value) : builder;
|
||||||
|
|
||||||
public static StringBuilder Trim(this StringBuilder builder)
|
public static StringBuilder Trim(this StringBuilder builder)
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using System.Runtime.CompilerServices;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
@ -8,6 +9,19 @@ namespace DiscordChatExporter.Domain.Utilities
|
||||||
{
|
{
|
||||||
public static class AsyncExtensions
|
public static class AsyncExtensions
|
||||||
{
|
{
|
||||||
|
private static async ValueTask<IReadOnlyList<T>> AggregateAsync<T>(this IAsyncEnumerable<T> asyncEnumerable)
|
||||||
|
{
|
||||||
|
var list = new List<T>();
|
||||||
|
|
||||||
|
await foreach (var i in asyncEnumerable)
|
||||||
|
list.Add(i);
|
||||||
|
|
||||||
|
return list;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static ValueTaskAwaiter<IReadOnlyList<T>> GetAwaiter<T>(this IAsyncEnumerable<T> asyncEnumerable) =>
|
||||||
|
asyncEnumerable.AggregateAsync().GetAwaiter();
|
||||||
|
|
||||||
public static async Task ParallelForEachAsync<T>(this IEnumerable<T> source, Func<T, Task> handleAsync, int degreeOfParallelism)
|
public static async Task ParallelForEachAsync<T>(this IEnumerable<T> source, Func<T, Task> handleAsync, int degreeOfParallelism)
|
||||||
{
|
{
|
||||||
using var semaphore = new SemaphoreSlim(degreeOfParallelism);
|
using var semaphore = new SemaphoreSlim(degreeOfParallelism);
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue