robotty / dank-twitch-irc

Connect to Twitch chat from Node.js
https://www.npmjs.com/package/dank-twitch-irc
MIT License
88 stars 25 forks source link
irc nodejs npm-package tmi twitch

dank-twitch-irc

I (randers) am no longer supporting or updating dank-twitch-irc. While it may continue to work, I'm no longer making sure it will continue to do so in the future. This package will also not receive any dependency or security updates. For this reason I recommend you to choose a different library in your projects, or to fork this project to continue development on it. If somebody makes a high-quality fork of this project, you can contact me and I can link to your fork in this README here. Thank you.

Build

Node.js-only Twitch IRC lib, written in TypeScript.

Requires Node.js 10 (LTS) or above.

Table of Contents

Usage

const { ChatClient } = require("dank-twitch-irc");

let client = new ChatClient();

client.on("ready", () => console.log("Successfully connected to chat"));
client.on("close", (error) => {
  if (error != null) {
    console.error("Client closed due to error", error);
  }
});

client.on("PRIVMSG", (msg) => {
  console.log(`[#${msg.channelName}] ${msg.displayName}: ${msg.messageText}`);
});

// See below for more events

client.connect();
client.join("forsen");

Available client events

All other commands (if they don't have a special parsed type like the ones listed above) will still be emitted under their command name as an IRCMessage, e.g.:

// :tmi.twitch.tv 372 botfactory :You are in a maze of twisty passages, all alike.
// msg will be an instance of IRCMessage
client.on("372", (msg) =>
  console.log(`Server MOTD is: ${msg.ircParameters[1]}`)
);

Handling USERNOTICE messages

The USERNOTICE message type is special because it encapsulates a wide range of events, including:

which are all emitted under the USERNOTICE event. See also the offical documentation about the USERNOTICE command.

Every USERNOTICE message is sent by a user, and always contains a msg.systemMessage (This is a message that twitch formats for you, e.g. 4 raiders from PotehtoO have joined! for a raid message.) Additionally, every USERNOTICE message can have a message that is additionally sent/shared from the sending user, for example the "share this message with the streamer" message sent with resubs and subs. If no message is sent by the user, msg.messageText is undefined.

dank-twitch-irc currently does not have special parsing code for each USERNOTICE messageTypeID (e.g. sub, resub, raid, etc...) - Instead the parser assigns all msg-param- tags to the msg.eventParams object. See below on what msg.eventParams are available for each of the messageTypeIDs.

Sub and resub

When a user subscribes or resubscribes with his own money/prime (this is NOT sent for gift subs, see below)

chatClient.on("USERNOTICE", (msg) => {
  // sub and resub messages have the same parameters, so we can handle them both the same way
  if (!msg.isSub() && !msg.isResub()) {
    return;
  }

  /*
   * msg.eventParams are:
   *
   * {
   *   "cumulativeMonths": 10,
   *   "cumulativeMonthsRaw": "10",
   *   "subPlan": "1000", // Prime, 1000, 2000 or 3000
   *   "subPlanName": "The Ninjas",
   *
   *   // if shouldShareStreak is false, then
   *   // streakMonths/streakMonthsRaw will be 0
   *   // (the user did not share their sub streak in chat)
   *   "shouldShareStreak": true,
   *   "streakMonths": 7,
   *   "streakMonthsRaw": "7"
   * }
   * Sender user of the USERNOTICE message is the user subbing/resubbing.
   */

  if (msg.isSub()) {
    // Leppunen just subscribed to ninja with a tier 1000 (The Ninjas) sub for the first time!
    console.log(
      msg.displayName +
        " just subscribed to " +
        msg.channelName +
        " with a tier " +
        msg.eventParams.subPlan +
        " (" +
        msg.eventParams.subPlanName +
        ") sub for the first time!"
    );
  } else if (msg.isResub()) {
    let streakMessage = "";
    if (msg.eventParams.shouldShareStreak) {
      streakMessage =
        ", currently " + msg.eventParams.streakMonths + " months in a row";
    }

    // Leppunen just resubscribed to ninja with a tier 1000 (The Ninjas) sub!
    // They are resubscribing for 10 months, currently 7 months in a row!
    console.log(
      msg.displayName +
        " just resubscribed to " +
        msg.channelName +
        " with a tier " +
        msg.eventParams.subPlan +
        " (" +
        msg.eventParams.subPlanName +
        ") sub! They are resubscribing for " +
        msg.eventParams.cumulativeMonths +
        " months" +
        streakMessage +
        "!"
    );
  }

  if (msg.messageText != null) {
    // you also have access to lots of other properties also present on PRIVMSG messages,
    // such as msg.badges, msg.senderUsername, msg.badgeInfo, msg.bits/msg.isCheer(),
    // msg.color, msg.emotes, msg.messageID, msg.serverTimestamp, etc...
    console.log(
      msg.displayName +
        " shared the following message with the streamer: " +
        msg.messageText
    );
  } else {
    console.log("They did not share a message with the streamer.");
  }
});

Incoming raids

Twitch says:

Incoming raid to a channel. Raid is a Twitch tool that allows broadcasters to send their viewers to another channel, to help support and grow other members in the community.)

