Writing a Twitch Bot From Scratch in C# (.NET Core)

Bradley Saunders
The Startup
Published in
9 min readNov 20, 2020

I’ve written a few Twitch bots and I’ve almost always used whatever library comes up first in a Google search. Using a library is great for getting things started but I’ve found that I usually run into issues with the library later on. Ultimately I’ve realized that for something simple like a Twitch bot a library isn’t necessary.

How A Twitch Chat Bot Works

Twitch bots are very simple. They use Internet Relay Chat Protocol(IRC) to communicate with Twitch’s servers. This is a simple protocol that uses plain text messages as commands. For example sending a message is as simple as writing

PRIVMSG #twitchchannelname :message goes here!

That would send a message in the chat of twitch.tv/twitchnamehere, but before you can just send messages like this you need to tell Twitch who you are, basically you need to login.

How Do You “Login” To an IRC Server?

Whenever you have a question about how to do “X” in IRC, you should look at the IRC specification. Don’t worry it’s not very complicated, and luckily in the context of Twitch bots most of these commands are not used or are not needed. What we’re looking for is in Section 4.1 Connection Registration.

We’re looking for the PASS and NICK commands. You might find other Twitch IRC libraries using the USER command but it is not necessary.

As the specification says, we just need to send a PASS command followed by a NICK command, so

PASS secretpasswordhere
NICK simple_irc_bot_name

So what password should you be using here? Well if we check Twitch’s IRC guide you’ll see they say to use an OAuth token. An OAuth token is basically a password that Twitch gives you to authenticate with their APIs.

This is a good time to create a new Twitch account if you haven’t already. Unless you want your main account talking as a bot it’s good practice to have an account specifically for it. You just setup an account like any other twitch account. Once you’re done with that you’re ready to get an OAuth token.

Make sure you’re logged into your bot account and then head to https://twitchapps.com/tmi/. Once there just click Connect and then authorize with Twitch and you’ll get an OAuth token. Write this down somewhere and make sure to keep it secret. People could control your chat bot if they had this.

Getting Into the Code

Now that we’ve got a bot account setup, an OAuth token, and we know what we need to do let’s actually get into writing some C# code.

We’re going to be using .NET Core 3.1 with C# 8 language version. If you’re using .NET Framework you might need to adapt a few things to get this working. I’m using .NET Core 3 because it’s multiplatform and once .NET 5 roles around there’s not going to be a Core/Framework distinction anymore, just one runtime similar to .NET Core. So what we learn here will be useful in the upcoming versions.

Firstly we’re just going to create a simple console application. You should find presets for this in any editor(Visual studio/ Visual studio code/Rider). I’ll personally be using Rider but it doesn’t really matter.

Once you’ve setup a project in your preferred editor you should have a simple hello world console application.

We’re going to be writing an async friendly IRC bot, so we’re going to need to change the main method to be async. If you’re not familiar with asynchronous code in C# you can read about it here, but put simply it allows you to pause execution and let other code run while you’re waiting for things that take awhile like getting something from the hard drive or waiting for network messages.

You might feel like you’d rather use normal synchronous C# code, I know I did for a long time, but writing this bot using async will be hugely beneficial. Using async will allow our bot to use less resources and run multiple bots in the same program. In fact, the bot will sit at 0% CPU usage while it’s waiting on messages while still allowing other code to be run.

There’s usually a non-async version of every async call if you really feel like using them.

Converting Main to an asynchronous method is as simple as replacing void with async task

Now we’re going to need some variables to hold our login info among other things. These can go inside the Main method or can be a field, it’s up to you. We’ll refactor everything into a class later.

Note: It’s probably a bad idea to keep your OAuth code in plain text in your source code. An easy way to store it is to use environment variables and get the OAuth using something like Environment.GetEnvironmentVariable(“TWITCH_OAUTH”)

The IP and port for Twitch’s IRC server comes from here. For this guide we’re not using SSL so we’re using port 6667.

Connecting to the IRC Server

We use TCP to connect to IRC servers so we’ll create a TcpClient and connect to the IP and port we just setup.

Now we’re connected to the Twitch IRC servers, but we need a way to read and send messages. TcpClient has a tcpClient.GetStream() which we both read and write to. We’re going to wrap that in the StreamWriter and StreamReader class for convenience.

As the comments say we’re going to use NewLine and AutoFlush to make the code a bit shorter and avoid forgetting \r\n on each line.

Now that we have our streams setup we can start writing messages. So like I said before we just need to send a PASS and NICK message and then we can start sending other commands.

If you’re not familiar with the $ string syntax (string interpolation), basically that allows you to write variables in the curly braces and it will fill in the variable in that spot. So that is equivalent to await streamWriter.WriteLineAsync(”PASS ” + password);

You might think we need to JOIN a the channel for our Twitch channel in order to send messages there, but in fact you can just start sending messages without joining. This is great if you only want to send messages and don’t need to listen to messages from chat, saving you CPU cycles and bandwidth.

So let’s test our bot works by sending a message. You can check the IRC spec for PRIVMSG for more details but all you really need to do is this

Note: The colon here is important. It always goes PRIVMSG #channelname :message

Replace twitchchannelname with whatever channel you want to send a message in and your bot should now send that message in that channel.

Even if all you need is to send messages you still need to listen to some messages from Twitch’s servers. Specifically the PING command. Twitch will send this every once in a while to check if the connection is still active, and Twitch expects you to respond with a PONG command.

Reading Messages

In order to reply to PING commands we’re going to need to read messages that Twitch is sending our way. We have our streamReader for that.

If you’re not familiar with async code you might think an infinite loop is bad, but actually it will pause on line 3 until it receives a message. We’ll move this to it’s own method later when we refactor things and it won’t block execution anymore.

Once you run this you should see some messages from Twitch

