diff --git a/package.json b/package.json index 49317366..6348a6cb 100755 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "frankerfacez", "author": "Dan Salvato LLC", - "version": "4.68.2", + "version": "4.69.0", "description": "FrankerFaceZ is a Twitch enhancement suite.", "private": true, "license": "Apache-2.0", diff --git a/src/modules/chat/index.js b/src/modules/chat/index.js index a22177c1..2e9f63dc 100644 --- a/src/modules/chat/index.js +++ b/src/modules/chat/index.js @@ -1895,6 +1895,7 @@ export default class Chat extends Module { offset = is_action ? 4 : 0, out = msg._ffz_message = { + id: msg.id, user: {...msg.from}, // Apollo seals this~ message: msg.content.slice(offset), is_action, diff --git a/src/sites/clips/line.jsx b/src/sites/clips/line.jsx index 92f34440..2fcd9c25 100644 --- a/src/sites/clips/line.jsx +++ b/src/sites/clips/line.jsx @@ -38,6 +38,7 @@ export default class Line extends Module { onEnable() { this.on('chat.overrides:changed', id => this.updateLinesByUser(id, null, false, false), this); this.on('chat:update-lines-by-user', this.updateLinesByUser, this); + this.on('chat:update-line', this.updateLineById, this); this.on('chat:update-lines', this.updateLines, this); this.on('chat:rerender-lines', this.rerenderLines, this); this.on('chat:update-line-tokens', this.updateLineTokens, this); @@ -174,6 +175,21 @@ export default class Line extends Module { } } + updateLineById(id, clear_tokens = true, clear_badges = null) { + if ( clear_badges == null ) + clear_badges = clear_tokens; + + for(const inst of this.ChatLine.instances) { + const msg = inst.props.node; + if ( msg?.id === id ) { + if ( clear_tokens || clear_badges ) + this.messages.delete(msg); + + inst.forceUpdate(); + return; + } + } + } maybeUpdateLines() { if ( this.chat.context.get('chat.rich.all-links') ) diff --git a/src/sites/twitch-twilight/modules/chat/input.jsx b/src/sites/twitch-twilight/modules/chat/input.jsx index 5bf9a93c..acd7c37b 100644 --- a/src/sites/twitch-twilight/modules/chat/input.jsx +++ b/src/sites/twitch-twilight/modules/chat/input.jsx @@ -12,6 +12,11 @@ import { TWITCH_POINTS_SETS, TWITCH_GLOBAL_SETS, TWITCH_PRIME_SETS, KNOWN_CODES, import Twilight from 'site'; +const COMMAND_KEYS = [ + '!', + '/' +]; + // Prefer using these statically-allocated collators to String.localeCompare const locale = Intl.Collator(); const localeCaseInsensitive = Intl.Collator(undefined, {sensitivity: 'accent'}); @@ -771,9 +776,22 @@ export default class Input extends Module { inst._ffz_override = true; inst.oldCommands = inst.getCommands; + inst.oldMatches = inst.getMatches; + inst.oldReplacement = inst.determineReplacement; const t = this; + inst.getMatches = function(input, unknown, index) { + try { + return index === 0 && COMMAND_KEYS.includes(input[0]) + ? inst.getCommands(input) : null; + + } catch(err) { + t.log.error('Error getting matches from command handler.', err); + return inst.oldCommands(input, unknown, index); + } + } + inst.getCommands = function(input) { try { const commands = inst.props.getCommands(inst.props.permissionLevel, { isEditor: inst.props.isCurrentUserEditor @@ -792,9 +810,13 @@ export default class Input extends Module { return null; // Trim off the starting / - const i = input.slice(1); + const prefix = input[0], + i = input.slice(1); + + const sorted = commands + .filter(cmd => prefix === (cmd.prefix ?? '/') && inst.doesCommandMatchTerm(cmd, i)) + .sort(inst.sortCommands); - const sorted = commands.filter(cmd => inst.doesCommandMatchTerm(cmd, i)).sort(inst.sortCommands); const out = []; for(const cmd of sorted) { const arg = cmd.commandArgs?.[0]; @@ -813,12 +835,44 @@ export default class Input extends Module { }); } + // If we're working with a non-/ prefix, and have no items, + // return null so we don't display ANY UI. + if ( prefix !== '/' && ! out.length ) + return null; return out; } catch(err) { console.error(err); return inst.oldCommands(input); - }} + } } + + inst.determineReplacement = function(cmd) { + const out = inst.oldReplacement(cmd); + if ( (cmd.prefix ?? '/') !== '/' && out.startsWith('/') ) + return cmd.prefix + out.slice(1); + return out; + } + + const React = this.site.getReact(), + createElement = React?.createElement; + + if ( createElement ) + inst.renderCommandSuggestion = function(cmd, input) { + const args = Array.isArray(cmd?.commandArgs) + ? cmd.commandArgs.map(arg => (
[{arg.name}]
)) + : null; + + return (
+
+
+ { cmd.prefix ?? '/' } + { cmd.name } +
+ {args} +
+

{ cmd.description }

+
); + } } diff --git a/src/sites/twitch-twilight/modules/chat/line.js b/src/sites/twitch-twilight/modules/chat/line.js index bd5003c1..63df9dd2 100644 --- a/src/sites/twitch-twilight/modules/chat/line.js +++ b/src/sites/twitch-twilight/modules/chat/line.js @@ -486,6 +486,7 @@ export default class ChatLine extends Module { async onEnable() { this.on('chat.overrides:changed', id => this.updateLinesByUser(id, null, false, false), this); this.on('chat:update-lines-by-user', this.updateLinesByUser, this); + this.on('chat:update-line', this.updateLineById, this); this.on('chat:update-lines', this.updateLines, this); this.on('chat:rerender-lines', this.rerenderLines, this); this.on('chat:update-line-tokens', this.updateLineTokens, this); @@ -1440,6 +1441,39 @@ other {# messages were deleted by a moderator.} } } + updateLineById(id, clear_tokens = true, clear_badges = null) { + if ( clear_badges == null ) + clear_badges = clear_tokens; + + for(const inst of this.ChatLine.instances) { + const msg = inst.props.message; + if ( msg?.id === id ) { + if ( clear_badges ) + msg.ffz_badges = msg.ffz_badge_cache = null; + + if ( clear_tokens ) { + msg.ffz_tokens = null; + msg.ffz_reply = null; + msg.highlights = msg.mentioned = msg.mention_color = msg.color_priority = null; + } + + inst.forceUpdate(); + return; + } + } + + for(const inst of this.WhisperLine.instances) { + const msg = inst.props.message?._ffz_message; + if ( msg?.id === id ) { + // TODO: Better support for clear_tokens and clear_badges + if ( clear_badges || clear_tokens ) + msg._ffz_message = null; + + inst.forceUpdate(); + return; + } + } + } updateLinesByUser(id, login, clear_tokens = true, clear_badges = true) { for(const inst of this.ChatLine.instances) { diff --git a/src/sites/twitch-twilight/modules/video_chat/index.jsx b/src/sites/twitch-twilight/modules/video_chat/index.jsx index 5e3e9fc7..8030cbec 100644 --- a/src/sites/twitch-twilight/modules/video_chat/index.jsx +++ b/src/sites/twitch-twilight/modules/video_chat/index.jsx @@ -99,6 +99,7 @@ export default class VideoChatHook extends Module { this.chat.context.on('changed:chat.video-chat.timestamps', this.rerenderLines, this); this.on('chat.overrides:changed', id => this.updateLinesByUser(id, null, false, false), this); this.on('chat:update-lines-by-user', this.updateLinesByUser, this); + this.on('chat:update-line', this.updateLineById, this); this.on('chat:update-lines', this.updateLines, this); this.on('chat:rerender-lines', this.rerenderLines, this); this.on('chat:update-line-tokens', this.updateLineTokens, this); @@ -488,6 +489,27 @@ export default class VideoChatHook extends Module { } + updateLineById(id, clear_tokens = true, clear_badges = null) { + if ( clear_badges == null ) + clear_badges = clear_tokens; + + for(const inst of this.VideoChatLine.instances) { + const context = inst.props.messageContext; + if ( ! context.comment ) + continue; + + if ( context.comment?.id === id ) { + // TODO: Better support for clear_tokens and clear_badges + if ( clear_tokens || clear_badges ) + context.comment._ffz_message = null; + + inst.forceUpdate(); + return; + } + } + } + + checkEffects() { for(const inst of this.VideoChatLine.instances) { const context = inst.props.messageContext, @@ -516,6 +538,7 @@ export default class VideoChatHook extends Module { msg_id = params && params['msg-id']; const out = comment._ffz_message = { + id: comment.id, user: { color: comment.message.userColor, id: author.id, @@ -668,4 +691,4 @@ export default class VideoChatHook extends Module { room.updateBitsConfig(formatBitsConfig(config)); } -} \ No newline at end of file +}