chatClient.on("USERNOTICE", (msg) => {
  if (!msg.isRaid()) {
    return;
  }

  /*
   * msg.eventParams are:
   * {
   *   "displayName": "Leppunen",
   *   "login": "leppunen",
   *   "viewerCount": 12,
   *   "viewerCountRaw": "12"
   * }
   * Sender user of the USERNOTICE message is the user raiding this channel.
   * Note that the display name and login present in msg.eventParams are
   * the same as msg.displayName and msg.senderUsername, so it doesn't matter
   * which one you use (although I recommend the properties directly on the
   * message object, not in eventParams)
   */

  // source user is the channel/streamer raiding
  // Leppunen just raided Supinic with 12 viewers!
  console.log(
    msg.displayName +
      " just raided " +
      msg.channelName +
      " with " +
      msg.eventParams.viewerCount +
      " viewers!"
  );
});

Subgift

When a user gifts somebody else a subscription.

chatClient.on("USERNOTICE", (msg) => {
  if (!msg.isSubgift()) {
    return;
  }

  /*
   * msg.eventParams are:
   * {
   *   "months": 5,
   *   "monthsRaw": "5",
   *   "giftMonths": 5,
   *   "giftMonthsRaw": "5",
   *   "recipientDisplayName": "Leppunen",
   *   "recipientID": "42239452",
   *   "recipientUsername": "leppunen",
   *   "subPlan": "1000",
   *   "subPlanName": "The Ninjas",
   *   "senderCount": 5,
   *   "senderCountRaw": "5",
   * }
   * Sender user of the USERNOTICE message is the user gifting the subscription.
   */

  if (msg.eventParams.months === 1) {
    // Leppunen just gifted NymN a fresh tier 1000 (The Ninjas) sub to ninja!
    console.log(
      msg.displayName +
        " just gifted " +
        msg.eventParams.recipientDisplayName +
        " a fresh tier " +
        msg.eventParams.subPlan +
        " (" +
        msg.eventParams +
        ") sub to " +
        msg.channelName +
        "!"
    );
  } else {
    // Leppunen just gifted NymN a tier 1000 (The Ninjas) resub to ninja, that's 7 months in a row!
    console.log(
      msg.displayName +
        " just gifted " +
        msg.eventParams.recipientDisplayName +
        " a tier " +
        msg.eventParams.subPlan +
        " (" +
        msg.eventParams +
        ") resub to " +
        msg.channelName +
        ", that's " +
        msg.eventParams.months +
        " in a row!"
    );
  }

  // note: if the subgift was from an anonymous user, the sender user for the USERNOTICE message will be
  // AnAnonymousGifter (user ID 274598607)
  if (msg.senderUserID === "274598607") {
    console.log("That (re)sub was gifted anonymously!");
  }
});

Anonsubgift

When an anonymous user gifts a subscription to a viewer.

