diff --git a/lib/src/utils/sanitizer.dart b/lib/src/utils/sanitizer.dart index e2a897e..ab7bb07 100644 --- a/lib/src/utils/sanitizer.dart +++ b/lib/src/utils/sanitizer.dart @@ -18,6 +18,14 @@ final channelMentionRegex = RegExp(r"<#(\d+)>"); /// A pattern that matches guild emojis in a message. final guildEmojiRegex = RegExp(r"<(a?):(\w+):(\d+)>"); +const _baseCommandNamePattern = r"[-_\p{L}\p{N}\p{sc=Deva}\p{sc=Thai}]+"; + +/// A pattern that matches slash commands in a message. +final commandMentionRegex = RegExp( + '<\\/(?(?:$_baseCommandNamePattern(?:\\s$_baseCommandNamePattern){0,2})):(\\d{17,19})>', + unicode: true, +); + /// A type of target [sanitizeContent] can operate on. enum SanitizerTarget { /// Sanitize user mentions that match [userMentionRegex]. @@ -34,6 +42,9 @@ enum SanitizerTarget { /// Sanitize guild emojis that match [guildEmojiRegex]. emojis, + + /// Sanitize slash commands mentions that match [commandMentionRegex]. + commands, } /// An action [sanitizeContent] can take on a target. @@ -77,9 +88,10 @@ Future sanitizeContent( SanitizerTarget.everyone => everyoneMentionRegex, SanitizerTarget.channels => channelMentionRegex, SanitizerTarget.emojis => guildEmojiRegex, + SanitizerTarget.commands => commandMentionRegex, }; - Future name(Match match, SanitizerTarget target) async => switch (target) { + Future name(RegExpMatch match, SanitizerTarget target) async => switch (target) { SanitizerTarget.everyone => match.group(1)!, SanitizerTarget.channels => switch (await client.channels[Snowflake.parse(match.group(1)!)].getOrNull()) { GuildChannel(:final name) || GroupDmChannel(:final name) => name, @@ -99,18 +111,23 @@ Future sanitizeContent( }, }, SanitizerTarget.emojis => match.group(2)!, + SanitizerTarget.commands => match.namedGroup('commandName')!, }; String prefix(SanitizerTarget target) => switch (target) { SanitizerTarget.users || SanitizerTarget.roles => '@', SanitizerTarget.everyone => '@$_whitespaceCharacter', SanitizerTarget.channels => '#', - SanitizerTarget.emojis => '', + SanitizerTarget.emojis => ':', + SanitizerTarget.commands => '/', }; - String suffix(SanitizerTarget target) => target == SanitizerTarget.emojis ? ':' : ''; + String suffix(SanitizerTarget target) => switch (target) { + SanitizerTarget.emojis => ':', + _ => '', + }; - Future resolve(Match match, SanitizerTarget target, SanitizerAction action) async => switch (action) { + Future resolve(RegExpMatch match, SanitizerTarget target, SanitizerAction action) async => switch (action) { SanitizerAction.ignore => match.group(0)!, SanitizerAction.remove => '', SanitizerAction.nameNoPrefix => await name(match, target), @@ -120,7 +137,8 @@ Future sanitizeContent( SanitizerTarget.roles => '<@&$_whitespaceCharacter${match.group(1)!}>', SanitizerTarget.everyone => '@$_whitespaceCharacter${match.group(1)!}', SanitizerTarget.channels => '<#$_whitespaceCharacter${match.group(1)!}>', - SanitizerTarget.emojis => '<$_whitespaceCharacter${match.group(1) ?? ''}:${match.group(2)}:${match.group(3)}>', + SanitizerTarget.emojis => '<$_whitespaceCharacter${match.group(1) ?? ''}\\:${match.group(2)}\\:${match.group(3)}>', + SanitizerTarget.commands => '', }, }; diff --git a/test/unit/sanitizer_test.dart b/test/unit/sanitizer_test.dart index c56edb6..3fa6ed7 100644 --- a/test/unit/sanitizer_test.dart +++ b/test/unit/sanitizer_test.dart @@ -5,10 +5,10 @@ import 'package:test/test.dart'; const _whitespaceCharacter = "‎"; -final sampleContent = '<@1234> test <@!2345> test2 <@&3456> test3 <#4567> test4 test5 <:test_emoji:6789>'; -final removed = ' test test2 test3 test4 test5 '; +final sampleContent = '<@1234> test <@!2345> test2 <@&3456> test3 <#4567> test4 test5 <:test_emoji:6789> test6 '; +final removed = ' test test2 test3 test4 test5 test6 '; final sanitized = - '<@${_whitespaceCharacter}1234> test <@${_whitespaceCharacter}2345> test2 <@&${_whitespaceCharacter}3456> test3 <#${_whitespaceCharacter}4567> test4 <${_whitespaceCharacter}a:test_emoji:5678> test5 <$_whitespaceCharacter:test_emoji:6789>'; + '<@${_whitespaceCharacter}1234> test <@${_whitespaceCharacter}2345> test2 <@&${_whitespaceCharacter}3456> test3 <#${_whitespaceCharacter}4567> test4 <${_whitespaceCharacter}a\\:test_emoji\\:5678> test5 <$_whitespaceCharacter\\:test_emoji\\:6789> test6 '; class MockNyxxRest with Mock implements NyxxRest {}