Repository: twitch-apis/twitch-js
Pull request #130 (2018-08-10 06:28:53)

Feature: sub gifts to the community

codecov[bot]: # [Codecov](https://codecov.io/gh/twitch-apis/twitch-js/pull/130?src=pr&el=h1) Report
> :exclamation: No coverage uploaded for pull request base (`next@5adbe0b`). [Click here to learn what that means](https://docs.codecov.io/docs/error-reference#section-missing-base-commit).
> The diff coverage is `100%`.

[![Impacted file tree graph](https://codecov.io/gh/twitch-apis/twitch-js/pull/130/graphs/tree.svg?width=650&height=150&src=pr&token=qKety0Jenl)](https://codecov.io/gh/twitch-apis/twitch-js/pull/130?src=pr&el=tree)

```diff
@@           Coverage Diff           @@
##             next     #130   +/-   ##
=======================================
  Coverage        ?   95.66%           
=======================================
  Files           ?       21           
  Lines           ?      484           
  Branches        ?       62           
=======================================
  Hits            ?      463           
  Misses          ?       20           
  Partials        ?        1
```


| [Impacted Files](https://codecov.io/gh/twitch-apis/twitch-js/pull/130?src=pr&el=tree) | Coverage Δ | |
|---|---|---|
| [src/Chat/constants.js](https://codecov.io/gh/twitch-apis/twitch-js/pull/130/diff?src=pr&el=tree#diff-c3JjL0NoYXQvY29uc3RhbnRzLmpz) | `100% <ø> (ø)` | |
| [src/Chat/utils/parsers/index.js](https://codecov.io/gh/twitch-apis/twitch-js/pull/130/diff?src=pr&el=tree#diff-c3JjL0NoYXQvdXRpbHMvcGFyc2Vycy9pbmRleC5qcw==) | `100% <100%> (ø)` | |
| [src/Chat/utils/parsers/tags.js](https://codecov.io/gh/twitch-apis/twitch-js/pull/130/diff?src=pr&el=tree#diff-c3JjL0NoYXQvdXRpbHMvcGFyc2Vycy90YWdzLmpz) | `100% <100%> (ø)` | |

------

[Continue to review full report at Codecov](https://codecov.io/gh/twitch-apis/twitch-js/pull/130?src=pr&el=continue).
> **Legend** - [Click here to learn more](https://docs.codecov.io/docs/codecov-delta)
> `Δ = absolute <relative> (impact)`, `ø = not affected`, `? = missing data`
> Powered by [Codecov](https://codecov.io/gh/twitch-apis/twitch-js/pull/130?src=pr&el=footer). Last update [5adbe0b...8646b7d](https://codecov.io/gh/twitch-apis/twitch-js/pull/130?src=pr&el=lastupdated). Read the [comment docs](https://docs.codecov.io/docs/pull-request-comments).


Patch:
diff --git a/.travis.yml b/.travis.yml
index 9cfe7d3..7682aab 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -67,4 +67,5 @@ deploy:
       branch: next
 
 after_deploy:
-  - git push --follow-tags https://$GITHUB_TOKEN@github.com/$TRAVIS_REPO_SLUG HEAD:$TRAVIS_BRANCH
\ No newline at end of file
+  - git push --follow-tags https://$GITHUB_TOKEN@github.com/$TRAVIS_REPO_SLUG HEAD:$TRAVIS_BRANCH
+  - yarn test:coverage:report
\ No newline at end of file
diff --git a/__mocks__/ws/__fixtures__/tags.json b/__mocks__/ws/__fixtures__/tags.json
index b18bbd4..1d1bb5f 100644
--- a/__mocks__/ws/__fixtures__/tags.json
+++ b/__mocks__/ws/__fixtures__/tags.json
@@ -18,7 +18,8 @@
     "SUBSCRIPTION_GIFT": "@badges=staff/1,premium/1;color=#0000FF;display-name=TWW2;emotes=;id=e9176cd8-5e22-4684-ad40-ce53c2561c5e;login=tww2;mod=0;msg-id=subgift;msg-param-months=1;msg-param-recipient-display-name=Mr_Woodchuck;msg-param-recipient-id=89614178;msg-param-recipient-name=mr_woodchuck;msg-param-sub-plan-name=House\\sof\\sNyoro~n;msg-param-sub-plan=1000;room-id=19571752;subscriber=0;system-msg=TWW2\\sgifted\\sa\\sTier\\s1\\ssub\\sto\\sMr_Woodchuck!;tmi-sent-ts=1521159445153;turbo=0;user-id=13405587;user-type=staff :tmi.twitch.tv USERNOTICE #forstycup",
     "RAID": "@badges=turbo/1;color=#9ACD32;display-name=TestChannel;emotes=;id=3d830f12-795c-447d-af3c-ea05e40fbddb;login=testchannel;mod=0;msg-id=raid;msg-param-displayName=TestChannel;msg-param-login=testchannel;msg-param-viewerCount=15;room-id=56379257;subscriber=0;system-msg=15\\sraiders\\sfrom\\sTestChannel\\shave\\sjoined\\n!;tmi-sent-ts=1507246572675;tmi-sent-ts=1507246572675;turbo=1;user-id=123456;user-type= :tmi.twitch.tv USERNOTICE #othertestchannel",
     "RITUAL": "@badges=;color=;display-name=SevenTest1;emotes=30259:0-6;id=37feed0f-b9c7-4c3a-b475-21c6c6d21c3d;login=seventest1;mod=0;msg-id=ritual;msg-param-ritual-name=new_chatter;room-id=6316121;subscriber=0;system-msg=Seventoes\\sis\\snew\\shere!;tmi-sent-ts=1508363903826;turbo=0;user-id=131260580;user-type= :tmi.twitch.tv USERNOTICE #seventoes :HeyGuys",
-    "UNKNOWN": "@badges=;color=;display-name=SevenTest1;emotes=30259:0-6;id=37feed0f-b9c7-4c3a-b475-21c6c6d21c3d;login=seventest1;mod=0;msg-id=unknown;room-id=6316121;subscriber=0;system-msg=Seventoes\\sis\\snew\\shere!;tmi-sent-ts=1508363903826;turbo=0;user-id=131260580;user-type= :tmi.twitch.tv USERNOTICE #seventoes :Unknown USERNOTICE"
+    "SUBMYSTERYGIFT": "@badges=subscriber/12,bits/5000,sub-gifter/1;color=;display-name=Nefarious_Ned;emotes=;id=761a4698-b867-4668-9658-c3844c7b8637;login=nefarious_ned;mod=0;msg-id=submysterygift;msg-param-mass-gift-count=5;msg-param-sender-count=37;msg-param-sub-plan=1000;room-id=73625408;subscriber=1;system-msg=Nefarious_Ned\\sis\\sgifting\\s5\\sTier\\s1\\sSubs\\sto\\sKate's\\scommunity!\\sThey've\\sgifted\\sa\\stotal\\sof\\s37\\sin\\sthe\\schannel!;tmi-sent-ts=1533875190783;turbo=0;user-id=109667930;user-type= :tmi.twitch.tv USERNOTICE #kate",
+    "UNKNOWN": "@badges=;color=;display-name=SevenTest1;emotes=30259:0-6;id=37feed0f-b9c7-4c3a-b475-21c6c6d21c3d;login=seventest1;mod=0;msg-id=unknown;msg-param-unknown=value;room-id=6316121;subscriber=0;system-msg=Unknown\\ssystem\\smessage;tmi-sent-ts=1508363903826;turbo=0;user-id=131260580;user-type= :tmi.twitch.tv USERNOTICE #seventoes :Unknown USERNOTICE"
   },
   "USERSTATE": {
     "JOIN": "@color=#0D4200;display-name=ronni;emote-sets=0,33,50,237,793,2126,3517,4578,5569,9400,10337,12239;mod=1;subscriber=1;turbo=1;user-type=staff :tmi.twitch.tv USERSTATE #dallas"
diff --git a/docs/reference/class.md b/docs/reference/class.md
index 08cc8b3..adcd57d 100644
--- a/docs/reference/class.md
+++ b/docs/reference/class.md
@@ -246,11 +246,12 @@ Chat client
     * ["NOTICE"](#Chat+event_NOTICE)
     * ["USERSTATE"](#Chat+event_USERSTATE)
     * ["PRIVMSG"](#Chat+event_PRIVMSG)
-    * ["USERNOTICE/SUBSCRIPTION"](#Chat+event_USERNOTICE/SUBSCRIPTION)
-    * ["USERNOTICE/RESUBSCRIPTION"](#Chat+event_USERNOTICE/RESUBSCRIPTION)
-    * ["USERNOTICE/SUBSCRIPTION_GIFT"](#Chat+event_USERNOTICE/SUBSCRIPTION_GIFT)
     * ["USERNOTICE/RAID"](#Chat+event_USERNOTICE/RAID)
+    * ["USERNOTICE/RESUBSCRIPTION"](#Chat+event_USERNOTICE/RESUBSCRIPTION)
     * ["USERNOTICE/RITUAL"](#Chat+event_USERNOTICE/RITUAL)
+    * ["USERNOTICE/SUBSCRIPTION_GIFT_COMMUNITY"](#Chat+event_USERNOTICE/SUBSCRIPTION_GIFT_COMMUNITY)
+    * ["USERNOTICE/SUBSCRIPTION_GIFT"](#Chat+event_USERNOTICE/SUBSCRIPTION_GIFT)
+    * ["USERNOTICE/SUBSCRIPTION"](#Chat+event_USERNOTICE/SUBSCRIPTION)
 
 
 * * *
@@ -817,8 +818,6 @@ When a user joins a channel or sends a PRIVMSG to a channel.
 <tr>
     <td>[event]</td><td><code>&#x27;CHEER&#x27;</code></td>
     </tr><tr>
-    <td>[event]</td><td><code>string</code></td>
-    </tr><tr>
     <td>[bits]</td><td><code>number</code></td>
     </tr>  </tbody>
 </table>
@@ -826,10 +825,10 @@ When a user joins a channel or sends a PRIVMSG to a channel.
 
 * * *
 
-<a name="Chat+event_USERNOTICE/SUBSCRIPTION"></a>
+<a name="Chat+event_USERNOTICE/RAID"></a>
 
-### "USERNOTICE/SUBSCRIPTION"
-On subscription (first month) to a channel.
+### "USERNOTICE/RAID"
+On channel raid.
 
 **Kind**: event emitted by [<code>Chat</code>](class#Chat)  
 **Mixes**: [<code>UserStateMessage</code>](mixin#UserStateMessage)  
@@ -843,15 +842,15 @@ On subscription (first month) to a channel.
   </thead>
   <tbody>
 <tr>
-    <td>event</td><td><code>&#x27;SUBSCRIPTION&#x27;</code></td>
+    <td>event</td><td><code>&#x27;RAID&#x27;</code></td>
     </tr><tr>
-    <td>systemMessage</td><td><code>string</code></td>
+    <td>parameters</td><td><code>Object</code></td>
     </tr><tr>
-    <td>months</td><td><code>string</code></td>
+    <td>parameters.displayName</td><td><code>string</code></td>
     </tr><tr>
-    <td>subPlan</td><td><code>string</code></td>
+    <td>parameters.login</td><td><code>string</code></td>
     </tr><tr>
-    <td>subPlanName</td><td><code>string</code></td>
+    <td>parameters.viewerCount</td><td><code>number</code></td>
     </tr>  </tbody>
 </table>
 
@@ -864,7 +863,7 @@ On subscription (first month) to a channel.
 On resubscription (subsequent months) to a channel.
 
 **Kind**: event emitted by [<code>Chat</code>](class#Chat)  
-**Mixes**: <code>UserNoticeSubscriptionMessage</code>  
+**Mixes**: [<code>UserStateMessage</code>](mixin#UserStateMessage)  
 **Properties**
 
 <table>
@@ -876,16 +875,24 @@ On resubscription (subsequent months) to a channel.
   <tbody>
 <tr>
     <td>event</td><td><code>&#x27;RESUBSCRIPTION&#x27;</code></td>
+    </tr><tr>
+    <td>parameters</td><td><code>Object</code></td>
+    </tr><tr>
+    <td>parameters.months</td><td><code>number</code></td>
+    </tr><tr>
+    <td>parameters.subPlan</td><td><code>string</code></td>
+    </tr><tr>
+    <td>parameters.subPlanName</td><td><code>string</code></td>
     </tr>  </tbody>
 </table>
 
 
 * * *
 
-<a name="Chat+event_USERNOTICE/SUBSCRIPTION_GIFT"></a>
+<a name="Chat+event_USERNOTICE/RITUAL"></a>
 
-### "USERNOTICE/SUBSCRIPTION_GIFT"
-On subscription gift to a channel.
+### "USERNOTICE/RITUAL"
+On channel ritual.
 
 **Kind**: event emitted by [<code>Chat</code>](class#Chat)  
 **Mixes**: [<code>UserStateMessage</code>](mixin#UserStateMessage)  
@@ -899,25 +906,53 @@ On subscription gift to a channel.
   </thead>
   <tbody>
 <tr>
-    <td>event</td><td><code>&#x27;SUBSCRIPTION_GIFT&#x27;</code></td>
+    <td>event</td><td><code>&#x27;RITUAL&#x27;</code></td>
     </tr><tr>
-    <td>systemMessage</td><td><code>string</code></td>
+    <td>parameters</td><td><code>Object</code></td>
     </tr><tr>
-    <td>recipientDisplayName</td><td><code>string</code></td>
+    <td>parameters.ritualName</td><td><code>string</code></td>
+    </tr>  </tbody>
+</table>
+
+
+* * *
+
+<a name="Chat+event_USERNOTICE/SUBSCRIPTION_GIFT_COMMUNITY"></a>
+
+### "USERNOTICE/SUBSCRIPTION_GIFT_COMMUNITY"
+On subscription gift to a channel community.
+
+**Kind**: event emitted by [<code>Chat</code>](class#Chat)  
+**Mixes**: [<code>UserStateMessage</code>](mixin#UserStateMessage)  
+**Properties**
+
+<table>
+  <thead>
+    <tr>
+      <th>Name</th><th>Type</th>
+    </tr>
+  </thead>
+  <tbody>
+<tr>
+    <td>event</td><td><code>&#x27;SUBSCRIPTION_GIFT_COMMUNITY&#x27;</code></td>
+    </tr><tr>
+    <td>parameters</td><td><code>Object</code></td>
+    </tr><tr>
+    <td>parameters.massGiftCount</td><td><code>number</code></td>
     </tr><tr>
-    <td>recipientId</td><td><code>string</code></td>
+    <td>parameters.senderCount</td><td><code>number</code></td>
     </tr><tr>
-    <td>recipientUserName</td><td><code>string</code></td>
+    <td>parameters.subPlan</td><td><code>string</code></td>
     </tr>  </tbody>
 </table>
 
 
 * * *
 
-<a name="Chat+event_USERNOTICE/RAID"></a>
+<a name="Chat+event_USERNOTICE/SUBSCRIPTION_GIFT"></a>
 
-### "USERNOTICE/RAID"
-On channel raid.
+### "USERNOTICE/SUBSCRIPTION_GIFT"
+On subscription gift to a channel.
 
 **Kind**: event emitted by [<code>Chat</code>](class#Chat)  
 **Mixes**: [<code>UserStateMessage</code>](mixin#UserStateMessage)  
@@ -931,25 +966,31 @@ On channel raid.
   </thead>
   <tbody>
 <tr>
-    <td>event</td><td><code>&#x27;RAID&#x27;</code></td>
+    <td>event</td><td><code>&#x27;SUBSCRIPTION_GIFT&#x27;</code></td>
+    </tr><tr>
+    <td>parameters</td><td><code>Object</code></td>
     </tr><tr>
-    <td>systemMessage</td><td><code>string</code></td>
+    <td>parameters.months</td><td><code>number</code></td>
     </tr><tr>
-    <td>raiderDisplayName</td><td><code>string</code></td>
+    <td>parameters.subPlan</td><td><code>string</code></td>
     </tr><tr>
-    <td>raiderUserName</td><td><code>string</code></td>
+    <td>parameters.subPlanName</td><td><code>string</code></td>
     </tr><tr>
-    <td>raiderViewerCount</td><td><code>string</code></td>
+    <td>parameters.recipientDisplayName</td><td><code>string</code></td>
+    </tr><tr>
+    <td>parameters.recipientId</td><td><code>string</code></td>
+    </tr><tr>
+    <td>parameters.recipientName</td><td><code>string</code></td>
     </tr>  </tbody>
 </table>
 
 
 * * *
 
-<a name="Chat+event_USERNOTICE/RITUAL"></a>
+<a name="Chat+event_USERNOTICE/SUBSCRIPTION"></a>
 
-### "USERNOTICE/RITUAL"
-On channel ritual.
+### "USERNOTICE/SUBSCRIPTION"
+On subscription (first month) to a channel.
 
 **Kind**: event emitted by [<code>Chat</code>](class#Chat)  
 **Mixes**: [<code>UserStateMessage</code>](mixin#UserStateMessage)  
@@ -963,11 +1004,15 @@ On channel ritual.
   </thead>
   <tbody>
 <tr>
-    <td>event</td><td><code>&#x27;RITUAL&#x27;</code></td>
+    <td>event</td><td><code>&#x27;SUBSCRIPTION&#x27;</code></td>
+    </tr><tr>
+    <td>parameters</td><td><code>Object</code></td>
+    </tr><tr>
+    <td>parameters.months</td><td><code>1</code></td>
     </tr><tr>
-    <td>systemMessage</td><td><code>string</code></td>
+    <td>parameters.subPlan</td><td><code>string</code></td>
     </tr><tr>
-    <td>ritualName</td><td><code>string</code></td>
+    <td>parameters.subPlanName</td><td><code>string</code></td>
     </tr>  </tbody>
 </table>
 
diff --git a/docs/reference/mixin.md b/docs/reference/mixin.md
index a1e5b92..40646c1 100644
--- a/docs/reference/mixin.md
+++ b/docs/reference/mixin.md
@@ -18,6 +18,9 @@ sidebar_label: Mixins
 </dd>
   <dt><a href="#UserStateMessage">UserStateMessage</a></dt>
   <dd><p>USERSTATE message</p>
+</dd>
+  <dt><a href="#UserNoticeMessage">UserNoticeMessage</a></dt>
+  <dd><p>USERNOTICE message</p>
 </dd>
   </dl>
 
@@ -137,3 +140,31 @@ USERSTATE message
 
 * * *
 
+<a name="UserNoticeMessage"></a>
+
+## UserNoticeMessage
+USERNOTICE message
+
+**Kind**: global mixin  
+**Mixes**: [<code>BaseMessage</code>](mixin#BaseMessage)  
+**Properties**
+
+<table>
+  <thead>
+    <tr>
+      <th>Name</th><th>Type</th>
+    </tr>
+  </thead>
+  <tbody>
+<tr>
+    <td>event</td><td><code>string</code></td>
+    </tr><tr>
+    <td>parameters</td><td><code>Object</code></td>
+    </tr><tr>
+    <td>systemMessage</td><td><code>string</code></td>
+    </tr>  </tbody>
+</table>
+
+
+* * *
+
diff --git a/docs/reference/typedef.md b/docs/reference/typedef.md
index a8137a7..d24170f 100644
--- a/docs/reference/typedef.md
+++ b/docs/reference/typedef.md
@@ -292,8 +292,6 @@ USERSTATE tags
 <tr>
     <td>badges</td><td><code><a href="typedef#BadgesTag">BadgesTag</a></code></td>
     </tr><tr>
-    <td>[bits]</td><td><code>number</code></td>
-    </tr><tr>
     <td>emotes</td><td><code>Object.&lt;number, EmoteTag&gt;</code></td>
     </tr><tr>
     <td>emoteSets</td><td><code>Array.&lt;string&gt;</code></td>
@@ -302,7 +300,11 @@ USERSTATE tags
     </tr><tr>
     <td>isSubscriber</td><td><code>boolean</code></td>
     </tr><tr>
+    <td>isSubGifter</td><td><code>boolean</code></td>
+    </tr><tr>
     <td>isTurboSubscriber</td><td><code>boolean</code></td>
+    </tr><tr>
+    <td>[bits]</td><td><code>number</code></td>
     </tr>  </tbody>
 </table>
 
diff --git a/package.json b/package.json
index 754aa13..aa79860 100644
--- a/package.json
+++ b/package.json
@@ -55,8 +55,11 @@
     "build:dist:dev": "webpack",
     "build:dist:prod": "webpack --env.production",
     "build": "run-p build:**",
-    "jsdoc2md:watch": "nodemon --exec npm run jsdoc2md",
-    "jsdoc2md": "node scripts/jsdoc2md/generateDocs.js"
+    "docs:build:watch": "nodemon --exec npm run docs:build",
+    "docs:build": "node scripts/jsdoc2md/generateDocs.js",
+    "website": "cd website && yarn start",
+    "website:build": "cd website && yarn build",
+    "webiste:publish": "cd website && yarn publish-gh-pages"
   },
   "dependencies": {
     "babel-runtime": "^6.26.0",
diff --git a/src/Chat/constants.js b/src/Chat/constants.js
index 039b887..54b0cf0 100644
--- a/src/Chat/constants.js
+++ b/src/Chat/constants.js
@@ -36,6 +36,11 @@ export const ERROR_COMMAND_TIMED_OUT = 'ERROR: command timed out'
 export const ERROR_COMMAND_UNRECOGNIZED = 'ERROR: command unrecognized'
 export const ERROR_PART_TIMED_OUT = 'ERROR: part timed out'
 
+export const MESSAGE_PARAMETER_PREFIX = 'msgParam'
+export const MESSAGE_PARAMETER_PREFIX_RE = new RegExp(
+  `^${MESSAGE_PARAMETER_PREFIX}(\\w+)`,
+)
+
 /** @typedef {string} ClientReadyState */
 /**
  * Chat client ready state
@@ -137,8 +142,9 @@ export const USER_NOTICE_MESSAGE_IDS = {
   RAID: 'raid',
   RESUBSCRIPTION: 'resub',
   RITUAL: 'ritual',
-  SUBSCRIPTION_GIFT: 'subgift',
   SUBSCRIPTION: 'sub',
+  SUBSCRIPTION_GIFT: 'subgift',
+  SUBSCRIPTION_GIFT_COMMUNITY: 'submysterygift',
 }
 
 export const MESSAGE_IDS = {
@@ -167,8 +173,9 @@ export const EVENTS = {
   RAID: 'RAID',
   RESUBSCRIPTION: 'RESUBSCRIPTION',
   RITUAL: 'RITUAL',
-  SUBSCRIPTION_GIFT: 'SUBSCRIPTION_GIFT',
   SUBSCRIPTION: 'SUBSCRIPTION',
+  SUBSCRIPTION_GIFT: 'SUBSCRIPTION_GIFT',
+  SUBSCRIPTION_GIFT_COMMUNITY: 'SUBSCRIPTION_GIFT_COMMUNITY',
 
   ROOM_MODS: 'ROOM_MODS',
   MOD_GAINED: 'MOD_GAINED',
diff --git a/src/Chat/utils/parsers/index.js b/src/Chat/utils/parsers/index.js
index 6fb30b2..a441c22 100644
--- a/src/Chat/utils/parsers/index.js
+++ b/src/Chat/utils/parsers/index.js
@@ -328,110 +328,106 @@ const userStateMessage = baseMessage => {
  * @event Chat#PRIVMSG
  * @mixes UserStateMessage PrivateMessage
  * @property {'CHEER'} [event]
- * @property {string} [event]
  * @property {number} [bits]
  */
 const privateMessage = userStateMessage
 
+/**
+ * USERNOTICE message
+ * @mixin UserNoticeMessage
+ * @mixes BaseMessage
+ * @property {string} event
+ * @property {Object} parameters
+ * @property {string} systemMessage
+ */
 const userNoticeMessage = baseMessage => {
   const tags = tagParsers.userNotice(baseMessage.tags)
 
+  /* eslint-disable no-fallthrough */
   switch (tags.msgId) {
-    case constants.USER_NOTICE_MESSAGE_IDS.SUBSCRIPTION:
-      /**
-       * On subscription (first month) to a channel.
-       * @event Chat#USERNOTICE/SUBSCRIPTION
-       * @mixes UserStateMessage UserNoticeSubscriptionMessage
-       * @property {'SUBSCRIPTION'} event
-       * @property {string} systemMessage
-       * @property {string} months
-       * @property {string} subPlan
-       * @property {string} subPlanName
-       */
-      return {
-        ...baseMessage,
-        tags,
-        event: constants.EVENTS.SUBSCRIPTION,
-        systemMessage: typeParsers.generalString(tags.systemMsg),
-        months: tags.msgParamMonths,
-        subPlan: tags.msgParamSubPlan,
-        subPlanName: typeParsers.generalString(tags.msgParamSubPlanName),
-      }
-    case constants.USER_NOTICE_MESSAGE_IDS.RESUBSCRIPTION:
-      /**
-       * On resubscription (subsequent months) to a channel.
-       * @event Chat#USERNOTICE/RESUBSCRIPTION
-       * @mixes UserNoticeSubscriptionMessage UserNoticeResubscriptionMessage
-       * @property {'RESUBSCRIPTION'} event
-       */
-      return {
-        ...baseMessage,
-        tags,
-        event: constants.EVENTS.RESUBSCRIPTION,
-        systemMessage: typeParsers.generalString(tags.systemMsg),
-        months: tags.msgParamMonths,
-        subPlan: tags.msgParamSubPlan,
-        subPlanName: typeParsers.generalString(tags.msgParamSubPlanName),
-      }
-    case constants.USER_NOTICE_MESSAGE_IDS.SUBSCRIPTION_GIFT:
-      /**
-       * On subscription gift to a channel.
-       * @event Chat#USERNOTICE/SUBSCRIPTION_GIFT
-       * @mixes UserStateMessage UserNoticeSubscriptionGiftMessage
-       * @property {'SUBSCRIPTION_GIFT'} event
-       * @property {string} systemMessage
-       * @property {string} recipientDisplayName
-       * @property {string} recipientId
-       * @property {string} recipientUserName
-       */
-      return {
-        ...baseMessage,
-        tags,
-        event: constants.EVENTS.SUBSCRIPTION_GIFT,
-        systemMessage: typeParsers.generalString(tags.systemMsg),
-        recipientDisplayName: tags.msgParamRecipientDisplayName,
-        recipientId: tags.msgParamRecipientId,
-        recipientUserName: tags.msgParamRecipientName,
-      }
+    /**
+     * On channel raid.
+     * @event Chat#USERNOTICE/RAID
+     * @mixes UserStateMessage
+     * @property {'RAID'} event
+     * @property {Object} parameters
+     * @property {string} parameters.displayName
+     * @property {string} parameters.login
+     * @property {number} parameters.viewerCount
+     */
     case constants.USER_NOTICE_MESSAGE_IDS.RAID:
-      /**
-       * On channel raid.
-       * @event Chat#USERNOTICE/RAID
-       * @mixes UserStateMessage UserNoticeRaidMessage
-       * @property {'RAID'} event
-       * @property {string} systemMessage
-       * @property {string} raiderDisplayName
-       * @property {string} raiderUserName
-       * @property {string} raiderViewerCount
-       */
-      return {
-        ...baseMessage,
-        tags,
-        event: constants.EVENTS.RAID,
-        systemMessage: typeParsers.generalString(tags.systemMsg),
-        raiderDisplayName: tags.msgParamDisplayName,
-        raiderUserName: tags.msgParamLogin,
-        raiderViewerCount: tags.msgParamViewerCount,
-      }
+
+    /**
+     * On resubscription (subsequent months) to a channel.
+     * @event Chat#USERNOTICE/RESUBSCRIPTION
+     * @mixes UserStateMessage
+     * @property {'RESUBSCRIPTION'} event
+     * @property {Object} parameters
+     * @property {number} parameters.months
+     * @property {string} parameters.subPlan
+     * @property {string} parameters.subPlanName
+     */
+    case constants.USER_NOTICE_MESSAGE_IDS.RESUBSCRIPTION:
+
+    /**
+     * On channel ritual.
+     * @event Chat#USERNOTICE/RITUAL
+     * @mixes UserStateMessage
+     * @property {'RITUAL'} event
+     * @property {Object} parameters
+     * @property {string} parameters.ritualName
+     */
     case constants.USER_NOTICE_MESSAGE_IDS.RITUAL:
-      /**
-       * On channel ritual.
-       * @event Chat#USERNOTICE/RITUAL
-       * @mixes UserStateMessage UserNoticeRitualMessage
-       * @property {'RITUAL'} event
-       * @property {string} systemMessage
-       * @property {string} ritualName
-       */
+
+    /**
+     * On subscription gift to a channel community.
+     * @event Chat#USERNOTICE/SUBSCRIPTION_GIFT_COMMUNITY
+     * @mixes UserStateMessage
+     * @property {'SUBSCRIPTION_GIFT_COMMUNITY'} event
+     * @property {Object} parameters
+     * @property {number} parameters.massGiftCount
+     * @property {number} parameters.senderCount
+     * @property {string} parameters.subPlan
+     */
+    case constants.USER_NOTICE_MESSAGE_IDS.SUBSCRIPTION_GIFT_COMMUNITY:
+
+    /**
+     * On subscription gift to a channel.
+     * @event Chat#USERNOTICE/SUBSCRIPTION_GIFT
+     * @mixes UserStateMessage
+     * @property {'SUBSCRIPTION_GIFT'} event
+     * @property {Object} parameters
+     * @property {number} parameters.months
+     * @property {string} parameters.subPlan
+     * @property {string} parameters.subPlanName
+     * @property {string} parameters.recipientDisplayName
+     * @property {string} parameters.recipientId
+     * @property {string} parameters.recipientName
+     */
+    case constants.USER_NOTICE_MESSAGE_IDS.SUBSCRIPTION_GIFT:
+
+    /**
+     * On subscription (first month) to a channel.
+     * @event Chat#USERNOTICE/SUBSCRIPTION
+     * @mixes UserStateMessage
+     * @property {'SUBSCRIPTION'} event
+     * @property {Object} parameters
+     * @property {1} parameters.months
+     * @property {string} parameters.subPlan
+     * @property {string} parameters.subPlanName
+     */
+    case constants.USER_NOTICE_MESSAGE_IDS.SUBSCRIPTION:
+
+    default:
       return {
         ...baseMessage,
-        tags,
-        event: constants.EVENTS.RITUAL,
+        tags: { ...tags, systemMsg: typeParsers.generalString(tags.systemMsg) },
+        parameters: tagParsers.userNoticeMessageParameters(tags),
+        event: tagParsers.userNoticeEvent(tags),
         systemMessage: typeParsers.generalString(tags.systemMsg),
-        ritualName: tags.msgParamRitualName,
       }
-    default:
-      return { ...baseMessage, tags }
   }
+  /* eslint-enable no-fallthrough */
 }
 
 export {
diff --git a/src/Chat/utils/parsers/tags.js b/src/Chat/utils/parsers/tags.js
index 5226376..7829296 100644
--- a/src/Chat/utils/parsers/tags.js
+++ b/src/Chat/utils/parsers/tags.js
@@ -1,5 +1,6 @@
-import { toLower } from 'lodash'
+import { camelCase, toLower, toUpper } from 'lodash'
 
+import * as constants from '../../constants'
 import * as types from './types'
 
 /**
@@ -65,16 +66,56 @@ const roomState = tags => ({
  */
 const userNotice = (...args) => userState(...args)
 
+const userNoticeMessageParameters = tags =>
+  Object.entries(tags).reduce((parameters, [tag, value]) => {
+    const [, param] = constants.MESSAGE_PARAMETER_PREFIX_RE.exec(tag) || []
+
+    switch (param) {
+      // Numbers.
+      case 'Months':
+      case 'MassGiftCount':
+      case 'SenderCount':
+      case 'ViewerCount':
+        return { ...parameters, [camelCase(param)]: types.generalNumber(value) }
+      // Not a msgParam.
+      case undefined:
+        return parameters
+      // Strings
+      default:
+        return { ...parameters, [camelCase(param)]: types.generalString(value) }
+    }
+  }, {})
+
+const userNoticeEvent = tags => {
+  switch (tags.msgId) {
+    case constants.USER_NOTICE_MESSAGE_IDS.RESUBSCRIPTION:
+      return constants.EVENTS.RESUBSCRIPTION
+    case constants.USER_NOTICE_MESSAGE_IDS.RAID:
+      return constants.EVENTS.RAID
+    case constants.USER_NOTICE_MESSAGE_IDS.RITUAL:
+      return constants.EVENTS.RITUAL
+    case constants.USER_NOTICE_MESSAGE_IDS.SUBSCRIPTION:
+      return constants.EVENTS.SUBSCRIPTION
+    case constants.USER_NOTICE_MESSAGE_IDS.SUBSCRIPTION_GIFT:
+      return constants.EVENTS.SUBSCRIPTION_GIFT
+    case constants.USER_NOTICE_MESSAGE_IDS.SUBSCRIPTION_GIFT_COMMUNITY:
+      return constants.EVENTS.SUBSCRIPTION_GIFT_COMMUNITY
+    default:
+      return toUpper(tags.msgId)
+  }
+}
+
 /**
  * USERSTATE tags
  * @typedef {Object} UserStateTags
  * @property {BadgesTag} badges
- * @property {number} [bits]
  * @property {Object<number, EmoteTag>} emotes
  * @property {Array<string>} emoteSets
  * @property {boolean} isModerator
  * @property {boolean} isSubscriber
+ * @property {boolean} isSubGifter
  * @property {boolean} isTurboSubscriber
+ * @property {number} [bits]
  * @see https://dev.twitch.tv/docs/irc/tags#userstate-twitch-tags
  */
 const userState = tags => ({
@@ -86,6 +127,7 @@ const userState = tags => ({
   userType: types.userType(tags.userType),
   isModerator: types.generalBoolean(tags.mod),
   isSubscriber: types.generalBoolean(tags.subscriber),
+  isSubGifter: /sub-gifter\/\d/.test(tags.badges),
   isTurboSubscriber: types.generalBoolean(tags.turbo),
 })
 
@@ -95,5 +137,7 @@ export {
   privateMessage,
   roomState,
   userNotice,
+  userNoticeMessageParameters,
+  userNoticeEvent,
   userState,
 }
diff --git a/src/Chat/utils/parsers/types.js b/src/Chat/utils/parsers/types.js
index b01fbd5..83cc8a0 100644
--- a/src/Chat/utils/parsers/types.js
+++ b/src/Chat/utils/parsers/types.js
@@ -2,7 +2,7 @@ import { gt, isFinite, replace } from 'lodash'
 
 const generalString = maybeMessage => {
   return typeof maybeMessage === 'string'
-    ? replace(maybeMessage, /\\s/g, ' ')
+    ? replace(maybeMessage, /\\[sn]/g, ' ')
     : undefined
 }
 
diff --git a/website/pages/en/index.js b/website/pages/en/index.js
index d6fdce2..fabf9cb 100755
--- a/website/pages/en/index.js
+++ b/website/pages/en/index.js
@@ -103,7 +103,7 @@ const FeatureCallout = props => (
         Aligns with official{' '}
         <a href="https://dev.twitch.tv/docs/irc/">Twitch IRC documentation</a>
       </li>
-      <li>Forward-compatible, minimally-assertive architecure </li>
+      <li>Forward-compatible, minimally-assertive architecture </li>
       <li>Supports Node environments </li>
       <li>Supports Browsers </li>
       <li>Connect to multiple channels </li>


Test patch:
diff --git a/src/Chat/__tests__/__snapshots__/index.test.js.snap b/src/Chat/__tests__/__snapshots__/index.test.js.snap
index 7b9522e..d6c765b 100644
--- a/src/Chat/__tests__/__snapshots__/index.test.js.snap
+++ b/src/Chat/__tests__/__snapshots__/index.test.js.snap
@@ -156,6 +156,7 @@ Array [
         ],
         "emotes": Object {},
         "isModerator": true,
+        "isSubGifter": false,
         "isSubscriber": true,
         "isTurboSubscriber": true,
         "mod": "1",
@@ -789,6 +790,7 @@ Object {
     "emotes": Object {},
     "id": "b34ccfc7-4977-403a-8a94-33c6bac34fb8",
     "isModerator": false,
+    "isSubGifter": false,
     "isSubscriber": false,
     "isTurboSubscriber": true,
     "mod": "0",
@@ -831,6 +833,7 @@ Object {
     },
     "id": "b34ccfc7-4977-403a-8a94-33c6bac34fb8",
     "isModerator": false,
+    "isSubGifter": false,
     "isSubscriber": false,
     "isTurboSubscriber": true,
     "mod": "0",
@@ -853,10 +856,12 @@ Object {
   "event": "RAID",
   "isSelf": false,
   "message": undefined,
-  "raiderDisplayName": "TestChannel",
-  "raiderUserName": "testchannel",
-  "raiderViewerCount": "15",
-  "systemMessage": "15 raiders from TestChannel have joined\\\\n!",
+  "parameters": Object {
+    "displayName": "TestChannel",
+    "login": "testchannel",
+    "viewerCount": 15,
+  },
+  "systemMessage": "15 raiders from TestChannel have joined !",
   "tags": Object {
     "badges": Object {
       "turbo": 1,
@@ -868,6 +873,7 @@ Object {
     "emotes": Object {},
     "id": "3d830f12-795c-447d-af3c-ea05e40fbddb",
     "isModerator": false,
+    "isSubGifter": false,
     "isSubscriber": false,
     "isTurboSubscriber": true,
     "login": "testchannel",
@@ -878,7 +884,7 @@ Object {
     "msgParamViewerCount": "15",
     "roomId": "56379257",
     "subscriber": "0",
-    "systemMsg": "15\\\\sraiders\\\\sfrom\\\\sTestChannel\\\\shave\\\\sjoined\\\\n!",
+    "systemMsg": "15 raiders from TestChannel have joined !",
     "tmiSentTs": "1507246572675",
     "turbo": "1",
     "userId": "123456",
@@ -896,9 +902,11 @@ Object {
   "event": "RESUBSCRIPTION",
   "isSelf": false,
   "message": "Great stream -- keep it up!",
-  "months": "6",
-  "subPlan": "Prime",
-  "subPlanName": "Prime",
+  "parameters": Object {
+    "months": 6,
+    "subPlan": "Prime",
+    "subPlanName": "Prime",
+  },
   "systemMessage": "ronni has subscribed for 6 months!",
   "tags": Object {
     "badges": Object {
@@ -913,6 +921,7 @@ Object {
     "emotes": Object {},
     "id": "db25007f-7a18-43eb-9379-80131e44d633",
     "isModerator": false,
+    "isSubGifter": false,
     "isSubscriber": true,
     "isTurboSubscriber": true,
     "login": "ronni",
@@ -923,7 +932,7 @@ Object {
     "msgParamSubPlanName": "Prime",
     "roomId": "1337",
     "subscriber": "1",
-    "systemMsg": "ronni\\\\shas\\\\ssubscribed\\\\sfor\\\\s6\\\\smonths!",
+    "systemMsg": "ronni has subscribed for 6 months!",
     "tmiSentTs": "1507246572675",
     "turbo": "1",
     "userId": "1337",
@@ -941,7 +950,9 @@ Object {
   "event": "RITUAL",
   "isSelf": false,
   "message": "HeyGuys",
-  "ritualName": "new_chatter",
+  "parameters": Object {
+    "ritualName": "new_chatter",
+  },
   "systemMessage": "Seventoes is new here!",
   "tags": Object {
     "badges": Object {},
@@ -957,6 +968,7 @@ Object {
     },
     "id": "37feed0f-b9c7-4c3a-b475-21c6c6d21c3d",
     "isModerator": false,
+    "isSubGifter": false,
     "isSubscriber": false,
     "isTurboSubscriber": false,
     "login": "seventest1",
@@ -965,7 +977,7 @@ Object {
     "msgParamRitualName": "new_chatter",
     "roomId": "6316121",
     "subscriber": "0",
-    "systemMsg": "Seventoes\\\\sis\\\\snew\\\\shere!",
+    "systemMsg": "Seventoes is new here!",
     "tmiSentTs": "1508363903826",
     "turbo": "0",
     "userId": "131260580",
@@ -975,6 +987,54 @@ Object {
 }
 `;
 
+exports[`Chat should handle messages USERNOTICE SUBMYSTERYGIFT 1`] = `
+Object {
+  "_raw": "@badges=subscriber/12,bits/5000,sub-gifter/1;color=;display-name=Nefarious_Ned;emotes=;id=761a4698-b867-4668-9658-c3844c7b8637;login=nefarious_ned;mod=0;msg-id=submysterygift;msg-param-mass-gift-count=5;msg-param-sender-count=37;msg-param-sub-plan=1000;room-id=73625408;subscriber=1;system-msg=Nefarious_Ned\\\\sis\\\\sgifting\\\\s5\\\\sTier\\\\s1\\\\sSubs\\\\sto\\\\sKate's\\\\scommunity!\\\\sThey've\\\\sgifted\\\\sa\\\\stotal\\\\sof\\\\s37\\\\sin\\\\sthe\\\\schannel!;tmi-sent-ts=1533875190783;turbo=0;user-id=109667930;user-type= :tmi.twitch.tv USERNOTICE #kate",
+  "channel": "#kate",
+  "command": "USERNOTICE",
+  "event": "SUBSCRIPTION_GIFT_COMMUNITY",
+  "isSelf": false,
+  "message": undefined,
+  "parameters": Object {
+    "massGiftCount": 5,
+    "senderCount": 37,
+    "subPlan": "1000",
+  },
+  "systemMessage": "Nefarious_Ned is gifting 5 Tier 1 Subs to Kate's community! They've gifted a total of 37 in the channel!",
+  "tags": Object {
+    "badges": Object {
+      "bits": 5000,
+      "sub-gifter": 1,
+      "subscriber": 12,
+    },
+    "bits": undefined,
+    "color": true,
+    "displayName": "Nefarious_Ned",
+    "emoteSets": undefined,
+    "emotes": Object {},
+    "id": "761a4698-b867-4668-9658-c3844c7b8637",
+    "isModerator": false,
+    "isSubGifter": true,
+    "isSubscriber": true,
+    "isTurboSubscriber": false,
+    "login": "nefarious_ned",
+    "mod": "0",
+    "msgId": "submysterygift",
+    "msgParamMassGiftCount": "5",
+    "msgParamSenderCount": "37",
+    "msgParamSubPlan": "1000",
+    "roomId": "73625408",
+    "subscriber": "1",
+    "systemMsg": "Nefarious_Ned is gifting 5 Tier 1 Subs to Kate's community! They've gifted a total of 37 in the channel!",
+    "tmiSentTs": "1533875190783",
+    "turbo": "0",
+    "userId": "109667930",
+    "userType": undefined,
+  },
+  "timestamp": 2018-01-01T00:00:00.000Z,
+}
+`;
+
 exports[`Chat should handle messages USERNOTICE SUBSCRIPTION 1`] = `
 Object {
   "_raw": "@badges=staff/1,broadcaster/1,turbo/1;color=#008000;display-name=ronni;emotes=;id=db25007f-7a18-43eb-9379-80131e44d633;login=ronni;mod=0;msg-id=sub;msg-param-months=1;msg-param-sub-plan=Prime;msg-param-sub-plan-name=Prime;room-id=1337;subscriber=1;system-msg=ronni\\\\sjust\\\\ssubscribed\\\\swith\\\\sTwitch\\\\sPrime!;tmi-sent-ts=1507246572675;turbo=1;user-id=1337;user-type=staff :tmi.twitch.tv USERNOTICE #dallas :Great stream -- keep it up!",
@@ -983,9 +1043,11 @@ Object {
   "event": "SUBSCRIPTION",
   "isSelf": false,
   "message": "Great stream -- keep it up!",
-  "months": "1",
-  "subPlan": "Prime",
-  "subPlanName": "Prime",
+  "parameters": Object {
+    "months": 1,
+    "subPlan": "Prime",
+    "subPlanName": "Prime",
+  },
   "systemMessage": "ronni just subscribed with Twitch Prime!",
   "tags": Object {
     "badges": Object {
@@ -1000,6 +1062,7 @@ Object {
     "emotes": Object {},
     "id": "db25007f-7a18-43eb-9379-80131e44d633",
     "isModerator": false,
+    "isSubGifter": false,
     "isSubscriber": true,
     "isTurboSubscriber": true,
     "login": "ronni",
@@ -1010,7 +1073,7 @@ Object {
     "msgParamSubPlanName": "Prime",
     "roomId": "1337",
     "subscriber": "1",
-    "systemMsg": "ronni\\\\sjust\\\\ssubscribed\\\\swith\\\\sTwitch\\\\sPrime!",
+    "systemMsg": "ronni just subscribed with Twitch Prime!",
     "tmiSentTs": "1507246572675",
     "turbo": "1",
     "userId": "1337",
@@ -1028,9 +1091,14 @@ Object {
   "event": "SUBSCRIPTION_GIFT",
   "isSelf": false,
   "message": undefined,
-  "recipientDisplayName": "Mr_Woodchuck",
-  "recipientId": "89614178",
-  "recipientUserName": "mr_woodchuck",
+  "parameters": Object {
+    "months": 1,
+    "recipientDisplayName": "Mr_Woodchuck",
+    "recipientId": "89614178",
+    "recipientName": "mr_woodchuck",
+    "subPlan": "1000",
+    "subPlanName": "House of Nyoro~n",
+  },
   "systemMessage": "TWW2 gifted a Tier 1 sub to Mr_Woodchuck!",
   "tags": Object {
     "badges": Object {
@@ -1044,6 +1112,7 @@ Object {
     "emotes": Object {},
     "id": "e9176cd8-5e22-4684-ad40-ce53c2561c5e",
     "isModerator": false,
+    "isSubGifter": false,
     "isSubscriber": false,
     "isTurboSubscriber": false,
     "login": "tww2",
@@ -1057,7 +1126,7 @@ Object {
     "msgParamSubPlanName": "House\\\\sof\\\\sNyoro~n",
     "roomId": "19571752",
     "subscriber": "0",
-    "systemMsg": "TWW2\\\\sgifted\\\\sa\\\\sTier\\\\s1\\\\ssub\\\\sto\\\\sMr_Woodchuck!",
+    "systemMsg": "TWW2 gifted a Tier 1 sub to Mr_Woodchuck!",
     "tmiSentTs": "1521159445153",
     "turbo": "0",
     "userId": "13405587",
@@ -1069,11 +1138,16 @@ Object {
 
 exports[`Chat should handle messages USERNOTICE UNKNOWN 1`] = `
 Object {
-  "_raw": "@badges=;color=;display-name=SevenTest1;emotes=30259:0-6;id=37feed0f-b9c7-4c3a-b475-21c6c6d21c3d;login=seventest1;mod=0;msg-id=unknown;room-id=6316121;subscriber=0;system-msg=Seventoes\\\\sis\\\\snew\\\\shere!;tmi-sent-ts=1508363903826;turbo=0;user-id=131260580;user-type= :tmi.twitch.tv USERNOTICE #seventoes :Unknown USERNOTICE",
+  "_raw": "@badges=;color=;display-name=SevenTest1;emotes=30259:0-6;id=37feed0f-b9c7-4c3a-b475-21c6c6d21c3d;login=seventest1;mod=0;msg-id=unknown;msg-param-unknown=value;room-id=6316121;subscriber=0;system-msg=Unknown\\\\ssystem\\\\smessage;tmi-sent-ts=1508363903826;turbo=0;user-id=131260580;user-type= :tmi.twitch.tv USERNOTICE #seventoes :Unknown USERNOTICE",
   "channel": "#seventoes",
   "command": "USERNOTICE",
+  "event": "UNKNOWN",
   "isSelf": false,
   "message": "Unknown USERNOTICE",
+  "parameters": Object {
+    "unknown": "value",
+  },
+  "systemMessage": "Unknown system message",
   "tags": Object {
     "badges": Object {},
     "bits": undefined,
@@ -1088,14 +1162,16 @@ Object {
     },
     "id": "37feed0f-b9c7-4c3a-b475-21c6c6d21c3d",
     "isModerator": false,
+    "isSubGifter": false,
     "isSubscriber": false,
     "isTurboSubscriber": false,
     "login": "seventest1",
     "mod": "0",
     "msgId": "unknown",
+    "msgParamUnknown": "value",
     "roomId": "6316121",
     "subscriber": "0",
-    "systemMsg": "Seventoes\\\\sis\\\\snew\\\\shere!",
+    "systemMsg": "Unknown system message",
     "tmiSentTs": "1508363903826",
     "turbo": "0",
     "userId": "131260580",
@@ -1159,6 +1235,7 @@ Object {
     ],
     "emotes": Object {},
     "isModerator": true,
+    "isSubGifter": false,
     "isSubscriber": true,
     "isTurboSubscriber": true,
     "mod": "1",