Building a TwitchPlays Game

in Go using gempir/go-twitch-irc and faiface/pixel

It is no surprise that with the growth of the Internet, consumers will look for new ways to stay entertained. Twitch.tv is one of the largest streaming platforms and has become one of the most influential platforms for a new type of entertainment: live-streaming. While most content is created by individuals playing games, TwitchPlays is one example where Chat plays the game. The idea was popularized by “Twitch Play Pokemon” and still remains active to this day. In this article, we will explore creating our own version of a TwitchPlays game.

Overview

If we won’t be optimizing video buffers being sent to Twitch, we know that we will be creating a desktop application. Interpreted languages will probably work best for prototyping but might not perform well in the long term. In terms of compiled languages, I am most comfortable in Go. Its focus on concurrency should also make it a great fit for reacting to chat commands.

Choosing a rending library might be the hard part. I wouldn’t imagine many people are building desktop applications in Go so I wasn’t expecting to find much support to being with. I was able to get the “Hello World” for faiface/pixel working pretty quickly. I don’t really know much about building games so I could understand why choosing something like Unity would be better if you wanted to program the game first.

Overall, I would like to build some sort of application with “mini-games” (probably similar to Mario Party). This will simplify the feature development and allow people to have some fun with little time investment.

Building the MVP

Overall, we expect the application code to contain the following three components:

  1. OAuth web server
  2. IRC bot
  3. Game render loop

In pseudo-Go, this should look like the following:

func main(){
tokenChan = make(chan string)
messageChan = make(chan string)
// webserver to handle oauth redirect
go func(){
server.onRedirect(func(req){ tokenChan <- req.Token })
}()
// chatbot to connect with token obtained from redirect
go func(){
token <- tokenChan
twitchChat.start(token)
twitchChat.onMessage(func(message){ messageChan <- message })
}()
// game renders twitch messages to canvas
for {
message <- messageChan
game.Draw(message)
game.Render()
}
}

The OAuth Token

In the Twitch Chatbot use case, this flow isn’t as obvious. To most of us, the Chatbot is another person and, just like we can, it should be able come and go from Twitch channels as it pleases. Why should a Chatbot require any sort of OAuth flow to act on behalf of us? This way of thinking held me up for some time and I spent some time failing to send messages to both my personal channel and to the bots own channel. The main reason I could come up with was this: if you were to “publish” your Chatbot, how would your customer ask the Chatbot to join the channel? If the Chatbot was doing more than sending and receiving messages (like Chat moderation), how would you grant the Chatbot the ability to operate on your channel data? That use case definitely sounds like OAuth! The tricky part is, the Chatbot (the Twitch user) needs to grant the Chatbot (the server-side application) the ability to act on behalf of itself (the Twitch user). This seems a bit confusing as the server-side application should be able to authenticate using a normal Twitch password. After all, this is how we humans authenticate. I think the main reason for using the OAuth flow is a mandatory “password” rotation (referred to as a “refresh token”). If bots are to be present in many high-profile Twitch channels, they will need to act as a sort of super-user that sends many more chat requests than the average person watching Twitch. If one password were to be compromised, the reputation of the Bot (and possibly Twitch) would suffer. Constantly rotating the value used to authenticate with Twitch is a mandatory security policy and OAuth provides an application flow to do so.

A Note On Twitch OAuth Documentation

OAuth Next Steps

  1. Creating a Chatbot Twitch account
  2. Creating a Twitch Application (not Extension)
  3. Granting our Twitch Application access to our Chatbot Account’s chat
  4. Creating an OAuth Token for our code to use
  5. Running our Application with this Token

For the first step, head to https://www.twitch.tv/signup to create your bot account. I found that I couldn’t reuse an existing email so you many want to create a new email for your Chatbot. I tried creating an email using Outlook but you need to wait a day before sending/receiving email. GMail ended up working well in the end. You will also need a phone number to create both the email and your Twitch account!

