From cf3de0d2382f2d197a88382d4f516e6191da24cb Mon Sep 17 00:00:00 2001 From: SirStendec Date: Mon, 27 Aug 2018 20:15:43 -0400 Subject: [PATCH] 4.0.0-rc12.15 * Not Fixed: Directory stuff. * Fixed: Chat handling. This is an initial release to add support for Twitch's changes to how chat works internally. Everything should be working. I'm not entirely happy with the code though, so I'll be reviewing this in the future when I have time to clean things up. I haven't had a chance to go over directory features yet, and likely won't for several days because of my job. --- src/main.js | 2 +- .../twitch-twilight/modules/chat/index.js | 308 ++++++++++++------ .../twitch-twilight/modules/host_button.js | 15 +- src/socket.js | 7 +- 4 files changed, 215 insertions(+), 117 deletions(-) diff --git a/src/main.js b/src/main.js index 520d8836..2738d8db 100644 --- a/src/main.js +++ b/src/main.js @@ -100,7 +100,7 @@ class FrankerFaceZ extends Module { FrankerFaceZ.Logger = Logger; const VER = FrankerFaceZ.version_info = { - major: 4, minor: 0, revision: 0, extra: '-rc12.14', + major: 4, minor: 0, revision: 0, extra: '-rc12.15', commit: __git_commit__, build: __webpack_hash__, toString: () => diff --git a/src/sites/twitch-twilight/modules/chat/index.js b/src/sites/twitch-twilight/modules/chat/index.js index c08fc861..8576d916 100644 --- a/src/sites/twitch-twilight/modules/chat/index.js +++ b/src/sites/twitch-twilight/modules/chat/index.js @@ -148,10 +148,21 @@ export default class ChatHook extends Module { this.inject(EmoteMenu); this.inject(TabCompletion); + this.ChatService = this.fine.define( + 'chat-service', + n => n.join && n.part && n.connectHandlers, + Twilight.CHAT_ROUTES + ); + + this.ChatBuffer = this.fine.define( + 'chat-buffer', + n => n.updateHandlers && n.delayedMessageBuffer && n.handleMessage, + Twilight.CHAT_ROUTES + ); this.ChatController = this.fine.define( 'chat-controller', - n => n.chatService, + n => n.hostingHandler && n.onRoomStateUpdated, Twilight.CHAT_ROUTES ); @@ -161,6 +172,12 @@ export default class ChatHook extends Module { Twilight.CHAT_ROUTES ); + this.ChatBufferConnector = this.fine.define( + 'chat-buffer-connector', + n => n.clearBufferHandle && n.syncBufferedMessages, + Twilight.CHAT_ROUTES + ); + this.PinnedCheer = this.fine.define( 'pinned-cheer', n => n.collapseCheer && n.saveRenderedMessageRef, @@ -392,6 +409,46 @@ export default class ChatHook extends Module { this.ChatController.on('unmount', this.chatUmounted, this); this.ChatController.on('receive-props', this.chatUpdated, this); + this.ChatService.ready((cls, instances) => { + this.wrapChatService(cls); + + for(const inst of instances) { + inst.client.events.removeAll(); + inst.connectHandlers(); + } + }); + + 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); + }) + this.ChatController.ready((cls, instances) => { const t = this, old_catch = cls.prototype.componentDidCatch, @@ -427,25 +484,8 @@ export default class ChatHook extends Module { return old_render.call(this); } - for(const inst of instances) { - const service = inst.chatService; - if ( ! service._ffz_was_here ) - this.wrapChatService(service.constructor); - - const buffer = inst.chatBuffer; - if ( ! buffer._ffz_was_here ) - this.wrapChatBuffer(buffer.constructor); - - if ( buffer.ffzConsumeChatEvent ) - buffer.consumeChatEvent = buffer.ffzConsumeChatEvent.bind(buffer); - - buffer.ffzController = inst; - - service.client.events.removeAll(); - service.connectHandlers(); - + for(const inst of instances) this.chatMounted(inst); - } }); @@ -500,24 +540,41 @@ export default class ChatHook extends Module { wrapChatBuffer(cls) { + if ( cls.prototype._ffz_was_here ) + return; + const t = this, - old_consume = cls.prototype.consumeChatEvent; + old_mount = cls.prototype.componentDidMount; - cls.prototype._ffz_was_here = true; + cls.prototype._ffzInstall = function() { + if ( this._ffz_installed ) + return; - if ( old_consume ) - cls.prototype.ffzConsumeChatEvent = cls.prototype.consumeChatEvent = function(msg) { + this._ffz_installed = true; + + 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) { if ( msg ) { try { const types = t.chat_types || {}; if ( msg.type === types.Message ) { const m = t.chat.standardizeMessage(msg), - cont = this.ffzController; - - let room = m.roomLogin = m.roomLogin ? m.roomLogin : m.channel ? m.channel.slice(1) : cont && cont.props.channelLogin, + cont = inst._ffz_connector, room_id = cont && cont.props.channelID; + let room = m.roomLogin = m.roomLogin ? m.roomLogin : m.channel ? m.channel.slice(1) : cont && cont.props.channelLogin; + if ( ! room && room_id ) { const r = t.chat.getRoom(room_id, null, true); if ( r && r.login ) @@ -536,7 +593,8 @@ export default class ChatHook extends Module { const event = new FFZEvent({ message: m, - channel: room + channel: room, + channelID: room_id }); t.emit('chat:receive-message', event); @@ -553,41 +611,79 @@ export default class ChatHook extends Module { if ( m.event ) m = m.event; - if ( m.type === types.Message && m.user && m.user.userLogin === login ) - m.deleted = true; + if ( m.type === types.Message ) { + if ( m.user && m.user.userLogin === login ) + m.deleted = true; + } else if ( m.type === types.Resubscription || m.type === types.Ritual ) { + if ( m.message && m.message.user && m.message.user.userLogin === login ) + m.deleted = true; + } }; if ( do_remove ) { - this.buffer = this.buffer.filter(m => m.type !== types.Message || ! m.user || m.user.userLogin !== login); - this._isDirty = true; - this.onBufferUpdate(); + const len = inst.buffer.length; + inst.buffer = inst.buffer.filter(m => m.type !== types.Message || ! m.user || m.user.userLogin !== login); + if ( len !== inst.buffer.length && ! inst.props.isBackground ) + inst.notifySubscribers(); } else - this.buffer.forEach(do_update); + inst.buffer.forEach(do_update); - this.delayedMessageBuffer.forEach(do_update); + inst.delayedMessageBuffer.forEach(do_update); - this.moderatedUsers.add(login); - setTimeout(this.unmoderateUser(login), 1000); + inst.moderatedUsers.add(login); + setTimeout(inst.unmoderateUser(login), 1000); return; } else if ( msg.type === types.Clear ) { if ( t.chat.context.get('chat.filtering.ignore-clear') ) msg = { - type: types.Notice, + types: types.Notice, message: t.i18n.t('chat.ignore-clear', 'An attempt to clear chat was ignored.') } - } } catch(err) { - t.log.capture(err, {extra: {msg}}) + t.log.capture(err, {extra: {msg}}); } } - return old_consume.call(this, msg); + return old_handle.call(this, msg); } + 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); + } + cls.prototype.flushRawMessages = function() { const out = [], now = Date.now(), @@ -611,41 +707,14 @@ export default class ChatHook extends Module { } this.delayedMessageBuffer = out; - - if ( changed ) { - this._isDirty = true; - this.onBufferUpdate(); - } - } - - cls.prototype.toArray = function() { - const buf = this.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; - } - - this.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. - this.buffer = buf.slice(0); - - this._isDirty = false; - return this.buffer; + if ( changed && ! this.props.isBackground ) + this.notifySubscribers(); } } sendMessage(room, message) { - const controller = this.ChatController.first, - service = controller && controller.chatService; + const service = this.ChatService.first; if ( ! service || ! room ) return null; @@ -653,7 +722,7 @@ export default class ChatHook extends Module { if ( room.startsWith('#') ) room = room.slice(1); - if ( room.toLowerCase() !== service.channelLogin.toLowerCase() ) + if ( room.toLowerCase() !== service.props.channelLogin.toLowerCase() ) return service.client.sendCommand(room, message); service.sendMessage(message); @@ -662,38 +731,10 @@ export default class ChatHook extends Module { wrapChatService(cls) { const t = this, - old_handler = cls.prototype.connectHandlers, - old_send = cls.prototype.sendMessage; + old_handler = cls.prototype.connectHandlers; cls.prototype._ffz_was_here = true; - - 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; - } - - const event = new FFZEvent({ - message: msg, - channel: this.channelLogin - }); - - t.emit('chat:pre-send-message', event); - - if ( event.defaultPrevented ) - return; - - return old_send.call(this, msg); - } - - cls.prototype.ffzGetEmotes = function() { const emote_map = this.client && this.client.session && this.client.session.emoteMap; if ( this._ffz_cached_map === emote_map ) @@ -733,6 +774,32 @@ export default class ChatHook extends Module { } } + const old_send = this.sendMessage; + this.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; + } + + const event = new FFZEvent({ + message: msg, + channel: this.channelLogin + }); + + t.emit('chat:pre-send-message', event); + + if ( event.defaultPrevented ) + return; + + return old_send.call(this, msg); + } + const old_chat = this.onChatMessageEvent; this.onChatMessageEvent = function(e) { if ( e && e.sentByCurrentUser ) { @@ -812,13 +879,13 @@ export default class ChatHook extends Module { return old_unhost.call(i, e, _t); } - const old_post = this.postMessage; - this.postMessage = function(e) { + const old_add = this.addMessage; + this.addMessage = function(e) { const original = i._wrapped; if ( original && ! e._ffz_checked ) return i.postMessageToCurrentChannel(original, e); - return old_post.call(i, e); + return old_add.call(i, e); } this._ffz_init = true; @@ -835,7 +902,7 @@ export default class ChatHook extends Module { if ( chan.startsWith('#') ) chan = chan.slice(1); - if ( chan !== this.channelLogin.toLowerCase() ) + if ( chan !== this.props.channelLogin.toLowerCase() ) return; message.roomLogin = chan; @@ -852,7 +919,7 @@ export default class ChatHook extends Module { message.message = original.message.body; } - this.postMessage(message); + this.addMessage(message); } } @@ -1013,6 +1080,37 @@ export default class ChatHook extends Module { } + // ======================================================================== + // 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; + } + + // ======================================================================== // Chat Containers // ======================================================================== diff --git a/src/sites/twitch-twilight/modules/host_button.js b/src/sites/twitch-twilight/modules/host_button.js index c50970c3..01d92357 100644 --- a/src/sites/twitch-twilight/modules/host_button.js +++ b/src/sites/twitch-twilight/modules/host_button.js @@ -152,7 +152,7 @@ export default class HostButton extends Module { } hookIntoChatConnection(inst) { - const userLogin = inst.props.userLogin; + const userLogin = inst.props.currentUserLogin; if (this._chat_con) { this.joinChannel(userLogin); @@ -181,7 +181,7 @@ export default class HostButton extends Module { this.metadata.updateMetadata('host'); }); - const chatServiceClient = inst.chatService.client; + const chatServiceClient = inst.client; this._chat_con = chatServiceClient; if (this.settings.get('metadata.host-button')) @@ -191,13 +191,12 @@ export default class HostButton extends Module { onEnable() { this.metadata.updateMetadata('host'); - this.chat.ChatController.ready((cls, instances) => { - for(const inst of instances) { - if (inst && inst.chatService) this.hookIntoChatConnection(inst); - } - }); + this.chat.ChatService.ready((cls, instances) => { + for(const inst of instances) + this.hookIntoChatConnection(inst); + }) - this.chat.ChatController.on('mount', this.hookIntoChatConnection, this); + this.chat.ChatService.on('mount', this.hookIntoChatConnection, this); } buildAutoHostMenu(vue, hosts, autoHostSettings, data) { diff --git a/src/socket.js b/src/socket.js index f34e8656..6b0fcebe 100644 --- a/src/socket.js +++ b/src/socket.js @@ -75,10 +75,11 @@ export default class SocketClient extends Module { // We don't have our own IRC connection yet, so the site's chat has to do. const _chat = this.resolve('site.chat'); - const chat = _chat && _chat.currentChat; - const con = chat.chatService && chat.chatService.client && chat.chatService.client.connection; + const chat = _chat && _chat.ChatService.first; + const con = chat.client && chat.client.connection; - if (con && con.send) con.send(`PRIVMSG #frankerfacezauthorizer :AUTH ${challenge}`); + if (con && con.send) + con.send(`PRIVMSG #frankerfacezauthorizer :AUTH ${challenge}`); });