mirror of
https://github.com/Tyrrrz/DiscordChatExporter.git
synced 2025-05-29 14:05:18 -04:00
parent
ae42554621
commit
abf7498667
6 changed files with 121 additions and 45 deletions
|
@ -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)
|
||||
{
|
||||
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))
|
||||
protected override MarkdownNode VisitUnixTimestamp(UnixTimestampNode timestamp)
|
||||
{
|
||||
_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))
|
||||
};
|
||||
|
||||
|
|
13
DiscordChatExporter.Core/Markdown/UnixTimestampNode.cs
Normal file
13
DiscordChatExporter.Core/Markdown/UnixTimestampNode.cs
Normal file
|
@ -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…
Add table
Add a link
Reference in a new issue