2017-11-13 01:23:39 -05:00
'use strict' ;
// ============================================================================
// Emote Handling and Default Provider
// ============================================================================
import Module from 'utilities/module' ;
import { ManagedStyle } from 'utilities/dom' ;
2023-03-03 15:24:20 -05:00
import { get , has , timeout , SourcedSet , make _enum _flags } from 'utilities/object' ;
import { NEW _API , IS _OSX , EmoteTypes , TWITCH _GLOBAL _SETS , TWITCH _POINTS _SETS , TWITCH _PRIME _SETS } from 'utilities/constants' ;
2017-11-13 01:23:39 -05:00
2019-01-15 16:14:21 -05:00
import GET _EMOTE from './emote_info.gql' ;
2019-10-28 14:56:55 -04:00
import GET _EMOTE _SET from './emote_set_info.gql' ;
2023-03-06 17:08:47 -05:00
import { FFZEvent } from 'src/utilities/events' ;
2019-01-15 16:14:21 -05:00
2021-03-20 18:47:12 -04:00
const HoverRAF = Symbol ( 'FFZ:Hover:RAF' ) ;
const HoverState = Symbol ( 'FFZ:Hover:State' ) ;
2018-04-09 19:57:05 -04:00
const MOD _KEY = IS _OSX ? 'metaKey' : 'ctrlKey' ;
2017-11-13 01:23:39 -05:00
2023-03-08 01:52:35 -05:00
const Flags = make _enum _flags (
2023-03-03 15:24:20 -05:00
'Hidden' ,
'FlipX' ,
'FlipY' ,
'GrowX' ,
2023-03-27 18:50:32 -04:00
'Slide' ,
'Appear' ,
'Leave' ,
'Rotate' ,
2023-03-03 15:24:20 -05:00
'Rotate90' ,
'Greyscale' ,
'Sepia' ,
'Rainbow' ,
'HyperRed' ,
'Shake' ,
2023-03-08 01:52:35 -05:00
'Cursed' ,
2023-03-06 17:08:47 -05:00
'Jam' ,
'Bounce'
2023-03-03 15:24:20 -05:00
) ;
2023-03-08 01:52:35 -05:00
export const MODIFIER _FLAGS = Flags ;
2023-03-03 15:24:20 -05:00
export const MODIFIER _KEYS = Object . values ( MODIFIER _FLAGS ) . filter ( x => typeof x === 'number' ) ;
2023-03-27 18:50:32 -04:00
const APPEAR _FRAMES = [
[ 0 , - 18 , 0 , 0 ] ,
[ 19.99 , - 18 , 0 , 0 ] ,
[ 20 , - 18 , 0.1 , 0 ] ,
[ 25 , - 16 , 0.2 , 0.6 ] ,
[ 30 , - 14 , 0.3 , - 4 ] ,
[ 35 , - 12 , 0.4 , 0.6 ] ,
[ 40 , - 10 , 0.5 , - 4 ] ,
[ 45 , - 8 , 0.6 , 2 ] ,
[ 50 , - 6 , 0.7 , - 3 ] ,
[ 55 , - 4 , 0.8 , 2 ] ,
[ 60 , - 2 , 0.9 , - 3 ] ,
[ 65 , 0 , 1 , 0 ] ,
[ 100 , 0 , 1 , 0 ]
] ;
const LEAVE _FRAMES = [
[ 0 , 0 , 1 , 0 ] ,
2023-03-27 19:49:24 -04:00
[ 39.99 , 0 , 1 , 0 ] ,
2023-03-27 18:50:32 -04:00
[ 40 , 0 , - . 9 , . 9 , - 3 ] ,
[ 45 , - 2 , - . 8 , . 8 , 2 ] ,
[ 50 , - 4 , - . 7 , . 7 , - 3 ] ,
[ 55 , - 6 , - . 6 , . 6 , 2 ] ,
[ 60 , - 8 , - . 5 , . 5 , - 4 ] ,
[ 65 , - 10 , - . 4 , . 4 , . 6 ] ,
[ 70 , - 12 , - . 3 , . 3 , - 4 ] ,
[ 75 , - 14 , - . 2 , . 2 , . 6 ] ,
2023-03-27 19:49:24 -04:00
[ 80 , - 16 , - . 1 , . 1 , 0 ] ,
2023-03-27 18:50:32 -04:00
[ 85 , - 18 , - 0.01 , 0 , 0 ] ,
2023-03-27 19:49:24 -04:00
[ 100 , - 18 , 0 , 0 , 0 ]
2023-03-27 18:50:32 -04:00
] ;
function appearLeaveToKeyframes ( source , multi = 1 , offset = 0 , has _var = false ) {
const out = [ ] ;
for ( const line of source ) {
const pct = ( line [ 0 ] * multi ) + offset ;
let vr , tx , scale , ty ;
vr = has _var ? ` var(--ffz-effect-transforms) ` : '' ;
tx = line [ 1 ] === 0 ? '' : ` translateX( ${ line [ 1 ] } px) ` ;
if ( line . length === 4 ) {
scale = ` scale( ${ line [ 2 ] } ) ` ;
ty = line [ 3 ] === 0 ? '' : ` translateY( ${ line [ 3 ] } px) ` ;
} else {
const sx = line [ 2 ] ,
sy = line [ 3 ] ;
scale = ` scale( ${ sx } , ${ sy } ) ` ;
ty = line [ 4 ] === 0 ? '' : ` translateY( ${ line [ 4 ] } px) ` ;
}
out . push ( ` \t ${ pct } % { transform: ${ vr } ${ tx } ${ scale } ${ ty } ; } ` ) ;
}
return out . join ( '\n' ) ;
}
2023-03-08 01:52:35 -05:00
const EFFECT _STYLES = [
{
setting : 'FlipX' ,
flags : Flags . FlipX ,
2023-03-03 15:24:20 -05:00
title : 'Flip Horizontal' ,
transform : 'scaleX(-1)'
} ,
2023-03-08 01:52:35 -05:00
{
setting : 'FlipY' ,
flags : Flags . FlipY ,
2023-03-03 15:24:20 -05:00
title : 'Flip Vertical' ,
transform : 'scaleY(-1)'
} ,
2023-03-08 01:52:35 -05:00
{
setting : 'ShrinkX' ,
flags : Flags . ShrinkX ,
title : 'Squish Horizontal'
2023-03-03 15:24:20 -05:00
} ,
2023-03-08 01:52:35 -05:00
{
setting : 'GrowX' ,
flags : Flags . GrowX ,
title : 'Stretch Horizontal'
2023-03-03 15:24:20 -05:00
} ,
2023-03-08 01:52:35 -05:00
{
2023-03-27 18:50:32 -04:00
setting : 'Slide' ,
flags : Flags . Slide ,
//not_flags: Flags.Rotate,
title : 'Slide Animation' ,
as _background : true ,
animation : 'ffz-effect-slide var(--ffz-speed-x) linear infinite' ,
raw : ` @keyframes ffz-effect-slide {
0 % { background - position - x : 0 ; }
100 % { background - position - x : calc ( - 1 * var ( -- ffz - width ) ) ; }
} `
2023-03-03 15:24:20 -05:00
} ,
2023-03-08 01:52:35 -05:00
{
2023-03-27 18:50:32 -04:00
setting : 'Appear' ,
flags : Flags . Appear ,
not _flags : Flags . Leave ,
title : 'Appear Animation' ,
animation : 'ffz-effect-appear 3s infinite linear' ,
animationTransform : 'ffz-effect-appear-transform 3s linear infinite' ,
raw : ` @keyframes ffz-effect-appear {
$ { appearLeaveToKeyframes ( APPEAR _FRAMES ) }
}
@ keyframes ffz - effect - appear - transform {
$ { appearLeaveToKeyframes ( APPEAR _FRAMES , 1 , 0 , true ) }
} `
2023-03-03 15:24:20 -05:00
} ,
2023-03-27 18:50:32 -04:00
{
setting : 'Leave' ,
flags : Flags . Leave ,
not _flags : Flags . Appear ,
title : 'Leave Animation' ,
animation : 'ffz-effect-leave 3s infinite linear' ,
animationTransform : 'ffz-effect-leave-transform 3s infinite linear' ,
raw : ` @keyframes ffz-effect-leave {
$ { appearLeaveToKeyframes ( LEAVE _FRAMES ) }
}
@ keyframes ffz - effect - leave - transform {
$ { appearLeaveToKeyframes ( LEAVE _FRAMES , 1 , 0 , true ) }
} `
2023-03-03 15:24:20 -05:00
} ,
2023-03-08 01:52:35 -05:00
{
2023-03-27 18:50:32 -04:00
setting : [
'Appear' ,
'Leave'
] ,
flags : Flags . Appear | Flags . Leave ,
animation : 'ffz-effect-in-out 6s infinite linear' ,
animationTransform : 'ffz-effect-in-out-transform 6s linear infinite' ,
raw : ` @keyframes ffz-effect-in-out {
$ { appearLeaveToKeyframes ( APPEAR _FRAMES , 0.5 , 0 ) }
$ { appearLeaveToKeyframes ( LEAVE _FRAMES , 0.5 , 50 ) }
}
@ keyframes ffz - effect - in - out - transform {
$ { appearLeaveToKeyframes ( APPEAR _FRAMES , 0.5 , 0 , true ) }
$ { appearLeaveToKeyframes ( LEAVE _FRAMES , 0.5 , 50 , true ) }
} `
} ,
{
setting : 'Rotate' ,
flags : Flags . Rotate ,
not _flags : Flags . Slide ,
title : 'Rotate Animation' ,
no _wide : true ,
animation : 'ffz-effect-rotate 1.5s infinite linear' ,
animationTransform : 'ffz-effect-rotate-transform 1.5s infinite linear' ,
raw : ` @keyframes ffz-effect-rotate {
0 % { transform : rotate ( 0 deg ) ; }
100 % { transform : rotate ( 360 deg ) ; }
}
@ keyframes ffz - effect - rotate - transform {
0 % { transform : var ( -- ffz - effect - transforms ) rotate ( 0 deg ) ; }
100 % { transform : var ( -- ffz - effect - transforms ) rotate ( 360 deg ) ; }
} `
} ,
/ * {
setting : [
'Slide' ,
'Rotate'
] ,
flags : Flags . Rotate | Flags . Slide ,
// Sync up the speed for slide and rotate if both are applied.
animation : 'ffz-effect-slide calc(1.5 * var(--ffz-speed-x)) linear infinite'
2023-03-03 15:24:20 -05:00
} ,
2023-03-08 01:52:35 -05:00
{
setting : 'Greyscale' ,
flags : Flags . Greyscale ,
2023-03-03 15:24:20 -05:00
filter : 'grayscale(1)'
} ,
2023-03-08 01:52:35 -05:00
{
setting : 'Sepia' ,
flags : Flags . Sepia ,
2023-03-03 15:24:20 -05:00
filter : 'sepia(1)'
2023-03-08 01:52:35 -05:00
} , * /
{
setting : 'Rainbow' ,
flags : Flags . Rainbow ,
2023-03-03 15:24:20 -05:00
title : 'Rainbow Animation' ,
animation : 'ffz-effect-rainbow 2s linear infinite' ,
animationFilter : 'ffz-effect-rainbow-filter 2s linear infinite' ,
raw : ` @keyframes ffz-effect-rainbow {
2023-03-08 01:52:35 -05:00
0 % { filter : hue - rotate ( 0 deg ) }
100 % { filter : hue - rotate ( 360 deg ) }
2023-03-03 15:24:20 -05:00
}
@ keyframes ffz - effect - rainbow - filter {
2023-03-08 01:52:35 -05:00
0 % { filter : var ( -- ffz - effect - filters ) hue - rotate ( 0 deg ) }
100 % { filter : var ( -- ffz - effect - filters ) hue - rotate ( 360 deg ) }
2023-03-03 15:24:20 -05:00
} `
} ,
2023-03-08 01:52:35 -05:00
{
setting : 'HyperRed' ,
flags : Flags . HyperRed ,
2023-03-03 15:24:20 -05:00
title : 'Hyper Red' ,
filter : 'brightness(0.2) sepia(1) brightness(2.2) contrast(3) saturate(8)'
} ,
2023-03-08 01:52:35 -05:00
{
setting : 'Shake' ,
flags : Flags . Shake ,
2023-03-03 15:24:20 -05:00
title : 'Hyper Shake Animation' ,
animation : 'ffz-effect-shake 0.1s linear infinite' ,
animationTransform : 'ffz-effect-shake-transform 0.1s linear infinite' ,
raw : ` @keyframes ffz-effect-shake-transform {
2023-03-08 01:52:35 -05:00
0 % { transform : var ( -- ffz - effect - transforms ) translate ( 1 px , 1 px ) ; }
10 % { transform : var ( -- ffz - effect - transforms ) translate ( - 1 px , - 2 px ) ; }
20 % { transform : var ( -- ffz - effect - transforms ) translate ( - 3 px , 0 px ) ; }
30 % { transform : var ( -- ffz - effect - transforms ) translate ( 3 px , 2 px ) ; }
40 % { transform : var ( -- ffz - effect - transforms ) translate ( 1 px , - 1 px ) ; }
50 % { transform : var ( -- ffz - effect - transforms ) translate ( - 1 px , 2 px ) ; }
60 % { transform : var ( -- ffz - effect - transforms ) translate ( - 3 px , 1 px ) ; }
70 % { transform : var ( -- ffz - effect - transforms ) translate ( 3 px , 1 px ) ; }
80 % { transform : var ( -- ffz - effect - transforms ) translate ( - 1 px , - 1 px ) ; }
90 % { transform : var ( -- ffz - effect - transforms ) translate ( 1 px , 2 px ) ; }
100 % { transform : var ( -- ffz - effect - transforms ) translate ( 1 px , - 2 px ) ; }
2023-03-03 15:24:20 -05:00
}
@ keyframes ffz - effect - shake {
2023-03-08 01:52:35 -05:00
0 % { transform : translate ( 1 px , 1 px ) ; }
10 % { transform : translate ( - 1 px , - 2 px ) ; }
20 % { transform : translate ( - 3 px , 0 px ) ; }
30 % { transform : translate ( 3 px , 2 px ) ; }
40 % { transform : translate ( 1 px , - 1 px ) ; }
50 % { transform : translate ( - 1 px , 2 px ) ; }
60 % { transform : translate ( - 3 px , 1 px ) ; }
70 % { transform : translate ( 3 px , 1 px ) ; }
80 % { transform : translate ( - 1 px , - 1 px ) ; }
90 % { transform : translate ( 1 px , 2 px ) ; }
100 % { transform : translate ( 1 px , - 2 px ) ; }
2023-03-03 15:24:20 -05:00
} `
} ,
2023-03-08 01:52:35 -05:00
{
setting : 'Photocopy' ,
flags : Flags . Cursed ,
2023-03-06 17:08:47 -05:00
title : 'Cursed' ,
filter : 'grayscale(1) brightness(0.7) contrast(2.5)'
} ,
2023-03-08 01:52:35 -05:00
{
setting : 'Jam' ,
flags : Flags . Jam ,
2023-03-06 17:08:47 -05:00
title : 'Jam Animation' ,
animation : 'ffz-effect-jam 0.6s linear infinite' ,
animationTransform : 'ffz-effect-jam-transform 0.6s linear infinite' ,
raw : ` @keyframes ffz-effect-jam {
2023-03-08 01:52:35 -05:00
0 % { transform : translate ( - 2 px , - 2 px ) rotate ( - 6 deg ) ; }
10 % { transform : translate ( - 1.5 px , - 2 px ) rotate ( - 8 deg ) ; }
20 % { transform : translate ( 1 px , - 1.5 px ) rotate ( - 8 deg ) ; }
30 % { transform : translate ( 3 px , 2.5 px ) rotate ( - 6 deg ) ; }
40 % { transform : translate ( 3 px , 4 px ) rotate ( - 2 deg ) ; }
50 % { transform : translate ( 2 px , 4 px ) rotate ( 3 deg ) ; }
60 % { transform : translate ( 1 px , 4 px ) rotate ( 3 deg ) ; }
70 % { transform : translate ( - 0.5 px , 3 px ) rotate ( 2 deg ) ; }
80 % { transform : translate ( - 1.25 px , 1 px ) rotate ( 0 deg ) ; }
90 % { transform : translate ( - 1.75 px , - 0.5 px ) rotate ( - 2 deg ) ; }
100 % { transform : translate ( - 2 px , - 2 px ) rotate ( - 5 deg ) ; }
2023-03-06 17:08:47 -05:00
}
@ keyframes ffz - effect - jam - transform {
2023-03-08 01:52:35 -05:00
0 % { transform : var ( -- ffz - effect - transforms ) translate ( - 2 px , - 2 px ) rotate ( - 6 deg ) ; }
10 % { transform : var ( -- ffz - effect - transforms ) translate ( - 1.5 px , - 2 px ) rotate ( - 8 deg ) ; }
20 % { transform : var ( -- ffz - effect - transforms ) translate ( 1 px , - 1.5 px ) rotate ( - 8 deg ) ; }
30 % { transform : var ( -- ffz - effect - transforms ) translate ( 3 px , 2.5 px ) rotate ( - 6 deg ) ; }
40 % { transform : var ( -- ffz - effect - transforms ) translate ( 3 px , 4 px ) rotate ( - 2 deg ) ; }
50 % { transform : var ( -- ffz - effect - transforms ) translate ( 2 px , 4 px ) rotate ( 3 deg ) ; }
60 % { transform : var ( -- ffz - effect - transforms ) translate ( 1 px , 4 px ) rotate ( 3 deg ) ; }
70 % { transform : var ( -- ffz - effect - transforms ) translate ( - 0.5 px , 3 px ) rotate ( 2 deg ) ; }
80 % { transform : var ( -- ffz - effect - transforms ) translate ( - 1.25 px , 1 px ) rotate ( 0 deg ) ; }
90 % { transform : var ( -- ffz - effect - transforms ) translate ( - 1.75 px , - 0.5 px ) rotate ( - 2 deg ) ; }
100 % { transform : var ( -- ffz - effect - transforms ) translate ( - 2 px , - 2 px ) rotate ( - 5 deg ) ; }
} `
2023-03-06 17:08:47 -05:00
} ,
2023-03-08 01:52:35 -05:00
{
setting : 'Bounce' ,
flags : Flags . Bounce ,
2023-03-06 17:08:47 -05:00
animation : 'ffz-effect-bounce 0.5s linear infinite' ,
animationTransform : 'ffz-effect-bounce-transform 0.5s linear infinite' ,
transformOrigin : 'bottom center' ,
raw : ` @keyframes ffz-effect-bounce {
2023-03-08 01:52:35 -05:00
0 % { transform : scale ( 0.8 , 1 ) ; }
10 % { transform : scale ( 0.9 , 0.8 ) ; }
20 % { transform : scale ( 1 , 0.4 ) ; }
25 % { transform : scale ( 1.2 , 0.3 ) ; }
25.001 % { transform : scale ( - 1.2 , 0.3 ) ; }
30 % { transform : scale ( - 1 , 0.4 ) ; }
40 % { transform : scale ( - 0.9 , 0.8 ) ; }
50 % { transform : scale ( - 0.8 , 1 ) ; }
60 % { transform : scale ( - 0.9 , 0.8 ) ; }
70 % { transform : scale ( - 1 , 0.4 ) ; }
75 % { transform : scale ( - 1.2 , 0.3 ) ; }
75.001 % { transform : scale ( 1.2 , 0.3 ) ; }
80 % { transform : scale ( 1 , 0.4 ) ; }
90 % { transform : scale ( 0.9 , 0.8 ) ; }
100 % { transform : scale ( 0.8 , 1 ) ; }
2023-03-06 17:08:47 -05:00
}
@ keyframes ffz - effect - bounce - transform {
2023-03-08 01:52:35 -05:00
0 % { transform : scale ( 0.8 , 1 ) var ( -- ffz - effect - transforms ) ; }
10 % { transform : scale ( 0.9 , 0.8 ) var ( -- ffz - effect - transforms ) ; }
20 % { transform : scale ( 1 , 0.4 ) var ( -- ffz - effect - transforms ) ; }
25 % { transform : scale ( 1.2 , 0.3 ) var ( -- ffz - effect - transforms ) ; }
25.001 % { transform : scale ( - 1.2 , 0.3 ) var ( -- ffz - effect - transforms ) ; }
30 % { transform : scale ( - 1 , 0.4 ) var ( -- ffz - effect - transforms ) ; }
40 % { transform : scale ( - 0.9 , 0.8 ) var ( -- ffz - effect - transforms ) ; }
50 % { transform : scale ( - 0.8 , 1 ) var ( -- ffz - effect - transforms ) ; }
60 % { transform : scale ( - 0.9 , 0.8 ) var ( -- ffz - effect - transforms ) ; }
70 % { transform : scale ( - 1 , 0.4 ) var ( -- ffz - effect - transforms ) ; }
75 % { transform : scale ( - 1.2 , 0.3 ) var ( -- ffz - effect - transforms ) ; }
75.001 % { transform : scale ( 1.2 , 0.3 ) var ( -- ffz - effect - transforms ) ; }
80 % { transform : scale ( 1 , 0.4 ) var ( -- ffz - effect - transforms ) ; }
90 % { transform : scale ( 0.9 , 0.8 ) var ( -- ffz - effect - transforms ) ; }
100 % { transform : scale ( 0.8 , 1 ) var ( -- ffz - effect - transforms ) ; }
2023-03-06 17:08:47 -05:00
} `
2023-03-08 01:52:35 -05:00
} ,
{
setting : [
'Bounce' ,
'FlipY'
] ,
flags : Flags . Bounce | Flags . FlipY ,
transform : 'translateY(100%)' ,
} ,
] ;
2023-03-03 15:24:20 -05:00
function generateBaseFilterCss ( ) {
const out = [
2023-03-27 18:50:32 -04:00
` .modified-emote[data-effects] > .chat-line__message--emote {
2023-03-03 15:24:20 -05:00
-- ffz - effect - filters : none ;
-- ffz - effect - transforms : initial ;
-- ffz - effect - animations : initial ;
2023-03-06 17:08:47 -05:00
} `
2023-03-03 15:24:20 -05:00
] ;
2023-03-08 01:52:35 -05:00
//for(const [key, val] of Object.entries(MODIFIER_FLAG_CSS)) {
for ( const val of EFFECT _STYLES ) {
2023-03-03 15:24:20 -05:00
if ( val . raw )
out . push ( val . raw ) ;
}
return out . join ( '\n' ) ;
}
2017-11-13 01:23:39 -05:00
const MODIFIERS = {
59847 : {
modifier _offset : '0 15px 15px 0' ,
modifier : true
} ,
70852 : {
modifier : true ,
modifier _offset : '0 5px 20px 0' ,
extra _width : 5 ,
shrink _to _fit : true
} ,
70854 : {
modifier : true ,
modifier _offset : '30px 0 0'
} ,
147049 : {
modifier : true ,
modifier _offset : '4px 1px 0 3px'
} ,
147011 : {
modifier : true ,
modifier _offset : '0'
} ,
70864 : {
modifier : true ,
modifier _offset : '0'
} ,
147038 : {
modifier : true ,
modifier _offset : '0'
}
} ;
export default class Emotes extends Module {
constructor ( ... args ) {
super ( ... args ) ;
2019-10-28 14:56:55 -04:00
this . EmoteTypes = EmoteTypes ;
2023-03-03 15:24:20 -05:00
this . ModifierFlags = MODIFIER _FLAGS ;
2019-10-28 14:56:55 -04:00
2017-12-13 20:22:11 -05:00
this . inject ( 'settings' ) ;
2018-04-12 02:29:43 -04:00
this . inject ( 'experiments' ) ;
2023-03-03 15:24:20 -05:00
this . inject ( 'staging' ) ;
2023-03-10 17:06:12 -05:00
this . inject ( 'load_tracker' ) ;
2017-11-13 01:23:39 -05:00
2019-10-28 01:06:02 -04:00
this . twitch _inventory _sets = new Set ; //(EXTRA_INVENTORY);
2019-12-12 18:44:19 -05:00
this . _ _twitch _emote _to _set = { } ;
this . _ _twitch _set _to _channel = { } ;
2023-03-27 18:50:32 -04:00
this . _ _twitch _emote _to _artist = { } ;
2017-11-13 01:23:39 -05:00
2023-03-03 15:24:20 -05:00
// Bulk data structure for collections applied to a lot of users.
// This lets us avoid allocating lots of individual user
// objects when we don't need to do so.
this . bulk = new Map ;
this . effects _enabled = { } ;
this . pending _effects = new Set ( ) ;
this . applyEffects = this . applyEffects . bind ( this ) ;
2023-03-06 17:08:47 -05:00
this . sub _sets = new SourcedSet ;
2017-11-15 21:59:13 -05:00
this . default _sets = new SourcedSet ;
this . global _sets = new SourcedSet ;
2017-11-13 01:23:39 -05:00
2018-04-06 21:12:12 -04:00
this . providers = new Map ;
this . providers . set ( 'featured' , {
name : 'Featured' ,
i18n _key : 'emote-menu.featured' ,
sort _key : 75
} )
2017-11-13 01:23:39 -05:00
this . emote _sets = { } ;
2017-11-22 20:21:01 -05:00
this . _set _refs = { } ;
this . _set _timers = { } ;
2017-12-13 20:22:11 -05:00
2021-04-27 16:23:19 -04:00
this . settings . add ( 'chat.emotes.enabled' , {
default : 2 ,
ui : {
path : 'Chat > Appearance >> Emotes' ,
title : 'Display Emotes' ,
sort : - 100 ,
force _seen : true ,
description : 'If you do not wish to see emotes, you can disable them here.' ,
component : 'setting-select-box' ,
data : [
{ value : 0 , title : 'Disabled' } ,
{ value : 1 , title : 'Twitch Only' } ,
{ value : 2 , title : 'Enabled' }
]
}
} ) ;
2021-02-15 17:48:30 -05:00
this . settings . add ( 'chat.emotes.2x' , {
2022-02-11 15:17:32 -05:00
default : 0 ,
process ( ctx , val ) {
if ( val === true ) return 1 ;
else if ( val === false ) return 0 ;
return val ;
} ,
2021-02-15 17:48:30 -05:00
ui : {
path : 'Chat > Appearance >> Emotes' ,
title : 'Larger Emotes' ,
description : 'This setting will make emotes appear twice as large in chat. It\'s good for use with larger fonts or just if you really like emotes.' ,
2022-02-11 15:17:32 -05:00
component : 'setting-select-box' ,
data : [
{ value : 0 , title : 'Disabled' } ,
{ value : 1 , title : 'Emotes' } ,
{ value : 2 , title : 'Emotes and Emoji' }
]
2021-02-15 17:48:30 -05:00
}
} ) ;
2022-03-05 15:15:27 -05:00
this . settings . add ( 'chat.emotes.limit-size' , {
default : true ,
ui : {
path : 'Chat > Appearance >> Emotes' ,
title : 'Limit Native Emote Size' ,
description : 'Sometimes, really obnoxiously large emotes slip through the cracks and wind up on Twitch. This limits the size of Twitch emotes to mitigate the issue.' ,
component : 'setting-check-box'
}
} ) ;
2017-12-13 20:22:11 -05:00
this . settings . add ( 'chat.fix-bad-emotes' , {
default : true ,
ui : {
path : 'Chat > Appearance >> Emotes' ,
title : 'Fix Bad Twitch Global Emotes' ,
description : 'Clean up the images for bad Twitch global emotes, removing white borders and solid backgrounds.' ,
component : 'setting-check-box'
}
} ) ;
2018-04-09 19:57:05 -04:00
2019-01-18 19:07:57 -05:00
this . settings . add ( 'chat.click-emotes' , {
default : true ,
ui : {
path : 'Chat > Behavior >> General' ,
title : 'Open emote information pages by Shift-Clicking them.' ,
component : 'setting-check-box'
}
} ) ;
this . settings . add ( 'chat.sub-emotes' , {
default : true ,
ui : {
path : 'Chat > Behavior >> General' ,
title : 'Open Twitch subscription pages by Shift-Clicking emotes when relevant.' ,
component : 'setting-check-box'
}
} ) ;
this . settings . add ( 'chat.emote-dialogs' , {
default : true ,
ui : {
path : 'Chat > Behavior >> General' ,
title : 'Open emote information cards for Twitch emotes by clicking them.' ,
component : 'setting-check-box'
}
} ) ;
2018-04-09 19:57:05 -04:00
2023-03-03 15:24:20 -05:00
this . settings . add ( 'chat.effects.enable' , {
default : true ,
ui : {
path : 'Chat > Emote Effects >> General' ,
title : 'Enable the use of emote effects.' ,
description : 'Emote Effects are special effects that can be applied to some emotes using special modifiers.' ,
component : 'setting-check-box'
}
} ) ;
2023-03-08 01:52:35 -05:00
for ( const val of EFFECT _STYLES ) {
if ( ! val . setting || Array . isArray ( val . setting ) )
continue ;
2023-03-03 15:24:20 -05:00
const setting = {
default : val . animation
? null
: true ,
ui : {
path : 'Chat > Emote Effects >> Specific Effect @{"description": "**Note:** Animated effects are, by default, only enabled when [Animated Emotes](~chat.appearance.emotes) are enabled."}' ,
2023-03-08 01:52:35 -05:00
title : ` Enable the effect " ${ val . title ? ? val . setting } ". ` ,
2023-03-03 15:24:20 -05:00
component : 'setting-check-box' ,
force _seen : true
}
} ;
if ( val . animation ) {
setting . default = null ;
setting . requires = [ 'chat.emotes.animated' ] ;
setting . process = function ( ctx , val ) {
if ( val == null )
return ctx . get ( 'chat.emotes.animated' ) === 1 ;
return val ;
} ;
}
2023-03-08 01:52:35 -05:00
this . settings . add ( ` chat.effects. ${ val . setting } ` , setting ) ;
2023-03-03 15:24:20 -05:00
}
2018-04-09 19:57:05 -04:00
// Because this may be used elsewhere.
this . handleClick = this . handleClick . bind ( this ) ;
2021-03-20 18:47:12 -04:00
this . animHover = this . animHover . bind ( this ) ;
this . animLeave = this . animLeave . bind ( this ) ;
2017-11-13 01:23:39 -05:00
}
onEnable ( ) {
this . style = new ManagedStyle ( 'emotes' ) ;
2023-03-03 15:24:20 -05:00
this . effect _style = new ManagedStyle ( 'effects' ) ;
// Generate the base filter CSS.
this . base _effect _css = generateBaseFilterCss ( ) ;
this . parent . context . on ( 'changed:chat.effects.enable' , this . updateEffects , this ) ;
2023-03-08 01:52:35 -05:00
for ( const input of EFFECT _STYLES )
if ( input . setting && ! Array . isArray ( input . setting ) )
this . parent . context . on ( ` changed:chat.effects. ${ input . setting } ` , this . updateEffects , this ) ;
2023-03-03 15:24:20 -05:00
this . updateEffects ( ) ;
2017-11-13 01:23:39 -05:00
2019-12-12 18:44:19 -05:00
// 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 ) ;
2017-11-13 01:23:39 -05:00
if ( Object . keys ( this . emote _sets ) . length ) {
this . log . info ( 'Generating CSS for existing emote sets.' ) ;
for ( const set _id in this . emote _sets )
if ( has ( this . emote _sets , set _id ) ) {
const emote _set = this . emote _sets [ set _id ] ;
2023-01-19 17:00:09 -05:00
if ( emote _set && ( emote _set . pending _css || emote _set . css ) ) {
this . style . set ( ` es-- ${ set _id } ` , ( emote _set . pending _css || '' ) + ( emote _set . css || '' ) ) ;
2017-11-13 01:23:39 -05:00
emote _set . pending _css = null ;
}
}
}
2021-03-02 16:55:25 -05:00
this . on ( 'socket:command:follow_sets' , this . updateFollowSets , this ) ;
2018-04-06 21:12:12 -04:00
2023-04-24 15:09:21 -04:00
this . on ( 'chat:reload-data' , flags => {
if ( ! flags || flags . emotes )
this . loadGlobalSets ( ) ;
} ) ;
2017-11-16 15:54:58 -05:00
this . loadGlobalSets ( ) ;
2017-11-13 01:23:39 -05:00
}
2023-03-03 15:24:20 -05:00
// ========================================================================
// Load Modifier Effects
// ========================================================================
ensureEffect ( flags ) {
if ( ! this . effect _style . has ( ` ${ flags } ` ) ) {
this . pending _effects . add ( flags ) ;
if ( ! this . _effect _timer )
this . _effect _timer = requestAnimationFrame ( this . applyEffects ) ;
}
}
applyEffects ( ) {
this . _effect _timer = null ;
const effects = this . pending _effects ;
this . pending _effects = new Set ;
for ( const flags of effects ) {
const result = this . generateFilterCss ( flags ) ;
this . effect _style . set ( ` ${ flags } ` , result ? ? '' ) ;
}
}
generateFilterCss ( flags ) {
if ( ! this . parent . context . get ( 'chat.effects.enable' ) )
return null ;
2023-03-06 17:08:47 -05:00
let filter , transformOrigin , transform , animation , animations = [ ] ;
2023-03-03 15:24:20 -05:00
2023-03-08 01:52:35 -05:00
for ( const input of this . activeEffectStyles ) {
if ( ( flags & input . flags ) !== input . flags )
2023-03-03 15:24:20 -05:00
continue ;
2023-03-27 18:50:32 -04:00
if ( input . not _flags && ( flags & input . not _flags ) === input . not _flags )
continue ;
2023-03-03 15:24:20 -05:00
if ( input . animation )
animations . push ( input ) ;
if ( input . filter )
filter = filter
? ` ${ filter } ${ input . filter } `
: input . filter ;
2023-03-06 17:08:47 -05:00
if ( input . transformOrigin )
transformOrigin = input . transformOrigin ;
2023-03-03 15:24:20 -05:00
if ( input . transform )
transform = transform
? ` ${ transform } ${ input . transform } `
: input . transform ;
}
if ( animations . length )
for ( const input of animations ) {
if ( filter && input . animationFilter )
animation = animation
? ` ${ animation } , ${ input . animationFilter } `
: input . animationFilter ;
else if ( transform && input . animationTransform )
animation = animation
? ` ${ animation } , ${ input . animationTransform } `
: input . animationTransform ;
else
animation = animation
? ` ${ animation } , ${ input . animation } `
: input . animation ;
}
if ( ! filter && ! transform && ! animation )
return null ;
2023-03-27 18:50:32 -04:00
return ` .modified-emote[data-effects=" ${ flags } "] > .chat-line__message--emote { ${ filter ? `
2023-03-03 15:24:20 -05:00
-- ffz - effect - filters : $ { filter } ;
2023-03-06 17:08:47 -05:00
filter : var ( -- ffz - effect - filters ) ; ` : ''} ${ transformOrigin ? `
transform - origin : $ { transformOrigin } ; ` : ''} ${ transform ? `
2023-03-03 15:24:20 -05:00
-- ffz - effect - transforms : $ { transform } ;
transform : var ( -- ffz - effect - transforms ) ; ` : ''} ${ animation ? `
-- ffz - effect - animations : $ { animation } ;
animation : var ( -- ffz - effect - animations ) ; ` : ''}
} ` ;
}
updateEffects ( ) {
// TODO: Smarter logic so it does less work.
const enabled = this . parent . context . get ( 'chat.effects.enable' ) ;
this . effects _enabled = { } ;
2023-03-08 01:52:35 -05:00
this . activeEffectStyles = [ ] ;
2023-03-27 18:50:32 -04:00
this . activeAsBackgroundMask = 0 ;
this . activeNoWideMask = 0 ;
2023-03-08 01:52:35 -05:00
for ( const input of EFFECT _STYLES ) {
if ( input . setting && ! Array . isArray ( input . setting ) )
this . effects _enabled [ input . setting ] = this . parent . context . get ( ` chat.effects. ${ input . setting } ` ) ;
}
for ( const input of EFFECT _STYLES ) {
let enabled = true ;
if ( Array . isArray ( input . setting ) ) {
for ( const setting of input . setting )
if ( ! this . effects _enabled [ setting ] ) {
enabled = false ;
break ;
}
} else if ( input . setting )
enabled = this . effects _enabled [ input . setting ] ;
2023-03-27 18:50:32 -04:00
if ( enabled ) {
2023-03-08 01:52:35 -05:00
this . activeEffectStyles . push ( input ) ;
2023-03-27 18:50:32 -04:00
if ( input . as _background )
this . activeAsBackgroundMask = this . activeAsBackgroundMask | input . flags ;
if ( input . no _wide )
this . activeNoWideMask = this . activeNoWideMask | input . flags ;
}
2023-03-08 01:52:35 -05:00
}
2023-03-03 15:24:20 -05:00
this . effect _style . clear ( ) ;
2023-03-08 01:52:35 -05:00
if ( ! enabled || ! this . activeEffectStyles . length )
2023-03-03 15:24:20 -05:00
return ;
this . effect _style . set ( 'base' , this . base _effect _css ) ;
this . emit ( ':update-effects' ) ;
}
2018-04-06 21:12:12 -04:00
// ========================================================================
// Featured Sets
// ========================================================================
updateFollowSets ( data ) {
for ( const room _login in data )
if ( has ( data , room _login ) ) {
2018-04-13 13:36:05 -04:00
const room = this . parent . getRoom ( null , room _login , true ) ;
if ( ! room || room . destroyed )
continue ;
const new _sets = data [ room _login ] || [ ] ,
2018-04-06 21:12:12 -04:00
emote _sets = room . emote _sets ,
2018-04-13 13:36:05 -04:00
providers = emote _sets && emote _sets . _sources ;
2018-04-06 21:12:12 -04:00
if ( providers && providers . has ( 'featured' ) )
2018-04-12 02:29:43 -04:00
for ( const item of providers . get ( 'featured' ) ) {
const idx = new _sets . indexOf ( item ) ;
if ( idx === - 1 )
2018-04-06 21:12:12 -04:00
room . removeSet ( 'featured' , item ) ;
2018-04-12 02:29:43 -04:00
else
new _sets . splice ( idx , 1 ) ;
}
2018-04-06 21:12:12 -04:00
for ( const set _id of new _sets ) {
room . addSet ( 'featured' , set _id ) ;
if ( ! this . emote _sets [ set _id ] )
this . loadSet ( set _id ) ;
}
}
}
2020-06-23 17:17:00 -04:00
// ========================================================================
// Hidden Checking
// ========================================================================
toggleHidden ( source , id , value = null ) {
const key = ` hidden-emotes. ${ source } ` ,
p = this . settings . provider ,
hidden = p . get ( key , [ ] ) ,
idx = hidden . indexOf ( id ) ;
if ( value === null )
value = idx === - 1 ;
if ( value && idx === - 1 )
hidden . push ( id ) ;
else if ( ! value && idx !== - 1 )
hidden . splice ( idx , 1 ) ;
else
return ;
if ( hidden . length )
p . set ( key , hidden ) ;
else
p . delete ( key ) ;
this . emit ( ':change-hidden' , source , id , value ) ;
}
isHidden ( source , id ) {
return this . getHidden ( source ) . includes ( id ) ;
}
getHidden ( source ) {
2020-11-15 17:33:55 -05:00
return this . settings . provider . get ( ` hidden-emotes. ${ source } ` ) || [ ] ;
2020-06-23 17:17:00 -04:00
}
setHidden ( source , list ) {
const key = ` hidden-emotes. ${ source } ` ;
if ( ! Array . isArray ( list ) || ! list . length )
this . settings . provider . delete ( key ) ;
else
this . settings . provider . set ( key , list ) ;
}
2021-03-20 18:47:12 -04:00
// ========================================================================
// Animation Hover
// ========================================================================
animHover ( event ) { // eslint-disable-line class-methods-use-this
const target = event . currentTarget ;
if ( target [ HoverState ] )
return ;
if ( target [ HoverRAF ] )
cancelAnimationFrame ( target [ HoverRAF ] ) ;
target [ HoverRAF ] = requestAnimationFrame ( ( ) => {
target [ HoverRAF ] = null ;
if ( target [ HoverState ] )
return ;
if ( ! target . matches ( ':hover' ) )
return ;
target [ HoverState ] = true ;
const emotes = target . querySelectorAll ( '.ffz-hover-emote' ) ;
for ( const em of emotes ) {
const ds = em . dataset ;
if ( ds . normalSrc && ds . hoverSrc ) {
em . src = ds . hoverSrc ;
em . srcset = ds . hoverSrcSet ;
}
}
} ) ;
}
animLeave ( event ) { // eslint-disable-line class-methods-use-this
const target = event . currentTarget ;
if ( ! target [ HoverState ] )
return ;
if ( target [ HoverRAF ] )
cancelAnimationFrame ( target [ HoverRAF ] ) ;
target [ HoverRAF ] = requestAnimationFrame ( ( ) => {
target [ HoverRAF ] = null ;
if ( ! target [ HoverState ] )
return ;
if ( target . matches ( ':hover' ) )
return ;
target [ HoverState ] = false ;
const emotes = target . querySelectorAll ( '.ffz-hover-emote' ) ;
for ( const em of emotes ) {
const ds = em . dataset ;
if ( ds . normalSrc ) {
em . src = ds . normalSrc ;
em . srcset = ds . normalSrcSet ;
}
}
} ) ;
}
2018-04-09 19:57:05 -04:00
// ========================================================================
// Favorite Checking
// ========================================================================
toggleFavorite ( source , id , value = null ) {
const key = ` favorite-emotes. ${ source } ` ,
p = this . settings . provider ,
favorites = p . get ( key ) || [ ] ,
idx = favorites . indexOf ( id ) ;
if ( value === null )
value = idx === - 1 ;
if ( value && idx === - 1 )
favorites . push ( id ) ;
else if ( ! value && idx !== - 1 )
favorites . splice ( idx , 1 ) ;
else
2023-03-30 14:54:33 -04:00
return value ;
2018-04-09 19:57:05 -04:00
if ( favorites . length )
p . set ( key , favorites ) ;
else
p . delete ( key ) ;
this . emit ( ':change-favorite' , source , id , value ) ;
2023-03-30 14:54:33 -04:00
return value ;
2018-04-09 19:57:05 -04:00
}
isFavorite ( source , id ) {
const favorites = this . settings . provider . get ( ` favorite-emotes. ${ source } ` ) ;
return favorites && favorites . includes ( id ) ;
}
getFavorites ( source ) {
return this . settings . provider . get ( ` favorite-emotes. ${ source } ` ) || [ ] ;
}
2019-12-12 18:44:19 -05:00
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 ) ;
}
2023-03-27 18:50:32 -04:00
handleClick ( event , favorite _only = false ) {
2018-04-09 19:57:05 -04:00
const target = event . target ,
ds = target && target . dataset ;
2023-03-27 18:50:32 -04:00
/ * c o n s t m o d i f i e d = t a r g e t . c l o s e s t ( ' . m o d i f i e d - e m o t e ' ) ;
if ( modified && modified !== target )
return ; * /
2018-04-09 19:57:05 -04:00
if ( ! ds )
return ;
2019-01-15 16:14:21 -05:00
const provider = ds . provider ,
click _emote = this . parent . context . get ( 'chat.click-emotes' ) ,
click _sub = this . parent . context . get ( 'chat.sub-emotes' ) ;
2018-04-09 19:57:05 -04:00
2019-01-15 16:14:21 -05:00
if ( event . shiftKey && ( click _emote || click _sub ) ) {
2018-04-11 17:05:31 -04:00
let url ;
2019-01-15 16:14:21 -05:00
if ( provider === 'twitch' ) {
2023-03-06 17:08:47 -05:00
url = null ; // = `https://twitchemotes.com/emotes/${ds.id}`;
2018-04-11 17:05:31 -04:00
2019-01-15 16:14:21 -05:00
if ( click _sub ) {
const apollo = this . resolve ( 'site.apollo' ) ;
if ( apollo ) {
apollo . client . query ( {
query : GET _EMOTE ,
variables : {
id : ds . id
}
} ) . then ( result => {
const prod = get ( 'data.emote.subscriptionProduct' , result ) ;
if ( prod && prod . state === 'ACTIVE' && prod . owner && prod . owner . login )
url = ` https://www.twitch.tv/subs/ ${ prod . owner . login } ` ;
else if ( ! click _emote )
return false ;
if ( url ) {
const win = window . open ( ) ;
if ( win ) {
win . opener = null ;
win . location = url ;
}
}
} ) ;
return true ;
}
}
} else if ( provider === 'ffz' ) {
2018-04-11 17:05:31 -04:00
const emote _set = this . emote _sets [ ds . set ] ,
emote = emote _set && emote _set . emotes [ ds . id ] ;
if ( ! emote )
return ;
if ( emote . click _url )
url = emote . click _url ;
else if ( ! emote _set . source )
url = ` https://www.frankerfacez.com/emoticons/ ${ emote . id } ` ;
}
2019-01-15 16:14:21 -05:00
if ( ! click _emote )
return false ;
2018-04-11 17:05:31 -04:00
if ( url ) {
const win = window . open ( ) ;
2018-07-26 19:40:53 -04:00
if ( win ) {
win . opener = null ;
win . location = url ;
}
2018-04-11 17:05:31 -04:00
}
return true ;
}
2018-04-09 19:57:05 -04:00
if ( event [ MOD _KEY ] ) {
// Favoriting Emotes
let source , id ;
if ( provider === 'twitch' ) {
source = 'twitch' ;
2019-12-12 18:44:19 -05:00
id = ds . id ;
2018-04-09 19:57:05 -04:00
} else if ( provider === 'ffz' ) {
const emote _set = this . emote _sets [ ds . set ] ,
emote = emote _set && emote _set . emotes [ ds . id ] ;
if ( ! emote )
return ;
source = emote _set . source || 'ffz' ;
id = emote . id ;
2018-04-12 20:30:00 -04:00
} else if ( provider === 'emoji' ) {
source = 'emoji' ;
id = ds . code ;
2018-04-09 19:57:05 -04:00
} else
return ;
this . toggleFavorite ( source , id ) ;
2020-07-18 15:44:02 -04:00
const tt = target . _ffz _tooltip ;
2018-04-09 19:57:05 -04:00
if ( tt && tt . visible ) {
tt . hide ( ) ;
setTimeout ( ( ) => document . contains ( target ) && tt . show ( ) , 0 ) ;
}
return true ;
}
2019-01-18 19:07:57 -05:00
2023-03-27 18:50:32 -04:00
if ( favorite _only )
return false ;
2023-03-30 14:54:33 -04:00
let modifiers ;
try {
modifiers = JSON . parse ( ds . modifierInfo ) ;
} catch ( err ) {
/* no-op */
}
2023-03-06 17:08:47 -05:00
const evt = new FFZEvent ( {
provider ,
id : ds . id ,
2023-03-27 18:50:32 -04:00
set : ds . set ,
2023-03-30 14:54:33 -04:00
code : ds . code ,
variant : ds . variant ,
2023-03-27 18:50:32 -04:00
name : ds . name || target . alt ,
2023-03-30 14:54:33 -04:00
modifiers ,
2023-03-06 17:08:47 -05:00
source : event
} ) ;
this . emit ( 'chat.emotes:click' , evt ) ;
if ( evt . defaultPrevented )
return true ;
2019-01-18 19:07:57 -05:00
if ( provider === 'twitch' && this . parent . context . get ( 'chat.emote-dialogs' ) ) {
const fine = this . resolve ( 'site.fine' ) ;
if ( ! fine )
return ;
2020-08-07 01:32:18 -04:00
const line = fine . searchParent ( target , n => n . props && n . props . message ) ,
2023-03-03 15:24:20 -05:00
opener = fine . searchParent ( target , n => n . onShowEmoteCard , 500 ) ;
2020-08-07 01:32:18 -04:00
if ( ! line || ! opener )
2019-01-18 19:07:57 -05:00
return ;
2021-12-16 15:00:14 -05:00
const rect = target . getBoundingClientRect ( ) ;
2020-08-07 01:32:18 -04:00
opener . onShowEmoteCard ( {
channelID : line . props . channelID || '' ,
channelLogin : line . props . channelLogin || '' ,
2019-01-18 19:07:57 -05:00
emoteID : ds . id ,
emoteCode : target . alt ,
sourceID : 'chat' ,
referrerID : '' ,
2021-12-16 15:00:14 -05:00
initialTopOffset : rect . bottom ,
initialBottomOffset : rect . top
2019-01-18 19:07:57 -05:00
} ) ;
return true ;
}
2018-04-09 19:57:05 -04:00
}
2017-11-13 01:23:39 -05:00
// ========================================================================
// Access
// ========================================================================
2023-03-06 17:08:47 -05:00
getTargetEmote ( ) {
2023-03-08 01:52:35 -05:00
this . target _emote = null ;
2023-03-06 17:08:47 -05:00
const me = this . resolve ( 'site' ) . getUser ( ) ,
Input = me ? this . resolve ( 'site.chat.input' ) : null ,
entered = Input ? Input . getInput ( ) : null ;
const menu = this . resolve ( 'site.chat.emote_menu' ) ? . MenuWrapper ? . first ,
emote _sets = menu ? . getAllSets ? . ( ) ,
emotes = emote _sets
2023-03-08 01:52:35 -05:00
? emote _sets . map ( x => x . emotes ) . flat ( ) . filter ( x => ! x . effects && ! x . locked )
2023-03-06 17:08:47 -05:00
: null ;
if ( entered && emotes ) {
// Okay this is gonna be oof.
const name _map = { } ;
for ( let i = 0 ; i < emotes . length ; i ++ )
if ( ! name _map [ emotes [ i ] . name ] )
name _map [ emotes [ i ] . name ] = i ;
const words = entered . split ( ' ' ) ;
let i = words . length ;
while ( i -- ) {
const word = words [ i ] ;
if ( name _map [ word ] != null )
return emotes [ name _map [ word ] ] ;
}
}
// Random emote
if ( emotes && emotes . length ) {
const idx = Math . floor ( Math . random ( ) * emotes . length ) ,
emote = emotes [ idx ] ;
2023-03-08 01:52:35 -05:00
this . target _emote = emote ;
2023-03-06 17:08:47 -05:00
return emote ;
}
// Return LaterSooner
2023-03-08 01:52:35 -05:00
return this . target _emote = {
2023-03-06 17:08:47 -05:00
provider : 'ffz' ,
set _id : 3 ,
id : 149346 ,
name : 'LaterSooner' ,
src : 'https://cdn.frankerfacez.com/emote/149346/1' ,
srcSet : 'https://cdn.frankerfacez.com/emote/149346/1 1x, https://cdn.frankerfacez.com/emote/149346/2 2x, https://cdn.frankerfacez.com/emote/149346/4 4x' ,
width : 25 ,
height : 32
}
}
2017-11-13 01:23:39 -05:00
getSetIDs ( user _id , user _login , room _id , room _login ) {
const room = this . parent . getRoom ( room _id , room _login , true ) ,
2017-12-13 17:35:20 -05:00
room _user = room && room . getUser ( user _id , user _login , true ) ,
2017-11-13 01:23:39 -05:00
user = this . parent . getUser ( user _id , user _login , true ) ;
2023-03-03 15:24:20 -05:00
const out = ( user ? . emote _sets ? user . emote _sets . _cache : [ ] ) . concat (
2021-03-22 18:19:09 -04:00
room _user ? . emote _sets ? room _user . emote _sets . _cache : [ ] ,
room ? . emote _sets ? room . emote _sets . _cache : [ ] ,
2017-11-15 21:59:13 -05:00
this . default _sets . _cache
2017-11-13 01:23:39 -05:00
) ;
2023-03-03 15:24:20 -05:00
if ( this . bulk . size ) {
const str _user = String ( user _id ) ;
for ( const [ set _id , users ] of this . bulk ) {
if ( users ? . _cache . has ( str _user ) )
out . push ( set _id ) ;
}
}
return out ;
2017-11-13 01:23:39 -05:00
}
getSets ( user _id , user _login , room _id , room _login ) {
return this . getSetIDs ( user _id , user _login , room _id , room _login )
. map ( set _id => this . emote _sets [ set _id ] ) ;
}
2018-04-06 21:12:12 -04:00
_withSources ( out , seen , emote _sets ) { // eslint-disable-line class-methods-use-this
2021-03-22 18:19:09 -04:00
if ( ! emote _sets ? . _sources )
2018-04-06 21:12:12 -04:00
return ;
for ( const [ provider , data ] of emote _sets . _sources )
for ( const item of data )
if ( ! seen . has ( item ) ) {
out . push ( [ item , provider ] ) ;
seen . add ( item ) ;
}
return out ;
}
getRoomSetIDsWithSources ( user _id , user _login , room _id , room _login ) {
const room = this . parent . getRoom ( room _id , room _login , true ) ,
room _user = room && room . getUser ( user _id , user _login , true ) ;
if ( ! room )
return [ ] ;
const out = [ ] , seen = new Set ;
this . _withSources ( out , seen , room . emote _sets ) ;
if ( room _user )
this . _withSources ( out , seen , room _user ) ;
return out ;
}
getRoomSetsWithSources ( user _id , user _login , room _id , room _login ) {
return this . getRoomSetIDsWithSources ( user _id , user _login , room _id , room _login )
. map ( ( [ set _id , source ] ) => [ this . emote _sets [ set _id ] , source ] ) ;
}
2017-12-13 17:35:20 -05:00
getRoomSetIDs ( user _id , user _login , room _id , room _login ) {
const room = this . parent . getRoom ( room _id , room _login , true ) ,
room _user = room && room . getUser ( user _id , user _login , true ) ;
if ( ! room )
return [ ] ;
2021-03-22 18:19:09 -04:00
if ( ! room _user ? . emote _sets )
return room . emote _sets ? room . emote _sets . _cache : [ ] ;
else if ( ! room . emote _sets )
return room _user . emote _sets . _cache ;
2017-12-13 17:35:20 -05:00
return room _user . emote _sets . _cache . concat ( room . emote _sets . _cache ) ;
}
getRoomSets ( user _id , user _login , room _id , room _login ) {
return this . getRoomSetIDs ( user _id , user _login , room _id , room _login )
. map ( set _id => this . emote _sets [ set _id ] ) ;
}
2018-04-06 21:12:12 -04:00
getGlobalSetIDsWithSources ( user _id , user _login ) {
const user = this . parent . getUser ( user _id , user _login , true ) ,
out = [ ] , seen = new Set ;
this . _withSources ( out , seen , this . default _sets ) ;
if ( user )
this . _withSources ( out , seen , user . emote _sets ) ;
2023-03-03 15:24:20 -05:00
if ( this . bulk . size ) {
const str _user = String ( user _id ) ;
for ( const [ set _id , users ] of this . bulk ) {
if ( ! seen . has ( set _id ) && users ? . _cache . has ( str _user ) ) {
for ( const [ provider , data ] of users . _sources ) {
if ( data && data . includes ( str _user ) ) {
out . push ( [ set _id , provider ] ) ;
break ;
}
}
}
}
}
2018-04-06 21:12:12 -04:00
return out ;
}
getGlobalSetsWithSources ( user _id , user _login ) {
return this . getGlobalSetIDsWithSources ( user _id , user _login )
. map ( ( [ set _id , source ] ) => [ this . emote _sets [ set _id ] , source ] ) ;
}
2023-03-06 17:08:47 -05:00
getSubSetIDsWithSources ( ) {
const out = [ ] , seen = new Set ;
this . _withSources ( out , seen , this . sub _sets ) ;
return out ;
}
getSubSetsWithSources ( ) {
return this . getSubSetIDsWithSources ( )
. map ( ( [ set _id , source ] ) => [ this . emote _sets [ set _id ] , source ] ) ;
}
2017-12-13 17:35:20 -05:00
getGlobalSetIDs ( user _id , user _login ) {
const user = this . parent . getUser ( user _id , user _login , true ) ;
2023-03-03 15:24:20 -05:00
const out = ( user ? . emote _sets ? user . emote _sets . _cache : [ ] ) . concat (
this . default _sets . _cache
) ;
if ( this . bulk . size ) {
const str _user = String ( user _id ) ;
for ( const [ set _id , users ] of this . bulk ) {
if ( users ? . _cache . has ( str _user ) )
out . push ( set _id ) ;
}
}
return out ;
2017-12-13 17:35:20 -05:00
}
getGlobalSets ( user _id , user _login ) {
return this . getGlobalSetIDs ( user _id , user _login )
. map ( set _id => this . emote _sets [ set _id ] ) ;
}
2017-11-22 15:39:38 -05:00
getEmotes ( user _id , user _login , room _id , room _login ) {
const emotes = { } ;
for ( const emote _set of this . getSets ( user _id , user _login , room _id , room _login ) )
if ( emote _set && emote _set . emotes )
for ( const emote of Object . values ( emote _set . emotes ) )
if ( emote && ! has ( emotes , emote . name ) )
emotes [ emote . name ] = emote ;
return emotes ;
}
2023-03-03 15:24:20 -05:00
// ========================================================================
// Bulk Management
// ========================================================================
setBulk ( source , set _id , entries ) {
let set = this . bulk . get ( set _id ) ;
if ( ! set )
this . bulk . set ( set _id , set = new SourcedSet ( true ) ) ;
const size = set . _cache . size ;
set . set ( source , entries ) ;
const new _size = set . _cache . size ;
if ( ! size && new _size )
this . refSet ( set _id ) ;
}
deleteBulk ( source , set _id ) {
const set = this . bulk . get ( set _id ) ;
if ( ! set )
return ;
const size = set . _cache . size ;
set . delete ( source ) ;
const new _size = set . _cache . size ;
if ( size && ! new _size )
this . unrefSet ( set _id ) ;
}
extendBulk ( source , set _id , entries ) {
let set = this . bulk . get ( set _id ) ;
if ( ! set )
this . bulk . set ( set _id , set = new SourcedSet ( true ) ) ;
if ( ! Array . isArray ( entries ) )
entries = [ entries ] ;
const size = set . _cache . size ;
set . extend ( source , ... entries ) ;
const new _size = set . _cache . size ;
if ( ! size && new _size )
this . refSet ( set _id ) ;
}
2017-11-13 01:23:39 -05:00
// ========================================================================
2017-11-22 20:21:01 -05:00
// Emote Set Ref Counting
// ========================================================================
addDefaultSet ( provider , set _id , data ) {
2021-06-10 18:53:16 -04:00
if ( typeof set _id === 'number' )
set _id = ` ${ set _id } ` ;
let changed = false , added = false ;
2017-11-22 20:21:01 -05:00
if ( ! this . default _sets . sourceIncludes ( provider , set _id ) ) {
2021-06-10 18:53:16 -04:00
changed = ! this . default _sets . includes ( set _id ) ;
2017-11-22 20:21:01 -05:00
this . default _sets . push ( provider , set _id ) ;
2021-06-10 18:53:16 -04:00
added = true ;
2017-11-22 20:21:01 -05:00
}
if ( data )
this . loadSetData ( set _id , data ) ;
2018-04-01 18:24:08 -04:00
2021-06-10 18:53:16 -04:00
if ( changed ) {
this . refSet ( set _id ) ;
2018-04-06 21:12:12 -04:00
this . emit ( ':update-default-sets' , provider , set _id , true ) ;
2021-06-10 18:53:16 -04:00
}
return added ;
2017-11-22 20:21:01 -05:00
}
removeDefaultSet ( provider , set _id ) {
2023-04-24 15:09:21 -04:00
if ( ! set _id ) {
const sets = this . default _sets . get ( provider ) ;
if ( sets )
for ( const set _id of Array . from ( sets ) )
this . removeDefaultSet ( provider , set _id ) ;
return ;
}
2021-06-10 18:53:16 -04:00
if ( typeof set _id === 'number' )
set _id = ` ${ set _id } ` ;
2017-11-22 20:21:01 -05:00
if ( this . default _sets . sourceIncludes ( provider , set _id ) ) {
this . default _sets . remove ( provider , set _id ) ;
2021-06-10 18:53:16 -04:00
if ( ! this . default _sets . includes ( set _id ) ) {
this . unrefSet ( set _id ) ;
this . emit ( ':update-default-sets' , provider , set _id , false ) ;
}
return true ;
2017-11-22 20:21:01 -05:00
}
2018-04-01 18:24:08 -04:00
2021-06-10 18:53:16 -04:00
return false ;
2017-11-22 20:21:01 -05:00
}
2023-03-06 17:08:47 -05:00
addSubSet ( provider , set _id , data ) {
if ( typeof set _id === 'number' )
set _id = ` ${ set _id } ` ;
let changed = false , added = false ;
if ( ! this . sub _sets . sourceIncludes ( provider , set _id ) ) {
changed = ! this . sub _sets . includes ( set _id ) ;
this . sub _sets . push ( provider , set _id ) ;
added = true ;
}
if ( data )
this . loadSetData ( set _id , data ) ;
if ( changed ) {
this . refSet ( set _id ) ;
this . emit ( ':update-sub-sets' , provider , set _id , true ) ;
}
return added ;
}
removeSubSet ( provider , set _id ) {
if ( typeof set _id === 'number' )
set _id = ` ${ set _id } ` ;
if ( this . sub _sets . sourceIncludes ( provider , set _id ) ) {
this . sub _sets . remove ( provider , set _id ) ;
if ( ! this . sub _sets . includes ( set _id ) ) {
this . unrefSet ( set _id ) ;
this . emit ( ':update-sub-sets' , provider , set _id , false ) ;
}
return true ;
}
return false ;
}
2017-11-22 20:21:01 -05:00
refSet ( set _id ) {
this . _set _refs [ set _id ] = ( this . _set _refs [ set _id ] || 0 ) + 1 ;
if ( this . _set _timers [ set _id ] ) {
clearTimeout ( this . _set _timers [ set _id ] ) ;
this . _set _timers [ set _id ] = null ;
}
}
unrefSet ( set _id ) {
const c = this . _set _refs [ set _id ] = ( this . _set _refs [ set _id ] || 1 ) - 1 ;
if ( c <= 0 && ! this . _set _timers [ set _id ] )
this . _set _timers [ set _id ] = setTimeout ( ( ) => this . unloadSet ( set _id ) , 5000 ) ;
}
// ========================================================================
// Emote Set Loading
2017-11-13 01:23:39 -05:00
// ========================================================================
2017-11-16 15:54:58 -05:00
async loadGlobalSets ( tries = 0 ) {
2023-03-10 17:06:12 -05:00
this . load _tracker . schedule ( 'chat-data' , 'ffz-global' ) ;
2017-11-13 01:23:39 -05:00
let response , data ;
2018-04-12 02:29:43 -04:00
2020-03-05 18:21:11 -05:00
if ( this . experiments . getAssignment ( 'api_load' ) && tries < 1 )
2018-04-12 02:29:43 -04:00
try {
fetch ( ` ${ NEW _API } /v1/set/global ` ) . catch ( ( ) => { } ) ;
} catch ( err ) { /* do nothing */ }
2017-11-13 01:23:39 -05:00
try {
2023-03-06 17:08:47 -05:00
response = await fetch ( ` ${ this . staging . api } /v1/set/global/ids ` )
2017-11-13 01:23:39 -05:00
} catch ( err ) {
tries ++ ;
if ( tries < 10 )
2017-11-16 15:54:58 -05:00
return setTimeout ( ( ) => this . loadGlobalSets ( tries ) , 500 * tries ) ;
2017-11-13 01:23:39 -05:00
this . log . error ( 'Error loading global emote sets.' , err ) ;
2023-03-10 17:06:12 -05:00
this . load _tracker . notify ( 'chat-data' , 'ffz-global' , false ) ;
2017-11-13 01:23:39 -05:00
return false ;
}
2023-03-10 17:06:12 -05:00
if ( ! response . ok ) {
this . load _tracker . notify ( 'chat-data' , 'ffz-global' , false ) ;
2017-11-13 01:23:39 -05:00
return false ;
2023-03-10 17:06:12 -05:00
}
2017-11-13 01:23:39 -05:00
try {
data = await response . json ( ) ;
} catch ( err ) {
this . log . error ( 'Error parsing global emote data.' , err ) ;
2023-03-10 17:06:12 -05:00
this . load _tracker . notify ( 'chat-data' , 'ffz-global' , false ) ;
2017-11-13 01:23:39 -05:00
return false ;
}
const sets = data . sets || { } ;
2023-04-24 15:09:21 -04:00
// Remove existing global sets, in case we have any.
this . removeDefaultSet ( 'ffz-global' ) ;
2017-11-22 20:21:01 -05:00
for ( const set _id of data . default _sets )
this . addDefaultSet ( 'ffz-global' , set _id ) ;
2017-11-13 01:23:39 -05:00
for ( const set _id in sets )
2023-03-06 17:08:47 -05:00
if ( has ( sets , set _id ) ) {
const id = sets [ set _id ] ? . id ;
2017-11-16 15:54:58 -05:00
this . loadSetData ( set _id , sets [ set _id ] ) ;
2023-03-06 17:08:47 -05:00
if ( id && ! data . default _sets . includes ( id ) )
this . addSubSet ( 'ffz-global' , set _id ) ;
}
2017-11-13 01:23:39 -05:00
2023-03-03 15:24:20 -05:00
if ( data . user _ids )
this . loadSetUserIds ( data . user _ids ) ;
else if ( data . users )
2017-11-16 15:54:58 -05:00
this . loadSetUsers ( data . users ) ;
2018-04-12 20:30:00 -04:00
2023-03-10 17:06:12 -05:00
this . load _tracker . notify ( 'chat-data' , 'ffz-global' ) ;
2018-04-12 20:30:00 -04:00
return true ;
2017-11-13 01:23:39 -05:00
}
2018-04-06 21:12:12 -04:00
async loadSet ( set _id , suppress _log = false , tries = 0 ) {
2023-03-10 17:06:12 -05:00
const load _key = ` ffz- ${ set _id } ` ;
this . load _tracker . schedule ( 'chat-data' , load _key ) ;
2018-04-06 21:12:12 -04:00
let response , data ;
2018-04-12 02:29:43 -04:00
if ( this . experiments . getAssignment ( 'api_load' ) )
try {
fetch ( ` ${ NEW _API } /v1/set/ ${ set _id } ` ) . catch ( ( ) => { } ) ;
} catch ( err ) { /* do nothing */ }
2018-04-06 21:12:12 -04:00
try {
2023-03-03 15:24:20 -05:00
response = await fetch ( ` ${ this . staging . api } /v1/set/ ${ set _id } ${ this . staging . active ? '/ids' : '' } ` )
2018-04-06 21:12:12 -04:00
} catch ( err ) {
tries ++ ;
if ( tries < 10 )
return setTimeout ( ( ) => this . loadGlobalSets ( tries ) , 500 * tries ) ;
this . log . error ( ` Error loading data for set " ${ set _id } ". ` , err ) ;
2023-03-10 17:06:12 -05:00
this . load _tracker . notify ( 'chat-data' , load _key , false ) ;
2018-04-06 21:12:12 -04:00
return false ;
}
2023-03-10 17:06:12 -05:00
if ( ! response . ok ) {
this . load _tracker . notify ( 'chat-data' , load _key , false ) ;
2018-04-06 21:12:12 -04:00
return false ;
2023-03-10 17:06:12 -05:00
}
2018-04-06 21:12:12 -04:00
try {
data = await response . json ( ) ;
} catch ( err ) {
this . log . error ( ` Error parsing data for set " ${ set _id } ". ` , err ) ;
2023-03-10 17:06:12 -05:00
this . load _tracker . notify ( 'chat-data' , load _key , false ) ;
2018-04-06 21:12:12 -04:00
return false ;
}
const set = data . set ;
if ( set )
this . loadSetData ( set . id , set , suppress _log ) ;
2023-03-03 15:24:20 -05:00
if ( data . user _ids )
this . loadSetUserIds ( data . user _ids ) ;
else if ( data . users )
2018-04-06 21:12:12 -04:00
this . loadSetUsers ( data . users ) ;
2023-03-10 17:06:12 -05:00
this . load _tracker . notify ( 'chat-data' , load _key , true ) ;
2018-04-06 21:12:12 -04:00
return true ;
}
2023-03-03 15:24:20 -05:00
loadSetUserIds ( data , suppress _log = false ) {
for ( const set _id in data )
if ( has ( data , set _id ) ) {
const emote _set = this . emote _sets [ set _id ] ,
users = data [ set _id ] ;
this . setBulk ( 'ffz-global' , set _id , users . map ( x => String ( x ) ) ) ;
if ( ! suppress _log )
this . log . info ( ` Added " ${ emote _set ? emote _set . title : set _id } " emote set to ${ users . length } users. ` ) ;
}
}
2018-04-06 21:12:12 -04:00
loadSetUsers ( data , suppress _log = false ) {
2017-11-13 01:23:39 -05:00
for ( const set _id in data )
if ( has ( data , set _id ) ) {
const emote _set = this . emote _sets [ set _id ] ,
users = data [ set _id ] ;
2017-11-22 20:21:01 -05:00
for ( const login of users )
this . parent . getUser ( undefined , login )
. addSet ( 'ffz-global' , set _id ) ;
2017-11-13 01:23:39 -05:00
2018-04-06 21:12:12 -04:00
if ( ! suppress _log )
this . log . info ( ` Added " ${ emote _set ? emote _set . title : set _id } " emote set to ${ users . length } users. ` ) ;
2017-11-13 01:23:39 -05:00
}
}
2023-01-19 17:00:09 -05:00
processEmote ( emote , set _id ) {
if ( ! emote . id || ! emote . name || ! emote . urls )
return null ;
emote . set _id = set _id ;
emote . src = emote . urls [ 1 ] ;
emote . srcSet = ` ${ emote . urls [ 1 ] } 1x ` ;
if ( emote . urls [ 2 ] )
emote . srcSet += ` , ${ emote . urls [ 2 ] } 2x ` ;
if ( emote . urls [ 4 ] )
emote . srcSet += ` , ${ emote . urls [ 4 ] } 4x ` ;
if ( emote . urls [ 2 ] ) {
emote . can _big = true ;
emote . src2 = emote . urls [ 2 ] ;
emote . srcSet2 = ` ${ emote . urls [ 2 ] } 1x ` ;
if ( emote . urls [ 4 ] )
emote . srcSet2 += ` , ${ emote . urls [ 4 ] } 2x ` ;
}
if ( emote . animated ? . [ 1 ] ) {
emote . animSrc = emote . animated [ 1 ] ;
emote . animSrcSet = ` ${ emote . animated [ 1 ] } 1x ` ;
if ( emote . animated [ 2 ] ) {
emote . animSrcSet += ` , ${ emote . animated [ 2 ] } 2x ` ;
emote . animSrc2 = emote . animated [ 2 ] ;
emote . animSrcSet2 = ` ${ emote . animated [ 2 ] } 1x ` ;
if ( emote . animated [ 4 ] ) {
emote . animSrcSet += ` , ${ emote . animated [ 4 ] } 4x ` ;
emote . animSrcSet2 += ` , ${ emote . animated [ 4 ] } 2x ` ;
}
}
}
2023-03-27 18:50:32 -04:00
// Check to see if this emote applies any effects with as_background.
/ * l e t a s _ b a c k g r o u n d = f a l s e ;
if ( emote . modifier _flags ) {
for ( const input of EFFECT _STYLES )
if ( ( emote . modifier _flags & input . flags ) === input . flags ) {
if ( input . as _background ) {
as _background = true ;
break ;
}
}
} * /
2023-01-19 17:00:09 -05:00
emote . token = {
type : 'emote' ,
id : emote . id ,
set : set _id ,
provider : 'ffz' ,
src : emote . src ,
srcSet : emote . srcSet ,
can _big : ! ! emote . urls [ 2 ] ,
src2 : emote . src2 ,
srcSet2 : emote . srcSet2 ,
animSrc : emote . animSrc ,
animSrcSet : emote . animSrcSet ,
animSrc2 : emote . animSrc2 ,
animSrcSet2 : emote . animSrcSet2 ,
2023-03-10 17:06:12 -05:00
masked : ! ! emote . mask ,
2023-03-30 14:54:33 -04:00
mod _hidden : ( emote . modifier _flags & 1 ) === 1 ,
2023-01-19 17:00:09 -05:00
text : emote . hidden ? '???' : emote . name ,
length : emote . name . length ,
2023-03-03 15:24:20 -05:00
height : emote . height ,
2023-03-06 17:08:47 -05:00
width : emote . width ,
2023-03-27 18:50:32 -04:00
source _modifier _flags : emote . modifier _flags ? ? 0 ,
//effect_bg: as_background
2023-01-19 17:00:09 -05:00
} ;
if ( has ( MODIFIERS , emote . id ) )
Object . assign ( emote , MODIFIERS [ emote . id ] ) ;
return emote ;
}
addEmoteToSet ( set _id , emote ) {
const set = this . emote _sets [ set _id ] ;
if ( ! set )
throw new Error ( ` Invalid emote set " ${ set _id } " ` ) ;
let processed = this . processEmote ( emote , set _id ) ;
if ( ! processed )
throw new Error ( "Invalid emote data object." ) ;
// Are we removing an existing emote?
const old _emote = set . emotes [ processed . id ] ,
old _css = old _emote && this . generateEmoteCSS ( old _emote ) ;
// Store the emote.
set . emotes [ processed . id ] = processed ;
if ( ! old _emote )
set . count ++ ;
// Now we need to update the CSS. If we had old emote CSS, then we
// will need to totally rebuild the CSS.
const style _key = ` es-- ${ set _id } ` ;
if ( old _css && old _css . length ) {
const css = [ ] ;
for ( const em of Object . values ( set . emotes ) ) {
const emote _css = this . generateEmoteCSS ( em ) ;
if ( emote _css && emote _css . length )
css . push ( emote _css ) ;
}
if ( this . style && ( css . length || set . css ) )
this . style . set ( style _key , css . join ( '' ) + ( set . css || '' ) ) ;
else if ( css . length )
set . pending _css = css . join ( '' ) ;
} else {
const emote _css = this . generateEmoteCSS ( processed ) ;
if ( emote _css && emote _css . length ) {
if ( this . style )
this . style . set ( style _key , ( this . style . get ( style _key ) || '' ) + emote _css ) ;
else
set . pending _css = ( set . pending _css || '' ) + emote _css ;
}
}
// Send a loaded event because this emote set changed.
this . emit ( ':loaded' , set _id , set ) ;
}
removeEmoteFromSet ( set _id , emote _id ) {
const set = this . emote _sets [ set _id ] ;
if ( ! set )
throw new Error ( ` Invalid emote set " ${ set _id } " ` ) ;
if ( emote _id && emote _id . id )
emote _id = emote _id . id ;
const emote = set . emotes [ emote _id ] ;
if ( ! emote )
return ;
const emote _css = this . generateEmoteCSS ( emote ) ;
const css = ( emote _css && emote _css . length ) ? [ ] : null ;
// Rebuild the emotes object to avoid gaps.
const new _emotes = { } ;
let count = 0 ;
for ( const em of Object . values ( set . emotes ) ) {
if ( em . id == emote _id )
continue ;
new _emotes [ em . id ] = em ;
count ++ ;
if ( css != null ) {
const em _css = this . generateEmoteCSS ( em ) ;
if ( em _css && em _css . length )
css . push ( em _css ) ;
}
}
set . emotes = new _emotes ;
set . count = count ;
if ( css != null ) {
const style _key = ` es-- ${ set _id } ` ;
if ( this . style && ( css . length || set . css ) )
this . style . set ( style _key , css . join ( '' ) + ( set . css || '' ) ) ;
else if ( css . length )
set . pending _css = css . join ( '' ) ;
}
// Send a loaded event because this emote set changed.
this . emit ( ':loaded' , set _id , set ) ;
}
2018-02-02 18:11:37 -05:00
loadSetData ( set _id , data , suppress _log = false ) {
2017-11-13 01:23:39 -05:00
const old _set = this . emote _sets [ set _id ] ;
if ( ! data ) {
if ( old _set )
this . emote _sets [ set _id ] = null ;
return ;
}
this . emote _sets [ set _id ] = data ;
let count = 0 ;
const ems = data . emotes || data . emoticons ,
new _ems = data . emotes = { } ,
css = [ ] ;
2018-04-06 21:12:12 -04:00
data . id = set _id ;
2017-11-13 01:23:39 -05:00
data . emoticons = undefined ;
2018-04-09 19:57:05 -04:00
const bad _emotes = [ ] ;
2017-11-13 01:23:39 -05:00
for ( const emote of ems ) {
2023-01-19 17:00:09 -05:00
let processed = this . processEmote ( emote , set _id ) ;
if ( ! processed ) {
2018-04-09 19:57:05 -04:00
bad _emotes . push ( emote ) ;
continue ;
}
2023-01-19 17:00:09 -05:00
const emote _css = this . generateEmoteCSS ( processed ) ;
2017-11-13 01:23:39 -05:00
if ( emote _css )
css . push ( emote _css ) ;
count ++ ;
2023-01-19 17:00:09 -05:00
new _ems [ processed . id ] = processed ;
2017-11-13 01:23:39 -05:00
}
2018-04-09 19:57:05 -04:00
if ( bad _emotes . length )
this . log . warn ( ` Bad Emote Data for Set # ${ set _id } ` , bad _emotes ) ;
2017-11-13 01:23:39 -05:00
data . count = count ;
if ( this . style && ( css . length || data . css ) )
this . style . set ( ` es-- ${ set _id } ` , css . join ( '' ) + ( data . css || '' ) ) ;
else if ( css . length )
data . pending _css = css . join ( '' ) ;
2018-02-02 18:11:37 -05:00
if ( ! suppress _log )
this . log . info ( ` Loaded emote set # ${ set _id } : ${ data . title } ( ${ count } emotes) ` ) ;
2017-11-13 01:23:39 -05:00
this . emit ( ':loaded' , set _id , data ) ;
2021-06-10 18:53:16 -04:00
// Don't let people endlessly load unused sets.
const refs = this . _set _refs [ set _id ] || 0 ;
if ( refs <= 0 && ! this . _set _timers [ set _id ] )
this . _set _timers [ set _id ] = setTimeout ( ( ) => this . unloadSet ( set _id ) , 5000 ) ;
2017-11-13 01:23:39 -05:00
}
2018-02-02 18:11:37 -05:00
unloadSet ( set _id , force = false , suppress _log = false ) {
2017-11-22 20:21:01 -05:00
const old _set = this . emote _sets [ set _id ] ,
count = this . _set _refs [ set _id ] || 0 ;
if ( ! old _set )
return ;
if ( count > 0 ) {
if ( ! force )
return this . log . warn ( ` Attempted to unload emote set # ${ set _id } with ${ count } users. ` ) ;
this . log . warn ( ` Unloading emote set ${ set _id } with ${ count } users. ` ) ;
}
2018-02-02 18:11:37 -05:00
if ( ! suppress _log )
this . log . info ( ` Unloaded emote set # ${ set _id } : ${ old _set . title } ` ) ;
2021-06-10 18:53:16 -04:00
if ( this . _set _timers [ set _id ] ) {
clearTimeout ( this . _set _timers [ set _id ] ) ;
this . _set _timers [ set _id ] = null ;
}
2017-11-22 20:21:01 -05:00
this . emit ( ':unloaded' , set _id , old _set ) ;
this . emote _sets [ set _id ] = null ;
}
2017-11-13 01:23:39 -05:00
// ========================================================================
// Emote CSS
// ========================================================================
generateEmoteCSS ( emote ) { // eslint-disable-line class-methods-use-this
2023-03-10 17:06:12 -05:00
if ( ! emote . mask && ! emote . margins && ( ! emote . modifier || ( ! emote . modifier _offset && ! emote . extra _width && ! emote . shrink _to _fit ) ) && ! emote . css )
2017-11-13 01:23:39 -05:00
return '' ;
let output = '' ;
if ( emote . modifier && ( emote . modifier _offset || emote . margins || emote . extra _width || emote . shrink _to _fit ) ) {
let margins = emote . modifier _offset || emote . margins || '0' ;
margins = margins . split ( /\s+/ ) . map ( x => parseInt ( x , 10 ) ) ;
if ( margins . length === 3 )
margins . push ( margins [ 1 ] ) ;
const l = margins . length ,
m _top = margins [ 0 % l ] ,
m _right = margins [ 1 % l ] ,
m _bottom = margins [ 2 % l ] ,
m _left = margins [ 3 % l ] ;
output = ` .modified-emote span .ffz-emote[data-id=" ${ emote . id } "] {
padding : $ { m _top } px $ { m _right } px $ { m _bottom } px $ { m _left } px ;
$ { emote . shrink _to _fit ? ` max-width: calc(100% - ${ 40 - m _left - m _right - ( emote . extra _width || 0 ) } px); ` : '' }
margin : 0 ! important ;
} ` ;
}
2023-03-10 17:06:12 -05:00
if ( emote . modifier && emote . mask ? . [ 1 ] ) {
2023-03-27 18:50:32 -04:00
output = ( output || '' ) + ` .modified-emote[data-modifiers~=" ${ emote . id } "] > .chat-line__message--emote {
2023-03-10 17:06:12 -05:00
- webkit - mask - image : url ( "${emote.mask[1]}" ) ;
- webkit - mask - position : center center ;
} `
}
2017-11-13 01:23:39 -05:00
return ` ${ output } .ffz-emote[data-id=" ${ emote . id } "] {
$ { ( emote . margins && ! emote . modifier ) ? ` margin: ${ emote . margins } !important; ` : '' }
$ { emote . css || '' }
} ` ;
}
// ========================================================================
// Twitch Data Lookup
// ========================================================================
2019-10-28 01:06:02 -04:00
setTwitchEmoteSet ( emote _id , set _id ) {
2019-12-12 18:44:19 -05:00
if ( typeof emote _id === 'number' ) {
if ( isNaN ( emote _id ) || ! isFinite ( emote _id ) )
return ;
emote _id = ` ${ emote _id } ` ;
}
if ( typeof set _id === 'number' ) {
if ( isNaN ( set _id ) || ! isFinite ( set _id ) )
return ;
set _id = ` ${ set _id } ` ;
}
2019-10-28 01:06:02 -04:00
2019-12-12 18:44:19 -05:00
this . _ _twitch _emote _to _set [ emote _id ] = set _id ;
2019-10-28 01:06:02 -04:00
}
setTwitchSetChannel ( set _id , channel ) {
2019-12-12 18:44:19 -05:00
if ( typeof set _id === 'number' ) {
if ( isNaN ( set _id ) || ! isFinite ( set _id ) )
return ;
2019-10-28 01:06:02 -04:00
2019-12-12 18:44:19 -05:00
set _id = ` ${ set _id } ` ;
}
this . _ _twitch _set _to _channel [ set _id ] = channel ;
2019-10-28 01:06:02 -04:00
}
2023-03-27 18:50:32 -04:00
_getTwitchEmoteSet ( emote _id , need _artist = false ) {
2019-10-28 14:56:55 -04:00
const tes = this . _ _twitch _emote _to _set ,
2023-03-27 18:50:32 -04:00
tsc = this . _ _twitch _set _to _channel ,
tsa = this . _ _twitch _emote _to _artist ;
2017-11-13 01:23:39 -05:00
2019-12-12 18:44:19 -05:00
if ( typeof emote _id === 'number' ) {
if ( isNaN ( emote _id ) || ! isFinite ( emote _id ) )
return Promise . resolve ( null ) ;
emote _id = ` ${ emote _id } ` ;
}
2019-10-28 14:56:55 -04:00
2023-03-27 18:50:32 -04:00
if ( has ( tes , emote _id ) && ( ! need _artist || has ( tsa , emote _id ) ) ) {
2019-12-12 18:44:19 -05:00
const val = tes [ emote _id ] ;
2019-10-28 14:56:55 -04:00
if ( Array . isArray ( val ) )
return new Promise ( s => val . push ( s ) ) ;
else
return Promise . resolve ( val ) ;
}
2017-11-13 01:23:39 -05:00
2019-10-28 14:56:55 -04:00
const apollo = this . resolve ( 'site.apollo' ) ;
if ( ! apollo ? . client )
return Promise . resolve ( null ) ;
2017-11-13 01:23:39 -05:00
2019-10-28 14:56:55 -04:00
return new Promise ( s => {
const promises = [ s ] ;
2019-12-12 18:44:19 -05:00
tes [ emote _id ] = promises ;
2019-08-12 22:52:57 -04:00
timeout ( apollo . client . query ( {
query : GET _EMOTE ,
variables : {
id : ` ${ emote _id } `
}
2019-10-28 14:56:55 -04:00
} ) , 2000 ) . then ( data => {
const emote = data ? . data ? . emote ;
let set _id = null ;
2019-08-12 22:52:57 -04:00
2019-10-28 14:56:55 -04:00
if ( emote ) {
2019-12-12 18:44:19 -05:00
set _id = emote . setID ;
2019-08-12 22:52:57 -04:00
2023-03-27 18:50:32 -04:00
if ( emote . id && ! has ( tsa , emote . id ) ) {
tsa [ emote . id ] = emote . artist ;
}
2019-12-12 18:44:19 -05:00
if ( set _id && ! has ( tsc , set _id ) ) {
2019-10-28 14:56:55 -04:00
const type = determineEmoteType ( emote ) ;
2019-08-12 22:52:57 -04:00
2019-12-12 18:44:19 -05:00
tsc [ set _id ] = {
2019-10-28 14:56:55 -04:00
id : set _id ,
type ,
2019-12-12 18:44:19 -05:00
owner : emote ? . subscriptionProduct ? . owner || emote ? . owner
} ;
2019-10-28 14:56:55 -04:00
}
}
2019-08-12 22:52:57 -04:00
2019-12-12 18:44:19 -05:00
tes [ emote _id ] = set _id ;
2019-10-28 14:56:55 -04:00
for ( const fn of promises )
fn ( set _id ) ;
2019-08-12 22:52:57 -04:00
2019-10-28 14:56:55 -04:00
} ) . catch ( ( ) => {
2019-12-12 18:44:19 -05:00
tes [ emote _id ] = null ;
2019-10-28 14:56:55 -04:00
for ( const fn of promises )
fn ( null ) ;
} ) ;
} ) ;
}
2019-08-12 22:52:57 -04:00
2019-10-28 14:56:55 -04:00
getTwitchEmoteSet ( emote _id , callback ) {
const promise = this . _getTwitchEmoteSet ( emote _id ) ;
if ( callback )
promise . then ( callback ) ;
else
return promise ;
}
2019-08-12 22:52:57 -04:00
2023-03-27 18:50:32 -04:00
_getTwitchEmoteArtist ( emote _id ) {
const tsa = this . _ _twitch _emote _to _artist ;
if ( has ( tsa , emote _id ) )
return Promise . resolve ( tsa [ emote _id ] ) ;
return this . _getTwitchEmoteSet ( emote _id , true )
. then ( ( ) => tsa [ emote _id ] )
. catch ( ( ) => {
tsa [ emote _id ] = null ;
return null ;
} ) ;
}
getTwitchEmoteArtist ( emote _id , callback ) {
const promise = this . _getTwitchEmoteArtist ( emote _id ) ;
if ( callback )
promise . then ( callback ) ;
else
return promise ;
}
2017-11-13 01:23:39 -05:00
2019-10-28 14:56:55 -04:00
_getTwitchSetChannel ( set _id ) {
const tsc = this . _ _twitch _set _to _channel ;
2017-11-13 01:23:39 -05:00
2019-12-12 18:44:19 -05:00
if ( typeof set _id === 'number' ) {
if ( isNaN ( set _id ) || ! isFinite ( set _id ) )
return Promise . resolve ( null ) ;
2019-10-28 14:56:55 -04:00
2019-12-12 18:44:19 -05:00
set _id = ` ${ set _id } ` ;
}
if ( has ( tsc , set _id ) ) {
const val = tsc [ set _id ] ;
2019-10-28 14:56:55 -04:00
if ( Array . isArray ( val ) )
return new Promise ( s => val . push ( s ) ) ;
else
return Promise . resolve ( val ) ;
}
2017-11-13 01:23:39 -05:00
2019-10-28 14:56:55 -04:00
const apollo = this . resolve ( 'site.apollo' ) ;
if ( ! apollo ? . client )
return Promise . resolve ( null ) ;
2017-11-13 01:23:39 -05:00
2019-10-28 14:56:55 -04:00
return new Promise ( s => {
const promises = [ s ] ;
2019-12-12 18:44:19 -05:00
tsc [ set _id ] = promises ;
2018-07-26 19:40:53 -04:00
2019-10-28 14:56:55 -04:00
timeout ( apollo . client . query ( {
query : GET _EMOTE _SET ,
variables : {
id : ` ${ set _id } `
}
} ) , 2000 ) . then ( data => {
const set = data ? . data ? . emoteSet ;
let result = null ;
if ( set ) {
result = {
id : set _id ,
type : determineSetType ( set ) ,
owner : set . owner ? {
id : set . owner . id ,
login : set . owner . login ,
displayName : set . owner . displayName
} : null
} ;
}
2018-07-26 19:40:53 -04:00
2019-12-12 18:44:19 -05:00
tsc [ set _id ] = result ;
2019-10-28 14:56:55 -04:00
for ( const fn of promises )
fn ( result ) ;
2018-07-26 19:40:53 -04:00
2019-10-28 14:56:55 -04:00
} ) . catch ( ( ) => {
2019-12-12 18:44:19 -05:00
tsc [ set _id ] = null ;
2019-10-28 14:56:55 -04:00
for ( const fn of promises )
fn ( null ) ;
} ) ;
} ) ;
}
2018-07-26 19:40:53 -04:00
2019-10-28 14:56:55 -04:00
getTwitchSetChannel ( set _id , callback ) {
const promise = this . _getTwitchSetChannel ( set _id ) ;
if ( callback )
promise . then ( callback ) ;
else
return promise ;
}
}
2019-08-12 22:52:57 -04:00
2018-07-26 19:40:53 -04:00
2019-10-28 14:56:55 -04:00
function determineEmoteType ( emote ) {
const product = emote . subscriptionProduct ;
if ( product ) {
if ( product . id == 12658 )
return EmoteTypes . Prime ;
else if ( product . id == 324 )
return EmoteTypes . Turbo ;
2019-10-28 01:06:02 -04:00
2019-10-28 14:56:55 -04:00
// TODO: Care about Overwatch League
const owner = product . owner ;
if ( owner ) {
if ( owner . id == 139075904 || product . state === 'INACTIVE' )
return EmoteTypes . LimitedTime ;
return EmoteTypes . Subscription ;
2018-07-26 19:40:53 -04:00
}
}
2019-10-28 14:56:55 -04:00
if ( emote . setID == 300238151 )
return EmoteTypes . ChannelPoints ;
2018-07-26 19:40:53 -04:00
2020-04-03 19:30:28 -04:00
if ( emote . setID == 300374282 )
return EmoteTypes . TwoFactor ;
2019-12-12 18:44:19 -05:00
const id = parseInt ( emote . setID , 10 ) ;
if ( ! isNaN ( id ) && isFinite ( id ) && id >= 5e8 )
return EmoteTypes . BitsTier ;
2019-10-28 14:56:55 -04:00
return EmoteTypes . Global ;
}
2018-07-26 19:40:53 -04:00
2017-11-13 01:23:39 -05:00
2019-10-28 14:56:55 -04:00
function determineSetType ( set ) {
2021-06-30 15:51:37 -04:00
const id = /^\d+$/ . test ( set . id ) ? parseInt ( set . id , 10 ) : null ;
2017-11-13 01:23:39 -05:00
2021-06-30 15:51:37 -04:00
if ( id && TWITCH _GLOBAL _SETS . includes ( id ) )
2019-10-28 14:56:55 -04:00
return EmoteTypes . Global ;
2018-07-26 19:40:53 -04:00
2021-06-30 15:51:37 -04:00
if ( id && TWITCH _POINTS _SETS . includes ( id ) )
2019-10-28 14:56:55 -04:00
return EmoteTypes . ChannelPoints ;
2018-07-26 19:40:53 -04:00
2021-06-30 15:51:37 -04:00
if ( id && TWITCH _PRIME _SETS . includes ( id ) )
2019-10-28 14:56:55 -04:00
return EmoteTypes . Prime ;
2017-11-13 01:23:39 -05:00
2020-04-03 19:30:28 -04:00
if ( id == 300374282 )
return EmoteTypes . TwoFactor ;
2019-10-28 14:56:55 -04:00
const owner = set . owner ;
if ( owner ) {
if ( owner . id == 139075904 )
return EmoteTypes . LimitedTime ;
2019-10-28 01:06:02 -04:00
2019-10-28 14:56:55 -04:00
let product ;
if ( Array . isArray ( owner . subscriptionProducts ) )
for ( const prod of owner . subscriptionProducts )
if ( set . id == prod . emoteSetID ) {
product = prod ;
break ;
}
2019-10-28 01:06:02 -04:00
2019-10-28 14:56:55 -04:00
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 ;
2017-11-13 01:23:39 -05:00
}
2019-10-28 14:56:55 -04:00
2019-12-12 18:44:19 -05:00
if ( id >= 5e8 )
return EmoteTypes . BitsTier ;
2019-10-28 14:56:55 -04:00
return EmoteTypes . Global ;
2017-11-13 01:23:39 -05:00
}