loading...
Cover image for Implement a simple chat queue in JavaScript

Implement a simple chat queue in JavaScript

bdougieyo profile image Brian Douglas Updated on ・4 min read

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.

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.

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

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 logo open-sauced / beybot

This is a twitch bot built with the ComfyJS library 💅🏾

beybot

This is a twitch bot built on the ComfyJS (a wrapper around tmi.js).

How can I implement this in my chat?

Before I jump into the code, I need to share some of the streaming basics.

OBS Studio

OBS Studio is a free and open-source software for live-streaming and screen recording. This walk-through will show OBS Studio and consult your platform's documentation or community on how to add browser source plugins.

Browser Source plugins When you set up an out of the box alert or chat system, like Streamlabs, they require you to add something called a Browser Source plugin. To do this, you add the URL pointing to the plugin, provided in the Streamlabs dashboard. This is the backbone of most streaming interactions on Twitch. Browser source plugins are HTML, CSS, and some JavaScript—so basically webpages.

If you are looking to code live on stream, you probably…

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 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.

Join my live coding stream every Tuesday and Fridays on Twitch

Twitch 🎬 https://twitch.tv/bdougieYO

Posted on by:

bdougieyo profile

Brian Douglas

@bdougieyo

Brian is a developer advocate at GitHub, which means he likes chatting with developers about developer things and sometimes writes code.

Discussion

markdown guide
 

I am wondering if the loop function does not exceed the JS recursion limit? ~10k iteractions

I guess it does not really matter, than 10k messages is quite a lot, but why opting for a recursion instead of a simpler loop?

 

That is a great question, I was not aware of the recursion limit, but after a quick search, its 21k on Chrome. If I end up getting that amount of messages at one time on a Twitch stream, I will hit Twitch Partner and be making enough money to pay a developer full time to write some tests for this project.

Aside, OBS uses Chrome as its browser source. stackoverflow.com/questions/280517...

Regarding using a simpler loop, this code was interested in stphnnnn's project. The recursion is preferred because the function can run indefinitely and not start and terminate every time a chat is completed.

 

I am going to be honest, I have some doubts about the quality of this implementation, but it is great that it works for you :)