Let's make a Twitch bot with Python!
Tags: python, tutorial, beginners, twitch
Published: 2019-10-18 11:35:00

This tutorial gets you up and running with a simple chat bot for Twitch channel.

## Who's this tutorial for?

**Beginners to coding and experienced coders new to Python.**

## Contents

We'll start out by setting up the accounts, getting the secrets, and installing the softywares. Then we'll setup and code the bot. By the end of this tutorial you should be ready to start adding your own custom commands.

BUT FIRST... we **need** to make sure we have our credentials in order. 👏

## Papers, please!

#### 📢 [Glory to Arstotzka!](https://www.youtube.com/watch?v=Kd2b7PYyZic)

1. Make an account on [Twitch](https://twitch.tv/) (for your bot) or use the one you stream with. Make it something cool like `RealStreamer69` 😎
2. [Request an oauth code](https://twitchapps.com/tmi/). You'll need to login and give the app permissions to generate it for you.
3. [Register your app with Twitch dev](https://dev.twitch.tv/console/apps/create) and request a client-id (so you can interface with Twitch's API)

Keep the oauth and client id somewhere handy but not public. You'll need them later to configure the bot.

> 💡 *PROTIP™* -- *Keep it secret. Keep it safe.*

## Install all the things! 🧹

1. Install Python 3.6 or 3.7 -- [Windows](https://www.python.org/downloads/release/python-375/) // [Linux](https://www.python.org/downloads/release/python-375/) // [OS X](https://www.python.org/downloads/release/python-375/)
2. Install PIPENV. In the console, run ⇒ `pip install pipenv`

### Create a cozy home for the bot to live in

Virtual environments require a couple extra steps to set up but make developing Python apps a breeze. For this tutorial, we'll use [PIPENV](https://github.com/pypa/pipenv) which marries pip and venv into a single package.

- In the console, navigate to your working directory
- Run ⇒ `pipenv --python 3.6` or  `pipenv --python 3.7`
  - This is going to create `pipfile` and `piplock`. They hold venv info like Python version and libraries you install.
- Then run ⇒ `pipenv install twitchio`

## Configuring and authorizing the bot
Create 2 files in your working directory. One called `bot.py` and another called  `.env` (no file name, just the extension - it's weird, I know).


### `/.env`

Your secrets will live inside the `.env` file. Add the oauth token and client-id from above after the `=` in the file. Fill in the other vars as well.

```bash
# .env
TMI_TOKEN=oauth:
CLIENT_ID=
BOT_NICK=
BOT_PREFIX=!
CHANNEL=
```
### `/bot.py`

Inside `bot.py`, import the libraries we'll need and create the bot obj that we'll start in the next step.

```python
# bot.py
import os # for importing env vars for the bot to use
from twitchio.ext import commands

bot = commands.Bot(
    # set up the bot
    irc_token=os.environ['TMI_TOKEN'],
    client_id=os.environ['CLIENT_ID'],
    nick=os.environ['BOT_NICK'],
    prefix=os.environ['BOT_PREFIX'],
    initial_channels=[os.environ['CHANNEL']]
)
```
> 💡 *PROTIP™* -- When we run `bot.py` using PIPENV, it first loads the variables from the `.env` file into the virtual environment and then runs `bot.py`. So, inside this venv, we have acess (on an instance-basis) to these variables. We're going to use python's `os` module to import them, just like we would import environment variables on our native environment.

**At the bottom** of the file, we need to make sure the bot runs when we call `bot.py` directly using `if __name__ == "__main__":`

```python
# bot.py
if __name__ == "__main__":
    bot.run()
```

## WAKE BOT UP (wake bot up inside!)
Let's test the bot and make sure it can connect to Twitch.

- In the console, run ⇒ `pipenv run python bot.py`

**If it worked**, you shouldn't get any errors - that means the environment variables loaded correctly and your bot successfully connected to Twitch!

 **If you got errors**, check out the next section before moving on.

<!-- Handle more issues later after feedback from comments -->
## Error: Can't wake up. [save me]

A wild `Request to join the channel has timed out. Make sure the channel exists.` appears. You evade with..

1. Make sure you have the right tokens (oauth and client-id) in the .env file and that they're in the same directory/folder as `bot.py`

2. Your directory structure at this point should look like this...
<!-- DIRSTRUCTURE_START_MARKER -->
<pre>
working-directory/
├─ .env
├─ bot.py
├─ Pipfile
└─ Pipfile.lock
</pre>
<!-- DIRSTRUCTURE_END_MARKER -->

3. If that still doesn't fix it, comment below and we'll sort it out for ya!

## Adding some functionality to the bot

### Greet the chat room!
Back to `bot.py`.... Below the bot object, let's create a function called `event_ready` with the decorator `@bot.event`. This function will run once when we start the bot. It then reports to terminal and chat that it successfully connected to Twitch.

```python
# bot.py, below bot object
@bot.event
async def event_ready():
    'Called once when the bot goes online.'
    print(f"{os.environ['BOT_NICK']} is online!")
    ws = bot._ws  # this is only needed to send messages within event_ready
    await ws.send_privmsg(os.environ['CHANNEL'], f"/me has landed!")
```

Go ahead and **test the bot again**. It should greet chat when it comes online now.

![landed](https://i.imgur.com/bkHPOj2.png)

### Respond to messages in chat

Next up, we're going to add a function that's run every time a message is sent in your channel. You can add all sorts of logic here later, but we'll start out with making sure the bot ignores itself.

```python
# bot.py, below event_ready
@bot.event
async def event_message(ctx):
    'Runs every time a message is sent in chat.'

    # make sure the bot ignores itself and the streamer
    if ctx.author.name.lower() == os.environ['BOT_NICK'].lower():
        return
```

After that, we'll drop in a line of code that will annoyingly echo back every message sent in chat. ᴷᵃᵖᵖᵃ

```python
# bot.py, in event_message, below the bot-ignoring stuff
await ctx.channel.send(ctx.content)
```
Restart the bot and check it out!

![rip](https://i.imgur.com/mV0hkBD.png)

> 💡 *PROTIP™* -- Comment out that line now cuz it's actually really annoying.

### Making a chat command

Any command you make needs to follow this format when defining them..

- Decorated with `@bot.command(name='whatever')`
- Be asynchronous functions with names that match the `name` variable in the decorator
- Pass the message context in through the function

How the function works and what it does is all up to you. For this example, we'll create a command called `!test` that says `test passed!` in chat when we call it.

```python
# bot.py, below event_message function
@bot.command(name='test')
async def test(ctx):
    await ctx.send('test passed!')
```

Before this can work, we need to make sure that the bot knows to listen for commands coming through.

Add this just below the ignore bot code in `event_message`:
```python
    #bot.py, in event_message, below the bot ignore stuffs
    await bot.handle_commands(ctx)
```

Alright! Time to test it out. Reboot the bot and send `!test` in chat!

![test passed](https://i.imgur.com/9feADGt.png)

### Responding to specific messages

> *Tell my bot I said... "Hello."*

You can respond to specific messages in your chat too, they don't have to be `!commands`. Let's write some code that says hi when people say hello.

```python
    # bot.py, at the bottom of event_message
    if 'hello' in ctx.content.lower():
        await ctx.channel.send(f"Hi, @{ctx.author.name}!")
```

Go ahead and test it out! You've got the framework to start buildng your bot and adding commands.

![tell my bot i said "hello."](https://i.imgur.com/Z2FXTpJ.png)

## Here's what you should have when you're done

### `/bot.py`

```python
import os
from twitchio.ext import commands

# set up the bot
bot = commands.Bot(
    irc_token=os.environ['TMI_TOKEN'],
    client_id=os.environ['CLIENT_ID'],
    nick=os.environ['BOT_NICK'],
    prefix=os.environ['BOT_PREFIX'],
    initial_channels=[os.environ['CHANNEL']]
)

@bot.event
async def event_ready():
    'Called once when the bot goes online.'
    print(f"{os.environ['BOT_NICK']} is online!")
    ws = bot._ws  # this is only needed to send messages within event_ready
    await ws.send_privmsg(os.environ['CHANNEL'], f"/me has landed!")


@bot.event
async def event_message(ctx):
    'Runs every time a message is sent in chat.'

    # make sure the bot ignores itself and the streamer
    if ctx.author.name.lower() == os.environ['BOT_NICK'].lower():
        return

    await bot.handle_commands(ctx)

    # await ctx.channel.send(ctx.content)

    if 'hello' in ctx.content.lower():
        await ctx.channel.send(f"Hi, @{ctx.author.name}!")


@bot.command(name='test')
async def test(ctx):
    await ctx.send('test passed!')


if __name__ == "__main__":
    bot.run()
```

And ofc your `.env` with your secrets and a `pipfile` and `Piplock`.

**I've uploaded the files to a [github repo](https://github.com/NinjaBunny9000/barebones-twitch-bot/pull/1) too, if that's your thing.**

## Congrats!! 🥳🎉

You've made it this far.. You know what that means? Time to celebrate by [clicking this GitKraken referral link](http://bit.ly/SOCKSPLS) and signing up so I can get [free socks](https://www.gitkraken.com/referrals) (or maybe a Tesla? 😎).

Also, feel free to check out the [Live Coding Stream recap](https://dev.to/ninjabunny9000/live-coding-recap-make-a-twitch-bot-with-python-2coe) where we developed this tutorial. Shoutouts to everyone in chat that collaborated!

## What do you want to do next?

**Questions? Comments? Ideas? Let me know in the comments below!**

I'll be following up to this post soon with some tips on how to get the most out of the TwitchIO library -- more stuff that's not really well-documented and was a PITA to figure out, like how to get the author, using badges for permissions, etc.