mirror of
https://github.com/Tyrrrz/DiscordChatExporter.git
synced 2025-05-30 06:25:22 -04:00
parent
ae42554621
commit
abf7498667
6 changed files with 121 additions and 45 deletions
|
@ -141,10 +141,16 @@
|
||||||
border-radius: 3px;
|
border-radius: 3px;
|
||||||
padding: 0 2px;
|
padding: 0 2px;
|
||||||
color: #7289da;
|
color: #7289da;
|
||||||
background: rgba(114, 137, 218, .1);
|
background-color: rgba(114, 137, 218, .1);
|
||||||
font-weight: 500;
|
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 {
|
.emoji {
|
||||||
width: 1.325em;
|
width: 1.325em;
|
||||||
height: 1.325em;
|
height: 1.325em;
|
||||||
|
@ -588,7 +594,7 @@
|
||||||
border-radius: 3px;
|
border-radius: 3px;
|
||||||
vertical-align: middle;
|
vertical-align: middle;
|
||||||
line-height: 1.3;
|
line-height: 1.3;
|
||||||
background: #5865F2;
|
background-color: #5865F2;
|
||||||
color: #ffffff;
|
color: #ffffff;
|
||||||
font-size: 0.625em;
|
font-size: 0.625em;
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
|
|
|
@ -75,6 +75,40 @@ namespace DiscordChatExporter.Core.Exporting.Writers.MarkdownVisitors
|
||||||
return base.VisitMultiLineCodeBlock(multiLineCodeBlock);
|
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)
|
protected override MarkdownNode VisitMention(MentionNode mention)
|
||||||
{
|
{
|
||||||
var mentionId = Snowflake.TryParse(mention.Id);
|
var mentionId = Snowflake.TryParse(mention.Id);
|
||||||
|
@ -126,38 +160,14 @@ namespace DiscordChatExporter.Core.Exporting.Writers.MarkdownVisitors
|
||||||
return base.VisitMention(mention);
|
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
|
_buffer
|
||||||
.Append($"<img loading=\"lazy\" class=\"emoji {jumboClass}\" alt=\"{emoji.Name}\" title=\"{emoji.Code}\" src=\"{emojiImageUrl}\">");
|
.Append("<span class=\"timestamp\">")
|
||||||
|
.Append(HtmlEncode(_context.FormatDate(timestamp.Value)))
|
||||||
|
.Append("</span>");
|
||||||
|
|
||||||
return base.VisitEmoji(emoji);
|
return base.VisitUnixTimestamp(timestamp);
|
||||||
}
|
|
||||||
|
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -23,6 +23,17 @@ namespace DiscordChatExporter.Core.Exporting.Writers.MarkdownVisitors
|
||||||
return base.VisitText(text);
|
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)
|
protected override MarkdownNode VisitMention(MentionNode mention)
|
||||||
{
|
{
|
||||||
var mentionId = Snowflake.TryParse(mention.Id);
|
var mentionId = Snowflake.TryParse(mention.Id);
|
||||||
|
@ -59,15 +70,13 @@ namespace DiscordChatExporter.Core.Exporting.Writers.MarkdownVisitors
|
||||||
return base.VisitMention(mention);
|
return base.VisitMention(mention);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override MarkdownNode VisitEmoji(EmojiNode emoji)
|
protected override MarkdownNode VisitUnixTimestamp(UnixTimestampNode timestamp)
|
||||||
{
|
{
|
||||||
_buffer.Append(
|
_buffer.Append(
|
||||||
emoji.IsCustomEmoji
|
_context.FormatDate(timestamp.Value)
|
||||||
? $":{emoji.Name}:"
|
|
||||||
: emoji.Name
|
|
||||||
);
|
);
|
||||||
|
|
||||||
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.Linq;
|
||||||
using System.Text.RegularExpressions;
|
using System.Text.RegularExpressions;
|
||||||
using DiscordChatExporter.Core.Utils;
|
using DiscordChatExporter.Core.Utils;
|
||||||
|
@ -225,6 +227,26 @@ namespace DiscordChatExporter.Core.Markdown.Parsing
|
||||||
(_, m) => new TextNode(m.Groups[1].Value)
|
(_, 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
|
// Combine all matchers into one
|
||||||
// Matchers that have similar patterns are ordered from most specific to least specific
|
// Matchers that have similar patterns are ordered from most specific to least specific
|
||||||
private static readonly IMatcher<MarkdownNode> AggregateNodeMatcher = new AggregateMatcher<MarkdownNode>(
|
private static readonly IMatcher<MarkdownNode> AggregateNodeMatcher = new AggregateMatcher<MarkdownNode>(
|
||||||
|
@ -266,7 +288,10 @@ namespace DiscordChatExporter.Core.Markdown.Parsing
|
||||||
// Emoji
|
// Emoji
|
||||||
StandardEmojiNodeMatcher,
|
StandardEmojiNodeMatcher,
|
||||||
CustomEmojiNodeMatcher,
|
CustomEmojiNodeMatcher,
|
||||||
CodedStandardEmojiNodeMatcher
|
CodedStandardEmojiNodeMatcher,
|
||||||
|
|
||||||
|
// Misc
|
||||||
|
UnixTimestampNodeMatcher
|
||||||
);
|
);
|
||||||
|
|
||||||
// Minimal set of matchers for non-multimedia formats (e.g. plain text)
|
// Minimal set of matchers for non-multimedia formats (e.g. plain text)
|
||||||
|
@ -279,7 +304,10 @@ namespace DiscordChatExporter.Core.Markdown.Parsing
|
||||||
RoleMentionNodeMatcher,
|
RoleMentionNodeMatcher,
|
||||||
|
|
||||||
// Emoji
|
// Emoji
|
||||||
CustomEmojiNodeMatcher
|
CustomEmojiNodeMatcher,
|
||||||
|
|
||||||
|
// Misc
|
||||||
|
UnixTimestampNodeMatcher
|
||||||
);
|
);
|
||||||
|
|
||||||
private static IReadOnlyList<MarkdownNode> Parse(StringPart stringPart, IMatcher<MarkdownNode> matcher) =>
|
private static IReadOnlyList<MarkdownNode> Parse(StringPart stringPart, IMatcher<MarkdownNode> matcher) =>
|
||||||
|
|
|
@ -5,7 +5,8 @@ namespace DiscordChatExporter.Core.Markdown.Parsing
|
||||||
{
|
{
|
||||||
internal abstract class MarkdownVisitor
|
internal abstract class MarkdownVisitor
|
||||||
{
|
{
|
||||||
protected virtual MarkdownNode VisitText(TextNode text) => text;
|
protected virtual MarkdownNode VisitText(TextNode text) =>
|
||||||
|
text;
|
||||||
|
|
||||||
protected virtual MarkdownNode VisitFormatted(FormattedNode formatted)
|
protected virtual MarkdownNode VisitFormatted(FormattedNode formatted)
|
||||||
{
|
{
|
||||||
|
@ -13,15 +14,23 @@ namespace DiscordChatExporter.Core.Markdown.Parsing
|
||||||
return formatted;
|
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
|
public MarkdownNode Visit(MarkdownNode node) => node switch
|
||||||
{
|
{
|
||||||
|
@ -32,6 +41,7 @@ namespace DiscordChatExporter.Core.Markdown.Parsing
|
||||||
LinkNode link => VisitLink(link),
|
LinkNode link => VisitLink(link),
|
||||||
EmojiNode emoji => VisitEmoji(emoji),
|
EmojiNode emoji => VisitEmoji(emoji),
|
||||||
MentionNode mention => VisitMention(mention),
|
MentionNode mention => VisitMention(mention),
|
||||||
|
UnixTimestampNode timestamp => VisitUnixTimestamp(timestamp),
|
||||||
_ => throw new ArgumentOutOfRangeException(nameof(node))
|
_ => 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