How to make a Twitch profanity filter Chrome extension
By: Nintendo Engineer
Tags: Twitch, Chrome, Google, Extension, Programming

I do a lot of Twitch related projects here and there, trying to see what the possibilities are. One of my latest projects involved creating a Twitch Emote Extension for Chrome where I showed how to get extra emote “slots” for free. That got me into browser extensions, so I figured let’s show how to make one from scratch by building a profanity filter for the Twitch chat.

At the end of this, we want to have an extension that, when installed, checks for a set of flagged words in chat and replaces them with more friendly versions. This could be useful for children watching streams, for instance.

If you just want the code for this extension, there is a link to the end result at the bottom of this article.

Tinker Time

Alright, so let’s get into it head first. Create a folder somewhere you like and call it profanity-filter. I created mine in a place where I sync up with GitHub.

Any name will do, but this seems fitting

Now inside this folder, create two new files: manifest.json and content.js. You’ll see how we’ll use them as we get to building the extension.

There we go

Open up manifest.json, which is the file Google looks at for metadata regarding your extension, and paste the following into it:

{ "manifest_version": 2, "name": "Twitch Profanity Filter Extension", "version": "0.1", "content_scripts": [ { "matches": ["https://www.twitch.tv/*"], "js": ["content.js"] } ] }

As you can see, we are telling Google that this extension should work on any page from Twitch and we’re also saying that the logic that should be run on these pages resides in the file called content.js. Note that I put the version as 0.1, as this is my initial try.

Getting the logic in

Now open up content.js in the text editor of your choice. I would highly recommend Visual Studio Code, it’s my go-to text editor for anything coding related and it’s free.

Now, the way we’re going to set this up is in a few steps. What do we want to achieve and how are we going to accomplish that? We know we want to change certain words to others, which means reading the chat messages, scanning them for flagged words and then updating those words to more friendly ones. So when the page loads, we want our logic to be able to do the following:

Find the chatbox

Every time a new chat message appears, get the message or message container

Change flagged words to their friendly counterparts

So let’s get to it. The first part is actually activating the script when the page, or more specifically the window, has been loaded. In javascript, you can hook into that loading event and we are going to do just that. Update your content.js to contain the following:

That will make sure that whatever we put inside that function, gets called when the page has loaded.

Find the chatbox

So how do we find the chatbox? Well, in the Chrome version of the Twitch website, the chatbox is contained in a <div> element that has a class called chat-scrollable-area__message-container. We are going to get that element, or node, through javascript. Update the file to look like this:

The reason we add .item(0) behind it, is because getElementsByClassName() returns an array of elements. Since we know only one element has this class, we take it from the array by specifiying we want the first thing (most programming languages start counting at 0).

Get every new message in chat

This is a bit more tricky to understand if you’re not used to programming or not used to observer patterns. The way we are going to get every new message is by using a MutationObserver on the chatbox node we just picked up. Update the file to match the following and then we’ll walk through what’s going on:

First, on line 4, we define a callback function that takes a list of mutations and the observer we’ll create on line 8. The logic in this function is what gets called whenever something changes, or mutates, in the node we are observing.

On line 8 we create the observer and pass it our callback function. Then, on line 9, we tell the observer to start observing our target node and we pass a configuration that says we are only interested in mutations to the childList. Messages appear as child nodes in the chatbox node we are looking at, so our callback function will get triggered every time a new chat message appears. That’s exactly what we want!

But now we still need to get the actual message elements and for that we add one more line of code in our callback:

The name might seem a bit weird, but all text parts of a message (we are not scanning emotes, just the actual texts) are placed in <span> HTML elements that have the class text-fragment. Also, because this function gets triggered at every new message, we know the message we want must be the last element in the chatbox node, so that’s why we’re grabbing the lastElementChild.

Change flagged words to their friendly counterparts

Let’s get to the juicy bit. If we want to change flagged words, we first need something that tells us what flagged words and their counterparts are. We’ll do this by using a simple dictionary. Update the file to look like this:

You can add more words as you like, just remember the comma at the end

Now we are going to read the actual text in all the text-fragment elements, loop over all forbidden words and replace them if necessary. Let’s get that loop started, so update your callback function with the following:

What’s happening here is that we are looping through all these elements that contain text. On line 18 we are taking one specific element at a time and on line 19 we get the actual text from it in lower case, because our flagged words are all in lower case as well. It makes it easier for comparison purposes, but in a more advanced version we’d take care of neatly maintaining casing. Now that we have our text, we want to loop over our flagged words and check if they’re present. If so, we delete them. Update the function:

The dictionary we made works with key-value pairs, where the keys are the flagged words and the values are the friendly words, so on line 21 we’re simply getting an array of all the keys, which is the the array of flaggedwords we want to loop over. In the loop we grab the flagged word we currently are looking at and if our text includes one or more of them, we grab the friendly word and update our text by replacing the flagged words. That’s almost all, now we just need to take our new and improved text and put it back into the element we grabbed it from:

On line 31 we have now placed the text back into the element and that is it! Let’s get to testing this in Chrome.

The test

Open Chrome and browse to chrome://extensions. You should see your extensions and in the top right you’ll see Developer mode. Toggle it on if it isn’t yet. Afterwards, in the top right, you’ll see the Load unpacked option. We are going to use that to load in our local extension without having to go through the Chrome Web Store route just yet. Click on it and select the folder where the files are located:

That should make it appear instantly in our extension list:

Good! Now let’s head over to any channel where we can chat. I’ll go to my friend Bjarke’s channel, a hilarious Danish streamer, and try it out:

There we go, it works! Our very own profanity filter. Wasn’t that hard, right? All that’s left is to release it to the Chrome Web Store if you want to. I’ll be expanding on this list of words and releasing this, so if you don’t need more than this, you don’t have to release it, seeing as it requires a fee to get into the program. I won’t be going into how the release works, but if you are interested, I made a Twitch Emote Extension to get your own limitless emote slots and this article explains how to release extensions at the end.

If you want to see all the code for this extension, check out the GitHub repo here. Merry coding!