npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

chinterface

v3.0.10

Published

Universal Chat interface library for popular stream services

Readme

Chinterface (Chat Interface)

A unified interface for Chats around the world.

Services Supported

  • Twitch (Read/Write)
  • YouTube (Read/Write)
  • YouTube Live (Read/Write)
  • Facebook Live (Read/Write)

Notes

It is recommend to apply the following styles to emotes across all services.

.emoticon {
  vertical-align: middle;
  display: inline-block;
}

Twitch

This interface connects to Twitch's chat socket server. New messages arrive instantly as they are sent on Twitch.

Setup and Usage

// Twitch.

import chinterface from 'chinterface';

async function twitch() {
  const twitch = chinterface.service('twitch');

  try {
    // Set the default config values.
    await twitch.setConfig({
      // Main configuration
      clientId: 'as545trdsFdgghNXoyit83nguf11gpOx', // REQUIRED. Your Twitch Application Client ID.
      channel: 'oyed', // REQUIRED. If you do not know the channel, pass through accessToken and make a request to loadUser().
      userId: 123456, // Optional; REQUIRED to fetch badges. If you do not know the user ID, pass through accessToken and make a request to loadUser().

      // Settings
      reconnect: false, // Default: true [auto reconnect if connection is lost]
      parseEmoticon: false, // Default: true [convert emoticons to images]
      formatMessages: false, // Default: true [process message and convert emoticons; body will be null if false]

      // Optional; REQUIRED for sending/writing messages
      username: 'oyed', // The user's Twitch username. This is ONLY used to authenticate to send messages.
      accessToken: 'as545trdsFdgghNXoyit83nguf11gpOx', // The user's Twitch API access token. This is ONLY used to authenticate to send messages.
    });

    // If you do not know the username, channel or userId, make a request to loadUser(). It will set these values for you.
    await twitch.loadUser();

    // Connect to the socket server.
    await twitch.connect();

    // To receive and listen for new, formatted messages.
    twitch.on('message', (data) => {
      /**
       * `data` will always return the following structure:
       *
       * {string} id - The chat message ID; generated by Twitch.
       * {string} username
       * {string} body - The processed message, with <img> tags for emotes.
       * {string} raw - The unprocessed message, direct from the service.
       * {number} timestamp - The time in milliseconds.
       * {Object} extra - Service-specific information.
       */
      console.log('New message', data);
    });

    // To send a message.
    twitch.send('test message');

    // Disconnect from the socket server.
    await twitch.disconnect();
  } catch (e) {
    console.log(e);
  }
}

Chinterface provides a set of unique events and functions that assist in the display and handling of messages for Twitch.

Fetch and Set Twitch User Information

In cases where you do not know the userId, channel or username, you can use the loadUser() function to set the config for you.

Requires clientId and accessToken. This function will NOT overwrite any previously set properties.

await twitch.loadUser();

Accessing TMI Events

Chinterface provides a clean wrapper to directly access TMI Events. This is useful when you want to listen to clearchat, timeouts or bans.

See here for a list of events: https://docs.tmijs.org/v1.2.1/Events.html

twitch.clientOn('timeout', (channel, username, reason, duration) => {
    // Remove all messages for the given channel and username
});

twitch.clientOn('ban', (channel, username, reason) => {
    // Remove all messages for the given channel and username
]);

twitch.clientOn('clearchat', channel => {
    // Remove all messages for the given channel
});

Badges

It's common to output a user's associated badges when displaying Twitch messages. You may use the getBadges() function to return a list of badges and respective URLs for the connected channel.

Requires clientId and userId. If you do not know the user ID, make a request to loadUser() first.

// Get all badges for all connected Twitch channels
const badges = await twitch.getBadges();

YouTube

This interface polls the YouTube API every interval milliseconds. New messages arrive periodically.

REMEMBER. YouTube auth tokens expire every 1 hour. You MUST listen for the refresh-token event and update the config value when you encounter this event.

Setup and Usage

// YouTube

import chinterface from 'chinterface';

