Add support for timestamp markers

Closes #637
pull/678/head
Tyrrrz 3 years ago
parent ae42554621
commit abf7498667

@ -141,10 +141,16 @@
border-radius: 3px;
padding: 0 2px;
color: #7289da;
background: rgba(114, 137, 218, .1);
background-color: rgba(114, 137, 218, .1);
font-weight: 500;
}
.timestamp {
border-radius: 3px;
padding: 0 2px;
background-color: @Themed("rgba(255, 255, 255, 0.06)", "rgba(6, 6, 7, 0.08)");
}
.emoji {
width: 1.325em;
height: 1.325em;
@ -588,7 +594,7 @@
border-radius: 3px;
vertical-align: middle;
line-height: 1.3;
background: #5865F2;
background-color: #5865F2;
color: #ffffff;
font-size: 0.625em;
font-weight: 500;

@ -75,6 +75,40 @@ namespace DiscordChatExporter.Core.Exporting.Writers.MarkdownVisitors
return base.VisitMultiLineCodeBlock(multiLineCodeBlock);
}
protected override MarkdownNode VisitLink(LinkNode link)
{
// Extract message ID if the link points to a Discord message
var linkedMessageId = Regex.Match(link.Url, "^https?://(?:discord|discordapp).com/channels/.*?/(\\d+)/?$").Groups[1].Value;
if (!string.IsNullOrWhiteSpace(linkedMessageId))
{
_buffer
.Append($"<a href=\"{Uri.EscapeUriString(link.Url)}\" onclick=\"scrollToMessage(event, '{linkedMessageId}')\">")
.Append(HtmlEncode(link.Title))
.Append("</a>");
}
else
{
_buffer
.Append($"<a href=\"{Uri.EscapeUriString(link.Url)}\">")
.Append(HtmlEncode(link.Title))
.Append("</a>");
}
return base.VisitLink(link);
}
protected override MarkdownNode VisitEmoji(EmojiNode emoji)
{
var emojiImageUrl = Emoji.GetImageUrl(emoji.Id, emoji.Name, emoji.IsAnimated);
var jumboClass = _isJumbo ? "emoji--large" : "";
_buffer
.Append($"<img loading=\"lazy\" class=\"emoji {jumboClass}\" alt=\"{emoji.Name}\" title=\"{emoji.Code}\" src=\"{emojiImageUrl}\">");
return base.VisitEmoji(emoji);
}
protected override MarkdownNode VisitMention(MentionNode mention)
{
var mentionId = Snowflake.TryParse(mention.Id);
@ -126,38 +160,14 @@ namespace DiscordChatExporter.Core.Exporting.Writers.MarkdownVisitors
return base.VisitMention(mention);
}
protected override MarkdownNode VisitEmoji(EmojiNode emoji)
protected override MarkdownNode VisitUnixTimestamp(UnixTimestampNode timestamp)
{
var emojiImageUrl = Emoji.GetImageUrl(emoji.Id, emoji.Name, emoji.IsAnimated);
var jumboClass = _isJumbo ? "emoji--large" : "";
_buffer
.Append($"<img loading=\"lazy\" class=\"emoji {jumboClass}\" alt=\"{emoji.Name}\" title=\"{emoji.Code}\" src=\"{emojiImageUrl}\">");
return base.VisitEmoji(emoji);
}
protected override MarkdownNode VisitLink(LinkNode link)
{
// Extract message ID if the link points to a Discord message
var linkedMessageId = Regex.Match(link.Url, "^https?://(?:discord|discordapp).com/channels/.*?/(\\d+)/?$").Groups[1].Value;
if (!string.IsNullOrWhiteSpace(linkedMessageId))
{
_buffer
.Append($"<a href=\"{Uri.EscapeUriString(link.Url)}\" onclick=\"scrollToMessage(event, '{linkedMessageId}')\">")
.Append(HtmlEncode(link.Title))
.Append("</a>");
}
else
{
_buffer
.Append($"<a href=\"{Uri.EscapeUriString(link.Url)}\">")
.Append(HtmlEncode(link.Title))
.Append("</a>");
}
.Append("<span class=\"timestamp\">")
.Append(HtmlEncode(_context.FormatDate(timestamp.Value)))
.Append("</span>");
return base.VisitLink(link);
return base.VisitUnixTimestamp(timestamp);
}
}

@ -23,6 +23,17 @@ namespace DiscordChatExporter.Core.Exporting.Writers.MarkdownVisitors
return base.VisitText(text);
}
protected override MarkdownNode VisitEmoji(EmojiNode emoji)
{
_buffer.Append(
emoji.IsCustomEmoji
? $":{emoji.Name}:"
: emoji.Name
);
return base.VisitEmoji(emoji);
}
protected override MarkdownNode VisitMention(MentionNode mention)
{
var mentionId = Snowflake.TryParse(mention.Id);
@ -59,15 +70,13 @@ namespace DiscordChatExporter.Core.Exporting.Writers.MarkdownVisitors
return base.VisitMention(mention);
}
protected override MarkdownNode VisitEmoji(EmojiNode emoji)
protected override MarkdownNode VisitUnixTimestamp(UnixTimestampNode timestamp)
{
_buffer.Append(
emoji.IsCustomEmoji
? $":{emoji.Name}:"
: emoji.Name
_context.FormatDate(timestamp.Value)
);
return base.VisitEmoji(emoji);
return base.VisitUnixTimestamp(timestamp);
}
}

