Add support for stickers (#802)

pull/812/head
Oleksii Holub 3 years ago committed by GitHub
parent d51d0d4872
commit cf83cbd89c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -0,0 +1,45 @@
using System.Threading.Tasks;
using DiscordChatExporter.Cli.Tests.Fixtures;
using DiscordChatExporter.Cli.Tests.TestData;
using DiscordChatExporter.Core.Discord;
using FluentAssertions;
using Xunit;
namespace DiscordChatExporter.Cli.Tests.Specs.HtmlWriting;
public record StickerSpecs(ExportWrapperFixture ExportWrapper) : IClassFixture<ExportWrapperFixture>
{
[Fact]
public async Task Message_with_a_PNG_based_sticker_is_rendered_correctly()
{
// Act
var message = await ExportWrapper.GetMessageAsHtmlAsync(
ChannelIds.StickerTestCases,
Snowflake.Parse("939670623158943754")
);
var container = message.QuerySelector("[title='rock']");
var sourceUrl = container?.QuerySelector("img")?.GetAttribute("src");
// Assert
container.Should().NotBeNull();
sourceUrl.Should().Be("https://discord.com/stickers/904215665597120572.png");
}
[Fact]
public async Task Message_with_a_Lottie_based_sticker_is_rendered_correctly()
{
// Act
var message = await ExportWrapper.GetMessageAsHtmlAsync(
ChannelIds.StickerTestCases,
Snowflake.Parse("939670526517997590")
);
var container = message.QuerySelector("[title='Yikes']");
var sourceUrl = container?.QuerySelector("div[data-source]")?.GetAttribute("data-source");
// Assert
container.Should().NotBeNull();
sourceUrl.Should().Be("https://discord.com/stickers/816087132447178774.json");
}
}

@ -0,0 +1,54 @@
using System.Linq;
using System.Threading.Tasks;
using DiscordChatExporter.Cli.Tests.Fixtures;
using DiscordChatExporter.Cli.Tests.TestData;
using DiscordChatExporter.Core.Discord;
using FluentAssertions;
using Xunit;
namespace DiscordChatExporter.Cli.Tests.Specs.JsonWriting;
public record StickerSpecs(ExportWrapperFixture ExportWrapper) : IClassFixture<ExportWrapperFixture>
{
[Fact]
public async Task Message_with_a_PNG_based_sticker_is_rendered_correctly()
{
// Act
var message = await ExportWrapper.GetMessageAsJsonAsync(
ChannelIds.StickerTestCases,
Snowflake.Parse("939670623158943754")
);
var sticker = message
.GetProperty("stickers")
.EnumerateArray()
.Single();
// Assert
sticker.GetProperty("id").GetString().Should().Be("904215665597120572");
sticker.GetProperty("name").GetString().Should().Be("rock");
sticker.GetProperty("format").GetString().Should().Be("PngAnimated");
sticker.GetProperty("sourceUrl").GetString().Should().Be("https://discord.com/stickers/904215665597120572.png");
}
[Fact]
public async Task Message_with_a_Lottie_based_sticker_is_rendered_correctly()
{
// Act
var message = await ExportWrapper.GetMessageAsJsonAsync(
ChannelIds.StickerTestCases,
Snowflake.Parse("939670526517997590")
);
var sticker = message
.GetProperty("stickers")
.EnumerateArray()
.Single();
// Assert
sticker.GetProperty("id").GetString().Should().Be("816087132447178774");
sticker.GetProperty("name").GetString().Should().Be("Yikes");
sticker.GetProperty("format").GetString().Should().Be("Lottie");
sticker.GetProperty("sourceUrl").GetString().Should().Be("https://discord.com/stickers/816087132447178774.json");
}
}

@ -59,6 +59,6 @@ public record PartitioningSpecs(TempOutputFixture TempOutput) : IClassFixture<Te
// Assert
Directory.EnumerateFiles(dirPath, fileNameWithoutExt + "*")
.Should()
.HaveCount(2);
.HaveCount(3);
}
}

@ -10,6 +10,8 @@ public static class ChannelIds
public static Snowflake EmbedTestCases { get; } = Snowflake.Parse("866472452459462687");
public static Snowflake StickerTestCases { get; } = Snowflake.Parse("939668868253769729");
public static Snowflake FilterTestCases { get; } = Snowflake.Parse("866744075033641020");
public static Snowflake MentionTestCases { get; } = Snowflake.Parse("866458801389174794");

