< Summary

Class:ChatHUDController
Assembly:ChatHUD
File(s):/tmp/workspace/unity-renderer/unity-renderer/Assets/Scripts/MainScripts/DCL/Controllers/HUD/ChatWidgetHUD/ChatHUDController.cs
Covered lines:149
Uncovered lines:15
Coverable lines:164
Total lines:321
Line coverage:90.8% (149 of 164)
Covered branches:0
Total branches:0

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity NPath complexity Sequence coverage
ChatHUDController(...)0%110100%
Initialize(...)0%110100%
AddChatMessage(...)0%110100%
AddChatMessage()0%28.9917065.38%
Dispose()0%110100%
ClearAllEntries()0%110100%
ResetInputField(...)0%110100%
FocusInputField()0%110100%
SetInputFieldText(...)0%110100%
UnfocusInputField()0%110100%
ActivatePreview()0%110100%
DeactivatePreview()0%110100%
FadeOutMessages()0%110100%
ChatMessageToChatEntry(...)0%13130100%
ContextMenu_OnShowMenu()0%2100%
IsProfanityFilteringEnabled()0%220100%
HandleMessageUpdated(...)0%220100%
HandleSendMessage(...)0%4.14081.82%
RegisterMessageHistory(...)0%3.033085.71%
ApplyWhisperAttributes(...)0%440100%
HandleInputFieldSelected()0%220100%
HandleInputFieldDeselected()0%220100%
IsSpamming(...)0%64050%
UpdateSpam(...)0%6.975057.14%
MessagesSentTooFast(...)0%110100%
CreateBaseDateTime()0%110100%
FillInputWithNextMessage()0%2.022083.33%
FillInputWithPreviousMessage()0%3.13077.78%

File(s)

/tmp/workspace/unity-renderer/unity-renderer/Assets/Scripts/MainScripts/DCL/Controllers/HUD/ChatWidgetHUD/ChatHUDController.cs

