Last active
May 2, 2022 10:52
-
-
Save AlcaDesign/14342b750623903906bf9f5c48189a57 to your computer and use it in GitHub Desktop.
An example for tracking mystery gift subs - https://github.com/tmijs/tmi.js/discussions/515
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
/** | |
* How long to wait in milliseconds if Twitch is taking too long to respond with | |
* all of the gifts. | |
*/ | |
const MYSTERYGIFT_TIMEOUT_MS = 10 * 1000; | |
/** | |
* How long to wait in milliseconds if the detected gift sub is not part of a | |
* larger mystery gift initially. | |
*/ | |
const GIFTSUB_ORPHAN_TIMEOUT_MS = 2 * 1000; | |
/** | |
* A list of all mystery gift groups. | |
* @type {Map<string, MysteryGift>} | |
*/ | |
const mysteryGiftEvents = new Map(); | |
/** | |
* A function that will be called when a mystery gift has finished getting all | |
* of the gifts. | |
* @param {MysteryGift} mysteryGift | |
*/ | |
function mysteryGiftFinished(mysteryGift) { | |
mysteryGiftEvents.delete(mysteryGift.id); | |
mysteryGift.clearTimeout(); | |
if(mysteryGift.registered) { | |
return; | |
} | |
mysteryGift.registered = true; | |
// Do something: | |
// addLife(points * mysteryGift.count, 'sub', mysteryGift.count); | |
} | |
class MysteryGift { | |
constructor(channel, tags) { | |
this.id = MysteryGift.getID(tags); | |
const channelLogin = channel.slice(1); | |
this.channel = { | |
id: tags['room-id'], | |
login: channelLogin, | |
displayName: MysteryGift.getChannelDisplayName(tags, channelLogin), | |
}; | |
this.gifter = { | |
id: tags['user-id'], | |
login: tags.login, | |
displayName: tags['display-name'] || tags.login, | |
}; | |
this.count = +(tags['msg-param-mass-gift-count'] ?? 1); | |
this.tier = +tags['msg-param-sub-plan'][0]; | |
} | |
static getID(tags) { | |
return `${tags['room-id']}:${tags['user-id']}`; | |
} | |
/** | |
* Helper function to parse the display name of a channel from a | |
* submysterygift system message. | |
* @param {object} tags | |
* @param {string} channelLogin | |
*/ | |
static getChannelDisplayName(tags, channelLogin = '') { | |
if(tags['msg-id'] !== 'submysterygift') { | |
return channelLogin; | |
} | |
const systemMsg = tags['system-msg']; | |
const channelDisplayNamePrefix = ' Subs to '; | |
const channelDisplayNamePrefixIndex = systemMsg.indexOf(channelDisplayNamePrefix); | |
return systemMsg.slice( | |
channelDisplayNamePrefixIndex + channelDisplayNamePrefix.length, | |
systemMsg.indexOf('\'s community', channelDisplayNamePrefixIndex) | |
); | |
} | |
/** | |
* Set a fallback timeout for the mystery gift. | |
* @returns {void} | |
*/ | |
setTimeout(timeoutMs = MYSTERYGIFT_TIMEOUT_MS) { | |
this.clearTimeout(); | |
this.timeout = setTimeout(() => mysteryGiftFinished(this), timeoutMs); | |
} | |
clearTimeout() { | |
clearTimeout(this.timeout); | |
this.timeout = null; | |
} | |
} | |
const client = new tmi.Client({ channels: [ 'some_channel' ] }); | |
client.connect(); | |
client.on('submysterygift', (channel, username, giftSubCount, methods, tags) => { | |
const mysteryGift = new MysteryGift(channel, tags); | |
if(mysteryGiftEvents.has(mysteryGift.id)) { | |
// Detected a mystery gift that is already being tracked as an orphan. | |
const orphan = mysteryGiftEvents.get(mysteryGift.id); | |
orphan.count = +tags['msg-param-mass-gift-count']; | |
return; | |
} | |
mysteryGift.setTimeout(); | |
mysteryGiftEvents.set(mysteryGift.id, mysteryGift); | |
}); | |
client.on('subgift', (channel, username, streakMonths, recipient, methods, tags) => { | |
const mysteryGiftID = MysteryGift.getID(tags); | |
const giftUser = { | |
id: tags['msg-param-recipient-id'], | |
login: tags['msg-param-recipient-user-name'], | |
displayName: recipient, | |
}; | |
// An existing mystery gift exists. | |
if(mysteryGiftEvents.has(mysteryGiftID)) { | |
const mysteryGift = mysteryGiftEvents.get(mysteryGiftID); | |
mysteryGift.gifts.push(giftUser); | |
// The mystery gift has collected all of the gifts sucessfully. | |
if(mysteryGift.gifts.length === mysteryGift.count) { | |
mysteryGiftFinished(mysteryGift); | |
} | |
// If the mystery gift is not yet finished, refresh the timeout. | |
else { | |
mysteryGift.setTimeout(); | |
} | |
} | |
// If the gift sub is not part of a mystery gift, add it as an orphan. | |
else { | |
const mysteryGift = new MysteryGift(channel, tags); | |
mysteryGift.gifts.push(giftUser); | |
// Set a shorter timeout for orphan gifts | |
mysteryGift.setTimeout(GIFTSUB_ORPHAN_TIMEOUT_MS); | |
} | |
}); |
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
interface TwitchUser { | |
/** The user's ID. */ | |
id: string; | |
/** The user's login. */ | |
login: string; | |
/** The user's display name. */ | |
displayName: string; | |
} | |
interface MysteryGift { | |
new(channel: string, tags: object): MysteryGift; | |
/** The unique-ish ID of the mystery gift. */ | |
id: string; | |
/** The channel the gift was sent to. */ | |
channel: TwitchUser; | |
/** The user who gifted the mystery gift. */ | |
gifter: TwitchUser; | |
/** The amount of gifts sent. */ | |
count: number; | |
/** The tier of the mystery gift. */ | |
tier: number; | |
/** The users who were gifted. */ | |
gifts: TwitchUser[]; | |
/** A fallback timeout to end the mystery gift. */ | |
timeout: ReturnType<typeof setTimeout>; | |
/** Has the mystery gift been tallied by the finished function */ | |
registered: boolean; | |
/** Generate the mystery gift's ID from the tags. */ | |
getID(tags: object): string; | |
/** | |
* Helper function to parse the display name of a channel from a | |
* submysterygift system message. | |
*/ | |
getChannelDisplayName(tags: object, channelLogin: string): string; | |
/** Set a fallback timeout */ | |
setTimeout(): void; | |
/** Clear the fallback timeout */ | |
clearTimeout(): void; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment