Created
February 10, 2019 08:11
-
-
Save AlcaDesign/da4a7e2b7f8dc28e51fa3dfd80c0d554 to your computer and use it in GitHub Desktop.
Custom USERNOTICE events, ahead of 1.4.0. (Examples included in `index.js`)
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
const tmiParse = require('tmi.js/lib/parser'); | |
const escapedIRCRegex = /\\([sn:r\\])/g; | |
const ircEscapedChars = { s: ' ', n: '', ':': ';', r: '' }; | |
const booleanTagKeys = [ 'mod', 'subscriber', 'msg-param-should-share-streak' ]; | |
const integerTagKeys = [ | |
'tmi-sent-ts', 'msg-param-months', 'msg-param-cumulative-months', | |
'msg-param-streak-months', 'msg-param-sender-count', | |
'msg-param-mass-gift-count', 'msg-param-viewerCount', 'msg-param-threshold', | |
'msg-param-bits-amount', 'msg-param-min-cheer-amount', | |
'msg-param-selected-count' | |
]; | |
const anonymousUserID = '274598607'; | |
const anonymousUserLogin = 'ananonymousgifter'; | |
function findIRCUnescapedChar(m, p) { | |
return p in ircEscapedChars ? ircEscapedChars[p] : p | |
} | |
function unescapeIRC(msg) { | |
if(!msg || !msg.includes('\\')) { | |
return msg; | |
} | |
return msg.replace(escapedIRCRegex, findIRCUnescapedChar); | |
} | |
function onRawMessage(str = '') { | |
const lines = str.split('\r\n'); | |
for(let line of lines) { | |
if(!line) { | |
return; | |
} | |
onRawMessageLine.call(this, line); | |
} | |
} | |
function onRawMessageLine(line) { | |
const data = tmiParse.msg(line); | |
const { tags } = data; | |
// const msgID = tags['msg-id']; | |
if( | |
data.command !== 'USERNOTICE' | |
// || [ 'sub', 'resub', 'ritual' ].includes(msgID) | |
) { | |
return; | |
} | |
tmiParse.emotes(tags); | |
tmiParse.badges(tags); | |
for(const key in tags) { | |
const val = tags[key]; | |
const type = typeof val; | |
if(type === 'boolean') { | |
tags[key] = null; | |
} | |
else if(type === 'string') { | |
if(integerTagKeys.includes(key)) { | |
tags[key] = parseInt(val); | |
} | |
else if(booleanTagKeys.includes(key)) { | |
tags[key] = val === '1'; | |
} | |
else { | |
tags[key] = unescapeIRC(val); | |
} | |
} | |
} | |
onUserNotice.call(this, data); | |
} | |
function onUserNotice(data) { | |
const { tags } = data; | |
const param = p => { | |
const val = data.tags[`msg-param-${p}`]; | |
return val !== undefined ? val : null; | |
}; | |
const channel = data.params[0].slice(1); | |
const userMessage = data.params[1] || null; | |
const systemMsg = tags['system-msg'] || null; | |
const userID = tags['user-id']; | |
const roomID = tags['room-id']; | |
const msgID = tags['msg-id']; | |
const login = tags['login']; | |
const displayName = tags['display-name']; | |
const massGiftCount = param('mass-gift-count'); | |
const senderTotalCount = param('sender-count'); | |
const subPlan = param('sub-plan'); | |
const subPlanName = param('sub-plan-name'); | |
const tier = { Prime: 1, 1000: 1, 2000: 2, 3000: 3 }[subPlan] || null; | |
// const months = param('months'); // Deprecated | |
const shouldShareStreak = param('should-share-streak'); | |
const streakMonths = param('streak-months'); | |
const cumulativeMonths = param('cumulative-months'); | |
const recipientDisplayName = param('recipient-display-name'); | |
const recipientID = param('recipient-id'); | |
const recipientUserName = param('user-name'); | |
const senderLogin = param('sender-login') || login || null; | |
const senderDisplayName = param('sender-name') || displayName || null; | |
const anonymous = userID === anonymousUserID; | |
// const originID = param('origin-id'); // Not too useful | |
const params = { msgID, systemMsg }; | |
switch(msgID) { | |
case 'sub': | |
Object.assign(params, { | |
shouldShareStreak, | |
cumulativeMonths, | |
sub: { | |
gift: false, | |
plan: subPlan, | |
planName: subPlanName, | |
tier | |
}, | |
sender: null, | |
recipient: { | |
login, | |
id: userID, | |
displayName | |
}, | |
channel: { | |
login: channel, | |
id: roomID, | |
displayName: null | |
} | |
}); | |
break; | |
case 'resub': | |
Object.assign(params, { | |
shouldShareStreak, | |
streakMonths, | |
cumulativeMonths, | |
sub: { | |
gift: false, | |
plan: subPlan, | |
planName: subPlanName, | |
tier | |
}, | |
sender: null, | |
recipient: { | |
login, | |
id: userID, | |
displayName | |
}, | |
channel: { | |
login: channel, | |
id: roomID, | |
displayName: null | |
} | |
}); | |
break; | |
case 'subgift': | |
Object.assign(params, { | |
totalCount: senderTotalCount, | |
sub: { | |
gift: true, | |
plan: subPlan, | |
planName: subPlanName, | |
tier | |
}, | |
// originID, | |
// months, | |
sender: { | |
login: senderLogin, | |
id: userID, | |
displayName: senderDisplayName, | |
anonymous | |
}, | |
recipient: { | |
login: recipientUserName, | |
id: recipientID, | |
displayName: recipientDisplayName | |
}, | |
channel: { | |
login: channel, | |
id: roomID, | |
displayName: null | |
} | |
}); | |
break; | |
case 'submysterygift': | |
Object.assign(params, { | |
giftCount: massGiftCount, | |
totalCount: senderTotalCount, | |
sub: { | |
gift: true, | |
plan: subPlan, | |
planName: null, | |
tier | |
}, | |
// originID | |
sender: { | |
login: senderLogin, | |
id: userID, | |
displayName: senderDisplayName, | |
anonymous | |
}, | |
recipient: null, | |
channel: { | |
login: channel, | |
id: roomID, | |
displayName: null | |
} | |
}); | |
break; | |
case 'giftpaidupgrade': | |
Object.assign(params, { | |
sub: { | |
gift: false, | |
plan: null, | |
planName: null, | |
tier: null | |
}, | |
sender: { | |
login: senderLogin, | |
id: null, | |
displayName: senderDisplayName, | |
anonymous: senderLogin === anonymousUserLogin | |
}, | |
recipient: { | |
login, | |
id: userID, | |
displayName | |
}, | |
channel: { | |
login: channel, | |
id: roomID, | |
displayName: null | |
} | |
}); | |
break; | |
case 'ritual': | |
const ritualName = param('ritual-name'); | |
params.ritualName = ritualName; | |
if(ritualName === 'new_chatter') { | |
const emoteKeys = Object.keys(tags.emotes || {}); | |
const emoteID = emoteKeys.length === 1 ? emoteKeys[0] : null; | |
const emoteIndex = emoteID ? | |
tags.emotes[emoteID][0].split('-').map((n, i) => +n + i) : | |
null; | |
const emote = userMessage && emoteIndex ? | |
userMessage.slice(...emoteIndex) : | |
null; | |
Object.assign(params, { | |
emoteID, | |
emoteIndex, | |
emote | |
}); | |
} | |
break; | |
case 'raid': | |
Object.assign(params, { | |
viewerCount: param('viewerCount'), | |
raider: { | |
login, | |
id: userID, | |
displayName, | |
profileImageURL: param('profileImageURL') | |
}, | |
channel: { | |
login: channel, | |
id: roomID, | |
displayName: null | |
} | |
}); | |
break; | |
case 'unraid': | |
Object.assign(params, { | |
viewerCount: null, | |
raider: { | |
login, | |
id: userID, | |
displayName, | |
profileImageURL: null | |
}, | |
// Probably same as raider, not 100% same to assume | |
channel: { | |
login: channel, | |
id: roomID, | |
displayName | |
} | |
}); | |
break; | |
case 'bitsbadgetier': | |
Object.assign(params, { | |
threshold: param('threshold'), | |
channel: { | |
login: channel, | |
id: roomID, | |
displayName: null | |
} | |
}); | |
break; | |
case 'rewardgift': | |
Object.assign(params, { | |
bitsAmount: param('bits-amount'), | |
minCheerAmount: param('min-cheer-amount'), | |
selectedCount: param('selected-count'), | |
domain: param('domain'), | |
sender: { | |
login, | |
id: userID, | |
displayName | |
}, | |
channel: { | |
login: channel, | |
id: roomID, | |
displayName: null | |
} | |
}); | |
} | |
const baseEmitType = 'custom-usernotice'; | |
const baseEmitValues = [ channel, params, userMessage, tags ]; | |
this.emits( | |
[ baseEmitType, `${baseEmitType}-${msgID}` ], | |
[ [ msgID, ...baseEmitValues ], baseEmitValues ] | |
); | |
} | |
module.exports = { | |
handleMissingUserNotices(client) { | |
if(!client.ws) { | |
throw new Error('Client is not connecting/connected. ' + | |
'Call client.connect() before this.'); | |
} | |
client.ws.on('message', onRawMessage.bind(client)); | |
} | |
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
const tmi = require('tmi.js'); | |
const client = tmi.client({ | |
options: { debug: true }, | |
channels: [ 'mychannel' ] | |
}); | |
client.connect(); | |
require('./custom-events').handleMissingUserNotices(client); | |
// All USERNOTICEs | |
client.on('custom-usernotice', (msgID, channel, params, userMessage, tags) => {}); | |
// Indiviual USERNOTICEs, including duplicates from tmi.js. | |
client.on('custom-usernotice-sub', (channel, params, userMessage, tags) => {}); | |
client.on('custom-usernotice-resub', (channel, params, userMessage, tags) => {}); | |
client.on('custom-usernotice-subgift', (channel, params, userMessage, tags) => {}); | |
client.on('custom-usernotice-submysterygift', (channel, params, userMessage, tags) => {}); | |
client.on('custom-usernotice-giftpaidupgrade', (channel, params, userMessage, tags) => {}); | |
client.on('custom-usernotice-ritual', (channel, params, userMessage, tags) => {}); | |
client.on('custom-usernotice-raid', (channel, params, userMessage, tags) => {}); | |
client.on('custom-usernotice-unraid', (channel, params, userMessage, tags) => {}); | |
client.on('custom-usernotice-bitsbadgetier', (channel, params, userMessage, tags) => {}); | |
client.on('custom-usernotice-rewardgift', (channel, params, userMessage, tags) => {}); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment