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;
}