mirror of
https://github.com/Tyrrrz/DiscordChatExporter.git
synced 2025-05-24 03:36:52 -04:00
parent
bb4db21b87
commit
f40589dea6
13 changed files with 297 additions and 139 deletions
|
@ -140,7 +140,41 @@ namespace DiscordChatExporter.Cli.Tests
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[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
|
// Arrange
|
||||||
var outputFilePath = _tempOutput.GetTempFilePath("html");
|
var outputFilePath = _tempOutput.GetTempFilePath("html");
|
||||||
|
|
|
@ -7,7 +7,7 @@ using System.Text.Json;
|
||||||
using DiscordChatExporter.Core.Utils.Extensions;
|
using DiscordChatExporter.Core.Utils.Extensions;
|
||||||
using JsonExtensions.Reading;
|
using JsonExtensions.Reading;
|
||||||
|
|
||||||
namespace DiscordChatExporter.Core.Discord.Data
|
namespace DiscordChatExporter.Core.Discord.Data.Embeds
|
||||||
{
|
{
|
||||||
// https://discord.com/developers/docs/resources/channel#embed-object
|
// https://discord.com/developers/docs/resources/channel#embed-object
|
||||||
public partial class Embed
|
public partial class Embed
|
||||||
|
@ -56,6 +56,8 @@ namespace DiscordChatExporter.Core.Discord.Data
|
||||||
Footer = footer;
|
Footer = footer;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public SpotifyTrackEmbedProjection? TryGetSpotifyTrack() => SpotifyTrackEmbedProjection.TryResolve(this);
|
||||||
|
|
||||||
public YouTubeVideoEmbedProjection? TryGetYouTubeVideo() => YouTubeVideoEmbedProjection.TryResolve(this);
|
public YouTubeVideoEmbedProjection? TryGetYouTubeVideo() => YouTubeVideoEmbedProjection.TryResolve(this);
|
||||||
|
|
||||||
[ExcludeFromCodeCoverage]
|
[ExcludeFromCodeCoverage]
|
|
@ -2,7 +2,7 @@ using System.Diagnostics.CodeAnalysis;
|
||||||
using System.Text.Json;
|
using System.Text.Json;
|
||||||
using JsonExtensions.Reading;
|
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
|
// https://discord.com/developers/docs/resources/channel#embed-object-embed-author-structure
|
||||||
public partial class EmbedAuthor
|
public partial class EmbedAuthor
|
|
@ -2,7 +2,7 @@ using System.Diagnostics.CodeAnalysis;
|
||||||
using System.Text.Json;
|
using System.Text.Json;
|
||||||
using JsonExtensions.Reading;
|
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
|
// https://discord.com/developers/docs/resources/channel#embed-object-embed-field-structure
|
||||||
public partial class EmbedField
|
public partial class EmbedField
|
|
@ -2,7 +2,7 @@ using System.Diagnostics.CodeAnalysis;
|
||||||
using System.Text.Json;
|
using System.Text.Json;
|
||||||
using JsonExtensions.Reading;
|
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
|
// https://discord.com/developers/docs/resources/channel#embed-object-embed-footer-structure
|
||||||
public partial class EmbedFooter
|
public partial class EmbedFooter
|
|
@ -1,7 +1,7 @@
|
||||||
using System.Text.Json;
|
using System.Text.Json;
|
||||||
using JsonExtensions.Reading;
|
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
|
// https://discord.com/developers/docs/resources/channel#embed-object-embed-image-structure
|
||||||
public partial class EmbedImage
|
public partial class EmbedImage
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,7 +1,7 @@
|
||||||
using System.Diagnostics.CodeAnalysis;
|
using System.Diagnostics.CodeAnalysis;
|
||||||
using System.Text.RegularExpressions;
|
using System.Text.RegularExpressions;
|
||||||
|
|
||||||
namespace DiscordChatExporter.Core.Discord.Data
|
namespace DiscordChatExporter.Core.Discord.Data.Embeds
|
||||||
{
|
{
|
||||||
public partial class YouTubeVideoEmbedProjection
|
public partial class YouTubeVideoEmbedProjection
|
||||||
{
|
{
|
|
@ -4,6 +4,7 @@ using System.Diagnostics.CodeAnalysis;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Text.Json;
|
using System.Text.Json;
|
||||||
using DiscordChatExporter.Core.Discord.Data.Common;
|
using DiscordChatExporter.Core.Discord.Data.Common;
|
||||||
|
using DiscordChatExporter.Core.Discord.Data.Embeds;
|
||||||
using DiscordChatExporter.Core.Utils.Extensions;
|
using DiscordChatExporter.Core.Utils.Extensions;
|
||||||
using JsonExtensions.Reading;
|
using JsonExtensions.Reading;
|
||||||
|
|
||||||
|
|
|
@ -176,8 +176,18 @@
|
||||||
@{/* Embeds */}
|
@{/* Embeds */}
|
||||||
@foreach (var embed in message.Embeds)
|
@foreach (var embed in message.Embeds)
|
||||||
{
|
{
|
||||||
var youTubeVideo = embed.TryGetYouTubeVideo();
|
// Spotify embed
|
||||||
|
if (embed.TryGetSpotifyTrack() is { } spotifyTrackEmbed)
|
||||||
|
{
|
||||||
|
<div class="chatlog__embed">
|
||||||
|
<div class="chatlog__embed-spotify-container">
|
||||||
|
<iframe class="chatlog__embed-spotify" src="@spotifyTrackEmbed.Url" width="400" height="80" allowtransparency="true" allow="encrypted-media"></iframe>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
// YouTube embed
|
||||||
|
else if (embed.TryGetYouTubeVideo() is { } youTubeVideoEmbed)
|
||||||
|
{
|
||||||
<div class="chatlog__embed">
|
<div class="chatlog__embed">
|
||||||
@{/* Color pill */}
|
@{/* Color pill */}
|
||||||
@if (embed.Color is not null)
|
@if (embed.Color is not null)
|
||||||
|
@ -234,14 +244,76 @@
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
|
|
||||||
@{/* Embed description (with special casing for YouTube videos) */}
|
@{/* Video player */}
|
||||||
@if (youTubeVideo is not null)
|
|
||||||
{
|
|
||||||
<div class="chatlog__embed-youtube-container">
|
<div class="chatlog__embed-youtube-container">
|
||||||
<iframe class="chatlog__embed-youtube" src="@youTubeVideo.Url" width="400" height="225"></iframe>
|
<iframe class="chatlog__embed-youtube" src="@youTubeVideoEmbed.Url" width="400" height="225"></iframe>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
else if (!string.IsNullOrWhiteSpace(embed.Description))
|
// Generic embed
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<div class="chatlog__embed">
|
||||||
|
@{/* Color pill */}
|
||||||
|
@if (embed.Color is not null)
|
||||||
|
{
|
||||||
|
<div class="chatlog__embed-color-pill" style="background-color: rgba(@embed.Color?.R,@embed.Color?.G,@embed.Color?.B,@embed.Color?.A)"></div>
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<div class="chatlog__embed-color-pill chatlog__embed-color-pill--default"></div>
|
||||||
|
}
|
||||||
|
|
||||||
|
<div class="chatlog__embed-content-container">
|
||||||
|
<div class="chatlog__embed-content">
|
||||||
|
<div class="chatlog__embed-text">
|
||||||
|
@{/* Embed author */}
|
||||||
|
@if (embed.Author is not null)
|
||||||
|
{
|
||||||
|
<div class="chatlog__embed-author">
|
||||||
|
@if (!string.IsNullOrWhiteSpace(embed.Author.IconUrl))
|
||||||
|
{
|
||||||
|
<img class="chatlog__embed-author-icon" src="@await ResolveUrlAsync(embed.Author.IconProxyUrl ?? embed.Author.IconUrl)" alt="Author icon" loading="lazy" onerror="this.style.visibility='hidden'">
|
||||||
|
}
|
||||||
|
|
||||||
|
@if (!string.IsNullOrWhiteSpace(embed.Author.Name))
|
||||||
|
{
|
||||||
|
<span class="chatlog__embed-author-name">
|
||||||
|
@if (!string.IsNullOrWhiteSpace(embed.Author.Url))
|
||||||
|
{
|
||||||
|
<a class="chatlog__embed-author-name-link" href="@embed.Author.Url">@embed.Author.Name</a>
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
@embed.Author.Name
|
||||||
|
}
|
||||||
|
</span>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
|
||||||
|
@{/* Embed title */}
|
||||||
|
@if (!string.IsNullOrWhiteSpace(embed.Title))
|
||||||
|
{
|
||||||
|
<div class="chatlog__embed-title">
|
||||||
|
@if (!string.IsNullOrWhiteSpace(embed.Url))
|
||||||
|
{
|
||||||
|
<a class="chatlog__embed-title-link" href="@embed.Url">
|
||||||
|
<div class="markdown preserve-whitespace">@Raw(FormatEmbedMarkdown(embed.Title))</div>
|
||||||
|
</a>
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<div class="markdown preserve-whitespace">@Raw(FormatEmbedMarkdown(embed.Title))</div>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
|
||||||
|
@{/* Embed description */}
|
||||||
|
@if (!string.IsNullOrWhiteSpace(embed.Description))
|
||||||
{
|
{
|
||||||
<div class="chatlog__embed-description">
|
<div class="chatlog__embed-description">
|
||||||
<div class="markdown preserve-whitespace">@Raw(FormatEmbedMarkdown(embed.Description))</div>
|
<div class="markdown preserve-whitespace">@Raw(FormatEmbedMarkdown(embed.Description))</div>
|
||||||
|
@ -274,8 +346,8 @@
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@{/* Embed content (not shown for YouTube videos) */}
|
@{/* Embed content */}
|
||||||
@if (embed.Thumbnail is not null && !string.IsNullOrWhiteSpace(embed.Thumbnail.Url) && youTubeVideo is null)
|
@if (embed.Thumbnail is not null && !string.IsNullOrWhiteSpace(embed.Thumbnail.Url))
|
||||||
{
|
{
|
||||||
<div class="chatlog__embed-thumbnail-container">
|
<div class="chatlog__embed-thumbnail-container">
|
||||||
<a class="chatlog__embed-thumbnail-link" href="@await ResolveUrlAsync(embed.Thumbnail.ProxyUrl ?? embed.Thumbnail.Url)">
|
<a class="chatlog__embed-thumbnail-link" href="@await ResolveUrlAsync(embed.Thumbnail.ProxyUrl ?? embed.Thumbnail.Url)">
|
||||||
|
@ -328,6 +400,7 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@{/* Message reactions */}
|
@{/* Message reactions */}
|
||||||
@if (message.Reactions.Any())
|
@if (message.Reactions.Any())
|
||||||
|
|
|
@ -558,6 +558,10 @@
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.chatlog__embed-spotify {
|
||||||
|
border: 0;
|
||||||
|
}
|
||||||
|
|
||||||
.chatlog__embed-youtube-container {
|
.chatlog__embed-youtube-container {
|
||||||
margin-top: 0.6em;
|
margin-top: 0.6em;
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,6 +3,7 @@ using System.Text.Encodings.Web;
|
||||||
using System.Text.Json;
|
using System.Text.Json;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using DiscordChatExporter.Core.Discord.Data;
|
using DiscordChatExporter.Core.Discord.Data;
|
||||||
|
using DiscordChatExporter.Core.Discord.Data.Embeds;
|
||||||
using DiscordChatExporter.Core.Exporting.Writers.MarkdownVisitors;
|
using DiscordChatExporter.Core.Exporting.Writers.MarkdownVisitors;
|
||||||
using DiscordChatExporter.Core.Utils.Extensions;
|
using DiscordChatExporter.Core.Utils.Extensions;
|
||||||
using JsonExtensions.Writing;
|
using JsonExtensions.Writing;
|
||||||
|
|
|
@ -3,6 +3,7 @@ using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using DiscordChatExporter.Core.Discord.Data;
|
using DiscordChatExporter.Core.Discord.Data;
|
||||||
|
using DiscordChatExporter.Core.Discord.Data.Embeds;
|
||||||
using DiscordChatExporter.Core.Exporting.Writers.MarkdownVisitors;
|
using DiscordChatExporter.Core.Exporting.Writers.MarkdownVisitors;
|
||||||
using Tyrrrz.Extensions;
|
using Tyrrrz.Extensions;
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue