@5e7en/dank-twitch-irc 中文文档教程

发布于 4年前 浏览 30 项目主页 更新于 3年前

dank-twitch-irc

Build

仅 Node.js 的 Twitch IRC 库,用 TypeScript 编写。

需要 Node.js 10 (LTS) 或更高版本。

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

  • client.on("connecting", () => { /* ... */ }):当客户端 第一次开始连接。

  • client.on("connect", () => { /* ... */ }):客户端连接时调用 第一次连接。 这在传输层时被调用 连接(例如建立 TCP 或 WebSocket 连接),而不是在登录时 到 IRC 成功。

  • client.on("ready", () => { /* ... */ }):当客户端变为 首次准备就绪(登录聊天服务器成功。)

  • client.on("close", (error?: Error) => { /* ... */ }):调用时 客户端作为一个整体被终止。 不要求单独的连接 断开连接。 例如,可能由无效的 OAuth 令牌(失败 登录),或者调用 client.close()client.destroy() 时。 <代码>错误 仅当客户端通过调用 client.close() 关闭时才为非空。

  • client.on("error", (error: Error?) => { /* ... */ }): 任何时候调用 客户端发生错误,包括非致命错误,例如一条消息 由于错误无法交付。

  • client.on("rawCommand", (cmd: string) => { /* ... */ }):任何时候调用 命令由客户端执行。

  • client.on("message", (message: IRCMessage) => { /* ... */ }):调用 每条传入的消息。 如果该消息是进一步解析的消息(我 在这个库中调用这些“twitch messages”)然后 message 传递给 此处理程序已经是特定类型,例如 PrivmsgMessage 命令是 PRIVMSG

  • client.on("PRIVMSG", (message: PrivmsgMessage) => { /* ... */ }):已调用 在命令为 PRIVMSG 的传入消息上。 message 参数是 总是 instanceof PrivmsgMessage。 (查看 API 文档了解什么 属性存在于所有 PrivmsgMessage 实例上)

    例如:

  client.on("CLEARCHAT", (msg) => {
    if (msg.isTimeout()) {
      console.log(
        `${msg.targetUsername} just got timed out for ` +
          `${msg.banDuration} seconds in channel ${msg.channelName}`
      );
    }
  });