@ -1,4 +1,6 @@
using System.Collections.Generic;
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Text.RegularExpressions;
using DiscordChatExporter.Core.Utils;
@ -225,6 +227,26 @@ namespace DiscordChatExporter.Core.Markdown.Parsing
(_, m) => new TextNode(m.Groups[1].Value)
);
/* Misc */
// Capture <t:12345678> or <t:12345678:R>
private static readonly IMatcher<MarkdownNode> UnixTimestampNodeMatcher = new RegexMatcher<MarkdownNode>(
new Regex("<t:(\\d+)(?::\\w)?>", DefaultRegexOptions),
(_, m) =>
{
// We don't care about the 'R' parameter because we're not going to
// show relative timestamps in an export anyway.
if (!long.TryParse(m.Groups[1].Value, NumberStyles.Integer, CultureInfo.InvariantCulture,
out var offset))
{
return null;
}
return new UnixTimestampNode(DateTimeOffset.UnixEpoch + TimeSpan.FromSeconds(offset));
}
);
// Combine all matchers into one
// Matchers that have similar patterns are ordered from most specific to least specific
private static readonly IMatcher<MarkdownNode> AggregateNodeMatcher = new AggregateMatcher<MarkdownNode>(
@ -266,7 +288,10 @@ namespace DiscordChatExporter.Core.Markdown.Parsing
// Emoji
StandardEmojiNodeMatcher,
CustomEmojiNodeMatcher,
CodedStandardEmojiNodeMatcher
CodedStandardEmojiNodeMatcher,
// Misc
UnixTimestampNodeMatcher
);
// Minimal set of matchers for non-multimedia formats (e.g. plain text)
@ -279,7 +304,10 @@ namespace DiscordChatExporter.Core.Markdown.Parsing
RoleMentionNodeMatcher,
// Emoji
CustomEmojiNodeMatcher
CustomEmojiNodeMatcher,
// Misc
UnixTimestampNodeMatcher
);
private static IReadOnlyList<MarkdownNode> Parse(StringPart stringPart, IMatcher<MarkdownNode> matcher) =>

@ -5,7 +5,8 @@ namespace DiscordChatExporter.Core.Markdown.Parsing
{
internal abstract class MarkdownVisitor
{
protected virtual MarkdownNode VisitText(TextNode text) => text;
protected virtual MarkdownNode VisitText(TextNode text) =>
text;
protected virtual MarkdownNode VisitFormatted(FormattedNode formatted)
{
@ -13,15 +14,23 @@ namespace DiscordChatExporter.Core.Markdown.Parsing
return formatted;
}
protected virtual MarkdownNode VisitInlineCodeBlock(InlineCodeBlockNode inlineCodeBlock) => inlineCodeBlock;
protected virtual MarkdownNode VisitInlineCodeBlock(InlineCodeBlockNode inlineCodeBlock) =>
inlineCodeBlock;
protected virtual MarkdownNode VisitMultiLineCodeBlock(MultiLineCodeBlockNode multiLineCodeBlock) => multiLineCodeBlock;
protected virtual MarkdownNode VisitMultiLineCodeBlock(MultiLineCodeBlockNode multiLineCodeBlock) =>
multiLineCodeBlock;
protected virtual MarkdownNode VisitLink(LinkNode link) => link;
protected virtual MarkdownNode VisitLink(LinkNode link) =>
link;
protected virtual MarkdownNode VisitEmoji(EmojiNode emoji) => emoji;
protected virtual MarkdownNode VisitEmoji(EmojiNode emoji) =>
emoji;
protected virtual MarkdownNode VisitMention(MentionNode mention) => mention;
protected virtual MarkdownNode VisitMention(MentionNode mention) =>
mention;
protected virtual MarkdownNode VisitUnixTimestamp(UnixTimestampNode timestamp) =>
timestamp;
public MarkdownNode Visit(MarkdownNode node) => node switch
{
@ -32,6 +41,7 @@ namespace DiscordChatExporter.Core.Markdown.Parsing
LinkNode link => VisitLink(link),
EmojiNode emoji => VisitEmoji(emoji),
MentionNode mention => VisitMention(mention),
UnixTimestampNode timestamp => VisitUnixTimestamp(timestamp),
_ => throw new ArgumentOutOfRangeException(nameof(node))
};

@ -0,0 +1,13 @@
using System;
namespace DiscordChatExporter.Core.Markdown
{
internal class UnixTimestampNode : MarkdownNode
{
public DateTimeOffset Value { get; }
public UnixTimestampNode(DateTimeOffset value) => Value = value;
public override string ToString() => Value.ToString();
}
}
Loading…
Cancel
Save