After creating both the Email and the Twitch Account, head to dev.twitch.tv and create your application. You’ll need to fill in the OAuth redirect URL with the future address of your local webserver ( http://localhost:8080 should work). You’ll also need to copy the client_id and generate a client_secret.

Now we are ready to create our OAuth server. Fortunately, Twitch provides some starter code in a few languages for this:

We basically need to copy most of the oauth-authorization-code's code and replace the following:

  • client_id
  • client_secret
  • redirect_url

You should be able to create a new go module and copy/paste the above code.

$ go version
go version go1.13.3 windows/amd64

Running go build was enough to download and build the server.

There is one modification we will need to make: scopes. Like the Facebook example above, our server-side application needs to ask the customer which actions it is allowed to perform on it’s behalf. For our use-case, we just need permissions to read and write to chat. This corresponds to the chat:edit and chat:read scopes:

scopes = []string{"chat:read", "chat:edit"}

After running the server, navigate to the address you have configured on your server (I have changed mine to localhost:8080). If you have configured both the Twitch Application in dev.twitch.tv and the Go Code to have the same redirect URL, you should see the following:

webpage displaying “login using twitch”
webpage displaying “login using twitch”

After clicking, you will see the page used to grant access to your Bot’s own Twitch chat:

a twitch webpage displaying “Clicking Authorize below will allow meecheebot2 to Send live Stream chat and rooms messages”
a twitch webpage displaying “Clicking Authorize below will allow meecheebot2 to Send live Stream chat and rooms messages”

When you are redirected back to your application running on localhost, you will see access token: abcdefg12345 in your logs! Wahoo! Now to set up an IRC bot to send and receive messages from our Twitch Chat.

Setting Up the IRC Bot

You’ll need to wrap either code in a goroutine and add a channel for token passing:

func main(){
tokenChan := make(chan string)
go func(){
// server code
// HandleOAuth2Callback
...
tokenChan <- token.AccessToken
...
// end server code
}()
token <- tokenChan

// paste README
client := twitch.NewClient(
"<yourbot>",
fmt.Sprintf("oauth:%s", token)
)
...
}

You can get more creative with your bot here. I have used the following function to respond to the room:

client.OnNewMessage(
func(channel string, user irc.User, message irc.Message) {

if user.Username == "jeffzzq" {
fmt.Println("jeff said something")
client.Say(channel, "jeff said hello world")
msgChan <- message.Text
}
}
)

I have set up the Bot to join its own room as this is where the TwitchPlays application would live. After rerunning the application, you should be able to follow the “login” flow again to generate another token. I needed to run through that flow twice before I generated a new token.

If you are only building a Chatbot and not an accompanying game, you likely won’t need to see the rest of the tutorial.

Most of the logic will be some comprehensive switch-case statement in the OnNewMessage function. There may be other Go frameworks to handle state management for complex chat interactions (maybe something like this project).

You can always revoke access (“Disconnect”) to the Twitch application if you want to start from scratch:

Twitch Settings Page under the Connections tab and after Other Connections heading
Twitch Settings Page under the Connections tab and after Other Connections heading

The Game Application

For the next step, we need to wrap the chatbot in a goroutine and let the game engine run on the main thread. We can copy most of the code in the tutorial located here:

Feel free to get creative with font sizes and placement!

The last piece to add is a message channel to the start of the program. This will send a Twitch Chat message to our Game to print it on the screen:

messageChan := make(chan string)

In the Chatbot’s OnMessage handler, forward the twitch chat message to the channel:

messageChan <- message.Text

In the game’s render loop, try reading from the channel every second. If you receive a message on the channel, write it to the text buffer and draw the text buffer on the screen. Lastly, update the window to display the screen.

for !win.Closed() {
select {
case msg := <-msgChan:
fmt.Fprintln(basicTxt, msg)
case <-time.After(1 * time.Second):
fmt.Println("timeout 1s")
}
basicTxt.Draw(win, pixel.IM)
win.Update()
}

All that is left now is to start up OBS, enter your streaming key, and capture the Game’s window. The finished product should look similar to the following:

OBS streaming Game Window. Typing in Chat draws on the Canvas.

Taa-Daa!

Next Steps

google pricing page showing 106.94 USD per month
google pricing page showing 106.94 USD per month
106.94 USD per month

I was only able to stream OBS at 20FPS reliably and at a resolution smaller than 1920 x 1080. The CPU percentage was at about 60% with those settings which would give some room for operations and the future application.

I would investigate hosting this on Linux to avoid the massive “Paid OS Cost” fee.

Thank You For Reading!