NOTE: Under Construction - I'm in the middle of upgrading my site and lots of stuff is kinda broken. Please forgive the mess.

Twitch Chat Single Message Bot With TMI.js

July 2022


This seems to work, but I still consider it suspect since I can't tell from the early work why sometimes the process would quit properly and sometimes it wouldn't. The sleep timer seems to take care of it but it feels like if some part of the network communication takes to long it might not close.


I'm playing around with an idea for a single Twitch bot that acts as a single place for folks to receive EventSub notifications from Twitch and then forward them on to chat so everybody doesn't have to setup their own public server to receive the webhooks.

I don't want to stay connected to all the different chat channels all the time. The goal is to login, forward the message and then close the connection.

After a fair amount of banging around, this is what I ended up with:

#!/usr/bin/env node

  const tmi = require('tmi.js')
  const { execSync } = require('child_process')

  const botname = 'thebotofalan'
  const channelName = 'theidofalan'

  const oauth_token = execSync(
    'security find-generic-password -w -a alan -s alan--twitch--oauth'

  function sleep(milliseconds) {
    const date = Date.now()
    let currentDate = null
    do {
      currentDate = Date.now()
    } while (currentDate - date < milliseconds)

  let client = new tmi.Client({
    options: { 
      debug: true,
    identity: {
      username: botname,
      password: oauth_token,
    channels: [channelName],


  client.on('join', (channel, username, self) => {
    if (username === botname) {
        client.say(channelName, `this is the message`)

You'll need to install tmi.js with:

npm i tmi.js

You'll also need to setup a Twitch App with an OAuth token that has the proper scopes for getting to chat (currently chat:read and `chat:edit`). The first steps of the related https://twurple.js.org/docs/examples/chat/basic-bot.html ] [ twurple bot guide shows how to do that


- I store my passwords and credentials in the macOS built-in Keychain Access app. The security call from execSync is accessing it here

- There's probably a better way to do the sleep

- I'm not sure if the sleep in the join event before the .disconnect() is necessary. There seemed to be timing issues at some points figuring this out so I'm leaving it in regardless

- The 9sec. (9000ms) sleep is to give some time to connect if it fails to connect the first time which I've seen before.

═══ § ═══