chatClient.on("USERNOTICE", (msg) => {
  if (!msg.isAnonSubgift()) {
    return;
  }

  /*
   * msg.eventParams are:
   * {
   *   "months": 5,
   *   "monthsRaw": "5",
   *   "recipientDisplayName": "Leppunen",
   *   "recipientID": "42239452",
   *   "recipientUsername": "leppunen",
   *   "subPlan": "1000",
   *   "subPlanName": "The Ninjas"
   * }
   *
   * WARNING! Sender user of the USERNOTICE message is the broadcaster (e.g. Ninja
   * in the example below)
   */

  if (msg.eventParams.months === 1) {
    // An anonymous gifter just gifted NymN a fresh tier 1000 (The Ninjas) sub to ninja!
    console.log(
      "An anonymous gifter just gifted " +
        msg.eventParams.recipientDisplayName +
        " a fresh tier " +
        msg.eventParams.subPlan +
        " (" +
        msg.eventParams +
        ") sub to " +
        msg.channelName +
        "!"
    );
  } else {
    // An anonymous gifter just gifted NymN a tier 1000 (The Ninjas) resub to ninja, that's 7 months in a row!
    console.log(
      "An anonymous gifter just gifted " +
        msg.eventParams.recipientDisplayName +
        " a tier " +
        msg.eventParams.subPlan +
        " (" +
        msg.eventParams +
        ") resub to " +
        msg.channelName +
        ", that's " +
        msg.eventParams.months +
        " in a row!"
    );
  }
});

anongiftpaidupgrade, giftpaidupgrade

When a user commits to continue the gift sub by another user (or an anonymous gifter).

chatClient.on("USERNOTICE", (msg) => {
  if (!msg.isAnonGiftPaidUpgrade()) {
    return;
  }

  /*
   * msg.eventParams are:
   * EITHER: (ONLY when a promotion is running!)
   * {
   *   "promoName": "Subtember 2018",
   *   "promoGiftTotal": 3987234,
   *   "promoGiftTotalRaw": "3987234"
   * }
   * OR: (when no promotion is running)
   * {}
   *
   * Sender user of the USERNOTICE message is the user continuing their sub.
   */

  // Leppunen is continuing their ninja gift sub they got from an anonymous user!
  console.log(
    msg.displayName +
      " is continuing their " +
      msg.channelName +
      " gift sub they got from an anonymous user!"
  );
});
chatClient.on("USERNOTICE", (msg) => {
  if (!msg.isGiftPaidUpgrade()) {
    return;
  }

  /*
   * msg.eventParams are:
   * EITHER: (ONLY when a promotion is running!)
   * {
   *   "promoName": "Subtember 2018",
   *   "promoGiftTotal": 3987234,
   *   "promoGiftTotalRaw": "3987234",
   *   "senderLogin": "krakenbul",
   *   "senderName": "Krakenbul"
   * }
   * OR: (when no promotion is running)
   * {
   *   "senderLogin": "krakenbul",
   *   "senderName": "Krakenbul"
   * }
   *
   * Sender user of the USERNOTICE message is the user continuing their sub.
   */

  // Leppunen is continuing their ninja gift sub they got from Krakenbul!
  console.log(
    msg.displayName +
      " is continuing their " +
      msg.channelName +
      " gift sub they got from " +
      msg.msgParam.senderName +
      "!"
  );
});

ritual

Channel ritual. Twitch says:

Channel ritual. Many channels have special rituals to celebrate viewer milestones when they are shared. The rituals notice extends the sharing of these messages to other viewer milestones (initially, a new viewer chatting for the first time).

chatClient.on("USERNOTICE", (msg) => {
  if (!msg.isRitual()) {
    return;
  }

  /*
   * msg.eventParams are:
   * {
   *   "ritualName": "new_chatter"
   * }
   *
   * Sender user of the USERNOTICE message is the user performing the
   * ritual (e.g. the new chatter).
   */

  // Leppunen is new to ninja's chat! Say hello!
  if (msg.eventParams.ritualName === "new_chatter") {
    console.log(
      msg.displayName + " is new to " + msg.channelName + "'s chat! Say hello!"
    );
  } else {
    console.warn(
      "Unknown (unhandled) ritual type: " + msg.eventParams.ritualName
    );
  }
});

bitsbadgetier

When a user cheers and earns himself a new bits badge with that cheer (e.g. they just cheered more than/exactly 10000 bits in total, and just earned themselves the 10k bits badge)

chatClient.on("USERNOTICE", (msg) => {
  if (!msg.isBitsBadgeTier()) {
    return;
  }

  /*
   * msg.eventParams are:
   * {
   *   "threshold": 10000,
   *   "thresholdRaw": "10000",
   * }
   *
   * Sender user of the USERNOTICE message is the user cheering the bits.
   */

  // Leppunen just earned themselves the 10000 bits badge in ninja's channel!
  console.log(
    msg.displayName +
      " just earned themselves the " +
      msg.threshold +
      " bits badge in " +
      msg.channelName +
      "'s channel!"
  );
});

