Skip to content

Commit

Permalink
fix sub emote indices
Browse files Browse the repository at this point in the history
  • Loading branch information
Felk committed Jan 3, 2025
1 parent 101868d commit ebad54e
Show file tree
Hide file tree
Showing 4 changed files with 75 additions and 4 deletions.
23 changes: 20 additions & 3 deletions TPP.Core/Chat/TwitchEventSubChat.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
using System.Collections.Immutable;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Text.Json;
using System.Text.RegularExpressions;
using System.Threading;
Expand Down Expand Up @@ -525,6 +526,22 @@ await _overlayConnection.Send(new NewSubscriber
}, CancellationToken.None);
}

private static readonly Encoding Utf8NoBom = new UTF8Encoding(encoderShouldEmitUTF8Identifier: false);

/// Parsing the emotes of the message sadly is not trivial, see <see cref="ChannelSubscriptionMessage.Emote"/>.
/// Of course, we currently don't even need the indices and could just look up an emote's code by its ID using the
/// Twitch API (maybe we don't even need the emote code in the webapp?), but this saves us roundtrips to Twitch.
public static ImmutableList<EmoteOccurrence> ParseEmotes(ChannelSubscriptionMessage.Emote[] emotes, string text)
{
byte[] messageBytes = Utf8NoBom.GetBytes(text);
return emotes.Select(emote =>
{
int endExclusive = emote.End + 1;
string emoteCode = Utf8NoBom.GetString(messageBytes[emote.Begin..endExclusive]);
return new EmoteOccurrence(emote.Id, emoteCode);
}).ToImmutableList();
}

private async Task ChannelSubscriptionMessageReceived(ChannelSubscriptionMessage channelSubscriptionMessage)
{
ChannelSubscriptionMessage.Event evt = channelSubscriptionMessage.Payload.Event;
Expand All @@ -544,9 +561,9 @@ private async Task ChannelSubscriptionMessageReceived(ChannelSubscriptionMessage
SubscriptionAt: channelSubscriptionMessage.Metadata.MessageTimestamp,
IsGift: false, // Resubscriptions are never gifts. Gifts are always "new" subscriptions, not continuations
Message: evt.Message?.Text,
Emotes: (evt.Message?.Emotes ?? []).Select(e => new EmoteOccurrence(
e.Id, evt.Message!.Text!.Substring(e.Begin, e.End - e.Begin), e.Begin, e.End))
.ToImmutableList()
Emotes: evt.Message?.Emotes is { Length: > 0 }
? ParseEmotes(evt.Message.Emotes, evt.Message.Text!)
: []
);
ISubscriptionProcessor.SubResult subResult = await _subscriptionProcessor.ProcessSubscription(
subscriptionInfo);
Expand Down
5 changes: 4 additions & 1 deletion TPP.Core/Subscriptions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,13 @@
using TPP.Common;
using TPP.Model;
using TPP.Persistence;
using TPP.Twitch.EventSub.Notifications;

namespace TPP.Core
{
public record EmoteOccurrence(string Id, string Code, int StartIndex, int EndIndex);
/// This used to also include <c>int StartIndex</c> and <c>int EndIndex</c>, but since those were unused and how
/// to count was ambiguous (see <see cref="ChannelSubscriptionMessage.Emote"/>), they were removed.
public record EmoteOccurrence(string Id, string Code);

/// <summary>
/// Information on a user subscription directly (not via a gift).
Expand Down
13 changes: 13 additions & 0 deletions TPP.Twitch.EventSub/Notifications/ChannelSubscriptionMessage.cs
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,19 @@ public record Condition(string BroadcasterUserId) : EventSub.Condition;
/// <param name="Begin">The index of where the Emote starts in the text.</param>
/// <param name="End">The index of where the Emote ends in the text.</param>
/// <param name="Id">The emote ID.</param>
/// Some additional gotchas that are not mentioned in the official documentation:
/// - The positions are counted in what appears to be UTF-8 bytes
/// - The begin is 0-based and inclusive (unsurprising), but the end is also inclusive (kinda surprising).
/// An experiment showed that this string:
/// <code>test Kappa 🌿 BabyRage 🐍 PogChamp 🎅🏿 RaccAttack ♥ PraiseIt 12345678901234567890</code>
/// resulted in these emote indices:
/// <code>
/// {"begin": 5, "end": 9, "id": "25"},
/// {"begin": 16, "end": 23, "id": "22639"},
/// {"begin": 30, "end": 37, "id": "305954156"},
/// {"begin": 48, "end": 57, "id": "114870"},
/// {"begin": 63, "end": 70, "id": "38586"}
/// </code>
public record Emote(
int Begin,
int End,
Expand Down
38 changes: 38 additions & 0 deletions tests/TPP.Core.Tests/Chat/TwitchEventSubChatTest.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
using System.Collections.Immutable;
using NUnit.Framework;
using TPP.Core.Chat;
using TPP.Twitch.EventSub.Notifications;

namespace TPP.Core.Tests.Chat;

[TestFixture]
[TestOf(typeof(TwitchEventSubChat))]
public class TwitchEventSubChatTest
{
/// <summary>
/// The data comes from an actual subscription that was observed.
/// See also <see cref="ChannelSubscriptionMessage.Emote"/>
/// </summary>
[Test]
public void ParseSubscriptionMessageEmotes()
{
const string message = "test Kappa 🌿 BabyRage 🐍 PogChamp 🎅🏿 RaccAttack ♥ PraiseIt 12345678901234567890";
ChannelSubscriptionMessage.Emote[] emotes =
[
new(5, 9, "25"),
new(16, 23, "22639"),
new(30, 37, "305954156"),
new(48, 57, "114870"),
new(63, 70, "38586")
];
ImmutableList<EmoteOccurrence> occurrences = TwitchEventSubChat.ParseEmotes(emotes, message);
ImmutableList<EmoteOccurrence> expected = [
new("25", "Kappa"),
new("22639", "BabyRage"),
new("305954156", "PogChamp"),
new("114870", "RaccAttack"),
new("38586", "PraiseIt")
];
Assert.That(occurrences, Is.EquivalentTo(expected));
}
}

0 comments on commit ebad54e

Please sign in to comment.