diff --git a/EOLib.Localization/Services/EDFLoaderService.cs b/EOLib.Localization/Services/EDFLoaderService.cs index a1580c885..43c44c986 100644 --- a/EOLib.Localization/Services/EDFLoaderService.cs +++ b/EOLib.Localization/Services/EDFLoaderService.cs @@ -1,5 +1,6 @@ using AutomaticTypeMapper; using EOLib.IO.Services; +using System; using System.Collections.Generic; using System.IO; using System.Linq; @@ -68,7 +69,7 @@ private IEDFFile LoadCurseFilter(string fileName) foreach (string encoded in lines) { string decoded = DecodeDatString(encoded, DataFiles.CurseFilter); - string[] curses = decoded.Split(':'); + string[] curses = decoded.Split(new[] { ':' }, StringSplitOptions.RemoveEmptyEntries); foreach (string curse in curses) data.Add(i++, curse); } diff --git a/EOLib/Domain/Chat/ChatActions.cs b/EOLib/Domain/Chat/ChatActions.cs index 0d488ba2a..5ebd90bb0 100644 --- a/EOLib/Domain/Chat/ChatActions.cs +++ b/EOLib/Domain/Chat/ChatActions.cs @@ -19,6 +19,7 @@ public class ChatActions : IChatActions private readonly IPacketSendService _packetSendService; private readonly ILocalCommandHandler _localCommandHandler; private readonly IChatProcessor _chatProcessor; + private readonly IConfigurationProvider _configurationProvider; public ChatActions(IChatRepository chatRepository, ICharacterProvider characterProvider, @@ -26,7 +27,8 @@ public ChatActions(IChatRepository chatRepository, IChatPacketBuilder chatPacketBuilder, IPacketSendService packetSendService, ILocalCommandHandler localCommandHandler, - IChatProcessor chatProcessor) + IChatProcessor chatProcessor, + IConfigurationProvider configurationProvider) { _chatRepository = chatRepository; _characterProvider = characterProvider; @@ -35,16 +37,17 @@ public ChatActions(IChatRepository chatRepository, _packetSendService = packetSendService; _localCommandHandler = localCommandHandler; _chatProcessor = chatProcessor; + _configurationProvider = configurationProvider; } - public string SendChatToServer(string chat, string targetCharacter) + public (bool, string) SendChatToServer(string chat, string targetCharacter) { var chatType = _chatTypeCalculator.CalculateChatType(chat); if (chatType == ChatType.Command) { if (HandleCommand(chat)) - return chat; + return (true, chat); //treat unhandled command as local chat chatType = ChatType.Local; @@ -59,6 +62,13 @@ public string SendChatToServer(string chat, string targetCharacter) } chat = _chatProcessor.RemoveFirstCharacterIfNeeded(chat, chatType, targetCharacter); + var (ok, filtered) = _chatProcessor.FilterCurses(chat); + if (!ok) + { + return (ok, filtered); + } + + chat = filtered; if (_characterProvider.MainCharacter.RenderProperties.IsDrunk) chat = _chatProcessor.MakeDrunk(chat); @@ -68,7 +78,7 @@ public string SendChatToServer(string chat, string targetCharacter) AddChatForLocalDisplay(chatType, chat, targetCharacter); - return chat; + return (ok, chat); } public void SetHearWhispers(bool whispersEnabled) @@ -147,7 +157,7 @@ private void AddChatForLocalDisplay(ChatType chatType, string chat, string targe public interface IChatActions { - string SendChatToServer(string chat, string targetCharacter); + (bool Ok, string Processed) SendChatToServer(string chat, string targetCharacter); void SetHearWhispers(bool whispersEnabled); diff --git a/EOLib/Domain/Chat/ChatData.cs b/EOLib/Domain/Chat/ChatData.cs index 1081de966..e02ed43a2 100644 --- a/EOLib/Domain/Chat/ChatData.cs +++ b/EOLib/Domain/Chat/ChatData.cs @@ -6,25 +6,26 @@ namespace EOLib.Domain.Chat [Record(Features.ObjectEquals | Features.ToString)] public sealed partial class ChatData { - public ChatTab Tab { get; } + public ChatTab Tab { get; private set; } - public ChatIcon Icon { get; } + public ChatIcon Icon { get; private set; } - public string Who { get; } + public string Who { get; private set; } - public string Message { get; } + public string Message { get; } // making this get-only makes it the only property that generates a .With method - public ChatColor ChatColor { get; } + public ChatColor ChatColor { get; private set; } - public DateTime ChatTime { get; } + public DateTime ChatTime { get; private set; } - public bool Log { get; } + public bool Log { get; private set; } - public ChatData(ChatTab tab, string who, - string message, - ChatIcon icon = ChatIcon.None, - ChatColor color = ChatColor.Default, - bool log = true) + public ChatData(ChatTab tab, + string who, + string message, + ChatIcon icon = ChatIcon.None, + ChatColor chatColor = ChatColor.Default, + bool log = true) { if (who == null) who = ""; @@ -38,10 +39,15 @@ public ChatData(ChatTab tab, string who, Icon = icon; Who = who; Message = message; - ChatColor = color; + ChatColor = chatColor; Log = log; ChatTime = DateTime.Now; } + + public ChatData WithMessage(string message) + { + return new ChatData(Tab, Who, message, Icon, ChatColor, Log); + } } } \ No newline at end of file diff --git a/EOLib/Domain/Chat/ChatProcessor.cs b/EOLib/Domain/Chat/ChatProcessor.cs index 2c7d9576e..e2f409788 100644 --- a/EOLib/Domain/Chat/ChatProcessor.cs +++ b/EOLib/Domain/Chat/ChatProcessor.cs @@ -2,6 +2,8 @@ using System.Linq; using System.Text; using AutomaticTypeMapper; +using EOLib.Config; +using EOLib.Localization; namespace EOLib.Domain.Chat { @@ -10,6 +12,16 @@ public class ChatProcessor : IChatProcessor { private readonly Random _random = new Random(); + private readonly IDataFileProvider _dataFileProvider; + private readonly IConfigurationProvider _configurationProvider; + + public ChatProcessor(IDataFileProvider dataFileProvider, + IConfigurationProvider configurationProvider) + { + _dataFileProvider = dataFileProvider; + _configurationProvider = configurationProvider; + } + public string RemoveFirstCharacterIfNeeded(string chat, ChatType chatType, string targetCharacter) { switch (chatType) @@ -42,6 +54,8 @@ public string MakeDrunk(string input) { // implementation from Phorophor::notepad (thanks Blo) // https://discord.com/channels/723989119503696013/785190349026492437/791376941822246953 + + // todo: make this use the authentic algorithm here: https://discord.com/channels/723989119503696013/787685796055482368/945700924536553544 var ret = new StringBuilder(); foreach (var c in input) @@ -61,6 +75,31 @@ public string MakeDrunk(string input) return ret.ToString(); } + + public (bool, string) FilterCurses(string input) + { + if (_configurationProvider.CurseFilterEnabled || _configurationProvider.StrictFilterEnabled) + { + foreach (var curse in _dataFileProvider.DataFiles[DataFiles.CurseFilter].Data.Values) + { + var index = input.IndexOf(curse, StringComparison.OrdinalIgnoreCase); + if (index >= 0) + { + if (_configurationProvider.CurseFilterEnabled) + { + input = input.Remove(index, curse.Length); + input = input.Insert(index, "****"); + } + else if (_configurationProvider.StrictFilterEnabled) + { + return (false, string.Empty); + } + } + } + } + + return (true, input); + } } public interface IChatProcessor @@ -68,5 +107,7 @@ public interface IChatProcessor string RemoveFirstCharacterIfNeeded(string input, ChatType chatType, string targetCharacter); string MakeDrunk(string input); + + (bool ShowChat, string FilteredMessage) FilterCurses(string input); } } diff --git a/EOLib/Domain/Chat/ChatRepository.cs b/EOLib/Domain/Chat/ChatRepository.cs index 3c8a7aefd..16e7508ae 100644 --- a/EOLib/Domain/Chat/ChatRepository.cs +++ b/EOLib/Domain/Chat/ChatRepository.cs @@ -29,6 +29,7 @@ public class ChatRepository : IChatRepository, IChatProvider, IResettable { private readonly IConfigurationProvider _configurationProvider; private readonly IChatLoggerProvider _chatLoggerProvider; + private readonly IChatProcessor _chatProcessor; public IReadOnlyDictionary> AllChat { get; private set; } @@ -47,10 +48,13 @@ IReadOnlyDictionary> IChatProvider.AllChat public string PMTarget2 { get; set; } public ChatRepository(IConfigurationProvider configurationProvider, - IChatLoggerProvider chatLoggerProvider) + IChatLoggerProvider chatLoggerProvider, + IChatProcessor chatProcessor) { _configurationProvider = configurationProvider; _chatLoggerProvider = chatLoggerProvider; + _chatProcessor = chatProcessor; + ResetState(); } @@ -58,7 +62,7 @@ public void ResetState() { var chat = new Dictionary>(); foreach (var tab in (ChatTab[]) Enum.GetValues(typeof(ChatTab))) - chat.Add(tab, new LoggingList(_configurationProvider, _chatLoggerProvider)); + chat.Add(tab, new LoggingList(_configurationProvider, _chatLoggerProvider, _chatProcessor)); AllChat = chat; } diff --git a/EOLib/Domain/Chat/LoggingList.cs b/EOLib/Domain/Chat/LoggingList.cs index 6eb50fe72..e90989019 100644 --- a/EOLib/Domain/Chat/LoggingList.cs +++ b/EOLib/Domain/Chat/LoggingList.cs @@ -1,4 +1,5 @@ using EOLib.Config; +using EOLib.Localization; using System; using System.Collections; using System.Collections.Generic; @@ -9,28 +10,41 @@ internal class LoggingList : IList, IReadOnlyList { private readonly IConfigurationProvider _configurationProvider; private readonly IChatLoggerProvider _chatLoggerProvider; + private readonly IChatProcessor _chatProcessor; private readonly List _l; public LoggingList(IConfigurationProvider configurationProvider, - IChatLoggerProvider chatLoggerProvider) + IChatLoggerProvider chatLoggerProvider, + IChatProcessor chatProcessor) { _configurationProvider = configurationProvider; _chatLoggerProvider = chatLoggerProvider; + _chatProcessor = chatProcessor; _l = new List(); } public LoggingList(IConfigurationProvider configurationProvider, IChatLoggerProvider chatLoggerProvider, + IChatProcessor chatProcessor, IList source) - : this(configurationProvider, chatLoggerProvider) + : this(configurationProvider, chatLoggerProvider, chatProcessor) { _l = new List(source); } public void Add(ChatData item) { + if (_configurationProvider.CurseFilterEnabled || _configurationProvider.StrictFilterEnabled) + { + var (ok, filtered) = _chatProcessor.FilterCurses(item.Message); + if (!ok) + return; + + item = item.WithMessage(filtered); + } + ((ICollection)_l).Add(item); if (_configurationProvider.LogChatToFile && item.Log) diff --git a/EndlessClient/Controllers/ChatController.cs b/EndlessClient/Controllers/ChatController.cs index 8d7534bdf..9469eceb2 100644 --- a/EndlessClient/Controllers/ChatController.cs +++ b/EndlessClient/Controllers/ChatController.cs @@ -3,10 +3,12 @@ using EndlessClient.ControlSets; using EndlessClient.Dialogs.Actions; using EndlessClient.GameExecution; +using EndlessClient.HUD; using EndlessClient.HUD.Chat; using EndlessClient.HUD.Controls; using EndlessClient.UIControls; using EOLib.Domain.Chat; +using EOLib.Localization; using EOLib.Net; using EOLib.Net.Communication; @@ -19,18 +21,21 @@ public class ChatController : IChatController private readonly IChatActions _chatActions; private readonly IPrivateMessageActions _privateMessageActions; private readonly IChatBubbleActions _chatBubbleActions; + private readonly IStatusLabelSetter _statusLabelSetter; private readonly IHudControlProvider _hudControlProvider; public ChatController(IChatTextBoxActions chatTextBoxActions, IChatActions chatActions, IPrivateMessageActions privateMessageActions, IChatBubbleActions chatBubbleActions, + IStatusLabelSetter statusLabelSetter, IHudControlProvider hudControlProvider) { _chatTextBoxActions = chatTextBoxActions; _chatActions = chatActions; _privateMessageActions = privateMessageActions; _chatBubbleActions = chatBubbleActions; + _statusLabelSetter = statusLabelSetter; _hudControlProvider = hudControlProvider; } @@ -39,11 +44,18 @@ public void SendChatAndClearTextBox() var localTypedText = ChatTextBox.Text; var targetCharacter = _privateMessageActions.GetTargetCharacter(localTypedText); - var updatedChat = _chatActions.SendChatToServer(localTypedText, targetCharacter); + var (ok, updatedChat) = _chatActions.SendChatToServer(localTypedText, targetCharacter); _chatTextBoxActions.ClearChatText(); - _chatBubbleActions.ShowChatBubbleForMainCharacter(updatedChat); + if (ok) + { + _chatBubbleActions.ShowChatBubbleForMainCharacter(updatedChat); + } + else + { + _statusLabelSetter.SetStatusLabel(EOResourceID.STATUS_LABEL_TYPE_WARNING, EOResourceID.YOUR_MIND_PREVENTS_YOU_TO_SAY); + } } public void SelectChatTextBox()