using System.Diagnostics.CodeAnalysis;
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
{
private static readonly Dictionary ShorthandToEmote = new()
{
{ ":)", "chatsan-smiles" },
{ ":]", "chatsan-smiles" },
{ "=)", "chatsan-smiles" },
{ "=]", "chatsan-smiles" },
{ "(:", "chatsan-smiles" },
{ "[:", "chatsan-smiles" },
{ "(=", "chatsan-smiles" },
{ "[=", "chatsan-smiles" },
{ "^^", "chatsan-smiles" },
{ "^-^", "chatsan-smiles" },
{ ":(", "chatsan-frowns" },
{ ":[", "chatsan-frowns" },
{ "=(", "chatsan-frowns" },
{ "=[", "chatsan-frowns" },
{ "):", "chatsan-frowns" },
{ ")=", "chatsan-frowns" },
{ "]:", "chatsan-frowns" },
{ "]=", "chatsan-frowns" },
{ ":D", "chatsan-smiles-widely" },
{ "D:", "chatsan-frowns-deeply" },
{ ":O", "chatsan-surprised" },
{ ":3", "chatsan-smiles" }, //nope
{ ":S", "chatsan-uncertain" },
{ ":>", "chatsan-grins" },
{ ":<", "chatsan-pouts" },
{ "xD", "chatsan-laughs" },
{ ":'(", "chatsan-cries" },
{ ":'[", "chatsan-cries" },
{ "='(", "chatsan-cries" },
{ "='[", "chatsan-cries" },
{ ")':", "chatsan-cries" },
{ "]':", "chatsan-cries" },
{ ")'=", "chatsan-cries" },
{ "]'=", "chatsan-cries" },
{ ";-;", "chatsan-cries" },
{ ";_;", "chatsan-cries" },
{ "qwq", "chatsan-cries" },
{ ":u", "chatsan-smiles-smugly" },
{ ":v", "chatsan-smiles-smugly" },
{ ">:i", "chatsan-annoyed" },
{ ":i", "chatsan-sighs" },
{ ":|", "chatsan-sighs" },
{ ":p", "chatsan-stick-out-tongue" },
{ ";p", "chatsan-stick-out-tongue" },
{ ":b", "chatsan-stick-out-tongue" },
{ "0-0", "chatsan-wide-eyed" },
{ "o-o", "chatsan-wide-eyed" },
{ "o.o", "chatsan-wide-eyed" },
{ "._.", "chatsan-surprised" },
{ ".-.", "chatsan-confused" },
{ "-_-", "chatsan-unimpressed" },
{ "smh", "chatsan-unimpressed" },
{ "o/", "chatsan-waves" },
{ "^^/", "chatsan-waves" },
{ ":/", "chatsan-uncertain" },
{ ":\\", "chatsan-uncertain" },
{ "lmao", "chatsan-laughs" },
{ "lmfao", "chatsan-laughs" },
{ "lol", "chatsan-laughs" },
{ "lel", "chatsan-laughs" },
{ "kek", "chatsan-laughs" },
{ "rofl", "chatsan-laughs" },
{ "o7", "chatsan-salutes" },
{ ";_;7", "chatsan-tearfully-salutes" },
{ "idk", "chatsan-shrugs" },
{ ";)", "chatsan-winks" },
{ ";]", "chatsan-winks" },
{ "(;", "chatsan-winks" },
{ "[;", "chatsan-winks" },
{ ":')", "chatsan-tearfully-smiles" },
{ ":']", "chatsan-tearfully-smiles" },
{ "=')", "chatsan-tearfully-smiles" },
{ "=']", "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()
{
_configurationManager.OnValueChanged(CCVars.ChatSanitizerEnabled, x => _doSanitize = x, true);
}
///
/// 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)
{
emote = null;
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;
}
}