async function youtube() {
  const youtube = chinterface.service('youtube');

  try {
    // Set the default config values.
    await youtube.setConfig({
      // Main configuration
      accessToken: '', // REQUIRED.
      liveChatId: 'EiEKGFVDVW1mV2tPSWxoek1fZDA2cXpvc2lBQRIFL2xpdmU', // REQUIRED. If unknown, set accessToken and make a request to loadChatId() before connecting.

      // Settings
      parseEmoticon: false, // Default: true [convert emoticons to images]
      parseUrl: false, // Default: true [convert URLs to anchor tags]
      formatMessages: false, // Default: true [process message and convert emoticons; body will be null if false]
      interval: 7000, // Default: 5000 [time in ms to check for next set of messages]
      maxResults: 500, // Default: 200 [the number of messages to return each check]
      profileImageSize: 32, // Default: 64 [size in pixels to return profile images at]
    });

    // If you do not know the liveChatId, set accessToken and make a request to this function.
    // If you already know the liveChatId, this function is NOT required.
    await youtube.loadChatId();

    // Start fetching new messages.
    youtube.connect();

    // To receive and listen for new, formatted messages.
    youtube.on('message', (data) => {
      /**
       * `data` will always return the following structure:
       *
       * {string} id - The chat message ID; generated by YouTube.
       * {string} username
       * {string} body - The processed message.
       * {string} raw - The unprocessed message, direct from the service.
       * {number} timestamp - The time in milliseconds.
       * {Object} extra - Service-specific information.
       */
      console.log('New message', data);
    });

    // To send a message.
    youtube.send('test message');

    // Listen for the refresh token event.
    youtube.on('refresh-token', () => {
      // Your refresh token logic here...

      const accessToken = 'new';
      youtube.setConfig({
        accessToken,
      });

      // Start fetching messages again.
      // If preferred, you can call youtube.fetchMessages() directly to avoid the connect logic.
      youtube.connect();
    });

    // Listen out for any API errors so to react accordingly.
    youtube.on('error', (error) => {
      switch (error.errors[0].reason) {
        case 'liveChatDisabled':
          // lets just destroy the interface
          break;
        case 'liveChatEnded':
          // lets disconnect
          break;
      }
    });

    // Disconnect from the socket server.
    await youtube.disconnect();
  } catch (e) {
    // This will typically return the error detail as listed here: https://developers.google.com/youtube/v3/live/docs/errors
    console.log(e);
  }
}

Chinterface provides a set of unique events and functions that assist in the display and handling of messages for YouTube.

Fetch and Set Live Chat ID

In cases where you do not know the liveChatId, you can use the loadChatId() function to set the config for you.

Requires accessToken. This function will NOT overwrite any previously set properties.

await youtube.loadChatId();

User Events

The YouTube interface emits a number of different events for actions including message deletion, user banning, chat ending and many more.

This interface will also emit an 'error' event for every 'Error detail' listed here: https://developers.google.com/youtube/v3/live/docs/errors You will receive the error object in the payload.

// An API error occurred
youtube.on('error', (errorDetails) => {
  console.log('API error', errorDetails);
});

// The user's token has expired
youtube.on('refresh-token', (errorDetails) => {
  console.log('Token expired', errorDetails);
});

// A message is deleted
youtube.on('delete-message', (messageId) => {
  console.log('Deleted Message (ID): ', messageId);
});

// A user is banned
youtube.on('user-banned', (data) => {
  /**
   * `data` will always return the following structure:
   *
   * {object} bannedUserDetails
   * ** {string} channelId - The author's channel ID
   * ** {string} channelUrl - The author's channel URL
   * ** {string} displayName - The author's username
   * ** {string} profileImageUrl - The author's avatar
   * {string} banType - The type of ban, permanent or temporary
   * {integer} banDurationSeconds - Length of time the ban is in effect for, only present if banType === temporary
   */
  console.log('User banned: ', data);
});

// Chat ended
youtube.on('chat-ended', () => {
  console.log('Chat has finished.');
  youtube.disconnect();
});

// New super chat event
youtube.on('super-chat', (data) => {
  /**
   * `data` will always return the following structure:
   *
   * {integer} amountMicros - The amount super chatted
   * {string} currency - The currency code that was super chatted
   * {string} amountDisplayString - Formatted amount with currency symbol prefixed
   * {string} userComment - The comment added by the user super chatting
   * {integer} tier - The YouTube defined tier based on the amount super chatted. See: https://support.google.com/youtube/answer/7277005
   */
  console.log('Super chat: ', data);
});

