< Summary

Class:ChatHUDController
Assembly:ChatHUD
File(s):/tmp/workspace/unity-renderer/unity-renderer/Assets/Scripts/MainScripts/DCL/Controllers/HUD/ChatWidgetHUD/ChatHUDController.cs
Covered lines:131
Uncovered lines:27
Coverable lines:158
Total lines:315
Line coverage:82.9% (131 of 158)
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%
FadeOutMessages()0%2100%
ChatMessageToChatEntry(...)0%13.1713090%
ContextMenu_OnShowMenu()0%2100%
IsProfanityFilteringEnabled()0%220100%
HandleMessageUpdated(...)0%6200%
HandleSendMessage(...)0%9.218073.33%
RegisterMessageHistory(...)0%3.213071.43%
ApplyWhisperAttributes(...)0%4.014091.67%
HandleInputFieldSelected()0%6200%
HandleInputFieldDeselected()0%2100%
IsSpamming(...)0%64050%
UpdateSpam(...)0%6.975057.14%
MessagesSentTooFast(...)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 = 3;
 13    private const int MAX_CONTINUOUS_MESSAGES = 10;
 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<ChatMessage> OnSendMessage;
 19    public event Action<string> OnMessageUpdated;
 20    public event Action<ChatMessage> OnMessageSentBlockedBySpam;
 21
 22    private readonly DataStore dataStore;
 23    private readonly IUserProfileBridge userProfileBridge;
 24    private readonly bool detectWhisper;
 25    private readonly IProfanityFilter profanityFilter;
 6426    private readonly Regex whisperRegex = new Regex(@"(?i)^\/(whisper|w) (\S+)( *)(.*)");
 6427    private readonly Dictionary<string, ulong> temporarilyMutedSenders = new Dictionary<string, ulong>();
 6428    private readonly List<ChatEntryModel> spamMessages = new List<ChatEntryModel>();
 6429    private readonly List<string> lastMessagesSent = new List<string>();
 30    private int currentHistoryIteration;
 31    private IChatHUDComponentView view;
 32
 6433    public ChatHUDController(DataStore dataStore,
 34        IUserProfileBridge userProfileBridge,
 35        bool detectWhisper,
 36        IProfanityFilter profanityFilter = null)
 37    {
 6438        this.dataStore = dataStore;
 6439        this.userProfileBridge = userProfileBridge;
 6440        this.detectWhisper = detectWhisper;
 6441        this.profanityFilter = profanityFilter;
 6442    }
 43
 44    public void Initialize(IChatHUDComponentView view)
 45    {
 6446        this.view = view;
 6447        this.view.OnPreviousChatInHistory -= FillInputWithPreviousMessage;
 6448        this.view.OnPreviousChatInHistory += FillInputWithPreviousMessage;
 6449        this.view.OnNextChatInHistory -= FillInputWithNextMessage;
 6450        this.view.OnNextChatInHistory += FillInputWithNextMessage;
 6451        this.view.OnShowMenu -= ContextMenu_OnShowMenu;
 6452        this.view.OnShowMenu += ContextMenu_OnShowMenu;
 6453        this.view.OnInputFieldSelected -= HandleInputFieldSelected;
 6454        this.view.OnInputFieldSelected += HandleInputFieldSelected;
 6455        this.view.OnInputFieldDeselected -= HandleInputFieldDeselected;
 6456        this.view.OnInputFieldDeselected += HandleInputFieldDeselected;
 6457        this.view.OnSendMessage -= HandleSendMessage;
 6458        this.view.OnSendMessage += HandleSendMessage;
 6459        this.view.OnMessageUpdated -= HandleMessageUpdated;
 6460        this.view.OnMessageUpdated += HandleMessageUpdated;
 6461    }
 62
 63    public void AddChatMessage(ChatMessage message, bool setScrollPositionToBottom = false, bool spamFiltering = true, b
 64    {
 465        AddChatMessage(ChatMessageToChatEntry(message), setScrollPositionToBottom, spamFiltering, limitMaxEntries).Forge
 466    }
 67
 68    public async UniTask AddChatMessage(ChatEntryModel chatEntryModel, bool setScrollPositionToBottom = false, bool spam
 69    {
 1670        if (IsSpamming(chatEntryModel.senderName) && spamFiltering) return;
 71
 1672        chatEntryModel.bodyText = ChatUtils.AddNoParse(chatEntryModel.bodyText);
 73
 1674        if (IsProfanityFilteringEnabled() && chatEntryModel.messageType != ChatMessage.Type.PRIVATE)
 75        {
 1076            chatEntryModel.bodyText = await profanityFilter.Filter(chatEntryModel.bodyText);
 77
 1078            if (!string.IsNullOrEmpty(chatEntryModel.senderName))
 1079                chatEntryModel.senderName = await profanityFilter.Filter(chatEntryModel.senderName);
 80
 1081            if (!string.IsNullOrEmpty(chatEntryModel.recipientName))
 282                chatEntryModel.recipientName = await profanityFilter.Filter(chatEntryModel.recipientName);
 83        }
 84
 1685        await UniTask.SwitchToMainThread();
 86
 1687        view.AddEntry(chatEntryModel, setScrollPositionToBottom);
 88
 1689        if (limitMaxEntries && view.EntryCount > MAX_CHAT_ENTRIES)
 190            view.RemoveFirstEntry();
 91
 2892        if (string.IsNullOrEmpty(chatEntryModel.senderId)) return;
 93
 494        if (spamFiltering)
 495            UpdateSpam(chatEntryModel);
 1696    }
 97
 98    public void Dispose()
 99    {
 27100        view.OnShowMenu -= ContextMenu_OnShowMenu;
 27101        view.OnMessageUpdated -= HandleMessageUpdated;
 27102        view.OnSendMessage -= HandleSendMessage;
 27103        view.OnInputFieldSelected -= HandleInputFieldSelected;
 27104        view.OnInputFieldDeselected -= HandleInputFieldDeselected;
 27105        view.OnPreviousChatInHistory -= FillInputWithPreviousMessage;
 27106        view.OnNextChatInHistory -= FillInputWithNextMessage;
 27107        OnSendMessage = null;
 27108        OnMessageUpdated = null;
 27109        OnInputFieldSelected = null;
 27110        view.Dispose();
 27111    }
 112
 24113    public void ClearAllEntries() => view.ClearAllEntries();
 114
 7115    public void ResetInputField(bool loseFocus = false) => view.ResetInputField(loseFocus);
 116
 19117    public void FocusInputField() => view.FocusInputField();
 118
 3119    public void SetInputFieldText(string setInputText) => view.SetInputFieldText(setInputText);
 120
 12121    public void UnfocusInputField() => view.UnfocusInputField();
 122
 0123    public void FadeOutMessages() => view.FadeOutMessages();
 124
 125
 126    private ChatEntryModel ChatMessageToChatEntry(ChatMessage message)
 127    {
 4128        var model = new ChatEntryModel();
 4129        var ownProfile = userProfileBridge.GetOwn();
 130
 4131        model.messageId = message.messageId;
 4132        model.messageType = message.messageType;
 4133        model.bodyText = message.body;
 4134        model.timestamp = message.timestamp;
 4135        model.isChannelMessage = message.isChannelMessage;
 136
 4137        if (message.recipient != null)
 138        {
 0139            var recipientProfile = userProfileBridge.Get(message.recipient);
 0140            model.recipientName = recipientProfile != null ? recipientProfile.userName : message.recipient;
 141        }
 142
 4143        if (message.sender != null)
 144        {
 4145            var senderProfile = userProfileBridge.Get(message.sender);
 4146            model.senderName = senderProfile != null ? senderProfile.userName : message.sender;
 4147            model.senderId = message.sender;
 148        }
 149
 4150        if (message.messageType == ChatMessage.Type.PRIVATE)
 151        {
 3152            model.subType = message.sender == ownProfile.userId
 153                ? ChatEntryModel.SubType.SENT
 154                : ChatEntryModel.SubType.RECEIVED;
 3155        }
 1156        else if (message.messageType == ChatMessage.Type.PUBLIC)
 157        {
 1158            model.subType = message.sender == ownProfile.userId
 159                ? ChatEntryModel.SubType.SENT
 160                : ChatEntryModel.SubType.RECEIVED;
 161        }
 162
 4163        return model;
 164    }
 165
 0166    private void ContextMenu_OnShowMenu() => view.OnMessageCancelHover();
 167
 168    private bool IsProfanityFilteringEnabled()
 169    {
 16170        return dataStore.settings.profanityChatFilteringEnabled.Get()
 171               && profanityFilter != null;
 172    }
 173
 0174    private void HandleMessageUpdated(string obj) => OnMessageUpdated?.Invoke(obj);
 175
 176    private void HandleSendMessage(ChatMessage message)
 177    {
 17178        var ownProfile = userProfileBridge.GetOwn();
 17179        message.sender = ownProfile.userId;
 180
 17181        RegisterMessageHistory(message);
 17182        currentHistoryIteration = 0;
 183
 17184        if (IsSpamming(message.sender) || IsSpamming(ownProfile.userName) && !string.IsNullOrEmpty(message.body))
 185        {
 0186            OnMessageSentBlockedBySpam?.Invoke(message);
 0187            return;
 188        }
 189
 17190        ApplyWhisperAttributes(message);
 191
 17192        if (message.body.ToLower().StartsWith("/join"))
 193        {
 1194            if (!ownProfile.isGuest)
 1195                dataStore.channels.channelJoinedSource.Set(ChannelJoinedSource.Command);
 196            else
 197            {
 0198                dataStore.HUDs.connectWalletModalVisible.Set(true);
 0199                return;
 200            }
 201        }
 202
 17203        OnSendMessage?.Invoke(message);
 9204    }
 205
 206    private void RegisterMessageHistory(ChatMessage message)
 207    {
 17208        if (string.IsNullOrEmpty(message.body)) return;
 209
 21210        lastMessagesSent.RemoveAll(s => s.Equals(message.body));
 17211        lastMessagesSent.Insert(0, message.body);
 212
 17213        if (lastMessagesSent.Count > MAX_HISTORY_ITERATION)
 0214            lastMessagesSent.RemoveAt(lastMessagesSent.Count - 1);
 17215    }
 216
 217    private void ApplyWhisperAttributes(ChatMessage message)
 218    {
 20219        if (!detectWhisper) return;
 14220        var body = message.body;
 14221        if (string.IsNullOrWhiteSpace(body)) return;
 222
 14223        var match = whisperRegex.Match(body);
 26224        if (!match.Success) return;
 225
 2226        message.messageType = ChatMessage.Type.PRIVATE;
 2227        message.recipient = match.Groups[2].Value;
 2228        message.body = match.Groups[4].Value;
 2229    }
 230
 231    private void HandleInputFieldSelected()
 232    {
 0233        currentHistoryIteration = 0;
 0234        OnInputFieldSelected?.Invoke();
 0235    }
 236
 0237    private void HandleInputFieldDeselected() => currentHistoryIteration = 0;
 238
 239    private bool IsSpamming(string senderName)
 240    {
 62241        if (string.IsNullOrEmpty(senderName)) return false;
 242
 38243        var isSpamming = false;
 244
 76245        if (!temporarilyMutedSenders.ContainsKey(senderName)) return false;
 246
 0247        var muteTimestamp = DateTimeOffset.FromUnixTimeMilliseconds((long) temporarilyMutedSenders[senderName]);
 0248        if ((DateTimeOffset.UtcNow - muteTimestamp).Minutes < TEMPORARILY_MUTE_MINUTES)
 0249            isSpamming = true;
 250        else
 0251            temporarilyMutedSenders.Remove(senderName);
 252
 0253        return isSpamming;
 254    }
 255
 256    private void UpdateSpam(ChatEntryModel model)
 257    {
 4258        if (spamMessages.Count == 0)
 259        {
 2260            spamMessages.Add(model);
 2261        }
 2262        else if (spamMessages[spamMessages.Count - 1].senderName == model.senderName)
 263        {
 2264            if (MessagesSentTooFast(spamMessages[spamMessages.Count - 1].timestamp, model.timestamp))
 265            {
 2266                spamMessages.Add(model);
 267
 2268                if (spamMessages.Count >= MAX_CONTINUOUS_MESSAGES)
 269                {
 0270                    temporarilyMutedSenders.Add(model.senderName, model.timestamp);
 0271                    spamMessages.Clear();
 272                }
 0273            }
 274            else
 275            {
 0276                spamMessages.Clear();
 277            }
 0278        }
 279        else
 280        {
 0281            spamMessages.Clear();
 282        }
 2283    }
 284
 285    private bool MessagesSentTooFast(ulong oldMessageTimeStamp, ulong newMessageTimeStamp)
 286    {
 2287        var oldDateTime = DateTimeOffset.FromUnixTimeMilliseconds((long) oldMessageTimeStamp);
 2288        var newDateTime = DateTimeOffset.FromUnixTimeMilliseconds((long) newMessageTimeStamp);
 2289        return (newDateTime - oldDateTime).TotalMilliseconds < MIN_MILLISECONDS_BETWEEN_MESSAGES;
 290    }
 291
 292    private void FillInputWithNextMessage()
 293    {
 5294        if (lastMessagesSent.Count == 0) return;
 5295        view.FocusInputField();
 5296        view.SetInputFieldText(lastMessagesSent[currentHistoryIteration]);
 5297        currentHistoryIteration = (currentHistoryIteration + 1) % lastMessagesSent.Count;
 5298    }
 299
 300    private void FillInputWithPreviousMessage()
 301    {
 2302        if (lastMessagesSent.Count == 0)
 303        {
 0304            view.ResetInputField();
 0305            return;
 306        }
 307
 2308        currentHistoryIteration--;
 2309        if (currentHistoryIteration < 0)
 1310            currentHistoryIteration = lastMessagesSent.Count - 1;
 311
 2312        view.FocusInputField();
 2313        view.SetInputFieldText(lastMessagesSent[currentHistoryIteration]);
 2314    }
 315}