ChatClient API

You probably will want to use these functions on ChatClient most frequently:

Extra functionality:

Note that channel names in the above functions always refer to the "login name" of a twitch channel. Channel names may not be capitalized, e.g. Forsen would be invalid, but forsen not. This library also does not accept the leading # character and never returns it on any message objects (e.g. msg.channelName would be forsen, not #forsen).

API Documentation

Generated API documentation can be found here: https://robotty.github.io/dank-twitch-irc

Client options

Pass options to the ChatClient constructor. More available options are documented in the Below are all possible options and their default values:

Note! ALL of these configuration options are optional! I highly recommend you only set the very config options you need, the rest are usually at a reasonable default.
For most bots, you only need to set username and password:

let client = new ChatClient({
  username: "your-bot-username",
  password: "0123456789abcdef1234567",
});

Nevertheless, here are examples of all possible config options:

let client = new ChatClient({
  username: "your-bot-username", // justinfan12345 by default - For anonymous chat connection
  password: "0123456789abcdef1234567", // undefined by default (no password)

  // Message rate limits configuration for verified and known bots
  // pick one of the presets or configure custom rates as shown below:
  rateLimits: "default",
  // or:
  rateLimits: "knownBot",
  // or:
  rateLimits: "verifiedBot",
  // or:
  rateLimits: {
    highPrivmsgLimits: 100,
    lowPrivmsgLimits: 20,
  },

  // Configuration options for the backing connections:
  // Plain TCP or TLS
  connection: {
    type: "tcp", // tcp by default
    secure: false, // true by default
    // host and port must both be specified at once
    host: "custom-chat-server.com", // irc.chat.twitch.tv by default
    port: 1234, // 6697/6667 by default, depending on the "secure" setting
  },
  // or:
  connection: {
    type: "websocket",
    secure: true, // use preset URL of irc-ws.chat.twitch.tv
  },
  // or:
  connection: {
    type: "websocket",
    url: "wss://custom-url.com/abc/def", // custom URL
  },
  // or:
  connection: {
    type: "duplex",
    stream: () => aNodeJsDuplexInstance, // read and write to a custom object
    // implementing the Duplex interface from Node.js
    // the function you specify is called for each new connection

    preSetup: true, // false by default, makes the lib skip login
    // and capabilities negotiation on connection startup
  },

  // how many channels each individual connection should join at max
  maxChannelCountPerConnection: 100, // 90 by default

  // custom parameters for connection rate limiting
  connectionRateLimits: {
    parallelConnections: 5, // 1 by default
    // time to wait after each connection before a new connection can begin
    releaseTime: 1000, // in milliseconds, 2 seconds by default
  },

  // I recommend you leave this off by default, it makes your bot faster
  // If you need live update of who's joining and leaving chat,
  // poll the tmi.twitch.tv chatters endpoint instead since it
  // is also more reliable
  requestMembershipCapability: false, // false by default

  // read more about mixins below
  // this disables the connection rate limiter, message rate limiter
  // and Room- and Userstate trackers (which are important for other mixins)
  installDefaultMixins: false, // true by default

  // Silence UnandledPromiseRejectionWarnings on all client methods
  // that return promises.
  // With this option enabled, the returned promises will still be rejected/
  // resolved as without this option, this option ONLY silences the
  // UnhandledPromiseRejectionWarning.
  ignoreUnhandledPromiseRejections: true, // false by default
});

Features

This client currently supports the following features:

Extra Mixins

There are some features you might find useful in your bot that are not necessary for general client/bot operations, so they were packaged as mixins. You can activate mixins by calling:

const { ChatClient, AlternateMessageModifier } = require("dank-twitch-irc");

let client = new ChatClient();

client.use(new AlternateMessageModifier(client));

Available mixins are:

and the mixins installed by default:

Tests

npm run test

Test run report is available in ./mochawesome-report/mochawesome.html. Coverage report is produced as ./coverage/index.html.

Lint and check code style

# Run eslint and tslint rules and checks code style with prettier
npm run lint
# Run eslint, tslint and pretter fixers
npm run lintfix