{"_id":"@5e7en/dank-twitch-irc","_rev":"1-e317a64006c5bfbf196f80fa6efef2a7","name":"@5e7en/dank-twitch-irc","dist-tags":{"latest":"4.3.2"},"versions":{"4.3.2":{"name":"@5e7en/dank-twitch-irc","version":"4.3.2","description":"Twitch IRC library for Node.js","main":"dist/index.js","types":"dist/index.d.ts","scripts":{"build":"tsc && sed -i '1,1 s/types=\"debug-logger\"/path=\"\\.\\.\\/types\\/debug-logger\\/index.d.ts\"/' ./dist/index.d.ts","check-format":"prettier --ignore-path .gitignore --check \"**/*.md\" \"**/*.js\" \"**/*.ts\" \"**/*.yml\" \"**/*.json\"","reformat":"prettier --ignore-path .gitignore --write \"**/*.md\" \"**/*.js\" \"**/*.ts\" \"**/*.yml\" \"**/*.json\"","lint":"eslint --ignore-path .gitignore --format codeframe --ext .ts --ext .js .","lintfix":"npm run lint -- --fix","test":"nyc mocha","clean":"rm -rf ./dist ./docs ./coverage ./.nyc_output ./mochawesome-report","docs":"typedoc --excludePrivate --excludeProtected -out ./docs lib/index.ts","generate-index":"npx create-ts-index@^1.10.2 create ./lib -w -i '.spec' && npm run reformat","precommit":"npm run clean && npm run build && npm run test && npm run lintfix && npm run reformat && npm run lint -- --max-warnings=0","generate-readme-toc":"doctoc README.md --github --title '## Table of Contents'"},"keywords":["twitch","irc","chat","tmi"],"author":{"name":"Ruben Anders"},"license":"MIT","repository":{"type":"git","url":"git+https://github.com/robotty/dank-twitch-irc.git"},"dependencies":{"@types/debug":"^4.1.5","@types/duplexify":"^3.6.0","debug-logger":"^0.4.1","duplexify":"^4.1.1","eventemitter3":"^4.0.7","lodash.camelcase":"^4.3.0","lodash.pickby":"^4.6.0","make-error-cause":"^2.3.0","ms":"^2.1.3","randomstring":"^1.1.5","semaphore-async-await":"^1.5.1","simple-websocket":"^9.0.0","split2":"^3.2.1","ts-toolbelt":"^9.1.7"},"devDependencies":{"@types/async-lock":"^1.1.2","@types/chai":"^4.2.12","@types/chai-as-promised":"^7.1.3","@types/eventemitter3":"^2.0.2","@types/lodash.camelcase":"^4.3.6","@types/lodash.pickby":"^4.6.6","@types/mocha":"^8.2.0","@types/ms":"^0.7.31","@types/node":"^14.14.14","@types/randomstring":"^1.1.6","@types/simple-websocket":"^7.0.1","@types/sinon":"^9.0.9","@types/split2":"^2.1.6","@types/ws":"^7.2.6","@typescript-eslint/eslint-plugin":"^4.10.0","@typescript-eslint/parser":"^4.10.0","chai":"^4.2.0","chai-as-promised":"^7.1.1","clarify":"^2.1.0","doctoc":"^2.0.0","eslint":"^7.16.0","mocha":"^8.1.1","mocha-junit-reporter":"^2.0.0","mochawesome":"^6.1.1","nyc":"^15.1.0","prettier":"^2.2.1","sinon":"^10.0.0","supports-color":"^8.0.0","ts-node":"^9.1.1","tsd":"^0.14.0","typedoc":"^0.20.1","typescript":"^4.1.3"},"gitHead":"366a919ab9c179b25023193e4bd0055c4a82fa11","bugs":{"url":"https://github.com/robotty/dank-twitch-irc/issues"},"homepage":"https://github.com/robotty/dank-twitch-irc#readme","_id":"@5e7en/dank-twitch-irc@4.3.2","_nodeVersion":"14.16.1","_npmVersion":"6.14.12","dist":{"integrity":"sha512-5lUIfAk6a6F1XSDL8vVNWT1FIgVh2B4CPJX4O+Up3GbUevihPLKYo+WwMUsERiCpEML6JUSPmaPqpYG8y77UeA==","shasum":"bd09e3fbe176147cc635200dae60b13bd2841bd5","tarball":"https://registry.npmjs.org/@5e7en/dank-twitch-irc/-/dank-twitch-irc-4.3.2.tgz","fileCount":910,"unpackedSize":1303559,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgeVzlCRA9TVsSAnZWagAA1EoP/1iTbrOcpVZYTbyVp59t\nmbpjPxDhzqanLQ8obcaXTIUJD5YKrKb32mFJpv2KSOhqc1kO7OAFWsk+PAy3\nXHuSONajtznU2+izVH00CCzyI06SN36PaBW1GwvB7XtZcQtix8IvkyIqBL3n\nkJcn/jmlWlJQtOn9lTnGqO0vKrcXVNmPZ7I8DrhN2wDw1UuHnqacKeRWzEWy\nePTJCRZPyUTs3cFe+ehBYTdBR7/g2m9MSjgeWWu1lqv3brKKBuQQMkNMLqDu\nYzxksvBpnpVRXH9F1wnla3eCru4YUzMgCCPZFVHbRnYf3xB++t0mJIVIXd7Y\ni2NNjRai8FhPTRmWDA6q8W1ga3gwoWATIjWudMobKV+39VUVSZF1dM7vK45e\n09/sN769EZdslzANFF+TOdiFqb38szSUYu3obCZcIftVfmE0XNdccmiWb+6x\n8IS22LOFE4RdWbJjGDVtjs5F0yWXuI7+F7S2hUyxXcQvI4w6C2zOAqVxPpJv\n+Az2WX5EOQiET5yqWNAyOd7Y5l2wLw3PxNWdg8A1lpt21WfiBJR1x2ZudYz6\nMFpubmGL6qXKlUoys5eILwIkHLiJWrxMPL6l+FZT53dZSm0QuR8r8H0xTwdz\nfUDAMo3dgS6EHPw6wgYi06qNa929Yq84jU5EeGqoqPYLTWyp5N2pll6S5Y1Z\nCW0k\r\n=zKSx\r\n-----END PGP SIGNATURE-----\r\n","signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEYCIQCgefYWij94ZGwlQgrsB6CX++jzrSjl87wB+e34jwOT4gIhANhnhlCogOcYiuH9F80DyPMLeL9mA11RtQKAklsxeDu+"}]},"_npmUser":{"name":"5e7en","email":"5e7en7@protonmail.com"},"directories":{},"maintainers":[{"name":"5e7en","email":"5e7en7@protonmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/dank-twitch-irc_4.3.2_1618566372922_0.42531536880079424"},"_hasShrinkwrap":false}},"time":{"created":"2021-04-16T09:46:12.853Z","4.3.2":"2021-04-16T09:46:13.118Z","modified":"2022-04-04T10:48:22.215Z"},"maintainers":[{"name":"5e7en","email":"5e7en7@protonmail.com"}],"description":"Twitch IRC library for Node.js","homepage":"https://github.com/robotty/dank-twitch-irc#readme","keywords":["twitch","irc","chat","tmi"],"repository":{"type":"git","url":"git+https://github.com/robotty/dank-twitch-irc.git"},"author":{"name":"Ruben Anders"},"bugs":{"url":"https://github.com/robotty/dank-twitch-irc/issues"},"license":"MIT","readme":"# dank-twitch-irc\n\n![Build](https://github.com/robotty/dank-twitch-irc/workflows/Build/badge.svg)\n\nNode.js-only Twitch IRC lib, written in TypeScript.\n\nRequires Node.js 10 (LTS) or above.\n\n- [View on GitHub](https://github.com/robotty/dank-twitch-irc)\n- [View on npm](https://www.npmjs.com/package/dank-twitch-irc)\n- [View documentation](https://robotty.github.io/dank-twitch-irc/)\n\n<!-- START doctoc generated TOC please keep comment here to allow auto update -->\n<!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE -->\n\n## Table of Contents\n\n- [Usage](#usage)\n- [Available client events](#available-client-events)\n- [Handling `USERNOTICE` messages](#handling-usernotice-messages)\n  - [Sub and resub](#sub-and-resub)\n  - [Incoming raids](#incoming-raids)\n  - [Subgift](#subgift)\n  - [Anonsubgift](#anonsubgift)\n  - [anongiftpaidupgrade, giftpaidupgrade](#anongiftpaidupgrade-giftpaidupgrade)\n  - [ritual](#ritual)\n  - [bitsbadgetier](#bitsbadgetier)\n- [ChatClient API](#chatclient-api)\n- [API Documentation](#api-documentation)\n- [Client options](#client-options)\n- [Features](#features)\n- [Extra Mixins](#extra-mixins)\n- [Tests](#tests)\n- [Lint and check code style](#lint-and-check-code-style)\n\n<!-- END doctoc generated TOC please keep comment here to allow auto update -->\n\n## Usage\n\n```javascript\nconst { ChatClient } = require(\"dank-twitch-irc\");\n\nlet client = new ChatClient();\n\nclient.on(\"ready\", () => console.log(\"Successfully connected to chat\"));\nclient.on(\"close\", (error) => {\n  if (error != null) {\n    console.error(\"Client closed due to error\", error);\n  }\n});\n\nclient.on(\"PRIVMSG\", (msg) => {\n  console.log(`[#${msg.channelName}] ${msg.displayName}: ${msg.messageText}`);\n});\n\n// See below for more events\n\nclient.connect();\nclient.join(\"forsen\");\n```\n\n## Available client events\n\n- **`client.on(\"connecting\", () => { /* ... */ })`**: Called when the client\n  starts connecting for the first time.\n- **`client.on(\"connect\", () => { /* ... */ })`**: Called when the client\n  connects for the first time. This is called when the transport layer\n  connections (e.g. TCP or WebSocket connection is established), not when login\n  to IRC succeeds.\n- **`client.on(\"ready\", () => { /* ... */ })`**: Called when the client becomes\n  ready for the first time (login to the chat server is successful.)\n- **`client.on(\"close\", (error?: Error) => { /* ... */ })`**: Called when the\n  client is terminated as a whole. Not called for individual connections that\n  were disconnected. Can be caused for example by a invalid OAuth token (failure\n  to login), or when `client.close()` or `client.destroy()` was called. `error`\n  is only non-null if the client was closed by a call to `client.close()`.\n- **`client.on(\"error\", (error: Error?) => { /* ... */ })`**: Called when any\n  error occurs on the client, including non-fatal errors such as a message that\n  could not be delivered due to an error.\n- **`client.on(\"rawCommand\", (cmd: string) => { /* ... */ })`**: Called when any\n  command is executed by the client.\n- **`client.on(\"message\", (message: IRCMessage) => { /* ... */ })`**: Called on\n  every incoming message. If the message is a message that is further parsed (I\n  called these \"twitch messages\" in this library) then the `message` passed to\n  this handler will already be the specific type, e.g. `PrivmsgMessage` if the\n  command is `PRIVMSG`.\n- **`client.on(\"PRIVMSG\", (message: PrivmsgMessage) => { /* ... */ })`**: Called\n  on incoming messages whose command is `PRIVMSG`. The `message` parameter is\n  always instanceof `PrivmsgMessage`. (See the API documentation for what\n  properties exist on all `PrivmsgMessage` instances)\n\n  For example:\n\n  ```javascript\n  client.on(\"CLEARCHAT\", (msg) => {\n    if (msg.isTimeout()) {\n      console.log(\n        `${msg.targetUsername} just got timed out for ` +\n          `${msg.banDuration} seconds in channel ${msg.channelName}`\n      );\n    }\n  });\n  ```\n\n  Other message types that have specific message parsing are:\n\n  - **`CLEARCHAT`** (maps to [`ClearchatMessage`][clearchat]) - Timeout and ban\n    messages\n  - **`CLEARMSG`** (maps to [`ClearmsgMessage`][clearmsg]) - Single message\n    deletions (initiated by `/delete`)\n  - **`HOSTTARGET`** (maps to [`HosttargetMessage`][hosttarget]) - A channel\n    entering or exiting host mode.\n  - **`NOTICE`** (maps to [`NoticeMessage`][notice]) - Various notices, such as\n    when you `/help`, a command fails, the error response when you are timed\n    out, etc.\n  - **`PRIVMSG`** (maps to [`PrivmsgMessage`][privmsg]) - Normal chat messages\n  - **`ROOMSTATE`** (maps to [`RoomstateMessage`][roomstate]) - A change to a\n    channel's followers mode, subscribers-only mode, r9k mode, followers mode,\n    slow mode etc.\n  - **`USERNOTICE`** (maps to [`UsernoticeMessage`][usernotice]) - Subs, resubs,\n    sub gifts, rituals, raids, etc. - See more details about how to handle this\n    message type below.\n  - **`USERSTATE`** (maps to [`UserstateMessage`][userstate]) - Your own state\n    (e.g. badges, color, display name, emote sets, mod status), sent on every\n    time you join a channel or send a `PRIVMSG` to a channel\n  - **`GLOBALUSERSTATE`** (maps to\n    [`GlobaluserstateMessage`][globaluserstate]) - Logged in user's \"global\n    state\", sent once on every login (Note that due to the used connection pool\n    you can receive this multiple times during your bot's runtime)\n  - **`WHISPER`** (maps to [`WhisperMessage`][whisper]) - Somebody else\n    whispering you\n  - **`JOIN`** (maps to [`JoinMessage`][join]) - You yourself joining a channel,\n    of if you have `requestMembershipCapability` enabled, also other users\n    joining channels you are joined to.\n  - **`PART`** (maps to [`JoinMessage`][part]) - You yourself parting (leaving)\n    a channel, of if you have `requestMembershipCapability` enabled, also other\n    users parting channels you are joined to.\n  - **`RECONNECT`** (maps to [`ReconnectMessage`][reconnect]) - When the twitch\n    server tells a client to reconnect and re-join channels (You don't have to\n    listen for this yourself, this is done automatically already)\n  - **`PING`** (maps to [`PingMessage`][ping]) - When the twitch server sends a\n    ping, expecting a pong back from the client to verify if the connection is\n    still alive. (You don't have to listen for this yourself, the client\n    automatically responds for you)\n  - **`PONG`** (maps to [`PongMessage`][pong]) - When the twitch server responds\n    to our `PING` requests (The library automatically sends a `PING` request\n    every 30 seconds to verify connections are alive)\n  - **`CAP`** (maps to [`CapMessage`][cap]) - Message type received once during\n    connection startup, acknowledging requested capabilities.\n\nAll other commands (if they don't have a special parsed type like the ones\nlisted above) will still be emitted under their command name as an\n[`IRCMessage`][ircmessage], e.g.:\n\n```javascript\n// :tmi.twitch.tv 372 botfactory :You are in a maze of twisty passages, all alike.\n// msg will be an instance of IRCMessage\nclient.on(\"372\", (msg) =>\n  console.log(`Server MOTD is: ${msg.ircParameters[1]}`)\n);\n```\n\n## Handling `USERNOTICE` messages\n\nThe `USERNOTICE` message type is special because it encapsulates a wide range of\nevents, including:\n\n- Subs\n- Resubs\n- Gift subscription\n- Incoming raid and\n- Channel rituals,\n\nwhich are all emitted under the `USERNOTICE` event. See also\n[the offical documentation](https://dev.twitch.tv/docs/irc/tags/#usernotice-twitch-tags)\nabout the `USERNOTICE` command.\n\nEvery `USERNOTICE` message is sent by a user, and always contains a\n`msg.systemMessage` (This is a message that twitch formats for you, e.g.\n`4 raiders from PotehtoO have joined!` for a `raid` message.) Additionally,\nevery `USERNOTICE` message can have a message that is additionally sent/shared\nfrom the sending user, for example the \"share this message with the streamer\"\nmessage sent with resubs and subs. If no message is sent by the user,\n`msg.messageText` is `undefined`.\n\n`dank-twitch-irc` currently does not have special parsing code for each\n`USERNOTICE` `messageTypeID` (e.g. `sub`, `resub`, `raid`, etc...) - Instead the\nparser assigns all `msg-param-` tags to the `msg.eventParams` object. See below\non what `msg.eventParams` are available for each of the `messageTypeID`s.\n\n### Sub and resub\n\nWhen a user subscribes or resubscribes with his own money/prime (this is NOT\nsent for gift subs, see below)\n\n```javascript\nchatClient.on(\"USERNOTICE\", (msg) => {\n  // sub and resub messages have the same parameters, so we can handle them both the same way\n  if (!msg.isSub() && !msg.isResub()) {\n    return;\n  }\n\n  /*\n   * msg.eventParams are:\n   *\n   * {\n   *   \"cumulativeMonths\": 10,\n   *   \"cumulativeMonthsRaw\": \"10\",\n   *   \"subPlan\": \"1000\", // Prime, 1000, 2000 or 3000\n   *   \"subPlanName\": \"The Ninjas\",\n   *\n   *   // if shouldShareStreak is false, then\n   *   // streakMonths/streakMonthsRaw will be 0\n   *   // (the user did not share their sub streak in chat)\n   *   \"shouldShareStreak\": true,\n   *   \"streakMonths\": 7,\n   *   \"streakMonthsRaw\": \"7\"\n   * }\n   * Sender user of the USERNOTICE message is the user subbing/resubbing.\n   */\n\n  if (msg.isSub()) {\n    // Leppunen just subscribed to ninja with a tier 1000 (The Ninjas) sub for the first time!\n    console.log(\n      msg.displayName +\n        \" just subscribed to \" +\n        msg.channelName +\n        \" with a tier \" +\n        msg.eventParams.subPlan +\n        \" (\" +\n        msg.eventParams.subPlanName +\n        \") sub for the first time!\"\n    );\n  } else if (msg.isResub()) {\n    let streakMessage = \"\";\n    if (msg.eventParams.shouldShareStreak) {\n      streakMessage =\n        \", currently \" + msg.eventParams.streakMonths + \" months in a row\";\n    }\n\n    // Leppunen just resubscribed to ninja with a tier 1000 (The Ninjas) sub!\n    // They are resubscribing for 10 months, currently 7 months in a row!\n    console.log(\n      msg.displayName +\n        \" just resubscribed to \" +\n        msg.channelName +\n        \" with a tier \" +\n        msg.eventParams.subPlan +\n        \" (\" +\n        msg.eventParams.subPlanName +\n        \") sub! They are resubscribing for \" +\n        msg.eventParams.cumulativeMonths +\n        \" months\" +\n        streakMessage +\n        \"!\"\n    );\n  }\n\n  if (msg.messageText != null) {\n    // you also have access to lots of other properties also present on PRIVMSG messages,\n    // such as msg.badges, msg.senderUsername, msg.badgeInfo, msg.bits/msg.isCheer(),\n    // msg.color, msg.emotes, msg.messageID, msg.serverTimestamp, etc...\n    console.log(\n      msg.displayName +\n        \" shared the following message with the streamer: \" +\n        msg.messageText\n    );\n  } else {\n    console.log(\"They did not share a message with the streamer.\");\n  }\n});\n```\n\n### Incoming raids\n\nTwitch says:\n\n> Incoming raid to a channel. Raid is a Twitch tool that allows broadcasters to\n> send their viewers to another channel, to help support and grow other members\n> in the community.)\n\n```javascript\nchatClient.on(\"USERNOTICE\", (msg) => {\n  if (!msg.isRaid()) {\n    return;\n  }\n\n  /*\n   * msg.eventParams are:\n   * {\n   *   \"displayName\": \"Leppunen\",\n   *   \"login\": \"leppunen\",\n   *   \"viewerCount\": 12,\n   *   \"viewerCountRaw\": \"12\"\n   * }\n   * Sender user of the USERNOTICE message is the user raiding this channel.\n   * Note that the display name and login present in msg.eventParams are\n   * the same as msg.displayName and msg.senderUsername, so it doesn't matter\n   * which one you use (although I recommend the properties directly on the\n   * message object, not in eventParams)\n   */\n\n  // source user is the channel/streamer raiding\n  // Leppunen just raided Supinic with 12 viewers!\n  console.log(\n    msg.displayName +\n      \" just raided \" +\n      msg.channelName +\n      \" with \" +\n      msg.eventParams.viewerCount +\n      \" viewers!\"\n  );\n});\n```\n\n### Subgift\n\nWhen a user gifts somebody else a subscription.\n\n```javascript\nchatClient.on(\"USERNOTICE\", (msg) => {\n  if (!msg.isSubgift()) {\n    return;\n  }\n\n  /*\n   * msg.eventParams are:\n   * {\n   *   \"months\": 5,\n   *   \"monthsRaw\": \"5\",\n   *   \"giftMonths\": 5,\n   *   \"giftMonthsRaw\": \"5\",\n   *   \"recipientDisplayName\": \"Leppunen\",\n   *   \"recipientID\": \"42239452\",\n   *   \"recipientUsername\": \"leppunen\",\n   *   \"subPlan\": \"1000\",\n   *   \"subPlanName\": \"The Ninjas\",\n   *   \"senderCount\": 5,\n   *   \"senderCountRaw\": \"5\",\n   * }\n   * Sender user of the USERNOTICE message is the user gifting the subscription.\n   */\n\n  if (msg.eventParams.months === 1) {\n    // Leppunen just gifted NymN a fresh tier 1000 (The Ninjas) sub to ninja!\n    console.log(\n      msg.displayName +\n        \" just gifted \" +\n        msg.eventParams.recipientDisplayName +\n        \" a fresh tier \" +\n        msg.eventParams.subPlan +\n        \" (\" +\n        msg.eventParams +\n        \") sub to \" +\n        msg.channelName +\n        \"!\"\n    );\n  } else {\n    // Leppunen just gifted NymN a tier 1000 (The Ninjas) resub to ninja, that's 7 months in a row!\n    console.log(\n      msg.displayName +\n        \" just gifted \" +\n        msg.eventParams.recipientDisplayName +\n        \" a tier \" +\n        msg.eventParams.subPlan +\n        \" (\" +\n        msg.eventParams +\n        \") resub to \" +\n        msg.channelName +\n        \", that's \" +\n        msg.eventParams.months +\n        \" in a row!\"\n    );\n  }\n\n  // note: if the subgift was from an anonymous user, the sender user for the USERNOTICE message will be\n  // AnAnonymousGifter (user ID 274598607)\n  if (msg.senderUserID === \"274598607\") {\n    console.log(\"That (re)sub was gifted anonymously!\");\n  }\n});\n```\n\n### Anonsubgift\n\nWhen an anonymous user gifts a subscription to a viewer.\n\n```javascript\nchatClient.on(\"USERNOTICE\", (msg) => {\n  if (!msg.isAnonSubgift()) {\n    return;\n  }\n\n  /*\n   * msg.eventParams are:\n   * {\n   *   \"months\": 5,\n   *   \"monthsRaw\": \"5\",\n   *   \"recipientDisplayName\": \"Leppunen\",\n   *   \"recipientID\": \"42239452\",\n   *   \"recipientUsername\": \"leppunen\",\n   *   \"subPlan\": \"1000\",\n   *   \"subPlanName\": \"The Ninjas\"\n   * }\n   *\n   * WARNING! Sender user of the USERNOTICE message is the broadcaster (e.g. Ninja\n   * in the example below)\n   */\n\n  if (msg.eventParams.months === 1) {\n    // An anonymous gifter just gifted NymN a fresh tier 1000 (The Ninjas) sub to ninja!\n    console.log(\n      \"An anonymous gifter just gifted \" +\n        msg.eventParams.recipientDisplayName +\n        \" a fresh tier \" +\n        msg.eventParams.subPlan +\n        \" (\" +\n        msg.eventParams +\n        \") sub to \" +\n        msg.channelName +\n        \"!\"\n    );\n  } else {\n    // An anonymous gifter just gifted NymN a tier 1000 (The Ninjas) resub to ninja, that's 7 months in a row!\n    console.log(\n      \"An anonymous gifter just gifted \" +\n        msg.eventParams.recipientDisplayName +\n        \" a tier \" +\n        msg.eventParams.subPlan +\n        \" (\" +\n        msg.eventParams +\n        \") resub to \" +\n        msg.channelName +\n        \", that's \" +\n        msg.eventParams.months +\n        \" in a row!\"\n    );\n  }\n});\n```\n\n### anongiftpaidupgrade, giftpaidupgrade\n\nWhen a user commits to continue the gift sub by another user (or an anonymous\ngifter).\n\n```javascript\nchatClient.on(\"USERNOTICE\", (msg) => {\n  if (!msg.isAnonGiftPaidUpgrade()) {\n    return;\n  }\n\n  /*\n   * msg.eventParams are:\n   * EITHER: (ONLY when a promotion is running!)\n   * {\n   *   \"promoName\": \"Subtember 2018\",\n   *   \"promoGiftTotal\": 3987234,\n   *   \"promoGiftTotalRaw\": \"3987234\"\n   * }\n   * OR: (when no promotion is running)\n   * {}\n   *\n   * Sender user of the USERNOTICE message is the user continuing their sub.\n   */\n\n  // Leppunen is continuing their ninja gift sub they got from an anonymous user!\n  console.log(\n    msg.displayName +\n      \" is continuing their \" +\n      msg.channelName +\n      \" gift sub they got from an anonymous user!\"\n  );\n});\n```\n\n```javascript\nchatClient.on(\"USERNOTICE\", (msg) => {\n  if (!msg.isGiftPaidUpgrade()) {\n    return;\n  }\n\n  /*\n   * msg.eventParams are:\n   * EITHER: (ONLY when a promotion is running!)\n   * {\n   *   \"promoName\": \"Subtember 2018\",\n   *   \"promoGiftTotal\": 3987234,\n   *   \"promoGiftTotalRaw\": \"3987234\",\n   *   \"senderLogin\": \"krakenbul\",\n   *   \"senderName\": \"Krakenbul\"\n   * }\n   * OR: (when no promotion is running)\n   * {\n   *   \"senderLogin\": \"krakenbul\",\n   *   \"senderName\": \"Krakenbul\"\n   * }\n   *\n   * Sender user of the USERNOTICE message is the user continuing their sub.\n   */\n\n  // Leppunen is continuing their ninja gift sub they got from Krakenbul!\n  console.log(\n    msg.displayName +\n      \" is continuing their \" +\n      msg.channelName +\n      \" gift sub they got from \" +\n      msg.msgParam.senderName +\n      \"!\"\n  );\n});\n```\n\n### ritual\n\nChannel ritual. Twitch says:\n\n> Channel _ritual_. Many channels have special rituals to celebrate viewer\n> milestones when they are shared. The rituals notice extends the sharing of\n> these messages to other viewer milestones (initially, a new viewer chatting\n> for the first time).\n\n```javascript\nchatClient.on(\"USERNOTICE\", (msg) => {\n  if (!msg.isRitual()) {\n    return;\n  }\n\n  /*\n   * msg.eventParams are:\n   * {\n   *   \"ritualName\": \"new_chatter\"\n   * }\n   *\n   * Sender user of the USERNOTICE message is the user performing the\n   * ritual (e.g. the new chatter).\n   */\n\n  // Leppunen is new to ninja's chat! Say hello!\n  if (msg.eventParams.ritualName === \"new_chatter\") {\n    console.log(\n      msg.displayName + \" is new to \" + msg.channelName + \"'s chat! Say hello!\"\n    );\n  } else {\n    console.warn(\n      \"Unknown (unhandled) ritual type: \" + msg.eventParams.ritualName\n    );\n  }\n});\n```\n\n### bitsbadgetier\n\nWhen a user cheers and earns himself a new bits badge with that cheer (e.g. they\njust cheered more than/exactly 10000 bits in total, and just earned themselves\nthe 10k bits badge)\n\n```javascript\nchatClient.on(\"USERNOTICE\", (msg) => {\n  if (!msg.isBitsBadgeTier()) {\n    return;\n  }\n\n  /*\n   * msg.eventParams are:\n   * {\n   *   \"threshold\": 10000,\n   *   \"thresholdRaw\": \"10000\",\n   * }\n   *\n   * Sender user of the USERNOTICE message is the user cheering the bits.\n   */\n\n  // Leppunen just earned themselves the 10000 bits badge in ninja's channel!\n  console.log(\n    msg.displayName +\n      \" just earned themselves the \" +\n      msg.threshold +\n      \" bits badge in \" +\n      msg.channelName +\n      \"'s channel!\"\n  );\n});\n```\n\n## ChatClient API\n\nYou probably will want to use these functions on `ChatClient` most frequently:\n\n- `client.join(channelName: string): Promise<void>` - Join (Listen to) the\n  channel given by the channel name\n- `client.joinAll(channelNames: string[]): Promise<void>` - Join (Listen to) all\n  of the listed channels at once (bulk join)\n- `client.part(channelName: string): Promise<void>` - Part (Leave/Unlisten) the\n  channel given by the channel name\n- `client.privmsg(channelName: string, message: string): Promise<void>` - Send a\n  raw `PRIVMSG` to the given channel. You can issue chat commands with this\n  function, e.g. `client.privmsg(\"forsen\", \"/timeout weeb123 5\")` or normal\n  messages, e.g. `client.privmsg(\"forsen\", \"Kappa Keepo PogChamp\")`.\n- `client.say(channelName: string, message: string): Promise<void>` - Say a\n  normal chat message in the given channel. If a command is given as `message`,\n  it will be escaped.\n- `client.me(channelName: string, message: string): Promise<void>` - Post a\n  `/me` message in the given channel.\n- `client.timeout(channelName: string, username: string, length: number, reason?: string): Promise<void>` -\n  Timeout `username` for `length` seconds in `channelName`. Optionally accepts a\n  reason to set.\n- `client.ban(channelName: string, username: string, reason?: string): Promise<void>` -\n  Ban `username` in `channelName`. Optionally accepts a reason to set.\n- `client.ping()` - Send a `PING` on a connection from the pool, and awaits the\n  `PONG` response. You can use this to measure server latency, for example.\n- `client.whisper(username: string, message: string)` - Send the user a whisper\n  from the bot.\n- `client.setColor(color: Color)` - set the username color of your bot account.\n  E.g. `client.setColor({ r: 255, g: 0, b: 127 })`.\n- `client.getMods(channelName: string)` and `client.getVips(channelName: string)` -\n  Get a list of moderators/VIPs in a channel. Returns\n  a promise that resolves to an array of strings (login names of the moderators/VIPs).\n  Note that due to Twitch's restrictions, this function cannot be used with anonymous chat clients.\n  (The request will time out if your chat client is logged in as anonymous.)\n\nExtra functionality:\n\n- `client.sendRaw(command: string): void` - Send a raw IRC command to a\n  connection in the connection pool.\n- `client.unconnected (boolean)` - Returns whether the client is unconnected.\n- `client.connecting (boolean)` - Returns whether the client is connecting.\n- `client.connected (boolean)` - Returns whether the client is connected\n  (Transport layer is connected).\n- `client.ready (boolean)` - Returns whether the client is ready (Logged into\n  IRC server).\n- `client.closed (boolean)` - Returns whether the client is closed.\n\nNote that channel names in the above functions always refer to the \"login name\"\nof a twitch channel. Channel names may not be capitalized, e.g. `Forsen` would\nbe invalid, but `forsen` not. This library also does not accept the leading `#`\ncharacter and never returns it on any message objects (e.g. `msg.channelName`\nwould be `forsen`, not `#forsen`).\n\n## API Documentation\n\nGenerated API documentation can be found here:\nhttps://robotty.github.io/dank-twitch-irc\n\n## Client options\n\nPass options to the `ChatClient` constructor. More available options are\ndocumented in the Below are all possible options and their default values:\n\n**Note! ALL of these configuration options are _optional_!** I highly recommend you\nonly set the very config options you need, the rest are usually at a reasonable default.  \nFor most bots, you only need to set `username` and `password`:\n\n```javascript\nlet client = new ChatClient({\n  username: \"your-bot-username\",\n  password: \"0123456789abcdef1234567\",\n});\n```\n\nNevertheless, here are examples of all possible config options:\n\n```javascript\nlet client = new ChatClient({\n  username: \"your-bot-username\", // justinfan12345 by default - For anonymous chat connection\n  password: \"0123456789abcdef1234567\", // undefined by default (no password)\n\n  // Message rate limits configuration for verified and known bots\n  // pick one of the presets or configure custom rates as shown below:\n  rateLimits: \"default\",\n  // or:\n  rateLimits: \"knownBot\",\n  // or:\n  rateLimits: \"verifiedBot\",\n  // or:\n  rateLimits: {\n    highPrivmsgLimits: 100,\n    lowPrivmsgLimits: 20,\n  },\n\n  // Configuration options for the backing connections:\n  // Plain TCP or TLS\n  connection: {\n    type: \"tcp\", // tcp by default\n    secure: false, // true by default\n    // host and port must both be specified at once\n    host: \"custom-chat-server.com\", // irc.chat.twitch.tv by default\n    port: 1234, // 6697/6667 by default, depending on the \"secure\" setting\n  },\n  // or:\n  connection: {\n    type: \"websocket\",\n    secure: true, // use preset URL of irc-ws.chat.twitch.tv\n  },\n  // or:\n  connection: {\n    type: \"websocket\",\n    url: \"wss://custom-url.com/abc/def\", // custom URL\n  },\n  // or:\n  connection: {\n    type: \"duplex\",\n    stream: () => aNodeJsDuplexInstance, // read and write to a custom object\n    // implementing the Duplex interface from Node.js\n    // the function you specify is called for each new connection\n\n    preSetup: true, // false by default, makes the lib skip login\n    // and capabilities negotiation on connection startup\n  },\n\n  // how many channels each individual connection should join at max\n  maxChannelCountPerConnection: 100, // 90 by default\n\n  // custom parameters for connection rate limiting\n  connectionRateLimits: {\n    parallelConnections: 5, // 1 by default\n    // time to wait after each connection before a new connection can begin\n    releaseTime: 1000, // in milliseconds, 2 seconds by default\n  },\n\n  // I recommend you leave this off by default, it makes your bot faster\n  // If you need live update of who's joining and leaving chat,\n  // poll the tmi.twitch.tv chatters endpoint instead since it\n  // is also more reliable\n  requestMembershipCapability: false, // false by default\n\n  // read more about mixins below\n  // this disables the connection rate limiter, message rate limiter\n  // and Room- and Userstate trackers (which are important for other mixins)\n  installDefaultMixins: false, // true by default\n\n  // Silence UnandledPromiseRejectionWarnings on all client methods\n  // that return promises.\n  // With this option enabled, the returned promises will still be rejected/\n  // resolved as without this option, this option ONLY silences the\n  // UnhandledPromiseRejectionWarning.\n  ignoreUnhandledPromiseRejections: true, // false by default\n});\n```\n\n## Features\n\nThis client currently supports the following features:\n\n- Connection pooling and round-robin connection usage\n- Automatic rate limiter for connection opening and chat commands\n- All twitch-specific message types parsed (`CLEARCHAT`, `CLEARMSG`,\n  `GLOBALUSERSTATE`, `HOSTTARGET`, `JOIN`, `NOTICE`, `PART`, `PING`, `PONG`,\n  `PRIVMSG`, `RECONNECT`, `ROOMSTATE`, `USERNOTICE`, `USERSTATE`, `WHISPER`,\n  `CAP`)\n- Accurate response to server responses (e.g. error thrown if you are banned\n  from channel/channel is suspended/login is invalid etc.)\n- Bulk join functionality to join lots of channels quickly\n- Implements the recommended connection control, utilizing `RECONNECT`, `PING`\n  and `PONG`\n- Full tracking of room state (e.g. submode, emote-only mode, followers mode,\n  r9k etc.) and user state (badges, moderator state, color, etc).\n- Most function calls return promises but errors can also be handled by\n  subscribing to the error event\n- Slow-mode rate limiter for non-VIP/moderator bots (waits either the global\n  ~1.3 sec/channel-specific slow mode)\n- Support for different types of transport (in-memory, TCP, WebSocket)\n\n## Extra Mixins\n\nThere are some features you might find useful in your bot that are not necessary\nfor general client/bot operations, so they were packaged as **mixins**. You can\nactivate mixins by calling:\n\n```javascript\nconst { ChatClient, AlternateMessageModifier } = require(\"dank-twitch-irc\");\n\nlet client = new ChatClient();\n\nclient.use(new AlternateMessageModifier(client));\n```\n\nAvailable mixins are:\n\n- `new AlternateMessageModifier(client)` will allow your bot to send the same\n  message within a 30 seconds period. You must also use `client.say` and\n  `client.me` for this mixin to behave consistently and reliably.\n- `new SlowModeRateLimiter(client, /* optional */ maxWaitingMessages)` will rate\n  limit your messages in channels where your bot is not moderator, VIP or\n  broadcaster and has to wait a bit between sending messages. If more than\n  `maxWaitingMessages` are waiting, the outgoing message will be dropped\n  silently. `maxWaitingMessages` defaults to 10. Note this mixin only has an\n  effect on `client.say` and `client.me` functions, not `client.privmsg`.\n\nand the mixins installed by default:\n\n- `new PrivmsgMessageRateLimiter(client)` - Rate limits outgoing messages\n  according to the rate limits imposed by Twitch. Configure the verified/known\n  status of your bot using the config (see above).\n- `new ConnectionRateLimiter(client)` - Rate limits new connections accoding to\n  the rate limits set in the config.\n- `new UserStateTracker(client)` - Used by other mixins. Keeps track of what\n  state your bot user has in all channels.\n- `new RoomStateTracker()` - Used by other mixins. Keeps track of each channel's\n  state, e.g. sub-mode etc.\n- `new IgnoreUnhandledPromiseRejectionsMixin()` - Silences\n  `UnhandledPromiseRejectionWarning`s on promises returned by the client's\n  functions. (installed for you if you activate the\n  `ignoreUnhandledPromiseRejections` client option)\n\n## Tests\n\n    npm run test\n\nTest run report is available in `./mochawesome-report/mochawesome.html`.\nCoverage report is produced as `./coverage/index.html`.\n\n## Lint and check code style\n\n```bash\n# Run eslint and tslint rules and checks code style with prettier\nnpm run lint\n```\n\n```bash\n# Run eslint, tslint and pretter fixers\nnpm run lintfix\n```\n\n[clearchat]: https://robotty.github.io/dank-twitch-irc/classes/clearchatmessage.html\n[clearmsg]: https://robotty.github.io/dank-twitch-irc/classes/clearmsgmessage.html\n[hosttarget]: https://robotty.github.io/dank-twitch-irc/classes/hosttargetmessage.html\n[notice]: https://robotty.github.io/dank-twitch-irc/classes/noticemessage.html\n[privmsg]: https://robotty.github.io/dank-twitch-irc/classes/privmsgmessage.html\n[roomstate]: https://robotty.github.io/dank-twitch-irc/classes/roomstatemessage.html\n[usernotice]: https://robotty.github.io/dank-twitch-irc/classes/usernoticemessage.html\n[userstate]: https://robotty.github.io/dank-twitch-irc/classes/userstatemessage.html\n[globaluserstate]: https://robotty.github.io/dank-twitch-irc/classes/globaluserstatemessage.html\n[whisper]: https://robotty.github.io/dank-twitch-irc/classes/whispermessage.html\n[join]: https://robotty.github.io/dank-twitch-irc/classes/joinmessage.html\n[part]: https://robotty.github.io/dank-twitch-irc/classes/partmessage.html\n[reconnect]: https://robotty.github.io/dank-twitch-irc/classes/reconnectmessage.html\n[ping]: https://robotty.github.io/dank-twitch-irc/classes/pingmessage.html\n[pong]: https://robotty.github.io/dank-twitch-irc/classes/pongmessage.html\n[cap]: https://robotty.github.io/dank-twitch-irc/classes/capmessage.html\n[ircmessage]: https://robotty.github.io/dank-twitch-irc/classes/ircmessage.html\n","readmeFilename":"README.md"}