1
0
Fork 0
mirror of https://github.com/FrankerFaceZ/FrankerFaceZ.git synced 2025-08-02 16:08:31 +00:00
* Added: Support for bits tier emote rewards.
* Fixed: Stop casting Twitch emote IDs to numbers. Bits reward emotes use hexadecimal IDs prefixed with `emotesv2_`.
This commit is contained in:
SirStendec 2019-12-12 18:44:19 -05:00
parent 5f27045b9f
commit 3ff9895713
7 changed files with 139 additions and 92 deletions

View file

@ -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": {

View file

@ -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);

View file

@ -1,6 +1,11 @@
query FFZ_GetEmoteInfo($id: ID!) {
emote(id: $id) {
id
owner {
id
login
displayName
}
setID
text
subscriptionProduct {

View file

@ -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 ( 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 ( 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 ( 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 ( 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;
}

View file

@ -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 )

View file

@ -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,6 +1311,18 @@ export default class EmoteMenu extends Module {
if ( title ) {
key = `twitch-${chan?.id}`;
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,
@ -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,13 +1691,21 @@ 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);
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() {
return (<div class="tw-align-center tw-pd-1">
@ -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
};

View file

@ -105,5 +105,6 @@ export const EmoteTypes = make_enum(
'ChannelPoints',
'Unavailable',
'Subscription',
'BitsTier',
'Global'
);