1
0
Fork 0
mirror of https://github.com/FrankerFaceZ/FrankerFaceZ.git synced 2025-08-07 23:00:54 +00:00
* Added: Option to display all emotes in the same tab of the emote menu. (Closes #684)
* Added: Button in the emote menu that opens the FFZ Control Center to `Chat > Emote Menu`.
* Changed: Start using Twitch for all emote information rather than the FFZ socket cluster.
* Fixed: When an emoji skin tone is set, use that skin tone for category icons if applicable.
* Fixed: When reduced padding for the emote menu is enabled, reduce the padding around the navigation buttons.
This commit is contained in:
SirStendec 2019-10-28 14:56:55 -04:00
parent a6ee3e5013
commit 742da82515
8 changed files with 396 additions and 247 deletions

View file

@ -1,7 +1,7 @@
{ {
"name": "frankerfacez", "name": "frankerfacez",
"author": "Dan Salvato LLC", "author": "Dan Salvato LLC",
"version": "4.15.1", "version": "4.15.2",
"description": "FrankerFaceZ is a Twitch enhancement suite.", "description": "FrankerFaceZ is a Twitch enhancement suite.",
"license": "Apache-2.0", "license": "Apache-2.0",
"scripts": { "scripts": {

View file

@ -5,13 +5,12 @@ query FFZ_GetEmoteInfo($id: ID!) {
text text
subscriptionProduct { subscriptionProduct {
id id
state
owner { owner {
id id
login login
displayName displayName
} }
tier
url
} }
} }
} }

View file

@ -0,0 +1,15 @@
query FFZ_GetEmoteSetInfo($id: ID!) {
emoteSet(id: $id) {
id
owner {
id
login
displayName
subscriptionProducts {
id
emoteSetID
state
}
}
}
}

View file

@ -7,14 +7,13 @@
import Module from 'utilities/module'; import Module from 'utilities/module';
import {ManagedStyle} from 'utilities/dom'; import {ManagedStyle} from 'utilities/dom';
import {get, has, timeout, SourcedSet} from 'utilities/object'; import {get, has, timeout, SourcedSet} from 'utilities/object';
import {CLIENT_ID, NEW_API, API_SERVER, IS_OSX} from 'utilities/constants'; import {NEW_API, API_SERVER, IS_OSX, EmoteTypes, TWITCH_GLOBAL_SETS, TWITCH_POINTS_SETS, TWITCH_PRIME_SETS} from 'utilities/constants';
import GET_EMOTE from './emote_info.gql'; import GET_EMOTE from './emote_info.gql';
import GET_EMOTE_SET from './emote_set_info.gql';
const MOD_KEY = IS_OSX ? 'metaKey' : 'ctrlKey'; const MOD_KEY = IS_OSX ? 'metaKey' : 'ctrlKey';
//const EXTRA_INVENTORY = [33563];
const MODIFIERS = { const MODIFIERS = {
59847: { 59847: {
modifier_offset: '0 15px 15px 0', modifier_offset: '0 15px 15px 0',
@ -60,6 +59,8 @@ export default class Emotes extends Module {
constructor(...args) { constructor(...args) {
super(...args); super(...args);
this.EmoteTypes = EmoteTypes;
this.inject('socket'); this.inject('socket');
this.inject('settings'); this.inject('settings');
this.inject('experiments'); this.inject('experiments');
@ -126,9 +127,6 @@ export default class Emotes extends Module {
} }
onEnable() { onEnable() {
// Just in case there's a weird load order going on.
// this.on('site:enabled', this.loadTwitchInventory);
this.style = new ManagedStyle('emotes'); this.style = new ManagedStyle('emotes');
if ( Object.keys(this.emote_sets).length ) { if ( Object.keys(this.emote_sets).length ) {
@ -146,7 +144,6 @@ export default class Emotes extends Module {
this.socket.on(':command:follow_sets', this.updateFollowSets, this); this.socket.on(':command:follow_sets', this.updateFollowSets, this);
this.loadGlobalSets(); this.loadGlobalSets();
//this.loadTwitchInventory();
} }
@ -755,39 +752,6 @@ export default class Emotes extends Module {
// Twitch Data Lookup // Twitch Data Lookup
// ======================================================================== // ========================================================================
async loadTwitchInventory() {
const user = this.resolve('site').getUser();
if ( ! user )
return;
let data;
try {
data = await fetch('https://api.twitch.tv/v5/inventory/emoticons', {
headers: {
'Client-ID': CLIENT_ID,
'Authorization': `OAuth ${user.authToken}`
}
}).then(r => {
if ( r.ok )
return r.json();
throw r.status;
});
} catch(err) {
this.log.error('Error loading Twitch inventory.', err);
return;
}
const sets = this.twitch_inventory_sets = new Set(EXTRA_INVENTORY);
for(const set in data.emoticon_sets)
if ( has(data.emoticon_sets, set) )
sets.add(parseInt(set, 10));
this.log.info('Twitch Inventory Sets:', this.twitch_inventory_sets);
}
setTwitchEmoteSet(emote_id, set_id) { setTwitchEmoteSet(emote_id, set_id) {
if ( isNaN(emote_id) || ! isFinite(emote_id) ) if ( isNaN(emote_id) || ! isFinite(emote_id) )
return; return;
@ -802,127 +766,201 @@ export default class Emotes extends Module {
this.__twitch_set_to_channel.set(set_id, channel); this.__twitch_set_to_channel.set(set_id, channel);
} }
_getTwitchEmoteSet(emote_id) {
getTwitchEmoteSet(emote_id, callback) { const tes = this.__twitch_emote_to_set,
const tes = this.__twitch_emote_to_set; tsc = this.__twitch_set_to_channel;
if ( isNaN(emote_id) || ! isFinite(emote_id) ) if ( isNaN(emote_id) || ! isFinite(emote_id) )
return null; return Promise.resolve(null);
if ( tes.has(emote_id) ) if ( tes.has(emote_id) ) {
return tes.get(emote_id); const val = tes.get(emote_id);
if ( Array.isArray(val) )
return new Promise(s => val.push(s));
else
return Promise.resolve(val);
}
tes.set(emote_id, null); const apollo = this.resolve('site.apollo');
if ( ! apollo?.client )
return Promise.resolve(null);
return new Promise(s => {
const promises = [s];
tes.set(emote_id, promises);
/*const apollo = this.resolve('site.apollo');
if ( apollo?.client ) {
timeout(apollo.client.query({ timeout(apollo.client.query({
query: GET_EMOTE, query: GET_EMOTE,
variables: { variables: {
id: `${emote_id}` id: `${emote_id}`
} }
}), 1000).then(result => { }), 2000).then(data => {
const emote = result?.data?.emote; const emote = data?.data?.emote;
let set_id = null;
if ( ! emote ) { if ( emote ) {
tes.delete(emote_id); set_id = parseInt(emote.setID, 10);
return;
if ( set_id && ! tsc.has(set_id) ) {
const type = determineEmoteType(emote);
tsc.set(set_id, {
id: set_id,
type,
owner: emote?.subscriptionProduct?.owner
});
}
} }
const set_id = parseInt(emote.setID, 10),
channel = emote?.subscriptionProduct?.owner;
this.__twitch_set_to_channel.set(set_id, {
s_id: set_id,
c_id: channel ? channel.id : null,
c_name: channel ? channel.login : null,
c_title: channel ? channel.displayName : null
});
tes.set(emote_id, set_id); tes.set(emote_id, set_id);
if ( callback ) for(const fn of promises)
callback(set_id); fn(set_id);
}).catch(() => tes.delete(emote_id)); }).catch(() => {
tes.set(emote_id, null);
for(const fn of promises)
fn(null);
});
});
}
return; getTwitchEmoteSet(emote_id, callback) {
}*/ const promise = this._getTwitchEmoteSet(emote_id);
if ( callback )
timeout(this.socket.call('get_emote', emote_id), 1000).then(data => { promise.then(callback);
const set_id = data['s_id']; else
tes.set(emote_id, set_id); return promise;
this.__twitch_set_to_channel.set(set_id, data);
if ( callback )
callback(data['s_id']);
}).catch(() => tes.delete(emote_id));
} }
async awaitTwitchSetChannel(set_id, perform_lookup = true) { _getTwitchSetChannel(set_id) {
const tes = this.__twitch_set_to_channel, const tsc = this.__twitch_set_to_channel;
inv = this.twitch_inventory_sets;
if ( isNaN(set_id) || ! isFinite(set_id) ) if ( isNaN(set_id) || ! isFinite(set_id) )
return null; return Promise.resolve(null);
if ( tes.has(set_id) ) if ( tsc.has(set_id) ) {
return tes.get(set_id); const val = tsc.get(set_id);
if ( Array.isArray(val) )
return new Promise(s => val.push(s));
else
return Promise.resolve(val);
}
if ( inv.has(set_id) ) const apollo = this.resolve('site.apollo');
return {s_id: set_id, c_id: null, c_name: 'twitch-inventory'} if ( ! apollo?.client )
return Promise.resolve(null);
if ( ! perform_lookup ) return new Promise(s => {
return null; const promises = [s];
tsc.set(set_id, promises);
tes.set(set_id, null); timeout(apollo.client.query({
query: GET_EMOTE_SET,
variables: {
id: `${set_id}`
}
}), 2000).then(data => {
const set = data?.data?.emoteSet;
let result = null;
try { if ( set ) {
const data = await timeout(this.socket.call('get_emote_set', set_id), 1000); result = {
tes.set(set_id, data); id: set_id,
return data; type: determineSetType(set),
owner: set.owner ? {
id: set.owner.id,
login: set.owner.login,
displayName: set.owner.displayName
} : null
};
}
} catch(err) { tsc.set(set_id, result);
if ( err === 'No known Twitch emote set with that ID.' ) for(const fn of promises)
return null; fn(result);
tes.delete(set_id); }).catch(() => {
tsc.set(set_id, null);
for(const fn of promises)
fn(null);
});
});
}
getTwitchSetChannel(set_id, callback) {
const promise = this._getTwitchSetChannel(set_id);
if ( callback )
promise.then(callback);
else
return promise;
}
}
function determineEmoteType(emote) {
const product = emote.subscriptionProduct;
if ( product ) {
if ( product.id == 12658 )
return EmoteTypes.Prime;
else if ( product.id == 324 )
return EmoteTypes.Turbo;
// TODO: Care about Overwatch League
const owner = product.owner;
if ( owner ) {
if ( owner.id == 139075904 || product.state === 'INACTIVE' )
return EmoteTypes.LimitedTime;
return EmoteTypes.Subscription;
} }
} }
if ( emote.setID == 300238151 )
return EmoteTypes.ChannelPoints;
getTwitchSetChannel(set_id, callback, perform_lookup = true) { return EmoteTypes.Global;
const tes = this.__twitch_set_to_channel, }
inv = this.twitch_inventory_sets;
if ( isNaN(set_id) || ! isFinite(set_id) )
return null;
if ( tes.has(set_id) ) function determineSetType(set) {
return tes.get(set_id); const id = parseInt(set.id, 10);
if ( inv.has(set_id) ) if ( TWITCH_GLOBAL_SETS.includes(id) )
return {s_id: set_id, c_id: null, c_name: 'twitch-inventory'} return EmoteTypes.Global;
if ( ! perform_lookup ) if ( TWITCH_POINTS_SETS.includes(id) )
return null; return EmoteTypes.ChannelPoints;
tes.set(set_id, null); if ( TWITCH_PRIME_SETS.includes(id) )
timeout(this.socket.call('get_emote_set', set_id), 1000).then(data => { return EmoteTypes.Prime;
tes.set(set_id, data);
if ( callback )
callback(data);
}).catch(err => { const owner = set.owner;
if ( err === 'No known Twitch emote set with that ID.' ) { if ( owner ) {
if ( callback ) if ( owner.id == 139075904 )
callback(null); return EmoteTypes.LimitedTime;
return; let product;
} if ( Array.isArray(owner.subscriptionProducts) )
for(const prod of owner.subscriptionProducts)
if ( set.id == prod.emoteSetID ) {
product = prod;
break;
}
tes.delete(set_id) if ( product ) {
}); if ( product.id == 12658 )
return EmoteTypes.Prime;
else if ( product.id == 324 )
return EmoteTypes.Turbo;
else if ( product.state === 'INACTIVE' )
return EmoteTypes.LimitedTime;
}
return EmoteTypes.Subscription;
} }
return EmoteTypes.Global;
} }

View file

@ -7,7 +7,7 @@
import {sanitize, createElement} from 'utilities/dom'; import {sanitize, createElement} from 'utilities/dom';
import {has, split_chars} from 'utilities/object'; import {has, split_chars} from 'utilities/object';
import {TWITCH_EMOTE_BASE, REPLACEMENT_BASE, REPLACEMENTS} from 'utilities/constants'; import {TWITCH_EMOTE_BASE, EmoteTypes, REPLACEMENT_BASE, REPLACEMENTS} from 'utilities/constants';
const EMOTE_CLASS = 'chat-image chat-line__message--emote', const EMOTE_CLASS = 'chat-image chat-line__message--emote',
@ -1011,7 +1011,7 @@ export const AddonEmotes = {
</span>); </span>);
}, },
tooltip(target, tip) { async tooltip(target, tip) {
const ds = target.dataset, const ds = target.dataset,
provider = ds.provider, provider = ds.provider,
modifiers = ds.modifierInfo; modifiers = ds.modifierInfo;
@ -1036,23 +1036,30 @@ export const AddonEmotes = {
if ( provider === 'twitch' ) { if ( provider === 'twitch' ) {
emote_id = parseInt(ds.id, 10); emote_id = parseInt(ds.id, 10);
const set_id = this.emotes.getTwitchEmoteSet(emote_id, tip.rerender), const set_id = hide_source ? null : await this.emotes.getTwitchEmoteSet(emote_id),
emote_set = set_id != null && this.emotes.getTwitchSetChannel(set_id, tip.rerender); emote_set = set_id != null && await this.emotes.getTwitchSetChannel(set_id);
preview = `//static-cdn.jtvnw.net/emoticons/v1/${ds.id}/3.0?_=preview`; preview = `${TWITCH_EMOTE_BASE}${ds.id}/3.0?_=preview`;
fav_source = 'twitch'; fav_source = 'twitch';
if ( emote_set ) { if ( emote_set ) {
source = emote_set.c_name; const type = emote_set.type;
if ( type === EmoteTypes.Global )
if ( source === '--global--' || emote_id === 80393 )
source = this.i18n.t('emote.global', 'Twitch Global'); source = this.i18n.t('emote.global', 'Twitch Global');
else if ( source === '--twitch-turbo--' || source === 'turbo' || source === '--turbo-faces--' || source === '--prime--' || source === '--prime-faces--' ) else if ( type === EmoteTypes.Prime || type === EmoteTypes.Turbo )
source = this.i18n.t('emote.prime', 'Twitch Prime'); source = this.i18n.t('emote.prime', 'Twitch Prime');
else else if ( type === EmoteTypes.LimitedTime )
source = this.i18n.t('tooltip.channel', 'Channel: {source}', {source}); source = this.i18n.t('emote.limited', 'Limited-Time Only Emote');
else if ( type === EmoteTypes.ChannelPoints )
source = this.i18n.t('emote.points', 'Channel Points Emote');
else if ( type === EmoteTypes.Subscription && emote_set.owner?.login )
source = this.i18n.t('tooltip.channel', 'Channel: {source}', {
source: emote_set.owner.displayName || emote_set.owner.login
});
} }
} else if ( provider === 'ffz' ) { } else if ( provider === 'ffz' ) {

View file

@ -5,7 +5,7 @@
// ============================================================================ // ============================================================================
import {has, get, once, maybe_call, set_equals} from 'utilities/object'; import {has, get, once, maybe_call, set_equals} from 'utilities/object';
import {WEBKIT_CSS as WEBKIT, IS_OSX, KNOWN_CODES, TWITCH_EMOTE_BASE, REPLACEMENT_BASE, REPLACEMENTS} from 'utilities/constants'; import {TWITCH_GLOBAL_SETS, EmoteTypes, TWITCH_POINTS_SETS, TWITCH_PRIME_SETS, WEBKIT_CSS as WEBKIT, IS_OSX, KNOWN_CODES, TWITCH_EMOTE_BASE, REPLACEMENT_BASE, REPLACEMENTS} from 'utilities/constants';
import {ClickOutside} from 'utilities/dom'; import {ClickOutside} from 'utilities/dom';
import Twilight from 'site'; import Twilight from 'site';
@ -13,23 +13,6 @@ import Module from 'utilities/module';
import SUB_STATUS from './sub_status.gql'; import SUB_STATUS from './sub_status.gql';
const GLOBAL_SETS = [
0,
33,
42
];
const POINTS_SETS = [
300238151
];
const PRIME_SETS = [
457,
793,
19151,
19194
];
const TIERS = { const TIERS = {
1000: 'Tier 1', 1000: 'Tier 1',
2000: 'Tier 2', 2000: 'Tier 2',
@ -202,6 +185,16 @@ export default class EmoteMenu extends Module {
}); });
this.settings.add('chat.emote-menu.combine-tabs', {
default: false,
ui: {
path: 'Chat > Emote Menu >> General',
title: 'Display all emotes on one tab.',
component: 'setting-check-box'
}
});
this.settings.add('chat.emote-menu.sort-emotes', { this.settings.add('chat.emote-menu.sort-emotes', {
default: 0, default: 0,
ui: { ui: {
@ -263,6 +256,7 @@ export default class EmoteMenu extends Module {
this.chat.context.on('changed:chat.emote-menu.show-heading', fup); this.chat.context.on('changed:chat.emote-menu.show-heading', fup);
this.chat.context.on('changed:chat.emote-menu.show-search', fup); this.chat.context.on('changed:chat.emote-menu.show-search', fup);
this.chat.context.on('changed:chat.emote-menu.reduced-padding', fup); this.chat.context.on('changed:chat.emote-menu.reduced-padding', fup);
this.chat.context.on('changed:chat.emote-menu.combine-tabs', fup);
this.chat.context.on('changed:chat.emoji.style', this.updateEmojiVariables, this); this.chat.context.on('changed:chat.emoji.style', this.updateEmojiVariables, this);
@ -545,7 +539,7 @@ export default class EmoteMenu extends Module {
const data = this.props.data, const data = this.props.data,
filtered = this.props.filtered; filtered = this.props.filtered;
let show_heading = ! data.is_favorites && t.chat.context.get('chat.emote-menu.show-heading'); let show_heading = ! (data.is_favorites && ! t.chat.context.get('chat.emote-menu.combine-tabs')) && t.chat.context.get('chat.emote-menu.show-heading');
if ( show_heading === 2 ) if ( show_heading === 2 )
show_heading = ! filtered; show_heading = ! filtered;
else else
@ -592,6 +586,10 @@ export default class EmoteMenu extends Module {
} }
} }
let source = data.source_i18n ? t.i18n.t(data.source_i18n, data.source) : data.source;
if ( source == null )
source = 'FrankerFaceZ';
return (<section ref={this.saveRef} data-key={data.key} class={filtered ? 'filtered' : ''} onMouseEnter={this.mouseEnter}> return (<section ref={this.saveRef} data-key={data.key} class={filtered ? 'filtered' : ''} onMouseEnter={this.mouseEnter}>
{show_heading ? (<heading class="tw-pd-1 tw-border-b tw-flex tw-flex-nowrap" onClick={this.clickHeading}> {show_heading ? (<heading class="tw-pd-1 tw-border-b tw-flex tw-flex-nowrap" onClick={this.clickHeading}>
{image} {image}
@ -604,7 +602,7 @@ export default class EmoteMenu extends Module {
/>)} />)}
</div> </div>
<div class="tw-flex-grow-1" /> <div class="tw-flex-grow-1" />
{(data.source_i18n ? t.i18n.t(data.source_i18n, data.source) : data.source) || 'FrankerFaceZ'} {source}
{filtered ? '' : <figure class={`tw-pd-l-05 ffz-i-${collapsed ? 'left' : 'down'}-dir`} />} {filtered ? '' : <figure class={`tw-pd-l-05 ffz-i-${collapsed ? 'left' : 'down'}-dir`} />}
</heading>) : null} </heading>) : null}
{collapsed || this.renderBody(show_heading)} {collapsed || this.renderBody(show_heading)}
@ -958,11 +956,64 @@ export default class EmoteMenu extends Module {
} }
clickTab(event) { clickTab(event) {
const tab = event.currentTarget.dataset.tab;
if ( t.chat.context.get('chat.emote-menu.combine-tabs') ) {
let sets;
switch(tab) {
case 'fav':
sets = this.state.filtered_fav_sets;
break;
case 'channel':
sets = this.state.filtered_channel_sets;
break;
case 'emoji':
sets = this.state.filtered_emoji_sets;
break;
case 'all':
default:
sets = this.state.filtered_all_sets;
break;
}
const set = sets && sets[0],
el = set && this.ref?.querySelector?.(`section[data-key="${set.key}"]`);
if ( el )
el.scrollIntoView();
return;
}
this.setState({ this.setState({
tab: event.currentTarget.dataset.tab tab
}); });
} }
clickSettings(event) { // eslint-disable-line class-methods-use-this
const layout = t.resolve('site.layout');
if ( (layout && layout.is_minimal) || (event && (event.ctrlKey || event.shiftKey)) ) {
const win = window.open(
'https://twitch.tv/popout/frankerfacez/chat?ffz-settings',
'_blank',
'resizable=yes,scrollbars=yes,width=850,height=600'
);
if ( win )
win.focus();
} else {
const menu = t.resolve('main_menu');
if ( menu ) {
menu.requestPage('chat.emote_menu');
if ( menu.showing )
return;
}
t.emit('site.menu_button:clicked');
}
}
/*clickRefresh(event) { /*clickRefresh(event) {
const target = event.currentTarget, const target = event.currentTarget,
tt = target && target._ffz_tooltip$0; tt = target && target._ffz_tooltip$0;
@ -1109,6 +1160,11 @@ export default class EmoteMenu extends Module {
} }
} }
const is_fav = emoji_favorites.includes(emoji.code),
toned = emoji.variants && emoji.variants[tone],
has_tone = toned && toned.has[style],
source = has_tone ? toned : emoji;
let cat = categories[emoji.category]; let cat = categories[emoji.category];
if ( ! cat ) { if ( ! cat ) {
cat = categories[emoji.category] = []; cat = categories[emoji.category] = [];
@ -1116,7 +1172,7 @@ export default class EmoteMenu extends Module {
sets.push({ sets.push({
key: `emoji-${emoji.category}`, key: `emoji-${emoji.category}`,
emoji: true, emoji: true,
image: t.emoji.getFullImage(emoji.image), image: t.emoji.getFullImage(source.image),
i18n: `emoji.category.${emoji.category.toSnakeCase()}`, i18n: `emoji.category.${emoji.category.toSnakeCase()}`,
title: emoji.category, title: emoji.category,
source: 'Emoji', source: 'Emoji',
@ -1125,31 +1181,26 @@ export default class EmoteMenu extends Module {
}); });
} }
const is_fav = emoji_favorites.includes(emoji.code), const em = {
toned = emoji.variants && emoji.variants[tone], provider: 'emoji',
has_tone = toned && toned.has[style], emoji: true,
source = has_tone ? toned : emoji, code: emoji.code,
name: source.raw,
variant: has_tone && tone,
em = { search: emoji.names[0],
provider: 'emoji',
emoji: true,
code: emoji.code,
name: source.raw,
variant: has_tone && tone,
search: emoji.names[0], height: 18,
width: 18,
height: 18, x: source.sheet_x,
width: 18, y: source.sheet_y,
x: source.sheet_x, favorite: is_fav,
y: source.sheet_y,
favorite: is_fav, src: t.emoji.getFullImage(source.image),
srcSet: t.emoji.getFullImageSet(source.image)
src: t.emoji.getFullImage(source.image), };
srcSet: t.emoji.getFullImageSet(source.image)
};
cat.push(em); cat.push(em);
@ -1161,6 +1212,12 @@ export default class EmoteMenu extends Module {
state.fav_sets = [{ state.fav_sets = [{
key: 'favorites', key: 'favorites',
title: 'Favorites',
i18n: 'emote-menu.favorites',
icon: 'star',
source: '',
is_favorites: true, is_favorites: true,
emotes: favorites emotes: favorites
}]; }];
@ -1232,7 +1289,7 @@ export default class EmoteMenu extends Module {
const set_id = parseInt(emote_set.id, 10), const set_id = parseInt(emote_set.id, 10),
owner = emote_set.owner, owner = emote_set.owner,
is_points = owner?.login === 'channel_points', is_points = TWITCH_POINTS_SETS.includes(set_id) || owner?.login === 'channel_points',
chan = is_points ? null : owner, chan = is_points ? null : owner,
set_data = data[set_id]; set_data = data[set_id];
@ -1251,27 +1308,54 @@ export default class EmoteMenu extends Module {
icon = 'twitch', icon = 'twitch',
title = chan && (chan.displayName || chan.login); title = chan && (chan.displayName || chan.login);
if ( title ) if ( title ) {
key = `twitch-${chan?.id}`; 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
}
});
else if ( ! chan ) { } else if ( ! chan ) {
if ( is_points || POINTS_SETS.includes(set_id) ) { if ( is_points ) {
title = t.i18n.t('emote-menu.points', 'Unlocked with Points'); title = t.i18n.t('emote-menu.points', 'Unlocked with Points');
key = 'twitch-points'; key = 'twitch-points';
icon = 'channel-points'; icon = 'channel-points';
sort_key = 45; sort_key = 45;
} else if ( GLOBAL_SETS.includes(set_id) ) { /*t.emotes.setTwitchSetChannel(set_id, {
id: set_id,
type: EmoteTypes.ChannelPoints,
owner: null
});*/
} else if ( TWITCH_GLOBAL_SETS.includes(set_id) ) {
title = t.i18n.t('emote-menu.global', 'Global Emotes'); title = t.i18n.t('emote-menu.global', 'Global Emotes');
key = 'twitch-global'; key = 'twitch-global';
sort_key = 100; sort_key = 100;
} else if ( PRIME_SETS.includes(set_id) ) { t.emotes.setTwitchSetChannel(set_id, {
id: set_id,
type: EmoteTypes.Global,
owner: null
});
} else if ( TWITCH_PRIME_SETS.includes(set_id) ) {
title = t.i18n.t('emote_menu.prime', 'Prime'); title = t.i18n.t('emote_menu.prime', 'Prime');
key = 'twitch-prime'; key = 'twitch-prime';
icon = 'crown'; icon = 'crown';
sort_key = 75; sort_key = 75;
t.emotes.setTwitchSetChannel(set_id, {
id: set_id,
type: EmoteTypes.Prime,
owner: null
});
} else { } else {
title = t.i18n.t('emote-menu.misc', 'Miscellaneous'); title = t.i18n.t('emote-menu.misc', 'Miscellaneous');
key = 'twitch-misc'; key = 'twitch-misc';
@ -1352,7 +1436,9 @@ export default class EmoteMenu extends Module {
favorite: is_fav favorite: is_fav
}; };
t.emotes.setTwitchEmoteSet(id, set_id); if ( ! is_points )
t.emotes.setTwitchEmoteSet(id, set_id);
emotes.push(em); emotes.push(em);
if ( is_fav && ! twitch_seen.has(id) ) if ( is_fav && ! twitch_seen.has(id) )
@ -1381,7 +1467,7 @@ export default class EmoteMenu extends Module {
locks = {}, locks = {},
section = { section = {
sort_key: -10, sort_key: -10,
key: `twitch-${user.id}`, key: `twitch-current-channel`,
image: badge && badge.image1x, image: badge && badge.image1x,
image_set: badge && `${badge.image1x} 1x, ${badge.image2x} 2x, ${badge.image4x} 4x`, image_set: badge && `${badge.image1x} 1x, ${badge.image2x} 2x, ${badge.image4x} 4x`,
icon: 'twitch', icon: 'twitch',
@ -1647,28 +1733,41 @@ export default class EmoteMenu extends Module {
return null; return null;
const loading = this.state.loading || this.props.loading, const loading = this.state.loading || this.props.loading,
padding = t.chat.context.get('chat.emote-menu.reduced-padding'); padding = t.chat.context.get('chat.emote-menu.reduced-padding'),
no_tabs = t.chat.context.get('chat.emote-menu.combine-tabs');
let tab = this.state.tab || t.chat.context.get('chat.emote-menu.default-tab'), sets; let tab, sets, is_emoji;
if ( (tab === 'channel' && ! this.state.has_channel_tab) || (tab === 'emoji' && ! this.state.has_emoji_tab) )
tab = 'all';
const is_emoji = tab === 'emoji'; if ( no_tabs ) {
sets = [
this.state.filtered_fav_sets,
this.state.filtered_channel_sets,
this.state.filtered_all_sets,
this.state.filtered_emoji_sets
].flat();
switch(tab) { } else {
case 'fav': tab = this.state.tab || t.chat.context.get('chat.emote-menu.default-tab');
sets = this.state.filtered_fav_sets; if ( (tab === 'channel' && ! this.state.has_channel_tab) || (tab === 'emoji' && ! this.state.has_emoji_tab) )
break; tab = 'all';
case 'channel':
sets = this.state.filtered_channel_sets; is_emoji = tab === 'emoji';
break;
case 'emoji': switch(tab) {
sets = this.state.filtered_emoji_sets; case 'fav':
break; sets = this.state.filtered_fav_sets;
case 'all': break;
default: case 'channel':
sets = this.state.filtered_all_sets; sets = this.state.filtered_channel_sets;
break; break;
case 'emoji':
sets = this.state.filtered_emoji_sets;
break;
case 'all':
default:
sets = this.state.filtered_all_sets;
break;
}
} }
return (<div return (<div
@ -1684,7 +1783,7 @@ export default class EmoteMenu extends Module {
<div ref={this.saveScrollRef} class="simplebar-scroll-content"> <div ref={this.saveScrollRef} class="simplebar-scroll-content">
<div class="simplebar-content"> <div class="simplebar-content">
{loading && this.renderLoading()} {loading && this.renderLoading()}
{!loading && sets && sets.map(data => createElement( {!loading && sets && sets.map(data => data && createElement(
data.emoji ? t.EmojiSection : t.MenuSection, data.emoji ? t.EmojiSection : t.MenuSection,
{ {
key: data.key, key: data.key,
@ -1717,7 +1816,7 @@ export default class EmoteMenu extends Module {
onChange={this.handleFilterChange} onChange={this.handleFilterChange}
onKeyDown={this.handleKeyDown} onKeyDown={this.handleKeyDown}
/> />
{is_emoji && <t.EmojiTonePicker {(no_tabs || is_emoji) && <t.EmojiTonePicker
tone={this.state.tone} tone={this.state.tone}
choices={this.state.tone_emoji} choices={this.state.tone_emoji}
pickTone={this.pickTone} pickTone={this.pickTone}
@ -1781,19 +1880,19 @@ export default class EmoteMenu extends Module {
</div> </div>
</button> </button>
</div>} </div>}
{/*<div class="tw-flex-grow-1" /> <div class="tw-flex-grow-1" />
{!loading && (<div class="emote-picker-tab-item tw-relative"> <div class="emote-picker-tab-item tw-relative">
<button <button
class="ffz-tooltip tw-block tw-full-width tw-interactable tw-interactable--hover-enabled tw-interactable--inverted tw-interactive" class="ffz-tooltip tw-block tw-full-width tw-interactable tw-interactable--hover-enabled tw-interactable--inverted tw-interactive"
data-tooltip-type="html" data-tooltip-type="html"
data-title={t.i18n.t('emote-menu.refresh', 'Refresh Data')} data-title={t.i18n.t('emote-menu.settings', 'Open Settings')}
onClick={this.clickRefresh} onClick={this.clickSettings}
> >
<div class="tw-inline-flex tw-pd-x-1 tw-pd-y-05 tw-font-size-4"> <div class="tw-inline-flex tw-pd-x-1 tw-pd-y-05 tw-font-size-4">
<figure class="ffz-i-arrows-cw" /> <figure class="ffz-i-cog" />
</div> </div>
</button> </button>
</div>)*/} </div>
</div> </div>
</div> </div>
</div> </div>
@ -1861,39 +1960,6 @@ export default class EmoteMenu extends Module {
type: product.type, type: product.type,
gift: node.gift?.isGift gift: node.gift?.isGift
}; };
/*const owner = product.owner || {},
badges = owner.broadcastBadges;
let image, image_set;
if ( badges )
for(const badge of badges)
if ( badge.setID === 'subscriber' && badge.version === '0' ) {
image = badge.imageURL;
if ( image.endsWith('/1') ) {
const base = image.slice(0, -2);
image_set = `${base}/1 1x, ${base}/2 2x, ${base}/3 4x`;
}
break;
}
out[set_id] = {
ends: maybe_date(node.endsAt),
renews: maybe_date(node.renewsAt),
prime: node.purchasedWithPrime,
set_id: parseInt(set_id, 10),
type: product.type,
image,
image_set,
user: {
id: owner.id,
login: owner.login,
display_name: owner.displayName
}
}*/
} }
this._data_sets = sets; this._data_sets = sets;

View file

@ -288,6 +288,11 @@
} }
&.reduced-padding { &.reduced-padding {
.emote-picker-tab-item button > div {
padding: 0.25rem 0.5rem !important;
font-size: 1.6rem !important;
}
.tw-pd-1 { .tw-pd-1 {
padding: 0.5rem !important; padding: 0.5rem !important;
} }

View file

@ -1,5 +1,8 @@
'use strict'; 'use strict';
import {make_enum} from 'utilities/object';
export const DEBUG = localStorage.ffzDebugMode === 'true' && document.body.classList.contains('ffz-dev'); export const DEBUG = localStorage.ffzDebugMode === 'true' && document.body.classList.contains('ffz-dev');
export const SERVER = DEBUG ? '//localhost:8000' : 'https://cdn.frankerfacez.com'; export const SERVER = DEBUG ? '//localhost:8000' : 'https://cdn.frankerfacez.com';
@ -87,4 +90,20 @@ export const IS_WIN = navigator.platform ? navigator.platform.indexOf('Win') !==
export const IS_WEBKIT = navigator.userAgent.indexOf('AppleWebKit/') !== -1 && navigator.userAgent.indexOf('Edge/') === -1; export const IS_WEBKIT = navigator.userAgent.indexOf('AppleWebKit/') !== -1 && navigator.userAgent.indexOf('Edge/') === -1;
export const IS_FIREFOX = navigator.userAgent.indexOf('Firefox/') !== -1; export const IS_FIREFOX = navigator.userAgent.indexOf('Firefox/') !== -1;
export const WEBKIT_CSS = IS_WEBKIT ? '-webkit-' : ''; export const WEBKIT_CSS = IS_WEBKIT ? '-webkit-' : '';
export const TWITCH_GLOBAL_SETS = [0, 33, 42];
export const TWITCH_POINTS_SETS = [300238151];
export const TWITCH_PRIME_SETS = [457, 793, 19151, 19194];
export const EmoteTypes = make_enum(
'Unknown',
'Prime',
'Turbo',
'LimitedTime',
'ChannelPoints',
'Unavailable',
'Subscription',
'Global'
);