Code Your Own Twitch Chat Controls For Robots — Or Just About Anything Else!

Twitch Plays Pokemon burst onto the then nascent livestreaming scene back in 2014, letting Twitch viewers take command of a Game Boy emulator running Pokemon Red via simple chat commands. Since then, the same concept has been applied to everything under the sun. Other video games, installing Linux, and even trading on the New York Stock Exchange have all been gameified through Twitch chat.

TwitchPlaysPokemon started a craze in crowdsourced control of video games, robots, and just about everything else.

You, thirsty reader, are wondering how you can get a slice of this delicious action. Fear not, for with a bit of ramshackle code, you can let Twitch chat take over pretty much anything in, on, or around your computer.

It’s Just IRC

The great thing about Twitch chat is that it runs on vanilla IRC (Internet Relay Chat). The protocol has been around forever, and libraries exist to make interfacing easy. Just like the original streamer behind Twitch Plays Pokemon, we’re going to use Python because it’s great for fun little experiments like these. With that said, any language will do fine — just apply the same techniques in the relevant syntax.

SimpleTwitchCommander, as I’ve named it on Github, assumes some familiarity with basic Python programming. The code will allow you to take commands from chat in two ways. Commands from chat can be tabulated, and only the one with the most votes executed, or every single command can be acted on directly. Actually getting this code to control your robot, video game, or pet viper is up to you. What we’re doing here is interfacing with Twitch chat and pulling out commands so you can make it do whatever you like. With that said, for this example, we’ve set up the code to parse commands for a simple wheeled robot. Let’s dive in.

Code Walking

import socket
from emoji import demojize
from apscheduler.schedulers.background import BackgroundScheduler
With this code, you too can watch as random people from the Internet drive your robot straight into a bush.

The first thing to do in our code is import the libraries we need. Libraries are great, they’re presents from talented programmers that make our lives easier — or at least we hope the are. We’re using four libraries in this case, but you can get by with just the first three depending on your application. The first, and perhaps most important, is the socket library, which handles all our network communication. The emoji library provides us with a neat tool for stripping out emojis out of chat messages, as they can get messy. APScheduler is used for our command voting system, which lets Twitch users vote on their desired action rather than simply letting every Twitch command through.

class TwitchControl:

This statement sets up the class for our program.

def __init__(self):
    self.server = 'irc.chat.twitch.tv'
    self.port = 6667
    self.nickname = 'yourtwitchusername'
    self.token = 'oauth:youroauthkeyhere'
    self.channel = '#yourtwitchchannel'

    self.sched = BackgroundScheduler()

    self.sock = socket.socket()
    self.sock.connect((self.server,self.port))
    self.sock.send(f"PASS {self.token}\n".encode('utf-8'))
    self.sock.send(f"NICK {self.nickname}\n".encode('utf-8'))
    self.sock.send(f"JOIN {self.channel}\n".encode('utf-8'))

__init__() is a special Python routine called when an object is created from a class. In plain terms, when we call on TwitchControl, the code in __init__() runs first. Here, we’re creating variables that store the address of Twitch’s chat server, the port, and our Twitch login and channel details. The oauth token is how the Twitch server knows who is connecting to the chat channel, and you can generate your own here.

    self.voteDict = {"null": 0, "fwd" : 0, "rev" : 0, "left" : 0, "right" : 0}    

Next, we create a special variable called a dictionary, and give it the name voteDict. Dictionaries are great, as they allow us to store data in neat little pairs. In our case, we have our desired commands, and each one has a number next to it. This will correspond to the number of votes for each command in chat. We initialise these at 0 to begin with.

    self.sched.add_job(self.voteCount, 'interval', seconds=2)
    self.sched.start()

The above lines set up APscheduler to run a function at regular 2 second intervals. That function goes by the name voteCount, and at each two second interval it checks voteDict to see which command got the most votes in chat, and then executes the winner. We’ll come back and look at voteCount in a bit. For now, let’s look at the main loop that runs once everything is initialised.

def loop(self):
    while True:
        resp = self.sock.recv(2048).decode('utf-8')
        if resp.startswith('PING'):
            self.sock.send("PONG\n".encode('utf-8'))
        elif len(resp) > 0:
            respClean = demojize(resp)
            print(respClean)
            msgComponents=respClean.split(" ",3)

            msgUser=msgComponents[0] #get username from message
            msgUser = msgUser[msgUser.find(':')+1: msgUser.find('!')]
            msgContent=msgComponents[3] #print message content

