2017-11-13 01:23:39 -05:00
'use strict' ;
// ============================================================================
// Chat Hooks
// ============================================================================
2020-11-23 18:12:07 -05:00
import { Color , ColorAdjuster } from 'utilities/color' ;
2020-06-30 19:48:46 -04:00
import { get , has , make _enum , shallow _object _equals , set _equals , deep _equals } from 'utilities/object' ;
2019-11-11 14:38:49 -05:00
import { WEBKIT _CSS as WEBKIT } from 'utilities/constants' ;
2018-05-22 17:23:20 -04:00
import { FFZEvent } from 'utilities/events' ;
2021-09-06 16:48:48 -04:00
import { useFont } from 'utilities/fonts' ;
2017-11-13 01:23:39 -05:00
import Module from 'utilities/module' ;
2018-03-14 13:58:04 -04:00
import Twilight from 'site' ;
2017-11-14 04:12:10 -05:00
import Scroller from './scroller' ;
2017-11-14 22:13:30 -05:00
import ChatLine from './line' ;
2017-11-15 14:04:59 -05:00
import SettingsMenu from './settings_menu' ;
2018-04-06 21:12:12 -04:00
import EmoteMenu from './emote_menu' ;
2019-05-07 15:04:12 -04:00
import Input from './input' ;
2019-05-31 23:21:49 -04:00
import ViewerCards from './viewer_card' ;
2022-12-18 17:30:34 -05:00
import { isHighlightedReward } from './points' ;
2017-11-14 22:13:30 -05:00
2020-06-30 19:48:46 -04:00
/ * c o n s t R E G E X _ E M O T E S = {
2018-06-27 20:17:45 -04:00
'B-?\\)' : [ 'B)' , 'B-)' ] ,
'R-?\\)' : [ 'R)' , 'R-)' ] ,
'[oO](_|\\.)[oO]' : [ 'o_o' , 'O_o' , 'o_O' , 'O_O' , 'o.o' , 'O.o' , 'o.O' , 'O.O' ] ,
'\\>\\;\\(' : [ '>(' ] ,
'\\<\\;3' : [ '<3' ] ,
'\\:-?(o|O)' : [ ':o' , ':O' , ':-o' , ':-O' ] ,
'\\:-?(p|P)' : [ ':p' , ':P' , ':-p' , ':-P' ] ,
'\\:-?D' : [ ':D' , ':-D' ] ,
'\\:-?[\\\\/]' : [ ':/' , ':-/' , ':\\' , ':-\\' ] ,
'\\:-?[z|Z|\\|]' : [ ':z' , ':Z' , ':|' , ':-z' , ':-Z' , ':-|' ] ,
'\\:-?\\(' : [ ':(' , ':-(' ] ,
'\\:-?\\)' : [ ':)' , ':-)' ] ,
'\\;-?(p|P)' : [ ';p' , ';P' , ';-p' , ';-P' ] ,
2018-06-29 19:45:33 -04:00
'\\;-?\\)' : [ ';)' , ';-)' ] ,
'#-?[\\\\/]' : [ '#/' , '#-/' , '#//' , '#-//' ] ,
':-?(?:7|L)' : [ ':7' , ':L' , ':-7' , ':-L' ] ,
'\\<\\;\\]' : [ '<]' ] ,
'\\:-?(S|s)' : [ ':s' , ':S' , ':-s' , ':-S' ] ,
'\\:\\>\\;' : [ ':>' ]
2020-06-30 19:48:46 -04:00
} ; * /
2018-06-27 20:17:45 -04:00
2018-10-16 18:04:54 -04:00
const MESSAGE _TYPES = make _enum (
'Post' ,
2020-11-23 18:12:07 -05:00
'Action'
2018-10-16 18:04:54 -04:00
) ;
const MOD _TYPES = make _enum (
'Ban' ,
'Timeout' ,
'Delete'
) ;
const AUTOMOD _TYPES = make _enum (
'MessageRejectedPrompt' ,
'CheerMessageRejectedPrompt' ,
'MessageRejected' ,
'MessageAllowed' ,
'MessageDenied' ,
'CheerMessageDenied' ,
'CheerMessageTimeout' ,
'MessageModApproved' ,
'MessageModDenied'
) ;
2020-08-17 13:33:30 -04:00
const UNBLOCKABLE _TYPES = [
'Message' ,
'Notice' ,
'Moderation' ,
'ModerationAction' ,
'TargetedModerationAction' ,
'AutoMod' ,
'SubscriberOnlyMode' ,
'FollowerOnlyMode' ,
'SlowMode' ,
'EmoteOnlyMode' ,
'R9KMode' ,
'Connected' ,
'Disconnected' ,
'Reconnect' ,
'RoomMods' ,
'RoomState' ,
'BadgesUpdated'
]
2018-10-16 18:04:54 -04:00
const CHAT _TYPES = make _enum (
'Message' ,
'ExtensionMessage' ,
'Moderation' ,
'ModerationAction' ,
'TargetedModerationAction' ,
'AutoMod' ,
'SubscriberOnlyMode' ,
'FollowerOnlyMode' ,
'SlowMode' ,
'EmoteOnlyMode' ,
'R9KMode' ,
'Connected' ,
'Disconnected' ,
'Reconnect' ,
'Subscription' ,
'Resubscription' ,
'GiftPaidUpgrade' ,
2018-11-12 13:34:53 -05:00
'AnonGiftPaidUpgrade' ,
2019-03-14 21:43:44 -04:00
'PrimePaidUpgrade' ,
2019-09-13 17:03:50 -04:00
'PrimeCommunityGiftReceivedEvent' ,
2019-08-09 14:24:26 -04:00
'ExtendSubscription' ,
2018-10-16 18:04:54 -04:00
'SubGift' ,
2018-11-12 13:34:53 -05:00
'AnonSubGift' ,
2018-10-16 18:04:54 -04:00
'Clear' ,
'RoomMods' ,
'RoomState' ,
'Raid' ,
'Unraid' ,
'Notice' ,
'Info' ,
'BadgesUpdated' ,
'Purchase' ,
'BitsCharity' ,
'CrateGift' ,
'RewardGift' ,
2018-11-12 13:34:53 -05:00
'SubMysteryGift' ,
2018-12-03 18:08:32 -05:00
'AnonSubMysteryGift' ,
2019-10-22 18:32:56 -04:00
'StandardPayForward' ,
'CommunityPayForward' ,
2019-02-05 14:24:45 -05:00
'FirstCheerMessage' ,
2021-06-30 15:51:37 -04:00
'FirstMessageHighlight' ,
2019-04-18 03:16:19 -04:00
'BitsBadgeTierMessage' ,
2019-06-14 21:24:48 -04:00
'InlinePrivateCallout' ,
2020-07-26 17:50:14 -04:00
'ChannelPointsReward' ,
2020-10-28 17:09:37 -04:00
'CommunityChallengeContribution' ,
2022-02-11 15:17:32 -05:00
'LiveMessageSeparator' ,
'RestrictedLowTrustUserMessage' ,
2023-03-03 15:24:20 -05:00
'CommunityIntroduction' ,
'Shoutout' ,
'AnnouncementMessage' ,
'MidnightSquid' ,
'CharityDonation' ,
'MessageIdUpdate' ,
'PinnedChat' ,
'ViewerMilestone'
2018-10-16 18:04:54 -04:00
) ;
2017-11-14 22:13:30 -05:00
const NULL _TYPES = [
'Reconnect' ,
'RoomState' ,
2018-04-28 17:56:03 -04:00
'BadgesUpdated' ,
'Clear'
2017-11-14 22:13:30 -05:00
] ;
2017-11-14 04:12:10 -05:00
2018-04-28 17:56:03 -04:00
const MISBEHAVING _EVENTS = [
2018-02-22 18:23:44 -05:00
'onBadgesUpdatedEvent' ,
2017-11-13 01:23:39 -05:00
] ;
export default class ChatHook extends Module {
constructor ( ... args ) {
super ( ... args ) ;
this . should _enable = true ;
this . colors = new ColorAdjuster ;
2018-04-28 17:56:03 -04:00
this . inverse _colors = new ColorAdjuster ;
2017-11-13 01:23:39 -05:00
this . inject ( 'settings' ) ;
2018-07-16 18:02:08 -04:00
this . inject ( 'i18n' ) ;
2020-07-22 21:31:41 -04:00
this . inject ( 'experiments' ) ;
2017-11-13 01:23:39 -05:00
this . inject ( 'site' ) ;
this . inject ( 'site.router' ) ;
this . inject ( 'site.fine' ) ;
this . inject ( 'site.web_munch' ) ;
this . inject ( 'site.css_tweaks' ) ;
2020-07-22 21:31:41 -04:00
this . inject ( 'site.subpump' ) ;
2017-11-13 01:23:39 -05:00
this . inject ( 'chat' ) ;
2017-11-14 04:12:10 -05:00
this . inject ( Scroller ) ;
2017-11-14 22:13:30 -05:00
this . inject ( ChatLine ) ;
2017-11-15 14:04:59 -05:00
this . inject ( SettingsMenu ) ;
2018-04-06 21:12:12 -04:00
this . inject ( EmoteMenu ) ;
2019-05-07 15:04:12 -04:00
this . inject ( Input ) ;
2019-05-31 23:21:49 -04:00
this . inject ( ViewerCards ) ;
2018-05-10 19:56:39 -04:00
2018-08-28 19:13:26 -04:00
this . ChatService = this . fine . define (
'chat-service' ,
2018-09-11 19:08:58 -04:00
n => n . join && n . connectHandlers ,
2018-08-28 19:13:26 -04:00
Twilight . CHAT _ROUTES
) ;
this . ChatBuffer = this . fine . define (
'chat-buffer' ,
n => n . updateHandlers && n . delayedMessageBuffer && n . handleMessage ,
Twilight . CHAT _ROUTES
) ;
2017-11-13 01:23:39 -05:00
this . ChatController = this . fine . define (
'chat-controller' ,
2022-10-07 15:12:15 -04:00
n => n . parseOutgoingMessage && n . onRoomStateUpdated && n . renderNotifications ,
2018-03-14 13:58:04 -04:00
Twilight . CHAT _ROUTES
2017-11-13 01:23:39 -05:00
) ;
this . ChatContainer = this . fine . define (
'chat-container' ,
2020-01-22 16:58:55 -05:00
n => n . closeViewersList && n . onChatInputFocus ,
2018-03-14 13:58:04 -04:00
Twilight . CHAT _ROUTES
2017-11-13 01:23:39 -05:00
) ;
2018-08-28 19:13:26 -04:00
this . ChatBufferConnector = this . fine . define (
'chat-buffer-connector' ,
n => n . clearBufferHandle && n . syncBufferedMessages ,
Twilight . CHAT _ROUTES
) ;
2020-06-30 19:48:46 -04:00
this . joined _raids = new Set ;
this . RaidController = this . fine . define (
'raid-controller' ,
n => n . handleLeaveRaid && n . handleJoinRaid ,
2018-03-14 13:58:04 -04:00
Twilight . CHAT _ROUTES
2020-06-30 19:48:46 -04:00
) ;
2018-04-28 17:56:03 -04:00
2019-06-18 19:48:51 -04:00
this . InlineCallout = this . fine . define (
'inline-callout' ,
n => n . showCTA && n . toggleContextMenu && n . actionClick ,
Twilight . CHAT _ROUTES
) ;
this . PinnedCallout = this . fine . define (
'pinned-callout' ,
n => n . getCalloutTitle && n . buildCalloutProps && n . pin ,
Twilight . CHAT _ROUTES
) ;
2017-11-13 01:23:39 -05:00
2019-09-13 17:03:50 -04:00
this . CalloutSelector = this . fine . define (
'callout-selector' ,
n => n . selectCalloutComponent && n . props && n . props . callouts ,
Twilight . CHAT _ROUTES
) ;
this . PointsButton = this . fine . define (
'points-button' ,
n => n . renderIcon && n . renderFlame && n . handleIconAnimationComplete ,
Twilight . CHAT _ROUTES
) ;
this . PointsClaimButton = this . fine . define (
'points-claim-button' ,
n => n . getClaim && n . onClick && n . props && n . props . claimCommunityPoints ,
Twilight . CHAT _ROUTES
) ;
2019-09-23 12:17:51 -04:00
this . CommunityChestBanner = this . fine . define (
'community-chest-banner' ,
n => n . getLastGifterText && n . getBannerText && has ( n , 'finalCount' ) ,
Twilight . CHAT _ROUTES
) ;
2019-11-11 14:38:49 -05:00
this . PointsInfo = this . fine . define (
'points-info' ,
n => n . pointIcon !== undefined && n . pointName !== undefined ,
Twilight . CHAT _ROUTES
) ;
this . GiftBanner = this . fine . define (
'gift-banner' ,
2020-01-11 17:13:56 -05:00
n => n . getBannerText && n . onGiftMoreClick ,
2019-11-11 14:38:49 -05:00
Twilight . CHAT _ROUTES
) ;
2017-11-13 01:23:39 -05:00
// Settings
2023-01-19 17:00:09 -05:00
this . settings . add ( 'chat.disable-handling' , {
default : null ,
requires : [ 'context.disable-chat-processing' ] ,
process ( ctx , val ) {
if ( val != null )
return ! val ;
if ( ctx . get ( 'context.disable-chat-processing' ) )
return true ;
return false ;
} ,
ui : {
path : 'Debugging > Chat >> Processing' ,
title : 'Enable processing of chat messages.' ,
component : 'setting-check-box' ,
force _seen : true
}
} ) ;
2022-12-18 17:30:34 -05:00
this . settings . addUI ( 'debug.chat-test' , {
path : 'Debugging > Chat >> Chat' ,
component : 'chat-tester' ,
getChat : ( ) => this ,
force _seen : true
} ) ;
2020-08-17 13:33:30 -04:00
this . settings . add ( 'chat.filtering.blocked-types' , {
default : [ ] ,
type : 'array_merge' ,
always _inherit : true ,
process ( ctx , val ) {
const out = new Set ;
for ( const v of val )
if ( v ? . v || ! UNBLOCKABLE _TYPES . includes ( v . v ) )
out . add ( v . v ) ;
return out ;
} ,
ui : {
2021-03-03 17:10:14 -05:00
path : 'Chat > Filtering > Block >> Message Types @{"description":"This filter allows you to remove all messages of a certain type from Twitch chat. It can be used to filter system messages, such as Hosts or Raids. Some types, such as moderation actions, cannot be blocked to prevent chat functionality from breaking."}' ,
2020-08-17 13:33:30 -04:00
component : 'blocked-types' ,
data : ( ) => Object
. keys ( this . chat _types )
. filter ( key => ! UNBLOCKABLE _TYPES . includes ( key ) && ! /^\d+$/ . test ( key ) )
2022-02-11 15:17:32 -05:00
. sort ( )
2020-08-17 13:33:30 -04:00
}
} ) ;
2020-08-13 14:00:47 -04:00
this . settings . add ( 'chat.replies.style' , {
2020-08-14 17:17:24 -04:00
default : 1 ,
2020-08-13 14:00:47 -04:00
ui : {
path : 'Chat > Appearance >> Replies' ,
title : 'Style' ,
2021-02-22 20:11:35 -05:00
description : ` Twitch's default style adds a floating button to the right and displays a notice above messages that are replies. FrankerFaceZ uses an In-Line Chat Action (that can be removed in [Chat > Actions > In-Line](~chat.actions.in_line)) and uses an in-line mention to denote replies. ` ,
2020-08-13 14:00:47 -04:00
component : 'setting-select-box' ,
data : [
{ value : 0 , title : 'Disabled' } ,
2020-08-14 17:17:24 -04:00
{ value : 1 , title : 'Twitch (Default)' } ,
2020-08-13 14:00:47 -04:00
{ value : 2 , title : 'FrankerFaceZ' }
]
}
} ) ;
2020-06-30 19:48:46 -04:00
this . settings . add ( 'channel.raids.no-autojoin' , {
default : false ,
ui : {
path : 'Channel > Behavior >> Raids' ,
title : 'Do not automatically join raids.' ,
component : 'setting-check-box'
}
} ) ;
2019-12-10 20:46:33 -05:00
this . settings . add ( 'chat.hide-community-highlights' , {
default : false ,
ui : {
path : 'Chat > Appearance >> Community' ,
title : 'Hide all Community Highlights from the top of chat.' ,
component : 'setting-check-box' ,
description : 'Community Highlights are polls, community gift subs, etc. that float over the top of chat temporarily with no way to close them.'
}
} ) ;
2019-11-11 14:38:49 -05:00
this . settings . add ( 'chat.subs.gift-banner' , {
default : true ,
ui : {
2019-12-10 20:46:33 -05:00
path : 'Chat > Appearance >> Community' ,
2019-11-11 14:38:49 -05:00
title : 'Display a banner at the top of chat when a mass gift sub happens.' ,
component : 'setting-check-box'
}
} ) ;
2022-06-14 16:26:34 -04:00
this . settings . add ( 'chat.banners.last-events' , {
default : true ,
ui : {
path : 'Chat > Appearance >> Community' ,
title : 'Allow the Support Activity Feed to be displayed in chat.' ,
component : 'setting-check-box'
}
} ) ;
2023-03-27 18:50:32 -04:00
this . settings . add ( 'chat.banners.charity' , {
default : true ,
ui : {
path : 'Chat > Appearance >> Community' ,
title : 'Allow the charity fundraiser progress to be displayed in chat.' ,
component : 'setting-check-box'
}
} ) ;
2020-03-06 01:44:33 -05:00
this . settings . add ( 'chat.banners.hype-train' , {
default : true ,
ui : {
path : 'Chat > Appearance >> Community' ,
title : 'Allow the Hype Train to be displayed in chat.' ,
component : 'setting-check-box' ,
}
} ) ;
2021-09-04 20:14:58 -04:00
this . settings . add ( 'chat.banners.drops' , {
default : true ,
ui : {
path : 'Chat > Appearance >> Community' ,
title : 'Allow messages about Drops to be displayed in chat.' ,
component : 'setting-check-box'
}
} ) ;
2020-03-06 23:29:25 -05:00
this . settings . add ( 'chat.banners.polls' , {
default : true ,
ui : {
path : 'Chat > Appearance >> Community' ,
title : 'Allow Polls to be displayed in chat.' ,
component : 'setting-check-box'
}
} ) ;
2021-02-15 17:48:30 -05:00
this . settings . add ( 'chat.banners.prediction' , {
default : true ,
ui : {
path : 'Chat > Appearance >> Community' ,
title : 'Allow Predictions to be displayed in chat.' ,
component : 'setting-check-box'
}
} ) ;
2019-09-23 12:17:51 -04:00
this . settings . add ( 'chat.community-chest.show' , {
default : true ,
ui : {
2019-12-10 20:46:33 -05:00
path : 'Chat > Appearance >> Community' ,
2019-09-23 12:17:51 -04:00
title : 'Display the Community Gift Chest banner.' ,
component : 'setting-check-box'
}
} ) ;
2020-01-22 16:58:55 -05:00
this . settings . add ( 'chat.points.allow-highlight' , {
2021-01-27 17:36:01 -05:00
default : 2 ,
2019-11-11 14:38:49 -05:00
ui : {
path : 'Chat > Channel Points >> Appearance' ,
2020-01-22 16:58:55 -05:00
title : 'Highlight the message in chat when someone redeems Highlight My Message.' ,
2021-01-27 17:36:01 -05:00
component : 'setting-select-box' ,
data : [
{ value : 0 , title : 'Disabled' } ,
{ value : 1 , title : 'Twitch Style' } ,
{ value : 2 , title : 'FFZ Style' }
]
2019-11-11 14:38:49 -05:00
}
} ) ;
2019-09-13 17:03:50 -04:00
this . settings . add ( 'chat.points.show-callouts' , {
default : true ,
ui : {
path : 'Chat > Channel Points >> General' ,
title : 'Display messages in chat about Channel Points rewards.' ,
component : 'setting-check-box'
}
} ) ;
this . settings . add ( 'chat.points.show-button' , {
default : true ,
ui : {
path : 'Chat > Channel Points >> General' ,
title : 'Display Channel Points button beneath chat.' ,
component : 'setting-check-box'
}
} ) ;
this . settings . add ( 'chat.points.show-rewards' , {
default : true ,
2020-11-24 16:43:51 -05:00
requires : [ 'layout.portrait-min-chat' ] ,
process ( ctx , val ) {
if ( ctx . get ( 'layout.portrait-min-chat' ) )
return false ;
return val ;
} ,
2019-09-13 17:03:50 -04:00
ui : {
path : 'Chat > Channel Points >> Behavior' ,
title : 'Allow available rewards to appear next to the Channel Points button.' ,
component : 'setting-check-box'
}
} ) ;
2021-03-24 13:03:29 -04:00
this . settings . add ( 'chat.points.auto-rewards' , {
default : false ,
ui : {
path : 'Chat > Channel Points >> Behavior' ,
title : 'Automatically claim bonus rewards.' ,
component : 'setting-check-box' ,
force _seen : true
}
} ) ;
2019-06-18 19:48:51 -04:00
this . settings . add ( 'chat.pin-resubs' , {
default : false ,
ui : {
path : 'Chat > Behavior >> General' ,
title : 'Automatically pin re-subscription messages in chat.' ,
component : 'setting-check-box'
}
} ) ;
2017-11-13 01:23:39 -05:00
this . settings . add ( 'chat.width' , {
2021-04-01 12:05:29 -04:00
default : null ,
2017-11-13 01:23:39 -05:00
ui : {
path : 'Chat > Appearance >> General @{"sort": -1}' ,
title : 'Width' ,
2018-01-16 17:36:56 -05:00
description : "How wide chat should be, in pixels. This may be affected by your browser's zoom and font size settings." ,
2017-11-13 01:23:39 -05:00
component : 'setting-text-box' ,
process ( val ) {
val = parseInt ( val , 10 ) ;
if ( isNaN ( val ) || ! isFinite ( val ) || val <= 0 )
2021-04-01 12:05:29 -04:00
return null ;
2017-11-13 01:23:39 -05:00
return val ;
}
}
} ) ;
2021-04-01 12:05:29 -04:00
this . settings . add ( 'chat.effective-width' , {
requires : [ 'chat.width' , 'context.ui.rightColumnWidth' ] ,
process ( ctx ) {
const val = ctx . get ( 'chat.width' ) ;
return val == null ? ( ctx . get ( 'context.ui.rightColumnWidth' ) || 340 ) : val ;
}
} ) ;
2019-05-16 14:46:26 -04:00
this . settings . add ( 'chat.use-width' , {
2020-09-29 14:15:43 -04:00
requires : [ 'chat.width' , 'context.ui.rightColumnExpanded' , 'context.isWatchParty' ] ,
2019-05-16 14:46:26 -04:00
process ( ctx ) {
2020-09-29 14:15:43 -04:00
if ( ! ctx . get ( 'context.ui.rightColumnExpanded' ) || ctx . get ( 'context.isWatchParty' ) )
2019-05-16 14:46:26 -04:00
return false ;
2021-04-01 12:05:29 -04:00
return ctx . get ( 'chat.width' ) != null ;
2019-05-16 14:46:26 -04:00
}
} ) ;
2017-11-13 01:23:39 -05:00
this . settings . add ( 'chat.bits.show-pinned' , {
2019-06-13 22:56:50 -04:00
requires : [ 'chat.bits.show' ] ,
default : null ,
process ( ctx , val ) {
if ( val != null )
return val ;
return ctx . get ( 'chat.bits.show' )
} ,
2017-11-13 01:23:39 -05:00
ui : {
2020-05-27 15:44:37 -04:00
path : 'Chat > Appearance >> Community' ,
title : 'Display Leaderboard' ,
2021-02-22 20:11:35 -05:00
description : 'The leaderboard shows the top cheerers and sub gifters in a channel.\n\nBy default due to a previous implementation, this inherits its value from [Chat > Bits and Cheering > Display Bits](~chat.bits_and_cheering).' ,
2019-06-13 22:56:50 -04:00
component : 'setting-check-box'
}
} ) ;
this . settings . add ( 'chat.bits.show-rewards' , {
requires : [ 'chat.bits.show' ] ,
default : null ,
process ( ctx , val ) {
if ( val != null )
return val ;
2017-11-13 01:23:39 -05:00
2019-06-13 22:56:50 -04:00
return ctx . get ( 'chat.bits.show' )
} ,
ui : {
path : 'Chat > Bits and Cheering >> Behavior' ,
title : 'Display messages when a cheer shares rewards to people in chat.' ,
description : 'By default, this inherits its value from Display Bits. This setting only affects newly arrived messages.' ,
2017-11-13 01:23:39 -05:00
component : 'setting-check-box'
}
} ) ;
2021-07-12 13:46:04 -04:00
this . settings . add ( 'chat.bits.cheer-notice' , {
default : true ,
ui : {
path : 'Chat > Bits and Cheering >> Appearance' ,
title : 'Display a notice on chat messages that include cheers.' ,
description : 'This feature is intended to prevent possible confusion from chatters using emotes to fake cheers in messages. When enabled, messages that contain real cheers will display a message above them, similar to how resubscription messages or point redemptions with messages function.' ,
component : 'setting-check-box'
}
} ) ;
2018-02-02 16:00:58 -05:00
this . settings . add ( 'chat.rituals.show' , {
default : true ,
ui : {
2021-03-03 17:10:14 -05:00
path : 'Chat > Filtering > General >> Rituals' ,
2018-02-02 16:00:58 -05:00
title : 'Display ritual messages such as "User is new here! Say Hello!".' ,
component : 'setting-check-box'
}
} ) ;
2021-04-01 12:05:29 -04:00
this . settings . add ( 'chat.extra-timestamps' , {
default : true ,
ui : {
path : 'Chat > Appearance >> Chat Lines' ,
title : 'Display timestamps on notices.' ,
description : 'When enabled, timestamps will be displayed on point redemptions, subscriptions, etc.' ,
component : 'setting-check-box'
}
} ) ;
2019-02-05 14:24:45 -05:00
this . settings . add ( 'chat.subs.show' , {
default : 3 ,
ui : {
path : 'Chat > Appearance >> Subscriptions' ,
title : 'Display Subs in Chat' ,
component : 'setting-select-box' ,
description : '**Note**: Messages sent with re-subs will always be displayed. This only controls the special "X subscribed!" message.' ,
data : [
{ value : 0 , title : 'Do Not Display' } ,
{ value : 1 , title : 'Re-Subs with Messages Only' } ,
{ value : 2 , title : 'Re-Subs Only' } ,
{ value : 3 , title : 'Display All' }
]
}
} ) ;
this . settings . add ( 'chat.subs.compact' , {
default : false ,
ui : {
path : 'Chat > Appearance >> Subscriptions' ,
title : 'Display subscription notices in a more compact (classic style) form.' ,
component : 'setting-check-box'
}
} ) ;
this . settings . add ( 'chat.subs.merge-gifts' , {
default : 1000 ,
ui : {
path : 'Chat > Appearance >> Subscriptions' ,
title : 'Merge Mass Sub Gifts' ,
component : 'setting-select-box' ,
data : [
{ value : 1000 , title : 'Disabled' } ,
{ value : 50 , title : 'More than 50' } ,
{ value : 20 , title : 'More than 20' } ,
{ value : 10 , title : 'More than 10' } ,
{ value : 5 , title : 'More than 5' } ,
{ value : 0 , title : 'Always' }
] ,
description : 'Merge mass gift subscriptions into a single message, depending on the quantity.\n**Note:** Only affects newly gifted subs.'
}
} ) ;
2019-03-14 21:43:44 -04:00
this . settings . add ( 'chat.subs.merge-gifts-visibility' , {
default : false ,
ui : {
path : 'Chat > Appearance >> Subscriptions' ,
title : 'Expand merged mass sub gift messages by default.' ,
component : 'setting-check-box'
}
} ) ;
2017-11-13 01:23:39 -05:00
this . settings . add ( 'chat.lines.alternate' , {
default : false ,
ui : {
path : 'Chat > Appearance >> Chat Lines' ,
title : 'Display lines with alternating background colors.' ,
component : 'setting-check-box'
}
} ) ;
this . settings . add ( 'chat.lines.padding' , {
default : false ,
ui : {
path : 'Chat > Appearance >> Chat Lines' ,
title : 'Reduce padding around lines.' ,
component : 'setting-check-box'
}
} ) ;
this . settings . add ( 'chat.lines.borders' , {
default : 0 ,
ui : {
path : 'Chat > Appearance >> Chat Lines' ,
title : 'Separators' ,
component : 'setting-select-box' ,
data : [
{ value : 0 , title : 'Disabled' } ,
{ value : 1 , title : 'Basic Line (1px Solid)' } ,
{ value : 2 , title : '3D Line (2px Groove)' } ,
{ value : 3 , title : '3D Line (2px Groove Inset)' } ,
{ value : 4 , title : 'Wide Line (2px Solid)' }
]
}
} ) ;
2020-07-02 18:19:41 -04:00
this . settings . add ( 'chat.input.show-mod-view' , {
default : true ,
ui : {
path : 'Chat > Input >> Appearance' ,
2022-12-07 16:52:07 -05:00
title : 'Allow the "Mod View" button to appear in relevant channels.' ,
component : 'setting-check-box'
}
} ) ;
this . settings . add ( 'chat.input.show-highlight' , {
default : true ,
ui : {
path : 'Chat > Input >> Appearance' ,
title : 'Allow the "Chat Highlight Settings" button to appear in relevant channels.' ,
component : 'setting-check-box'
}
} ) ;
this . settings . add ( 'chat.input.show-shield' , {
default : true ,
ui : {
path : 'Chat > Input >> Appearance' ,
title : 'Allow the "Shield Mode" button to appear in relevant channels.' ,
2020-07-02 18:19:41 -04:00
component : 'setting-check-box'
}
} ) ;
2022-10-07 15:12:15 -04:00
this . settings . add ( 'chat.input.show-elevate-your-message' , {
default : true ,
ui : {
path : 'Chat > Input >> Appearance' ,
title : 'Allow the "Elevate Your Message" button to be displayed.' ,
component : 'setting-check-box'
}
} ) ;
2017-11-13 01:23:39 -05:00
}
get currentChat ( ) {
for ( const inst of this . ChatController . instances )
if ( inst && inst . chatService )
return inst ;
2019-06-20 15:15:54 -04:00
return null ;
2017-11-13 01:23:39 -05:00
}
updateColors ( ) {
const is _dark = this . chat . context . get ( 'theme.is-dark' ) ,
mode = this . chat . context . get ( 'chat.adjustment-mode' ) ,
contrast = this . chat . context . get ( 'chat.adjustment-contrast' ) ,
2018-04-28 17:56:03 -04:00
c = this . colors ,
ic = this . inverse _colors ;
2017-11-13 01:23:39 -05:00
2020-11-25 19:27:25 -05:00
let chat _dark = is _dark ,
chat _color = Color . RGBA . fromCSS (
this . chat . context . get ( 'theme.color.chat-background' ) ||
this . chat . context . get ( 'theme.color.background' )
) ;
2020-11-23 18:12:07 -05:00
2020-11-25 19:27:25 -05:00
if ( chat _color )
2020-12-01 18:19:17 -05:00
chat _dark = chat _color . toHSLA ( ) . l < 0.5 ;
2020-11-23 18:12:07 -05:00
2020-11-25 19:27:25 -05:00
chat _color = chat _dark ? '#191919' : '#E0E0E0' ;
2020-11-23 18:12:07 -05:00
2020-11-25 19:27:25 -05:00
let text _dark = ! chat _dark ,
chat _text = Color . RGBA . fromCSS (
this . chat . context . get ( 'theme.color.chat-text' ) ||
this . chat . context . get ( 'theme.color.text' )
) ;
if ( chat _text )
2020-12-01 18:19:17 -05:00
text _dark = chat _text . toHSLA ( ) . l < 0.5 ;
2020-11-25 19:27:25 -05:00
2020-11-25 20:19:15 -05:00
chat _text = text _dark ? '#19171c' : '#dad8de' ;
2020-11-23 18:12:07 -05:00
2017-11-13 01:23:39 -05:00
// TODO: Get the background color from the theme system.
2017-11-20 16:59:52 -05:00
// Updated: Use the lightest/darkest colors from alternating rows for better readibility.
2020-11-23 18:12:07 -05:00
c . _base = chat _color ; // is_dark ? '#191919' : '#e0e0e0'; //#0e0c13' : '#faf9fa';
2017-11-13 01:23:39 -05:00
c . mode = mode ;
c . contrast = contrast ;
2020-11-23 18:12:07 -05:00
ic . _base = chat _text ; // is_dark ? '#dad8de' : '#19171c';
2018-04-28 17:56:03 -04:00
ic . mode = mode ;
2018-05-31 18:34:15 -04:00
ic . contrast = contrast ;
2018-04-28 17:56:03 -04:00
4.25.0
* Fixed: Smooth Scrolling no longer causing chat to scroll. (Closes #1068)
* Fixed: Issue with users using certain external stylesheets causing chat messages to become impossible to read on mouse hover. (Closes #1066)
* Fixed: Issues with badge sorting causing some badges to be overridden when they shouldn't be.
* Changed: Improve caching of badge data, such that re-rendering chat lines requires less computation.
* Changed: Refactor how chat lines listen for settings changes to reduce code duplication.
* Changed: Refactor how chat lines are invalidated to minimize work when changing settings.
* API Added: `chat:rerender-lines` event that, when emitted, causes all chat lines to be re-rendered.
* API Added: `chat:update-line-tokens` event that, when emitted, causes all chat lines to have their tokens invalidated and recalculated.
* API Added: `chat:update-line-badges` event that, when emitted, causes all chat lines to have their cached badges invalidated and recalculated.
* API Changed: `chat:update-lines-by-user` now has extra properties for separately invalidating tokens or badges. The full signature is `chat:update-lines-by-user(id, login, invalidate_tokens = true, invalidate_badges = true)`
2021-06-23 16:08:57 -04:00
this . chat _line . rerenderLines ( ) ;
//this.updateChatLines();
2019-07-31 17:13:56 -04:00
this . updateMentionCSS ( ) ;
2019-05-31 23:21:49 -04:00
this . emit ( ':update-colors' ) ;
2017-11-13 01:23:39 -05:00
}
2017-11-14 18:48:56 -05:00
updateChatCSS ( ) {
2019-05-16 15:18:39 -04:00
if ( ! this . _update _css _waiter )
this . _update _css _waiter = requestAnimationFrame ( ( ) => this . _updateChatCSS ( ) ) ;
2019-05-16 14:46:26 -04:00
}
_updateChatCSS ( ) {
2019-05-16 15:18:39 -04:00
cancelAnimationFrame ( this . _update _css _waiter ) ;
this . _update _css _waiter = null ;
2019-05-16 14:46:26 -04:00
2021-04-01 12:05:29 -04:00
const width = this . chat . context . get ( 'chat.effective-width' ) ,
2021-03-02 16:55:25 -05:00
action _size = this . chat . context . get ( 'chat.actions.size' ) ,
2022-12-18 17:30:34 -05:00
hover _action _size = this . chat . context . get ( 'chat.actions.hover-size' ) ,
2021-03-02 19:50:25 -05:00
ts _size = this . chat . context . get ( 'chat.timestamp-size' ) ,
2017-11-14 18:48:56 -05:00
size = this . chat . context . get ( 'chat.font-size' ) ,
2018-04-08 21:20:46 +02:00
emote _alignment = this . chat . context . get ( 'chat.lines.emote-alignment' ) ,
2017-11-17 14:59:46 -05:00
lh = Math . round ( ( 20 / 12 ) * size ) ;
2017-11-14 18:48:56 -05:00
2022-12-18 17:30:34 -05:00
const hover _action _icon = Math . round ( hover _action _size * ( 2 / 3 ) ) ,
hover _action _padding = hover _action _size - hover _action _icon ;
2017-11-17 14:59:46 -05:00
let font = this . chat . context . get ( 'chat.font-family' ) || 'inherit' ;
2021-09-06 16:48:48 -04:00
const [ processed , unloader ] = useFont ( font ) ;
font = processed ;
if ( this . _font _unloader )
this . _font _unloader ( ) ;
this . _font _unloader = unloader ;
2017-11-17 14:59:46 -05:00
if ( font . indexOf ( ' ' ) !== - 1 && font . indexOf ( ',' ) === - 1 && font . indexOf ( '"' ) === - 1 && font . indexOf ( "'" ) === - 1 )
font = ` " ${ font } " ` ;
2017-11-14 18:48:56 -05:00
2021-03-02 19:50:25 -05:00
if ( ts _size )
this . css _tweaks . set ( 'ts-size' , ` .chat-line__timestamp{font-size: ${ ts _size / 10 } rem} ` ) ;
else
this . css _tweaks . delete ( 'ts-size' ) ;
2021-03-02 16:55:25 -05:00
this . css _tweaks . setVariable ( 'chat-actions-size' , ` ${ action _size / 10 } rem ` ) ;
2022-12-18 17:30:34 -05:00
this . css _tweaks . setVariable ( 'chat-actions-hover-size' , ` ${ hover _action _icon / 10 } rem ` ) ;
this . css _tweaks . setVariable ( 'chat-actions-hover-padding' , ` ${ hover _action _padding / 20 } rem ` ) ;
2018-01-16 17:36:56 -05:00
this . css _tweaks . setVariable ( 'chat-font-size' , ` ${ size / 10 } rem ` ) ;
this . css _tweaks . setVariable ( 'chat-line-height' , ` ${ lh / 10 } rem ` ) ;
2017-11-17 14:59:46 -05:00
this . css _tweaks . setVariable ( 'chat-font-family' , font ) ;
2018-01-16 17:36:56 -05:00
this . css _tweaks . setVariable ( 'chat-width' , ` ${ width / 10 } rem ` ) ;
2020-05-27 15:44:37 -04:00
this . css _tweaks . setVariable ( 'negative-chat-width' , ` ${ - width / 10 } rem ` ) ;
2017-11-14 18:48:56 -05:00
2020-10-05 13:16:37 -04:00
this . css _tweaks . toggle ( 'chat-font' , size !== 13 || font !== 'inherit' ) ;
2019-05-16 15:10:34 -04:00
this . css _tweaks . toggle ( 'chat-width' , this . settings . get ( 'chat.use-width' ) ) ;
2018-04-08 21:20:46 +02:00
this . css _tweaks . toggle ( 'emote-alignment-padded' , emote _alignment === 1 ) ;
this . css _tweaks . toggle ( 'emote-alignment-baseline' , emote _alignment === 2 ) ;
2019-08-24 00:01:46 +02:00
this . emit ( ':update-chat-css' ) ;
2020-05-27 15:44:37 -04:00
this . emit ( 'site.player:fix-player' ) ;
2020-10-16 15:19:11 -04:00
this . emit ( 'site.layout:resize' ) ;
2017-11-13 01:23:39 -05:00
}
updateLineBorders ( ) {
const mode = this . chat . context . get ( 'chat.lines.borders' ) ;
this . css _tweaks . toggle ( 'chat-borders' , mode > 0 ) ;
this . css _tweaks . toggle ( 'chat-borders-3d' , mode === 2 ) ;
this . css _tweaks . toggle ( 'chat-borders-3d-inset' , mode === 3 ) ;
this . css _tweaks . toggle ( 'chat-borders-wide' , mode === 4 ) ;
}
2017-11-17 14:59:46 -05:00
updateMentionCSS ( ) {
const enabled = this . chat . context . get ( 'chat.filtering.highlight-mentions' ) ;
this . css _tweaks . toggle ( 'chat-mention-token' , this . chat . context . get ( 'chat.filtering.highlight-tokens' ) ) ;
2019-07-31 17:13:56 -04:00
const raw _color = this . chat . context . get ( 'chat.filtering.mention-color' ) ;
if ( raw _color ) {
this . css _tweaks . toggle ( 'chat-mention-bg' , false ) ;
this . css _tweaks . toggle ( 'chat-mention-bg-alt' , false ) ;
this . css _tweaks . toggle ( 'chat-mention-bg-custom' , true ) ;
this . css _tweaks . setVariable ( 'chat-mention-color' , this . inverse _colors . process ( raw _color ) ) ;
} else {
this . css _tweaks . toggle ( 'chat-mention-bg-custom' , false ) ;
this . css _tweaks . toggle ( 'chat-mention-bg' , enabled ) ;
this . css _tweaks . toggle ( 'chat-mention-bg-alt' , enabled && this . chat . context . get ( 'chat.lines.alternate' ) ) ;
}
2017-11-17 14:59:46 -05:00
}
2017-11-13 01:23:39 -05:00
2019-11-11 14:38:49 -05:00
updatePointsInfo ( inst ) {
const icon = inst ? . pointIcon ,
name = inst ? . pointName ;
if ( icon ) {
this . css _tweaks . set ( 'points-icon' , ` .ffz--points-icon:before { display: none }
. ffz -- points - icon : after {
display : inline - block ;
margin : 0 0.5 rem - 0.6 rem ;
background - image : url ( "${icon.url}" ) ;
background - image : $ { WEBKIT } image - set ( url ( "${icon.url}" ) 1 x , url ( "${icon.url2x}" ) 2 x , url ( "${icon.url4x}" ) 4 x ) ;
} ` );
} else
this . css _tweaks . delete ( 'points-icon' ) ;
this . point _name = name || null ;
}
2018-05-18 17:48:10 -04:00
async grabTypes ( ) {
2021-02-24 14:38:25 -05:00
if ( this . types _loaded )
return ;
const ct = await this . web _munch . findModule ( 'chat-types' ) ;
2018-07-13 14:32:12 -04:00
2021-02-24 14:38:25 -05:00
this . automod _types = ct ? . automod || AUTOMOD _TYPES ;
this . chat _types = ct ? . chat || CHAT _TYPES ;
this . message _types = ct ? . message || MESSAGE _TYPES ;
this . mod _types = ct ? . mod || MOD _TYPES ;
2018-07-13 20:39:01 -04:00
2018-07-13 14:32:12 -04:00
if ( ! ct )
return ;
2021-02-24 14:38:25 -05:00
this . types _loaded = true ;
const changes = [ ] ;
if ( ! shallow _object _equals ( this . automod _types , AUTOMOD _TYPES ) )
2018-07-13 14:32:12 -04:00
changes . push ( 'AUTOMOD_TYPES' ) ;
2021-02-24 14:38:25 -05:00
if ( ! shallow _object _equals ( this . chat _types , CHAT _TYPES ) )
2018-07-13 14:32:12 -04:00
changes . push ( 'CHAT_TYPES' ) ;
2021-02-24 14:38:25 -05:00
if ( ! shallow _object _equals ( this . message _types , MESSAGE _TYPES ) )
2018-07-13 14:32:12 -04:00
changes . push ( 'MESSAGE_TYPES' ) ;
2021-02-24 14:38:25 -05:00
if ( ! shallow _object _equals ( this . mod _types , MOD _TYPES ) )
2018-07-13 14:32:12 -04:00
changes . push ( 'MOD_TYPES' ) ;
if ( changes . length )
this . log . info ( 'Chat Types have changed from static mappings for categories:' , changes . join ( ' ' ) ) ;
2018-04-28 17:56:03 -04:00
}
2023-01-19 17:00:09 -05:00
updateDisableHandling ( ) {
this . disable _handling = this . chat . context . get ( 'chat.disable-handling' ) ;
}
2018-04-28 17:56:03 -04:00
onEnable ( ) {
this . on ( 'site.web_munch:loaded' , this . grabTypes ) ;
2020-03-05 18:21:11 -05:00
this . on ( 'site.web_munch:loaded' , this . defineClasses ) ;
2018-04-28 17:56:03 -04:00
this . grabTypes ( ) ;
2020-03-05 18:21:11 -05:00
this . defineClasses ( ) ;
2017-11-14 22:13:30 -05:00
2019-09-13 17:03:50 -04:00
this . chat . context . on ( 'changed:chat.points.show-callouts' , ( ) => {
this . InlineCallout . forceUpdate ( ) ;
this . CalloutSelector . forceUpdate ( ) ;
} ) ;
this . chat . context . on ( 'changed:chat.points.show-button' , ( ) => this . PointsButton . forceUpdate ( ) ) ;
this . chat . context . on ( 'changed:chat.points.show-rewards' , ( ) => {
this . PointsButton . forceUpdate ( ) ;
this . PointsClaimButton . forceUpdate ( ) ;
} ) ;
2020-03-06 01:44:33 -05:00
this . chat . context . on ( 'changed:chat.banners.hype-train' , this . cleanHighlights , this ) ;
this . chat . context . on ( 'changed:chat.subs.gift-banner' , this . cleanHighlights , this ) ;
this . chat . context . on ( 'changed:chat.banners.polls' , this . cleanHighlights , this ) ;
2021-02-15 17:48:30 -05:00
this . chat . context . on ( 'changed:chat.banners.prediction' , this . cleanHighlights , this ) ;
2021-09-04 20:14:58 -04:00
this . chat . context . on ( 'changed:chat.banners.drops' , this . cleanHighlights , this ) ;
2020-03-06 01:44:33 -05:00
2023-01-19 17:00:09 -05:00
this . chat . context . on ( 'changed:chat.disable-handling' , this . updateDisableHandling , this ) ;
2023-03-27 18:50:32 -04:00
this . chat . context . on ( 'changed:chat.banners.charity' , ( ) => {
this . ChatContainer . forceUpdate ( ) ;
} ) ;
2019-11-11 14:38:49 -05:00
this . chat . context . on ( 'changed:chat.subs.gift-banner' , ( ) => this . GiftBanner . forceUpdate ( ) , this ) ;
2021-04-01 12:05:29 -04:00
this . chat . context . on ( 'changed:chat.effective-width' , this . updateChatCSS , this ) ;
2019-05-24 22:46:34 -04:00
this . settings . main _context . on ( 'changed:chat.use-width' , this . updateChatCSS , this ) ;
2021-03-02 16:55:25 -05:00
this . chat . context . on ( 'changed:chat.actions.size' , this . updateChatCSS , this ) ;
2022-12-18 17:30:34 -05:00
this . chat . context . on ( 'changed:chat.actions.hover-size' , this . updateChatCSS , this ) ;
2017-11-14 18:48:56 -05:00
this . chat . context . on ( 'changed:chat.font-size' , this . updateChatCSS , this ) ;
2021-03-02 19:50:25 -05:00
this . chat . context . on ( 'changed:chat.timestamp-size' , this . updateChatCSS , this ) ;
2017-11-14 18:48:56 -05:00
this . chat . context . on ( 'changed:chat.font-family' , this . updateChatCSS , this ) ;
2018-04-08 15:51:50 -04:00
this . chat . context . on ( 'changed:chat.lines.emote-alignment' , this . updateChatCSS , this ) ;
2017-11-13 01:23:39 -05:00
this . chat . context . on ( 'changed:chat.adjustment-mode' , this . updateColors , this ) ;
this . chat . context . on ( 'changed:chat.adjustment-contrast' , this . updateColors , this ) ;
this . chat . context . on ( 'changed:theme.is-dark' , this . updateColors , this ) ;
2020-11-23 18:12:07 -05:00
this . chat . context . on ( 'changed:theme.color.background' , this . updateColors , this ) ;
this . chat . context . on ( 'changed:theme.color.chat-background' , this . updateColors , this ) ;
this . chat . context . on ( 'changed:theme.color.text' , this . updateColors , this ) ;
this . chat . context . on ( 'changed:theme.color.chat-text' , this . updateColors , this ) ;
2017-11-13 01:23:39 -05:00
this . chat . context . on ( 'changed:chat.lines.borders' , this . updateLineBorders , this ) ;
2017-11-17 14:59:46 -05:00
this . chat . context . on ( 'changed:chat.filtering.highlight-mentions' , this . updateMentionCSS , this ) ;
this . chat . context . on ( 'changed:chat.filtering.highlight-tokens' , this . updateMentionCSS , this ) ;
2019-07-31 17:13:56 -04:00
this . chat . context . on ( 'changed:chat.filtering.mention-color' , this . updateMentionCSS , this ) ;
2019-06-18 19:48:51 -04:00
this . chat . context . on ( 'changed:chat.pin-resubs' , val => {
if ( val ) {
this . updateInlineCallouts ( ) ;
this . updatePinnedCallouts ( ) ;
}
} , this ) ;
2017-11-13 01:23:39 -05:00
2019-09-23 12:17:51 -04:00
this . chat . context . on ( 'changed:chat.community-chest.show' , ( ) => {
this . CommunityChestBanner . forceUpdate ( ) ;
this . CalloutSelector . forceUpdate ( ) ;
} , this ) ;
2022-03-05 15:15:27 -05:00
this . chat . context . getChanges ( 'chat.emotes.2x' , val => {
this . css _tweaks . toggle ( 'big-emoji' , val > 1 ) ;
this . toggleEmoteJail ( ) ;
} ) ;
this . chat . context . getChanges ( 'chat.emotes.limit-size' , ( ) =>
this . toggleEmoteJail ( ) ) ;
2022-02-11 15:17:32 -05:00
2022-06-14 16:26:34 -04:00
this . chat . context . getChanges ( 'chat.banners.last-events' , val =>
this . css _tweaks . toggleHide ( 'last-x-events' , ! val ) ) ;
2021-11-05 18:01:28 -04:00
this . chat . context . getChanges ( 'chat.input.show-mod-view' , val =>
2022-12-07 16:52:07 -05:00
this . css _tweaks . toggleHide ( 'ci-mod-view' , ! val ) ) ;
this . chat . context . getChanges ( 'chat.input.show-highlight' , val =>
this . css _tweaks . toggleHide ( 'ci-highlight-settings' , ! val ) ) ;
this . chat . context . getChanges ( 'chat.input.show-shield' , val =>
this . css _tweaks . toggleHide ( 'ci-shield-mode' , ! val ) ) ;
2019-09-23 12:17:51 -04:00
2021-11-05 18:01:28 -04:00
this . chat . context . getChanges ( 'chat.lines.padding' , val =>
2017-11-13 01:23:39 -05:00
this . css _tweaks . toggle ( 'chat-padding' , val ) ) ;
2021-11-05 18:01:28 -04:00
this . chat . context . getChanges ( 'chat.bits.show' , val =>
2017-11-13 01:23:39 -05:00
this . css _tweaks . toggle ( 'hide-bits' , ! val ) ) ;
2021-11-05 18:01:28 -04:00
this . chat . context . getChanges ( 'chat.bits.show-pinned' , val =>
2017-11-13 01:23:39 -05:00
this . css _tweaks . toggleHide ( 'pinned-cheer' , ! val ) ) ;
2021-11-05 18:01:28 -04:00
this . chat . context . getChanges ( 'chat.filtering.deleted-style' , val => {
2019-04-18 03:16:19 -04:00
this . css _tweaks . toggle ( 'chat-deleted-strike' , val === 1 || val === 2 ) ;
this . css _tweaks . toggle ( 'chat-deleted-fade' , val < 2 ) ;
} ) ;
2018-08-02 14:29:18 -04:00
2021-11-05 18:01:28 -04:00
this . chat . context . getChanges ( 'chat.filtering.clickable-mentions' , val =>
this . css _tweaks . toggle ( 'clickable-mentions' , val ) ) ;
this . chat . context . getChanges ( 'chat.filtering.bold-mentions' , val =>
this . css _tweaks . toggle ( 'chat-mention-no-bold' , ! val ) ) ;
2019-05-31 16:05:50 -04:00
2021-11-05 18:01:28 -04:00
this . chat . context . getChanges ( 'chat.hide-community-highlights' , val =>
this . css _tweaks . toggleHide ( 'community-highlights' , val ) ) ;
this . chat . context . getChanges ( 'chat.lines.alternate' , val => {
this . css _tweaks . toggle ( 'chat-rows' , val ) ;
this . updateMentionCSS ( ) ;
} ) ;
2022-10-07 15:12:15 -04:00
this . chat . context . getChanges ( 'chat.input.show-elevate-your-message' , val =>
this . css _tweaks . toggleHide ( 'elevate-your-message' , ! val ) ) ;
2023-01-19 17:00:09 -05:00
this . updateDisableHandling ( ) ;
2017-11-14 18:48:56 -05:00
this . updateChatCSS ( ) ;
2017-11-13 01:23:39 -05:00
this . updateColors ( ) ;
this . updateLineBorders ( ) ;
2021-11-05 18:01:28 -04:00
//this.updateMentionCSS();
2017-11-13 01:23:39 -05:00
2023-04-20 00:55:52 -04:00
this . on ( 'chat:get-tab-commands' , e => {
e . commands . push ( {
name : 'reconnect' ,
description : 'Force chat to reconnect.' ,
permissionLevel : 0 ,
ffz _group : 'FrankerFaceZ'
} )
} ) ;
this . on ( 'chat:pre-send-message' , e => {
const msg = e . message ,
inst = e . _inst ;
if ( ! /^\/reconnect ?/i . test ( msg ) )
return ;
e . preventDefault ( ) ;
if ( ! inst . client ? . reconnect )
inst . addMessage ( {
type : t . chat _types . Notice ,
message : t . i18n . t ( 'chat.reconnect.unable' , 'FFZ is unable to force chat to reconnect.' )
} ) ;
else {
inst . addMessage ( {
type : t . chat _types . Notice ,
message : t . i18n . t ( 'chat.reconnect' , 'FFZ is forcing chat to reconnect...' )
} ) ;
inst . client . reconnect ( ) ;
}
} ) ;
2020-06-30 19:48:46 -04:00
this . RaidController . on ( 'mount' , this . wrapRaidController , this ) ;
this . RaidController . on ( 'update' , this . noAutoRaids , this ) ;
this . RaidController . ready ( ( cls , instances ) => {
for ( const inst of instances )
this . wrapRaidController ( inst ) ;
} ) ;
2019-06-18 19:48:51 -04:00
this . InlineCallout . on ( 'mount' , this . onInlineCallout , this ) ;
this . InlineCallout . on ( 'update' , this . onInlineCallout , this ) ;
this . InlineCallout . ready ( ( ) => this . updateInlineCallouts ( ) ) ;
this . PinnedCallout . on ( 'mount' , this . onPinnedCallout , this ) ;
this . PinnedCallout . on ( 'update' , this . onPinnedCallout , this ) ;
this . PinnedCallout . ready ( ( ) => this . updatePinnedCallouts ( ) ) ;
2019-09-13 17:03:50 -04:00
const t = this ;
2019-11-11 14:38:49 -05:00
this . PointsInfo . on ( 'mount' , this . updatePointsInfo , this ) ;
this . PointsInfo . on ( 'update' , this . updatePointsInfo , this ) ;
this . PointsInfo . on ( 'unmount' , ( ) => this . updatePointsInfo ( null ) ) ;
this . PointsInfo . ready ( ( ) => this . updatePointsInfo ( this . PointsInfo . first ) ) ;
this . GiftBanner . ready ( cls => {
const old _render = cls . prototype . render ;
cls . prototype . render = function ( ) {
if ( ! t . chat . context . get ( 'chat.subs.gift-banner' ) )
return null ;
return old _render . call ( this ) ;
}
this . GiftBanner . forceUpdate ( ) ;
} ) ;
2019-09-23 12:17:51 -04:00
this . CommunityChestBanner . ready ( cls => {
const old _render = cls . prototype . render ;
cls . prototype . render = function ( ) {
try {
if ( ! t . chat . context . get ( 'chat.community-chest.show' ) )
return null ;
} catch ( err ) {
t . log . capture ( err ) ;
t . log . error ( err ) ;
}
return old _render . call ( this ) ;
} ;
this . CommunityChestBanner . forceUpdate ( ) ;
} ) ;
2019-09-13 17:03:50 -04:00
this . InlineCallout . ready ( cls => {
const old _render = cls . prototype . render ;
cls . prototype . render = function ( ) {
try {
2019-10-14 00:58:00 -04:00
const callout = this . props ? . event ? . callout ,
ctype = callout ? . trackingType ;
if ( ctype === 'community_points_reward' && ! t . chat . context . get ( 'chat.points.show-callouts' ) )
return null ;
if ( ctype === 'prime_gift_bomb' && ! t . chat . context . get ( 'chat.community-chest.show' ) )
2019-09-13 17:03:50 -04:00
return null ;
2019-10-14 00:58:00 -04:00
if ( ctype === 'megacheer_emote_recipient' && ! t . chat . context . get ( 'chat.bits.show-rewards' ) )
2019-09-23 12:17:51 -04:00
return null ;
2019-09-13 17:03:50 -04:00
} catch ( err ) {
t . log . capture ( err ) ;
t . log . error ( err ) ;
}
return old _render . call ( this ) ;
}
this . InlineCallout . forceUpdate ( ) ;
2019-09-23 12:17:51 -04:00
} ) ;
2019-09-13 17:03:50 -04:00
this . CalloutSelector . ready ( cls => {
const old _render = cls . prototype . render ;
cls . prototype . render = function ( ) {
try {
const callout = this . props . callouts [ 0 ] || this . props . pinnedCallout ,
ctype = callout ? . event ? . type ;
2019-09-23 12:17:51 -04:00
if ( ctype === 'prime-gift-bomb-gifter' && ! t . chat . context . get ( 'chat.community-chest.show' ) )
return null ;
2019-09-13 17:03:50 -04:00
if ( ctype === 'community-points-rewards' && ! t . chat . context . get ( 'chat.points.show-callouts' ) )
return null ;
2019-10-14 00:58:00 -04:00
if ( ( ctype === 'mega-recipient-rewards' || ctype === 'mega-benefactor-rewards' ) && ! t . chat . context . get ( 'chat.bits.show-rewards' ) )
return null ;
2019-09-13 17:03:50 -04:00
} catch ( err ) {
t . log . capture ( err ) ;
t . log . error ( err ) ;
}
return old _render . call ( this ) ;
}
this . CalloutSelector . forceUpdate ( ) ;
} ) ;
this . PointsButton . ready ( cls => {
const old _render = cls . prototype . render ;
cls . prototype . render = function ( ) {
try {
if ( ! t . chat . context . get ( 'chat.points.show-button' ) )
return null ;
if ( ! t . chat . context . get ( 'chat.points.show-rewards' ) ) {
const aq = this . state . animationQueue ;
this . state . animationQueue = [ ] ;
const out = old _render . call ( this ) ;
this . state . animationQueue = aq ;
return out ;
}
} catch ( err ) {
t . log . capture ( err ) ;
t . log . error ( err ) ;
}
return old _render . call ( this ) ;
}
this . PointsButton . forceUpdate ( ) ;
} ) ;
this . PointsClaimButton . ready ( cls => {
2019-11-11 14:38:49 -05:00
cls . prototype . ffzHasOffer = function ( ) {
return ! this . props . hidden && ! this . state ? . error && this . getClaim ( ) != null ;
} ;
2019-09-13 17:03:50 -04:00
const old _render = cls . prototype . render ;
cls . prototype . render = function ( ) {
try {
2019-11-11 14:38:49 -05:00
if ( this . ffzHasOffer ( ) && ! this . _ffz _timer && t . chat . context . get ( 'chat.points.auto-rewards' ) )
2019-09-13 17:03:50 -04:00
this . _ffz _timer = setTimeout ( ( ) => {
this . _ffz _timer = null ;
2019-11-11 14:38:49 -05:00
if ( this . onClick && this . ffzHasOffer ( ) )
2019-09-13 17:03:50 -04:00
this . onClick ( ) ;
} , 1000 + Math . floor ( Math . random ( ) * 5000 ) ) ;
if ( ! t . chat . context . get ( 'chat.points.show-rewards' ) )
return null ;
} catch ( err ) {
t . log . capture ( err ) ;
t . log . error ( err ) ;
}
return old _render . call ( this ) ;
}
this . PointsClaimButton . forceUpdate ( ) ;
} ) ;
2017-11-13 01:23:39 -05:00
this . ChatController . on ( 'mount' , this . chatMounted , this ) ;
2019-06-14 21:24:48 -04:00
this . ChatController . on ( 'unmount' , this . chatUnmounted , this ) ;
2019-11-11 14:38:49 -05:00
//this.ChatController.on('receive-props', this.chatUpdated, this);
this . ChatController . on ( 'update' , this . chatUpdated , this ) ;
2017-11-13 01:23:39 -05:00
2018-08-28 19:13:26 -04:00
this . ChatService . ready ( ( cls , instances ) => {
this . wrapChatService ( cls ) ;
for ( const inst of instances ) {
inst . client . events . removeAll ( ) ;
2018-10-14 13:12:51 -04:00
inst . _ffzInstall ( ) ;
2020-03-31 18:14:27 -04:00
const channel = inst . joinedChannel ,
state = inst . client ? . session ? . channelstate ? . [ ` # ${ channel } ` ] ? . roomState ;
if ( state )
this . updateChatState ( state ) ;
2018-08-28 19:13:26 -04:00
inst . connectHandlers ( ) ;
2018-10-14 13:12:51 -04:00
inst . props . setChatConnectionAPI ( {
sendMessage : inst . sendMessage ,
_ffz _inst : inst
} ) ;
2018-08-28 19:13:26 -04:00
}
} ) ;
this . ChatBuffer . ready ( ( cls , instances ) => {
this . wrapChatBuffer ( cls ) ;
for ( const inst of instances ) {
const handler = inst . props . messageHandlerAPI ;
if ( handler )
handler . removeMessageHandler ( inst . handleMessage ) ;
inst . _ffzInstall ( ) ;
if ( handler )
handler . addMessageHandler ( inst . handleMessage ) ;
2020-03-31 18:14:27 -04:00
// We grab this from the chat client now.
/ * i f ( A r r a y . i s A r r a y ( i n s t . b u f f e r ) ) {
2019-08-27 16:18:12 -04:00
let i = inst . buffer . length ;
const ct = this . chat _types || CHAT _TYPES ;
while ( i -- ) {
const msg = inst . buffer [ i ] ;
if ( msg && msg . type === ct . RoomState && msg . state ) {
2020-03-31 18:14:27 -04:00
this . updateChatState ( msg . state ) ;
2019-08-27 16:18:12 -04:00
break ;
}
}
2020-03-31 18:14:27 -04:00
} * /
2019-08-27 16:18:12 -04:00
2018-08-28 19:13:26 -04:00
inst . props . setMessageBufferAPI ( {
addUpdateHandler : inst . addUpdateHandler ,
removeUpdateHandler : inst . removeUpdateHandler ,
getMessages : inst . getMessages ,
2019-06-03 19:47:41 -04:00
isPaused : inst . isPaused ,
setPaused : inst . setPaused ,
hasNewerLeft : inst . hasNewerLeft ,
loadNewer : inst . loadNewer ,
loadNewest : inst . loadNewest ,
2018-08-28 19:13:26 -04:00
_ffz _inst : inst
} ) ;
}
} ) ;
this . ChatBufferConnector . on ( 'mount' , this . connectorMounted , this ) ;
2019-11-11 14:38:49 -05:00
this . ChatBufferConnector . on ( 'update' , this . connectorUpdated , this ) ;
2018-08-28 19:13:26 -04:00
this . ChatBufferConnector . on ( 'unmount' , this . connectorUnmounted , this ) ;
this . ChatBufferConnector . ready ( ( cls , instances ) => {
for ( const inst of instances )
this . connectorMounted ( inst ) ;
} )
2017-11-13 01:23:39 -05:00
this . ChatController . ready ( ( cls , instances ) => {
2018-03-01 13:40:24 -05:00
const t = this ,
2018-03-03 16:38:50 -05:00
old _catch = cls . prototype . componentDidCatch ,
old _render = cls . prototype . render ;
2018-03-01 13:40:24 -05:00
// Try catching errors. With any luck, maybe we can
// recover from the error when we re-build?
cls . prototype . componentDidCatch = function ( err , info ) {
// Don't log infinitely if stuff gets super screwed up.
const errs = this . state . ffz _errors || 0 ;
if ( errs < 100 ) {
this . setState ( { ffz _errors : errs + 1 } ) ;
t . log . info ( 'Error within Chat' , err , info , errs ) ;
}
if ( old _catch )
return old _catch . call ( this , err , info ) ;
}
2018-03-03 16:38:50 -05:00
cls . prototype . render = function ( ) {
if ( this . state . ffz _errors > 0 ) {
const React = t . web _munch . getModule ( 'react' ) ,
2018-04-01 18:24:08 -04:00
createElement = React && React . createElement ;
2018-03-03 16:38:50 -05:00
2018-04-01 18:24:08 -04:00
if ( ! createElement )
2018-03-03 16:38:50 -05:00
return null ;
2018-04-01 18:24:08 -04:00
return createElement ( 'div' , {
2018-09-25 18:37:14 -04:00
className : 'tw-border-l tw-c-background-alt-2 tw-c-text-base tw-full-width tw-full-height tw-align-items-center tw-flex tw-flex-column tw-justify-content-center tw-relative'
2018-03-03 16:38:50 -05:00
} , 'There was an error displaying chat.' ) ;
} else
return old _render . call ( this ) ;
}
2018-08-28 19:13:26 -04:00
for ( const inst of instances )
2017-11-13 01:23:39 -05:00
this . chatMounted ( inst ) ;
} ) ;
this . ChatContainer . on ( 'mount' , this . containerMounted , this ) ;
2020-06-30 19:48:46 -04:00
this . ChatContainer . on ( 'unmount' , this . containerUnmounted , this ) ; //removeRoom, this);
2019-05-07 15:04:12 -04:00
this . ChatContainer . on ( 'update' , this . containerUpdated , this ) ;
2017-11-13 01:23:39 -05:00
this . ChatContainer . ready ( ( cls , instances ) => {
2018-03-01 13:40:24 -05:00
const t = this ,
2020-03-05 18:21:11 -05:00
old _render = cls . prototype . render ,
2018-03-01 13:40:24 -05:00
old _catch = cls . prototype . componentDidCatch ;
2018-03-03 16:38:50 -05:00
2023-03-27 18:50:32 -04:00
cls . prototype . ffzRender = function ( ) {
if ( t . chat . context . get ( 'chat.banners.charity' ) )
return old _render . call ( this ) ;
const cd = this . props . campaignData ;
this . props . campaignData = null ;
let result ;
try {
result = old _render . call ( this ) ;
} catch ( err ) {
this . props . campaignData = cd ;
throw err ;
}
this . props . campaignData = cd ;
return result ;
}
2020-03-05 18:21:11 -05:00
cls . prototype . render = function ( ) {
2020-03-05 18:26:45 -05:00
try {
2020-03-05 18:21:11 -05:00
if ( t . CommunityStackHandler ) {
const React = t . web _munch . getModule ( 'react' ) ,
2023-03-27 18:50:32 -04:00
out = this . ffzRender ( ) ,
2020-03-05 18:21:11 -05:00
thing = out ? . props ? . children ? . props ? . children ;
if ( React && Array . isArray ( thing ) )
thing . push ( React . createElement ( t . CommunityStackHandler ) ) ;
return out ;
}
2020-03-05 18:26:45 -05:00
} catch ( err ) {
2020-03-05 18:21:11 -05:00
// No op
2020-03-05 18:26:45 -05:00
}
2020-03-05 18:21:11 -05:00
2023-03-27 18:50:32 -04:00
return this . ffzRender ( ) ;
2020-03-05 18:21:11 -05:00
}
2018-03-01 13:40:24 -05:00
// Try catching errors. With any luck, maybe we can
// recover from the error when we re-build?
cls . prototype . componentDidCatch = function ( err , info ) {
// Don't log infinitely if stuff gets super screwed up.
const errs = this . state . ffz _errors || 0 ;
if ( errs < 100 ) {
this . setState ( { ffz _errors : errs + 1 } ) ;
t . log . info ( 'Error within Chat Container' , err , info , errs ) ;
}
if ( old _catch )
return old _catch . call ( this , err , info ) ;
}
2019-05-04 03:36:10 -04:00
2017-11-13 01:23:39 -05:00
for ( const inst of instances )
this . containerMounted ( inst ) ;
} ) ;
2020-07-22 21:31:41 -04:00
this . subpump . on ( ':pubsub-message' , event => {
2023-01-19 17:00:09 -05:00
if ( event . prefix !== 'community-points-channel-v1' || this . disable _handling )
2020-07-22 21:31:41 -04:00
return ;
const service = this . ChatService . first ,
message = event . message ,
data = message ? . data ? . redemption ;
if ( ! message || ! service || message . type !== 'reward-redeemed' || service . props . channelID != data ? . channel _id )
return ;
2020-07-26 17:50:14 -04:00
if ( data . user _input )
return ;
2020-07-22 21:31:41 -04:00
const reward = data . reward ? . id && get ( data . reward . id , service . props . rewardMap ) ;
if ( ! reward )
return ;
2020-08-17 13:33:30 -04:00
if ( ! this . chat . context . get ( 'chat.filtering.blocked-types' ) . has ( 'ChannelPointsReward' ) ) {
const msg = {
id : data . id ,
type : this . chat _types . Message ,
ffz _type : 'points' ,
ffz _reward : reward ,
2022-12-18 17:30:34 -05:00
ffz _reward _highlight : isHighlightedReward ( reward ) ,
2020-08-17 13:33:30 -04:00
messageParts : [ ] ,
user : {
id : data . user . id ,
login : data . user . login ,
displayName : data . user . display _name
} ,
timestamp : new Date ( message . data . timestamp || data . redeemed _at ) . getTime ( )
} ;
service . postMessageToCurrentChannel ( { } , msg ) ;
}
2020-07-22 21:31:41 -04:00
2021-03-22 18:19:09 -04:00
//event.preventDefault();
2020-07-22 21:31:41 -04:00
} ) ;
2020-06-30 19:48:46 -04:00
}
2017-11-13 01:23:39 -05:00
2020-06-30 19:48:46 -04:00
wrapRaidController ( inst ) {
if ( inst . _ffz _wrapped )
return this . noAutoRaids ( inst ) ;
2017-11-13 01:23:39 -05:00
2020-06-30 19:48:46 -04:00
inst . _ffz _wrapped = true ;
const t = this ,
old _handle _join = inst . handleJoinRaid ;
inst . handleJoinRaid = function ( event , ... args ) {
const raid _id = inst . props && inst . props . raid && inst . props . raid . id ;
if ( event && event . type && raid _id )
t . joined _raids . add ( raid _id ) ;
return old _handle _join . call ( this , event , ... args ) ;
}
this . noAutoRaids ( inst ) ;
}
noAutoRaids ( inst ) {
if ( this . settings . get ( 'channel.raids.no-autojoin' ) )
setTimeout ( ( ) => {
if ( inst . props && inst . props . raid && ! inst . isRaidCreator && inst . hasJoinedCurrentRaid ) {
const id = inst . props . raid . id ;
if ( this . joined _raids . has ( id ) )
return ;
this . log . info ( 'Automatically leaving raid:' , id ) ;
inst . handleLeaveRaid ( ) ;
}
} ) ;
2018-04-28 17:56:03 -04:00
}
2022-03-05 15:15:27 -05:00
toggleEmoteJail ( ) {
const bigger = this . chat . context . get ( 'chat.emotes.2x' ) ,
enabled = this . chat . context . get ( 'chat.emotes.limit-size' ) ;
this . css _tweaks . toggle ( 'big-emote-jail' , enabled && ! bigger ) ;
this . css _tweaks . toggle ( 'bigger-emote-jail' , enabled && bigger ) ;
}
2018-04-28 17:56:03 -04:00
2020-03-06 01:44:33 -05:00
cleanHighlights ( ) {
const types = {
'community_sub_gift' : this . chat . context . get ( 'chat.subs.gift-banner' ) ,
'megacheer' : this . chat . context . get ( 'chat.bits.show' ) ,
'hype_train' : this . chat . context . get ( 'chat.banners.hype-train' ) ,
2021-02-15 17:48:30 -05:00
'prediction' : this . chat . context . get ( 'chat.banners.prediction' ) ,
2021-07-26 16:33:43 -04:00
'poll' : this . chat . context . get ( 'chat.banners.polls' ) ,
2021-09-04 20:14:58 -04:00
'mw-drop-available' : this . chat . context . get ( 'chat.banners.drops' )
2020-03-06 01:44:33 -05:00
} ;
const highlights = this . community _stack ? . highlights ;
if ( ! Array . isArray ( highlights ) )
return ;
for ( const entry of highlights ) {
if ( ! entry || ! entry . event || ! entry . id )
continue ;
const type = entry . event . type ;
2021-09-04 20:14:58 -04:00
if ( type && has ( types , type ) && ! types [ type ] ) {
this . log . info ( 'Removing community highlight: ' , type , '#' , entry . id ) ;
2020-03-06 01:44:33 -05:00
this . community _dispatch ( {
type : 'remove-highlight' ,
id : entry . id
} ) ;
2021-09-04 20:14:58 -04:00
}
2020-03-06 01:44:33 -05:00
}
}
2020-03-05 18:21:11 -05:00
defineClasses ( ) {
if ( this . CommunityStackHandler )
return true ;
const t = this ,
React = this . web _munch . getModule ( 'react' ) ,
2021-02-24 14:38:25 -05:00
createElement = React && React . createElement ,
StackMod = this . web _munch . getModule ( 'highlightstack' ) ;
2020-03-05 18:21:11 -05:00
2021-02-24 14:38:25 -05:00
if ( ! createElement || ! StackMod )
2020-03-05 18:21:11 -05:00
return false ;
this . CommunityStackHandler = function ( ) {
2021-02-24 14:38:25 -05:00
const stack = React . useContext ( StackMod . stack ) ,
dispatch = React . useContext ( StackMod . dispatch ) ;
2020-03-05 18:21:11 -05:00
t . community _stack = stack ;
t . community _dispatch = dispatch ;
2020-03-06 01:44:33 -05:00
t . cleanHighlights ( ) ;
2020-03-05 18:21:11 -05:00
return null ;
}
this . ChatContainer . forceUpdate ( ) ;
}
2020-03-31 18:14:27 -04:00
updateChatState ( state ) {
const old _state = this . chat . context . get ( 'context.chat_state' ) || { } ;
if ( deep _equals ( state , old _state ) )
return ;
this . chat . context . updateContext ( {
chat _state : state
} ) ;
this . input . updateInput ( ) ;
}
2019-06-18 19:48:51 -04:00
updatePinnedCallouts ( ) {
for ( const inst of this . PinnedCallout . instances )
this . onPinnedCallout ( inst ) ;
}
onPinnedCallout ( inst ) {
if ( ! this . chat . context . get ( 'chat.pin-resubs' ) || inst . _ffz _pinned )
return ;
const props = inst . props ,
event = props && props . event ;
if ( props . pinned || ! event || event . type !== 'share-resub' )
return ;
this . log . info ( 'Automatically pinning re-sub notice.' ) ;
inst . _ffz _pinned = true ;
inst . pin ( ) ;
}
updateInlineCallouts ( ) {
for ( const inst of this . InlineCallout . instances )
this . onInlineCallout ( inst ) ;
}
onInlineCallout ( inst ) {
if ( ! this . chat . context . get ( 'chat.pin-resubs' ) || inst . _ffz _pinned )
return ;
const event = get ( 'props.event.callout' , inst ) ;
if ( ! event || event . cta !== 'Share' )
return ;
const onPin = get ( 'contextMenuProps.onPin' , event ) ;
if ( ! onPin )
return ;
this . log . info ( 'Automatically pinning re-sub notice.' ) ;
inst . _ffz _pinned = true ;
if ( inst . hideOnContextMenuAction )
inst . hideOnContextMenuAction ( onPin ) ( ) ;
else
onPin ( ) ;
}
2019-05-08 15:39:14 -04:00
tryUpdateBadges ( ) {
2019-05-08 22:47:38 -04:00
if ( ! this . _badge _timer )
this . _badge _timer = setTimeout ( ( ) => this . _tryUpdateBadges ( ) , 0 ) ;
}
_tryUpdateBadges ( ) {
if ( this . _badge _timer )
clearTimeout ( this . _badge _timer ) ;
this . _badge _timer = null ;
this . log . info ( 'Trying to update badge data from the chat container.' ) ;
2019-05-08 15:39:14 -04:00
const inst = this . ChatContainer . first ;
if ( inst )
this . containerUpdated ( inst , inst . props ) ;
}
2017-11-14 22:13:30 -05:00
wrapChatBuffer ( cls ) {
2018-08-28 19:13:26 -04:00
if ( cls . prototype . _ffz _was _here )
return ;
2018-07-14 14:13:28 -04:00
const t = this ,
2019-06-03 19:47:41 -04:00
old _clear = cls . prototype . clear ,
old _flush = cls . prototype . flushRawMessages ,
2018-08-28 19:13:26 -04:00
old _mount = cls . prototype . componentDidMount ;
2017-11-13 01:23:39 -05:00
2018-08-28 19:13:26 -04:00
cls . prototype . _ffzInstall = function ( ) {
if ( this . _ffz _installed )
return ;
this . _ffz _installed = true ;
2018-08-27 20:15:43 -04:00
2018-08-28 19:13:26 -04:00
const inst = this ,
old _handle = inst . handleMessage ,
old _set = inst . props . setMessageBufferAPI ;
inst . props . setMessageBufferAPI = function ( api ) {
if ( api )
api . _ffz _inst = inst ;
return old _set ( api ) ;
}
inst . handleMessage = function ( msg ) {
2018-07-16 15:07:22 -04:00
if ( msg ) {
try {
2018-10-16 02:13:14 -04:00
const types = t . chat _types || { } ,
2020-08-17 13:33:30 -04:00
mod _types = t . mod _types || { } ,
blocked _types = t . chat . context . get ( 'chat.filtering.blocked-types' ) ;
if ( blocked _types . has ( types [ msg . type ] ) )
return ;
2018-07-14 14:13:28 -04:00
2021-03-22 18:19:09 -04:00
if ( msg . type === types . ChannelPointsReward )
return ;
2019-06-13 22:56:50 -04:00
if ( msg . type === types . RewardGift && ! t . chat . context . get ( 'chat.bits.show-rewards' ) )
return ;
2018-07-16 15:07:22 -04:00
if ( msg . type === types . Message ) {
const m = t . chat . standardizeMessage ( msg ) ,
2021-05-01 14:36:20 -04:00
cont = inst . _ffz _connector ? ? inst . ffzGetConnector ( ) ;
2018-08-27 20:15:43 -04:00
2021-05-01 14:36:20 -04:00
let room _id = m . roomID = m . roomID ? m . roomID : cont ? . props ? . channelID ;
let room = m . roomLogin = m . roomLogin ? m . roomLogin : m . channel ? m . channel . slice ( 1 ) : cont ? . props ? . channelLogin ;
2018-08-28 19:13:26 -04:00
2018-07-16 15:07:22 -04:00
if ( ! room && room _id ) {
const r = t . chat . getRoom ( room _id , null , true ) ;
if ( r && r . login )
room = m . roomLogin = r . login ;
}
2018-07-14 14:13:28 -04:00
2021-05-01 14:36:20 -04:00
if ( ! room _id && room ) {
const r = t . chat . getRoom ( null , room , true ) ;
if ( r && r . id )
room _id = m . roomID = r . id ;
}
2021-04-30 17:38:49 -04:00
const u = t . site . getUser ( ) ;
2018-07-16 15:07:22 -04:00
if ( u && cont ) {
u . moderator = cont . props . isCurrentUserModerator ;
u . staff = cont . props . isStaff ;
}
2021-04-30 17:38:49 -04:00
m . ffz _tokens = m . ffz _tokens || t . chat . tokenizeMessage ( m , u , true ) ;
2021-03-22 18:19:09 -04:00
if ( m . ffz _removed )
return ;
2018-07-16 15:07:22 -04:00
2021-04-30 17:38:49 -04:00
if ( t . hasListeners ( 'chat:receive-message' ) ) {
const event = new FFZEvent ( {
message : m ,
2021-05-01 14:36:20 -04:00
inst ,
2021-04-30 17:38:49 -04:00
channel : room ,
channelID : room _id
} ) ;
2018-07-16 15:07:22 -04:00
2021-04-30 17:38:49 -04:00
t . emit ( 'chat:receive-message' , event ) ;
if ( event . defaultPrevented || m . ffz _removed )
return ;
}
2018-07-14 14:13:28 -04:00
2021-07-12 13:46:04 -04:00
} / * else if ( msg . type === types . ModerationAction ) {
t . emit ( 'chat:mod-user' , msg . moderationActionType , )
} * / e l s e i f ( m s g . t y p e = = = t y p e s . M o d e r a t i o n ) {
t . emit ( 'chat:mod-user' , msg . moderationType , msg . userLogin , msg . targetMessageID , msg ) ;
2023-04-19 17:19:10 -04:00
// Special handling
if ( ! inst . props . isCurrentUserModerator ) {
const type = msg . moderationType ,
target = msg . userLogin ;
// Whee~
let mat ;
if ( type === mod _types . Ban )
mat = 'ban' ;
else if ( type === mod _types . Timeout )
mat = 'timeout' ;
else if ( type === mod _types . Delete )
mat = 'delete' ;
if ( mat )
msg . moderationActionType = mat ;
// Handle moderation events ourself if it's not
// a delete, so that we can pass the action info.
if ( ! inst . moderatedUsers . has ( target ) && type !== mod _types . Delete ) {
inst . moderateBuffers (
[
inst . buffer ,
inst . delayedMessageBuffer . map ( e => e . event )
] ,
target ,
msg
) ;
inst . delayedMessageBuffer . push ( {
event : msg ,
time : Date . now ( ) ,
shouldDelay : false
} ) ;
return ;
}
}
2021-07-12 13:46:04 -04:00
} / * else if ( msg . type === types . ModerationAction && false && inst . markUserEventDeleted && inst . unsetModeratedUser ) {
2019-09-23 12:17:51 -04:00
if ( ! ( ( ! msg . level || ! msg . level . length ) && msg . targetUserLogin && msg . targetUserLogin === inst . props . currentUserLogin ) ) {
//t.log.info('Moderation Action', msg);
if ( ! inst . props . isCurrentUserModerator )
2019-04-18 21:07:11 -04:00
return ;
2019-09-23 12:17:51 -04:00
const mod _action = msg . moderationActionType ;
if ( mod _action === 'ban' || mod _action === 'timeout' || mod _action === 'delete' ) {
const user = msg . targetUserLogin ;
if ( inst . moderatedUsers . has ( user ) )
return ;
const do _remove = t . chat . context . get ( 'chat.filtering.remove-deleted' ) === 3 ;
if ( do _remove ) {
const len = inst . buffer . length ,
target _id = msg . messageID ;
inst . buffer = inst . buffer . filter ( m =>
m . type !== types . Message || ! m . user || m . user . userLogin !== user ||
( target _id && m . id !== target _id )
) ;
if ( len !== inst . buffer . length && ! inst . props . isBackground )
inst . notifySubscribers ( ) ;
2020-08-15 15:45:50 -04:00
inst . moderateBuffers ( [
inst . delayedMessageBuffer . map ( e => e . event )
] , user , msg ) ;
2019-09-23 12:17:51 -04:00
} else
2020-08-15 15:45:50 -04:00
inst . moderateBuffers ( [
inst . buffer ,
inst . delayedMessageBuffer . map ( e => e . event )
] , user , msg ) ;
2019-09-23 12:17:51 -04:00
inst . delayedMessageBuffer . push ( {
event : msg ,
time : Date . now ( ) ,
shouldDelay : false
} ) ;
2019-04-18 21:07:11 -04:00
2019-09-23 12:17:51 -04:00
return ;
}
2019-04-18 21:07:11 -04:00
}
2019-04-18 03:16:19 -04:00
2020-08-15 15:45:50 -04:00
} else if ( msg . type === types . Moderation && false && inst . unsetModeratedUser ) {
2019-04-18 21:07:11 -04:00
//t.log.info('Moderation', msg);
if ( inst . props . isCurrentUserModerator )
2018-07-16 15:07:22 -04:00
return ;
2018-07-14 14:13:28 -04:00
2019-04-18 21:07:11 -04:00
const user = msg . userLogin ;
if ( inst . moderatedUsers . has ( user ) )
return ;
2018-07-14 14:13:28 -04:00
2019-04-18 21:07:11 -04:00
const mod _action = msg . moderationType ;
let new _action ;
if ( mod _action === mod _types . Ban )
new _action = 'ban' ;
else if ( mod _action === mod _types . Delete )
new _action = 'delete' ;
else if ( mod _action === mod _types . Unban )
new _action = 'unban' ;
else if ( mod _action === mod _types . Timeout )
new _action = 'timeout' ;
if ( new _action )
msg . moderationActionType = new _action ;
const do _remove = t . chat . context . get ( 'chat.filtering.remove-deleted' ) === 3 ;
2018-07-16 15:07:22 -04:00
if ( do _remove ) {
2019-04-18 21:07:11 -04:00
const len = inst . buffer . length ,
target _id = msg . targetMessageID ;
inst . buffer = inst . buffer . filter ( m =>
m . type !== types . Message || ! m . user || m . user . userLogin !== user ||
( target _id && m . id !== target _id )
) ;
2018-08-28 19:13:26 -04:00
if ( len !== inst . buffer . length && ! inst . props . isBackground )
inst . notifySubscribers ( ) ;
2018-07-16 15:07:22 -04:00
2020-08-15 15:45:50 -04:00
inst . moderateBuffers ( [
inst . delayedMessageBuffer . map ( e => e . event )
] , user , msg ) ;
2019-04-18 21:07:11 -04:00
2018-07-16 15:07:22 -04:00
} else
2020-08-15 15:45:50 -04:00
inst . moderateBuffers ( [
inst . buffer ,
inst . delayedMessageBuffer . map ( e => e . event )
] , user , msg ) ;
2018-07-16 15:07:22 -04:00
2019-04-18 21:07:11 -04:00
inst . delayedMessageBuffer . push ( {
event : msg ,
time : Date . now ( ) ,
shouldDelay : false
} ) ;
2018-07-16 15:07:22 -04:00
2019-04-18 21:07:11 -04:00
return ;
2018-07-14 14:13:28 -04:00
2021-07-12 13:46:04 -04:00
} * / e l s e i f ( m s g . t y p e = = = t y p e s . C l e a r ) {
2018-07-16 15:07:22 -04:00
if ( t . chat . context . get ( 'chat.filtering.ignore-clear' ) )
msg = {
2019-04-18 03:16:19 -04:00
type : types . Info ,
message : t . i18n . t ( 'chat.ignore-clear' , 'An attempt by a moderator to clear chat was ignored.' )
2018-07-16 15:07:22 -04:00
}
2021-07-12 13:46:04 -04:00
else
t . emit ( 'chat:clear-chat' , msg ) ;
2018-07-16 15:07:22 -04:00
}
} catch ( err ) {
2018-08-28 19:13:26 -04:00
t . log . error ( 'Error processing chat event.' , err ) ;
t . log . capture ( err , { extra : { msg } } ) ;
2018-07-16 15:07:22 -04:00
}
2018-07-14 14:13:28 -04:00
}
2018-07-16 15:07:22 -04:00
2018-08-28 19:13:26 -04:00
return old _handle . call ( inst , msg ) ;
2018-07-16 15:07:22 -04:00
}
2020-08-15 15:45:50 -04:00
/ * i n s t . f f z M o d e r a t e B u f f e r = f u n c t i o n ( b u f f e r s , e v e n t ) {
2019-04-18 21:07:11 -04:00
const mod _types = t . mod _types || { } ,
2020-08-15 15:45:50 -04:00
ctypes = t . chat _types || { } ,
2019-04-18 21:07:11 -04:00
mod _type = event . moderationActionType ,
user _login = event . targetUserLogin || event . userLogin ,
mod _login = event . createdByLogin ,
target _id = event . targetMessageID || event . messageID ;
let deleted _count = 0 , last _msg ;
const is _delete = mod _type === mod _types . Delete ,
updater = m => {
if ( m . event )
m = m . event ;
2020-08-15 15:45:50 -04:00
/ * i f ( m . m e s s a g e & & m . t y p e i n [ c t y p e s . C h a n n e l P o i n t s R e w a r d , c t y p e s . R e s u b s c r i p t i o n , c t y p e s . R i t u a l ] )
m = m . message ; /
if ( m . reply ) {
if ( target _id ? target _id === m . reply . parentMsgId : ( user _login && user _login === m . reply . parentUserLogin ) )
m . reply = {
... m . reply ,
parentDeleted : true
}
}
if ( ! user _login || ! m . user || user _login !== m . user . userLogin || ! m . messageParts )
2019-04-18 21:07:11 -04:00
return ;
2020-08-15 15:45:50 -04:00
if ( ! m || m . deleted )
2019-04-18 21:07:11 -04:00
return ;
2020-08-15 15:45:50 -04:00
if ( target _id && m . id !== target _id )
return ;
2019-04-18 21:07:11 -04:00
2020-08-15 15:45:50 -04:00
m . deleted = true ;
m . banned = mod _type === mod _types . Ban ;
2019-04-18 21:07:11 -04:00
2020-08-15 15:45:50 -04:00
last _msg = m ;
deleted _count ++ ;
m . modLogin = mod _login ;
m . modActionType = mod _type ;
m . duration = event . duration ;
2019-04-18 21:07:11 -04:00
} ;
for ( const buffer of buffers )
if ( buffer . some ( updater ) )
break ;
//t.log.info('Moderate Buffer', mod_type, user_login, mod_login, target_id, deleted_count, last_msg);
if ( last _msg )
last _msg . deletedCount = deleted _count ;
2020-08-15 15:45:50 -04:00
} * /
2019-04-18 21:07:11 -04:00
2019-06-03 19:47:41 -04:00
inst . setPaused = function ( paused ) {
if ( inst . paused === paused )
return ;
2019-04-18 21:07:11 -04:00
2019-06-03 19:47:41 -04:00
inst . paused = paused ;
if ( ! paused ) {
inst . slidingWindowEnd = Math . min ( inst . buffer . length , t . chat . context . get ( 'chat.scrollback-length' ) ) ;
if ( ! inst . props . isBackground )
inst . notifySubscribers ( ) ;
}
}
2019-04-18 21:07:11 -04:00
2019-06-03 19:47:41 -04:00
inst . loadNewer = function ( ) {
if ( ! inst . hasNewerLeft ( ) )
return ;
2018-08-28 19:13:26 -04:00
2019-06-03 19:47:41 -04:00
const end = Math . min ( inst . buffer . length , inst . slidingWindowEnd + 40 ) ,
start = Math . max ( 0 , end - t . chat . context . get ( 'chat.scrollback-length' ) ) ;
2018-08-28 19:13:26 -04:00
2019-06-03 19:47:41 -04:00
inst . clear ( inst . buffer . length - start ) ;
inst . slidingWindowEnd = end - start ;
if ( ! inst . props . isBackground )
inst . notifySubscribers ( ) ;
}
inst . loadNewest = function ( ) {
if ( ! inst . hasNewerLeft ( ) )
return ;
const max _size = t . chat . context . get ( 'chat.scrollback-length' ) ;
2018-08-28 19:13:26 -04:00
2019-06-03 19:47:41 -04:00
inst . clear ( max _size ) ;
inst . slidingWindowEnd = Math . min ( max _size , inst . buffer . length ) ;
if ( ! inst . props . isBackground )
inst . notifySubscribers ( ) ;
2018-08-28 19:13:26 -04:00
}
2019-06-04 00:50:21 -04:00
inst . getMessages = function ( ) {
return inst . buffer . slice ( 0 , inst . slidingWindowEnd + ( inst . ffz _extra || 0 ) ) ;
}
2018-08-28 19:13:26 -04:00
}
cls . prototype . componentDidMount = function ( ) {
try {
this . _ffzInstall ( ) ;
} catch ( err ) {
t . log . error ( 'Error installing FFZ features onto chat buffer.' , err ) ;
}
return old _mount . call ( this ) ;
}
2019-06-03 19:47:41 -04:00
cls . prototype . clear = function ( count ) {
try {
if ( count == null )
count = 0 ;
const max _size = t . chat . context . get ( 'chat.scrollback-length' ) ;
if ( ! this . isPaused ( ) && count > max _size )
count = max _size ;
if ( count <= 0 ) {
2019-06-04 00:50:21 -04:00
this . ffz _extra = 0 ;
2019-06-03 19:47:41 -04:00
this . buffer = [ ] ;
this . delayedMessageBuffer = [ ] ;
this . paused = false ;
} else {
const buffer = this . buffer ,
ct = t . chat _types || CHAT _TYPES ,
target = buffer . length - count ;
if ( target > 0 ) {
let removed = 0 , last ;
for ( let i = 0 ; i < target ; i ++ )
if ( buffer [ i ] && ! NULL _TYPES . includes ( ct [ buffer [ i ] . type ] ) ) {
removed ++ ;
last = i ;
}
2018-07-16 15:07:22 -04:00
2019-06-04 00:50:21 -04:00
// When we remove less then expected, we want to keep track
// of that so we can return the extra messages from getMessages.
this . buffer = buffer . slice ( removed % 2 !== 0 ? Math . max ( target - 4 , last ) : target ) ;
2019-06-04 12:20:44 -04:00
this . ffz _extra = buffer . length - count ;
2018-07-16 15:07:22 -04:00
2019-06-04 00:50:21 -04:00
} else {
this . ffz _extra = 0 ;
2019-06-03 19:47:41 -04:00
this . buffer = this . buffer . slice ( 0 ) ;
2019-06-04 00:50:21 -04:00
}
2018-07-16 15:07:22 -04:00
2019-06-03 19:47:41 -04:00
if ( this . paused && this . buffer . length >= 900 )
this . setPaused ( false ) ;
}
} catch ( err ) {
t . log . error ( 'Error running clear' , err ) ;
return old _clear . call ( this , count ) ;
2018-07-14 14:13:28 -04:00
}
2019-06-03 19:47:41 -04:00
}
2021-05-01 14:36:20 -04:00
cls . prototype . ffzGetConnector = function ( ) {
if ( this . _ffz _connector )
return this . _ffz _connector ;
const now = Date . now ( ) ;
if ( now - ( this . _ffz _connect _tried || 0 ) > 5000 ) {
this . _ffz _connect _tried = now ;
const thing = t . ChatBufferConnector . first ;
if ( thing ? . props ? . messageBufferAPI ? . _ffz _inst === this )
return this . _ffz _connector = thing ;
}
}
2019-06-03 19:47:41 -04:00
cls . prototype . flushRawMessages = function ( ) {
try {
const out = [ ] ,
2020-11-23 18:12:07 -05:00
ct = t . chat _types || CHAT _TYPES ,
2019-06-03 19:47:41 -04:00
now = Date . now ( ) ,
raw _delay = t . chat . context . get ( 'chat.delay' ) ,
delay = raw _delay === - 1 ? this . delayDuration : raw _delay ,
first = now - delay ,
see _deleted = this . shouldSeeBlockedAndDeletedMessages || this . props && this . props . shouldSeeBlockedAndDeletedMessages ,
has _newer = this . hasNewerLeft ( ) ,
paused = this . isPaused ( ) ,
max _size = t . chat . context . get ( 'chat.scrollback-length' ) ,
2021-04-30 17:38:49 -04:00
do _remove = t . chat . context . get ( 'chat.filtering.remove-deleted' ) ,
2021-05-01 14:36:20 -04:00
want _event = t . hasListeners ( 'chat:buffer-message' ) ;
2019-06-03 19:47:41 -04:00
let added = 0 ,
buffered = this . slidingWindowEnd ,
2021-05-01 14:36:20 -04:00
changed = false ,
event ;
2019-06-03 19:47:41 -04:00
for ( const msg of this . delayedMessageBuffer ) {
if ( msg . time <= first || ! msg . shouldDelay ) {
if ( do _remove !== 0 && ( do _remove > 1 || ! see _deleted ) && this . isDeletable ( msg . event ) && msg . event . deleted )
continue ;
2021-05-01 14:36:20 -04:00
if ( want _event ) {
if ( ! event ) {
event = new FFZEvent ( ) ;
2021-04-30 17:38:49 -04:00
2021-05-01 14:36:20 -04:00
const cont = this . _ffz _connector ? ? this . ffzGetConnector ( ) ,
room _id = cont && cont . props . channelID ;
event . inst = this ;
event . channelID = room _id ;
if ( room _id ) {
const r = t . chat . getRoom ( room _id , null , true ) ;
if ( r && r . login )
event . channel = r . login ;
}
} else
event . _reset ( ) ;
event . message = msg . event ;
2021-04-30 17:38:49 -04:00
t . emit ( 'chat:buffer-message' , event ) ;
if ( event . defaultPrevented || msg . event . ffz _removed )
continue ;
}
2020-11-23 18:12:07 -05:00
const last = this . buffer [ this . buffer . length - 1 ] ,
type = last ? . type ;
2021-04-30 17:38:49 -04:00
if ( ! (
! this . props . isLoadingHistoricalMessages &&
! this . props . historicalMessages ||
type !== ct . Connected ||
msg . event . type === ct . Connected ||
this . buffer . find ( e => e . type === ct . LiveMessageSeparator )
) )
this . buffer . push ( {
type : ct . LiveMessageSeparator ,
id : 'live-message-separator'
} ) ;
/ * i f ( t y p e = = = c t . C o n n e c t e d ) {
2020-11-23 18:12:07 -05:00
const non _null = this . buffer . filter ( x => x && ct [ x . type ] && ! NULL _TYPES . includes ( ct [ x . type ] ) ) ;
if ( non _null . length > 1 )
this . buffer . push ( {
type : ct . LiveMessageSeparator ,
id : 'live-message-separator'
} ) ;
2021-04-30 17:38:49 -04:00
} * /
2020-11-23 18:12:07 -05:00
2019-06-03 19:47:41 -04:00
this . buffer . push ( msg . event ) ;
changed = true ;
if ( ! this . paused ) {
if ( this . buffer . length > max _size )
added ++ ;
else
buffered ++ ;
}
2018-07-14 14:13:28 -04:00
2019-06-03 19:47:41 -04:00
} else
out . push ( msg ) ;
}
this . delayedMessageBuffer = out ;
if ( changed ) {
this . clear ( Math . min ( 900 , this . buffer . length - added ) ) ;
if ( ! ( added === 0 && buffered === this . slidingWindowEnd && has _newer === this . hasNewerLeft ( ) && paused === this . isPaused ( ) ) ) {
this . slidingWindowEnd = buffered ;
if ( ! this . props . isBackground )
this . notifySubscribers ( ) ;
}
}
} catch ( err ) {
t . log . error ( 'Error running flush.' , err ) ;
return old _flush . call ( this ) ;
}
2017-11-13 01:23:39 -05:00
}
}
2021-07-12 20:00:14 -04:00
addNotice ( room , message ) {
if ( ! room )
return false ;
if ( room . startsWith ( '#' ) )
room = room . slice ( 1 ) ;
room = room . toLowerCase ( ) ;
for ( const inst of this . ChatService . instances ) {
2023-04-24 15:09:21 -04:00
if ( room === '*' || inst . props . channelLogin . toLowerCase ( ) === room ) {
2021-07-12 20:00:14 -04:00
inst . addMessage ( {
type : this . chat _types . Notice ,
message
} ) ;
return true ;
}
}
return false ;
}
2018-04-28 17:56:03 -04:00
sendMessage ( room , message ) {
2018-08-28 19:13:26 -04:00
const service = this . ChatService . first ;
2018-04-28 17:56:03 -04:00
2018-04-29 01:28:19 -04:00
if ( ! service || ! room )
2018-04-28 17:56:03 -04:00
return null ;
if ( room . startsWith ( '#' ) )
room = room . slice ( 1 ) ;
2018-08-28 19:13:26 -04:00
if ( room . toLowerCase ( ) !== service . props . channelLogin . toLowerCase ( ) )
2018-04-29 01:28:19 -04:00
return service . client . sendCommand ( room , message ) ;
2018-04-28 17:56:03 -04:00
service . sendMessage ( message ) ;
}
2021-12-28 13:46:26 -05:00
scheduleMystery ( mystery ) { // eslint-disable-line class-methods-use-this
if ( ! mystery . line )
return ;
if ( mystery . _timer )
return ;
mystery . _timer = setTimeout ( ( ) => requestAnimationFrame ( ( ) => {
mystery . _timer = null ;
if ( mystery . line )
mystery . line . forceUpdate ( ) ;
} ) , 250 ) ;
}
2017-11-13 01:23:39 -05:00
wrapChatService ( cls ) {
const t = this ,
2018-10-14 13:12:51 -04:00
old _mount = cls . prototype . componentDidMount ,
2018-08-28 19:13:26 -04:00
old _handler = cls . prototype . connectHandlers ;
2017-11-13 01:23:39 -05:00
cls . prototype . _ffz _was _here = true ;
2018-10-14 13:12:51 -04:00
cls . prototype . _ffzInstall = function ( ) {
if ( this . _ffz _installed )
return ;
this . _ffz _installed = true ;
const inst = this ,
2020-08-12 16:10:06 -04:00
old _send = this . sendMessage ,
addMessage = ( ... args ) => inst . addMessage ( ... args ) ,
sendMessage = ( msg , extra ) => inst . sendMessage ( msg , extra ) ;
2018-10-14 13:12:51 -04:00
2020-08-12 16:10:06 -04:00
inst . sendMessage = function ( msg , extra ) {
2019-12-08 16:09:37 -05:00
msg = msg . replace ( /\s+/g , ' ' ) ;
2018-10-14 13:12:51 -04:00
2023-05-19 15:02:25 -04:00
if ( msg . startsWith ( '/ffz:' ) ) {
2023-04-24 15:09:21 -04:00
msg = msg . slice ( 5 ) . trim ( ) ;
const idx = msg . indexOf ( ' ' ) ;
let subcmd ;
if ( idx === - 1 ) {
subcmd = msg ;
msg = '' ;
} else {
subcmd = msg . slice ( 0 , idx ) ;
msg = msg . slice ( idx + 1 ) . trimStart ( ) ;
}
const event = new FFZEvent ( {
command : subcmd ,
message : msg ,
extra ,
context : t . chat . context ,
channel : inst . props . channelLogin ,
_inst : inst ,
addMessage ,
sendMessage
2023-04-19 17:19:10 -04:00
} ) ;
2023-04-24 15:09:21 -04:00
const topic = ` chat:ffz-command: ${ subcmd } ` ,
listeners = t . listeners ( topic ) ;
if ( listeners ? . length > 0 )
t . emit ( topic , event ) ;
else
inst . addMessage ( {
type : t . chat _types . Notice ,
2023-05-19 15:02:25 -04:00
message : t . i18n . t ( 'chat.ffz-command.invalid' , 'No such command: /ffz:{subcmd}' , { subcmd } )
2023-04-24 15:09:21 -04:00
} ) ;
2023-04-19 17:19:10 -04:00
return false ;
}
2018-10-14 13:12:51 -04:00
const event = new FFZEvent ( {
message : msg ,
2020-08-12 16:10:06 -04:00
extra ,
2022-02-11 15:17:32 -05:00
context : t . chat . context ,
2020-08-12 16:10:06 -04:00
channel : inst . props . channelLogin ,
2023-04-20 00:55:52 -04:00
_inst : inst ,
2020-08-12 16:10:06 -04:00
addMessage ,
sendMessage
2018-10-14 13:12:51 -04:00
} ) ;
t . emit ( 'chat:pre-send-message' , event ) ;
if ( event . defaultPrevented )
return ;
2020-08-12 16:10:06 -04:00
return old _send . call ( this , event . message , event . extra ) ;
2018-10-14 13:12:51 -04:00
}
}
cls . prototype . componentDidMount = function ( ) {
try {
this . _ffzInstall ( ) ;
} catch ( err ) {
t . log . error ( 'Error installing FFZ features onto chat service.' , err ) ;
}
return old _mount . call ( this ) ;
}
2017-11-13 01:23:39 -05:00
cls . prototype . connectHandlers = function ( ... args ) {
if ( ! this . _ffz _init ) {
2018-04-28 17:56:03 -04:00
const i = this ;
2017-11-13 01:23:39 -05:00
2018-04-28 17:56:03 -04:00
for ( const key of MISBEHAVING _EVENTS ) {
2017-11-13 01:23:39 -05:00
const original = this [ key ] ;
if ( original )
this [ key ] = function ( e , t ) {
i . _wrapped = e ;
const ret = original . call ( i , e , t ) ;
i . _wrapped = null ;
return ret ;
}
}
2022-03-31 17:15:43 -04:00
const old _announce = this . onAnnouncementEvent ;
this . onAnnouncementEvent = function ( e ) {
2023-01-19 17:00:09 -05:00
//console.log('announcement', e);
2022-03-31 17:15:43 -04:00
return old _announce . call ( this , e ) ;
}
2018-06-27 14:13:59 -04:00
2019-02-05 14:24:45 -05:00
const old _sub = this . onSubscriptionEvent ;
this . onSubscriptionEvent = function ( e ) {
try {
2020-08-17 13:33:30 -04:00
if ( t . chat . context . get ( 'chat.filtering.blocked-types' ) . has ( 'Subscription' ) )
return ;
2023-01-19 17:00:09 -05:00
if ( t . disable _handling )
return old _sub . call ( i , e ) ;
2019-02-05 14:24:45 -05:00
if ( t . chat . context . get ( 'chat.subs.show' ) < 3 )
return ;
e . body = '' ;
const out = i . convertMessage ( { message : e } ) ;
out . ffz _type = 'resub' ;
2021-09-04 20:14:58 -04:00
out . gift _theme = e . giftTheme ;
out . sub _goal = i . getGoalData ? i . getGoalData ( e . goalData ) : null ;
2019-02-05 14:24:45 -05:00
out . sub _plan = e . methods ;
return i . postMessageToCurrentChannel ( e , out ) ;
} catch ( err ) {
t . log . capture ( err , { extra : e } ) ;
return old _sub . call ( i , e ) ;
}
}
2019-08-27 16:18:12 -04:00
const old _state = this . onRoomStateEvent ;
this . onRoomStateEvent = function ( e ) {
try {
const channel = e . channel ,
current = t . chat . context . get ( 'context.channel' ) ;
if ( channel && ( channel === current || channel === ` # ${ current } ` ) )
2020-03-31 18:14:27 -04:00
t . updateChatState ( e . state ) ;
2019-08-27 16:18:12 -04:00
} catch ( err ) {
t . log . capture ( err , { extra : e } ) ;
}
return old _state . call ( i , e ) ;
}
2017-11-23 02:49:23 -05:00
const old _resub = this . onResubscriptionEvent ;
this . onResubscriptionEvent = function ( e ) {
try {
2020-08-17 13:33:30 -04:00
if ( t . chat . context . get ( 'chat.filtering.blocked-types' ) . has ( 'Resubscription' ) )
return ;
2023-01-19 17:00:09 -05:00
if ( t . disable _handling )
return old _resub . call ( i , e ) ;
2019-02-05 14:24:45 -05:00
if ( t . chat . context . get ( 'chat.subs.show' ) < 2 && ! e . body )
return ;
2017-11-23 02:49:23 -05:00
const out = i . convertMessage ( { message : e } ) ;
out . ffz _type = 'resub' ;
2021-09-04 20:14:58 -04:00
out . gift _theme = e . giftTheme ;
out . sub _goal = i . getGoalData ? i . getGoalData ( e . goalData ) : null ;
2019-01-31 19:58:21 -05:00
out . sub _cumulative = e . cumulativeMonths || 0 ;
out . sub _streak = e . streakMonths || 0 ;
out . sub _share _streak = e . shouldShareStreakTenure ;
2017-11-23 02:49:23 -05:00
out . sub _months = e . months ;
out . sub _plan = e . methods ;
2019-01-31 19:58:21 -05:00
//t.log.info('Resub Event', e, out);
2018-04-28 17:56:03 -04:00
return i . postMessageToCurrentChannel ( e , out ) ;
2017-11-23 02:49:23 -05:00
} catch ( err ) {
2018-04-11 17:05:31 -04:00
t . log . capture ( err , { extra : e } ) ;
2017-11-23 02:49:23 -05:00
return old _resub . call ( i , e ) ;
}
}
2019-02-05 14:24:45 -05:00
const mysteries = this . ffz _mysteries = { } ;
const old _subgift = this . onSubscriptionGiftEvent ;
this . onSubscriptionGiftEvent = function ( e ) {
try {
2020-08-17 13:33:30 -04:00
if ( t . chat . context . get ( 'chat.filtering.blocked-types' ) . has ( 'SubGift' ) )
return ;
2023-01-19 17:00:09 -05:00
if ( t . disable _handling )
return old _subgift . call ( i , e ) ;
2019-02-05 14:24:45 -05:00
const key = ` ${ e . channel } : ${ e . user . userID } ` ,
mystery = mysteries [ key ] ;
if ( mystery ) {
if ( mystery . expires < Date . now ( ) ) {
mysteries [ key ] = null ;
} else {
mystery . recipients . push ( {
id : e . recipientID ,
login : e . recipientLogin ,
displayName : e . recipientName
} ) ;
if ( mystery . recipients . length >= mystery . size )
mysteries [ key ] = null ;
if ( mystery . line )
2021-12-28 13:46:26 -05:00
t . scheduleMystery ( mystery ) ;
2019-02-05 14:24:45 -05:00
return ;
}
}
e . body = '' ;
const out = i . convertMessage ( { message : e } ) ;
out . ffz _type = 'sub_gift' ;
out . sub _recipient = {
id : e . recipientID ,
login : e . recipientLogin ,
displayName : e . recipientName
} ;
2021-09-04 20:14:58 -04:00
out . gift _theme = e . giftTheme ;
out . sub _goal = i . getGoalData ? i . getGoalData ( e . goalData ) : null ;
2020-07-02 14:54:46 -04:00
out . sub _months = e . giftMonths ;
2019-02-05 14:24:45 -05:00
out . sub _plan = e . methods ;
2019-02-06 14:45:27 -05:00
out . sub _total = e . senderCount ;
2019-02-05 14:24:45 -05:00
//t.log.info('Sub Gift', e, out);
return i . postMessageToCurrentChannel ( e , out ) ;
} catch ( err ) {
t . log . capture ( err , { extra : e } ) ;
return old _subgift . call ( i , e ) ;
}
}
2022-02-11 15:17:32 -05:00
const old _communityintro = this . onCommunityIntroductionEvent ;
this . onCommunityIntroductionEvent = function ( e ) {
2022-01-12 14:35:38 -05:00
try {
2023-01-19 17:00:09 -05:00
if ( t . disable _handling )
return old _communityintro . call ( this , e ) ;
2022-02-11 15:17:32 -05:00
if ( t . chat . context . get ( 'chat.filtering.blocked-types' ) . has ( 'CommunityIntroduction' ) ) {
const out = i . convertMessage ( e ) ;
return i . postMessageToCurrentChannel ( e , out ) ;
}
2022-01-12 14:35:38 -05:00
} catch ( err ) {
t . log . capture ( err , { extra : e } ) ;
2022-02-11 15:17:32 -05:00
t . log . error ( err ) ;
2022-01-12 14:35:38 -05:00
}
2022-02-11 15:17:32 -05:00
return old _communityintro . call ( this , e ) ;
}
2022-01-12 14:35:38 -05:00
2019-02-05 14:24:45 -05:00
const old _anonsubgift = this . onAnonSubscriptionGiftEvent ;
this . onAnonSubscriptionGiftEvent = function ( e ) {
try {
2020-08-17 13:33:30 -04:00
if ( t . chat . context . get ( 'chat.filtering.blocked-types' ) . has ( 'AnonSubGift' ) )
return ;
2023-01-19 17:00:09 -05:00
if ( t . disable _handling )
return old _anonsubgift . call ( i , e ) ;
2019-02-05 14:24:45 -05:00
const key = ` ${ e . channel } :ANON ` ,
mystery = mysteries [ key ] ;
if ( mystery ) {
if ( mystery . expires < Date . now ( ) )
mysteries [ key ] = null ;
else {
mystery . recipients . push ( {
id : e . recipientID ,
login : e . recipientLogin ,
displayName : e . recipientName
} ) ;
if ( mystery . recipients . length >= mystery . size )
mysteries [ key ] = null ;
if ( mystery . line )
2021-12-28 13:46:26 -05:00
t . scheduleMystery ( mystery ) ;
2019-02-05 14:24:45 -05:00
return ;
}
}
e . body = '' ;
const out = i . convertMessage ( { message : e } ) ;
out . ffz _type = 'sub_gift' ;
2021-09-04 20:14:58 -04:00
out . gift _theme = e . giftTheme ;
out . sub _goal = i . getGoalData ? i . getGoalData ( e . goalData ) : null ;
2019-02-05 14:24:45 -05:00
out . sub _anon = true ;
out . sub _recipient = {
id : e . recipientID ,
login : e . recipientLogin ,
displayName : e . recipientName
} ;
2020-07-02 14:54:46 -04:00
out . sub _months = e . giftMonths ;
2019-02-05 14:24:45 -05:00
out . sub _plan = e . methods ;
2019-02-06 14:45:27 -05:00
out . sub _total = e . senderCount ;
2019-02-05 14:24:45 -05:00
//t.log.info('Anon Sub Gift', e, out);
return i . postMessageToCurrentChannel ( e , out ) ;
} catch ( err ) {
t . log . capture ( err , { extra : e } ) ;
return old _anonsubgift . call ( i , e ) ;
}
}
const old _submystery = this . onSubscriptionMysteryGiftEvent ;
this . onSubscriptionMysteryGiftEvent = function ( e ) {
try {
2020-08-17 13:33:30 -04:00
if ( t . chat . context . get ( 'chat.filtering.blocked-types' ) . has ( 'SubMysteryGift' ) )
return ;
2023-01-19 17:00:09 -05:00
if ( t . disable _handling )
return old _submystery . call ( i , e ) ;
2019-02-05 14:24:45 -05:00
let mystery = null ;
if ( e . massGiftCount > t . chat . context . get ( 'chat.subs.merge-gifts' ) ) {
const key = ` ${ e . channel } : ${ e . user . userID } ` ;
mystery = mysteries [ key ] = {
recipients : [ ] ,
size : e . massGiftCount ,
expires : Date . now ( ) + 30000
} ;
}
e . body = '' ;
const out = i . convertMessage ( { message : e } ) ;
out . ffz _type = 'sub_mystery' ;
2021-09-04 20:14:58 -04:00
out . gift _theme = e . giftTheme ;
out . sub _goal = i . getGoalData ? i . getGoalData ( e . goalData ) : null ;
2019-02-05 14:24:45 -05:00
out . mystery = mystery ;
out . sub _plan = e . plan ;
out . sub _count = e . massGiftCount ;
out . sub _total = e . senderCount ;
//t.log.info('Sub Mystery', e, out);
return i . postMessageToCurrentChannel ( e , out ) ;
} catch ( err ) {
t . log . capture ( err , { extra : e } ) ;
return old _submystery . call ( i , e ) ;
}
}
const old _anonsubmystery = this . onAnonSubscriptionMysteryGiftEvent ;
this . onAnonSubscriptionMysteryGiftEvent = function ( e ) {
try {
2020-08-17 13:33:30 -04:00
if ( t . chat . context . get ( 'chat.filtering.blocked-types' ) . has ( 'AnonSubMysteryGift' ) )
return ;
2023-01-19 17:00:09 -05:00
if ( t . disable _handling )
return old _anonsubmystery . call ( i , e ) ;
2019-02-05 14:24:45 -05:00
let mystery = null ;
if ( e . massGiftCount > t . chat . context . get ( 'chat.subs.merge-gifts' ) ) {
const key = ` ${ e . channel } :ANON ` ;
mystery = mysteries [ key ] = {
recipients : [ ] ,
size : e . massGiftCount ,
expires : Date . now ( ) + 30000
} ;
}
e . body = '' ;
const out = i . convertMessage ( { message : e } ) ;
out . ffz _type = 'sub_mystery' ;
out . sub _anon = true ;
out . mystery = mystery ;
out . sub _plan = e . plan ;
out . sub _count = e . massGiftCount ;
out . sub _total = e . senderCount ;
//t.log.info('Anon Sub Mystery', e, out);
return i . postMessageToCurrentChannel ( e , out ) ;
} catch ( err ) {
t . log . capture ( err , { extra : e } ) ;
return old _anonsubmystery . call ( i , e ) ;
}
}
2018-02-02 16:00:58 -05:00
const old _ritual = this . onRitualEvent ;
this . onRitualEvent = function ( e ) {
try {
2020-08-17 13:33:30 -04:00
if ( t . chat . context . get ( 'chat.filtering.blocked-types' ) . has ( 'Ritual' ) )
return ;
2023-01-19 17:00:09 -05:00
if ( t . disable _handling )
return old _ritual . call ( i , e ) ;
2018-02-02 16:00:58 -05:00
const out = i . convertMessage ( e ) ;
out . ffz _type = 'ritual' ;
out . ritual = e . type ;
2018-04-28 17:56:03 -04:00
return i . postMessageToCurrentChannel ( e , out ) ;
2018-02-02 16:00:58 -05:00
} catch ( err ) {
2018-04-11 17:05:31 -04:00
t . log . capture ( err , { extra : e } ) ;
2018-02-02 16:00:58 -05:00
return old _ritual . call ( i , e ) ;
}
}
2019-11-11 14:38:49 -05:00
const old _points = this . onChannelPointsRewardEvent ;
this . onChannelPointsRewardEvent = function ( e ) {
try {
2020-08-17 13:33:30 -04:00
if ( t . chat . context . get ( 'chat.filtering.blocked-types' ) . has ( 'ChannelPointsReward' ) )
return ;
2023-01-19 17:00:09 -05:00
if ( t . disable _handling )
return old _points . call ( i , e ) ;
2020-01-22 16:58:55 -05:00
const reward = e . rewardID && get ( e . rewardID , i . props . rewardMap ) ;
if ( reward ) {
const out = i . convertMessage ( e ) ;
2019-11-11 14:38:49 -05:00
2020-01-22 16:58:55 -05:00
out . ffz _type = 'points' ;
out . ffz _reward = reward ;
2022-12-18 17:30:34 -05:00
out . ffz _reward _highlight = isHighlightedReward ( reward ) ;
2019-11-11 14:38:49 -05:00
2020-01-22 16:58:55 -05:00
return i . postMessageToCurrentChannel ( e , out ) ;
2019-11-11 14:38:49 -05:00
}
} catch ( err ) {
t . log . error ( err ) ;
t . log . capture ( err , { extra : e } ) ;
}
return old _points . call ( i , e ) ;
}
2022-10-07 15:12:15 -04:00
/ * c o n s t o l d _ h o s t = t h i s . o n H o s t i n g E v e n t ;
2017-12-14 05:43:56 +01:00
this . onHostingEvent = function ( e , _t ) {
t . emit ( 'tmi:host' , e , _t ) ;
return old _host . call ( i , e , _t ) ;
}
const old _unhost = this . onUnhostEvent ;
this . onUnhostEvent = function ( e , _t ) {
t . emit ( 'tmi:unhost' , e , _t ) ;
return old _unhost . call ( i , e , _t ) ;
2022-10-07 15:12:15 -04:00
} * /
2017-12-14 05:43:56 +01:00
2018-08-28 19:13:26 -04:00
const old _add = this . addMessage ;
this . addMessage = function ( e ) {
2018-06-27 14:13:59 -04:00
const original = i . _wrapped ;
2018-04-28 17:56:03 -04:00
if ( original && ! e . _ffz _checked )
2018-06-27 14:13:59 -04:00
return i . postMessageToCurrentChannel ( original , e ) ;
2017-11-13 01:23:39 -05:00
2018-08-28 19:13:26 -04:00
return old _add . call ( i , e ) ;
2017-11-13 01:23:39 -05:00
}
this . _ffz _init = true ;
}
return old _handler . apply ( this , ... args ) ;
}
2018-04-28 17:56:03 -04:00
cls . prototype . postMessageToCurrentChannel = function ( original , message ) {
2019-08-23 18:13:35 -04:00
const original _msg = message ;
2018-04-28 17:56:03 -04:00
message . _ffz _checked = true ;
2019-08-23 18:13:35 -04:00
// For certain message types, the message is contained within
// a message sub-object.
2022-03-31 17:15:43 -04:00
if ( message . type === t . chat _types . ChannelPointsReward || message . type === t . chat _types . CommunityIntroduction || ( message . message ? . user & message . message ? . badgeDynamicData ) ) {
2019-08-23 18:13:35 -04:00
message = message . message ;
2022-03-31 17:15:43 -04:00
}
2019-08-23 18:13:35 -04:00
2018-04-28 17:56:03 -04:00
if ( original . channel ) {
let chan = message . channel = original . channel . toLowerCase ( ) ;
if ( chan . startsWith ( '#' ) )
chan = chan . slice ( 1 ) ;
2018-08-28 19:13:26 -04:00
if ( chan !== this . props . channelLogin . toLowerCase ( ) )
2018-04-28 17:56:03 -04:00
return ;
message . roomLogin = chan ;
}
if ( original . message ) {
2019-01-18 19:07:57 -05:00
const user = original . message . user ,
flags = original . message . flags ;
2018-04-28 17:56:03 -04:00
if ( user )
message . emotes = user . emotes ;
2019-10-11 17:41:07 -04:00
if ( flags && this . getFilterFlagOptions ) {
const clear _mod = this . props . isCurrentUserModerator && t . chat . context . get ( 'chat.automod.run-as-mod' ) ;
if ( clear _mod )
this . props . isCurrentUserModerator = false ;
2019-01-18 19:07:57 -05:00
message . flags = this . getFilterFlagOptions ( flags ) ;
2019-10-11 17:41:07 -04:00
if ( clear _mod )
this . props . isCurrentUserModerator = true ;
}
2019-01-18 19:07:57 -05:00
2022-03-31 17:15:43 -04:00
if ( ! message . message || typeof message . message === 'string' ) {
if ( typeof original . action === 'string' )
message . message = original . action ;
else
message . message = original . message . body ;
}
2018-04-28 17:56:03 -04:00
}
2019-08-23 18:13:35 -04:00
this . addMessage ( original _msg ) ;
2018-04-28 17:56:03 -04:00
}
2017-11-13 01:23:39 -05:00
}
4.25.0
* Fixed: Smooth Scrolling no longer causing chat to scroll. (Closes #1068)
* Fixed: Issue with users using certain external stylesheets causing chat messages to become impossible to read on mouse hover. (Closes #1066)
* Fixed: Issues with badge sorting causing some badges to be overridden when they shouldn't be.
* Changed: Improve caching of badge data, such that re-rendering chat lines requires less computation.
* Changed: Refactor how chat lines listen for settings changes to reduce code duplication.
* Changed: Refactor how chat lines are invalidated to minimize work when changing settings.
* API Added: `chat:rerender-lines` event that, when emitted, causes all chat lines to be re-rendered.
* API Added: `chat:update-line-tokens` event that, when emitted, causes all chat lines to have their tokens invalidated and recalculated.
* API Added: `chat:update-line-badges` event that, when emitted, causes all chat lines to have their cached badges invalidated and recalculated.
* API Changed: `chat:update-lines-by-user` now has extra properties for separately invalidating tokens or badges. The full signature is `chat:update-lines-by-user(id, login, invalidate_tokens = true, invalidate_badges = true)`
2021-06-23 16:08:57 -04:00
/ * u p d a t e C h a t L i n e s ( ) {
2017-11-14 22:13:30 -05:00
this . chat _line . updateLines ( ) ;
4.25.0
* Fixed: Smooth Scrolling no longer causing chat to scroll. (Closes #1068)
* Fixed: Issue with users using certain external stylesheets causing chat messages to become impossible to read on mouse hover. (Closes #1066)
* Fixed: Issues with badge sorting causing some badges to be overridden when they shouldn't be.
* Changed: Improve caching of badge data, such that re-rendering chat lines requires less computation.
* Changed: Refactor how chat lines listen for settings changes to reduce code duplication.
* Changed: Refactor how chat lines are invalidated to minimize work when changing settings.
* API Added: `chat:rerender-lines` event that, when emitted, causes all chat lines to be re-rendered.
* API Added: `chat:update-line-tokens` event that, when emitted, causes all chat lines to have their tokens invalidated and recalculated.
* API Added: `chat:update-line-badges` event that, when emitted, causes all chat lines to have their cached badges invalidated and recalculated.
* API Changed: `chat:update-lines-by-user` now has extra properties for separately invalidating tokens or badges. The full signature is `chat:update-lines-by-user(id, login, invalidate_tokens = true, invalidate_badges = true)`
2021-06-23 16:08:57 -04:00
} * /
2017-11-13 01:23:39 -05:00
// ========================================================================
// Room Handling
// ========================================================================
addRoom ( thing , props ) {
if ( ! props )
props = thing . props ;
if ( ! props . channelID )
return null ;
2017-11-14 18:48:56 -05:00
const room = thing . _ffz _room = this . chat . getRoom ( props . channelID , props . channelLogin && props . channelLogin . toLowerCase ( ) , false , true ) ;
2017-11-13 01:23:39 -05:00
room . ref ( thing ) ;
return room ;
}
removeRoom ( thing ) { // eslint-disable-line class-methods-use-this
if ( ! thing . _ffz _room )
return ;
thing . _ffz _room . unref ( thing ) ;
thing . _ffz _room = null ;
}
// ========================================================================
// Chat Controller
// ========================================================================
chatMounted ( chat , props ) {
2018-07-14 14:13:28 -04:00
if ( chat . chatBuffer )
chat . chatBuffer . ffzController = chat ;
2017-11-13 01:23:39 -05:00
if ( ! props )
props = chat . props ;
if ( ! this . addRoom ( chat , props ) )
return ;
this . updateRoomBitsConfig ( chat , props . bitsConfig ) ;
2018-08-20 14:33:30 -04:00
// TODO: Check if this is the room for the current channel.
this . settings . updateContext ( {
moderator : props . isCurrentUserModerator ,
chatHidden : props . isHidden
} ) ;
2019-06-14 21:24:48 -04:00
if ( props . isEmbedded || props . isPopout )
this . settings . updateContext ( {
channel : props . channelLogin && props . channelLogin . toLowerCase ( ) ,
channelID : props . channelID
} ) ;
2018-08-20 14:33:30 -04:00
this . chat . context . updateContext ( {
moderator : props . isCurrentUserModerator ,
channel : props . channelLogin && props . channelLogin . toLowerCase ( ) ,
channelID : props . channelID ,
2019-06-13 22:56:50 -04:00
/ * u i : {
2018-08-20 14:33:30 -04:00
theme : props . theme
2019-06-13 22:56:50 -04:00
} * /
2018-08-20 14:33:30 -04:00
} ) ;
2017-11-13 01:23:39 -05:00
}
2019-06-14 21:24:48 -04:00
chatUnmounted ( chat ) {
2018-07-14 14:13:28 -04:00
if ( chat . chatBuffer && chat . chatBuffer . ffzController === this )
chat . chatBuffer . ffzController = null ;
2019-06-14 21:24:48 -04:00
if ( chat . props . isEmbedded || chat . props . isPopout )
this . settings . updateContext ( {
channel : null ,
channelID : null
} ) ;
2019-11-11 14:38:49 -05:00
this . settings . updateContext ( {
moderator : false ,
chatHidden : false
} ) ;
2019-06-14 21:24:48 -04:00
this . chat . context . updateContext ( {
moderator : false ,
channel : null ,
channelID : null
} ) ;
2018-07-14 14:13:28 -04:00
this . removeRoom ( chat ) ;
}
2017-11-13 01:23:39 -05:00
chatUpdated ( chat , props ) {
2018-07-14 14:13:28 -04:00
if ( chat . chatBuffer )
chat . chatBuffer . ffzController = chat ;
2019-05-04 03:36:10 -04:00
if ( ! chat . _ffz _room || props . channelID != chat . _ffz _room . id ) {
2017-11-13 01:23:39 -05:00
this . removeRoom ( chat ) ;
2019-05-04 00:21:43 -04:00
if ( chat . _ffz _mounted )
2019-11-11 14:38:49 -05:00
this . chatMounted ( chat ) ;
2017-11-13 01:23:39 -05:00
return ;
}
if ( props . bitsConfig !== chat . props . bitsConfig )
2019-11-11 14:38:49 -05:00
this . updateRoomBitsConfig ( chat , chat . props . bitsConfig ) ;
2017-11-13 01:23:39 -05:00
// TODO: Check if this is the room for the current channel.
2019-11-11 14:38:49 -05:00
let login = chat . props . channelLogin ;
if ( login )
login = login . toLowerCase ( ) ;
if ( chat . props . isEmbedded || chat . props . isPopout )
2019-06-14 21:24:48 -04:00
this . settings . updateContext ( {
2019-11-11 14:38:49 -05:00
channel : login ,
channelID : chat . props . channelID
2019-06-14 21:24:48 -04:00
} ) ;
2017-11-13 01:23:39 -05:00
this . settings . updateContext ( {
2019-11-11 14:38:49 -05:00
moderator : chat . props . isCurrentUserModerator ,
chatHidden : chat . props . isHidden
2017-11-13 01:23:39 -05:00
} ) ;
this . chat . context . updateContext ( {
2019-11-11 14:38:49 -05:00
moderator : chat . props . isCurrentUserModerator ,
channel : login ,
channelID : chat . props . channelID ,
2019-06-13 22:56:50 -04:00
/ * u i : {
2017-11-13 01:23:39 -05:00
theme : props . theme
2019-06-13 22:56:50 -04:00
} * /
2017-11-13 01:23:39 -05:00
} ) ;
}
updateRoomBitsConfig ( chat , config ) { // eslint-disable-line class-methods-use-this
const room = chat . _ffz _room ;
if ( ! room )
return ;
2019-04-30 15:18:29 -04:00
// We have to check that the available cheers haven't changed
// to avoid doing too many recalculations.
let new _bits = null ;
if ( config && Array . isArray ( config . orderedActions ) ) {
new _bits = new Set ;
for ( const action of config . orderedActions )
if ( action && action . prefix )
new _bits . add ( action . prefix ) ;
}
if ( ( ! this . _ffz _old _bits && ! new _bits ) || set _equals ( this . _ffz _old _bits , new _bits ) )
return ;
this . _ffz _old _bits = new _bits ;
2017-11-13 01:23:39 -05:00
room . updateBitsConfig ( formatBitsConfig ( config ) ) ;
4.25.0
* Fixed: Smooth Scrolling no longer causing chat to scroll. (Closes #1068)
* Fixed: Issue with users using certain external stylesheets causing chat messages to become impossible to read on mouse hover. (Closes #1066)
* Fixed: Issues with badge sorting causing some badges to be overridden when they shouldn't be.
* Changed: Improve caching of badge data, such that re-rendering chat lines requires less computation.
* Changed: Refactor how chat lines listen for settings changes to reduce code duplication.
* Changed: Refactor how chat lines are invalidated to minimize work when changing settings.
* API Added: `chat:rerender-lines` event that, when emitted, causes all chat lines to be re-rendered.
* API Added: `chat:update-line-tokens` event that, when emitted, causes all chat lines to have their tokens invalidated and recalculated.
* API Added: `chat:update-line-badges` event that, when emitted, causes all chat lines to have their cached badges invalidated and recalculated.
* API Changed: `chat:update-lines-by-user` now has extra properties for separately invalidating tokens or badges. The full signature is `chat:update-lines-by-user(id, login, invalidate_tokens = true, invalidate_badges = true)`
2021-06-23 16:08:57 -04:00
this . chat _line . updateLineTokens ( ) ;
//this.updateChatLines();
2017-11-13 01:23:39 -05:00
}
2018-08-28 19:13:26 -04:00
// ========================================================================
// Chat Buffer Connector
// ========================================================================
connectorMounted ( inst ) { // eslint-disable-line class-methods-use-this
const buffer = inst . props . messageBufferAPI ;
if ( buffer && buffer . _ffz _inst && buffer . _ffz _inst . _ffz _connector !== inst )
buffer . _ffz _inst . _ffz _connector = inst ;
}
connectorUpdated ( inst , props ) { // eslint-disable-line class-methods-use-this
2019-11-11 14:38:49 -05:00
const buffer = props . messageBufferAPI ,
new _buffer = inst . props . messageBufferAPI ;
2018-08-28 19:13:26 -04:00
if ( buffer === new _buffer )
return ;
if ( buffer && buffer . _ffz _inst && buffer . _ffz _inst . _ffz _connector === inst )
buffer . _ffz _inst . _ffz _connector = null ;
if ( new _buffer && new _buffer . _ffz _inst && new _buffer . _ffz _inst . _ffz _connector !== inst )
buffer . _ffz _inst . _ffz _connector = inst ;
}
connectorUnmounted ( inst ) { // eslint-disable-line class-methods-use-this
const buffer = inst . props . messageBufferAPI ;
if ( buffer && buffer . _ffz _inst && buffer . _ffz _inst . _ffz _connector === inst )
buffer . _ffz _inst . _ffz _connector = null ;
}
2017-11-13 01:23:39 -05:00
// ========================================================================
// Chat Containers
// ========================================================================
2021-03-22 18:19:09 -04:00
get shouldUpdateChannel ( ) {
const route = this . router . current _name ;
return Twilight . POPOUT _ROUTES . includes ( route ) || Twilight . SUNLIGHT _ROUTES . includes ( route ) ;
}
2017-11-13 01:23:39 -05:00
containerMounted ( cont , props ) {
if ( ! props )
props = cont . props ;
if ( ! this . addRoom ( cont , props ) )
return ;
2019-09-07 14:34:40 -04:00
this . updateRoomBitsConfig ( cont , props . bitsConfig ) ;
2018-01-19 17:17:16 -05:00
if ( props . data ) {
2021-03-22 18:19:09 -04:00
if ( this . shouldUpdateChannel ) {
2020-06-30 19:48:46 -04:00
const color = props . data . user ? . primaryColorHex ;
this . resolve ( 'site.channel' ) . updateChannelColor ( color ) ;
this . settings . updateContext ( {
channel : props . channelLogin ,
channelID : props . channelID ,
channelColor : color
} ) ;
}
2018-01-19 17:17:16 -05:00
this . chat . badges . updateTwitchBadges ( props . data . badges ) ;
this . updateRoomBadges ( cont , props . data . user && props . data . user . broadcastBadges ) ;
2019-04-29 18:14:04 -04:00
this . updateRoomRules ( cont , props . chatRules ) ;
2017-11-13 01:23:39 -05:00
}
}
2020-06-30 19:48:46 -04:00
containerUnmounted ( cont ) {
2021-03-22 18:19:09 -04:00
if ( this . shouldUpdateChannel ) {
2020-06-30 19:48:46 -04:00
this . resolve ( 'site.channel' ) . updateChannelColor ( ) ;
this . settings . updateContext ( {
channel : null ,
channelID : null ,
channelColor : null
} ) ;
}
this . removeRoom ( cont ) ;
}
2017-11-13 01:23:39 -05:00
containerUpdated ( cont , props ) {
2019-05-07 15:04:12 -04:00
// If we don't have a room, or if the room ID doesn't match our ID
// then we need to just create a new Room because the chat room changed.
2019-05-04 03:36:10 -04:00
if ( ! cont . _ffz _room || props . channelID != cont . _ffz _room . id ) {
2017-11-13 01:23:39 -05:00
this . removeRoom ( cont ) ;
2019-05-04 00:21:43 -04:00
if ( cont . _ffz _mounted )
this . containerMounted ( cont , props ) ;
2017-11-13 01:23:39 -05:00
return ;
}
2019-09-07 14:34:40 -04:00
if ( props . bitsConfig !== cont . props . bitsConfig )
this . updateRoomBitsConfig ( cont , props . bitsConfig ) ;
2020-06-30 19:48:46 -04:00
if ( props . data && Twilight . POPOUT _ROUTES . includes ( this . router . current _name ) ) {
const color = props . data . user ? . primaryColorHex ;
this . resolve ( 'site.channel' ) . updateChannelColor ( color ) ;
this . settings . updateContext ( {
channel : props . channelLogin ,
channelID : props . channelID ,
channelColor : color
} ) ;
}
2017-11-13 01:23:39 -05:00
// Twitch, React, and Apollo are the trifecta of terror so we
// can't compare the badgeSets property in any reasonable way.
// Instead, just check the lengths to see if they've changed
// and hope that badge versions will never change separately.
2018-01-19 17:17:16 -05:00
const data = props . data || { } ,
odata = cont . props . data || { } ,
2017-11-13 01:23:39 -05:00
2018-01-19 17:17:16 -05:00
bs = data . badges || [ ] ,
obs = odata . badges || [ ] ,
2017-11-13 01:23:39 -05:00
2018-01-19 17:17:16 -05:00
cs = data . user && data . user . broadcastBadges || [ ] ,
ocs = odata . user && odata . user . broadcastBadges || [ ] ;
2017-11-13 01:23:39 -05:00
2019-05-08 22:47:38 -04:00
if ( this . chat . badges . getTwitchBadgeCount ( ) !== bs . length || bs . length !== obs . length )
2018-01-19 17:17:16 -05:00
this . chat . badges . updateTwitchBadges ( bs ) ;
2017-11-13 01:23:39 -05:00
2019-05-08 22:47:38 -04:00
if ( cont . _ffz _room . badgeCount ( ) !== cs . length || cs . length !== ocs . length )
2018-01-19 17:17:16 -05:00
this . updateRoomBadges ( cont , cs ) ;
2019-04-29 18:14:04 -04:00
this . updateRoomRules ( cont , props . chatRules ) ;
2017-11-13 01:23:39 -05:00
}
2019-05-03 19:30:46 -04:00
hasRoomBadges ( cont ) { // eslint-disable-line class-methods-use-this
const room = cont . _ffz _room ;
if ( ! room )
return false ;
return room . hasBadges ( ) ;
}
2017-11-13 01:23:39 -05:00
updateRoomBadges ( cont , badges ) { // eslint-disable-line class-methods-use-this
const room = cont . _ffz _room ;
if ( ! room )
return ;
room . updateBadges ( badges ) ;
4.25.0
* Fixed: Smooth Scrolling no longer causing chat to scroll. (Closes #1068)
* Fixed: Issue with users using certain external stylesheets causing chat messages to become impossible to read on mouse hover. (Closes #1066)
* Fixed: Issues with badge sorting causing some badges to be overridden when they shouldn't be.
* Changed: Improve caching of badge data, such that re-rendering chat lines requires less computation.
* Changed: Refactor how chat lines listen for settings changes to reduce code duplication.
* Changed: Refactor how chat lines are invalidated to minimize work when changing settings.
* API Added: `chat:rerender-lines` event that, when emitted, causes all chat lines to be re-rendered.
* API Added: `chat:update-line-tokens` event that, when emitted, causes all chat lines to have their tokens invalidated and recalculated.
* API Added: `chat:update-line-badges` event that, when emitted, causes all chat lines to have their cached badges invalidated and recalculated.
* API Changed: `chat:update-lines-by-user` now has extra properties for separately invalidating tokens or badges. The full signature is `chat:update-lines-by-user(id, login, invalidate_tokens = true, invalidate_badges = true)`
2021-06-23 16:08:57 -04:00
this . chat _line . updateLineBadges ( ) ;
//this.updateChatLines();
2017-11-13 01:23:39 -05:00
}
2019-04-29 18:14:04 -04:00
updateRoomRules ( cont , rules ) { // eslint-disable-line class-methods-use-this
const room = cont . _ffz _room ;
if ( ! room )
return ;
room . rules = rules ;
}
2017-11-13 01:23:39 -05:00
}
// ============================================================================
// Processing Functions
// ============================================================================
export function formatBitsConfig ( config ) {
if ( ! config )
return ;
const out = { } ,
2020-03-25 19:05:38 -04:00
actions = config . indexedActions ,
tier _colors = { } ;
if ( Array . isArray ( config . tiers ) )
for ( const tier of config . tiers )
tier _colors [ tier . bits ] = tier . color ;
2017-11-13 01:23:39 -05:00
for ( const key in actions )
if ( has ( actions , key ) ) {
const action = actions [ key ] ,
new _act = out [ key ] = {
id : action . id ,
prefix : action . prefix ,
tiers : [ ]
} ;
2020-03-25 19:05:38 -04:00
if ( config ? . getImage ) {
for ( const tier of action . orderedTiers ) {
const images = { } ;
for ( const theme of [ 'light' , 'dark' ] ) {
const themed = images [ theme ] = images [ theme ] || { } ,
stat = themed . static = themed . static || { } ,
animated = themed . animated = themed . animated || { } ;
for ( const scale of [ 1 , 2 , 4 ] ) {
// Static Images
stat [ scale ] = config . getImage ( action . prefix , theme , 'static' , tier . bits , scale , 'png' ) ;
// Animated Images
animated [ scale ] = config . getImage ( action . prefix , theme , 'animated' , tier . bits , scale , 'gif' ) ;
}
}
2017-11-13 01:23:39 -05:00
2020-03-25 19:05:38 -04:00
new _act . tiers . push ( {
amount : tier . bits ,
color : tier . color || tier _colors [ tier . bits ] || 'inherit' ,
id : tier . id ,
images
} ) ;
2017-11-13 01:23:39 -05:00
}
2020-03-25 19:05:38 -04:00
} else if ( action . orderedTiers [ 0 ] ? . images ) {
for ( const tier of action . orderedTiers ) {
const images = { } ;
for ( const im of tier . images ) {
const themed = images [ im . theme ] = images [ im . theme ] || [ ] ,
ak = im . isAnimated ? 'animated' : 'static' ,
anim = themed [ ak ] = themed [ ak ] || { } ;
anim [ im . dpiScale ] = im . url ;
}
new _act . tiers . push ( {
amount : tier . bits ,
color : tier . color || tier _colors [ tier . bits ] || 'inherit' ,
id : tier . id ,
images
} )
}
2017-11-13 01:23:39 -05:00
}
}
return out ;
2020-06-30 19:48:46 -04:00
}