Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support properly rendering Subtember gift subs #1237

Merged
merged 5 commits into from
Oct 28, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 27 additions & 8 deletions TwitchDownloaderCore.Tests/ToolTests/HighlightIconsTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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\":[]}",
Expand Down Expand Up @@ -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<Commenter>(commenterString)!;
var message = JsonSerializer.Deserialize<Message>(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>(COMMENTER_STRING)!;
var message = JsonSerializer.Deserialize<Message>(MESSAGE_STRING)!;
var commenter = JsonSerializer.Deserialize<Commenter>(commenterString)!;
var message = JsonSerializer.Deserialize<Message>(messageString)!;
var comment = CreateCommentWithCommenterAndMessage(commenter, message);

var actualType = HighlightIcons.GetHighlightType(comment);
Expand Down
2 changes: 1 addition & 1 deletion TwitchDownloaderCore/ChatRenderer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
22 changes: 17 additions & 5 deletions TwitchDownloaderCore/Tools/HighlightIcons.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand All @@ -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

Expand All @@ -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;
Expand All @@ -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);
}
}

Expand Down Expand Up @@ -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;
}

Expand All @@ -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)
{
Expand Down Expand Up @@ -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);

Expand Down
Loading