diff --git a/Content.Server/Chat/Managers/ChatSanitizationManager.cs b/Content.Server/Chat/Managers/ChatSanitizationManager.cs index 634d8cdefa..b0d28eae75 100644 --- a/Content.Server/Chat/Managers/ChatSanitizationManager.cs +++ b/Content.Server/Chat/Managers/ChatSanitizationManager.cs @@ -1,17 +1,19 @@ using System.Diagnostics.CodeAnalysis; -using System.Globalization; +using System.Text.RegularExpressions; using Content.Shared.CCVar; using Robust.Shared.Configuration; namespace Content.Server.Chat.Managers; +/// +/// Sanitizes messages! +/// It currently ony removes the shorthands for emotes (like "lol" or "^-^") from a chat message and returns the last +/// emote in their message +/// public sealed class ChatSanitizationManager : IChatSanitizationManager { - [Dependency] private readonly IConfigurationManager _configurationManager = default!; - - private static readonly Dictionary SmileyToEmote = new() + private static readonly Dictionary ShorthandToEmote = new() { - // I could've done this with regex, but felt it wasn't the right idea. { ":)", "chatsan-smiles" }, { ":]", "chatsan-smiles" }, { "=)", "chatsan-smiles" }, @@ -75,7 +77,7 @@ public sealed class ChatSanitizationManager : IChatSanitizationManager { "kek", "chatsan-laughs" }, { "rofl", "chatsan-laughs" }, { "o7", "chatsan-salutes" }, - { ";_;7", "chatsan-tearfully-salutes"}, + { ";_;7", "chatsan-tearfully-salutes" }, { "idk", "chatsan-shrugs" }, { ";)", "chatsan-winks" }, { ";]", "chatsan-winks" }, @@ -88,9 +90,12 @@ public sealed class ChatSanitizationManager : IChatSanitizationManager { "(':", "chatsan-tearfully-smiles" }, { "[':", "chatsan-tearfully-smiles" }, { "('=", "chatsan-tearfully-smiles" }, - { "['=", "chatsan-tearfully-smiles" }, + { "['=", "chatsan-tearfully-smiles" } }; + [Dependency] private readonly IConfigurationManager _configurationManager = default!; + [Dependency] private readonly ILocalizationManager _loc = default!; + private bool _doSanitize; public void Initialize() @@ -98,29 +103,60 @@ public sealed class ChatSanitizationManager : IChatSanitizationManager _configurationManager.OnValueChanged(CCVars.ChatSanitizerEnabled, x => _doSanitize = x, true); } - public bool TrySanitizeOutSmilies(string input, EntityUid speaker, out string sanitized, [NotNullWhen(true)] out string? emote) + /// + /// Remove the shorthands from the message, returning the last one found as the emote + /// + /// The pre-sanitized message + /// The speaker + /// The sanitized message with shorthands removed + /// The localized emote + /// True if emote has been sanitized out + public bool TrySanitizeEmoteShorthands(string message, + EntityUid speaker, + out string sanitized, + [NotNullWhen(true)] out string? emote) { - if (!_doSanitize) - { - sanitized = input; - emote = null; - return false; - } - - input = input.TrimEnd(); - - foreach (var (smiley, replacement) in SmileyToEmote) - { - if (input.EndsWith(smiley, true, CultureInfo.InvariantCulture)) - { - sanitized = input.Remove(input.Length - smiley.Length).TrimEnd(); - emote = Loc.GetString(replacement, ("ent", speaker)); - return true; - } - } - - sanitized = input; emote = null; - return false; + sanitized = message; + + if (!_doSanitize) + return false; + + // -1 is just a canary for nothing found yet + var lastEmoteIndex = -1; + + foreach (var (shorthand, emoteKey) in ShorthandToEmote) + { + // We have to escape it because shorthands like ":)" or "-_-" would break the regex otherwise. + var escaped = Regex.Escape(shorthand); + + // So there are 2 cases: + // - If there is whitespace before it and after it is either punctuation, whitespace, or the end of the line + // Delete the word and the whitespace before + // - If it is at the start of the string and is followed by punctuation, whitespace, or the end of the line + // Delete the word and the punctuation if it exists. + var pattern = + $@"\s{escaped}(?=\p{{P}}|\s|$)|^{escaped}(?:\p{{P}}|(?=\s|$))"; + + var r = new Regex(pattern, RegexOptions.RightToLeft | RegexOptions.IgnoreCase); + + // We're using sanitized as the original message until the end so that we can make sure the indices of + // the emotes are accurate. + var lastMatch = r.Match(sanitized); + + if (!lastMatch.Success) + continue; + + if (lastMatch.Index > lastEmoteIndex) + { + lastEmoteIndex = lastMatch.Index; + emote = _loc.GetString(emoteKey, ("ent", speaker)); + } + + message = r.Replace(message, string.Empty); + } + + sanitized = message.Trim(); + return emote is not null; } } diff --git a/Content.Server/Chat/Managers/IChatSanitizationManager.cs b/Content.Server/Chat/Managers/IChatSanitizationManager.cs index c067cf02ee..ac85d4b4a7 100644 --- a/Content.Server/Chat/Managers/IChatSanitizationManager.cs +++ b/Content.Server/Chat/Managers/IChatSanitizationManager.cs @@ -6,5 +6,8 @@ public interface IChatSanitizationManager { public void Initialize(); - public bool TrySanitizeOutSmilies(string input, EntityUid speaker, out string sanitized, [NotNullWhen(true)] out string? emote); + public bool TrySanitizeEmoteShorthands(string input, + EntityUid speaker, + out string sanitized, + [NotNullWhen(true)] out string? emote); } diff --git a/Content.Server/Chat/Systems/ChatSystem.cs b/Content.Server/Chat/Systems/ChatSystem.cs index 624c18130b..41646bee2e 100644 --- a/Content.Server/Chat/Systems/ChatSystem.cs +++ b/Content.Server/Chat/Systems/ChatSystem.cs @@ -749,6 +749,9 @@ public sealed partial class ChatSystem : SharedChatSystem var newMessage = message.Trim(); newMessage = SanitizeMessageReplaceWords(newMessage); + // Sanitize it first as it might change the word order + _sanitizer.TrySanitizeEmoteShorthands(newMessage, source, out newMessage, out emoteStr); + if (capitalize) newMessage = SanitizeMessageCapital(newMessage); if (capitalizeTheWordI) @@ -756,8 +759,6 @@ public sealed partial class ChatSystem : SharedChatSystem if (punctuate) newMessage = SanitizeMessagePeriod(newMessage); - _sanitizer.TrySanitizeOutSmilies(newMessage, source, out newMessage, out emoteStr); - return newMessage; }