具有特定消息解析的其他消息类型是:

  • CLEARCHAT (maps to ClearchatMessage) - Timeout and ban messages
  • CLEARMSG (maps to ClearmsgMessage) - Single message deletions (initiated by /delete)
  • HOSTTARGET (maps to HosttargetMessage) - A channel entering or exiting host mode.
  • NOTICE (maps to NoticeMessage) - Various notices, such as when you /help, a command fails, the error response when you are timed out, etc.
  • PRIVMSG (maps to PrivmsgMessage) - Normal chat messages
  • ROOMSTATE (maps to RoomstateMessage) - A change to a channel's followers mode, subscribers-only mode, r9k mode, followers mode, slow mode etc.
  • USERNOTICE (maps to UsernoticeMessage) - Subs, resubs, sub gifts, rituals, raids, etc. - See more details about how to handle this message type below.
  • USERSTATE (maps to UserstateMessage) - Your own state (e.g. badges, color, display name, emote sets, mod status), sent on every time you join a channel or send a PRIVMSG to a channel
  • GLOBALUSERSTATE (maps to GlobaluserstateMessage) - Logged in user's "global state", sent once on every login (Note that due to the used connection pool you can receive this multiple times during your bot's runtime)
  • WHISPER (maps to WhisperMessage) - Somebody else whispering you
  • JOIN (maps to JoinMessage) - You yourself joining a channel, of if you have requestMembershipCapability enabled, also other users joining channels you are joined to.
  • PART (maps to JoinMessage) - You yourself parting (leaving) a channel, of if you have requestMembershipCapability enabled, also other users parting channels you are joined to.
  • RECONNECT (maps to ReconnectMessage) - When the twitch server tells a client to reconnect and re-join channels (You don't have to listen for this yourself, this is done automatically already)
  • PING (maps to PingMessage) - When the twitch server sends a ping, expecting a pong back from the client to verify if the connection is still alive. (You don't have to listen for this yourself, the client automatically responds for you)
  • PONG (maps to PongMessage) - When the twitch server responds to our PING requests (The library automatically sends a PING request every 30 seconds to verify connections are alive)
  • CAP (maps to CapMessage) - Message type received once during connection startup, acknowledging requested capabilities.

所有其他命令(如果它们没有像 上面列出的)仍将在其命令名称下作为 IRCMessage,例如:

// :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

USERNOTICE 消息类型比较特殊,因为它封装了范围广泛的 事件,包括:

  • Subs
  • Resubs
  • Gift subscription
  • Incoming raid and
  • Channel rituals,

全部在 USERNOTICE 事件下发出。 也可以看看 官方文档 关于 USERNOTICE 命令。

每条 USERNOTICE 消息都是由用户发送的,并且总是包含一个 msg.systemMessage(这是一条为您调整格式的消息,例如 4 名来自 PotehtoO 的袭击者已加入! raid 消息。)此外, 每条 USERNOTICE 消息都可以有一条额外发送/共享的消息 来自发送用户,例如“与流媒体分享此消息” 与 resubs 和 subs 一起发送的消息。 如果用户没有发送消息, msg.messageTextundefined

dank-twitch-irc目前没有针对每个的特殊解析代码 USERNOTICE messageTypeID(例如subresubraid 等)- 相反这 解析器将所有 msg-param- 标签分配给 msg.eventParams 对象。 见下文 关于哪些 msg.eventParams 可用于每个 messageTypeID

Sub and resub

当用户使用自己的钱/prime 订阅或重新订阅时(这不是 发送礼物潜艇,见下文)

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 说:

进入频道的突袭。 Raid 是一种 Twitch 工具,允许广播公司 将他们的观众发送到另一个频道,以帮助支持和发展其他成员 在社区中。)

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

当用户向其他人赠送订阅时。

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

当匿名用户向查看者赠送订阅时。

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

当用户承诺继续由另一个用户(或匿名 送礼者)。

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

渠道仪式。 Twitch 说:

引导仪式。 许多频道都有特殊的仪式来庆祝观众 共享时的里程碑。 仪式通知扩展了共享 这些消息发送给其他观众里程碑(最初,新观众聊天 首次)。

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

当用户欢呼并为自己赢得一个新的比特徽章时(例如,他们 刚刚欢呼了一万多比特,刚刚赚到了自己 10k 位徽章)

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

您可能希望在 ChatClient 上最频繁地使用这些功能:

  • client.join(channelName: string): Promise<void> - Join (Listen to) the channel given by the channel name
  • client.joinAll(channelNames: string[]): Promise<void> - Join (Listen to) all of the listed channels at once (bulk join)
  • client.part(channelName: string): Promise<void> - Part (Leave/Unlisten) the channel given by the channel name
  • client.privmsg(channelName: string, message: string): Promise<void> - Send a raw PRIVMSG to the given channel. You can issue chat commands with this function, e.g. client.privmsg("forsen", "/timeout weeb123 5") or normal messages, e.g. client.privmsg("forsen", "Kappa Keepo PogChamp").
  • client.say(channelName: string, message: string): Promise<void> - Say a normal chat message in the given channel. If a command is given as message, it will be escaped.
  • client.me(channelName: string, message: string): Promise<void> - Post a /me message in the given channel.
  • client.timeout(channelName: string, username: string, length: number, reason?: string): Promise<void> - Timeout username for length seconds in channelName. Optionally accepts a reason to set.
  • client.ban(channelName: string, username: string, reason?: string): Promise<void> - Ban username in channelName. Optionally accepts a reason to set.
  • client.ping() - Send a PING on a connection from the pool, and awaits the PONG response. You can use this to measure server latency, for example.
  • client.whisper(username: string, message: string) - Send the user a whisper from the bot.
  • client.setColor(color: Color) - set the username color of your bot account. E.g. client.setColor({ r: 255, g: 0, b: 127 }).
  • client.getMods(channelName: string) and client.getVips(channelName: string) - Get a list of moderators/VIPs in a channel. Returns a promise that resolves to an array of strings (login names of the moderators/VIPs). Note that due to Twitch's restrictions, this function cannot be used with anonymous chat clients. (The request will time out if your chat client is logged in as anonymous.)

额外功能:

  • client.sendRaw(command: string): void - Send a raw IRC command to a connection in the connection pool.
  • client.unconnected (boolean) - Returns whether the client is unconnected.
  • client.connecting (boolean) - Returns whether the client is connecting.
  • client.connected (boolean) - Returns whether the client is connected (Transport layer is connected).
  • client.ready (boolean) - Returns whether the client is ready (Logged into IRC server).
  • client.closed (boolean) - Returns whether the client is closed.

请注意,上述功能中的频道名称始终引用“登录名” 抽搐频道。 频道名称不能大写,例如 Forsen 会 无效,但 forsen 无效。 该库也不接受前导 # 字符并且永远不会在任何消息对象上返回它(例如 msg.channelName 将是 forsen,而不是 #forsen)。

API Documentation

可以在此处找到生成的 API 文档: https://robotty.github.io/dank-twitch-irc

Client options

将选项传递给 ChatClient 构造函数。 更多可用选项是 下面记录了所有可能的选项及其默认值:

注意! 所有这些配置选项都是可选的我强烈推荐你 只设置你需要的配置选项,其余的通常是合理的默认值。
对于大多数机器人,您只需要设置 usernamepassword

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

然而,这里是所有可能的配置选项的示例:

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

该客户端目前支持以下功能:

  • Connection pooling and round-robin connection usage
  • Automatic rate limiter for connection opening and chat commands
  • All twitch-specific message types parsed (CLEARCHAT, CLEARMSG, GLOBALUSERSTATE, HOSTTARGET, JOIN, NOTICE, PART, PING, PONG, PRIVMSG, RECONNECT, ROOMSTATE, USERNOTICE, USERSTATE, WHISPER, CAP)
  • Accurate response to server responses (e.g. error thrown if you are banned from channel/channel is suspended/login is invalid etc.)
  • Bulk join functionality to join lots of channels quickly
  • Implements the recommended connection control, utilizing RECONNECT, PING and PONG
  • Full tracking of room state (e.g. submode, emote-only mode, followers mode, r9k etc.) and user state (badges, moderator state, color, etc).
  • Most function calls return promises but errors can also be handled by subscribing to the error event
  • Slow-mode rate limiter for non-VIP/moderator bots (waits either the global ~1.3 sec/channel-specific slow mode)
  • Support for different types of transport (in-memory, TCP, WebSocket)

Extra Mixins

有一些功能你可能会发现在你的机器人中有用但不是必需的 用于一般的客户端/机器人操作,因此它们被打包为mixins。 你可以 通过调用激活 mixins:

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

let client = new ChatClient();

client.use(new AlternateMessageModifier(client));

可用的 mixins 是:

  • new AlternateMessageModifier(client) will allow your bot to send the same message within a 30 seconds period. You must also use client.say and client.me for this mixin to behave consistently and reliably.
  • new SlowModeRateLimiter(client, /* optional */ maxWaitingMessages) will rate limit your messages in channels where your bot is not moderator, VIP or broadcaster and has to wait a bit between sending messages. If more than maxWaitingMessages are waiting, the outgoing message will be dropped silently. maxWaitingMessages defaults to 10. Note this mixin only has an effect on client.say and client.me functions, not client.privmsg.

默认安装的 mixins:

  • new PrivmsgMessageRateLimiter(client) - Rate limits outgoing messages according to the rate limits imposed by Twitch. Configure the verified/known status of your bot using the config (see above).
  • new ConnectionRateLimiter(client) - Rate limits new connections accoding to the rate limits set in the config.
  • new UserStateTracker(client) - Used by other mixins. Keeps track of what state your bot user has in all channels.
  • new RoomStateTracker() - Used by other mixins. Keeps track of each channel's state, e.g. sub-mode etc.
  • new IgnoreUnhandledPromiseRejectionsMixin() - Silences UnhandledPromiseRejectionWarnings on promises returned by the client's functions. (installed for you if you activate the ignoreUnhandledPromiseRejections client option)

Tests

npm run test

测试运行报告在 ./mochawesome-report/mochawesome.html 中可用。 覆盖率报告生成为 ./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

dank-twitch-irc

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

  • client.on("connecting", () => { /* ... */ }): Called when the client starts connecting for the first time.

  • client.on("connect", () => { /* ... */ }): Called when the client connects for the first time. This is called when the transport layer connections (e.g. TCP or WebSocket connection is established), not when login to IRC succeeds.

  • client.on("ready", () => { /* ... */ }): Called when the client becomes ready for the first time (login to the chat server is successful.)

  • client.on("close", (error?: Error) => { /* ... */ }): Called when the client is terminated as a whole. Not called for individual connections that were disconnected. Can be caused for example by a invalid OAuth token (failure to login), or when client.close() or client.destroy() was called. error is only non-null if the client was closed by a call to client.close().

  • client.on("error", (error: Error?) => { /* ... */ }): Called when any error occurs on the client, including non-fatal errors such as a message that could not be delivered due to an error.

  • client.on("rawCommand", (cmd: string) => { /* ... */ }): Called when any command is executed by the client.

  • client.on("message", (message: IRCMessage) => { /* ... */ }): Called on every incoming message. If the message is a message that is further parsed (I called these "twitch messages" in this library) then the message passed to this handler will already be the specific type, e.g. PrivmsgMessage if the command is PRIVMSG.

  • client.on("PRIVMSG", (message: PrivmsgMessage) => { /* ... */ }): Called on incoming messages whose command is PRIVMSG. The message parameter is always instanceof PrivmsgMessage. (See the API documentation for what properties exist on all PrivmsgMessage instances)

    For example:

  client.on("CLEARCHAT", (msg) => {
    if (msg.isTimeout()) {
      console.log(
        `${msg.targetUsername} just got timed out for ` +
          `${msg.banDuration} seconds in channel ${msg.channelName}`
      );
    }
  });

Other message types that have specific message parsing are:

  • CLEARCHAT (maps to ClearchatMessage) - Timeout and ban messages
  • CLEARMSG (maps to ClearmsgMessage) - Single message deletions (initiated by /delete)
  • HOSTTARGET (maps to HosttargetMessage) - A channel entering or exiting host mode.
  • NOTICE (maps to NoticeMessage) - Various notices, such as when you /help, a command fails, the error response when you are timed out, etc.
  • PRIVMSG (maps to PrivmsgMessage) - Normal chat messages
  • ROOMSTATE (maps to RoomstateMessage) - A change to a channel's followers mode, subscribers-only mode, r9k mode, followers mode, slow mode etc.
  • USERNOTICE (maps to UsernoticeMessage) - Subs, resubs, sub gifts, rituals, raids, etc. - See more details about how to handle this message type below.
  • USERSTATE (maps to UserstateMessage) - Your own state (e.g. badges, color, display name, emote sets, mod status), sent on every time you join a channel or send a PRIVMSG to a channel
  • GLOBALUSERSTATE (maps to GlobaluserstateMessage) - Logged in user's "global state", sent once on every login (Note that due to the used connection pool you can receive this multiple times during your bot's runtime)
  • WHISPER (maps to WhisperMessage) - Somebody else whispering you
  • JOIN (maps to JoinMessage) - You yourself joining a channel, of if you have requestMembershipCapability enabled, also other users joining channels you are joined to.
  • PART (maps to JoinMessage) - You yourself parting (leaving) a channel, of if you have requestMembershipCapability enabled, also other users parting channels you are joined to.
  • RECONNECT (maps to ReconnectMessage) - When the twitch server tells a client to reconnect and re-join channels (You don't have to listen for this yourself, this is done automatically already)
  • PING (maps to PingMessage) - When the twitch server sends a ping, expecting a pong back from the client to verify if the connection is still alive. (You don't have to listen for this yourself, the client automatically responds for you)
  • PONG (maps to PongMessage) - When the twitch server responds to our PING requests (The library automatically sends a PING request every 30 seconds to verify connections are alive)
  • CAP (maps to CapMessage) - Message type received once during connection startup, acknowledging requested capabilities.

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:

  • Subs
  • Resubs
  • Gift subscription
  • Incoming raid and
  • Channel rituals,

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:

  • client.join(channelName: string): Promise<void> - Join (Listen to) the channel given by the channel name
  • client.joinAll(channelNames: string[]): Promise<void> - Join (Listen to) all of the listed channels at once (bulk join)
  • client.part(channelName: string): Promise<void> - Part (Leave/Unlisten) the channel given by the channel name
  • client.privmsg(channelName: string, message: string): Promise<void> - Send a raw PRIVMSG to the given channel. You can issue chat commands with this function, e.g. client.privmsg("forsen", "/timeout weeb123 5") or normal messages, e.g. client.privmsg("forsen", "Kappa Keepo PogChamp").
  • client.say(channelName: string, message: string): Promise<void> - Say a normal chat message in the given channel. If a command is given as message, it will be escaped.
  • client.me(channelName: string, message: string): Promise<void> - Post a /me message in the given channel.
  • client.timeout(channelName: string, username: string, length: number, reason?: string): Promise<void> - Timeout username for length seconds in channelName. Optionally accepts a reason to set.
  • client.ban(channelName: string, username: string, reason?: string): Promise<void> - Ban username in channelName. Optionally accepts a reason to set.
  • client.ping() - Send a PING on a connection from the pool, and awaits the PONG response. You can use this to measure server latency, for example.
  • client.whisper(username: string, message: string) - Send the user a whisper from the bot.
  • client.setColor(color: Color) - set the username color of your bot account. E.g. client.setColor({ r: 255, g: 0, b: 127 }).
  • client.getMods(channelName: string) and client.getVips(channelName: string) - Get a list of moderators/VIPs in a channel. Returns a promise that resolves to an array of strings (login names of the moderators/VIPs). Note that due to Twitch's restrictions, this function cannot be used with anonymous chat clients. (The request will time out if your chat client is logged in as anonymous.)

Extra functionality:

  • client.sendRaw(command: string): void - Send a raw IRC command to a connection in the connection pool.
  • client.unconnected (boolean) - Returns whether the client is unconnected.
  • client.connecting (boolean) - Returns whether the client is connecting.
  • client.connected (boolean) - Returns whether the client is connected (Transport layer is connected).
  • client.ready (boolean) - Returns whether the client is ready (Logged into IRC server).
  • client.closed (boolean) - Returns whether the client is closed.

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:

  • Connection pooling and round-robin connection usage
  • Automatic rate limiter for connection opening and chat commands
  • All twitch-specific message types parsed (CLEARCHAT, CLEARMSG, GLOBALUSERSTATE, HOSTTARGET, JOIN, NOTICE, PART, PING, PONG, PRIVMSG, RECONNECT, ROOMSTATE, USERNOTICE, USERSTATE, WHISPER, CAP)
  • Accurate response to server responses (e.g. error thrown if you are banned from channel/channel is suspended/login is invalid etc.)
  • Bulk join functionality to join lots of channels quickly
  • Implements the recommended connection control, utilizing RECONNECT, PING and PONG
  • Full tracking of room state (e.g. submode, emote-only mode, followers mode, r9k etc.) and user state (badges, moderator state, color, etc).
  • Most function calls return promises but errors can also be handled by subscribing to the error event
  • Slow-mode rate limiter for non-VIP/moderator bots (waits either the global ~1.3 sec/channel-specific slow mode)
  • Support for different types of transport (in-memory, TCP, WebSocket)

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:

  • new AlternateMessageModifier(client) will allow your bot to send the same message within a 30 seconds period. You must also use client.say and client.me for this mixin to behave consistently and reliably.
  • new SlowModeRateLimiter(client, /* optional */ maxWaitingMessages) will rate limit your messages in channels where your bot is not moderator, VIP or broadcaster and has to wait a bit between sending messages. If more than maxWaitingMessages are waiting, the outgoing message will be dropped silently. maxWaitingMessages defaults to 10. Note this mixin only has an effect on client.say and client.me functions, not client.privmsg.

and the mixins installed by default:

  • new PrivmsgMessageRateLimiter(client) - Rate limits outgoing messages according to the rate limits imposed by Twitch. Configure the verified/known status of your bot using the config (see above).
  • new ConnectionRateLimiter(client) - Rate limits new connections accoding to the rate limits set in the config.
  • new UserStateTracker(client) - Used by other mixins. Keeps track of what state your bot user has in all channels.
  • new RoomStateTracker() - Used by other mixins. Keeps track of each channel's state, e.g. sub-mode etc.
  • new IgnoreUnhandledPromiseRejectionsMixin() - Silences UnhandledPromiseRejectionWarnings on promises returned by the client's functions. (installed for you if you activate the ignoreUnhandledPromiseRejections client option)

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
    我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
    原文