public void _u_WebSocketOpened(/* int connectionID */) { debug._u_Log("[TwitchChatBehavior] _u_WebSocketOpened"); webManager._u_WebSocketSendStringUnicode(connectionID, "CAP REQ :twitch.tv/tags twitch.tv/commands", true); state = TwitchChatState.CapReqSent; // First message sent }
void _u_TryStartChat() { debug._u_Log("[TwitchChatBehavior] _u_TryStartChat"); if (!_u_PlayerIsOnlineAndAvailable(Networking.GetOwner(gameObject))) { debug._u_Log("[TwitchChatBehavior] No player is web connected!"); output.text = "No player is web connected!"; currentMessageCount = 0; return; } // The current behavior is to only allow the chat owner to connect through the web handler. // Since messages are going to be broadcast to everyone anyway, it would be best for all // other web conncted clients to save their Udon processing power and connection counts. // Could make chat brokering opt-out and brokering disable options in the future. if (!webManager.online || !Networking.IsOwner(gameObject) || !urlIsTwitchChannel) { return; } string url = syncedURL.Get(); string channelToConnectTo = url.Remove(0, 22); if (connectionID != -1 && state == TwitchChatState.ChannelJoined && channelToConnectTo != connectedChannelName) { // Preserve existing websocket connection, simply change irc channel webManager._u_WebSocketSendStringUnicode(connectionID, "PART #" + connectedChannelName, true); output.text = ""; currentMessageCount = 0; webManager._u_ClearConnection(connectionID); webManager._u_WebSocketSendStringUnicode(connectionID, "JOIN #" + channelToConnectTo, true); } else { // Fresh connect! // Perform a full reset in case something goes wrong with the websocket connection. // Twitch doesn't like to follow full websocket protocol so the helper program runs into issues _u_Reset(); connectionID = webManager._u_WebSocketOpen("wss://irc-ws.chat.twitch.tv/", this, true, true); if (connectionID == -1) { output.text = "Too many active connections! Finding new broker.\n"; _u_Resync(); return; } else { state = TwitchChatState.WebSocketOpening; } } connectedChannelName = channelToConnectTo; }
void _u_Reset() { debug._u_Log("[TwitchChatBehavior] _u_Reset"); // Close if open state = TwitchChatState.Off; if (connectionID != -1) { webManager._u_WebSocketClose(connectionID); state = TwitchChatState.Closing; // Complete reset } connectedChannelName = ""; output.text = ""; currentMessageCount = 0; }
public void _u_WebSocketClosed(/* int connectionID */) { debug._u_Log("[TwitchChatBehavior] _u_WebSocketClosed"); connectionID = -1; output.text = ""; currentMessageCount = 0; // This close could have been achieved gracefully as the result of // _u_Reset() or ungracefully from twitch aborting the connection // because the justinfan nickname was already in use. If a close wasn't // expected, resync. connectedChannelName = ""; if (state != TwitchChatState.Closing) { _u_TryStartChat(); } else { state = TwitchChatState.Off; } }
public void _u_WebSocketReceive(/* int connectionID, byte[] connectionData, string connectionString, bool messageIsText */) { if (connectionString == "PING :tmi.twitch.tv\r\n") { debug._u_Log("[TwitchChatBehavior] PING received"); webManager._u_WebSocketSendStringUnicode(connectionID, "PONG", true); return; } else if (state == TwitchChatState.CapReqSent) { webManager._u_WebSocketSendStringUnicode(connectionID, "PASS SCHMOOPIIE", true); username = "******" + (int)(1000 + Random.value * 80000); webManager._u_WebSocketSendStringUnicode(connectionID, "NICK " + username, true); state = TwitchChatState.NicknameSent; // Second message sent } else if (state == TwitchChatState.NicknameSent && connectionString.Contains("Welcome, GLHF!")) { webManager._u_WebSocketSendStringUnicode(connectionID, "USER " + username + " 8 * :" + username, true); webManager._u_WebSocketSendStringUnicode(connectionID, "JOIN #" + connectedChannelName, true); state = TwitchChatState.ChannelJoined; // Third message set sent } else if (state == TwitchChatState.ChannelJoined) { // Ready to receive chat messages string[] split = connectionString.Split(' '); if (split.Length < 5 || split[2] != "PRIVMSG") { // Remove this for release, as this is a security flaw that could allow injecting log lines via twitch chat messages // Debug option exclusively for non-chat-message websocket messages. // Debug.Log("[TwitchChatBehavior] WebSocketReceive: " + connectionString); return; } string color = "white"; string[] kvPairs = split[0].Split(';'); foreach (string kv in kvPairs) { string[] kvSplit = kv.Split('='); if (kvSplit.Length > 1 && kvSplit[0] == "color") { color = kvSplit[1]; break; } } int nicknameSplitIndex = split[1].IndexOf('!'); if (nicknameSplitIndex < 1 || split[1].Length < 2) { return; } string name = split[1].Substring(1, nicknameSplitIndex - 1); if (split[4].Length < 1) { return; } string messageIndexIdentifier = " PRIVMSG #" + connectedChannelName + " :"; // Ignore messages from other channels that may be left over after flushing the websocket connection int messageStart = connectionString.IndexOf(messageIndexIdentifier); if (messageStart != -1) { string message = connectionString.Substring(messageStart + messageIndexIdentifier.Length); // Replace < and > to prevent escapeing Unity UI's rich text markup // ≺ ≻ string completeMessage = "<b><color=" + color + ">" + name + "</color></b>: " + message.Replace('<', '〈').Replace('>', '〉').Replace("\r\n", ""); if (brokeredMessage != "") { brokeredMessage += "\n"; } brokeredMessage += completeMessage; brokeredMessageSerializations++; RequestSerialization(); _u_AppendChatMessage(); // Special case where OnPostSerialization is never reached if (VRCPlayerApi.GetPlayerCount() == 1) { brokeredMessage = ""; } } } }