using System.Diagnostics.CodeAnalysis; using System.Text.RegularExpressions; using Content.Shared._Sunrise.SunriseCCVars; using Content.Shared.Chat; using Robust.Server.Player; using Robust.Shared.Configuration; using Robust.Shared.Player; namespace Content.Server._Sunrise.Chat.Sanitization; /// /// Система автоматической очистки (санитизации) чата. /// public sealed partial class ChatSanitizationSystem : EntitySystem { [Dependency] private readonly IConfigurationManager _configuration = default!; [Dependency] private readonly ISharedChatManager _chat = default!; [Dependency] private readonly IPlayerManager _player = default!; private bool _chatSanitizationEnabled; private bool _aggressive; /// /// Расширенный паттерн для обнаружения ссылок. /// private static readonly Regex UrlRegex = new("[-a-zA-Z0-9@:%._\\+~#=]{1,256}\\.[a-zA-Z0-9()]{1,6}\\b([-a-zA-Z0-9()@:%_\\+.~#?&\\/\\/=]*)", RegexOptions.Compiled); /// /// ASCII-art фильтр: отбрасывает нестандартные символы, но сохраняет все локали и управляемые символы. /// private static readonly Regex AsciiArtRegex = new("[^\\x09\\x0A\\x0D\\x20-\\x7E\\u0400-\\u04FF]", RegexOptions.Compiled); private const int MaxAdminAlertMessageLength = 20; public override void Initialize() { base.Initialize(); _chatSanitizationEnabled = _configuration.GetCVar(SunriseCCVars.ChatSanitizationEnable); _aggressive = _configuration.GetCVar(SunriseCCVars.ChatSanitizationAggressive); _configuration.OnValueChanged(SunriseCCVars.ChatSanitizationEnable, val => _chatSanitizationEnabled = val); _configuration.OnValueChanged(SunriseCCVars.ChatSanitizationAggressive, val => _aggressive = val); SubscribeLocalEvent(OnTrySendChatMessage); InitializeAntiSpam(); } private void OnTrySendChatMessage(Entity ent, ref TrySendChatMessageEvent args) { if (args.Cancelled) return; ApplySanitization(ent, ref args); if (args.Cancelled) return; SpamCheck(ent, ref args); } private void ApplySanitization(Entity ent, ref TrySendChatMessageEvent args) { if (!_chatSanitizationEnabled) return; if (!_player.TryGetSessionByEntity(ent, out var session)) return; if (_aggressive) HandleAggressiveSanitization(session, ref args); else HandleSoftSanitization(ref args); } private void HandleAggressiveSanitization(ICommonSession session, ref TrySendChatMessageEvent args) { if (!ContainsProhibitedContent(args.Message, out var reason)) return; NotifyAdminsAboutBlockedMessage(session, args.Message, reason); args.Cancel(); } private static void HandleSoftSanitization(ref TrySendChatMessageEvent args) { args.Message = UrlRegex.Replace(args.Message, string.Empty); args.Message = AsciiArtRegex.Replace(args.Message, string.Empty); } private bool ContainsProhibitedContent(string message, [NotNullWhen(true)] out string? reason) { reason = null; if (UrlRegex.IsMatch(message)) { reason = Loc.GetString("chatsan-blocked-reason-url"); return true; } if (AsciiArtRegex.IsMatch(message)) { reason = Loc.GetString("chatsan-blocked-reason-ascii-art"); return true; } return false; } private void NotifyAdminsAboutBlockedMessage(ICommonSession session, string message, string reason) { var cropped = message.Length > MaxAdminAlertMessageLength ? message[..MaxAdminAlertMessageLength] : message; _chat.SendAdminAlert(Loc.GetString( "chatsan-admin-alert", ("user", session.Data.UserName), ("reason", reason), ("message_cropped", cropped) )); } }