#LineLine coverage
 1using System;
 2using System.Collections.Generic;
 3using System.Linq;
 4using System.Text.RegularExpressions;
 5using Cysharp.Threading.Tasks;
 6using DCL;
 7using DCL.Interface;
 8
 9public class ChatHUDController : IDisposable
 10{
 11    public const int MAX_CHAT_ENTRIES = 30;
 12    private const int TEMPORARILY_MUTE_MINUTES = 10;
 13    private const int MAX_CONTINUOUS_MESSAGES = 6;
 14    private const int MIN_MILLISECONDS_BETWEEN_MESSAGES = 1500;
 15    private const int MAX_HISTORY_ITERATION = 10;
 16
 17    public event Action OnInputFieldSelected;
 18    public event Action OnInputFieldDeselected;
 19    public event Action<ChatMessage> OnSendMessage;
 20    public event Action<string> OnMessageUpdated;
 21
 22    private readonly DataStore dataStore;
 23    private readonly IUserProfileBridge userProfileBridge;
 24    private readonly bool detectWhisper;
 25    private readonly IProfanityFilter profanityFilter;
 6526    private readonly Regex whisperRegex = new Regex(@"(?i)^\/(whisper|w) (\S+)( *)(.*)");
 6527    private readonly Dictionary<string, ulong> temporarilyMutedSenders = new Dictionary<string, ulong>();
 6528    private readonly List<ChatEntryModel> spamMessages = new List<ChatEntryModel>();
 6529    private readonly List<string> lastMessagesSent = new List<string>();
 30    private int currentHistoryIteration;
 31    private IChatHUDComponentView view;
 32
 6533    public ChatHUDController(DataStore dataStore,
 34        IUserProfileBridge userProfileBridge,
 35        bool detectWhisper,
 36        IProfanityFilter profanityFilter = null)
 37    {
 6538        this.dataStore = dataStore;
 6539        this.userProfileBridge = userProfileBridge;
 6540        this.detectWhisper = detectWhisper;
 6541        this.profanityFilter = profanityFilter;
 6542    }
 43
 144    public bool IsInputSelected => view.IsInputFieldSelected;
 45
 46    public void Initialize(IChatHUDComponentView view)
 47    {
 6548        this.view = view;
 6549        this.view.OnPreviousChatInHistory -= FillInputWithPreviousMessage;
 6550        this.view.OnPreviousChatInHistory += FillInputWithPreviousMessage;
 6551        this.view.OnNextChatInHistory -= FillInputWithNextMessage;
 6552        this.view.OnNextChatInHistory += FillInputWithNextMessage;
 6553        this.view.OnShowMenu -= ContextMenu_OnShowMenu;
 6554        this.view.OnShowMenu += ContextMenu_OnShowMenu;
 6555        this.view.OnInputFieldSelected -= HandleInputFieldSelected;
 6556        this.view.OnInputFieldSelected += HandleInputFieldSelected;
 6557        this.view.OnInputFieldDeselected -= HandleInputFieldDeselected;
 6558        this.view.OnInputFieldDeselected += HandleInputFieldDeselected;
 6559        this.view.OnSendMessage -= HandleSendMessage;
 6560        this.view.OnSendMessage += HandleSendMessage;
 6561        this.view.OnMessageUpdated -= HandleMessageUpdated;
 6562        this.view.OnMessageUpdated += HandleMessageUpdated;
 6563    }
 64
 65    public void AddChatMessage(ChatMessage message, bool setScrollPositionToBottom = false, bool spamFiltering = true)
 66    {
 2567        AddChatMessage(ChatMessageToChatEntry(message), setScrollPositionToBottom, spamFiltering).Forget();
 2568    }
 69
 70    public async UniTask AddChatMessage(ChatEntryModel chatEntryModel, bool setScrollPositionToBottom = false, bool spam
 71    {
 3772        if (IsSpamming(chatEntryModel.senderName) && spamFiltering) return;
 73
 3774        chatEntryModel.bodyText = ChatUtils.AddNoParse(chatEntryModel.bodyText);
 75
 3776        if (IsProfanityFilteringEnabled() && chatEntryModel.messageType != ChatMessage.Type.PRIVATE)
 77        {
 1078            chatEntryModel.bodyText = await profanityFilter.Filter(chatEntryModel.bodyText);
 79
 1080            if (!string.IsNullOrEmpty(chatEntryModel.senderName))
 1081                chatEntryModel.senderName = await profanityFilter.Filter(chatEntryModel.senderName);
 82
 1083            if (!string.IsNullOrEmpty(chatEntryModel.recipientName))
 284                chatEntryModel.recipientName = await profanityFilter.Filter(chatEntryModel.recipientName);
 85        }
 86
 3787        await UniTask.SwitchToMainThread();
 88
 3789        view.AddEntry(chatEntryModel, setScrollPositionToBottom);
 90
 3791        if (view.EntryCount > MAX_CHAT_ENTRIES)
 192            view.RemoveFirstEntry();
 93
 4994        if (string.IsNullOrEmpty(chatEntryModel.senderId)) return;
 95
 2596        if (spamFiltering)
 697            UpdateSpam(chatEntryModel);
 3798    }
 99
 100    public void Dispose()
 101    {
 29102        view.OnShowMenu -= ContextMenu_OnShowMenu;
 29103        view.OnMessageUpdated -= HandleMessageUpdated;
 29104        view.OnSendMessage -= HandleSendMessage;
 29105        view.OnInputFieldSelected -= HandleInputFieldSelected;
 29106        view.OnInputFieldDeselected -= HandleInputFieldDeselected;
 29107        view.OnPreviousChatInHistory -= FillInputWithPreviousMessage;
 29108        view.OnNextChatInHistory -= FillInputWithNextMessage;
 29109        OnSendMessage = null;
 29110        OnMessageUpdated = null;
 29111        OnInputFieldSelected = null;
 29112        view.Dispose();
 29113    }
 114
 18115    public void ClearAllEntries() => view.ClearAllEntries();
 116
 6117    public void ResetInputField(bool loseFocus = false) => view.ResetInputField(loseFocus);
 118
 13119    public void FocusInputField() => view.FocusInputField();
 120
 4121    public void SetInputFieldText(string setInputText) => view.SetInputFieldText(setInputText);
 122
 8123    public void UnfocusInputField() => view.UnfocusInputField();
 124
 12125    public void ActivatePreview() => view.ActivatePreview();
 126
 7127    public void DeactivatePreview() => view.DeactivatePreview();
 128
 5129    public void FadeOutMessages() => view.FadeOutMessages();
 130
 131
 132    private ChatEntryModel ChatMessageToChatEntry(ChatMessage message)
 133    {
 25134        var model = new ChatEntryModel();
 25135        var ownProfile = userProfileBridge.GetOwn();
 136
 25137        model.messageType = message.messageType;
 25138        model.bodyText = message.body;
 25139        model.timestamp = message.timestamp;
 140
 25141        if (message.recipient != null)
 142        {
 21143            var recipientProfile = userProfileBridge.Get(message.recipient);
 21144            model.recipientName = recipientProfile != null ? recipientProfile.userName : message.recipient;
 145        }
 146
 25147        if (message.sender != null)
 148        {
 25149            var senderProfile = userProfileBridge.Get(message.sender);
 25150            model.senderName = senderProfile != null ? senderProfile.userName : message.sender;
 25151            model.senderId = message.sender;
 152        }
 153
 25154        if (message.messageType == ChatMessage.Type.PRIVATE)
 155        {
 24156            if (message.recipient == ownProfile.userId)
 157            {
 1158                model.subType = ChatEntryModel.SubType.RECEIVED;
 1159                model.otherUserId = message.sender;
 1160            }
 23161            else if (message.sender == ownProfile.userId)
 162            {
 1163                model.subType = ChatEntryModel.SubType.SENT;
 1164                model.otherUserId = message.recipient;
 1165            }
 166            else
 167            {
 22168                model.subType = ChatEntryModel.SubType.NONE;
 169            }
 22170        }
 1171        else if (message.messageType == ChatMessage.Type.PUBLIC)
 172        {
 1173            model.subType = message.sender == ownProfile.userId
 174                ? ChatEntryModel.SubType.SENT
 175                : ChatEntryModel.SubType.RECEIVED;
 176        }
 177
 25178        return model;
 179    }
 180
 0181    private void ContextMenu_OnShowMenu() => view.OnMessageCancelHover();
 182
 183    private bool IsProfanityFilteringEnabled()
 184    {
 37185        return dataStore.settings.profanityChatFilteringEnabled.Get()
 186               && profanityFilter != null;
 187    }
 188
 1189    private void HandleMessageUpdated(string obj) => OnMessageUpdated?.Invoke(obj);
 190
 191    private void HandleSendMessage(ChatMessage message)
 192    {
 15193        var ownProfile = userProfileBridge.GetOwn();
 15194        message.sender = ownProfile.userId;
 15195        RegisterMessageHistory(message);
 15196        currentHistoryIteration = 0;
 15197        if (IsSpamming(message.sender)) return;
 15198        if (IsSpamming(ownProfile.userName)) return;
 15199        ApplyWhisperAttributes(message);
 15200        OnSendMessage?.Invoke(message);
 8201    }
 202
 203    private void RegisterMessageHistory(ChatMessage message)
 204    {
 16205        if (string.IsNullOrEmpty(message.body)) return;
 206
 18207        lastMessagesSent.RemoveAll(s => s.Equals(message.body));
 14208        lastMessagesSent.Insert(0, message.body);
 209
 14210        if (lastMessagesSent.Count > MAX_HISTORY_ITERATION)
 0211            lastMessagesSent.RemoveAt(lastMessagesSent.Count - 1);
 14212    }
 213
 214    private void ApplyWhisperAttributes(ChatMessage message)
 215    {
 16216        if (!detectWhisper) return;
 14217        var body = message.body;
 15218        if (string.IsNullOrWhiteSpace(body)) return;
 219
 13220        var match = whisperRegex.Match(body);
 24221        if (!match.Success) return;
 222
 2223        message.messageType = ChatMessage.Type.PRIVATE;
 2224        message.recipient = match.Groups[2].Value;
 2225        message.body = match.Groups[4].Value;
 2226    }
 227
 228    private void HandleInputFieldSelected()
 229    {
 2230        currentHistoryIteration = 0;
 2231        OnInputFieldSelected?.Invoke();
 2232    }
 233
 234    private void HandleInputFieldDeselected()
 235    {
 2236        currentHistoryIteration = 0;
 2237        OnInputFieldDeselected?.Invoke();
 2238    }
 239
 240    private bool IsSpamming(string senderName)
 241    {
 78242        if (string.IsNullOrEmpty(senderName)) return false;
 243
 56244        var isSpamming = false;
 245
 112246        if (!temporarilyMutedSenders.ContainsKey(senderName)) return false;
 247
 0248        var muteTimestamp = CreateBaseDateTime().AddMilliseconds(temporarilyMutedSenders[senderName]).ToLocalTime();
 0249        if ((DateTime.Now - muteTimestamp).Minutes < TEMPORARILY_MUTE_MINUTES)
 0250            isSpamming = true;
 251        else
 0252            temporarilyMutedSenders.Remove(senderName);
 253
 0254        return isSpamming;
 255    }
 256
 257    private void UpdateSpam(ChatEntryModel model)
 258    {
 6259        if (spamMessages.Count == 0)
 260        {
 4261            spamMessages.Add(model);
 4262        }
 2263        else if (spamMessages[spamMessages.Count - 1].senderName == model.senderName)
 264        {
 2265            if (MessagesSentTooFast(spamMessages[spamMessages.Count - 1].timestamp, model.timestamp))
 266            {
 2267                spamMessages.Add(model);
 268
 2269                if (spamMessages.Count == MAX_CONTINUOUS_MESSAGES)
 270                {
 0271                    temporarilyMutedSenders.Add(model.senderName, model.timestamp);
 0272                    spamMessages.Clear();
 273                }
 0274            }
 275            else
 276            {
 0277                spamMessages.Clear();
 278            }
 0279        }
 280        else
 281        {
 0282            spamMessages.Clear();
 283        }
 2284    }
 285
 286    private bool MessagesSentTooFast(ulong oldMessageTimeStamp, ulong newMessageTimeStamp)
 287    {
 2288        DateTime oldDateTime = CreateBaseDateTime().AddMilliseconds(oldMessageTimeStamp).ToLocalTime();
 2289        DateTime newDateTime = CreateBaseDateTime().AddMilliseconds(newMessageTimeStamp).ToLocalTime();
 2290        return (newDateTime - oldDateTime).TotalMilliseconds < MIN_MILLISECONDS_BETWEEN_MESSAGES;
 291    }
 292
 293    private DateTime CreateBaseDateTime()
 294    {
 4295        return new DateTime(1970, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc);
 296    }
 297
 298    private void FillInputWithNextMessage()
 299    {
 5300        if (lastMessagesSent.Count == 0) return;
 5301        view.FocusInputField();
 5302        view.SetInputFieldText(lastMessagesSent[currentHistoryIteration]);
 5303        currentHistoryIteration = (currentHistoryIteration + 1) % lastMessagesSent.Count;
 5304    }
 305
 306    private void FillInputWithPreviousMessage()
 307    {
 2308        if (lastMessagesSent.Count == 0)
 309        {
 0310            view.ResetInputField();
 0311            return;
 312        }
 313
 2314        currentHistoryIteration--;
 2315        if (currentHistoryIteration < 0)
 1316            currentHistoryIteration = lastMessagesSent.Count - 1;
 317
 2318        view.FocusInputField();
 2319        view.SetInputFieldText(lastMessagesSent[currentHistoryIteration]);
 2320    }
 321}