diff --git a/src/Discord.Net.Rest/Entities/Messages/MessageHelper.cs b/src/Discord.Net.Rest/Entities/Messages/MessageHelper.cs index d9254d034..a11182414 100644 --- a/src/Discord.Net.Rest/Entities/Messages/MessageHelper.cs +++ b/src/Discord.Net.Rest/Entities/Messages/MessageHelper.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; using System.Collections.Immutable; using System.Linq; +using System.Text.RegularExpressions; using System.Threading.Tasks; using Model = Discord.API.Message; @@ -101,34 +102,55 @@ namespace Discord.Rest int index = 0; int codeBlockIndex = 0; int closeIndex; + bool isBlock = false; + + //var codeBlockRegex = new Regex(@"((?!\\)`){3}", RegexOptions.Compiled); + ////var codeInlineRegex = new Regex(@"[^\\]`", RegexOptions.Compiled); + //var codeInlineRegex = new Regex(@"((?!\\)`)", RegexOptions.Compiled); + + var inlineRegex = new Regex(@"[^\\]?(`).+?[^\\](`)", RegexOptions.Compiled | RegexOptions.Multiline | RegexOptions.Singleline); + var blockRegex = new Regex(@"[^\\]?(```).+?[^\\](```)", RegexOptions.Compiled | RegexOptions.Multiline | RegexOptions.Singleline); - // gets the next index of a codeblock stop/start - int IndexOfCode(int startIndex) - { - if (startIndex == -1) - return -1; - // 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; - } // checks if the tag being parsed is wrapped in code blocks bool CheckWrappedCode() { - closeIndex = 0; - codeBlockIndex = IndexOfCode(codeBlockIndex); - // loop through all code blocks, before the starting index - while (codeBlockIndex != -1 && closeIndex != -1 && closeIndex < index) + // util to check if the index of a tag is within the bounds of the codeblock + bool EnclosedInBlock(Match m) + => codeBlockIndex + m.Groups[1].Index < index && index < codeBlockIndex + m.Groups[2].Index; + + // need to check if the upper bound is beyond the end of the regex + int upperBound = -1; + while (upperBound < index) { - closeIndex = IndexOfCode(codeBlockIndex); - codeBlockIndex = IndexOfCode(closeIndex); + var blockMatch = blockRegex.Match(text, codeBlockIndex); + if (blockMatch.Success) + { + upperBound = blockMatch.Groups[2].Index + codeBlockIndex; + if (upperBound < index) + break; + if (EnclosedInBlock(blockMatch)) + { + codeBlockIndex = blockMatch.Groups[2].Index + blockMatch.Groups[2].Length; + return true; + } + return false; + } + var inlineMatch = inlineRegex.Match(text, codeBlockIndex); + if (inlineMatch.Success) + { + upperBound = inlineMatch.Groups[2].Index + codeBlockIndex; + if (upperBound < index) + break; + if (EnclosedInBlock(inlineMatch)) + { + codeBlockIndex = inlineMatch.Groups[2].Index + inlineMatch.Groups[2].Length; + return true; + } + return false; + } + return false; } - // break if unclosed code block, or code block beyond starting index - return closeIndex == -1 || closeIndex > index; + return false; } while (true) diff --git a/test/Discord.Net.Tests/MessageHelperTests.cs b/test/Discord.Net.Tests/MessageHelperTests.cs index dd015aa90..41918c850 100644 --- a/test/Discord.Net.Tests/MessageHelperTests.cs +++ b/test/Discord.Net.Tests/MessageHelperTests.cs @@ -38,6 +38,8 @@ namespace Discord [InlineData("``` code block 1 ``` ``` code block 2 ``` <@&163184946742034432>")] [InlineData("` code block 1 ``` ` code block 2 ``` <@&163184946742034432>")] [InlineData("<@&163184946742034432> ``` code block 1 ```")] + [InlineData("``` code ``` ``` code ``` @here ``` code ``` ``` more ```")] + [InlineData("``` code ``` @here ``` more ```")] public void ParseTagsAroundCode(string testData) { // don't care that I'm passing in null channels/guilds/users @@ -46,6 +48,16 @@ namespace Discord Assert.NotEmpty(result); } + [Theory] + [InlineData(@"\` @everyone \`")] + [InlineData(@"\`\`\` @everyone \`\`\`")] + [InlineData(@"hey\`\`\`@everyone\`\`\`!!")] + public void IgnoreEscapedCodeBlocks(string testData) + { + var result = MessageHelper.ParseTags(testData, null, null, null); + Assert.NotEmpty(result); + } + // cannot test parsing a user, as it uses the ReadOnlyCollection arg. // this could be done if mocked entities are merged in PR #1290