YouTube Live

This interface streams live chat messages using YouTube's liveChatMessages.streamList API over HTTP GET streaming (fetch). New messages arrive with lower latency than the polling youtube service.

Works in browser apps (including UMD imports). Uses the same host and auth as the REST YouTube API.

REMEMBER. YouTube auth tokens expire every 1 hour. You MUST listen for the refresh-token event and update the config value when you encounter this event.

The message payload and all other events (delete-message, user-banned, chat-ended, super-chat, etc.) use the same structure as the youtube service.

Setup and Usage

// YouTube Live (browser)

import ChinterfaceModule from 'chinterface/dist/chinterface.umd.js';

const chinterface = ChinterfaceModule.default || ChinterfaceModule;

async function youtubeLive() {
  const youtubeLive = chinterface.service('youtubeLive');

  try {
    // Set the default config values.
    await youtubeLive.setConfig({
      // Main configuration
      accessToken: '', // REQUIRED.
      liveChatId: 'EiEKGFVDVW1mV2tPSWxoek1fZDA2cXpvc2lBQRIFL2xpdmU', // REQUIRED. If unknown, set accessToken and make a request to loadChatId() before connecting.

      // Settings
      parseEmoticon: false, // Default: true [convert emoticons to images]
      parseUrl: false, // Default: true [convert URLs to anchor tags]
      formatMessages: false, // Default: true [process message and convert emoticons; body will be null if false]
      maxResults: 500, // Default: 500 [max messages returned on initial stream connect; 200–2000]
      profileImageSize: 32, // Default: 64 [size in pixels to return profile images at]
      reconnect: true, // Default: true [auto reconnect if the stream ends]
      streamBaseUrl: 'https://www.googleapis.com', // Default. Base URL for streamList HTTP streaming.
    });

    // If you do not know the liveChatId, set accessToken and make a request to this function.
    // If you already know the liveChatId, this function is NOT required.
    await youtubeLive.loadChatId();

    // Start streaming live chat messages.
    youtubeLive.connect();

    // To receive and listen for new, formatted messages.
    youtubeLive.on('message', (data) => {
      /**
       * `data` will always return the following structure:
       *
       * {string} id - The chat message ID; generated by YouTube.
       * {string} username
       * {string} body - The processed message.
       * {string} raw - The unprocessed message, direct from the service.
       * {number} timestamp - The time in milliseconds.
       * {Object} extra - Service-specific information.
       */
      console.log('New message', data);
    });

    // To send a message.
    youtubeLive.send('test message');

    // Listen for the refresh token event.
    youtubeLive.on('refresh-token', () => {
      // Your refresh token logic here...

      const accessToken = 'new';
      youtubeLive.setConfig({
        accessToken,
      });

      // Reconnect the stream with the new token.
      youtubeLive.connect();
    });

    // Listen out for any API errors so to react accordingly.
    youtubeLive.on('error', (error) => {
      switch (error.errors[0].reason) {
        case 'liveChatDisabled':
          // lets just destroy the interface
          break;
        case 'liveChatEnded':
          // lets disconnect
          break;
      }
    });

    // Disconnect from the stream.
    await youtubeLive.disconnect();
  } catch (e) {
    // This will typically return the error detail as listed here: https://developers.google.com/youtube/v3/live/docs/errors
    console.log(e);
  }
}

YouTube vs YouTube Live

| | youtube | youtubeLive | |---|---|---| | Transport | REST polling | HTTP stream (/liveChat/messages/stream) | | Latency | Periodic (interval, default 5s) | Push as messages arrive | | Environment | Browser and Node.js | Browser and Node.js | | Config | interval, maxResults | maxResults, reconnect, streamBaseUrl | | Sending messages | REST API | REST API (same as youtube) |

Use youtubeLive when you need lower-latency chat via streamList. Use youtube for a simpler polling setup.

Fetch and Set Live Chat ID

In cases where you do not know the liveChatId, you can use the loadChatId() function to set the config for you.

Requires accessToken. This function will NOT overwrite any previously set properties.

await youtubeLive.loadChatId();

User Events

The YouTube Live interface emits the same events as the youtube interface for actions including message deletion, user banning, chat ending and many more.

