diff --git a/TwitchDownloaderCore.Tests/ToolTests/HighlightIconsTests.cs b/TwitchDownloaderCore.Tests/ToolTests/HighlightIconsTests.cs index 1cd6f80b..95ddaf9b 100644 --- a/TwitchDownloaderCore.Tests/ToolTests/HighlightIconsTests.cs +++ b/TwitchDownloaderCore.Tests/ToolTests/HighlightIconsTests.cs @@ -90,6 +90,7 @@ private static Comment CreateCommentWithMessage(string viewerDisplayName, string [InlineData( "{\"body\":\"viewer8 is gifting 5 Tier 1 Subs to streamer8's community! They've gifted a total of 349 in the channel! \",\"bits_spent\":0,\"fragments\":[{\"text\":\"viewer8 is gifting 5 Tier 1 Subs to streamer8's community! They've gifted a total of 349 in the channel! \",\"emoticon\":null}],\"user_badges\":[{\"_id\":\"subscriber\",\"version\":\"6\"},{\"_id\":\"bits\",\"version\":\"50000\"}],\"user_color\":\"#DAA520\",\"emoticons\":[]}", HighlightType.GiftedMany)] + // +Special case in separate method. // GiftedSingle [InlineData( "{\"body\":\"viewer8 gifted a Tier 1 sub to viewer9! \",\"bits_spent\":0,\"fragments\":[{\"text\":\"viewer8 gifted a Tier 1 sub to viewer9! \",\"emoticon\":null}],\"user_badges\":[{\"_id\":\"subscriber\",\"version\":\"6\"},{\"_id\":\"bits\",\"version\":\"50000\"}],\"user_color\":\"#DAA520\",\"emoticons\":[]}", @@ -153,17 +154,35 @@ public void CorrectlyIdentifiesHighlightTypes(string messageString, HighlightTyp Assert.Equal(expectedType, actualType); } - [Fact] - public void CorrectlyIdentifiesAnonymousGiftSub() + [Theory] + [InlineData("{\"display_name\":\"Twitch\",\"_id\":\"12826\",\"name\":\"twitch\",\"bio\":\"Twitch is where thousands of communities come together for whatever, every day. \",\"created_at\":\"2007-05-22T10:39:54.238271Z\",\"updated_at\":\"2024-09-22T22:28:39.594659Z\",\"logo\":\"https://static-cdn.jtvnw.net/jtv_user_pictures/aa88230d-7af5-4053-a7cd-889e626d3382-profile_image-300x300.png\"}", + "{\"body\":\"We added 13 Gift Subs AND 10 Bonus Gift Subs to viewer8's gift! \",\"bits_spent\":0,\"fragments\":[{\"text\":\"We added 13 Gift Subs AND 10 Bonus Gift Subs to viewer8's gift! \",\"emoticon\":null}],\"user_badges\":[],\"user_color\":null,\"emoticons\":[]}")] + [InlineData("{\"display_name\":\"Twitch\",\"_id\":\"12826\",\"name\":\"twitch\",\"bio\":\"Twitch is where thousands of communities come together for whatever, every day. \",\"created_at\":\"2007-05-22T10:39:54.238271Z\",\"updated_at\":\"2024-09-22T22:28:39.594659Z\",\"logo\":\"https://static-cdn.jtvnw.net/jtv_user_pictures/aa88230d-7af5-4053-a7cd-889e626d3382-profile_image-300x300.png\"}", + "{\"body\":\"We added 1 Gift Subs to viewer8's gift! \",\"bits_spent\":0,\"fragments\":[{\"text\":\"We added 1 Gift Subs to viewer8's gift! \",\"emoticon\":null}],\"user_badges\":[],\"user_color\":null,\"emoticons\":[]}")] + public void CorrectlyIdentifiesSubtemberGiftedMany(string commenterString, string messageString) + { + const HighlightType EXPECTED_TYPE = HighlightType.GiftedMany; + + var commenter = JsonSerializer.Deserialize(commenterString)!; + var message = JsonSerializer.Deserialize(messageString)!; + var comment = CreateCommentWithCommenterAndMessage(commenter, message); + + var actualType = HighlightIcons.GetHighlightType(comment); + + Assert.Equal(EXPECTED_TYPE, actualType); + } + + [Theory] + [InlineData("{\"display_name\":\"AnAnonymousGifter\",\"_id\":\"274598607\",\"name\":\"ananonymousgifter\",\"type\":\"user\",\"bio\":\"?????????????????????????????\",\"created_at\":\"2018-11-12T21:57:31.811529Z\",\"updated_at\":\"2022-04-18T21:57:27.392173Z\",\"logo\":\"https://static-cdn.jtvnw.net/jtv_user_pictures/ae7b05c6-c924-44ab-8203-475a2d3e488c-profile_image-300x300.png\"}", + "{\"body\":\"An anonymous user gifted a Tier 1 sub to viewer8! \",\"bits_spent\":0,\"fragments\":[{\"text\":\"An anonymous user gifted a Tier 1 sub to viewer8! \",\"emoticon\":null}],\"user_badges\":[],\"user_color\":null,\"emoticons\":[]}")] + [InlineData("{\"display_name\":\"Twitch\",\"_id\":\"12826\",\"name\":\"twitch\",\"bio\":\"Twitch is where thousands of communities come together for whatever, every day. \",\"created_at\":\"2007-05-22T10:39:54.238271Z\",\"updated_at\":\"2024-09-22T22:28:39.594659Z\",\"logo\":\"https://static-cdn.jtvnw.net/jtv_user_pictures/aa88230d-7af5-4053-a7cd-889e626d3382-profile_image-300x300.png\"}", + "{\"body\":\"An anonymous user gifted a Tier 1 sub to viewer8! \",\"bits_spent\":0,\"fragments\":[{\"text\":\"An anonymous user gifted a Tier 1 sub to viewer8! \",\"emoticon\":null}],\"user_badges\":[],\"user_color\":null,\"emoticons\":[]}")] + public void CorrectlyIdentifiesAnonymousGiftSub(string commenterString, string messageString) { - const string COMMENTER_STRING = - "{\"display_name\":\"AnAnonymousGifter\",\"_id\":\"274598607\",\"name\":\"ananonymousgifter\",\"type\":\"user\",\"bio\":\"?????????????????????????????\",\"created_at\":\"2018-11-12T21:57:31.811529Z\",\"updated_at\":\"2022-04-18T21:57:27.392173Z\",\"logo\":\"https://static-cdn.jtvnw.net/jtv_user_pictures/ae7b05c6-c924-44ab-8203-475a2d3e488c-profile_image-300x300.png\"}"; - const string MESSAGE_STRING = - "{\"body\":\"An anonymous user gifted a Tier 1 sub to viewer8! \",\"bits_spent\":0,\"fragments\":[{\"text\":\"An anonymous user gifted a Tier 1 sub to viewer8! \",\"emoticon\":null}],\"user_badges\":[],\"user_color\":null,\"emoticons\":[]}"; const HighlightType EXPECTED_TYPE = HighlightType.GiftedAnonymous; - var commenter = JsonSerializer.Deserialize(COMMENTER_STRING)!; - var message = JsonSerializer.Deserialize(MESSAGE_STRING)!; + var commenter = JsonSerializer.Deserialize(commenterString)!; + var message = JsonSerializer.Deserialize(messageString)!; var comment = CreateCommentWithCommenterAndMessage(commenter, message); var actualType = HighlightIcons.GetHighlightType(comment); diff --git a/TwitchDownloaderCore/ChatRenderer.cs b/TwitchDownloaderCore/ChatRenderer.cs index 9d8081e8..5241de47 100644 --- a/TwitchDownloaderCore/ChatRenderer.cs +++ b/TwitchDownloaderCore/ChatRenderer.cs @@ -688,7 +688,7 @@ private void DrawAccentedMessage(Comment comment, List<(SKImageInfo info, SKBitm Point iconPoint = new() { X = drawPos.X, - Y = (int)((renderOptions.SectionHeight - highlightIcon?.Height) / 2.0 ?? 0) + Y = (int)((renderOptions.SectionHeight - highlightIcon.Height) / 2.0) }; switch (highlightType) diff --git a/TwitchDownloaderCore/Tools/HighlightIcons.cs b/TwitchDownloaderCore/Tools/HighlightIcons.cs index 8e66cde1..b9b501ae 100644 --- a/TwitchDownloaderCore/Tools/HighlightIcons.cs +++ b/TwitchDownloaderCore/Tools/HighlightIcons.cs @@ -32,6 +32,8 @@ public sealed class HighlightIcons : IDisposable { public bool Disposed { get; private set; } + private double FinalIconSize => _fontSize / 0.6; // 20x20px @ 12pt font + private const string SUBSCRIBED_TIER_ICON_SVG = "m 32.599229,13.144498 c 1.307494,-2.80819 5.494049,-2.80819 6.80154,0 l 5.648628,12.140919 13.52579,1.877494 c 3.00144,0.418654 4.244522,3.893468 2.138363,5.967405 -3.357829,3.309501 -6.715662,6.618992 -10.073491,9.928491 L 53.07148,56.81637 c 0.524928,2.962772 -2.821092,5.162303 -5.545572,3.645496 L 36,54.043603 24.474093,60.461866 C 21.749613,61.975455 18.403591,59.779142 18.92852,56.81637 L 21.359942,43.058807 11.286449,33.130316 c -2.1061588,-2.073937 -0.863074,-5.548751 2.138363,-5.967405 l 13.52579,-1.877494 z"; private const string SUBSCRIBED_PRIME_ICON_SVG = "m 61.894653,21.663055 v 25.89488 c 0,3.575336 -2.898361,6.47372 -6.473664,6.47372 H 16.57901 c -3.573827,-0.0036 -6.470094,-2.89986 -6.473663,-6.47372 V 21.663055 L 23.052674,31.373635 36,18.426194 c 4.315772,4.315816 8.631553,8.631629 12.947323,12.947441 z"; private const string GIFTED_SINGLE_ICON_SVG = "m 55.187956,23.24523 h 6.395987 V 42.433089 H 58.38595 V 61.620947 H 13.614042 V 42.433089 H 10.416049 V 23.24523 h 6.395987 v -3.859957 c 0,-8.017328 9.689919,-12.0307888 15.359963,-6.363975 0.418936,0.418935 0.796298,0.879444 1.125692,1.371934 l 2.702305,4.055034 2.702305,-4.055034 a 8.9863623,8.9863139 0 0 1 1.125692,-1.371934 c 5.666845,-5.6668138 15.359963,-1.653353 15.359963,6.363975 z M 23.208023,19.385273 v 3.859957 h 8.301992 l -3.536982,-5.305444 a 2.6031666,2.6031528 0 0 0 -4.76501,1.445487 z m 25.583946,0 v 3.859957 h -8.301991 l 3.536983,-5.305444 a 2.6031666,2.6031528 0 0 1 4.765008,1.442286 z m 6.395987,10.255909 v 6.395951 H 39.19799 v -6.395951 z m -3.197992,25.58381 V 42.433089 H 39.19799 V 55.224992 Z M 32.802003,29.641182 v 6.395951 H 16.812036 v -6.395951 z m 0,12.791907 H 20.010028 v 12.791903 h 12.791975 z"; @@ -41,6 +43,7 @@ public sealed class HighlightIcons : IDisposable private const string WATCH_STREAK_ICON_SVG = "M 38.84325,21.169078 33.156748,14.060989 21.215093,27.992844 a 21.267516,21.267402 0 0 0 -5.11785,13.846557 c 0,9.752298 7.961102,17.713358 17.713453,17.713358 H 38.50206 A 17.400696,17.400602 0 0 0 55.902755,42.152157 c 0,-5.288419 -1.848114,-10.406242 -5.231581,-14.500501 L 41.686501,16.904225 Z m -13.306415,10.519973 7.619913,-9.098354 5.686502,7.108089 2.843251,-4.264854 4.606066,5.885497 a 16.945776,16.945684 0 0 1 3.923686,10.832728 c 0,5.91393 -4.407039,10.804296 -10.121973,11.600401 1.02357,-1.336321 1.592221,-2.985397 1.592221,-4.719771 0,-1.478483 -0.511786,-2.900101 -1.421626,-4.065827 l -4.264877,-5.316851 -4.264876,5.316851 c -0.90984,1.137294 -1.421625,2.587344 -1.421625,4.065827 0,1.705941 0.56865,3.355018 1.535355,4.662906 A 12.026952,12.026887 0 0 1 21.783744,41.839401 c 0,-3.72464 1.336328,-7.335548 3.753091,-10.15035 z"; private const string CHARITY_DONATION_ICON_SVG = "M 14.211579,29.774743 23.549474,11.09897 H 48.450526 L 57.788421,29.774743 47.345541,42.829108 60.901052,60.90103 H 39.112633 L 36,57.010242 32.887368,60.90103 h -21.78842 l 13.55551,-18.071922 z m 13.185107,-12.450515 -3.112631,6.225256 h 23.43189 l -3.112632,-6.225256 z m 2.378051,12.450515 2.334473,3.112628 -3.598202,4.796559 -6.32798,-7.909187 z m 10.20943,22.255295 2.119703,2.645734 h 6.346656 l -5.12028,-6.829109 -3.342966,4.180262 z M 23.549474,54.675772 42.225261,29.774743 h 7.59171 L 29.89613,54.675772 Z"; private const string CHANNEL_POINT_ICON_SVG = "m 34.074833,10.317667 a 25.759205,25.759174 0 0 0 -23.83413,25.686052 25.759298,25.759267 0 0 0 51.518594,0 25.759205,25.759174 0 0 0 -27.684464,-25.686052 z m 0.329458,6.432744 a 19.319404,19.319381 0 0 1 20.915597,19.253308 19.319888,19.319865 0 0 1 -38.639776,0 19.319404,19.319381 0 0 1 17.724179,-19.253308 z M 36,23.124918 v 6.439401 a 6.4398012,6.4397935 0 0 1 6.439407,6.4394 H 48.88048 A 12.879602,12.879587 0 0 0 36,23.124918 Z"; + private const string BLANK_ICON_SVG = " "; // A single space is enough to pass the SVG parse and get an empty path private const int ICON_SIZE = 72; // Icon SVG strings are scaled for 72x72 @@ -56,6 +59,7 @@ public sealed class HighlightIcons : IDisposable private SKImage _bitBadgeTierNotificationIcon; private SKImage _watchStreakIcon; private SKImage _charityDonationIcon; + private SKImage _blankIcon; private readonly DirectoryInfo _cacheDir; private readonly SKColor _purple; @@ -75,7 +79,7 @@ public HighlightIcons(ChatRenderOptions renderOptions, SKColor iconPurple, SKPai if (_outline) { _outlinePaint = outlinePaint.Clone(); - _outlinePaint.StrokeWidth *= (float)(ICON_SIZE / (_fontSize / 0.6)); + _outlinePaint.StrokeWidth *= (float)(ICON_SIZE / FinalIconSize); } } @@ -151,10 +155,18 @@ public static HighlightType GetHighlightType(Comment comment) return HighlightType.Raid; } + const string TWITCH_ACCOUNT_ID = "12826"; const string ANONYMOUS_GIFT_ACCOUNT_ID = "274598607"; // Display name is 'AnAnonymousGifter' - if (comment.commenter._id is ANONYMOUS_GIFT_ACCOUNT_ID && GiftAnonymousRegex.IsMatch(comment.message.body)) + if (comment.commenter._id is ANONYMOUS_GIFT_ACCOUNT_ID or TWITCH_ACCOUNT_ID && GiftAnonymousRegex.IsMatch(comment.message.body)) return HighlightType.GiftedAnonymous; + if (comment.commenter._id is TWITCH_ACCOUNT_ID && comment.message.body.EndsWith("'s gift! ") && + Regex.IsMatch(comment.message.body, @"^We added \d+ Gift Subs (?:AND \d+ Bonus Gift Subs )?to ")) + { + // TODO: Make a dedicated enum value for Subtember? + return HighlightType.GiftedMany; + } + return HighlightType.None; } @@ -172,14 +184,14 @@ public SKImage GetHighlightIcon(HighlightType highlightType, SKColor textColor) HighlightType.BitBadgeTierNotification => _bitBadgeTierNotificationIcon ??= GenerateSvgIcon(BIT_BADGE_TIER_NOTIFICATION_ICON_SVG, textColor), HighlightType.WatchStreak => _watchStreakIcon ??= GenerateSvgIcon(WATCH_STREAK_ICON_SVG, textColor), HighlightType.CharityDonation => _charityDonationIcon ??= GenerateSvgIcon(CHARITY_DONATION_ICON_SVG, textColor), - _ => null + _ => _blankIcon ??= GenerateSvgIcon(BLANK_ICON_SVG, textColor) }; } private SKImage GenerateGiftedManyIcon() { //int newSize = (int)(fontSize / 0.2727); // 44*44px @ 12pt font // Doesn't work because our image sections aren't tall enough and I'm not rewriting that right now - var finalIconSize = (int)(_fontSize / 0.6); // 20x20px @ 12pt font + var finalIconSize = (int)FinalIconSize; if (_offline) { @@ -218,7 +230,7 @@ private SKImage GenerateSvgIcon(string iconSvgString, SKColor iconColor) } tempCanvas.DrawPath(iconPath, iconPaint); - var newSize = (int)(_fontSize / 0.6); // 20*20px @ 12pt font + var newSize = (int)FinalIconSize; var imageInfo = new SKImageInfo(newSize, newSize); var resizedBitmap = tempBitmap.Resize(imageInfo, SKFilterQuality.High);