Created
July 20, 2025 20:30
-
-
Save StoneyEagle/a823ca929d5b7e347ffdb0004393c5cd to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| using System.Text; | |
| using NoMercyBot.Database.Models.ChatMessage; | |
| using NoMercyBot.Services.Emotes; | |
| using NoMercyBot.Services.Emotes.Dto; | |
| using NoMercyBot.Services.Other; | |
| namespace NoMercyBot.Services.Twitch; | |
| public class TwitchMessageDecorator | |
| { | |
| private readonly FrankerFacezService _frankerFacezService; | |
| private readonly BttvService _bttvService; | |
| private readonly SevenTvService _sevenTvService; | |
| private readonly HtmlMetadataService _htmlMetadataService; | |
| private ChatMessage ChatMessage { get; set; } | |
| private List<ChatMessageFragment> _fragments = []; | |
| public TwitchMessageDecorator( | |
| FrankerFacezService frankerFacezService, | |
| BttvService bttvService, | |
| SevenTvService sevenTvService, | |
| HtmlMetadataService htmlMetadataService) | |
| { | |
| _frankerFacezService = frankerFacezService; | |
| _bttvService = bttvService; | |
| _sevenTvService = sevenTvService; | |
| _htmlMetadataService = htmlMetadataService; | |
| ChatMessage = new(); | |
| } | |
| public void DecorateMessage(ChatMessage chatMessage) | |
| { | |
| ChatMessage = chatMessage; | |
| // Copy the fragments to a new list to avoid modifying the original message | |
| _fragments = chatMessage.Fragments.ToList(); | |
| // Split up all text fragments into individual word fragments so that we can decorate them with emotes | |
| ExplodeTextFragments(); | |
| DecorateTwitchEmotes(); | |
| DecorateFrankerFaceEmotes(); | |
| DecorateBttvEmotes(); | |
| DecorateSevenTvEmotes(); | |
| DecorateUrlFragments(); | |
| // Merge all consecutive text fragments back together to avoid having too many text fragments | |
| ImplodeTextFragments(); | |
| DecorateCodeSnippet(); | |
| // Overwrite the original fragments with the modified ones | |
| ChatMessage.Fragments = _fragments; | |
| } | |
| private void ExplodeTextFragments() | |
| { | |
| List<ChatMessageFragment> newFragments = []; | |
| foreach (ChatMessageFragment fragment in _fragments.ToList()) | |
| { | |
| int index = _fragments.IndexOf(fragment); | |
| if (fragment.Type != "text") | |
| { | |
| newFragments.Add(fragment); | |
| if (_fragments.ElementAtOrDefault(index - 0)?.Text != " ") | |
| { | |
| newFragments.Add(new() | |
| { | |
| Type = "text", | |
| Text = " ", | |
| }); | |
| } | |
| continue; | |
| } | |
| string[] words = fragment.Text.Split(' ', StringSplitOptions.RemoveEmptyEntries); | |
| foreach (string word in words) | |
| { | |
| if (newFragments.Count > 0 || _fragments.ElementAtOrDefault(index - 0)?.Text != " ") | |
| { | |
| newFragments.Add(new() | |
| { | |
| Type = "text", | |
| Text = " ", | |
| }); | |
| } | |
| newFragments.Add(new() | |
| { | |
| Type = "text", | |
| Text = string.IsNullOrEmpty(word) | |
| ? $"{word} " | |
| : word, | |
| }); | |
| if(newFragments.LastOrDefault()?.Text != " ") | |
| { | |
| newFragments.Add(new() | |
| { | |
| Type = "text", | |
| Text = " ", | |
| }); | |
| } | |
| } | |
| } | |
| if(newFragments.FirstOrDefault()?.Text == " ") | |
| { | |
| newFragments.RemoveAt(0); | |
| } | |
| if(newFragments.LastOrDefault()?.Text == " ") | |
| { | |
| newFragments.RemoveAt(newFragments.Count - 1); | |
| } | |
| _fragments = newFragments; | |
| } | |
| private void ImplodeTextFragments() | |
| { | |
| List<ChatMessageFragment> newFragments = []; | |
| StringBuilder currentText = new(); | |
| foreach (ChatMessageFragment fragment in _fragments.ToList()) | |
| { | |
| if (fragment.Type == "text") | |
| { | |
| currentText.Append(fragment.Text); | |
| } | |
| else | |
| { | |
| if (currentText.Length > 0) | |
| { | |
| newFragments.Add(new() | |
| { | |
| Type = "text", | |
| Text = currentText.ToString() | |
| }); | |
| currentText.Clear(); | |
| } | |
| newFragments.Add(fragment); | |
| } | |
| } | |
| if (currentText.Length > 0) | |
| { | |
| newFragments.Add(new() | |
| { | |
| Type = "text", | |
| Text = currentText.ToString() | |
| }); | |
| } | |
| if(newFragments.LastOrDefault()?.Text == " ") | |
| { | |
| newFragments.RemoveAt(newFragments.Count - 1); | |
| } | |
| _fragments = newFragments; | |
| } | |
| private void DecorateTwitchEmotes() | |
| { | |
| foreach (ChatMessageFragment fragment in _fragments.ToList()) | |
| { | |
| if (fragment.Type != "emote" || fragment.Emote is null) continue; | |
| int index = _fragments.IndexOf(fragment); | |
| _fragments[index] = new() | |
| { | |
| Type = fragment.Type, | |
| Text = fragment.Text, | |
| Cheermote = fragment.Cheermote, | |
| Mention = fragment.Mention, | |
| Emote = new() | |
| { | |
| Id = fragment.Emote.Id, | |
| Format = fragment.Emote.Format, | |
| OwnerId = fragment.Emote.OwnerId, | |
| EmoteSetId = fragment.Emote.EmoteSetId, | |
| Urls = new() | |
| { | |
| { "1", new($"https://static-cdn.jtvnw.net/emoticons/v2/{fragment.Emote.Id}/default/dark/1.0") }, | |
| { "2", new($"https://static-cdn.jtvnw.net/emoticons/v2/{fragment.Emote.Id}/default/dark/2.0") }, | |
| { "3", new($"https://static-cdn.jtvnw.net/emoticons/v2/{fragment.Emote.Id}/default/dark/3.0") } | |
| } | |
| } | |
| }; | |
| } | |
| } | |
| private void DecorateFrankerFaceEmotes() | |
| { | |
| foreach (ChatMessageFragment fragment in _fragments.ToList()) | |
| { | |
| if (fragment.Type != "text" || string.IsNullOrWhiteSpace(fragment.Text)) continue; | |
| Emoticon? emote = _frankerFacezService.FrankerFacezEmotes | |
| .FirstOrDefault(e => e.Name.Equals(fragment.Text, StringComparison.OrdinalIgnoreCase)); | |
| if (emote == null) continue; | |
| int index = _fragments.IndexOf(fragment); | |
| _fragments[index] = new() | |
| { | |
| Type = "emote", | |
| Text = fragment.Text, | |
| Emote = new() | |
| { | |
| Id = emote.Id.ToString(), | |
| Urls = emote.Urls, | |
| } | |
| }; | |
| } | |
| } | |
| private void DecorateBttvEmotes() | |
| { | |
| foreach (ChatMessageFragment fragment in _fragments.ToList()) | |
| { | |
| if (fragment.Type != "text" || string.IsNullOrWhiteSpace(fragment.Text)) continue; | |
| BttvEmote? emote = _bttvService.BttvEmotes | |
| .FirstOrDefault(e => e.Code.Equals(fragment.Text, StringComparison.OrdinalIgnoreCase)); | |
| if (emote == null) continue; | |
| int index = _fragments.IndexOf(fragment); | |
| _fragments[index] = new() | |
| { | |
| Type = "emote", | |
| Text = fragment.Text, | |
| Emote = new() | |
| { | |
| Id = emote.Id, | |
| Urls = new() | |
| { | |
| { "1", new($"https://cdn.betterttv.net/emote/{emote.Id}/1x") }, | |
| { "2", new($"https://cdn.betterttv.net/emote/{emote.Id}/2x") }, | |
| { "3", new($"https://cdn.betterttv.net/emote/{emote.Id}/3x") } | |
| } | |
| } | |
| }; | |
| } | |
| } | |
| private void DecorateSevenTvEmotes() | |
| { | |
| foreach (ChatMessageFragment fragment in _fragments.ToList()) | |
| { | |
| if (fragment.Type != "text" || string.IsNullOrWhiteSpace(fragment.Text)) continue; | |
| SevenTvEmote? emote = _sevenTvService.SevenTvEmotes | |
| .FirstOrDefault(e => e.Name.Equals(fragment.Text, StringComparison.OrdinalIgnoreCase)); | |
| if (emote == null) continue; | |
| int index = _fragments.IndexOf(fragment); | |
| _fragments[index] = new() | |
| { | |
| Type = "emote", | |
| Text = fragment.Text, | |
| Emote = new() | |
| { | |
| Id = emote.Id, | |
| Urls = new() | |
| { | |
| { "1", new($"https://7tv.app/emotes/{emote.Id}/1x") }, | |
| { "2", new($"https://7tv.app/emotes/{emote.Id}/2x") }, | |
| { "3", new($"https://7tv.app/emotes/{emote.Id}/3x") }, | |
| { "4", new($"https://7tv.app/emotes/{emote.Id}/4x") }, | |
| } | |
| } | |
| }; | |
| } | |
| } | |
| private void DecorateCodeSnippet() | |
| { | |
| throw new NotImplementedException(); | |
| } | |
| private void DecorateUrlFragments() | |
| { | |
| foreach (ChatMessageFragment fragment in _fragments.ToList()) | |
| { | |
| if (fragment.Type != "text" || string.IsNullOrWhiteSpace(fragment.Text)) continue; | |
| // Check if the text is a URL | |
| if (Uri.TryCreate(fragment.Text, UriKind.Absolute, out Uri? uri) && (uri.Scheme == Uri.UriSchemeHttp || uri.Scheme == Uri.UriSchemeHttps)) | |
| { | |
| _htmlMetadataService.Initialize(uri); | |
| _htmlMetadataService.DecorateOgData(); | |
| _htmlMetadataService.DecorateYoutube(); | |
| _htmlMetadataService.DecorateTwitch(); | |
| int index = _fragments.IndexOf(fragment); | |
| _fragments[index] = new() | |
| { | |
| Type = "url", | |
| Text = fragment.Text, | |
| HtmlPreviewCustomContent = _htmlMetadataService.MakeComponent(), | |
| }; | |
| } | |
| } | |
| } | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment