//Example IRC message: @badges=moderator/1,warcraft/alliance;color=;display-name=Swiftyspiffyv4;emotes=;mod=1;room-id=40876073;subscriber=0;turbo=0;user-id=103325214;user-type=mod :[email protected] PRIVMSG #swiftyspiffy :asd /// <summary>Constructor for ChatMessage object.</summary> /// <param name="botUsername">The username of the bot that received the message.</param> /// <param name="ircMessage">The IRC message from Twitch to be processed.</param> /// <param name="emoteCollection">The <see cref="MessageEmoteCollection"/> to register new emotes on and, if desired, use for emote replacement.</param> /// <param name="replaceEmotes">Whether to replace emotes for this chat message. Defaults to false.</param> public ChatMessage( string botUsername, IrcMessage ircMessage, ref MessageEmoteCollection emoteCollection, bool replaceEmotes = false) { BotUsername = botUsername; RawIrcMessage = ircMessage.ToString(); Message = ircMessage.Message; _emoteCollection = emoteCollection; Username = ircMessage.User; Channel = ircMessage.Channel; foreach (var tag in ircMessage.Tags.Keys) { var tagValue = ircMessage.Tags[tag]; switch (tag) { case Tags.Badges: Badges = Common.Helpers.ParseBadges(tagValue); // Iterate through saved badges for special circumstances foreach (var badge in Badges) { switch (badge.Key) { case "bits": CheerBadge = new CheerBadge(int.Parse(badge.Value)); break; case "subscriber": // Prioritize BadgeInfo subscribe count, as its more accurate if (SubscribedMonthCount == 0) { SubscribedMonthCount = int.Parse(badge.Value); } break; case "vip": IsVip = true; break; } } break; case Tags.BadgeInfo: BadgeInfo = Common.Helpers.ParseBadges(tagValue); // check if founder is one of them, and get months from that var founderBadge = BadgeInfo.Find(b => b.Key == "founder"); if (!founderBadge.Equals(default(KeyValuePair <string, string>))) { IsSubscriber = true; SubscribedMonthCount = int.Parse(founderBadge.Value); } else { var subBadge = BadgeInfo.Find(b => b.Key == "subscriber"); // BadgeInfo has better accuracy than Badges subscriber value if (!subBadge.Equals(default(KeyValuePair <string, string>))) { SubscribedMonthCount = int.Parse(subBadge.Value); } } break; case Tags.Bits: Bits = int.Parse(tagValue); BitsInDollars = ConvertBitsToUsd(Bits); break; case Tags.Color: ColorHex = tagValue; if (!string.IsNullOrWhiteSpace(ColorHex)) { Color = ColorTranslator.FromHtml(ColorHex); } break; case Tags.CustomRewardId: CustomRewardId = tagValue; break; case Tags.DisplayName: DisplayName = tagValue; break; case Tags.Emotes: EmoteSet = new EmoteSet(tagValue, Message); break; case Tags.Id: Id = tagValue; break; case Tags.MsgId: handleMsgId(tagValue); break; case Tags.Mod: IsModerator = Common.Helpers.ConvertToBool(tagValue); break; case Tags.Noisy: Noisy = Common.Helpers.ConvertToBool(tagValue) ? Noisy.True : Noisy.False; break; case Tags.RoomId: RoomId = tagValue; break; case Tags.Subscriber: // this check because when founder is set, the subscriber value is actually 0, which is problematic IsSubscriber = IsSubscriber == false?Common.Helpers.ConvertToBool(tagValue) : true; break; case Tags.TmiSentTs: TmiSentTs = tagValue; break; case Tags.Turbo: IsTurbo = Common.Helpers.ConvertToBool(tagValue); break; case Tags.UserId: UserId = tagValue; break; case Tags.UserType: switch (tagValue) { case "mod": UserType = UserType.Moderator; break; case "global_mod": UserType = UserType.GlobalModerator; break; case "admin": UserType = UserType.Admin; break; case "staff": UserType = UserType.Staff; break; default: UserType = UserType.Viewer; break; } break; } } if (Message.Length > 0 && (byte)Message[0] == 1 && (byte)Message[Message.Length - 1] == 1) { //Actions (/me {action}) are wrapped by byte=1 and prepended with "ACTION " //This setup clears all of that leaving just the action's text. //If you want to clear just the nonstandard bytes, use: //_message = _message.Substring(1, text.Length-2); if (Message.StartsWith("\u0001ACTION ") && Message.EndsWith("\u0001")) { Message = Message.Trim('\u0001').Substring(7); IsMe = true; } } //Parse the emoteSet if (EmoteSet != null && Message != null && EmoteSet.Emotes.Count > 0) { var uniqueEmotes = EmoteSet.RawEmoteSetString.Split('/'); foreach (var emote in uniqueEmotes) { var firstColon = emote.IndexOf(':'); var firstComma = emote.IndexOf(','); if (firstComma == -1) { firstComma = emote.Length; } var firstDash = emote.IndexOf('-'); if (firstColon > 0 && firstDash > firstColon && firstComma > firstDash) { if (int.TryParse(emote.Substring(firstColon + 1, firstDash - firstColon - 1), out var low) && int.TryParse(emote.Substring(firstDash + 1, firstComma - firstDash - 1), out var high)) { if (low >= 0 && low < high && high < Message.Length) { //Valid emote, let's parse var id = emote.Substring(0, firstColon); //Pull the emote text from the message var text = Message.Substring(low, high - low + 1); _emoteCollection.Add(new MessageEmote(id, text)); } } } } if (replaceEmotes) { EmoteReplacedMessage = _emoteCollection.ReplaceEmotes(Message); } } if (EmoteSet == null) { EmoteSet = new EmoteSet(default(string), Message); } // Check if display name was set, and if it wasn't, set it to username if (string.IsNullOrEmpty(DisplayName)) { DisplayName = Username; } // Check if message is from broadcaster if (string.Equals(Channel, Username, StringComparison.InvariantCultureIgnoreCase)) { UserType = UserType.Broadcaster; IsBroadcaster = true; } if (Channel.Split(':').Length == 3) { if (string.Equals(Channel.Split(':')[1], UserId, StringComparison.InvariantCultureIgnoreCase)) { UserType = UserType.Broadcaster; IsBroadcaster = true; } } }
//Example IRC message: @badges=moderator/1,warcraft/alliance;color=;display-name=Swiftyspiffyv4;emotes=;mod=1;room-id=40876073;subscriber=0;turbo=0;user-id=103325214;user-type=mod :[email protected] PRIVMSG #swiftyspiffy :asd /// <summary>Constructor for ChatMessage object.</summary> /// <param name="botUsername">The username of the bot that received the message.</param> /// <param name="ircString">The raw string received from Twitch to be processed.</param> /// <param name="emoteCollection">The <see cref="MessageEmoteCollection"/> to register new emotes on and, if desired, use for emote replacement.</param> /// <param name="replaceEmotes">Whether to replace emotes for this chat message. Defaults to false.</param> public ChatMessage(string botUsername, string ircString, ref MessageEmoteCollection emoteCollection, bool replaceEmotes = false) { BotUsername = botUsername; RawIrcMessage = ircString; _emoteCollection = emoteCollection; Username = ircString.Split('!')[1].Split('@')[0]; Channel = ircString.Split(new[] { " #" }, StringSplitOptions.None)[1].Split(' ')[0]; var userTypeStr = ircString.Split(new[] { ";user-type=" }, StringSplitOptions.None)[1].Split(' ')[0]; switch (userTypeStr) { case "mod": UserType = Enums.UserType.Moderator; break; case "global_mod": UserType = Enums.UserType.GlobalModerator; break; case "admin": UserType = Enums.UserType.Admin; break; case "staff": UserType = Enums.UserType.Staff; break; default: UserType = Enums.UserType.Viewer; break; } var parts = ircString.Split(new[] { $":{Username}!{Username}@{Username}.tmi.twitch.tv" }, StringSplitOptions.None)[0].Split(';'); foreach (var part in parts) { if (part.Contains("@badges=")) { Badges = new List <KeyValuePair <string, string> >(); var badges = part.Split('=')[1]; if (badges.Contains('/')) { if (!badges.Contains(",")) { Badges.Add(new KeyValuePair <string, string>(badges.Split('/')[0], badges.Split('/')[1])); } else { foreach (var badge in badges.Split(',')) { Badges.Add(new KeyValuePair <string, string>(badge.Split('/')[0], badge.Split('/')[1])); } } } // Iterate through saved badges for special circumstances foreach (var badge in Badges) { switch (badge.Key) { case "bits": CheerBadge = new CheerBadge(int.Parse(badge.Value)); break; case "subscriber": SubscribedMonthCount = int.Parse(badge.Value); break; } } } else if (part.Contains("bits=")) { Bits = int.Parse(part.Split('=')[1]); BitsInDollars = ConvertBitsToUsd(Bits); } else if (part.Contains("color=")) { if (ColorHex == null) { ColorHex = part.Split('=')[1]; } if (!string.IsNullOrEmpty(ColorHex)) { Color = ColorTranslator.FromHtml(ColorHex); } } else if (part.Contains("display-name")) { if (DisplayName == null) { DisplayName = part.Split('=')[1]; } } else if (part.Contains("emotes=")) { if (_emoteSetStorage == null) { _emoteSetStorage = part.Split('=')[1]; } } else if (part.Contains("subscriber=")) { IsSubscriber = part.Split('=')[1] == "1"; } else if (part.Contains("turbo=")) { IsTurbo = part.Split('=')[1] == "1"; } else if (part.Contains("user-id=")) { UserId = part.Split('=')[1]; } else if (part.Contains("mod=")) { IsModerator = part.Split('=')[1] == "1"; } else if (part.Contains("room-id=")) { RoomId = part.Split('=')[1]; } else if (part.Contains("noisy=")) { Noisy = part.Split('=')[1] == "1" ? Enums.Noisy.True : Enums.Noisy.False; } } Message = ircString.Split(new[] { $" PRIVMSG #{Channel} :" }, 2, StringSplitOptions.None)[1]; EmoteSet = new EmoteSet(_emoteSetStorage, Message); if (Message.Length > 0 && (byte)Message[0] == 1 && (byte)Message[Message.Length - 1] == 1) { //Actions (/me {action}) are wrapped by byte=1 and prepended with "ACTION " //This setup clears all of that leaving just the action's text. //If you want to clear just the nonstandard bytes, use: //_message = _message.Substring(1, text.Length-2); if (Message.StartsWith("\u0001ACTION ") && Message.EndsWith("\u0001")) { Message = Message.Trim('\u0001').Substring(7); IsMe = true; } } //Parse the emoteSet if (EmoteSet != null && Message != null && EmoteSet.Emotes.Count > 0) { var uniqueEmotes = EmoteSet.RawEmoteSetString.Split('/'); string id, text; int firstColon, firstComma, firstDash, low, high; foreach (var emote in uniqueEmotes) { firstColon = emote.IndexOf(':'); firstComma = emote.IndexOf(','); if (firstComma == -1) { firstComma = emote.Length; } firstDash = emote.IndexOf('-'); if (firstColon > 0 && firstDash > firstColon && firstComma > firstDash) { if (int.TryParse(emote.Substring(firstColon + 1, firstDash - firstColon - 1), out low) && int.TryParse(emote.Substring(firstDash + 1, firstComma - firstDash - 1), out high)) { if (low >= 0 && low < high && high < Message.Length) { //Valid emote, let's parse id = emote.Substring(0, firstColon); //Pull the emote text from the message text = Message.Substring(low, high - low + 1); _emoteCollection.Add(new MessageEmote(id, text)); } } } } if (replaceEmotes) { EmoteReplacedMessage = _emoteCollection.ReplaceEmotes(Message); } } // Check if display name was set, and if it wasn't, set it to username if (string.IsNullOrEmpty(DisplayName)) { DisplayName = Username; } // Check if message is from broadcaster if (string.Equals(Channel, Username, StringComparison.InvariantCultureIgnoreCase)) { UserType = Enums.UserType.Broadcaster; IsBroadcaster = true; } if (Channel.Split(':').Length == 3) { if (string.Equals(Channel.Split(':')[1], UserId, StringComparison.InvariantCultureIgnoreCase)) { UserType = Enums.UserType.Broadcaster; IsBroadcaster = true; } } if (string.Equals(Channel, Username, StringComparison.InvariantCultureIgnoreCase)) { UserType = Enums.UserType.Broadcaster; IsBroadcaster = true; } }