back to homepage

Building Twitch Tools For More Inclusive Streams

, written on A11y Up. Check out More Articles

Being on Twitch for a while now, both as a viewer and streamer, I got to learn a lot about Twitch culture, its memes and conventions. It was only a matter of time that I “needed” to dive into coding custom Twitch tools that allow for more inclusive Twitch streams.

There were three areas that were lacking for me in my streams and that I wanted to tackle. They resulted in three distinct projects.

My aim with these 3 projects was to not only allow for more inclusive Twitch streams, but to also make these tools reusable and customizable for anyone. Also, I wanted to build these as vanilla as possible, which means no JavaScript or CSS frameworks.

All the tools are available both on my GitHub and on my Glitch page.

How Do the Tools Work

Single-Page-Websites

Broadcasting software like OBS allows users to add browser sources to their streaming scenes. This means that any website under a given URL can be included and displayed in a scene. Those websites can contain JavaScript code in them to react to things dynamically. For example, with JavaScript you can establish connections to Twitch’s chat services to continuously receive messages when they happen. OBS, for example, uses the Chromium Embedded Framework (CEF). So essentially, there is a relatively up-to-date Chrome browser running inside OBS.

In order for the tools to be reusable by anyone, I thus built each tool as a website with bundled JavaScript code. This means that users don’t have to deal with running any apps locally or use any other sort of backend service directly.

All three tools use the JavaScript library tmi.js to connect to a given Twitch channel’s chat (chats are public on Twitch). The tools then receive an event every time a message is posted and handle those events respectively. For example, the chat overlay tool displays the received chat message by rendering some new message HTML into the DOM. The polling tool and the content warning command listen for certain type of chat commands (e.g. !cw) to then go and provide their respective features.

Initial Steps

My first few steps with building these tools were quite limited, as I tried to code everything within one HTML file. The reasoning behind this was that users would only have to download that file and use it for a local-file-based browser source in OBS. That way they didn’t have to deal with running a web server and hosting that website. Instead, it would just be served locally via the file:// protocol.

This had many disadvantages. First, there are security limitations with using the file:// protocol that do not allow loading any external JavaScript. Secondly, the code became very unmaintainable and quasi non-testable, as everything lived in a <script> tag. Not the kind of vanilla development that I was striving for.

But making everything more modular and split the code into separate modules/files meant that all the code would need to get bundled in the end and served from an HTTP server still. Now the ease-of-use for non-developer users was completely gone.

Vite And Glitch to The Rescue

Luckily, I rediscovered glitch.com and learned about Vite for the first time. Vite is a very modern and superfast bundler that is also zero-configuration (for my purposes at least), and works well with Glitch. The latter is a service that allows for website projects to be shared and collaborated on. User’s can remix a Glitch project to duplicate it and use it as their own project. They can then customize it to their heart’s content. Each project gets a new unique URL, under which the website is also automatically hosted.

This solved my bundling and HTTP server problem. What’s even cooler is that Glitch actually runs a live (hot-reloading) version of a preview while the project is open, and you are coding. But as soon as you close your Glitch project, it uses Vite to build a production bundle of your project. This production code is actually what gets served when you visit your unique project URL.

Custom Twitch Chat Overlay

Building your Twitch chat overlay, when there are so many readily usable solutions out there, sounds like reinventing the wheel. And it might be, I admit. I wanted to do it anyway.

Firstly, I wanted to have a very basic overlay with minimal design, so that it can be expanded easily.

A far more important reason for me was that I wanted to have a chat overlay, in which pronouns of chat user’s would be displayed. Visible pronouns are not a native Twitch feature and are only made possible by the pronouns service by Alejo Pereyra and the additional browser plugins for Chrome or Firefox that make pronouns visible when you visit a Twitch chat.

As mentioned in the introduction, I used Vanilla JavaScript to deal with receiving messages and adding them to the overlay via standard browser DOM methods. For receiving the messages, I used the tmi.js library. It allows for handling different kinds of message events. Setting tmi.js up is pretty straight forward:

const client = tmi.Client({
  channels: [CHANNEL_NAME],
});

client.connect();
client.on('message', addMessage);
client.on('messagedeleted', removeMessage);
client.on('timeout', removeAllMessagesOfUser);
client.on('ban', removeAllMessagesOfUser);

When we receive a message event, we call a addMessage function that does the main work. When messageDeleted is received, removeMessage is called to remove one particular message (messages have unique IDs). And with timeout and ban events, we immediately delete all messages by that certain user with removeAllMessagesOfUser.

Adding a message works like this (shortened version):

/* the arguments channel, tags, message and self
   are passed in from the tmi.js library */
async function addMessage(channel, tags, message, self) {
  const messageElement = await createMessageHTMLElement(tags, message);

  const chatBoxElement = document.querySelector(".chat-box");
  chatBoxElement.append(messageElement);
  ...
}

