mirror of
https://github.com/FrankerFaceZ/FrankerFaceZ.git
synced 2025-08-02 16:08:31 +00:00
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_`.
This commit is contained in:
parent
5f27045b9f
commit
3ff9895713
7 changed files with 139 additions and 92 deletions
|
@ -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": {
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -1,6 +1,11 @@
|
|||
query FFZ_GetEmoteInfo($id: ID!) {
|
||||
emote(id: $id) {
|
||||
id
|
||||
owner {
|
||||
id
|
||||
login
|
||||
displayName
|
||||
}
|
||||
setID
|
||||
text
|
||||
subscriptionProduct {
|
||||
|
|
|
@ -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;
|
||||
}
|
|
@ -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 )
|
||||
|
|
|
@ -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
|
||||
};
|
||||
|
|
|
@ -105,5 +105,6 @@ export const EmoteTypes = make_enum(
|
|||
'ChannelPoints',
|
||||
'Unavailable',
|
||||
'Subscription',
|
||||
'BitsTier',
|
||||
'Global'
|
||||
);
|
Loading…
Add table
Add a link
Reference in a new issue