2017-11-13 01:23:39 -05:00
|
|
|
'use strict';
|
|
|
|
|
|
|
|
// ============================================================================
|
|
|
|
// Chat Hooks
|
|
|
|
// ============================================================================
|
|
|
|
|
2018-05-10 19:56:39 -04:00
|
|
|
import {ColorAdjuster} from 'utilities/color';
|
2017-11-13 01:23:39 -05:00
|
|
|
import {setChildren} from 'utilities/dom';
|
2018-08-04 15:01:00 -04:00
|
|
|
import {has, split_chars, shallow_object_equals} from 'utilities/object';
|
2018-05-22 17:23:20 -04:00
|
|
|
import {FFZEvent} from 'utilities/events';
|
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';
|
2018-04-07 19:10:45 -04:00
|
|
|
import TabCompletion from './tab_completion';
|
2017-11-14 22:13:30 -05:00
|
|
|
|
|
|
|
|
2018-06-27 20:17:45 -04:00
|
|
|
const REGEX_EMOTES = {
|
|
|
|
'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'],
|
|
|
|
'\\:\\>\\;': [':>']
|
2018-06-27 20:17:45 -04:00
|
|
|
};
|
|
|
|
|
|
|
|
|
2018-04-28 17:56:03 -04:00
|
|
|
const MESSAGE_TYPES = ((e = {}) => {
|
2017-11-17 14:59:46 -05:00
|
|
|
e[e.Post = 0] = 'Post';
|
2017-11-16 15:54:58 -05:00
|
|
|
e[e.Action = 1] = 'Action';
|
|
|
|
e[e.PostWithMention = 2] = 'PostWithMention';
|
2017-11-16 14:24:17 -05:00
|
|
|
return e;
|
2018-04-28 17:56:03 -04:00
|
|
|
})();
|
|
|
|
|
|
|
|
|
|
|
|
const MOD_TYPES = ((e = {}) => {
|
|
|
|
e[e.Ban = 0] = 'Ban';
|
|
|
|
e[e.Timeout = 1] = 'Timeout';
|
|
|
|
return e;
|
|
|
|
})();
|
|
|
|
|
|
|
|
|
|
|
|
const AUTOMOD_TYPES = ((e = {}) => {
|
|
|
|
e[e.MessageRejectedPrompt = 0] = 'MessageRejectedPrompt';
|
|
|
|
e[e.MessageRejected = 1] = 'MessageRejected';
|
|
|
|
e[e.MessageAllowed = 2] = 'MessageAllowed';
|
|
|
|
e[e.MessageDenied = 3] = 'MessageDenied';
|
|
|
|
return e;
|
|
|
|
})();
|
|
|
|
|
|
|
|
|
|
|
|
const CHAT_TYPES = ((e = {}) => {
|
|
|
|
e[e.Message = 0] = 'Message';
|
|
|
|
e[e.Moderation = 1] = 'Moderation';
|
|
|
|
e[e.ModerationAction = 2] = 'ModerationAction';
|
|
|
|
e[e.TargetedModerationAction = 3] = 'TargetedModerationAction';
|
|
|
|
e[e.AutoMod = 4] = 'AutoMod';
|
2018-07-13 14:32:12 -04:00
|
|
|
e[e.SubscriberOnlyMode = 5] = 'SubscriberOnlyMode';
|
|
|
|
e[e.FollowerOnlyMode = 6] = 'FollowerOnlyMode';
|
|
|
|
e[e.SlowMode = 7] = 'SlowMode';
|
|
|
|
e[e.EmoteOnlyMode = 8] = 'EmoteOnlyMode';
|
|
|
|
e[e.R9KMode = 9] = 'R9KMode';
|
|
|
|
e[e.Connected = 10] = 'Connected';
|
|
|
|
e[e.Disconnected = 11] = 'Disconnected';
|
|
|
|
e[e.Reconnect = 12] = 'Reconnect';
|
|
|
|
e[e.Hosting = 13] = 'Hosting';
|
|
|
|
e[e.Unhost = 14] = 'Unhost';
|
|
|
|
e[e.Hosted = 15] = 'Hosted';
|
|
|
|
e[e.Subscription = 16] = 'Subscription';
|
|
|
|
e[e.Resubscription = 17] = 'Resubscription';
|
|
|
|
e[e.SubGift = 18] = 'SubGift';
|
|
|
|
e[e.Clear = 19] = 'Clear';
|
|
|
|
e[e.RoomMods = 20] = 'RoomMods';
|
|
|
|
e[e.RoomState = 21] = 'RoomState';
|
|
|
|
e[e.Raid = 22] = 'Raid';
|
|
|
|
e[e.Unraid = 23] = 'Unraid';
|
|
|
|
e[e.Ritual = 24] = 'Ritual';
|
|
|
|
e[e.Notice = 25] = 'Notice';
|
|
|
|
e[e.Info = 26] = 'Info';
|
|
|
|
e[e.BadgesUpdated = 27] = 'BadgesUpdated';
|
|
|
|
e[e.Purchase = 28] = 'Purchase';
|
|
|
|
e[e.BitsCharity = 29] = 'BitsCharity';
|
|
|
|
e[e.CrateGift = 30] = 'CrateGift';
|
|
|
|
e[e.RewardGift = 31] = 'RewardGift';
|
2018-08-04 15:01:00 -04:00
|
|
|
e[e.SubMysteryGift = 32] = 'SubMysteryGift';
|
2018-04-28 17:56:03 -04:00
|
|
|
return e;
|
|
|
|
})();
|
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 = [
|
|
|
|
'onBitsCharityEvent',
|
|
|
|
//'onRitualEvent', -- handled by conversion to Message event
|
2018-02-22 18:23:44 -05:00
|
|
|
'onBadgesUpdatedEvent',
|
|
|
|
'onPurchaseEvent',
|
2018-04-28 17:56:03 -04:00
|
|
|
'onCrateEvent'
|
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');
|
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');
|
|
|
|
|
|
|
|
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);
|
2018-04-07 19:10:45 -04:00
|
|
|
this.inject(TabCompletion);
|
2018-05-10 19:56:39 -04:00
|
|
|
|
2017-11-13 01:23:39 -05:00
|
|
|
|
|
|
|
this.ChatController = this.fine.define(
|
|
|
|
'chat-controller',
|
2018-03-14 13:58:04 -04:00
|
|
|
n => n.chatService,
|
|
|
|
Twilight.CHAT_ROUTES
|
2017-11-13 01:23:39 -05:00
|
|
|
);
|
|
|
|
|
|
|
|
this.ChatContainer = this.fine.define(
|
|
|
|
'chat-container',
|
2018-03-14 13:58:04 -04:00
|
|
|
n => n.showViewersList && n.onChatInputFocus,
|
|
|
|
Twilight.CHAT_ROUTES
|
2017-11-13 01:23:39 -05:00
|
|
|
);
|
|
|
|
|
|
|
|
this.PinnedCheer = this.fine.define(
|
|
|
|
'pinned-cheer',
|
2018-03-14 13:58:04 -04:00
|
|
|
n => n.collapseCheer && n.saveRenderedMessageRef,
|
|
|
|
Twilight.CHAT_ROUTES
|
2017-11-13 01:23:39 -05:00
|
|
|
);
|
2018-03-30 19:54:26 -04:00
|
|
|
|
2018-04-28 17:56:03 -04:00
|
|
|
this.RoomPicker = this.fine.define(
|
|
|
|
'chat-picker',
|
|
|
|
n => n.closeRoomPicker && n.handleRoomSelect,
|
|
|
|
Twilight.CHAT_ROUTES
|
|
|
|
);
|
|
|
|
|
2017-11-13 01:23:39 -05:00
|
|
|
|
|
|
|
// Settings
|
|
|
|
|
|
|
|
this.settings.add('chat.width', {
|
|
|
|
default: 340,
|
|
|
|
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 )
|
|
|
|
return 340;
|
|
|
|
|
|
|
|
return val;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
this.settings.add('chat.bits.show-pinned', {
|
|
|
|
default: true,
|
|
|
|
ui: {
|
2018-03-01 04:13:52 -05:00
|
|
|
path: 'Chat > Bits and Cheering >> Appearance',
|
|
|
|
title: 'Display Top Cheerers',
|
2017-11-13 01:23:39 -05:00
|
|
|
|
|
|
|
component: 'setting-check-box'
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
2018-02-02 16:00:58 -05:00
|
|
|
this.settings.add('chat.rituals.show', {
|
|
|
|
default: true,
|
|
|
|
ui: {
|
|
|
|
path: 'Chat > Filtering >> Rituals',
|
|
|
|
title: 'Display ritual messages such as "User is new here! Say Hello!".',
|
|
|
|
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)'}
|
|
|
|
]
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
get currentChat() {
|
|
|
|
for(const inst of this.ChatController.instances)
|
|
|
|
if ( inst && inst.chatService )
|
|
|
|
return inst;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
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
|
|
|
|
|
|
|
// 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.
|
|
|
|
c._base = is_dark ? '#191919' : '#e0e0e0'; //#0e0c13' : '#faf9fa';
|
2017-11-13 01:23:39 -05:00
|
|
|
c.mode = mode;
|
|
|
|
c.contrast = contrast;
|
|
|
|
|
2018-04-28 17:56:03 -04:00
|
|
|
ic._base = is_dark ? '#dad8de' : '#19171c';
|
|
|
|
ic.mode = mode;
|
2018-05-31 18:34:15 -04:00
|
|
|
ic.contrast = contrast;
|
2018-04-28 17:56:03 -04:00
|
|
|
|
2017-11-13 01:23:39 -05:00
|
|
|
this.updateChatLines();
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2017-11-14 18:48:56 -05:00
|
|
|
updateChatCSS() {
|
|
|
|
const width = this.chat.context.get('chat.width'),
|
|
|
|
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
|
|
|
|
2017-11-17 14:59:46 -05:00
|
|
|
let font = this.chat.context.get('chat.font-family') || 'inherit';
|
|
|
|
if ( font.indexOf(' ') !== -1 && font.indexOf(',') === -1 && font.indexOf('"') === -1 && font.indexOf("'") === -1 )
|
|
|
|
font = `"${font}"`;
|
2017-11-14 18:48:56 -05:00
|
|
|
|
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`);
|
2017-11-14 18:48:56 -05:00
|
|
|
|
2017-11-17 17:31:21 -05:00
|
|
|
this.css_tweaks.toggle('chat-font', size !== 12 || font);
|
2017-11-17 14:59:46 -05:00
|
|
|
this.css_tweaks.toggle('chat-width', width !== 340);
|
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);
|
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'));
|
|
|
|
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-13 01:23:39 -05:00
|
|
|
|
2018-05-18 17:48:10 -04:00
|
|
|
async grabTypes() {
|
2018-07-13 14:32:12 -04:00
|
|
|
const ct = await this.web_munch.findModule('chat-types'),
|
|
|
|
changes = [];
|
|
|
|
|
2018-07-13 20:39:01 -04:00
|
|
|
this.automod_types = ct && ct.a || AUTOMOD_TYPES;
|
|
|
|
this.chat_types = ct && ct.b || CHAT_TYPES;
|
|
|
|
this.message_types = ct && ct.c || MESSAGE_TYPES;
|
|
|
|
this.mod_types = ct && ct.e || MOD_TYPES;
|
|
|
|
|
2018-07-13 14:32:12 -04:00
|
|
|
if ( ! ct )
|
|
|
|
return;
|
|
|
|
|
2018-07-13 20:39:01 -04:00
|
|
|
if ( ct.a && ! shallow_object_equals(ct.a, AUTOMOD_TYPES) )
|
2018-07-13 14:32:12 -04:00
|
|
|
changes.push('AUTOMOD_TYPES');
|
|
|
|
|
2018-07-13 20:39:01 -04:00
|
|
|
if ( ct.b && ! shallow_object_equals(ct.b, CHAT_TYPES) )
|
2018-07-13 14:32:12 -04:00
|
|
|
changes.push('CHAT_TYPES');
|
|
|
|
|
2018-07-13 20:39:01 -04:00
|
|
|
if ( ct.c && ! shallow_object_equals(ct.c, MESSAGE_TYPES) )
|
2018-07-13 14:32:12 -04:00
|
|
|
changes.push('MESSAGE_TYPES');
|
|
|
|
|
2018-07-13 20:39:01 -04:00
|
|
|
if ( ct.e && ! shallow_object_equals(ct.e, 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
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
onEnable() {
|
|
|
|
this.on('site.web_munch:loaded', this.grabTypes);
|
|
|
|
this.grabTypes();
|
2017-11-14 22:13:30 -05:00
|
|
|
|
2017-11-14 18:48:56 -05:00
|
|
|
this.chat.context.on('changed:chat.width', this.updateChatCSS, this);
|
|
|
|
this.chat.context.on('changed:chat.font-size', this.updateChatCSS, this);
|
|
|
|
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);
|
|
|
|
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);
|
2017-12-13 20:22:11 -05:00
|
|
|
this.chat.context.on('changed:chat.fix-bad-emotes', this.updateChatLines, this);
|
2017-11-13 01:23:39 -05:00
|
|
|
|
2017-11-17 14:59:46 -05:00
|
|
|
this.chat.context.on('changed:chat.lines.alternate', val => {
|
|
|
|
this.css_tweaks.toggle('chat-rows', val);
|
|
|
|
this.updateMentionCSS();
|
|
|
|
});
|
2017-11-13 01:23:39 -05:00
|
|
|
|
|
|
|
this.chat.context.on('changed:chat.lines.padding', val =>
|
|
|
|
this.css_tweaks.toggle('chat-padding', val));
|
|
|
|
|
|
|
|
this.chat.context.on('changed:chat.bits.show', val =>
|
|
|
|
this.css_tweaks.toggle('hide-bits', !val));
|
|
|
|
this.chat.context.on('changed:chat.bits.show-pinned', val =>
|
|
|
|
this.css_tweaks.toggleHide('pinned-cheer', !val));
|
|
|
|
|
2018-08-02 14:29:18 -04:00
|
|
|
this.chat.context.on('changed:chat.filtering.deleted-style', val =>
|
|
|
|
this.css_tweaks.toggle('chat-deleted-strike', val === 1))
|
|
|
|
|
|
|
|
this.css_tweaks.toggle('chat-deleted-strike', this.chat.context.get('chat.filtering.deleted-style') === 1);
|
|
|
|
|
2017-11-13 01:23:39 -05:00
|
|
|
this.css_tweaks.toggleHide('pinned-cheer', !this.chat.context.get('chat.bits.show-pinned'));
|
|
|
|
this.css_tweaks.toggle('hide-bits', !this.chat.context.get('chat.bits.show'));
|
|
|
|
this.css_tweaks.toggle('chat-rows', this.chat.context.get('chat.lines.alternate'));
|
|
|
|
this.css_tweaks.toggle('chat-padding', this.chat.context.get('chat.lines.padding'));
|
|
|
|
|
2017-11-14 18:48:56 -05:00
|
|
|
this.updateChatCSS();
|
2017-11-13 01:23:39 -05:00
|
|
|
this.updateColors();
|
|
|
|
this.updateLineBorders();
|
2017-11-17 14:59:46 -05:00
|
|
|
this.updateMentionCSS();
|
2017-11-13 01:23:39 -05:00
|
|
|
|
|
|
|
this.ChatController.on('mount', this.chatMounted, this);
|
2018-07-14 14:13:28 -04:00
|
|
|
this.ChatController.on('unmount', this.chatUmounted, this);
|
2017-11-13 01:23:39 -05:00
|
|
|
this.ChatController.on('receive-props', this.chatUpdated, this);
|
|
|
|
|
|
|
|
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-03-03 16:38:50 -05:00
|
|
|
className: 'tw-border-l tw-c-background-alt-2 tw-c-text tw-full-width tw-full-height tw-align-items-center tw-flex tw-flex-column tw-justify-content-center tw-relative'
|
|
|
|
}, 'There was an error displaying chat.');
|
|
|
|
|
|
|
|
} else
|
|
|
|
return old_render.call(this);
|
|
|
|
}
|
|
|
|
|
2017-11-13 01:23:39 -05:00
|
|
|
for(const inst of instances) {
|
|
|
|
const service = inst.chatService;
|
|
|
|
if ( ! service._ffz_was_here )
|
|
|
|
this.wrapChatService(service.constructor);
|
|
|
|
|
2017-11-14 22:13:30 -05:00
|
|
|
const buffer = inst.chatBuffer;
|
|
|
|
if ( ! buffer._ffz_was_here )
|
|
|
|
this.wrapChatBuffer(buffer.constructor);
|
|
|
|
|
2018-07-16 15:07:22 -04:00
|
|
|
if ( buffer.ffzConsumeChatEvent )
|
|
|
|
buffer.consumeChatEvent = buffer.ffzConsumeChatEvent.bind(buffer);
|
|
|
|
|
2018-07-14 14:13:28 -04:00
|
|
|
buffer.ffzController = inst;
|
|
|
|
|
2017-11-13 01:23:39 -05:00
|
|
|
service.client.events.removeAll();
|
|
|
|
service.connectHandlers();
|
|
|
|
|
|
|
|
this.chatMounted(inst);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
this.ChatContainer.on('mount', this.containerMounted, this);
|
|
|
|
this.ChatContainer.on('unmount', this.removeRoom, this);
|
|
|
|
this.ChatContainer.on('receive-props', this.containerUpdated, this);
|
|
|
|
|
|
|
|
this.ChatContainer.ready((cls, instances) => {
|
2018-03-01 13:40:24 -05:00
|
|
|
const t = this,
|
|
|
|
old_catch = cls.prototype.componentDidCatch;
|
2018-03-03 16:38:50 -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);
|
|
|
|
}
|
|
|
|
|
2017-11-13 01:23:39 -05:00
|
|
|
for(const inst of instances)
|
|
|
|
this.containerMounted(inst);
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
this.PinnedCheer.on('mount', this.fixPinnedCheer, this);
|
|
|
|
this.PinnedCheer.on('update', this.fixPinnedCheer, this);
|
|
|
|
|
|
|
|
this.PinnedCheer.ready((cls, instances) => {
|
|
|
|
for(const inst of instances)
|
|
|
|
this.fixPinnedCheer(inst);
|
|
|
|
});
|
2018-04-28 17:56:03 -04:00
|
|
|
|
|
|
|
|
|
|
|
this.RoomPicker.ready((cls, instances) => {
|
|
|
|
for(const inst of instances)
|
|
|
|
this.closeRoomPicker(inst);
|
|
|
|
});
|
|
|
|
|
|
|
|
this.RoomPicker.on('mount', this.closeRoomPicker, this);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
closeRoomPicker(inst) { // eslint-disable-line class-methods-use-this
|
|
|
|
inst.closeRoomPicker();
|
2017-11-14 22:13:30 -05:00
|
|
|
}
|
2017-11-13 01:23:39 -05:00
|
|
|
|
|
|
|
|
2017-11-14 22:13:30 -05:00
|
|
|
wrapChatBuffer(cls) {
|
2018-07-14 14:13:28 -04:00
|
|
|
const t = this,
|
|
|
|
old_consume = cls.prototype.consumeChatEvent;
|
2017-11-13 01:23:39 -05:00
|
|
|
|
2017-11-14 22:13:30 -05:00
|
|
|
cls.prototype._ffz_was_here = true;
|
2017-11-13 01:23:39 -05:00
|
|
|
|
2018-07-16 15:07:22 -04:00
|
|
|
if ( old_consume )
|
|
|
|
cls.prototype.ffzConsumeChatEvent = cls.prototype.consumeChatEvent = function(msg) {
|
|
|
|
if ( msg ) {
|
|
|
|
try {
|
|
|
|
const types = t.chat_types || {};
|
2018-07-14 14:13:28 -04:00
|
|
|
|
2018-07-16 15:07:22 -04:00
|
|
|
if ( msg.type === types.Message ) {
|
|
|
|
const m = t.chat.standardizeMessage(msg),
|
|
|
|
cont = this.ffzController;
|
2018-07-14 14:13:28 -04:00
|
|
|
|
2018-07-16 15:07:22 -04:00
|
|
|
let room = m.roomLogin = m.roomLogin ? m.roomLogin : m.channel ? m.channel.slice(1) : cont && cont.props.channelLogin,
|
|
|
|
room_id = cont && cont.props.channelID;
|
2018-07-14 14:13:28 -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
|
|
|
|
2018-07-16 15:07:22 -04:00
|
|
|
const u = t.site.getUser(),
|
|
|
|
r = {id: room_id, login: room};
|
2018-07-14 14:13:28 -04:00
|
|
|
|
2018-07-16 15:07:22 -04:00
|
|
|
if ( u && cont ) {
|
|
|
|
u.moderator = cont.props.isCurrentUserModerator;
|
|
|
|
u.staff = cont.props.isStaff;
|
|
|
|
}
|
|
|
|
|
|
|
|
m.ffz_tokens = m.ffz_tokens || t.chat.tokenizeMessage(m, u, r);
|
|
|
|
|
|
|
|
const event = new FFZEvent({
|
|
|
|
message: m,
|
|
|
|
channel: room
|
|
|
|
});
|
|
|
|
|
|
|
|
t.emit('chat:receive-message', event);
|
|
|
|
if ( event.defaultPrevented || m.ffz_removed )
|
|
|
|
return;
|
2018-07-14 14:13:28 -04:00
|
|
|
|
2018-07-16 15:07:22 -04:00
|
|
|
} else if ( msg.type === types.Moderation ) {
|
|
|
|
const login = msg.userLogin;
|
|
|
|
if ( this.moderatedUsers.has(login) )
|
|
|
|
return;
|
2018-07-14 14:13:28 -04:00
|
|
|
|
2018-07-16 15:28:53 -04:00
|
|
|
const do_remove = t.chat.context.get('chat.filtering.remove-deleted') === 3,
|
2018-07-16 15:07:22 -04:00
|
|
|
do_update = m => {
|
2018-07-16 15:28:53 -04:00
|
|
|
if ( m.event )
|
|
|
|
m = m.event;
|
|
|
|
|
2018-07-16 15:07:22 -04:00
|
|
|
if ( m.type === types.Message && m.user && m.user.userLogin === login )
|
|
|
|
m.deleted = true;
|
|
|
|
};
|
2018-07-14 14:13:28 -04:00
|
|
|
|
2018-07-16 15:07:22 -04:00
|
|
|
if ( do_remove ) {
|
|
|
|
this.buffer = this.buffer.filter(m => m.type !== types.Message || ! m.user || m.user.userLogin !== login);
|
|
|
|
this._isDirty = true;
|
|
|
|
this.onBufferUpdate();
|
|
|
|
|
|
|
|
} else
|
|
|
|
this.buffer.forEach(do_update);
|
|
|
|
|
|
|
|
this.delayedMessageBuffer.forEach(do_update);
|
|
|
|
|
|
|
|
this.moderatedUsers.add(login);
|
|
|
|
setTimeout(this.unmoderateUser(login), 1000);
|
2018-07-14 14:13:28 -04:00
|
|
|
return;
|
|
|
|
|
2018-07-16 15:07:22 -04:00
|
|
|
} else if ( msg.type === types.Clear ) {
|
|
|
|
if ( t.chat.context.get('chat.filtering.ignore-clear') )
|
|
|
|
msg = {
|
|
|
|
type: types.Notice,
|
2018-07-16 15:28:53 -04:00
|
|
|
message: t.i18n.t('chat.ignore-clear', 'An attempt to clear chat was ignored.')
|
2018-07-16 15:07:22 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
} catch(err) {
|
|
|
|
t.log.capture(err, {extra: {msg}})
|
|
|
|
}
|
2018-07-14 14:13:28 -04:00
|
|
|
}
|
2018-07-16 15:07:22 -04:00
|
|
|
|
|
|
|
return old_consume.call(this, msg);
|
|
|
|
}
|
|
|
|
|
|
|
|
cls.prototype.flushRawMessages = function() {
|
|
|
|
const out = [],
|
|
|
|
now = Date.now(),
|
|
|
|
raw_delay = t.chat.context.get('chat.delay'),
|
|
|
|
delay = raw_delay === -1 ? this.delayDuration : raw_delay,
|
|
|
|
first = now - delay,
|
2018-07-16 15:28:53 -04:00
|
|
|
do_remove = t.chat.context.get('chat.filtering.remove-deleted');
|
2018-07-16 15:07:22 -04:00
|
|
|
|
|
|
|
let changed = false;
|
|
|
|
|
|
|
|
for(const msg of this.delayedMessageBuffer) {
|
|
|
|
if ( msg.time <= first || ! msg.shouldDelay ) {
|
2018-07-16 15:28:53 -04:00
|
|
|
if ( do_remove !== 0 && (do_remove > 1 || ! this.shouldSeeBlockedAndDeletedMessages) && this.isDeletable(msg.event) && msg.event.deleted )
|
2018-07-16 15:07:22 -04:00
|
|
|
continue;
|
|
|
|
|
|
|
|
this.buffer.push(msg.event);
|
|
|
|
changed = true;
|
|
|
|
|
|
|
|
} else
|
|
|
|
out.push(msg);
|
2018-07-14 14:13:28 -04:00
|
|
|
}
|
|
|
|
|
2018-07-16 15:07:22 -04:00
|
|
|
this.delayedMessageBuffer = out;
|
|
|
|
|
|
|
|
if ( changed ) {
|
|
|
|
this._isDirty = true;
|
|
|
|
this.onBufferUpdate();
|
|
|
|
}
|
2018-07-14 14:13:28 -04:00
|
|
|
}
|
|
|
|
|
2017-11-14 22:13:30 -05:00
|
|
|
cls.prototype.toArray = function() {
|
|
|
|
const buf = this.buffer,
|
2017-11-17 14:59:46 -05:00
|
|
|
size = t.chat.context.get('chat.scrollback-length'),
|
2017-11-22 15:39:38 -05:00
|
|
|
ct = t.chat_types || CHAT_TYPES,
|
2017-11-17 14:59:46 -05:00
|
|
|
target = buf.length - size;
|
2017-11-14 22:13:30 -05:00
|
|
|
|
|
|
|
if ( target > 0 ) {
|
|
|
|
let removed = 0, last;
|
|
|
|
for(let i=0; i < target; i++)
|
|
|
|
if ( buf[i] && ! NULL_TYPES.includes(ct[buf[i].type]) ) {
|
|
|
|
removed++;
|
|
|
|
last = i;
|
|
|
|
}
|
2017-11-13 01:23:39 -05:00
|
|
|
|
2017-11-16 14:24:17 -05:00
|
|
|
this.buffer = buf.slice(removed % 2 === 0 ? target : Math.max(target - 10, last));
|
2017-11-14 22:13:30 -05:00
|
|
|
} else
|
|
|
|
// Make a shallow copy of the array because other code expects it to change.
|
2017-11-16 15:54:58 -05:00
|
|
|
this.buffer = buf.slice(0);
|
2017-11-13 01:23:39 -05:00
|
|
|
|
2017-11-14 22:13:30 -05:00
|
|
|
this._isDirty = false;
|
|
|
|
return this.buffer;
|
2017-11-13 01:23:39 -05:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2018-04-28 17:56:03 -04:00
|
|
|
sendMessage(room, message) {
|
|
|
|
const controller = this.ChatController.first,
|
|
|
|
service = controller && controller.chatService;
|
|
|
|
|
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);
|
|
|
|
|
|
|
|
if ( room.toLowerCase() !== service.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);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2017-11-13 01:23:39 -05:00
|
|
|
wrapChatService(cls) {
|
|
|
|
const t = this,
|
2018-04-28 17:56:03 -04:00
|
|
|
old_handler = cls.prototype.connectHandlers,
|
|
|
|
old_send = cls.prototype.sendMessage;
|
2017-11-13 01:23:39 -05:00
|
|
|
|
|
|
|
cls.prototype._ffz_was_here = true;
|
|
|
|
|
2018-04-28 17:56:03 -04:00
|
|
|
|
|
|
|
cls.prototype.sendMessage = function(raw_msg) {
|
|
|
|
const msg = raw_msg.replace(/\n/g, '');
|
|
|
|
|
|
|
|
if ( msg.startsWith('/ffz') ) {
|
|
|
|
this.postMessage({
|
|
|
|
type: t.chat_types.Notice,
|
|
|
|
message: 'The /ffz command is not yet re-implemented.'
|
|
|
|
})
|
|
|
|
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2018-05-22 17:23:20 -04:00
|
|
|
const event = new FFZEvent({
|
|
|
|
message: msg,
|
|
|
|
channel: this.channelLogin
|
|
|
|
});
|
|
|
|
|
|
|
|
t.emit('chat:pre-send-message', event);
|
|
|
|
|
|
|
|
if ( event.defaultPrevented )
|
|
|
|
return;
|
|
|
|
|
2018-04-28 17:56:03 -04:00
|
|
|
return old_send.call(this, msg);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2018-06-27 14:13:59 -04:00
|
|
|
cls.prototype.ffzGetEmotes = function() {
|
2018-07-18 17:06:14 -04:00
|
|
|
const emote_map = this.client && this.client.session && this.client.session.emoteMap;
|
|
|
|
if ( this._ffz_cached_map === emote_map )
|
2018-06-27 14:13:59 -04:00
|
|
|
return this._ffz_cached_emotes;
|
|
|
|
|
2018-07-18 17:06:14 -04:00
|
|
|
this._ffz_cached_map = emote_map;
|
2018-06-27 14:13:59 -04:00
|
|
|
const emotes = this._ffz_cached_emotes = {};
|
|
|
|
|
2018-07-18 17:06:14 -04:00
|
|
|
if ( emote_map )
|
|
|
|
for(const emote of Object.values(emote_map))
|
|
|
|
if ( emote ) {
|
|
|
|
const token = emote.token;
|
|
|
|
if ( Array.isArray(REGEX_EMOTES[token]) ) {
|
|
|
|
for(const tok of REGEX_EMOTES[token] )
|
|
|
|
emotes[tok] = emote.id;
|
|
|
|
|
|
|
|
} else
|
|
|
|
emotes[token] = emote.id;
|
|
|
|
}
|
2018-06-27 14:13:59 -04:00
|
|
|
|
|
|
|
return emotes;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-06-27 14:13:59 -04:00
|
|
|
const old_chat = this.onChatMessageEvent;
|
|
|
|
this.onChatMessageEvent = function(e) {
|
|
|
|
if ( e && e.sentByCurrentUser ) {
|
|
|
|
try {
|
2018-07-14 14:13:28 -04:00
|
|
|
e.message.user.emotes = findEmotes(
|
2018-06-27 14:13:59 -04:00
|
|
|
e.message.body,
|
|
|
|
i.ffzGetEmotes()
|
|
|
|
);
|
|
|
|
|
|
|
|
} catch(err) {
|
|
|
|
t.log.capture(err, {extra: e});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return old_chat.call(i, e);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const old_action = this.onChatActionEvent;
|
|
|
|
this.onChatActionEvent = function(e) {
|
|
|
|
if ( e && e.sentByCurrentUser ) {
|
|
|
|
try {
|
2018-07-14 14:13:28 -04:00
|
|
|
e.message.user.emotes = findEmotes(
|
2018-07-21 16:26:10 -04:00
|
|
|
e.message.body,
|
2018-06-27 14:13:59 -04:00
|
|
|
i.ffzGetEmotes()
|
|
|
|
);
|
|
|
|
|
|
|
|
} catch(err) {
|
|
|
|
t.log.capture(err, {extra: e});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return old_action.call(i, e);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2017-11-23 02:49:23 -05:00
|
|
|
const old_resub = this.onResubscriptionEvent;
|
|
|
|
this.onResubscriptionEvent = function(e) {
|
|
|
|
try {
|
|
|
|
const out = i.convertMessage({message: e});
|
|
|
|
out.ffz_type = 'resub';
|
|
|
|
out.sub_months = e.months;
|
|
|
|
out.sub_plan = e.methods;
|
|
|
|
|
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);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-02-02 16:00:58 -05:00
|
|
|
const old_ritual = this.onRitualEvent;
|
|
|
|
this.onRitualEvent = function(e) {
|
|
|
|
try {
|
|
|
|
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);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-12-14 05:43:56 +01:00
|
|
|
const old_host = this.onHostingEvent;
|
|
|
|
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);
|
|
|
|
}
|
|
|
|
|
2018-04-28 17:56:03 -04:00
|
|
|
const old_post = this.postMessage;
|
2017-11-13 01:23:39 -05:00
|
|
|
this.postMessage = 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-06-27 14:13:59 -04:00
|
|
|
return old_post.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) {
|
|
|
|
message._ffz_checked = true;
|
|
|
|
|
|
|
|
if ( original.channel ) {
|
|
|
|
let chan = message.channel = original.channel.toLowerCase();
|
|
|
|
if ( chan.startsWith('#') )
|
|
|
|
chan = chan.slice(1);
|
|
|
|
|
|
|
|
if ( chan !== this.channelLogin.toLowerCase() )
|
|
|
|
return;
|
|
|
|
|
|
|
|
message.roomLogin = chan;
|
|
|
|
}
|
|
|
|
|
|
|
|
if ( original.message ) {
|
|
|
|
const user = original.message.user;
|
|
|
|
if ( user )
|
|
|
|
message.emotes = user.emotes;
|
|
|
|
|
|
|
|
if ( typeof original.action === 'string' )
|
|
|
|
message.message = original.action;
|
|
|
|
else
|
|
|
|
message.message = original.message.body;
|
|
|
|
}
|
|
|
|
|
|
|
|
this.postMessage(message);
|
|
|
|
}
|
2017-11-13 01:23:39 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
updateChatLines() {
|
2018-03-01 04:13:52 -05:00
|
|
|
this.PinnedCheer.forceUpdate();
|
2017-11-14 22:13:30 -05:00
|
|
|
this.chat_line.updateLines();
|
2017-11-13 01:23:39 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// ========================================================================
|
|
|
|
// Pinned Cheers
|
|
|
|
// ========================================================================
|
|
|
|
|
|
|
|
fixPinnedCheer(inst) {
|
2018-02-22 18:38:13 -05:00
|
|
|
const el = this.fine.getChildNode(inst),
|
2017-11-13 01:23:39 -05:00
|
|
|
container = el && el.querySelector && el.querySelector('.pinned-cheer__headline'),
|
|
|
|
tc = inst.props.topCheer;
|
|
|
|
|
|
|
|
if ( ! container || ! tc )
|
|
|
|
return;
|
|
|
|
|
|
|
|
container.dataset.roomId = inst.props.channelID;
|
2017-11-14 18:48:56 -05:00
|
|
|
container.dataset.room = inst.props.channelLogin && inst.props.channelLogin.toLowerCase();
|
2017-11-13 01:23:39 -05:00
|
|
|
container.dataset.userId = tc.user.userID;
|
2017-11-14 18:48:56 -05:00
|
|
|
container.dataset.user = tc.user.userLogin && tc.user.userLogin.toLowerCase();
|
2017-11-13 01:23:39 -05:00
|
|
|
|
|
|
|
if ( tc.user.color ) {
|
|
|
|
const user_el = container.querySelector('.chat-author__display-name');
|
|
|
|
if ( user_el )
|
|
|
|
user_el.style.color = this.colors.process(tc.user.color);
|
|
|
|
|
|
|
|
const login_el = container.querySelector('.chat-author__intl-login');
|
|
|
|
if ( login_el )
|
|
|
|
login_el.style.color = this.colors.process(tc.user.color);
|
|
|
|
}
|
|
|
|
|
|
|
|
const bit_el = container.querySelector('.chat-line__message--emote'),
|
|
|
|
cont = bit_el ? bit_el.parentElement.parentElement : container.querySelector('.ffz--pinned-top-emote'),
|
|
|
|
prefix = extractCheerPrefix(tc.messageParts);
|
|
|
|
|
|
|
|
if ( cont && prefix ) {
|
|
|
|
const tokens = this.chat.tokenizeString(`${prefix}${tc.bits}`, tc);
|
|
|
|
|
|
|
|
cont.classList.add('ffz--pinned-top-emote');
|
|
|
|
cont.innerHTML = '';
|
|
|
|
setChildren(cont, this.chat.renderTokens(tokens));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// ========================================================================
|
|
|
|
// 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
|
|
|
|
});
|
|
|
|
|
|
|
|
this.chat.context.updateContext({
|
|
|
|
moderator: props.isCurrentUserModerator,
|
|
|
|
channel: props.channelLogin && props.channelLogin.toLowerCase(),
|
|
|
|
channelID: props.channelID,
|
|
|
|
ui: {
|
|
|
|
theme: props.theme
|
|
|
|
}
|
|
|
|
});
|
2017-11-13 01:23:39 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2018-07-14 14:13:28 -04:00
|
|
|
chatUmounted(chat) {
|
|
|
|
if ( chat.chatBuffer && chat.chatBuffer.ffzController === this )
|
|
|
|
chat.chatBuffer.ffzController = null;
|
|
|
|
|
|
|
|
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;
|
|
|
|
|
2017-11-13 01:23:39 -05:00
|
|
|
if ( props.channelID !== chat.props.channelID ) {
|
|
|
|
this.removeRoom(chat);
|
|
|
|
this.chatMounted(chat, props);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if ( props.bitsConfig !== chat.props.bitsConfig )
|
|
|
|
this.updateRoomBitsConfig(chat, props.bitsConfig);
|
|
|
|
|
|
|
|
// TODO: Check if this is the room for the current channel.
|
|
|
|
|
|
|
|
this.settings.updateContext({
|
|
|
|
moderator: props.isCurrentUserModerator,
|
|
|
|
chatHidden: props.isHidden
|
|
|
|
});
|
|
|
|
|
|
|
|
this.chat.context.updateContext({
|
|
|
|
moderator: props.isCurrentUserModerator,
|
2017-11-14 18:48:56 -05:00
|
|
|
channel: props.channelLogin && props.channelLogin.toLowerCase(),
|
2017-11-13 01:23:39 -05:00
|
|
|
channelID: props.channelID,
|
|
|
|
ui: {
|
|
|
|
theme: props.theme
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
updateRoomBitsConfig(chat, config) { // eslint-disable-line class-methods-use-this
|
|
|
|
const room = chat._ffz_room;
|
|
|
|
if ( ! room )
|
|
|
|
return;
|
|
|
|
|
|
|
|
room.updateBitsConfig(formatBitsConfig(config));
|
|
|
|
this.updateChatLines();
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// ========================================================================
|
|
|
|
// Chat Containers
|
|
|
|
// ========================================================================
|
|
|
|
|
|
|
|
containerMounted(cont, props) {
|
|
|
|
if ( ! props )
|
|
|
|
props = cont.props;
|
|
|
|
|
|
|
|
if ( ! this.addRoom(cont, props) )
|
|
|
|
return;
|
|
|
|
|
2018-01-19 17:17:16 -05:00
|
|
|
if ( props.data ) {
|
|
|
|
this.chat.badges.updateTwitchBadges(props.data.badges);
|
|
|
|
this.updateRoomBadges(cont, props.data.user && props.data.user.broadcastBadges);
|
2017-11-13 01:23:39 -05:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
containerUpdated(cont, props) {
|
|
|
|
if ( props.channelID !== cont.props.channelID ) {
|
|
|
|
this.removeRoom(cont);
|
|
|
|
this.containerMounted(cont, props);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// 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
|
|
|
|
2018-01-19 17:17:16 -05:00
|
|
|
if ( bs.length !== obs.length )
|
|
|
|
this.chat.badges.updateTwitchBadges(bs);
|
2017-11-13 01:23:39 -05:00
|
|
|
|
2018-01-19 17:17:16 -05:00
|
|
|
if ( cs.length !== ocs.length )
|
|
|
|
this.updateRoomBadges(cont, cs);
|
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);
|
|
|
|
this.updateChatLines();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// ============================================================================
|
|
|
|
// Processing Functions
|
|
|
|
// ============================================================================
|
|
|
|
|
|
|
|
export function formatBitsConfig(config) {
|
|
|
|
if ( ! config )
|
|
|
|
return;
|
|
|
|
|
|
|
|
const out = {},
|
|
|
|
actions = config.indexedActions;
|
|
|
|
|
|
|
|
for(const key in actions)
|
|
|
|
if ( has(actions, key) ) {
|
|
|
|
const action = actions[key],
|
|
|
|
new_act = out[key] = {
|
|
|
|
id: action.id,
|
|
|
|
prefix: action.prefix,
|
|
|
|
tiers: []
|
|
|
|
};
|
|
|
|
|
|
|
|
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,
|
|
|
|
id: tier.id,
|
|
|
|
images
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return out;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2017-11-23 02:49:23 -05:00
|
|
|
export function findEmotes(msg, emotes) {
|
|
|
|
const out = {};
|
|
|
|
let idx = 0;
|
|
|
|
|
|
|
|
for(const part of msg.split(' ')) {
|
|
|
|
const len = split_chars(part).length;
|
|
|
|
|
|
|
|
if ( has(emotes, part) ) {
|
|
|
|
const em = emotes[part],
|
2018-06-27 14:13:59 -04:00
|
|
|
matches = out[em] = out[em] || [];
|
2017-11-23 02:49:23 -05:00
|
|
|
|
|
|
|
matches.push({
|
|
|
|
startIndex: idx,
|
|
|
|
endIndex: idx + len - 1
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
idx += len + 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
return out;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2017-11-13 01:23:39 -05:00
|
|
|
function extractCheerPrefix(parts) {
|
|
|
|
for(const part of parts) {
|
|
|
|
if ( part.type !== 3 || ! part.content.cheerAmount )
|
|
|
|
continue;
|
|
|
|
|
|
|
|
return part.content.alt;
|
|
|
|
}
|
|
|
|
|
|
|
|
return null;
|
2018-04-08 21:20:46 +02:00
|
|
|
}
|