< 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:26
Coverable lines:157
Total lines:313
Line coverage:83.4% (131 of 157)
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%
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.RemoveOldestEntry();
 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
 18117    public void FocusInputField() => view.FocusInputField();
 118
 3119    public void SetInputFieldText(string setInputText) => view.SetInputFieldText(setInputText);
 120
 12121    public void UnfocusInputField() => view.UnfocusInputField();
 122
 123
 124    private ChatEntryModel ChatMessageToChatEntry(ChatMessage message)
 125    {
 4126        var model = new ChatEntryModel();
 4127        var ownProfile = userProfileBridge.GetOwn();
 128
 4129        model.messageId = message.messageId;
 4130        model.messageType = message.messageType;
 4131        model.bodyText = message.body;
 4132        model.timestamp = message.timestamp;
 4133        model.isChannelMessage = message.isChannelMessage;
 134
 4135        if (message.recipient != null)
 136        {
 0137            var recipientProfile = userProfileBridge.Get(message.recipient);
 0138            model.recipientName = recipientProfile != null ? recipientProfile.userName : message.recipient;
 139        }
 140
 4141        if (message.sender != null)
 142        {
 4143            var senderProfile = userProfileBridge.Get(message.sender);
 4144            model.senderName = senderProfile != null ? senderProfile.userName : message.sender;
 4145            model.senderId = message.sender;
 146        }
 147
 4148        if (message.messageType == ChatMessage.Type.PRIVATE)
 149        {
 3150            model.subType = message.sender == ownProfile.userId
 151                ? ChatEntryModel.SubType.SENT
 152                : ChatEntryModel.SubType.RECEIVED;
 3153        }
 1154        else if (message.messageType == ChatMessage.Type.PUBLIC)
 155        {
 1156            model.subType = message.sender == ownProfile.userId
 157                ? ChatEntryModel.SubType.SENT
 158                : ChatEntryModel.SubType.RECEIVED;
 159        }
 160
 4161        return model;
 162    }
 163
 0164    private void ContextMenu_OnShowMenu() => view.OnMessageCancelHover();
 165
 166    private bool IsProfanityFilteringEnabled()
 167    {
 16168        return dataStore.settings.profanityChatFilteringEnabled.Get()
 169               && profanityFilter != null;
 170    }
 171
 0172    private void HandleMessageUpdated(string obj) => OnMessageUpdated?.Invoke(obj);
 173
 174    private void HandleSendMessage(ChatMessage message)
 175    {
 17176        var ownProfile = userProfileBridge.GetOwn();
 17177        message.sender = ownProfile.userId;
 178
 17179        RegisterMessageHistory(message);
 17180        currentHistoryIteration = 0;
 181
 17182        if (IsSpamming(message.sender) || IsSpamming(ownProfile.userName) && !string.IsNullOrEmpty(message.body))
 183        {
 0184            OnMessageSentBlockedBySpam?.Invoke(message);
 0185            return;
 186        }
 187
 17188        ApplyWhisperAttributes(message);
 189
 17190        if (message.body.ToLower().StartsWith("/join"))
 191        {
 1192            if (!ownProfile.isGuest)
 1193                dataStore.channels.channelJoinedSource.Set(ChannelJoinedSource.Command);
 194            else
 195            {
 0196                dataStore.HUDs.connectWalletModalVisible.Set(true);
 0197                return;
 198            }
 199        }
 200
 17201        OnSendMessage?.Invoke(message);
 9202    }
 203
 204    private void RegisterMessageHistory(ChatMessage message)
 205    {
 17206        if (string.IsNullOrEmpty(message.body)) return;
 207
 21208        lastMessagesSent.RemoveAll(s => s.Equals(message.body));
 17209        lastMessagesSent.Insert(0, message.body);
 210
 17211        if (lastMessagesSent.Count > MAX_HISTORY_ITERATION)
 0212            lastMessagesSent.RemoveAt(lastMessagesSent.Count - 1);
 17213    }
 214
 215    private void ApplyWhisperAttributes(ChatMessage message)
 216    {
 20217        if (!detectWhisper) return;
 14218        var body = message.body;
 14219        if (string.IsNullOrWhiteSpace(body)) return;
 220
 14221        var match = whisperRegex.Match(body);
 26222        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    {
 0231        currentHistoryIteration = 0;
 0232        OnInputFieldSelected?.Invoke();
 0233    }
 234
 0235    private void HandleInputFieldDeselected() => currentHistoryIteration = 0;
 236
 237    private bool IsSpamming(string senderName)
 238    {
 62239        if (string.IsNullOrEmpty(senderName)) return false;
 240
 38241        var isSpamming = false;
 242
 76243        if (!temporarilyMutedSenders.ContainsKey(senderName)) return false;
 244
 0245        var muteTimestamp = DateTimeOffset.FromUnixTimeMilliseconds((long) temporarilyMutedSenders[senderName]);
 0246        if ((DateTimeOffset.UtcNow - muteTimestamp).Minutes < TEMPORARILY_MUTE_MINUTES)
 0247            isSpamming = true;
 248        else
 0249            temporarilyMutedSenders.Remove(senderName);
 250
 0251        return isSpamming;
 252    }
 253
 254    private void UpdateSpam(ChatEntryModel model)
 255    {
 4256        if (spamMessages.Count == 0)
 257        {
 2258            spamMessages.Add(model);
 2259        }
 2260        else if (spamMessages[spamMessages.Count - 1].senderName == model.senderName)
 261        {
 2262            if (MessagesSentTooFast(spamMessages[spamMessages.Count - 1].timestamp, model.timestamp))
 263            {
 2264                spamMessages.Add(model);
 265
 2266                if (spamMessages.Count >= MAX_CONTINUOUS_MESSAGES)
 267                {
 0268                    temporarilyMutedSenders.Add(model.senderName, model.timestamp);
 0269                    spamMessages.Clear();
 270                }
 0271            }
 272            else
 273            {
 0274                spamMessages.Clear();
 275            }
 0276        }
 277        else
 278        {
 0279            spamMessages.Clear();
 280        }
 2281    }
 282
 283    private bool MessagesSentTooFast(ulong oldMessageTimeStamp, ulong newMessageTimeStamp)
 284    {
 2285        var oldDateTime = DateTimeOffset.FromUnixTimeMilliseconds((long) oldMessageTimeStamp);
 2286        var newDateTime = DateTimeOffset.FromUnixTimeMilliseconds((long) newMessageTimeStamp);
 2287        return (newDateTime - oldDateTime).TotalMilliseconds < MIN_MILLISECONDS_BETWEEN_MESSAGES;
 288    }
 289
 290    private void FillInputWithNextMessage()
 291    {
 5292        if (lastMessagesSent.Count == 0) return;
 5293        view.FocusInputField();
 5294        view.SetInputFieldText(lastMessagesSent[currentHistoryIteration]);
 5295        currentHistoryIteration = (currentHistoryIteration + 1) % lastMessagesSent.Count;
 5296    }
 297
 298    private void FillInputWithPreviousMessage()
 299    {
 2300        if (lastMessagesSent.Count == 0)
 301        {
 0302            view.ResetInputField();
 0303            return;
 304        }
 305
 2306        currentHistoryIteration--;
 2307        if (currentHistoryIteration < 0)
 1308            currentHistoryIteration = lastMessagesSent.Count - 1;
 309
 2310        view.FocusInputField();
 2311        view.SetInputFieldText(lastMessagesSent[currentHistoryIteration]);
 2312    }
 313}