@ -21,6 +21,7 @@ public record Message(
string Content,
IReadOnlyList<Attachment> Attachments,
IReadOnlyList<Embed> Embeds,
IReadOnlyList<Sticker> Stickers,
IReadOnlyList<Reaction> Reactions,
IReadOnlyList<User> MentionedUsers,
MessageReference? Reference,
@ -60,6 +61,10 @@ public record Message(
json.GetPropertyOrNull("embeds")?.EnumerateArrayOrNull()?.Select(Embed.Parse).ToArray() ??
Array.Empty<Embed>();
var stickers =
json.GetPropertyOrNull("sticker_items")?.EnumerateArrayOrNull()?.Select(Sticker.Parse).ToArray() ??
Array.Empty<Sticker>();
var reactions =
json.GetPropertyOrNull("reactions")?.EnumerateArrayOrNull()?.Select(Reaction.Parse).ToArray() ??
Array.Empty<Reaction>();
@ -79,6 +84,7 @@ public record Message(
content,
attachments,
embeds,
stickers,
reactions,
mentionedUsers,
messageReference,

@ -0,0 +1,25 @@
using System.Text.Json;
using DiscordChatExporter.Core.Utils.Extensions;
using JsonExtensions.Reading;
namespace DiscordChatExporter.Core.Discord.Data;
public record Sticker(Snowflake Id, string Name, StickerFormat Format, string SourceUrl)
{
private static string GetSourceUrl(Snowflake id, StickerFormat format)
{
var extension = format == StickerFormat.Lottie ? "json" : "png";
return $"https://discord.com/stickers/{id}.{extension}";
}
public static Sticker Parse(JsonElement json)
{
var id = json.GetProperty("id").GetNonWhiteSpaceString().Pipe(Snowflake.Parse);
var name = json.GetProperty("name").GetNonWhiteSpaceString();
var format = (StickerFormat)json.GetProperty("format_type").GetInt32();
var sourceUrl = GetSourceUrl(id, format);
return new Sticker(id, name, format, sourceUrl);
}
}

@ -0,0 +1,8 @@
namespace DiscordChatExporter.Core.Discord.Data;
public enum StickerFormat
{
Png = 1,
PngAnimated = 2,
Lottie = 3
}

@ -1,6 +1,7 @@
@using System
@using System.Linq
@using System.Threading.Tasks
@using DiscordChatExporter.Core.Discord.Data
@using DiscordChatExporter.Core.Exporting.Writers.Html;
@namespace DiscordChatExporter.Core.Exporting.Writers.Html
@ -411,6 +412,21 @@
}
}
@{/* Stickers */}
@foreach (var sticker in message.Stickers)
{
<div class="chatlog__sticker" title="@sticker.Name">
@if (sticker.Format is StickerFormat.Png or StickerFormat.PngAnimated)
{
<img class="chatlog__sticker--media" src="@(await ResolveUrlAsync(sticker.SourceUrl))" alt="Sticker">
}
else if (sticker.Format == StickerFormat.Lottie)
{
<div class="chatlog__sticker--media" data-source="@(await ResolveUrlAsync(sticker.SourceUrl))"></div>
}
</div>
}
@{/* Message reactions */}
@if (message.Reactions.Any())
{

@ -597,6 +597,16 @@
border-radius: 3px;
}
.chatlog__sticker {
width: 180px;
height: 180px;
}
.chatlog__sticker--media {
max-width: 100%;
max-height: 100%;
}
.chatlog__reactions {
display: flex;
}
@ -660,7 +670,29 @@
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/9.15.6/highlight.min.js"></script>
<script>
document.addEventListener('DOMContentLoaded', () => {
document.querySelectorAll('.pre--multiline').forEach(block => hljs.highlightBlock(block));
document.querySelectorAll('.pre--multiline').forEach(e => hljs.highlightBlock(e));
});
</script>
@{/* Lottie animation support */}
<script src="https://cdnjs.cloudflare.com/ajax/libs/lottie-web/5.8.1/lottie.min.js"></script>
<script>
document.addEventListener('DOMContentLoaded', () => {
document.querySelectorAll('.chatlog__sticker--media[data-source]').forEach(e => {
const imageDataUrl = e.getAttribute('data-source');
const anim = lottie.loadAnimation({
container: e,
renderer: 'svg',
loop: true,
autoplay: true,
path: imageDataUrl
});
anim.addEventListener('data_failed', () =>
e.innerHTML = '<strong>[Sticker cannot be rendered]</strong>'
);
});
});
</script>

@ -161,6 +161,21 @@ internal class JsonMessageWriter : MessageWriter
await _writer.FlushAsync(cancellationToken);
}
private async ValueTask WriteStickerAsync(
Sticker sticker,
CancellationToken cancellationToken = default)
{
_writer.WriteStartObject();
_writer.WriteString("id", sticker.Id.ToString());
_writer.WriteString("name", sticker.Name);
_writer.WriteString("format", sticker.Format.ToString());
_writer.WriteString("sourceUrl", await Context.ResolveMediaUrlAsync(sticker.SourceUrl, cancellationToken));
_writer.WriteEndObject();
await _writer.FlushAsync(cancellationToken);
}
private async ValueTask WriteReactionAsync(
Reaction reaction,
CancellationToken cancellationToken = default)
@ -276,6 +291,14 @@ internal class JsonMessageWriter : MessageWriter
_writer.WriteEndArray();
// Stickers
_writer.WriteStartArray("stickers");
foreach (var sticker in message.Stickers)
await WriteStickerAsync(sticker, cancellationToken);
_writer.WriteEndArray();
// Reactions
_writer.WriteStartArray("reactions");

@ -98,6 +98,25 @@ internal class PlainTextMessageWriter : MessageWriter
}
}
private async ValueTask WriteStickersAsync(
IReadOnlyList<Sticker> stickers,
CancellationToken cancellationToken = default)
{
if (!stickers.Any())
return;
await _writer.WriteLineAsync("{Stickers}");
foreach (var sticker in stickers)
{
cancellationToken.ThrowIfCancellationRequested();
await _writer.WriteLineAsync(await Context.ResolveMediaUrlAsync(sticker.SourceUrl, cancellationToken));
}
await _writer.WriteLineAsync();
}
private async ValueTask WriteReactionsAsync(
IReadOnlyList<Reaction> reactions,
CancellationToken cancellationToken = default)
@ -156,9 +175,10 @@ internal class PlainTextMessageWriter : MessageWriter
await _writer.WriteLineAsync();
// Attachments, embeds, reactions
// Attachments, embeds, reactions, etc.
await WriteAttachmentsAsync(message.Attachments, cancellationToken);
await WriteEmbedsAsync(message.Embeds, cancellationToken);
await WriteStickersAsync(message.Stickers, cancellationToken);
await WriteReactionsAsync(message.Reactions, cancellationToken);
await _writer.WriteLineAsync();

Loading…
Cancel
Save