public static TwitchBadge?AsTwitchBadge(this IChatBadge badge) { return(badge as TwitchBadge); }
/// <summary> /// Takes a raw Twitch message and parses it into an IChatMessage /// </summary> /// <param name="rawMessage">The raw message sent from Twitch</param> /// <param name="loggedInUser"></param> /// <param name="parsedMessages">A list of chat messages that were parsed from the rawMessage</param> /// <param name="channelInfo"></param> /// <returns>True if parsedMessages.Count > 0</returns> public bool ParseRawMessage(string rawMessage, ConcurrentDictionary <string, IChatChannel> channelInfo, IChatUser loggedInUser, out IChatMessage[] parsedMessages) { var stopwatch = Stopwatch.StartNew(); parsedMessages = null !; var matches = _twitchMessageRegex.Matches(rawMessage); if (matches.Count == 0) { _logger.LogInformation($"Unhandled message: {rawMessage}"); stopwatch.Stop(); return(false); } var messages = new List <IChatMessage>(); _logger.LogInformation($"Parsing message {rawMessage}"); foreach (Match match in matches) { if (!match.Groups["MessageType"].Success) { _logger.LogInformation($"Failed to get messageType for message {match.Value}"); continue; } //_logger.LogInformation($"Message: {match.Value}"); var messageType = match.Groups["MessageType"].Value; var messageText = match.Groups["Message"].Success ? match.Groups["Message"].Value : ""; var messageChannelName = match.Groups["ChannelName"].Success ? match.Groups["ChannelName"].Value.Trim('#') : ""; var messageRoomId = string.Empty; if (channelInfo.TryGetValue(messageChannelName, out var channel)) { messageRoomId = channel.AsTwitchChannel() !.Roomstate?.RoomId; } try { var userBadges = new IChatBadge[0]; var messageEmotes = new List <IChatEmote>(); TwitchRoomstate messageRoomstate = null !; var foundTwitchEmotes = new HashSet <string>(); var isActionMessage = false; var isHighlighted = false; if (messageText.StartsWith("\u0001ACTION")) { messageText = messageText.Remove(messageText.Length - 1, 1).Remove(0, 8); isActionMessage = true; } var messageMeta = new ReadOnlyDictionary <string, string>(_tagRegex.Matches(match.Value).Cast <Match>().Aggregate(new Dictionary <string, string>(), (dict, m) => { dict[m.Groups["Tag"].Value] = m.Groups["Value"].Value; return(dict); })); var messageBits = messageMeta.TryGetValue("bits", out var bitsString) && int.TryParse(bitsString, out var bitsInt) ? bitsInt : 0; if (messageMeta.TryGetValue("badges", out var badgeStr)) { userBadges = badgeStr.Split(',').Aggregate(new List <IChatBadge>(), (list, m) => { var badgeId = m.Replace("/", ""); if (_twitchDataProvider.TryGetBadgeInfo(badgeId, messageRoomId, out var badgeInfo)) { list.Add(new TwitchBadge { Id = $"{badgeInfo.Type}_{badgeId}", Name = m.Split('/')[0], Uri = badgeInfo.Uri }); } return(list); }).ToArray(); } if (messageType == "PRIVMSG" || messageType == "NOTIFY" || messageType == "USERNOTICE") { if (messageText.Length > 0) { if (_settings.ParseTwitchEmotes && messageMeta.TryGetValue("emotes", out var emoteStr)) { // Parse all the normal Twitch emotes messageEmotes = emoteStr.Split('/').Aggregate(new List <IChatEmote>(), (emoteList, emoteInstanceString) => { var emoteParts = emoteInstanceString.Split(':'); foreach (var instanceString in emoteParts[1].Split(',')) { var instanceParts = instanceString.Split('-'); var startIndex = int.Parse(instanceParts[0]); var endIndex = int.Parse(instanceParts[1]); if (startIndex >= messageText.Length) { _logger.LogWarning($"Start index is greater than message length! RawMessage: {match.Value}, InstanceString: {instanceString}, EmoteStr: {emoteStr}, StartIndex: {startIndex}, MessageLength: {messageText.Length}, IsActionMessage: {isActionMessage}"); } var emoteName = messageText.Substring(startIndex, endIndex - startIndex + 1); foundTwitchEmotes.Add(emoteName); emoteList.Add(new TwitchEmote { Id = $"TwitchEmote_{emoteParts[0]}", Name = emoteName,//endIndex >= messageText.Length ? messageText.Substring(startIndex) : , Uri = $"https://static-cdn.jtvnw.net/emoticons/v1/{emoteParts[0]}/3.0", StartIndex = startIndex, EndIndex = endIndex, IsAnimated = false, Bits = 0, Color = "" }); } return(emoteList); }); } // Parse all the third party (BTTV, FFZ, etc) emotes var currentWord = new StringBuilder(); for (var i = 0; i <= messageText.Length; i++) { if (i == messageText.Length || char.IsWhiteSpace(messageText[i])) { if (currentWord.Length > 0) { var lastWord = currentWord.ToString(); var startIndex = i - lastWord.Length; var endIndex = i - 1; if (!foundTwitchEmotes.Contains(lastWord)) { // Make sure we haven't already matched a Twitch emote with the same string, just incase the user has a BTTV/FFZ emote with the same name if (_settings.ParseCheermotes && messageBits > 0 && _twitchDataProvider.TryGetCheermote(lastWord, messageRoomId, out var cheermoteData, out var numBits) && numBits > 0) { //_logger.LogInformation($"Got cheermote! Total message bits: {messageBits}"); var tier = cheermoteData.GetTier(numBits); if (tier != null) { messageEmotes.Add(new TwitchEmote { Id = $"TwitchCheermote_{cheermoteData.Prefix}{tier.MinBits}", Name = lastWord, Uri = tier.Uri, StartIndex = startIndex, EndIndex = endIndex, IsAnimated = true, Bits = numBits, Color = tier.Color }); } } else if (_twitchDataProvider.TryGetThirdPartyEmote(lastWord, messageChannelName, out var emoteData)) { if (emoteData.Type.StartsWith("BTTV") && _settings.ParseBTTVEmotes || emoteData.Type.StartsWith("FFZ") && _settings.ParseFFZEmotes) { messageEmotes.Add(new TwitchEmote { Id = $"{emoteData.Type}_{lastWord}", Name = lastWord, Uri = emoteData.Uri, StartIndex = startIndex, EndIndex = endIndex, IsAnimated = emoteData.IsAnimated, Bits = 0, Color = string.Empty }); } } } currentWord.Clear(); } } else { currentWord.Append(messageText[i]); } } if (_settings.ParseEmojis) { // Parse all emojis messageEmotes.AddRange(_emojiParser.FindEmojis(messageText)); } // Sort the emotes in descending order to make replacing them in the string later on easier messageEmotes.Sort((a, b) => b.StartIndex - a.StartIndex); } }