diff --git a/src/Discord.Net.Rest/Entities/Messages/MessageHelper.cs b/src/Discord.Net.Rest/Entities/Messages/MessageHelper.cs index a132eca52..6517caeae 100644 --- a/src/Discord.Net.Rest/Entities/Messages/MessageHelper.cs +++ b/src/Discord.Net.Rest/Entities/Messages/MessageHelper.cs @@ -98,14 +98,56 @@ namespace Discord.Rest public static ImmutableArray ParseTags(string text, IMessageChannel channel, IGuild guild, IReadOnlyCollection userMentions) { var tags = ImmutableArray.CreateBuilder(); + + // gets the next index of a codeblock stop/start + int IndexOfCode(int startIndex) + { + // code block has precedence over inline code + var codeIndex = text.IndexOf("```", startIndex); + if (codeIndex != -1) + return codeIndex + 3; + codeIndex = text.IndexOf('`', startIndex); + if (codeIndex != -1) + return codeIndex + 1; + return -1; + } int index = 0; + int codeBlockIndex = 0; while (true) { index = text.IndexOf('<', index); if (index == -1) break; int endIndex = text.IndexOf('>', index + 1); if (endIndex == -1) break; + + int closeIndex = 0; + bool CheckCloseIndex() + => closeIndex != -1 && closeIndex < index; + + // check for code blocks before the start index, of either type + codeBlockIndex = IndexOfCode(codeBlockIndex); + while (codeBlockIndex != -1) + { + // code block opens before this tag + closeIndex = IndexOfCode(codeBlockIndex); + // not closed at all + if (closeIndex == -1) + break; + // closed somewhere after the start index + if (closeIndex > index) + break; + // advance codeblock start index + codeBlockIndex = IndexOfCode(closeIndex); + } + // unclosed code block + if (closeIndex == -1) + break; + // closed somewhere after the start index + if (closeIndex > index) + break; + + string content = text.Substring(index, endIndex - index + 1); if (MentionUtils.TryParseUser(content, out ulong id)) @@ -148,8 +190,35 @@ namespace Discord.Rest } index = 0; + codeBlockIndex = 0; while (true) { + int closeIndex = 0; + bool CheckCloseIndex() + => closeIndex != -1 && closeIndex < index; + + // check for code blocks before the start index, of either type + codeBlockIndex = IndexOfCode(codeBlockIndex); + while (codeBlockIndex != -1) + { + // code block opens before this tag + closeIndex = IndexOfCode(codeBlockIndex); + // not closed at all + if (closeIndex == -1) + break; + // closed somewhere after the start index + if (closeIndex > index) + break; + // advance codeblock start index + codeBlockIndex = IndexOfCode(closeIndex); + } + // unclosed code block + if (closeIndex == -1) + break; + // closed somewhere after the start index + if (closeIndex > index) + break; + index = text.IndexOf("@everyone", index); if (index == -1) break; var tagIndex = FindIndex(tags, index); diff --git a/test/Discord.Net.Tests/MessageHelperTests.cs b/test/Discord.Net.Tests/MessageHelperTests.cs new file mode 100644 index 000000000..5caad4ce5 --- /dev/null +++ b/test/Discord.Net.Tests/MessageHelperTests.cs @@ -0,0 +1,44 @@ +using System; +using System.Collections.Generic; +using System.Text; +using Xunit; + +namespace Discord +{ + /// + /// Tests for parsing. + /// + public class MessageHelperTests + { + /// + /// Tests that no tags work while they are in code blocks. + /// + [Theory] + [InlineData("`@everyone`")] + [InlineData("`<@&163184946742034432>`")] + [InlineData("```@everyone```")] + [InlineData("```cs \n @everyone```")] + [InlineData("```cs <@&163184946742034432> ```")] + [InlineData("``` test ``` ```cs <@&163184946742034432> ```")] + public void NoTagsInCodeBlocks(string testData) + { + // don't care that I'm passing in null channels/guilds/users + // as they shouldn't be required + var result = Rest.MessageHelper.ParseTags(testData, null, null, null); + Assert.Empty(result); + } + + [Theory] + [InlineData("`` <@&163184946742034432>")] + [InlineData("``` test ``` ``` test ``` <@&163184946742034432>")] + public void TagsWork(string testData) // todo better names + { + // don't care that I'm passing in null channels/guilds/users + // as they shouldn't be required + var result = Rest.MessageHelper.ParseTags(testData, null, null, null); + Assert.NotEmpty(result); + } + + } +} +