mirror of
https://github.com/Tyrrrz/DiscordChatExporter.git
synced 2025-05-29 05:55:21 -04:00
parent
9fa40dca00
commit
9f6090b3af
9 changed files with 302 additions and 26 deletions
|
@ -5,6 +5,7 @@
|
||||||
PlainText,
|
PlainText,
|
||||||
HtmlDark,
|
HtmlDark,
|
||||||
HtmlLight,
|
HtmlLight,
|
||||||
Csv
|
Csv,
|
||||||
|
Json
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -18,6 +18,7 @@ namespace DiscordChatExporter.Core.Models
|
||||||
ExportFormat.HtmlDark => "html",
|
ExportFormat.HtmlDark => "html",
|
||||||
ExportFormat.HtmlLight => "html",
|
ExportFormat.HtmlLight => "html",
|
||||||
ExportFormat.Csv => "csv",
|
ExportFormat.Csv => "csv",
|
||||||
|
ExportFormat.Json => "json",
|
||||||
_ => throw new ArgumentOutOfRangeException(nameof(format))
|
_ => throw new ArgumentOutOfRangeException(nameof(format))
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -28,6 +29,7 @@ namespace DiscordChatExporter.Core.Models
|
||||||
ExportFormat.HtmlDark => "HTML (Dark)",
|
ExportFormat.HtmlDark => "HTML (Dark)",
|
||||||
ExportFormat.HtmlLight => "HTML (Light)",
|
ExportFormat.HtmlLight => "HTML (Light)",
|
||||||
ExportFormat.Csv => "Comma Separated Values (CSV)",
|
ExportFormat.Csv => "Comma Separated Values (CSV)",
|
||||||
|
ExportFormat.Json => "JavaScript Object Notation (JSON)",
|
||||||
_ => throw new ArgumentOutOfRangeException(nameof(format))
|
_ => throw new ArgumentOutOfRangeException(nameof(format))
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,19 +7,28 @@ namespace DiscordChatExporter.Core.Rendering.Formatters
|
||||||
{
|
{
|
||||||
public class CsvMessageWriter : MessageWriterBase
|
public class CsvMessageWriter : MessageWriterBase
|
||||||
{
|
{
|
||||||
public CsvMessageWriter(TextWriter writer, RenderContext context)
|
private readonly TextWriter _writer;
|
||||||
: base(writer, context)
|
|
||||||
|
public CsvMessageWriter(Stream stream, RenderContext context)
|
||||||
|
: base(stream, context)
|
||||||
{
|
{
|
||||||
|
_writer = new StreamWriter(stream);
|
||||||
}
|
}
|
||||||
|
|
||||||
public override async Task WritePreambleAsync()
|
public override async Task WritePreambleAsync()
|
||||||
{
|
{
|
||||||
await Writer.WriteLineAsync(CsvRenderingLogic.FormatHeader(Context));
|
await _writer.WriteLineAsync(CsvRenderingLogic.FormatHeader(Context));
|
||||||
}
|
}
|
||||||
|
|
||||||
public override async Task WriteMessageAsync(Message message)
|
public override async Task WriteMessageAsync(Message message)
|
||||||
{
|
{
|
||||||
await Writer.WriteLineAsync(CsvRenderingLogic.FormatMessage(Context, message));
|
await _writer.WriteLineAsync(CsvRenderingLogic.FormatMessage(Context, message));
|
||||||
|
}
|
||||||
|
|
||||||
|
public override async ValueTask DisposeAsync()
|
||||||
|
{
|
||||||
|
await _writer.DisposeAsync();
|
||||||
|
await base.DisposeAsync();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -14,6 +14,7 @@ namespace DiscordChatExporter.Core.Rendering.Formatters
|
||||||
{
|
{
|
||||||
public partial class HtmlMessageWriter : MessageWriterBase
|
public partial class HtmlMessageWriter : MessageWriterBase
|
||||||
{
|
{
|
||||||
|
private readonly TextWriter _writer;
|
||||||
private readonly string _themeName;
|
private readonly string _themeName;
|
||||||
private readonly List<Message> _messageGroupBuffer = new List<Message>();
|
private readonly List<Message> _messageGroupBuffer = new List<Message>();
|
||||||
|
|
||||||
|
@ -23,9 +24,10 @@ namespace DiscordChatExporter.Core.Rendering.Formatters
|
||||||
|
|
||||||
private long _messageCount;
|
private long _messageCount;
|
||||||
|
|
||||||
public HtmlMessageWriter(TextWriter writer, RenderContext context, string themeName)
|
public HtmlMessageWriter(Stream stream, RenderContext context, string themeName)
|
||||||
: base(writer, context)
|
: base(stream, context)
|
||||||
{
|
{
|
||||||
|
_writer = new StreamWriter(stream);
|
||||||
_themeName = themeName;
|
_themeName = themeName;
|
||||||
|
|
||||||
_preambleTemplate = Template.Parse(GetPreambleTemplateCode());
|
_preambleTemplate = Template.Parse(GetPreambleTemplateCode());
|
||||||
|
@ -77,7 +79,7 @@ namespace DiscordChatExporter.Core.Rendering.Formatters
|
||||||
templateContext.PushGlobal(scriptObject);
|
templateContext.PushGlobal(scriptObject);
|
||||||
|
|
||||||
// Push output
|
// Push output
|
||||||
templateContext.PushOutput(new TextWriterOutput(Writer));
|
templateContext.PushOutput(new TextWriterOutput(_writer));
|
||||||
|
|
||||||
return templateContext;
|
return templateContext;
|
||||||
}
|
}
|
||||||
|
@ -131,6 +133,12 @@ namespace DiscordChatExporter.Core.Rendering.Formatters
|
||||||
|
|
||||||
await templateContext.EvaluateAsync(_postambleTemplate.Page);
|
await templateContext.EvaluateAsync(_postambleTemplate.Page);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public override async ValueTask DisposeAsync()
|
||||||
|
{
|
||||||
|
await _writer.DisposeAsync();
|
||||||
|
await base.DisposeAsync();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public partial class HtmlMessageWriter
|
public partial class HtmlMessageWriter
|
||||||
|
|
|
@ -0,0 +1,222 @@
|
||||||
|
using System.IO;
|
||||||
|
using System.Text.Json;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using DiscordChatExporter.Core.Models;
|
||||||
|
using DiscordChatExporter.Core.Rendering.Internal;
|
||||||
|
using DiscordChatExporter.Core.Rendering.Logic;
|
||||||
|
|
||||||
|
namespace DiscordChatExporter.Core.Rendering.Formatters
|
||||||
|
{
|
||||||
|
public class JsonMessageWriter : MessageWriterBase
|
||||||
|
{
|
||||||
|
private readonly Utf8JsonWriter _writer;
|
||||||
|
|
||||||
|
private long _messageCount;
|
||||||
|
|
||||||
|
public JsonMessageWriter(Stream stream, RenderContext context)
|
||||||
|
: base(stream, context)
|
||||||
|
{
|
||||||
|
_writer = new Utf8JsonWriter(stream, new JsonWriterOptions
|
||||||
|
{
|
||||||
|
Indented = true
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public override async Task WritePreambleAsync()
|
||||||
|
{
|
||||||
|
// Root object (start)
|
||||||
|
_writer.WriteStartObject();
|
||||||
|
|
||||||
|
// Guild
|
||||||
|
_writer.WriteStartObject("guild");
|
||||||
|
_writer.WriteString("id", Context.Guild.Id);
|
||||||
|
_writer.WriteString("name", Context.Guild.Name);
|
||||||
|
_writer.WriteString("iconUrl", Context.Guild.IconUrl);
|
||||||
|
_writer.WriteEndObject();
|
||||||
|
|
||||||
|
// Channel
|
||||||
|
_writer.WriteStartObject("channel");
|
||||||
|
_writer.WriteString("id", Context.Channel.Id);
|
||||||
|
_writer.WriteString("type", Context.Channel.Type.ToString());
|
||||||
|
_writer.WriteString("name", Context.Channel.Name);
|
||||||
|
_writer.WriteString("topic", Context.Channel.Topic);
|
||||||
|
_writer.WriteEndObject();
|
||||||
|
|
||||||
|
// Date range
|
||||||
|
_writer.WriteStartObject("dateRange");
|
||||||
|
_writer.WriteString("after", Context.After);
|
||||||
|
_writer.WriteString("before", Context.Before);
|
||||||
|
_writer.WriteEndObject();
|
||||||
|
|
||||||
|
// Message array (start)
|
||||||
|
_writer.WriteStartArray("messages");
|
||||||
|
|
||||||
|
await _writer.FlushAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
public override async Task WriteMessageAsync(Message message)
|
||||||
|
{
|
||||||
|
_writer.WriteStartObject();
|
||||||
|
|
||||||
|
// Metadata
|
||||||
|
_writer.WriteString("id", message.Id);
|
||||||
|
_writer.WriteString("type", message.Type.ToString());
|
||||||
|
_writer.WriteString("timestamp", message.Timestamp);
|
||||||
|
_writer.WriteString("timestampEdited", message.EditedTimestamp);
|
||||||
|
_writer.WriteBoolean("isPinned", message.IsPinned);
|
||||||
|
|
||||||
|
// Content
|
||||||
|
var content = PlainTextRenderingLogic.FormatMessageContent(Context, message);
|
||||||
|
_writer.WriteString("content", content);
|
||||||
|
|
||||||
|
// Author
|
||||||
|
_writer.WriteStartObject("author");
|
||||||
|
_writer.WriteString("id", message.Author.Id);
|
||||||
|
_writer.WriteString("name", message.Author.Name);
|
||||||
|
_writer.WriteString("discriminator", $"{message.Author.Discriminator:0000}");
|
||||||
|
_writer.WriteBoolean("isBot", message.Author.IsBot);
|
||||||
|
_writer.WriteString("avatarUrl", message.Author.AvatarUrl);
|
||||||
|
_writer.WriteEndObject();
|
||||||
|
|
||||||
|
// Attachments
|
||||||
|
_writer.WriteStartArray("attachments");
|
||||||
|
|
||||||
|
foreach (var attachment in message.Attachments)
|
||||||
|
{
|
||||||
|
_writer.WriteStartObject();
|
||||||
|
|
||||||
|
_writer.WriteString("id", attachment.Id);
|
||||||
|
_writer.WriteString("url", attachment.Url);
|
||||||
|
_writer.WriteString("fileName", attachment.FileName);
|
||||||
|
_writer.WriteNumber("fileSizeBytes", (long) attachment.FileSize.Bytes);
|
||||||
|
|
||||||
|
_writer.WriteEndObject();
|
||||||
|
}
|
||||||
|
|
||||||
|
_writer.WriteEndArray();
|
||||||
|
|
||||||
|
// Embeds
|
||||||
|
_writer.WriteStartArray("embeds");
|
||||||
|
|
||||||
|
foreach (var embed in message.Embeds)
|
||||||
|
{
|
||||||
|
_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();
|
||||||
|
|
||||||
|
// Reactions
|
||||||
|
_writer.WriteStartArray("reactions");
|
||||||
|
|
||||||
|
foreach (var reaction in message.Reactions)
|
||||||
|
{
|
||||||
|
_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.WriteEndObject();
|
||||||
|
|
||||||
|
_messageCount++;
|
||||||
|
|
||||||
|
// Flush every 100 messages
|
||||||
|
if (_messageCount % 100 == 0)
|
||||||
|
await _writer.FlushAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
public override async Task WritePostambleAsync()
|
||||||
|
{
|
||||||
|
// Message array (end)
|
||||||
|
_writer.WriteEndArray();
|
||||||
|
|
||||||
|
// Message count
|
||||||
|
_writer.WriteNumber("messageCount", _messageCount);
|
||||||
|
|
||||||
|
// Root object (end)
|
||||||
|
_writer.WriteEndObject();
|
||||||
|
|
||||||
|
await _writer.FlushAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
public override async ValueTask DisposeAsync()
|
||||||
|
{
|
||||||
|
await _writer.DisposeAsync();
|
||||||
|
await base.DisposeAsync();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -7,13 +7,13 @@ namespace DiscordChatExporter.Core.Rendering.Formatters
|
||||||
{
|
{
|
||||||
public abstract class MessageWriterBase : IAsyncDisposable
|
public abstract class MessageWriterBase : IAsyncDisposable
|
||||||
{
|
{
|
||||||
protected TextWriter Writer { get; }
|
protected Stream Stream { get; }
|
||||||
|
|
||||||
protected RenderContext Context { get; }
|
protected RenderContext Context { get; }
|
||||||
|
|
||||||
protected MessageWriterBase(TextWriter writer, RenderContext context)
|
protected MessageWriterBase(Stream stream, RenderContext context)
|
||||||
{
|
{
|
||||||
Writer = writer;
|
Stream = stream;
|
||||||
Context = context;
|
Context = context;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -23,6 +23,6 @@ namespace DiscordChatExporter.Core.Rendering.Formatters
|
||||||
|
|
||||||
public virtual Task WritePostambleAsync() => Task.CompletedTask;
|
public virtual Task WritePostambleAsync() => Task.CompletedTask;
|
||||||
|
|
||||||
public async ValueTask DisposeAsync() => await Writer.DisposeAsync();
|
public virtual async ValueTask DisposeAsync() => await Stream.DisposeAsync();
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -7,30 +7,39 @@ namespace DiscordChatExporter.Core.Rendering.Formatters
|
||||||
{
|
{
|
||||||
public class PlainTextMessageWriter : MessageWriterBase
|
public class PlainTextMessageWriter : MessageWriterBase
|
||||||
{
|
{
|
||||||
|
private readonly TextWriter _writer;
|
||||||
|
|
||||||
private long _messageCount;
|
private long _messageCount;
|
||||||
|
|
||||||
public PlainTextMessageWriter(TextWriter writer, RenderContext context)
|
public PlainTextMessageWriter(Stream stream, RenderContext context)
|
||||||
: base(writer, context)
|
: base(stream, context)
|
||||||
{
|
{
|
||||||
|
_writer = new StreamWriter(stream);
|
||||||
}
|
}
|
||||||
|
|
||||||
public override async Task WritePreambleAsync()
|
public override async Task WritePreambleAsync()
|
||||||
{
|
{
|
||||||
await Writer.WriteLineAsync(PlainTextRenderingLogic.FormatPreamble(Context));
|
await _writer.WriteLineAsync(PlainTextRenderingLogic.FormatPreamble(Context));
|
||||||
}
|
}
|
||||||
|
|
||||||
public override async Task WriteMessageAsync(Message message)
|
public override async Task WriteMessageAsync(Message message)
|
||||||
{
|
{
|
||||||
await Writer.WriteLineAsync(PlainTextRenderingLogic.FormatMessage(Context, message));
|
await _writer.WriteLineAsync(PlainTextRenderingLogic.FormatMessage(Context, message));
|
||||||
await Writer.WriteLineAsync();
|
await _writer.WriteLineAsync();
|
||||||
|
|
||||||
_messageCount++;
|
_messageCount++;
|
||||||
}
|
}
|
||||||
|
|
||||||
public override async Task WritePostambleAsync()
|
public override async Task WritePostambleAsync()
|
||||||
{
|
{
|
||||||
await Writer.WriteLineAsync();
|
await _writer.WriteLineAsync();
|
||||||
await Writer.WriteLineAsync(PlainTextRenderingLogic.FormatPostamble(_messageCount));
|
await _writer.WriteLineAsync(PlainTextRenderingLogic.FormatPostamble(_messageCount));
|
||||||
|
}
|
||||||
|
|
||||||
|
public override async ValueTask DisposeAsync()
|
||||||
|
{
|
||||||
|
await _writer.DisposeAsync();
|
||||||
|
await base.DisposeAsync();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -1,4 +1,6 @@
|
||||||
using System.Text;
|
using System;
|
||||||
|
using System.Text;
|
||||||
|
using System.Text.Json;
|
||||||
|
|
||||||
namespace DiscordChatExporter.Core.Rendering.Internal
|
namespace DiscordChatExporter.Core.Rendering.Internal
|
||||||
{
|
{
|
||||||
|
@ -17,5 +19,25 @@ namespace DiscordChatExporter.Core.Rendering.Internal
|
||||||
|
|
||||||
return builder;
|
return builder;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static void WriteString(this Utf8JsonWriter writer, string propertyName, DateTimeOffset? value)
|
||||||
|
{
|
||||||
|
writer.WritePropertyName(propertyName);
|
||||||
|
|
||||||
|
if (value != null)
|
||||||
|
writer.WriteStringValue(value.Value);
|
||||||
|
else
|
||||||
|
writer.WriteNullValue();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void WriteNumber(this Utf8JsonWriter writer, string propertyName, int? value)
|
||||||
|
{
|
||||||
|
writer.WritePropertyName(propertyName);
|
||||||
|
|
||||||
|
if (value != null)
|
||||||
|
writer.WriteNumberValue(value.Value);
|
||||||
|
else
|
||||||
|
writer.WriteNullValue();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -99,21 +99,24 @@ namespace DiscordChatExporter.Core.Rendering
|
||||||
|
|
||||||
private static MessageWriterBase CreateMessageWriter(string filePath, ExportFormat format, RenderContext context)
|
private static MessageWriterBase CreateMessageWriter(string filePath, ExportFormat format, RenderContext context)
|
||||||
{
|
{
|
||||||
// Create inner writer (it will get disposed by the wrapper)
|
// Create a stream (it will get disposed by the writer)
|
||||||
var writer = File.CreateText(filePath);
|
var stream = File.Create(filePath);
|
||||||
|
|
||||||
// Create formatter
|
// Create formatter
|
||||||
if (format == ExportFormat.PlainText)
|
if (format == ExportFormat.PlainText)
|
||||||
return new PlainTextMessageWriter(writer, context);
|
return new PlainTextMessageWriter(stream, context);
|
||||||
|
|
||||||
if (format == ExportFormat.Csv)
|
if (format == ExportFormat.Csv)
|
||||||
return new CsvMessageWriter(writer, context);
|
return new CsvMessageWriter(stream, context);
|
||||||
|
|
||||||
if (format == ExportFormat.HtmlDark)
|
if (format == ExportFormat.HtmlDark)
|
||||||
return new HtmlMessageWriter(writer, context, "Dark");
|
return new HtmlMessageWriter(stream, context, "Dark");
|
||||||
|
|
||||||
if (format == ExportFormat.HtmlLight)
|
if (format == ExportFormat.HtmlLight)
|
||||||
return new HtmlMessageWriter(writer, context, "Light");
|
return new HtmlMessageWriter(stream, context, "Light");
|
||||||
|
|
||||||
|
if (format == ExportFormat.Json)
|
||||||
|
return new JsonMessageWriter(stream, context);
|
||||||
|
|
||||||
throw new InvalidOperationException($"Unknown export format [{format}].");
|
throw new InvalidOperationException($"Unknown export format [{format}].");
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue