< Summary

Class:ChatHUDController
Assembly:ChatHUD
File(s):/tmp/workspace/unity-renderer/unity-renderer/Assets/Scripts/MainScripts/DCL/Controllers/HUD/ChatWidgetHUD/ChatHUDController.cs
Covered lines:150
Uncovered lines:15
Coverable lines:165
Total lines:322
Line coverage:90.9% (150 of 165)
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%31.4418065.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 Cysharp.Threading.Tasks;
 2using DCL;
 3using DCL.Interface;
 4using System;
 5using System.Collections.Generic;
 6using System.Text.RegularExpressions;
 7using DCL.Chat;
 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, b
 66    {
 667        AddChatMessage(ChatMessageToChatEntry(message), setScrollPositionToBottom, spamFiltering, limitMaxEntries).Forge
 668    }
 69
 70    public async UniTask AddChatMessage(ChatEntryModel chatEntryModel, bool setScrollPositionToBottom = false, bool spam
 71    {
 1872        if (IsSpamming(chatEntryModel.senderName) && spamFiltering) return;
 73
 1874        chatEntryModel.bodyText = ChatUtils.AddNoParse(chatEntryModel.bodyText);
 75
 1876        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
 1887        await UniTask.SwitchToMainThread();
 88
 1889        view.AddEntry(chatEntryModel, setScrollPositionToBottom);
 90
 1891        if (limitMaxEntries && view.EntryCount > MAX_CHAT_ENTRIES)
 192            view.RemoveFirstEntry();
 93
 3094        if (string.IsNullOrEmpty(chatEntryModel.senderId)) return;
 95
 696        if (spamFiltering)
 697            UpdateSpam(chatEntryModel);
 1898    }
 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
 17115    public void ClearAllEntries() => view.ClearAllEntries();
 116
 6117    public void ResetInputField(bool loseFocus = false) => view.ResetInputField(loseFocus);
 118
 16119    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    {
 6134        var model = new ChatEntryModel();
 6135        var ownProfile = userProfileBridge.GetOwn();
 136
 6137        model.messageId = message.messageId;
 6138        model.messageType = message.messageType;
 6139        model.bodyText = message.body;
 6140        model.timestamp = message.timestamp;
 141
 6142        if (message.recipient != null)
 143        {
 2144            var recipientProfile = userProfileBridge.Get(message.recipient);
 2145            model.recipientName = recipientProfile != null ? recipientProfile.userName : message.recipient;
 146        }
 147
 6148        if (message.sender != null)
 149        {
 6150            var senderProfile = userProfileBridge.Get(message.sender);
 6151            model.senderName = senderProfile != null ? senderProfile.userName : message.sender;
 6152            model.senderId = message.sender;
 153        }
 154
 6155        if (message.messageType == ChatMessage.Type.PRIVATE)
 156        {
 5157            if (message.recipient == ownProfile.userId)
 158            {
 1159                model.subType = ChatEntryModel.SubType.RECEIVED;
 1160                model.otherUserId = message.sender;
 1161            }
 4162            else if (message.sender == ownProfile.userId)
 163            {
 1164                model.subType = ChatEntryModel.SubType.SENT;
 1165                model.otherUserId = message.recipient;
 1166            }
 167            else
 168            {
 3169                model.subType = ChatEntryModel.SubType.NONE;
 170            }
 3171        }
 1172        else if (message.messageType == ChatMessage.Type.PUBLIC)
 173        {
 1174            model.subType = message.sender == ownProfile.userId
 175                ? ChatEntryModel.SubType.SENT
 176                : ChatEntryModel.SubType.RECEIVED;
 177        }
 178
 6179        return model;
 180    }
 181
 0182    private void ContextMenu_OnShowMenu() => view.OnMessageCancelHover();
 183
 184    private bool IsProfanityFilteringEnabled()
 185    {
 18186        return dataStore.settings.profanityChatFilteringEnabled.Get()
 187               && profanityFilter != null;
 188    }
 189
 1190    private void HandleMessageUpdated(string obj) => OnMessageUpdated?.Invoke(obj);
 191
 192    private void HandleSendMessage(ChatMessage message)
 193    {
 15194        var ownProfile = userProfileBridge.GetOwn();
 15195        message.sender = ownProfile.userId;
 15196        RegisterMessageHistory(message);
 15197        currentHistoryIteration = 0;
 15198        if (IsSpamming(message.sender)) return;
 15199        if (IsSpamming(ownProfile.userName)) return;
 15200        ApplyWhisperAttributes(message);
 15201        OnSendMessage?.Invoke(message);
 8202    }
 203
 204    private void RegisterMessageHistory(ChatMessage message)
 205    {
 16206        if (string.IsNullOrEmpty(message.body)) return;
 207
 18208        lastMessagesSent.RemoveAll(s => s.Equals(message.body));
 14209        lastMessagesSent.Insert(0, message.body);
 210
 14211        if (lastMessagesSent.Count > MAX_HISTORY_ITERATION)
 0212            lastMessagesSent.RemoveAt(lastMessagesSent.Count - 1);
 14213    }
 214
 215    private void ApplyWhisperAttributes(ChatMessage message)
 216    {
 16217        if (!detectWhisper) return;
 14218        var body = message.body;
 15219        if (string.IsNullOrWhiteSpace(body)) return;
 220
 13221        var match = whisperRegex.Match(body);
 24222        if (!match.Success) return;
 223
 2224        message.messageType = ChatMessage.Type.PRIVATE;
 2225        message.recipient = match.Groups[2].Value;
 2226        message.body = match.Groups[4].Value;
 2227    }
 228
 229    private void HandleInputFieldSelected()
 230    {
 2231        currentHistoryIteration = 0;
 2232        OnInputFieldSelected?.Invoke();
 2233    }
 234
 235    private void HandleInputFieldDeselected()
 236    {
 2237        currentHistoryIteration = 0;
 2238        OnInputFieldDeselected?.Invoke();
 2239    }
 240
 241    private bool IsSpamming(string senderName)
 242    {
 59243        if (string.IsNullOrEmpty(senderName)) return false;
 244
 37245        var isSpamming = false;
 246
 74247        if (!temporarilyMutedSenders.ContainsKey(senderName)) return false;
 248
 0249        var muteTimestamp = CreateBaseDateTime().AddMilliseconds(temporarilyMutedSenders[senderName]).ToLocalTime();
 0250        if ((DateTime.Now - muteTimestamp).Minutes < TEMPORARILY_MUTE_MINUTES)
 0251            isSpamming = true;
 252        else
 0253            temporarilyMutedSenders.Remove(senderName);
 254
 0255        return isSpamming;
 256    }
 257
 258    private void UpdateSpam(ChatEntryModel model)
 259    {
 6260        if (spamMessages.Count == 0)
 261        {
 4262            spamMessages.Add(model);
 4263        }
 2264        else if (spamMessages[spamMessages.Count - 1].senderName == model.senderName)
 265        {
 2266            if (MessagesSentTooFast(spamMessages[spamMessages.Count - 1].timestamp, model.timestamp))
 267            {
 2268                spamMessages.Add(model);
 269
 2270                if (spamMessages.Count == MAX_CONTINUOUS_MESSAGES)
 271                {
 0272                    temporarilyMutedSenders.Add(model.senderName, model.timestamp);
 0273                    spamMessages.Clear();
 274                }
 0275            }
 276            else
 277            {
 0278                spamMessages.Clear();
 279            }
 0280        }
 281        else
 282        {
 0283            spamMessages.Clear();
 284        }
 2285    }
 286
 287    private bool MessagesSentTooFast(ulong oldMessageTimeStamp, ulong newMessageTimeStamp)
 288    {
 2289        DateTime oldDateTime = CreateBaseDateTime().AddMilliseconds(oldMessageTimeStamp).ToLocalTime();
 2290        DateTime newDateTime = CreateBaseDateTime().AddMilliseconds(newMessageTimeStamp).ToLocalTime();
 2291        return (newDateTime - oldDateTime).TotalMilliseconds < MIN_MILLISECONDS_BETWEEN_MESSAGES;
 292    }
 293
 294    private DateTime CreateBaseDateTime()
 295    {
 4296        return new DateTime(1970, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc);
 297    }
 298
 299    private void FillInputWithNextMessage()
 300    {
 5301        if (lastMessagesSent.Count == 0) return;
 5302        view.FocusInputField();
 5303        view.SetInputFieldText(lastMessagesSent[currentHistoryIteration]);
 5304        currentHistoryIteration = (currentHistoryIteration + 1) % lastMessagesSent.Count;
 5305    }
 306
 307    private void FillInputWithPreviousMessage()
 308    {
 2309        if (lastMessagesSent.Count == 0)
 310        {
 0311            view.ResetInputField();
 0312            return;
 313        }
 314
 2315        currentHistoryIteration--;
 2316        if (currentHistoryIteration < 0)
 1317            currentHistoryIteration = lastMessagesSent.Count - 1;
 318
 2319        view.FocusInputField();
 2320        view.SetInputFieldText(lastMessagesSent[currentHistoryIteration]);
 2321    }
 322}