The main magic happens in createMessageHTMLElement. It deals with fetching pronouns, replacing emote text codes with the appropriate emote images etc.

Once the tool connects to a given Twitch chat (you can actually use any Twitch channel’s chat; again, Twitch chats are public) and messages start flowing in the final result looks like this:

The custom twitch chat box is an almost black, rounded corner box on the left side of the screen. For users that provided their pronouns, those are visible just before their username
The custom twitch chat box is an almost black, rounded corner box on the left side of the screen. For users that provided their pronouns, those are visible just before their username

Content Warning Command

The idea for a content warning command came up after I discovered Does the Dog Die (DTDD) a while back. Does the Dog Die is a service that collects crowdsourced content warnings for all kinds of media. You can use a search field directly on their website to find a piece of media and its content warnings. They also provide an API, so you can fetch content warnings via code.

At first, I used to add a !cw command to my existing Twitch chatbot (via StreamElements), which would respond with a URL to a game on DTDD. But the response was always hand-picked by me before the stream and meant that I would have to change the command response every time I started a new game, for example.

There is already a global ContentWarningBot (built by zactopus) on Twitch that you can register yourself with. This bot account would then post content warnings messages when it registers a !cw in your chat. I was going to build something similar, but then I discovered this tool and I absolutely love it.

The only catch for me was that I wanted my own bot to respond with content warning messages. So I gave it a shot myself.

Technically, there are three parts to its functionality.

  1. Use tmi.js to listen in your channel for a !cw message in chat.
  2. Use the Twitch APIs to determine the currently set the game for your Twitch channel.
  3. Use the DTDD API to query it for the determined game and get the DTDD URL for that game to respond with in chat.

Polling Tool

While there are polling functionalities built into Twitch itself, they are only made available for streamers that become at least Twitch affiliates. This technically means that your viewers will from then on see a lot of ads, unless they buy a subscription or otherwise partake in the Twitch/Amazon money making machine.

Since I, and from my experience many others, like to stay independent, there was a need for a custom polling tool that can be used via chat commands.

Again, the tool uses tmi.js to listen for chat messages. But this time for a bunch of commands. For example, a poll with a main question and two answer options can be opened when the streamer or a moderator write !poll "what next" "coffee" "tea" into chat.

Internally, the tool manages the poll state with a pollState object that gets updated any time a command comes through. After each relevant state update, the rendering functions do their work and update the HTML to reflect the new state. If you are not new to modern web dev and think this sounds like “UI as a function of state”, you are not wrong. The general architecture is inspired by React and Redux, but I still kept it framework-free and tried to apply the same patterns just with Vanilla JavaScript. While it was very refreshing to do it this way, I also quite quickly came to realize at what point frameworks actually come in handy again.

This is the pollState object in its initial state:

const pollState = {
  active: false,
  visible: false,
  title: 'Poll',
  options: {},
  userVotes: {},
};

And after a poll is created and users have already voted, it would look like this:

const pollState = {
  active: true,
  visible: true,
  title: 'Poll',
  options: { 1: 'Pizza', 2: 'Jam', 3: 'Coffee' },
  userVotes: { user1: '1', user2: '3', user3: '3' },
};

After which the render functions would render out the poll like this:

The poll has three options in this order: Pizza, Jam, Coffee. Pizza has one vote and thus 33% and Coffee has 3 votes and thus 67% of the votes. This is visualized as both text and also with filling "progress bars" in violet.
The poll has three options in this order: Pizza, Jam, Coffee. Pizza has one vote and thus 33% and Coffee has 3 votes and thus 67% of the votes. This is visualized as both text and also with filling "progress bars" in violet.

The Journey & Support

Building these tools was a lot of fun, but also quite a journey. It ultimately took me a few iterations. A few glitches and bugs were fixed after gathering community feedback, but the tools are still very basic and probably will evolve more with time (bug fixes included).

Most of my time wasn’t necessarily always spend in the core functionalities of the tools, but rather often in trying to overcome certain technical hurdles. Especially the question of how to make all of this usable by non-dev users, and at the same time keep it as customizable and DIY as possible for dev users or those that are curious to take a peek behind the curtain and check out the code.

The first feedback by some friends and the Twitch bubble was quite positive. When I saw the first streamers adopting the chat overlay and the twitch polling tool and redesigning the look of it by tweaking a few CSS values, it actually put a smile on my face.

Another really positive side effect: using the chat overlay on my stream actually spread awareness of the existence of the pronouns service and tools. Many streamers saw it and immediately wanted to share their pronouns as well. I love this.

These tools would not have been possible with the work and inspiration from these other people and their tools:

Feel free to contribute and send some support in the direction of these people.