This interface will also emit an 'error' event for every 'Error detail' listed here: https://developers.google.com/youtube/v3/live/docs/errors You will receive the error object in the payload.

// An API error occurred
youtubeLive.on('error', (errorDetails) => {
  console.log('API error', errorDetails);
});

// The user's token has expired
youtubeLive.on('refresh-token', (errorDetails) => {
  console.log('Token expired', errorDetails);
});

// A message is deleted
youtubeLive.on('delete-message', (messageId) => {
  console.log('Deleted Message (ID): ', messageId);
});

// A user is banned
youtubeLive.on('user-banned', (data) => {
  console.log('User banned: ', data);
});

// Chat ended
youtubeLive.on('chat-ended', () => {
  console.log('Chat has finished.');
  youtubeLive.disconnect();
});

// New super chat event
youtubeLive.on('super-chat', (data) => {
  console.log('Super chat: ', data);
});

Facebook

This interface polls the Facebook Graph API every interval milliseconds. New comments arrive periodically from the /{live-video-id}/comments edge.

All requests use the latest Graph API version (v25.0 by default) against https://graph.facebook.com.

On each poll, the interface also checks the live video status and disconnects automatically when it is no longer LIVE or SCHEDULED_LIVE. After the first comments are received, subsequent polls pass a since timestamp so only newer comments are returned.

Setup and Usage

// Facebook

import chinterface from 'chinterface';

async function facebook() {
  const facebook = chinterface.service('facebook');

  try {
    // Set the default config values.
    await facebook.setConfig({
      // Main configuration
      liveVideoId: '1234567890', // REQUIRED. Live Video ID to poll comments for.
      accessToken: 'page-access-token', // REQUIRED. Page access token for the broadcasting Page.

      // Settings
      version: 'v25.0', // Default: v25.0 [Graph API version]
      parseUrl: false, // Default: true [convert URLs to anchor tags]
      interval: 5000, // Default: 5000 [time in ms between comment polls]
      videoId: '9876543210', // Optional; REQUIRED to send comments via send(). The underlying Video object ID.
    });

    // Fetches the token owner's id and name, and detects whether the token is a Page token.
    // If you already know userId and username, this function is NOT required.
    await facebook.loadUser();

    // Start polling for comments.
    await facebook.connect();

    // To receive and listen for new, formatted messages.
    facebook.on('message', (data) => {
      /**
       * `data` will always return the following structure:
       *
       * {string} id - The comment ID; generated by Facebook.
       * {string} username
       * {string} body - The processed message.
       * {string} raw - The unprocessed message, direct from the service.
       * {number} timestamp - The time in milliseconds.
       * {Object} extra - Service-specific information.
       *  - {user_id} - int [sender's user id]
       *  - {image} - string [sender's profile image, or null]
       *  - {broadcaster} - boolean [if message is from the owner]
       *  - {attachment} - object [comment attachment, if any]
       */
      console.log('New message', data);
    });

    // Sending is only available when loadUser() detects a Page access token
    // and videoId is set.
    facebook.send('test message');

    // Stop polling.
    await facebook.disconnect();
  } catch (e) {
    console.log(e);
  }
}

Fetch and Set User Information

In cases where you do not know the userId or username, you can use the loadUser() function to set the config for you.

Requires accessToken. This function will NOT overwrite any previously set properties. It also probes whether the token is a Page access token (required for send()).

await facebook.loadUser();

Sending Comments

Posting comments requires:

  • A Page access token (detected by loadUser())
  • videoId set to the underlying Video object ID (not the Live Video ID)

send() posts to /{videoId}/comments. If you need to post against the live video node directly, use sendMessageToPage(config, message) instead.

Events

// A new comment was received
facebook.on('message', (data) => {
  console.log('New message', data);
});

// Polling started
facebook.on('connected', () => {
  console.log('Connected');
});

// Polling stopped (manual disconnect or video no longer live)
facebook.on('disconnected', () => {
  console.log('Disconnected');
});

// A Graph API error occurred during polling
facebook.on('error', (error) => {
  console.error(error);
});

Note on Reactions & Stars: Facebook also exposes a live_reactions streaming edge, and Stars are delivered via server-side Webhooks. This interface currently polls only live video comments.