Don't crash on invalid mentions

Fixes #816
This commit is contained in:
Oleksii Holub 2022-02-19 00:03:17 +02:00
parent 5b563d4f00
commit 407c2bd2ae
6 changed files with 35 additions and 21 deletions

View file

@ -107,27 +107,34 @@ internal partial class HtmlMarkdownVisitor : MarkdownVisitor
protected override MarkdownNode VisitMention(MentionNode mention) protected override MarkdownNode VisitMention(MentionNode mention)
{ {
if (mention.Kind == MentionKind.Meta) if (mention.Kind == MentionKind.Everyone)
{ {
_buffer _buffer
.Append("<span class=\"mention\">") .Append("<span class=\"mention\">")
.Append("@").Append(HtmlEncode(mention.Id.ToString())) .Append("@everyone")
.Append("</span>");
}
else if (mention.Kind == MentionKind.Here)
{
_buffer
.Append("<span class=\"mention\">")
.Append("@here")
.Append("</span>"); .Append("</span>");
} }
else if (mention.Kind == MentionKind.User) else if (mention.Kind == MentionKind.User)
{ {
var member = _context.TryGetMember(mention.Id); var member = mention.TargetId?.Pipe(_context.TryGetMember);
var fullName = member?.User.FullName ?? "Unknown"; var fullName = member?.User.FullName ?? "Unknown";
var nick = member?.Nick ?? "Unknown"; var nick = member?.Nick ?? "Unknown";
_buffer _buffer
.Append($"<span class=\"mention\" title=\"{HtmlEncode(fullName)}\">") .Append($"<span class=\"mention\" title=\"{HtmlEncode(fullName)}\">")
.Append("@").Append(HtmlEncode(nick)) .Append('@').Append(HtmlEncode(nick))
.Append("</span>"); .Append("</span>");
} }
else if (mention.Kind == MentionKind.Channel) else if (mention.Kind == MentionKind.Channel)
{ {
var channel = _context.TryGetChannel(mention.Id); var channel = mention.TargetId?.Pipe(_context.TryGetChannel);
var symbol = channel?.IsVoiceChannel == true ? "🔊" : "#"; var symbol = channel?.IsVoiceChannel == true ? "🔊" : "#";
var name = channel?.Name ?? "deleted-channel"; var name = channel?.Name ?? "deleted-channel";
@ -138,7 +145,7 @@ internal partial class HtmlMarkdownVisitor : MarkdownVisitor
} }
else if (mention.Kind == MentionKind.Role) else if (mention.Kind == MentionKind.Role)
{ {
var role = _context.TryGetRole(mention.Id); var role = mention.TargetId?.Pipe(_context.TryGetRole);
var name = role?.Name ?? "deleted-role"; var name = role?.Name ?? "deleted-role";
var color = role?.Color; var color = role?.Color;
@ -148,7 +155,7 @@ internal partial class HtmlMarkdownVisitor : MarkdownVisitor
_buffer _buffer
.Append($"<span class=\"mention\" style=\"{style}\">") .Append($"<span class=\"mention\" style=\"{style}\">")
.Append("@").Append(HtmlEncode(name)) .Append('@').Append(HtmlEncode(name))
.Append("</span>"); .Append("</span>");
} }

View file

@ -1,6 +1,7 @@
using System.Text; using System.Text;
using DiscordChatExporter.Core.Markdown; using DiscordChatExporter.Core.Markdown;
using DiscordChatExporter.Core.Markdown.Parsing; using DiscordChatExporter.Core.Markdown.Parsing;
using DiscordChatExporter.Core.Utils.Extensions;
namespace DiscordChatExporter.Core.Exporting.Writers.MarkdownVisitors; namespace DiscordChatExporter.Core.Exporting.Writers.MarkdownVisitors;
@ -34,20 +35,24 @@ internal partial class PlainTextMarkdownVisitor : MarkdownVisitor
protected override MarkdownNode VisitMention(MentionNode mention) protected override MarkdownNode VisitMention(MentionNode mention)
{ {
if (mention.Kind == MentionKind.Meta) if (mention.Kind == MentionKind.Everyone)
{ {
_buffer.Append($"@{mention.Id}"); _buffer.Append("@everyone");
}
else if (mention.Kind == MentionKind.Here)
{
_buffer.Append("@here");
} }
else if (mention.Kind == MentionKind.User) else if (mention.Kind == MentionKind.User)
{ {
var member = _context.TryGetMember(mention.Id); var member = mention.TargetId?.Pipe(_context.TryGetMember);
var name = member?.User.Name ?? "Unknown"; var name = member?.User.Name ?? "Unknown";
_buffer.Append($"@{name}"); _buffer.Append($"@{name}");
} }
else if (mention.Kind == MentionKind.Channel) else if (mention.Kind == MentionKind.Channel)
{ {
var channel = _context.TryGetChannel(mention.Id); var channel =mention.TargetId?.Pipe(_context.TryGetChannel);
var name = channel?.Name ?? "deleted-channel"; var name = channel?.Name ?? "deleted-channel";
_buffer.Append($"#{name}"); _buffer.Append($"#{name}");
@ -58,7 +63,7 @@ internal partial class PlainTextMarkdownVisitor : MarkdownVisitor
} }
else if (mention.Kind == MentionKind.Role) else if (mention.Kind == MentionKind.Role)
{ {
var role = _context.TryGetRole(mention.Id); var role = mention.TargetId?.Pipe(_context.TryGetRole);
var name = role?.Name ?? "deleted-role"; var name = role?.Name ?? "deleted-role";
_buffer.Append($"@{name}"); _buffer.Append($"@{name}");

View file

@ -2,7 +2,8 @@
internal enum MentionKind internal enum MentionKind
{ {
Meta, Everyone,
Here,
User, User,
Channel, Channel,
Role Role

View file

@ -2,4 +2,5 @@
namespace DiscordChatExporter.Core.Markdown; namespace DiscordChatExporter.Core.Markdown;
internal record MentionNode(Snowflake Id, MentionKind Kind) : MarkdownNode; // Null ID means it's a meta mention or an invalid mention
internal record MentionNode(Snowflake? TargetId, MentionKind Kind) : MarkdownNode;

View file

@ -124,31 +124,31 @@ internal static partial class MarkdownParser
// Capture @everyone // Capture @everyone
private static readonly IMatcher<MarkdownNode> EveryoneMentionNodeMatcher = new StringMatcher<MarkdownNode>( private static readonly IMatcher<MarkdownNode> EveryoneMentionNodeMatcher = new StringMatcher<MarkdownNode>(
"@everyone", "@everyone",
_ => new MentionNode(Snowflake.Zero, MentionKind.Meta) _ => new MentionNode(null, MentionKind.Everyone)
); );
// Capture @here // Capture @here
private static readonly IMatcher<MarkdownNode> HereMentionNodeMatcher = new StringMatcher<MarkdownNode>( private static readonly IMatcher<MarkdownNode> HereMentionNodeMatcher = new StringMatcher<MarkdownNode>(
"@here", "@here",
_ => new MentionNode(Snowflake.Zero, MentionKind.Meta) _ => new MentionNode(null, MentionKind.Here)
); );
// Capture <@123456> or <@!123456> // Capture <@123456> or <@!123456>
private static readonly IMatcher<MarkdownNode> UserMentionNodeMatcher = new RegexMatcher<MarkdownNode>( private static readonly IMatcher<MarkdownNode> UserMentionNodeMatcher = new RegexMatcher<MarkdownNode>(
new Regex("<@!?(\\d+)>", DefaultRegexOptions), new Regex("<@!?(\\d+)>", DefaultRegexOptions),
(_, m) => new MentionNode(Snowflake.Parse(m.Groups[1].Value), MentionKind.User) (_, m) => new MentionNode(Snowflake.TryParse(m.Groups[1].Value), MentionKind.User)
); );
// Capture <#123456> // Capture <#123456>
private static readonly IMatcher<MarkdownNode> ChannelMentionNodeMatcher = new RegexMatcher<MarkdownNode>( private static readonly IMatcher<MarkdownNode> ChannelMentionNodeMatcher = new RegexMatcher<MarkdownNode>(
new Regex("<#!?(\\d+)>", DefaultRegexOptions), new Regex("<#!?(\\d+)>", DefaultRegexOptions),
(_, m) => new MentionNode(Snowflake.Parse(m.Groups[1].Value), MentionKind.Channel) (_, m) => new MentionNode(Snowflake.TryParse(m.Groups[1].Value), MentionKind.Channel)
); );
// Capture <@&123456> // Capture <@&123456>
private static readonly IMatcher<MarkdownNode> RoleMentionNodeMatcher = new RegexMatcher<MarkdownNode>( private static readonly IMatcher<MarkdownNode> RoleMentionNodeMatcher = new RegexMatcher<MarkdownNode>(
new Regex("<@&(\\d+)>", DefaultRegexOptions), new Regex("<@&(\\d+)>", DefaultRegexOptions),
(_, m) => new MentionNode(Snowflake.Parse(m.Groups[1].Value), MentionKind.Role) (_, m) => new MentionNode(Snowflake.TryParse(m.Groups[1].Value), MentionKind.Role)
); );
/* Emoji */ /* Emoji */
@ -179,7 +179,7 @@ internal static partial class MarkdownParser
private static readonly IMatcher<MarkdownNode> CustomEmojiNodeMatcher = new RegexMatcher<MarkdownNode>( private static readonly IMatcher<MarkdownNode> CustomEmojiNodeMatcher = new RegexMatcher<MarkdownNode>(
new Regex("<(a)?:(.+?):(\\d+?)>", DefaultRegexOptions), new Regex("<(a)?:(.+?):(\\d+?)>", DefaultRegexOptions),
(_, m) => new EmojiNode( (_, m) => new EmojiNode(
Snowflake.Parse(m.Groups[3].Value), Snowflake.TryParse(m.Groups[3].Value),
m.Groups[2].Value, m.Groups[2].Value,
!string.IsNullOrWhiteSpace(m.Groups[1].Value) !string.IsNullOrWhiteSpace(m.Groups[1].Value)
) )

View file

@ -2,5 +2,5 @@
namespace DiscordChatExporter.Core.Markdown; namespace DiscordChatExporter.Core.Markdown;
// Null means invalid date // Null date means invalid timestamp
internal record UnixTimestampNode(DateTimeOffset? Date) : MarkdownNode; internal record UnixTimestampNode(DateTimeOffset? Date) : MarkdownNode;