Implement a simple chat queue in JavaScript
Tags: twitch, javascript, tutorial, bot
Published: 2020-08-11 01:16:34

I wrote about a simple Twitch chatbot I built using some basic HTML, CSS, and JavaScript, this is the same bot I use on a stream and has not received chats from more than ten chatters at a time, until recently. Like normally websites, with popularity comes bugs, and I had a big one that did not limit the number of chat commands displayed on the screen. 

{% link https://dev.to/bdougieyo/building-a-twitch-chatbot-easy-using-html-and-javascript-2dn %}


This post will cover how I implemented a simple queue service in JavaScript without any 3rd party libraries. This implementation is heavily inspired (copied) from another Twitch streamer's chat integration to display animal gifs.

{% github stphnnnn/obs-twitch-chat-animal-gif-overlay no-readme %}

## Simplicity first

My original goal for the chatbot was not to include frameworks or npm packages to keep the project approachable. Twitch streaming is not known to be a place for code, but for gaming and I did not want to limit the use case for the bot to be limited to the experience of only competent programmers. Anyone with the ability to copy and paste will be able to leverage this.

## The queue is what the US calls an array (I know it is not, JK)

That is not entirely true, but I am aware of the Queen’s English term for the queue for what we Americans call “getting in line.” This idea is exactly what I needed to prevent chatter command collisions. 

When a chatter sends a command to the Twitch chatbot, I would like their request to wait (queue) until the previously completed command. 

![chat being used](https://user-images.githubusercontent.com/5713670/88496209-4db23480-cf71-11ea-83c9-8c9b9fbc82d8.gif)

This implementation uses an array to do that keep the queuing in order. This is beneficial because Arrays are ordered by position and this is mostly predictability (not guaranteed though, but that is ok). If Garland sends a command to the chat and Ethel sends a command 3 seconds later, I would expect Garland’s chat command to loops while Ethel’s waits to begin once Garland’s has finished. 

The `shift()` function provides the ability to queue up the next request and ensure the command triggered with the first-in, first-out (FIFO) method. 

## What we are we working with

To make the chatbot work in OBS, it needs to be HTML. This HTML could be built to leverage Webpack or similar, but I opted for just standard HTML and JavaScript to powers this bot so it could scale to whatever framework you would like. The JavaScript code powers the insertion of the alerts with DOM interactions. 


    const container = document.querySelector(".alerts");
    const DISPLAY_DURATION = 10 * 1000; // 10 seconds
    
    function gifAlert(user, gif, audio, type) {
      queue.add(async () => {
        audio.play();
        container.innerHTML = `
          <h1 class="text-shadows">${user + generateTitle[type]}</h1>
          <img src="${gif}" />
        `;
        container.style.opacity = 1;
    
        await wait(DISPLAY_DURATION);
    
        if (!queue.isLooping) {
          container.style.opacity = 0;
        }
    
      });
    }

## Respect the loop

When a chatter sends a command it is looped for 10000ms or 10 seconds. The alert will show on the screen after activated by the corresponding chat commend. 

The loop works with two parts. The first setting a boolean to let the chatbot know that it is already working on something. The second part is the `await` that prevents the gifAlert from being removed for the DURATION. The final component is where the loop function triggers the gifAlert.


    this.loop = async () => {
      isLooping = true;
    
      const item = queue[0];
      queue.shift();
      await item();
    
      if (!queue.length || isPaused) {
        isLooping = false;
        return;
      }
    
      this.loop();
    };


## Add, remove, and pause the queue

Something I did not consider in my original implementation was unable to pause the commands if things got unruly. Similar to the **DISPLAY_DURATION,** I have a  **PAUSE_DURATION** that prevents commands from displaying. 


    this.pause = (duration = 0) => {
      isPaused = true;
      setTimeout(() => (isPaused = false), duration);
    };


## Finally the implementation

Instead of inserting using the `addElement`, we are now leveraging innerHTML. There was inconsistency in how the elements get added and removed. This changed has fixed that and created the consistency that I needed to see. 

The `add()` is added using an async function and leverages the power of await to act the waiting function to make this all work with low effort and not a ton of code.


    function gifAlert(user, gif, audio, type) {
      queue.add(async () => {
        audio.play();
        container.innerHTML = `
          <h1 class="text-shadows">${user + generateTitle[type]}</h1>
          <img src="${gif}" />
        `;
        container.style.opacity = 1;
    
        await wait(DISPLAY_DURATION);
    
        if (!queue.isLooping) {
          container.style.opacity = 0;
        }
    
      });
    }


## The best way to learn is forking and trying yourself

{% github open-sauced/beybot %}

If you want to try this out, you can fork the project and deploy using GitHub pages to try this out. Remember to replace the Twitch handle with your’s or visit my [Twitch chat](https://twitch.tv/bdougieyo) to test, don’t worry, I don’t mind.

If you would like to see the chat built with Vue, React, or Svelte, let me know in a comment below.

{% twitter 1292524843125166081 %}

Join my live coding stream every Tuesday and Fridays on Twitch

Twitch 🎬  https://twitch.tv/bdougieYO