From 3ff98957137dec96591664fb974b3e5f78c24582 Mon Sep 17 00:00:00 2001 From: SirStendec Date: Thu, 12 Dec 2019 18:44:19 -0500 Subject: [PATCH] 4.17.5 * Added: Support for bits tier emote rewards. * Fixed: Stop casting Twitch emote IDs to numbers. Bits reward emotes use hexadecimal IDs prefixed with `emotesv2_`. --- package.json | 2 +- src/modules/chat/actions/index.jsx | 37 ------ src/modules/chat/emote_info.gql | 5 + src/modules/chat/emotes.js | 105 +++++++++++++----- src/modules/chat/tokenizers.jsx | 12 +- .../modules/chat/emote_menu.jsx | 69 ++++++++---- src/utilities/constants.js | 1 + 7 files changed, 139 insertions(+), 92 deletions(-) diff --git a/package.json b/package.json index ea89c162..aa996fbe 100755 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "frankerfacez", "author": "Dan Salvato LLC", - "version": "4.17.4", + "version": "4.17.5", "description": "FrankerFaceZ is a Twitch enhancement suite.", "license": "Apache-2.0", "scripts": { diff --git a/src/modules/chat/actions/index.jsx b/src/modules/chat/actions/index.jsx index b4a018e5..fd946c9f 100644 --- a/src/modules/chat/actions/index.jsx +++ b/src/modules/chat/actions/index.jsx @@ -166,43 +166,6 @@ export default class Actions extends Module { } }); - this.settings.add('chat.actions.viewer-card', { - // Filter out actions - process: (ctx, val) => - val.filter(x => x.type || (x.appearance && - this.renderers[x.appearance.type] && - (! this.renderers[x.appearance.type].load || this.renderers[x.appearance.type].load(x.appearance)) && - (! x.action || this.actions[x.action]) - )), - - default: [ - {v: {action: 'friend'}}, - {v: {action: 'whisper', appearance: {type: 'combo', icon: '', text: 'Whisper', button: true}}}, - {v: {type: 'space'}}, - {v: {action: 'card_menu'}}, - {v: {type: 'new-line'}}, - {v: {action: 'ban', appearance: {type: 'icon', icon: 'ffz-i-block'}, display: {mod: true}}}, - {v: {action: 'timeout', appearance: {type: 'icon', icon: 'ffz-i-clock'}, display: {mod: true}}} - ], - - type: 'array_merge', - _ui: { - path: 'Chat > Viewer Cards >> tabs ~> Actions @{"description": "Here, you define what actions are available on viewer cards."}', - component: 'chat-actions', - context: ['user', 'room', 'product'], - - data: () => { - const chat = this.resolve('site.chat'); - - return { - color: val => chat && chat.colors ? chat.colors.process(val) : val, - actions: deep_copy(this.actions), - renderers: deep_copy(this.renderers) - } - } - } - }) - this.handleClick = this.handleClick.bind(this); this.handleContext = this.handleContext.bind(this); this.handleUserContext = this.handleUserContext.bind(this); diff --git a/src/modules/chat/emote_info.gql b/src/modules/chat/emote_info.gql index 33b09a26..10b5232e 100644 --- a/src/modules/chat/emote_info.gql +++ b/src/modules/chat/emote_info.gql @@ -1,6 +1,11 @@ query FFZ_GetEmoteInfo($id: ID!) { emote(id: $id) { id + owner { + id + login + displayName + } setID text subscriptionProduct { diff --git a/src/modules/chat/emotes.js b/src/modules/chat/emotes.js index f21de7c8..d8b6d5ae 100644 --- a/src/modules/chat/emotes.js +++ b/src/modules/chat/emotes.js @@ -66,8 +66,8 @@ export default class Emotes extends Module { this.inject('experiments'); this.twitch_inventory_sets = new Set; //(EXTRA_INVENTORY); - this.__twitch_emote_to_set = new Map; - this.__twitch_set_to_channel = new Map; + this.__twitch_emote_to_set = {}; + this.__twitch_set_to_channel = {}; this.default_sets = new SourcedSet; this.global_sets = new SourcedSet; @@ -129,6 +129,19 @@ export default class Emotes extends Module { onEnable() { this.style = new ManagedStyle('emotes'); + // Fix numeric Twitch favorite IDs. + const favs = this.getFavorites('twitch'); + let changed = false; + for(let i=0; i < favs.length; i++) { + if ( typeof favs[i] === 'number' ) { + changed = true; + favs[i] = `${favs[i]}`; + } + } + + if ( changed ) + this.setFavorites('twitch', favs); + if ( Object.keys(this.emote_sets).length ) { this.log.info('Generating CSS for existing emote sets.'); for(const set_id in this.emote_sets) @@ -219,6 +232,14 @@ export default class Emotes extends Module { return this.settings.provider.get(`favorite-emotes.${source}`) || []; } + setFavorites(source, favs) { + const key = `favorite-emotes.${source}`; + if ( ! Array.isArray(favs) || ! favs.length ) + this.settings.provider.delete(key); + else + this.settings.provider.set(key, favs); + } + handleClick(event) { const target = event.target, @@ -299,7 +320,7 @@ export default class Emotes extends Module { if ( provider === 'twitch' ) { source = 'twitch'; - id = parseInt(ds.id, 10); + id = ds.id; } else if ( provider === 'ffz' ) { const emote_set = this.emote_sets[ds.set], @@ -753,28 +774,45 @@ export default class Emotes extends Module { // ======================================================================== setTwitchEmoteSet(emote_id, set_id) { - if ( isNaN(emote_id) || ! isFinite(emote_id) ) - return; + if ( typeof emote_id === 'number' ) { + if ( isNaN(emote_id) || ! isFinite(emote_id) ) + return; + emote_id = `${emote_id}`; + } - this.__twitch_emote_to_set.set(emote_id, set_id); + if ( typeof set_id === 'number' ) { + if ( isNaN(set_id) || ! isFinite(set_id) ) + return; + set_id = `${set_id}`; + } + + this.__twitch_emote_to_set[emote_id] = set_id; } setTwitchSetChannel(set_id, channel) { - if ( isNaN(set_id) || ! isFinite(set_id) ) - return; + if ( typeof set_id === 'number' ) { + if ( isNaN(set_id) || ! isFinite(set_id) ) + return; - this.__twitch_set_to_channel.set(set_id, channel); + set_id = `${set_id}`; + } + + this.__twitch_set_to_channel[set_id] = channel; } _getTwitchEmoteSet(emote_id) { const tes = this.__twitch_emote_to_set, tsc = this.__twitch_set_to_channel; - if ( isNaN(emote_id) || ! isFinite(emote_id) ) - return Promise.resolve(null); + if ( typeof emote_id === 'number' ) { + if ( isNaN(emote_id) || ! isFinite(emote_id) ) + return Promise.resolve(null); - if ( tes.has(emote_id) ) { - const val = tes.get(emote_id); + emote_id = `${emote_id}`; + } + + if ( has(tes, emote_id) ) { + const val = tes[emote_id]; if ( Array.isArray(val) ) return new Promise(s => val.push(s)); else @@ -787,7 +825,7 @@ export default class Emotes extends Module { return new Promise(s => { const promises = [s]; - tes.set(emote_id, promises); + tes[emote_id] = promises; timeout(apollo.client.query({ query: GET_EMOTE, @@ -799,25 +837,25 @@ export default class Emotes extends Module { let set_id = null; if ( emote ) { - set_id = parseInt(emote.setID, 10); + set_id = emote.setID; - if ( set_id && ! tsc.has(set_id) ) { + if ( set_id && ! has(tsc, set_id) ) { const type = determineEmoteType(emote); - tsc.set(set_id, { + tsc[set_id] = { id: set_id, type, - owner: emote?.subscriptionProduct?.owner - }); + owner: emote?.subscriptionProduct?.owner || emote?.owner + }; } } - tes.set(emote_id, set_id); + tes[emote_id] = set_id; for(const fn of promises) fn(set_id); }).catch(() => { - tes.set(emote_id, null); + tes[emote_id] = null; for(const fn of promises) fn(null); }); @@ -836,11 +874,15 @@ export default class Emotes extends Module { _getTwitchSetChannel(set_id) { const tsc = this.__twitch_set_to_channel; - if ( isNaN(set_id) || ! isFinite(set_id) ) - return Promise.resolve(null); + if ( typeof set_id === 'number' ) { + if ( isNaN(set_id) || ! isFinite(set_id) ) + return Promise.resolve(null); - if ( tsc.has(set_id) ) { - const val = tsc.get(set_id); + set_id = `${set_id}`; + } + + if ( has(tsc, set_id) ) { + const val = tsc[set_id]; if ( Array.isArray(val) ) return new Promise(s => val.push(s)); else @@ -853,7 +895,7 @@ export default class Emotes extends Module { return new Promise(s => { const promises = [s]; - tsc.set(set_id, promises); + tsc[set_id] = promises; timeout(apollo.client.query({ query: GET_EMOTE_SET, @@ -876,12 +918,12 @@ export default class Emotes extends Module { }; } - tsc.set(set_id, result); + tsc[set_id] = result; for(const fn of promises) fn(result); }).catch(() => { - tsc.set(set_id, null); + tsc[set_id] = null; for(const fn of promises) fn(null); }); @@ -921,6 +963,10 @@ function determineEmoteType(emote) { if ( emote.setID == 300238151 ) return EmoteTypes.ChannelPoints; + const id = parseInt(emote.setID, 10); + if ( ! isNaN(id) && isFinite(id) && id >= 5e8 ) + return EmoteTypes.BitsTier; + return EmoteTypes.Global; } @@ -962,5 +1008,8 @@ function determineSetType(set) { return EmoteTypes.Subscription; } + if ( id >= 5e8 ) + return EmoteTypes.BitsTier; + return EmoteTypes.Global; } \ No newline at end of file diff --git a/src/modules/chat/tokenizers.jsx b/src/modules/chat/tokenizers.jsx index 195e7c26..b6cd49bb 100644 --- a/src/modules/chat/tokenizers.jsx +++ b/src/modules/chat/tokenizers.jsx @@ -1036,7 +1036,7 @@ export const AddonEmotes = { } if ( provider === 'twitch' ) { - emote_id = parseInt(ds.id, 10); + emote_id = ds.id; const set_id = hide_source ? null : await this.emotes.getTwitchEmoteSet(emote_id), emote_set = set_id != null && await this.emotes.getTwitchSetChannel(set_id); @@ -1048,7 +1048,15 @@ export const AddonEmotes = { if ( type === EmoteTypes.Global ) source = this.i18n.t('emote.global', 'Twitch Global'); - else if ( type === EmoteTypes.Prime || type === EmoteTypes.Turbo ) + else if ( type === EmoteTypes.BitsTier ) { + source = this.i18n.t('emote.bits', 'Twitch Bits Reward'); + if ( emote_set.owner?.login ) + source = this.i18n.t('emote.bits-owner', '{source}\nChannel: {channel}', { + source, + channel: emote_set.owner.displayName || emote_set.owner.login + }); + + } else if ( type === EmoteTypes.Prime || type === EmoteTypes.Turbo ) source = this.i18n.t('emote.prime', 'Twitch Prime'); else if ( type === EmoteTypes.LimitedTime ) diff --git a/src/sites/twitch-twilight/modules/chat/emote_menu.jsx b/src/sites/twitch-twilight/modules/chat/emote_menu.jsx index fe47c344..d686b895 100644 --- a/src/sites/twitch-twilight/modules/chat/emote_menu.jsx +++ b/src/sites/twitch-twilight/modules/chat/emote_menu.jsx @@ -527,7 +527,7 @@ export default class EmoteMenu extends Module { } onMouseEnter(event) { - const set_id = parseInt(event.currentTarget.dataset.setId,10); + const set_id = event.currentTarget.dataset.setId; this.setState({unlocked: set_id}); } @@ -833,7 +833,7 @@ export default class EmoteMenu extends Module { tone: t.settings.provider.get('emoji-tone', null) } - this.componentWillReceiveProps(props); + this.componentDidUpdate({}); this.observing = new Map; @@ -1065,7 +1065,7 @@ export default class EmoteMenu extends Module { props = props || this.props; const emote_sets = props.emote_data && props.emote_data.emoteSets, - sets = Array.isArray(emote_sets) ? new Set(emote_sets.map(x => parseInt(x.id, 10))) : new Set; + sets = Array.isArray(emote_sets) ? new Set(emote_sets.map(x => x.id)) : new Set; force = force || (state.set_data && ! set_equals(state.set_sets, sets)); @@ -1287,19 +1287,20 @@ export default class EmoteMenu extends Module { if ( ! emote_set || ! Array.isArray(emote_set.emotes) ) continue; - const set_id = parseInt(emote_set.id, 10), + const set_id = emote_set.id, owner = emote_set.owner, + is_bits = parseInt(emote_set.id, 10) > 5e8, is_points = TWITCH_POINTS_SETS.includes(set_id) || owner?.login === 'channel_points', chan = is_points ? null : owner, set_data = data[set_id]; - if ( chan ) + /*if ( chan ) t.emotes.setTwitchSetChannel(set_id, { s_id: set_id, c_id: chan.id, c_name: chan.login, c_title: chan.displayName - }); + });*/ set_ids.add(set_id); @@ -1310,15 +1311,27 @@ export default class EmoteMenu extends Module { if ( title ) { key = `twitch-${chan?.id}`; - t.emotes.setTwitchSetChannel(set_id, { - id: set_id, - type: EmoteTypes.Subscription, - owner: { - id: chan.id, - login: chan.login, - displayName: chan.displayName - } - }); + + if ( is_bits ) + t.emotes.setTwitchSetChannel(set_id, { + id: set_id, + type: EmoteTypes.BitsTier, + owner: { + id: chan.id, + login: chan.login, + displayName: chan.displayName + } + }); + else + t.emotes.setTwitchSetChannel(set_id, { + id: set_id, + type: EmoteTypes.Subscription, + owner: { + id: chan.id, + login: chan.login, + displayName: chan.displayName + } + }); } else if ( ! chan ) { if ( is_points ) { @@ -1407,7 +1420,7 @@ export default class EmoteMenu extends Module { if ( ! emote || ! emote.id || ! emote.token ) continue; - const id = parseInt(emote.id, 10), + const id = emote.id, name = KNOWN_CODES[emote.token] || emote.token, mapped = emote_map && emote_map[name], overridden = mapped && mapped.id != id, @@ -1431,7 +1444,7 @@ export default class EmoteMenu extends Module { name, src, srcSet, - overridden: overridden ? parseInt(mapped.id,10) : null, + overridden: overridden ? mapped.id : null, misc: ! chan, favorite: is_fav }; @@ -1482,7 +1495,7 @@ export default class EmoteMenu extends Module { if ( ! product || ! Array.isArray(product.emotes) ) continue; - const set_id = parseInt(product.emoteSetID, 10), + const set_id = product.emoteSetID, set_data = data[set_id], locked = ! set_ids.has(set_id); @@ -1516,7 +1529,7 @@ export default class EmoteMenu extends Module { if ( ! emote || ! emote.id || ! emote.token ) continue; - const id = parseInt(emote.id, 10), + const id = emote.id, base = `${TWITCH_EMOTE_BASE}${id}`, name = KNOWN_CODES[emote.token] || emote.token, seen = twitch_seen.has(id), @@ -1678,12 +1691,20 @@ export default class EmoteMenu extends Module { } - componentWillReceiveProps(props) { - if ( props.visible ) + componentDidUpdate(old_props) { + if ( this.props.visible && ! old_props.visible ) this.loadData(); - const state = this.buildState(props, this.state); - this.setState(this.filterState(state.filter, state)); + if ( this.props.channel_data !== old_props.channel_data || + this.props.emote_data !== old_props.emote_data || + this.props.user_id !== old_props.user_id || + this.props.channel_id !== old_props.channel_id || + this.props.loading !== old_props.loading || + this.props.error !== old_props.error ) { + + const state = this.buildState(this.props, this.state); + this.setState(this.filterState(state.filter, state)); + } } renderError() { @@ -1956,7 +1977,7 @@ export default class EmoteMenu extends Module { ends: maybe_date(node.endsAt), renews: maybe_date(node.renewsAt), prime: node.purchasedWithPrime, - set_id: parseInt(set_id, 10), + set_id, type: product.type, gift: node.gift?.isGift }; diff --git a/src/utilities/constants.js b/src/utilities/constants.js index 40f4784e..c2eff9fe 100644 --- a/src/utilities/constants.js +++ b/src/utilities/constants.js @@ -105,5 +105,6 @@ export const EmoteTypes = make_enum( 'ChannelPoints', 'Unavailable', 'Subscription', + 'BitsTier', 'Global' ); \ No newline at end of file