< 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:22
Coverable lines:153
Total lines:314
Line coverage:85.6% (131 of 153)
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%13130100%
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.25063.64%
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;
 8using DCL.ProfanityFiltering;
 9
 10public class ChatHUDController : IDisposable
 11{
 12    public const int MAX_CHAT_ENTRIES = 30;
 13    private const int TEMPORARILY_MUTE_MINUTES = 3;
 14    private const int MAX_CONTINUOUS_MESSAGES = 10;
 15    private const int MIN_MILLISECONDS_BETWEEN_MESSAGES = 1500;
 16    private const int MAX_HISTORY_ITERATION = 10;
 17
 18    public event Action OnInputFieldSelected;
 19    public event Action<ChatMessage> OnSendMessage;
 20    public event Action<string> OnMessageUpdated;
 21    public event Action<ChatMessage> OnMessageSentBlockedBySpam;
 22
 23    private readonly DataStore dataStore;
 24    private readonly IUserProfileBridge userProfileBridge;
 25    private readonly bool detectWhisper;
 26    private readonly IProfanityFilter profanityFilter;
 6627    private readonly Regex whisperRegex = new Regex(@"(?i)^\/(whisper|w) (\S+)( *)(.*)");
 6628    private readonly Dictionary<string, ulong> temporarilyMutedSenders = new Dictionary<string, ulong>();
 6629    private readonly List<ChatEntryModel> spamMessages = new List<ChatEntryModel>();
 6630    private readonly List<string> lastMessagesSent = new List<string>();
 31    private int currentHistoryIteration;
 32    private IChatHUDComponentView view;
 33
 6634    public ChatHUDController(DataStore dataStore,
 35        IUserProfileBridge userProfileBridge,
 36        bool detectWhisper,
 37        IProfanityFilter profanityFilter = null)
 38    {
 6639        this.dataStore = dataStore;
 6640        this.userProfileBridge = userProfileBridge;
 6641        this.detectWhisper = detectWhisper;
 6642        this.profanityFilter = profanityFilter;
 6643    }
 44
 45    public void Initialize(IChatHUDComponentView view)
 46    {
 6647        this.view = view;
 6648        this.view.OnPreviousChatInHistory -= FillInputWithPreviousMessage;
 6649        this.view.OnPreviousChatInHistory += FillInputWithPreviousMessage;
 6650        this.view.OnNextChatInHistory -= FillInputWithNextMessage;
 6651        this.view.OnNextChatInHistory += FillInputWithNextMessage;
 6652        this.view.OnShowMenu -= ContextMenu_OnShowMenu;
 6653        this.view.OnShowMenu += ContextMenu_OnShowMenu;
 6654        this.view.OnInputFieldSelected -= HandleInputFieldSelected;
 6655        this.view.OnInputFieldSelected += HandleInputFieldSelected;
 6656        this.view.OnInputFieldDeselected -= HandleInputFieldDeselected;
 6657        this.view.OnInputFieldDeselected += HandleInputFieldDeselected;
 6658        this.view.OnSendMessage -= HandleSendMessage;
 6659        this.view.OnSendMessage += HandleSendMessage;
 6660        this.view.OnMessageUpdated -= HandleMessageUpdated;
 6661        this.view.OnMessageUpdated += HandleMessageUpdated;
 6662    }
 63
 64    public void AddChatMessage(ChatMessage message, bool setScrollPositionToBottom = false, bool spamFiltering = true, b
 65    {
 1166        AddChatMessage(ChatMessageToChatEntry(message), setScrollPositionToBottom, spamFiltering, limitMaxEntries).Forge
 1167    }
 68
 69    public async UniTask AddChatMessage(ChatEntryModel chatEntryModel, bool setScrollPositionToBottom = false, bool spam
 70    {
 2371        if (IsSpamming(chatEntryModel.senderName) && spamFiltering) return;
 72
 2373        chatEntryModel.bodyText = ChatUtils.AddNoParse(chatEntryModel.bodyText);
 74
 2375        if (IsProfanityFilteringEnabled() && chatEntryModel.messageType != ChatMessage.Type.PRIVATE)
 76        {
 1077            chatEntryModel.bodyText = await profanityFilter.Filter(chatEntryModel.bodyText);
 78
 1079            if (!string.IsNullOrEmpty(chatEntryModel.senderName))
 1080                chatEntryModel.senderName = await profanityFilter.Filter(chatEntryModel.senderName);
 81
 1082            if (!string.IsNullOrEmpty(chatEntryModel.recipientName))
 283                chatEntryModel.recipientName = await profanityFilter.Filter(chatEntryModel.recipientName);
 84        }
 85
 2386        await UniTask.SwitchToMainThread();
 87
 2388        view.AddEntry(chatEntryModel, setScrollPositionToBottom);
 89
 2390        if (limitMaxEntries && view.EntryCount > MAX_CHAT_ENTRIES)
 191            view.RemoveOldestEntry();
 92
 3593        if (string.IsNullOrEmpty(chatEntryModel.senderId)) return;
 94
 1195        if (spamFiltering)
 1196            UpdateSpam(chatEntryModel);
 2397    }
 98
 99    public void Dispose()
 100    {
 27101        view.OnShowMenu -= ContextMenu_OnShowMenu;
 27102        view.OnMessageUpdated -= HandleMessageUpdated;
 27103        view.OnSendMessage -= HandleSendMessage;
 27104        view.OnInputFieldSelected -= HandleInputFieldSelected;
 27105        view.OnInputFieldDeselected -= HandleInputFieldDeselected;
 27106        view.OnPreviousChatInHistory -= FillInputWithPreviousMessage;
 27107        view.OnNextChatInHistory -= FillInputWithNextMessage;
 27108        OnSendMessage = null;
 27109        OnMessageUpdated = null;
 27110        OnInputFieldSelected = null;
 27111        view.Dispose();
 27112    }
 113
 27114    public void ClearAllEntries() => view.ClearAllEntries();
 115
 6116    public void ResetInputField(bool loseFocus = false) => view.ResetInputField(loseFocus);
 117
 19118    public void FocusInputField() => view.FocusInputField();
 119
 3120    public void SetInputFieldText(string setInputText) => view.SetInputFieldText(setInputText);
 121
 10122    public void UnfocusInputField() => view.UnfocusInputField();
 123
 124
 125    private ChatEntryModel ChatMessageToChatEntry(ChatMessage message)
 126    {
 11127        var model = new ChatEntryModel();
 11128        var ownProfile = userProfileBridge.GetOwn();
 129
 11130        model.messageId = message.messageId;
 11131        model.messageType = message.messageType;
 11132        model.bodyText = message.body;
 11133        model.timestamp = message.timestamp;
 11134        model.isChannelMessage = message.isChannelMessage;
 135
 11136        if (message.recipient != null)
 137        {
 4138            var recipientProfile = userProfileBridge.Get(message.recipient);
 4139            model.recipientName = recipientProfile != null ? recipientProfile.userName : message.recipient;
 140        }
 141
 11142        if (message.sender != null)
 143        {
 11144            var senderProfile = userProfileBridge.Get(message.sender);
 11145            model.senderName = senderProfile != null ? senderProfile.userName : message.sender;
 11146            model.senderId = message.sender;
 147        }
 148
 11149        if (message.messageType == ChatMessage.Type.PRIVATE)
 150        {
 5151            model.subType = message.sender == ownProfile.userId
 152                ? ChatEntryModel.SubType.SENT
 153                : ChatEntryModel.SubType.RECEIVED;
 154        }
 6155        else if (message.messageType == ChatMessage.Type.PUBLIC)
 156        {
 6157            model.subType = message.sender == ownProfile.userId
 158                ? ChatEntryModel.SubType.SENT
 159                : ChatEntryModel.SubType.RECEIVED;
 160        }
 161
 11162        return model;
 163    }
 164
 0165    private void ContextMenu_OnShowMenu() => view.OnMessageCancelHover();
 166
 167    private bool IsProfanityFilteringEnabled()
 168    {
 23169        return dataStore.settings.profanityChatFilteringEnabled.Get()
 170               && profanityFilter != null;
 171    }
 172
 0173    private void HandleMessageUpdated(string obj) => OnMessageUpdated?.Invoke(obj);
 174
 175    private void HandleSendMessage(ChatMessage message)
 176    {
 16177        var ownProfile = userProfileBridge.GetOwn();
 16178        message.sender = ownProfile.userId;
 179
 16180        RegisterMessageHistory(message);
 16181        currentHistoryIteration = 0;
 182
 16183        if (IsSpamming(message.sender) || IsSpamming(ownProfile.userName) && !string.IsNullOrEmpty(message.body))
 184        {
 0185            OnMessageSentBlockedBySpam?.Invoke(message);
 0186            return;
 187        }
 188
 16189        ApplyWhisperAttributes(message);
 190
 16191        if (message.body.ToLower().StartsWith("/join"))
 192        {
 1193            if (!ownProfile.isGuest)
 1194                dataStore.channels.channelJoinedSource.Set(ChannelJoinedSource.Command);
 195            else
 196            {
 0197                dataStore.HUDs.connectWalletModalVisible.Set(true);
 0198                return;
 199            }
 200        }
 201
 16202        OnSendMessage?.Invoke(message);
 8203    }
 204
 205    private void RegisterMessageHistory(ChatMessage message)
 206    {
 16207        if (string.IsNullOrEmpty(message.body)) return;
 208
 20209        lastMessagesSent.RemoveAll(s => s.Equals(message.body));
 16210        lastMessagesSent.Insert(0, message.body);
 211
 16212        if (lastMessagesSent.Count > MAX_HISTORY_ITERATION)
 0213            lastMessagesSent.RemoveAt(lastMessagesSent.Count - 1);
 16214    }
 215
 216    private void ApplyWhisperAttributes(ChatMessage message)
 217    {
 18218        if (!detectWhisper) return;
 14219        var body = message.body;
 14220        if (string.IsNullOrWhiteSpace(body)) return;
 221
 14222        var match = whisperRegex.Match(body);
 26223        if (!match.Success) return;
 224
 2225        message.messageType = ChatMessage.Type.PRIVATE;
 2226        message.recipient = match.Groups[2].Value;
 2227        message.body = match.Groups[4].Value;
 2228    }
 229
 230    private void HandleInputFieldSelected()
 231    {
 0232        currentHistoryIteration = 0;
 0233        OnInputFieldSelected?.Invoke();
 0234    }
 235
 0236    private void HandleInputFieldDeselected() => currentHistoryIteration = 0;
 237
 238    private bool IsSpamming(string senderName)
 239    {
 67240        if (string.IsNullOrEmpty(senderName)) return false;
 241
 43242        var isSpamming = false;
 243
 86244        if (!temporarilyMutedSenders.ContainsKey(senderName)) return false;
 245
 0246        var muteTimestamp = DateTimeOffset.FromUnixTimeMilliseconds((long) temporarilyMutedSenders[senderName]);
 0247        if ((DateTimeOffset.UtcNow - muteTimestamp).Minutes < TEMPORARILY_MUTE_MINUTES)
 0248            isSpamming = true;
 249        else
 0250            temporarilyMutedSenders.Remove(senderName);
 251
 0252        return isSpamming;
 253    }
 254
 255    private void UpdateSpam(ChatEntryModel model)
 256    {
 11257        if (spamMessages.Count == 0)
 258        {
 6259            spamMessages.Add(model);
 260        }
 5261        else if (spamMessages[spamMessages.Count - 1].senderName == model.senderName)
 262        {
 5263            if (MessagesSentTooFast(spamMessages[spamMessages.Count - 1].timestamp, model.timestamp))
 264            {
 5265                spamMessages.Add(model);
 266
 5267                if (spamMessages.Count >= MAX_CONTINUOUS_MESSAGES)
 268                {
 0269                    temporarilyMutedSenders.Add(model.senderName, model.timestamp);
 0270                    spamMessages.Clear();
 271                }
 272            }
 273            else
 274            {
 0275                spamMessages.Clear();
 276            }
 277        }
 278        else
 279        {
 0280            spamMessages.Clear();
 281        }
 5282    }
 283
 284    private bool MessagesSentTooFast(ulong oldMessageTimeStamp, ulong newMessageTimeStamp)
 285    {
 5286        var oldDateTime = DateTimeOffset.FromUnixTimeMilliseconds((long) oldMessageTimeStamp);
 5287        var newDateTime = DateTimeOffset.FromUnixTimeMilliseconds((long) newMessageTimeStamp);
 5288        return (newDateTime - oldDateTime).TotalMilliseconds < MIN_MILLISECONDS_BETWEEN_MESSAGES;
 289    }
 290
 291    private void FillInputWithNextMessage()
 292    {
 5293        if (lastMessagesSent.Count == 0) return;
 5294        view.FocusInputField();
 5295        view.SetInputFieldText(lastMessagesSent[currentHistoryIteration]);
 5296        currentHistoryIteration = (currentHistoryIteration + 1) % lastMessagesSent.Count;
 5297    }
 298
 299    private void FillInputWithPreviousMessage()
 300    {
 2301        if (lastMessagesSent.Count == 0)
 302        {
 0303            view.ResetInputField();
 0304            return;
 305        }
 306
 2307        currentHistoryIteration--;
 2308        if (currentHistoryIteration < 0)
 1309            currentHistoryIteration = lastMessagesSent.Count - 1;
 310
 2311        view.FocusInputField();
 2312        view.SetInputFieldText(lastMessagesSent[currentHistoryIteration]);
 2313    }
 314}