From f40589dea68ae7c8f1ce8c4ff319d4251b7b096d Mon Sep 17 00:00:00 2001 From: Tyrrrz Date: Fri, 23 Jul 2021 00:54:00 +0300 Subject: [PATCH] [HTML] Special case Spotify embeds Closes #657 --- DiscordChatExporter.Cli.Tests/EmbedSpecs.cs | 36 +- .../Discord/Data/{ => Embeds}/Embed.cs | 4 +- .../Discord/Data/{ => Embeds}/EmbedAuthor.cs | 2 +- .../Discord/Data/{ => Embeds}/EmbedField.cs | 2 +- .../Discord/Data/{ => Embeds}/EmbedFooter.cs | 2 +- .../Discord/Data/{ => Embeds}/EmbedImage.cs | 2 +- .../Embeds/SpotifyTrackEmbedProjection.cs | 42 +++ .../YouTubeVideoEmbedProjection.cs | 2 +- .../Discord/Data/Message.cs | 1 + .../Writers/Html/MessageGroupTemplate.cshtml | 329 +++++++++++------- .../Writers/Html/PreambleTemplate.cshtml | 4 + .../Exporting/Writers/JsonMessageWriter.cs | 1 + .../Writers/PlainTextMessageWriter.cs | 1 + 13 files changed, 293 insertions(+), 135 deletions(-) rename DiscordChatExporter.Core/Discord/Data/{ => Embeds}/Embed.cs (94%) rename DiscordChatExporter.Core/Discord/Data/{ => Embeds}/EmbedAuthor.cs (95%) rename DiscordChatExporter.Core/Discord/Data/{ => Embeds}/EmbedField.cs (94%) rename DiscordChatExporter.Core/Discord/Data/{ => Embeds}/EmbedFooter.cs (95%) rename DiscordChatExporter.Core/Discord/Data/{ => Embeds}/EmbedImage.cs (94%) create mode 100644 DiscordChatExporter.Core/Discord/Data/Embeds/SpotifyTrackEmbedProjection.cs rename DiscordChatExporter.Core/Discord/Data/{ => Embeds}/YouTubeVideoEmbedProjection.cs (97%) diff --git a/DiscordChatExporter.Cli.Tests/EmbedSpecs.cs b/DiscordChatExporter.Cli.Tests/EmbedSpecs.cs index 9a86b70..e35b130 100644 --- a/DiscordChatExporter.Cli.Tests/EmbedSpecs.cs +++ b/DiscordChatExporter.Cli.Tests/EmbedSpecs.cs @@ -140,7 +140,41 @@ namespace DiscordChatExporter.Cli.Tests } [Fact] - public async Task Message_with_YouTube_video_is_rendered_using_an_iframe_player_in_HTML() + public async Task Message_with_a_Spotify_track_is_rendered_using_an_iframe_in_HTML() + { + // Arrange + var outputFilePath = _tempOutput.GetTempFilePath("html"); + + // Act + var htmlData = await GlobalCache.WrapAsync("embed-specs-output-html", async () => + { + await new ExportChannelsCommand + { + TokenValue = Secrets.DiscordToken, + IsBotToken = Secrets.IsDiscordTokenBot, + ChannelIds = new[] {Snowflake.Parse(ChannelIds.EmbedTestCases)}, + ExportFormat = ExportFormat.HtmlDark, + OutputPath = outputFilePath + }.ExecuteAsync(new FakeConsole()); + + return await File.ReadAllTextAsync(outputFilePath); + }); + + _testOutput.WriteLine(htmlData); + + var html = Html.Parse(htmlData); + + var messageHtml = html.QuerySelector("#message-867886632203976775"); + var iframeHtml = messageHtml?.QuerySelector("iframe"); + + // Assert + iframeHtml.Should().NotBeNull(); + iframeHtml?.GetAttribute("src").Should() + .StartWithEquivalent("https://open.spotify.com/embed/track/1LHZMWefF9502NPfArRfvP"); + } + + [Fact] + public async Task Message_with_a_YouTube_video_is_rendered_using_an_iframe_in_HTML() { // Arrange var outputFilePath = _tempOutput.GetTempFilePath("html"); diff --git a/DiscordChatExporter.Core/Discord/Data/Embed.cs b/DiscordChatExporter.Core/Discord/Data/Embeds/Embed.cs similarity index 94% rename from DiscordChatExporter.Core/Discord/Data/Embed.cs rename to DiscordChatExporter.Core/Discord/Data/Embeds/Embed.cs index 21a29f1..e826267 100644 --- a/DiscordChatExporter.Core/Discord/Data/Embed.cs +++ b/DiscordChatExporter.Core/Discord/Data/Embeds/Embed.cs @@ -7,7 +7,7 @@ using System.Text.Json; using DiscordChatExporter.Core.Utils.Extensions; using JsonExtensions.Reading; -namespace DiscordChatExporter.Core.Discord.Data +namespace DiscordChatExporter.Core.Discord.Data.Embeds { // https://discord.com/developers/docs/resources/channel#embed-object public partial class Embed @@ -56,6 +56,8 @@ namespace DiscordChatExporter.Core.Discord.Data Footer = footer; } + public SpotifyTrackEmbedProjection? TryGetSpotifyTrack() => SpotifyTrackEmbedProjection.TryResolve(this); + public YouTubeVideoEmbedProjection? TryGetYouTubeVideo() => YouTubeVideoEmbedProjection.TryResolve(this); [ExcludeFromCodeCoverage] diff --git a/DiscordChatExporter.Core/Discord/Data/EmbedAuthor.cs b/DiscordChatExporter.Core/Discord/Data/Embeds/EmbedAuthor.cs similarity index 95% rename from DiscordChatExporter.Core/Discord/Data/EmbedAuthor.cs rename to DiscordChatExporter.Core/Discord/Data/Embeds/EmbedAuthor.cs index 825bfa0..c420a99 100644 --- a/DiscordChatExporter.Core/Discord/Data/EmbedAuthor.cs +++ b/DiscordChatExporter.Core/Discord/Data/Embeds/EmbedAuthor.cs @@ -2,7 +2,7 @@ using System.Diagnostics.CodeAnalysis; using System.Text.Json; using JsonExtensions.Reading; -namespace DiscordChatExporter.Core.Discord.Data +namespace DiscordChatExporter.Core.Discord.Data.Embeds { // https://discord.com/developers/docs/resources/channel#embed-object-embed-author-structure public partial class EmbedAuthor diff --git a/DiscordChatExporter.Core/Discord/Data/EmbedField.cs b/DiscordChatExporter.Core/Discord/Data/Embeds/EmbedField.cs similarity index 94% rename from DiscordChatExporter.Core/Discord/Data/EmbedField.cs rename to DiscordChatExporter.Core/Discord/Data/Embeds/EmbedField.cs index 39ed9f1..1e21c64 100644 --- a/DiscordChatExporter.Core/Discord/Data/EmbedField.cs +++ b/DiscordChatExporter.Core/Discord/Data/Embeds/EmbedField.cs @@ -2,7 +2,7 @@ using System.Diagnostics.CodeAnalysis; using System.Text.Json; using JsonExtensions.Reading; -namespace DiscordChatExporter.Core.Discord.Data +namespace DiscordChatExporter.Core.Discord.Data.Embeds { // https://discord.com/developers/docs/resources/channel#embed-object-embed-field-structure public partial class EmbedField diff --git a/DiscordChatExporter.Core/Discord/Data/EmbedFooter.cs b/DiscordChatExporter.Core/Discord/Data/Embeds/EmbedFooter.cs similarity index 95% rename from DiscordChatExporter.Core/Discord/Data/EmbedFooter.cs rename to DiscordChatExporter.Core/Discord/Data/Embeds/EmbedFooter.cs index a405454..931f15e 100644 --- a/DiscordChatExporter.Core/Discord/Data/EmbedFooter.cs +++ b/DiscordChatExporter.Core/Discord/Data/Embeds/EmbedFooter.cs @@ -2,7 +2,7 @@ using System.Diagnostics.CodeAnalysis; using System.Text.Json; using JsonExtensions.Reading; -namespace DiscordChatExporter.Core.Discord.Data +namespace DiscordChatExporter.Core.Discord.Data.Embeds { // https://discord.com/developers/docs/resources/channel#embed-object-embed-footer-structure public partial class EmbedFooter diff --git a/DiscordChatExporter.Core/Discord/Data/EmbedImage.cs b/DiscordChatExporter.Core/Discord/Data/Embeds/EmbedImage.cs similarity index 94% rename from DiscordChatExporter.Core/Discord/Data/EmbedImage.cs rename to DiscordChatExporter.Core/Discord/Data/Embeds/EmbedImage.cs index 0ec33e1..6f0569c 100644 --- a/DiscordChatExporter.Core/Discord/Data/EmbedImage.cs +++ b/DiscordChatExporter.Core/Discord/Data/Embeds/EmbedImage.cs @@ -1,7 +1,7 @@ using System.Text.Json; using JsonExtensions.Reading; -namespace DiscordChatExporter.Core.Discord.Data +namespace DiscordChatExporter.Core.Discord.Data.Embeds { // https://discord.com/developers/docs/resources/channel#embed-object-embed-image-structure public partial class EmbedImage diff --git a/DiscordChatExporter.Core/Discord/Data/Embeds/SpotifyTrackEmbedProjection.cs b/DiscordChatExporter.Core/Discord/Data/Embeds/SpotifyTrackEmbedProjection.cs new file mode 100644 index 0000000..ca77d85 --- /dev/null +++ b/DiscordChatExporter.Core/Discord/Data/Embeds/SpotifyTrackEmbedProjection.cs @@ -0,0 +1,42 @@ +using System.Diagnostics.CodeAnalysis; +using System.Text.RegularExpressions; + +namespace DiscordChatExporter.Core.Discord.Data.Embeds +{ + public partial class SpotifyTrackEmbedProjection + { + public string TrackId { get; } + + public string Url => $"https://open.spotify.com/embed/track/{TrackId}"; + + public SpotifyTrackEmbedProjection(string trackId) => TrackId = trackId; + + [ExcludeFromCodeCoverage] + public override string ToString() => Url; + } + + public partial class SpotifyTrackEmbedProjection + { + private static string? TryParseTrackId(string embedUrl) + { + // https://open.spotify.com/track/1LHZMWefF9502NPfArRfvP?si=3efac6ce9be04f0a + var trackId = Regex.Match(embedUrl, @"spotify\.com/track/(.*?)(?:\?|&|/|$)").Groups[1].Value; + if (!string.IsNullOrWhiteSpace(trackId)) + return trackId; + + return null; + } + + public static SpotifyTrackEmbedProjection? TryResolve(Embed embed) + { + if (string.IsNullOrWhiteSpace(embed.Url)) + return null; + + var trackId = TryParseTrackId(embed.Url); + if (string.IsNullOrWhiteSpace(trackId)) + return null; + + return new SpotifyTrackEmbedProjection(trackId); + } + } +} \ No newline at end of file diff --git a/DiscordChatExporter.Core/Discord/Data/YouTubeVideoEmbedProjection.cs b/DiscordChatExporter.Core/Discord/Data/Embeds/YouTubeVideoEmbedProjection.cs similarity index 97% rename from DiscordChatExporter.Core/Discord/Data/YouTubeVideoEmbedProjection.cs rename to DiscordChatExporter.Core/Discord/Data/Embeds/YouTubeVideoEmbedProjection.cs index 90db28c..bb48448 100644 --- a/DiscordChatExporter.Core/Discord/Data/YouTubeVideoEmbedProjection.cs +++ b/DiscordChatExporter.Core/Discord/Data/Embeds/YouTubeVideoEmbedProjection.cs @@ -1,7 +1,7 @@ using System.Diagnostics.CodeAnalysis; using System.Text.RegularExpressions; -namespace DiscordChatExporter.Core.Discord.Data +namespace DiscordChatExporter.Core.Discord.Data.Embeds { public partial class YouTubeVideoEmbedProjection { diff --git a/DiscordChatExporter.Core/Discord/Data/Message.cs b/DiscordChatExporter.Core/Discord/Data/Message.cs index 6ea4f40..5504271 100644 --- a/DiscordChatExporter.Core/Discord/Data/Message.cs +++ b/DiscordChatExporter.Core/Discord/Data/Message.cs @@ -4,6 +4,7 @@ using System.Diagnostics.CodeAnalysis; using System.Linq; using System.Text.Json; using DiscordChatExporter.Core.Discord.Data.Common; +using DiscordChatExporter.Core.Discord.Data.Embeds; using DiscordChatExporter.Core.Utils.Extensions; using JsonExtensions.Reading; diff --git a/DiscordChatExporter.Core/Exporting/Writers/Html/MessageGroupTemplate.cshtml b/DiscordChatExporter.Core/Exporting/Writers/Html/MessageGroupTemplate.cshtml index 9f18e39..e34709c 100644 --- a/DiscordChatExporter.Core/Exporting/Writers/Html/MessageGroupTemplate.cshtml +++ b/DiscordChatExporter.Core/Exporting/Writers/Html/MessageGroupTemplate.cshtml @@ -176,157 +176,230 @@ @{/* Embeds */} @foreach (var embed in message.Embeds) { - var youTubeVideo = embed.TryGetYouTubeVideo(); + // Spotify embed + if (embed.TryGetSpotifyTrack() is { } spotifyTrackEmbed) + { +
+
+ +
+
+ } + // YouTube embed + else if (embed.TryGetYouTubeVideo() is { } youTubeVideoEmbed) + { +
+ @{/* Color pill */} + @if (embed.Color is not null) + { +
+ } + else + { +
+ } -
- @{/* Color pill */} - @if (embed.Color is not null) - { -
- } - else - { -
- } +
+
+
+ @{/* Embed author */} + @if (embed.Author is not null) + { +
+ @if (!string.IsNullOrWhiteSpace(embed.Author.IconUrl)) + { + Author icon + } + + @if (!string.IsNullOrWhiteSpace(embed.Author.Name)) + { + + @if (!string.IsNullOrWhiteSpace(embed.Author.Url)) + { + @embed.Author.Name + } + else + { + @embed.Author.Name + } + + } +
+ } -
-
-
- @{/* Embed author */} - @if (embed.Author is not null) - { -
- @if (!string.IsNullOrWhiteSpace(embed.Author.IconUrl)) - { - Author icon - } + @{/* Embed title */} + @if (!string.IsNullOrWhiteSpace(embed.Title)) + { +
+ @if (!string.IsNullOrWhiteSpace(embed.Url)) + { + +
@Raw(FormatEmbedMarkdown(embed.Title))
+
+ } + else + { +
@Raw(FormatEmbedMarkdown(embed.Title))
+ } +
+ } - @if (!string.IsNullOrWhiteSpace(embed.Author.Name)) - { - - @if (!string.IsNullOrWhiteSpace(embed.Author.Url)) - { - @embed.Author.Name - } - else - { - @embed.Author.Name - } - - } + @{/* Video player */} +
+
- } +
+
+
+
+ } + // Generic embed + else + { +
+ @{/* Color pill */} + @if (embed.Color is not null) + { +
+ } + else + { +
+ } - @{/* Embed title */} - @if (!string.IsNullOrWhiteSpace(embed.Title)) - { -
- @if (!string.IsNullOrWhiteSpace(embed.Url)) - { - +
+
+
+ @{/* Embed author */} + @if (embed.Author is not null) + { + + } + + @{/* Embed title */} + @if (!string.IsNullOrWhiteSpace(embed.Title)) + { +
+ @if (!string.IsNullOrWhiteSpace(embed.Url)) + { + +
@Raw(FormatEmbedMarkdown(embed.Title))
+
+ } + else + {
@Raw(FormatEmbedMarkdown(embed.Title))
- - } - else - { -
@Raw(FormatEmbedMarkdown(embed.Title))
- } -
- } + } +
+ } - @{/* Embed description (with special casing for YouTube videos) */} - @if (youTubeVideo is not null) - { -
- -
- } - else if (!string.IsNullOrWhiteSpace(embed.Description)) - { -
-
@Raw(FormatEmbedMarkdown(embed.Description))
-
- } + @{/* Embed description */} + @if (!string.IsNullOrWhiteSpace(embed.Description)) + { +
+
@Raw(FormatEmbedMarkdown(embed.Description))
+
+ } - @{/* Embed fields */} - @if (embed.Fields.Any()) + @{/* Embed fields */} + @if (embed.Fields.Any()) + { +
+ @foreach (var field in embed.Fields) + { +
+ @if (!string.IsNullOrWhiteSpace(field.Name)) + { +
+
@Raw(FormatEmbedMarkdown(field.Name))
+
+ } + + @if (!string.IsNullOrWhiteSpace(field.Value)) + { +
+
@Raw(FormatEmbedMarkdown(field.Value))
+
+ } +
+ } +
+ } +
+ + @{/* Embed content */} + @if (embed.Thumbnail is not null && !string.IsNullOrWhiteSpace(embed.Thumbnail.Url)) { -
- @foreach (var field in embed.Fields) - { -
- @if (!string.IsNullOrWhiteSpace(field.Name)) - { -
-
@Raw(FormatEmbedMarkdown(field.Name))
-
- } - - @if (!string.IsNullOrWhiteSpace(field.Value)) - { -
-
@Raw(FormatEmbedMarkdown(field.Value))
-
- } -
- } + }
- @{/* Embed content (not shown for YouTube videos) */} - @if (embed.Thumbnail is not null && !string.IsNullOrWhiteSpace(embed.Thumbnail.Url) && youTubeVideo is null) + @{/* Embed image */} + @if (embed.Image is not null && !string.IsNullOrWhiteSpace(embed.Image.Url)) { - - - @{/* Embed image */} - @if (embed.Image is not null && !string.IsNullOrWhiteSpace(embed.Image.Url)) - { -
- - Image - -
- } - @{/* Embed footer & icon */} - @if (embed.Footer is not null || embed.Timestamp is not null) - { - + } +
-
+ } } @{/* Message reactions */} diff --git a/DiscordChatExporter.Core/Exporting/Writers/Html/PreambleTemplate.cshtml b/DiscordChatExporter.Core/Exporting/Writers/Html/PreambleTemplate.cshtml index bf452a7..8abaf14 100644 --- a/DiscordChatExporter.Core/Exporting/Writers/Html/PreambleTemplate.cshtml +++ b/DiscordChatExporter.Core/Exporting/Writers/Html/PreambleTemplate.cshtml @@ -558,6 +558,10 @@ font-weight: 500; } + .chatlog__embed-spotify { + border: 0; + } + .chatlog__embed-youtube-container { margin-top: 0.6em; } diff --git a/DiscordChatExporter.Core/Exporting/Writers/JsonMessageWriter.cs b/DiscordChatExporter.Core/Exporting/Writers/JsonMessageWriter.cs index 8ea1427..a27482f 100644 --- a/DiscordChatExporter.Core/Exporting/Writers/JsonMessageWriter.cs +++ b/DiscordChatExporter.Core/Exporting/Writers/JsonMessageWriter.cs @@ -3,6 +3,7 @@ using System.Text.Encodings.Web; using System.Text.Json; using System.Threading.Tasks; using DiscordChatExporter.Core.Discord.Data; +using DiscordChatExporter.Core.Discord.Data.Embeds; using DiscordChatExporter.Core.Exporting.Writers.MarkdownVisitors; using DiscordChatExporter.Core.Utils.Extensions; using JsonExtensions.Writing; diff --git a/DiscordChatExporter.Core/Exporting/Writers/PlainTextMessageWriter.cs b/DiscordChatExporter.Core/Exporting/Writers/PlainTextMessageWriter.cs index 0ccab7d..ea7cad4 100644 --- a/DiscordChatExporter.Core/Exporting/Writers/PlainTextMessageWriter.cs +++ b/DiscordChatExporter.Core/Exporting/Writers/PlainTextMessageWriter.cs @@ -3,6 +3,7 @@ using System.IO; using System.Linq; using System.Threading.Tasks; using DiscordChatExporter.Core.Discord.Data; +using DiscordChatExporter.Core.Discord.Data.Embeds; using DiscordChatExporter.Core.Exporting.Writers.MarkdownVisitors; using Tyrrrz.Extensions;