Once we get into the while loop, we need to receive data from the IRC server. If the data is “PING”, we respond with “PONG” as per typical IRC practice to keep the connection alive. Otherwise, we clean the data with the demojize function, which replaces any emojis in the message with plain text. Then we use string functions to split up the raw message from the server into its components: the username that sent the message, and the actual message content. At this point, we can hunt directly for a command, and if we want that to trigger directly off messages, we can do it here.

            if msgContent.find("LIGHTS") >=0:
                print("Turning Lights On!") 
                #code to turn lights on here

Alternatively, if we want to collect commands in chat and see which is voted the most popular, we can do that too. Each time one of the following commands is detected, its corresponding field in the voteDict dictionary is incremented by 1.

            if msgContent.find("FWD") >=0:
                self.voteDict["fwd"] = self.voteDict["fwd"] +1
            if msgContent.find("REV") >=0:
                self.voteDict["rev"] = self.voteDict["rev"] +1
            if msgContent.find("LEFT") >=0:
                self.voteDict["left"] = self.voteDict["left"] +1
            if msgContent.find("RIGHT") >=0:
                self.voteDict["right"] = self.voteDict["right"] +1

That’s all our main loop does. It receives data from the IRC server as it comes in, processes it, and increments the vote count from incoming commands. To actually act on those votes, we need to go to our voteCount function. Thanks to the APscheduler routine we set up before, this automatically runs every two seconds.

def voteCount(self): #function responsible for checking votes and executing commands
    print('Counting votes and executing command')
    voteWinner = max(self.voteDict, key=self.voteDict.get)
    print("biggest vote:" + voteWinner)
    nullCheck=all(x==0 for x in self.voteDict.values())

    if nullCheck:
        print('doing a nullo')

    elif voteWinner=="fwd":
        print('going Forward')
        #code to go forward here
    elif voteWinner=="rev":
        print('going Reverse')
        #code to go reverse here
    elif voteWinner=="left":
        print('going Left')
        #code to go left here
    elif voteWinner=="right":
        print('going Right')
        #code to go right here
    self.voteDict = {"null" : 0, "fwd" : 0, "rev" : 0, "left" : 0, "right" : 0}

voteCount is a simple function, one which uses Python’s inbuilt dictionary functions to determine the command that had the most votes. Note that we also check to see if all votes equal zero — in that case, we must do nothing, hence the nullCheck code. Once the winner is determined, the code for the relevant command can be executed.

What Will You Unleash on Twitch?

There’s a little more boilerplate to string things together, but fundamentally, these are the blocks that make everything work. Grab the real thing from Github if you wish to tinker at home. Obviously, from here, it’s simply a matter of customisation to shape the code to your particular purpose. If you’re looking to control a robot, put your commands for servos or motors in the necessary spots, or send instructions over serial to your microcontroller that handles those tasks. Alternatively, if you’re working with a game in an emulator, have the Python code simply emulate the relevant button presses.

The code is in no way optimised; writing this article took about twice as long as writing the code itself. There’s likely huge gains to be made by using more idealised string management code and other such tweaks. Further work in wrapping all this up in a neat library is left as an exercise for the reader. Hopefully this gets you well on your way to having some crowdsourced fun on Twitch! As always, happy hacking.

4 thoughts on “Code Your Own Twitch Chat Controls For Robots — Or Just About Anything Else!

  1. if msgContent.find("FWD") >=0:
    self.voteDict["fwd"] = self.voteDict["fwd"] +1
    if msgContent.find("REV") >=0:
    self.voteDict["rev"] = self.voteDict["rev"] +1
    if msgContent.find("LEFT") >=0:
    self.voteDict["left"] = self.voteDict["left"] +1
    if msgContent.find("RIGHT") >=0:
    self.voteDict["right"] = self.voteDict["right"] +1

    We allergic to the += operator? Let’s try:

    if msgContent.find("FWD") >=0:
    self.voteDict["fwd"] += 1
    if msgContent.find("REV") >=0:
    self.voteDict["rev"] += +1
    if msgContent.find("LEFT") >=0:
    self.voteDict["left"] += +1
    if msgContent.find("RIGHT") >=0:
    self.voteDict["right"] += +1

    1. (Wonderful, strip the leading whitespace why don’t you WordPress…) Obviously, re-indent the way it should have been: it seems this comment system does not respect whitespace.

Leave a Reply

Please be kind and respectful to help make the comments section excellent. (Comment Policy)

This site uses Akismet to reduce spam. Learn how your comment data is processed.