:tmi.twitch.tv 001 simple_irc_bot :Welcome, GLHF!
:tmi.twitch.tv 002 simple_irc_bot :Your host is tmi.twitch.tv
:tmi.twitch.tv 003 simple_irc_bot :This server is rather new
:tmi.twitch.tv 004 simple_irc_bot :-
:tmi.twitch.tv 375 simple_irc_bot :-
:tmi.twitch.tv 372 simple_irc_bot :You are in a maze of twisty passages, all alike.
:tmi.twitch.tv 376 simple_irc_bot :>

These are basically the “Message of the Day,” but for us these don’t matter. It does let you know things are working though.

If you let this run for a few minutes or so you’ll get a PING message from twitch, and if you run it for too long Twitch will close the connection.

PING :tmi.twitch.tv

In order to respond to this we can just check if the message starts with PING and send a PONG back

We’re splitting the line “PING :tmi.twitch.tv” based on where spaces are giving us an array with “PING” and “:tmi.twitch.tv” in it. We’ll make use of this to grab parts of the message and decide what to do with it.

Now that we’re replying to PINGs, Twitch shouldn’t close the connection anymore.

Reading Chat Messages

First in order to read chat messages we need to use the JOIN command, effectively subscribing us to any messages sent in that twitch channel. Let’s modify our initial login to JOIN our channel too

Now if you log into your other account and send a message you’ll see something like this

:tmi.twitch.tv 001 simple_irc_bot :Welcome, GLHF!
:tmi.twitch.tv 002 simple_irc_bot :Your host is tmi.twitch.tv
:tmi.twitch.tv 003 simple_irc_bot :This server is rather new
:tmi.twitch.tv 004 simple_irc_bot :-
:tmi.twitch.tv 375 simple_irc_bot :-
:tmi.twitch.tv 372 simple_irc_bot :You are in a maze of twisty passages, all alike.
:tmi.twitch.tv 376 simple_irc_bot :>
:simple_irc_bot!simple_irc_bot@simple_irc_bot.tmi.twitch.tv JOIN #mytwitchchannel
:simple_irc_bot.tmi.twitch.tv 353 simple_irc_bot = #mytwitchchannel :simple_irc_bot
:simple_irc_bot.tmi.twitch.tv 366 simple_irc_bot #mytwitchchannel :End of /NAMES list
:mytwitchchannel!mytwitchchannel@mytwitchchannel.tmi.twitch.tv PRIVMSG #mytwitchchannel :test

The important lines here are

:simple_irc_bot!simple_irc_bot@simple_irc_bot.tmi.twitch.tv JOIN #mytwitchchannel

This is the message you get once you successfully join a channel

:mytwitchchannel!mytwitchchannel@mytwitchchannel.tmi.twitch.tv PRIVMSG #mytwitchchannel :test

This is me sending a message that said “test” in my Twitch channel.

So with the split variable we made earlier we can check if a message is a PRIVMSG by checking ifsplit[1] (the second element) is PRIVMSG, and then the message contents are everything past the 2nd colon. We can grab the Username from between the first colon and exclamation point like in here:mytwitchchannel!

Now we basically have a bot that can send message, read message, and respond to PINGs. That’s all we really need. Let’s get to refactoring this.

Refactoring Our Work So Far

Let’s make sure we’re on the same page, here’s what we have so far

~50 lines of code for a Twitch bot isn’t so bad. Let’s move things around. Firstly let’s create a class for our Twitch bot and create a Start task we can run to start the bot

Now we can run the bot in our main program like this

Note that we still haven’t gotten the bot running in the background. That last Console.WriteLine is never run.

In order to run it in the background we can remove the await, but then our program ends immediately. This is because it hits the end of main and the program ends. We can pause execution with a Task.Delay(-1) and later we can run more than one bot here.

Note: The twitchBot.Start(); line will give you a warning here, and rightfully so. You will not get any exceptions from the Task you just ran. A good solution is to use this package for an extension method that helps solve this problem. Also watch the associated talk on youtube if you’re curious.

You can open the NuGet window in your editor and search “AsyncAwaitBestPractices” and install that, then change it to

Now any exceptions will be thrown back to the main program, there won’t be a warning, and your intention to fire and forget that task is clearly visible.

Sending Messages And Joining channels

You might think you can just add this

public async Task SendMessage(string channel, string message)
{
await streamWriter.WriteLineAsync($"PRIVMSG #{channel} :{message}");
}

public async Task JoinChannel(string channel)
{
await streamWriter.WriteLineAsync($"JOIN #{channel}");
}

But this is not correct

If you try to do

twitchBot.Start().SafeFireAndForget();
await twitchBot.JoinChannel("twitchchannelname");
await twitchBot.SendMessage("twitchchannelname", "Hey my bot has started up");

You might get a null reference exception. If the Start task hasn’t finished connecting before you try to send a message streamWriter will be null. We need to wait for the Start method to finish. We could use .ContinueWith() but that means we have to make sure to do that every time. An easier way is to use a TaskCompletetionSource

Now we’re waiting to be connected before joining or sending messages. This is basically all you need for a Twitch Chat bot.

Final Code

Here’s the final code with the addition of an event for listening to messages from outside the bot class.

A Small Addition

After writing this I became curious what connecting via SSL looked like and I had trouble finding any info on that, so I definitely need to add this here.

Switching to using SSL is as simple as changing the port from 6667 to 6697, and then after connecting wrap the TcpClient’s stream in an SslStream, then call sslStream.AuthenticateAsClientAsync(ip). Also make sure to pass the new SslStream to the StreamReader and StreamWriter constructor.

Since this is so simple there’s not much reason not to do it. You are sending an OAuth key that is supposed to be kept secret, so using a secure protocol makes sense.

--

--