Team-TAU / tau

TAU- Twitch API Unifier, a containerized relay/proxy to unify the WebHook- and WebSocket-based real-time Twitch APIs under a single (local) WebSocket connection.
MIT License
154 stars 39 forks source link

Switch to use IRC websocket for Channel Point Redemption and Cheer events. #20

Closed FiniteSingularity closed 3 years ago

FiniteSingularity commented 3 years ago

Currently neither EventSub nor PubSub provide emote data for messages submitted from Channel Point Redemptions or Cheers (while for some mysterious reason Subscription events via PubSub do provide emote data). The only way that emotes in cheer and channel point redemptions can be determined are via IRC. In order to do this, the following must occur:

  1. Add a websocket IRC connection to Twitch's IRC server irc-ws.chat.twitch.tv.
  2. Add interface to handle IRC message format.
  3. Add listener for Channel Point Redemption (irc message tags include custom-reward-id)
  4. Add listener for Cheers (irc message tags include bits)

In addition to gaining access to emote data for channel points and cheers, interfacing with IRC will also allow TAU to monitor/record/forward on moderation events, viewer join/part, etc.

FiniteSingularity commented 3 years ago

We discovered on stream last night, we need to use data from BOTH the EventSub Channel Point event and the IRC Even Channel point event, in order to get "all of the things" when it comes to channel points.

1) Channel point redemptions that do not have user input, DO NOT show up in the IRC stream. This is easy to handle, as we simply need to save any incoming EventSub channel point events, that have no user input, to the database as we currently are. 2) For channel point redemptions that do require user input, the only way we can get emote information is by looking at the IRC tag information. This is actually the only piece of data we need to save from the IRC event, as everything else can be found in the EventSub event. 3) The IRC event and EventSub event both return different IDs, and the timestamp is not exactly the same (though they are quite close) 4) the EventSub event returns an event payload that looks like this:

    "event": {
        "id": "1234",
        "broadcaster_user_id": "1337",
        "broadcaster_user_login": "cool_user",
        "broadcaster_user_name": "Cool_User",
        "user_id": "9001",
        "user_login": "cooler_user",
        "user_name": "Cooler_User",
        "user_input": "Hello World, finite6Ollie",
        "status": "unfulfilled",
        "reward": {
            "id": "9001",
            "title": "title",
            "cost": 100,
            "prompt": "reward prompt"
        },
        "redeemed_at": "2020-07-15T17:16:03.17106713Z"
    }

5) The IRC event data provides the following:

    "event": {
        "id": "3d9308a9-ae5f-4065-a42a-19a8e549707c",
        "broadcaster_user_id": "1337",
        "broadcaster_user_login": "cool_user",
        "broadcaster_user_name": "Cool_User",
        "user_id": "9001",
        "user_login": "cooler_user",
        "user_name": "Cooler_User",
        "user_input":{
            "text":"Hello World, finite6Ollie",
            "emotes":"304167937:13-24"
        },
        "status":null,
        "reward": {
            "id": "9001",
            "title": "title",
            "cost": 100,
            "prompt": "reward prompt"
        },
        "tmi-sent-ts": "1620248481896"
    }

Things to notice- a) the event ID does not match between the EventSub and IRC events b) the IRC event does not provide status information. c) IRC does not return the "redeemed_at" DT stamp, but does provide a tmi-sent-ts (which is epoch time) d) tmi-sent-ts != redeemed_at. They are slightly different. e) the IRC message DOES provide emote information (yay!) f) if we want to add the "Channel Point Redemption update" event from EventSub, which fires off when the streamer or a mod redeems or cancels the event from the control panel, we will need the EventSub ID, not the IRC ID

How to move forward on this? The easiest way, if we dont plan on adding support for the Channel Point Redemption update (f), would be to take any events with user input from IRC, then look up the channel point reward id ("9001") in helix, to see if the reward is auto-fulfilled. If it is, set the status to "fulfilled", if not set the status to "unfulfilled". If we want to also monitor the Channel Point Redemption Update event from EventSub, we will need to somehow merge the two event messages. This is not straight forward, as it is two completely different processes that listen for the messages- the websocket worker will be listening for the IRC messages, while the http django server will be listening for the its webhook endpoints being hit. To further complicate things, we dont know the order in which these messages will arrive, though we do know they will arrive very close to one another.

One option that comes to mind: 1) process the incoming EventSub message. Modify the user_input field to have the {"text": string, "emotes": []} format. Save the message, but prevent its websocket event from being fired off. 2) in the websocket worker, when an incoming Channel Point Redemption is detected, spawn a new process that begins with a 500ms delay. After 500ms, query the DB for a Channel Point Redemption event, in the last 1000ms with a matching user_id, reward_id, and user_input.text. If found, update with the emote information, save the entry, and fire off the websocket event. If one is not found, wait another second and try again. 3) 1) will need to monitor for a change from 2, and if one isn't found in say, 3 seconds, it should fire off the websocket event without the emote data, as nothing came through via IRC.

I am definitely open to suggestions if anyone can think of a way to simplify this whole process. I would definitely like to implement the Channel Point Redemption Update event, as I think it could be quite useful to people. Thoughts?