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';
|
2019-04-30 15:18:29 -04:00
|
|
|
import {has, make_enum, split_chars, shallow_object_equals, set_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';
|
2019-05-07 15:04:12 -04:00
|
|
|
import Input from './input';
|
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-10-16 18:04:54 -04:00
|
|
|
const MESSAGE_TYPES = make_enum(
|
|
|
|
'Post',
|
|
|
|
'Action',
|
|
|
|
'PostWithMention'
|
|
|
|
);
|
|
|
|
|
|
|
|
const MOD_TYPES = make_enum(
|
|
|
|
'Ban',
|
|
|
|
'Timeout',
|
|
|
|
'Delete'
|
|
|
|
);
|
|
|
|
|
|
|
|
const AUTOMOD_TYPES = make_enum(
|
|
|
|
'MessageRejectedPrompt',
|
|
|
|
'CheerMessageRejectedPrompt',
|
|
|
|
'MessageRejected',
|
|
|
|
'MessageAllowed',
|
|
|
|
'MessageDenied',
|
|
|
|
'CheerMessageDenied',
|
|
|
|
'CheerMessageTimeout',
|
|
|
|
'MessageModApproved',
|
|
|
|
'MessageModDenied'
|
|
|
|
);
|
|
|
|
|
|
|
|
const CHAT_TYPES = make_enum(
|
|
|
|
'Message',
|
|
|
|
'ExtensionMessage',
|
|
|
|
'Moderation',
|
|
|
|
'ModerationAction',
|
|
|
|
'TargetedModerationAction',
|
|
|
|
'AutoMod',
|
|
|
|
'SubscriberOnlyMode',
|
|
|
|
'FollowerOnlyMode',
|
|
|
|
'SlowMode',
|
|
|
|
'EmoteOnlyMode',
|
|
|
|
'R9KMode',
|
|
|
|
'Connected',
|
|
|
|
'Disconnected',
|
|
|
|
'Reconnect',
|
|
|
|
'Hosting',
|
|
|
|
'Unhost',
|
|
|
|
'Hosted',
|
|
|
|
'Subscription',
|
|
|
|
'Resubscription',
|
|
|
|
'GiftPaidUpgrade',
|
2018-11-12 13:34:53 -05:00
|
|
|
'AnonGiftPaidUpgrade',
|
2019-03-14 21:43:44 -04:00
|
|
|
'PrimePaidUpgrade',
|
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',
|
|
|
|
'Ritual',
|
|
|
|
'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-02-05 14:24:45 -05:00
|
|
|
'FirstCheerMessage',
|
2019-04-18 03:16:19 -04:00
|
|
|
'BitsBadgeTierMessage',
|
|
|
|
'InlinePrivateCallout'
|
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');
|
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);
|
2019-05-07 15:04:12 -04:00
|
|
|
this.inject(Input);
|
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',
|
2018-08-28 19:13:26 -04:00
|
|
|
n => n.hostingHandler && n.onRoomStateUpdated,
|
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',
|
2018-03-14 13:58:04 -04:00
|
|
|
n => n.showViewersList && n.onChatInputFocus,
|
|
|
|
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
|
|
|
|
);
|
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
2019-05-16 14:46:26 -04:00
|
|
|
this.settings.add('chat.use-width', {
|
|
|
|
requires: ['chat.width', 'context.ui.rightColumnExpanded'],
|
|
|
|
process(ctx) {
|
|
|
|
if ( ! ctx.get('context.ui.rightColumnExpanded') )
|
|
|
|
return false;
|
|
|
|
|
|
|
|
return ctx.get('chat.width') != 340;
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
2017-11-13 01:23:39 -05:00
|
|
|
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'
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
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)'}
|
|
|
|
]
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
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() {
|
2019-05-16 14:46:26 -04:00
|
|
|
if ( ! this._update_chat_css_timer )
|
|
|
|
this._update_chat_css_timer = setTimeout(() => this._updateChatCSS(), 0);
|
|
|
|
}
|
|
|
|
|
|
|
|
_updateChatCSS() {
|
|
|
|
clearTimeout(this._update_chat_css_timer);
|
|
|
|
this._update_chat_css_timer = null;
|
|
|
|
|
2017-11-14 18:48:56 -05:00
|
|
|
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);
|
2019-05-16 14:46:26 -04:00
|
|
|
this.css_tweaks.toggle('chat-width', this.chat.context.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);
|
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);
|
2019-05-16 14:46:26 -04:00
|
|
|
this.chat.context.on('changed:chat.use-width', this.updateChatCSS, this);
|
2017-11-14 18:48:56 -05:00
|
|
|
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);
|
2019-04-18 21:07:11 -04:00
|
|
|
this.chat.context.on('changed:chat.filtering.display-deleted', this.updateChatLines, this);
|
|
|
|
this.chat.context.on('changed:chat.filtering.display-mod-action', 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));
|
|
|
|
|
2019-04-18 03:16:19 -04:00
|
|
|
this.chat.context.on('changed:chat.filtering.deleted-style', val => {
|
|
|
|
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
|
|
|
|
2019-04-18 03:16:19 -04:00
|
|
|
const val = this.chat.context.get('chat.filtering.deleted-style');
|
|
|
|
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
|
|
|
|
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);
|
|
|
|
|
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();
|
|
|
|
|
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);
|
|
|
|
|
|
|
|
inst.props.setMessageBufferAPI({
|
|
|
|
addUpdateHandler: inst.addUpdateHandler,
|
|
|
|
removeUpdateHandler: inst.removeUpdateHandler,
|
|
|
|
getMessages: inst.getMessages,
|
|
|
|
_ffz_inst: inst
|
|
|
|
});
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
this.ChatBufferConnector.on('mount', this.connectorMounted, this);
|
|
|
|
this.ChatBufferConnector.on('receive-props', this.connectorUpdated, this);
|
|
|
|
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);
|
|
|
|
this.ChatContainer.on('unmount', 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,
|
|
|
|
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);
|
|
|
|
}
|
|
|
|
|
2019-05-04 03:36:10 -04:00
|
|
|
|
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);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
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);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2018-04-28 17:56:03 -04:00
|
|
|
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-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,
|
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 || {},
|
|
|
|
mod_types = t.mod_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),
|
2018-08-28 19:13:26 -04:00
|
|
|
cont = inst._ffz_connector,
|
2018-08-28 09:45:53 -04:00
|
|
|
room_id = cont && cont.props.channelID;
|
2018-08-27 20:15:43 -04:00
|
|
|
|
2018-08-28 19:13:26 -04:00
|
|
|
let room = m.roomLogin = m.roomLogin ? m.roomLogin : m.channel ? m.channel.slice(1) : cont && cont.props.channelLogin;
|
|
|
|
|
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,
|
2018-08-28 19:13:26 -04:00
|
|
|
channel: room,
|
|
|
|
channelID: room_id
|
2018-07-16 15:07:22 -04:00
|
|
|
});
|
|
|
|
|
|
|
|
t.emit('chat:receive-message', event);
|
|
|
|
if ( event.defaultPrevented || m.ffz_removed )
|
|
|
|
return;
|
2018-07-14 14:13:28 -04:00
|
|
|
|
2019-05-07 15:04:12 -04:00
|
|
|
} else if ( msg.type === types.ModerationAction && inst.markUserEventDeleted && inst.unsetModeratedUser ) {
|
2019-04-18 21:07:11 -04:00
|
|
|
//t.log.info('Moderation Action', msg);
|
|
|
|
if ( ! inst.props.isCurrentUserModerator )
|
|
|
|
return;
|
2019-04-18 03:16:19 -04:00
|
|
|
|
2019-04-18 21:07:11 -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();
|
|
|
|
|
|
|
|
inst.ffzModerateBuffer([inst.delayedMessageBuffer], msg);
|
|
|
|
|
|
|
|
} else
|
|
|
|
inst.ffzModerateBuffer([inst.buffer, inst.delayedMessageBuffer], msg);
|
|
|
|
|
|
|
|
inst.moderatedUsers.add(user);
|
|
|
|
setTimeout(inst.unsetModeratedUser(user), 1e3);
|
|
|
|
|
|
|
|
inst.delayedMessageBuffer.push({
|
|
|
|
event: msg,
|
|
|
|
time: Date.now(),
|
|
|
|
shouldDelay: false
|
|
|
|
});
|
|
|
|
|
|
|
|
return;
|
|
|
|
}
|
2019-04-18 03:16:19 -04:00
|
|
|
|
2019-05-07 15:04:12 -04:00
|
|
|
} else if ( msg.type === types.Moderation && inst.markUserEventDeleted && 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
|
|
|
|
2019-04-18 21:07:11 -04:00
|
|
|
inst.ffzModerateBuffer([inst.delayedMessageBuffer], msg);
|
|
|
|
|
2018-07-16 15:07:22 -04:00
|
|
|
} else
|
2019-04-18 21:07:11 -04:00
|
|
|
inst.ffzModerateBuffer([inst.buffer, inst.delayedMessageBuffer], msg);
|
|
|
|
|
|
|
|
inst.moderatedUsers.add(user);
|
|
|
|
setTimeout(inst.unsetModeratedUser(user), 1e3);
|
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
|
|
|
|
2018-07-16 15:07:22 -04:00
|
|
|
} else if ( msg.type === types.Clear ) {
|
|
|
|
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
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
} 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
|
|
|
}
|
|
|
|
|
2019-04-18 21:07:11 -04:00
|
|
|
inst.ffzModerateBuffer = function(buffers, event) {
|
|
|
|
const mod_types = t.mod_types || {},
|
|
|
|
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;
|
|
|
|
|
|
|
|
if ( target_id && m.id !== target_id )
|
|
|
|
return;
|
|
|
|
|
|
|
|
const msg = inst.markUserEventDeleted(m, user_login);
|
|
|
|
if ( ! msg )
|
|
|
|
return;
|
|
|
|
|
|
|
|
last_msg = msg;
|
|
|
|
deleted_count++;
|
|
|
|
|
|
|
|
msg.modLogin = mod_login;
|
|
|
|
msg.modActionType = mod_type;
|
|
|
|
msg.duration = event.duration;
|
|
|
|
|
|
|
|
if ( is_delete )
|
|
|
|
return true;
|
|
|
|
};
|
|
|
|
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
2018-08-28 19:13:26 -04:00
|
|
|
inst.getMessages = function() {
|
|
|
|
const buf = inst.buffer,
|
|
|
|
size = t.chat.context.get('chat.scrollback-length'),
|
|
|
|
ct = t.chat_types || CHAT_TYPES,
|
|
|
|
target = buf.length - size;
|
|
|
|
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
|
|
|
inst.buffer = buf.slice(removed % 2 === 0 ? target : Math.max(target - 10, last));
|
|
|
|
} else
|
|
|
|
// Make a shallow copy of the array because other code expects it to change.
|
|
|
|
inst.buffer = buf.slice(0);
|
|
|
|
|
|
|
|
return inst.buffer;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
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);
|
|
|
|
}
|
|
|
|
|
2018-07-16 15:07:22 -04:00
|
|
|
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,
|
2019-01-31 19:58:21 -05:00
|
|
|
see_deleted = this.shouldSeeBlockedAndDeletedMessages || this.props && this.props.shouldSeeBlockedAndDeletedMessages,
|
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 ) {
|
2019-01-31 19:58:21 -05:00
|
|
|
if ( do_remove !== 0 && (do_remove > 1 || ! see_deleted) && 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;
|
2018-08-28 19:13:26 -04:00
|
|
|
if ( changed && ! this.props.isBackground )
|
|
|
|
this.notifySubscribers();
|
2017-11-13 01:23:39 -05:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
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);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
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-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;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2018-10-14 13:12:51 -04:00
|
|
|
cls.prototype._ffzInstall = function() {
|
|
|
|
if ( this._ffz_installed )
|
|
|
|
return;
|
|
|
|
|
|
|
|
this._ffz_installed = true;
|
|
|
|
|
|
|
|
const inst = this,
|
|
|
|
old_send = this.sendMessage;
|
|
|
|
|
|
|
|
inst.sendMessage = function(raw_msg) {
|
|
|
|
const msg = raw_msg.replace(/\n/g, '');
|
|
|
|
|
|
|
|
if ( msg.startsWith('/ffz') ) {
|
|
|
|
inst.addMessage({
|
|
|
|
type: t.chat_types.Notice,
|
|
|
|
message: 'The /ffz command is not yet re-implemented.'
|
|
|
|
})
|
|
|
|
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
const event = new FFZEvent({
|
|
|
|
message: msg,
|
|
|
|
channel: inst.props.channelLogin
|
|
|
|
});
|
|
|
|
|
|
|
|
t.emit('chat:pre-send-message', event);
|
|
|
|
|
|
|
|
if ( event.defaultPrevented )
|
|
|
|
return;
|
|
|
|
|
|
|
|
return old_send.call(this, msg);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
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);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2019-02-05 14:24:45 -05:00
|
|
|
const old_sub = this.onSubscriptionEvent;
|
|
|
|
this.onSubscriptionEvent = function(e) {
|
|
|
|
try {
|
|
|
|
if ( t.chat.context.get('chat.subs.show') < 3 )
|
|
|
|
return;
|
|
|
|
|
|
|
|
e.body = '';
|
|
|
|
const out = i.convertMessage({message: e});
|
|
|
|
out.ffz_type = 'resub';
|
|
|
|
out.sub_plan = e.methods;
|
|
|
|
return i.postMessageToCurrentChannel(e, out);
|
|
|
|
|
|
|
|
} catch(err) {
|
|
|
|
t.log.capture(err, {extra: e});
|
|
|
|
return old_sub.call(i, e);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-11-23 02:49:23 -05:00
|
|
|
const old_resub = this.onResubscriptionEvent;
|
|
|
|
this.onResubscriptionEvent = function(e) {
|
|
|
|
try {
|
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';
|
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 {
|
|
|
|
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 )
|
|
|
|
mystery.line.forceUpdate();
|
|
|
|
|
|
|
|
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
|
|
|
|
};
|
|
|
|
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);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
const old_anonsubgift = this.onAnonSubscriptionGiftEvent;
|
|
|
|
this.onAnonSubscriptionGiftEvent = function(e) {
|
|
|
|
try {
|
|
|
|
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 )
|
|
|
|
mystery.line.forceUpdate();
|
|
|
|
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
e.body = '';
|
|
|
|
const out = i.convertMessage({message: e});
|
|
|
|
out.ffz_type = 'sub_gift';
|
|
|
|
out.sub_anon = true;
|
|
|
|
out.sub_recipient = {
|
|
|
|
id: e.recipientID,
|
|
|
|
login: e.recipientLogin,
|
|
|
|
displayName: e.recipientName
|
|
|
|
};
|
|
|
|
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 {
|
|
|
|
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';
|
|
|
|
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 {
|
|
|
|
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 {
|
|
|
|
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-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) {
|
|
|
|
message._ffz_checked = true;
|
|
|
|
|
|
|
|
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-01-18 19:07:57 -05:00
|
|
|
if ( flags && this.getFilterFlagOptions )
|
|
|
|
message.flags = this.getFilterFlagOptions(flags);
|
|
|
|
|
2018-04-28 17:56:03 -04:00
|
|
|
if ( typeof original.action === 'string' )
|
|
|
|
message.message = original.action;
|
|
|
|
else
|
|
|
|
message.message = original.message.body;
|
|
|
|
}
|
|
|
|
|
2018-08-28 19:13:26 -04:00
|
|
|
this.addMessage(message);
|
2018-04-28 17:56:03 -04:00
|
|
|
}
|
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;
|
|
|
|
|
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 )
|
|
|
|
this.chatMounted(chat, props);
|
2017-11-13 01:23:39 -05:00
|
|
|
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;
|
|
|
|
|
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));
|
|
|
|
this.updateChatLines();
|
|
|
|
}
|
|
|
|
|
|
|
|
|
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
|
|
|
|
const buffer = inst.props.messageBufferAPI,
|
|
|
|
new_buffer = props.messageBufferAPI;
|
|
|
|
|
|
|
|
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
|
|
|
|
// ========================================================================
|
|
|
|
|
|
|
|
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);
|
2019-04-29 18:14:04 -04:00
|
|
|
this.updateRoomRules(cont, props.chatRules);
|
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;
|
|
|
|
}
|
|
|
|
|
|
|
|
// 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);
|
|
|
|
this.updateChatLines();
|
|
|
|
}
|
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 = {},
|
|
|
|
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
|
|
|
}
|