diff --git a/package.json b/package.json index 45fa8f95..0f6ae66c 100755 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "frankerfacez", "author": "Dan Salvato LLC", - "version": "4.66.0", + "version": "4.67.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 d2645912..a22177c1 100644 --- a/src/modules/chat/index.js +++ b/src/modules/chat/index.js @@ -2644,58 +2644,59 @@ export default class Chat extends Module { handleLinkToS(data) { + if ( ! Array.isArray(data?.urls) ) + return data; + // Check for YouTube const agreed = this.settings.provider.get('agreed-tos', []), rejected = this.settings.provider.get('declined-tos', []); - const resolvers = data.urls ? new Set(data.urls.map(x => x.resolver).filter(x => x)) : null; - if ( resolvers ) { - for(const [key, info] of Object.entries(RESOLVERS_REQUIRE_TOS)) { - if ( resolvers.has(key) && ! agreed.includes(key) ) { - const declined = rejected.includes(key); + const resolvers = new Set(data.urls.map(x => x.resolver).filter(x => x)); + for(const [key, info] of Object.entries(RESOLVERS_REQUIRE_TOS)) { + if ( resolvers.has(key) && ! agreed.includes(key) ) { + const declined = rejected.includes(key); - return { - ...data, - url: null, - short: [ - { - type: 'box', - content: [ - info.i18n_key - ? {type: 'i18n', key: info.i18n_key, phrase: info.label} - : info.label, - declined ? null : ' ', - declined ? null : { - type: 'conditional', - tooltip: false, + return { + ...data, + url: null, + short: [ + { + type: 'box', + content: [ + info.i18n_key + ? {type: 'i18n', key: info.i18n_key, phrase: info.label} + : info.label, + declined ? null : ' ', + declined ? null : { + type: 'conditional', + tooltip: false, + content: { + type: 'i18n', + key: 'embed.tos-open-settings', + phrase: '{link} to open your settings.', content: { - type: 'i18n', - key: 'embed.tos-open-settings', - phrase: '{link} to open your settings.', - content: { - link: { - type: 'open_settings', - item: 'chat.tooltips', - content: { - type: 'i18n', - key: 'embed.tos-open-settings.click', - phrase: 'Click here' - } + link: { + type: 'open_settings', + item: 'chat.tooltips', + content: { + type: 'i18n', + key: 'embed.tos-open-settings.click', + phrase: 'Click here' } } - }, - alternative: { - type: 'i18n', - key: 'embed.tos-settings', - phrase: 'Open the FFZ Control Center and navigate to Chat > Tooltips to agree.' } + }, + alternative: { + type: 'i18n', + key: 'embed.tos-settings', + phrase: 'Open the FFZ Control Center and navigate to Chat > Tooltips to agree.' } - ] - }, - ], - mid: null, - full: null - } + } + ] + }, + ], + mid: null, + full: null } } } diff --git a/src/sites/twitch-twilight/modules/chat/index.js b/src/sites/twitch-twilight/modules/chat/index.js index c7f69a25..1eead1fa 100644 --- a/src/sites/twitch-twilight/modules/chat/index.js +++ b/src/sites/twitch-twilight/modules/chat/index.js @@ -5,11 +5,11 @@ // ============================================================================ import {Color, ColorAdjuster} from 'utilities/color'; -import {get, has, make_enum, shallow_object_equals, set_equals, deep_equals, glob_to_regex, escape_regex} from 'utilities/object'; +import {get, has, make_enum, shallow_object_equals, set_equals, deep_equals, glob_to_regex, escape_regex, generateUUID} from 'utilities/object'; import {WEBKIT_CSS as WEBKIT} from 'utilities/constants'; import {useFont} from 'utilities/fonts'; - +import awaitMD, { getMD } from 'utilities/markdown'; import Module from 'utilities/module'; import Twilight from 'site'; @@ -2389,10 +2389,40 @@ export default class ChatHook extends Module { for(const inst of this.ChatService.instances) { if ( room === '*' || inst.props.channelLogin.toLowerCase() === room ) { - inst.addMessage({ - type: this.chat_types.Notice, - message - }); + if ( typeof message === 'string' ) + inst.addMessage({ + type: this.chat_types.Notice, + message + }); + else { + const props = inst.props, + login = props.channelLogin, + id = props.channelID; + + if ( message.markdown ) { + const md = getMD(); + if ( ! md ) + awaitMD(); + } + + inst.addMessage({ + type: this.chat_types.Message, + channel: `#${login}`, + roomID: id, + roomLogin: login, + id: `ffz_notice_${generateUUID()}`, + ffz_type: 'notice', + ffz_no_actions: true, + ffz_data: message, + message: null, + messageParts: [], + timestamp: Date.now(), + user: { + userID: id, + userLogin: login + } + }) + } return true; } diff --git a/src/sites/twitch-twilight/modules/chat/line.js b/src/sites/twitch-twilight/modules/chat/line.js index e5acba64..d84c25e3 100644 --- a/src/sites/twitch-twilight/modules/chat/line.js +++ b/src/sites/twitch-twilight/modules/chat/line.js @@ -13,6 +13,7 @@ import { KEYS, RERENDER_SETTINGS, UPDATE_BADGE_SETTINGS, UPDATE_TOKEN_SETTINGS } import { print_duration } from 'utilities/time'; import { getRewardTitle, getRewardCost } from './points'; +import awaitMD, {getMD} from 'utilities/markdown'; const SUB_TIERS = { 1000: 1, @@ -45,6 +46,65 @@ export default class ChatLine extends Module { } }; + this.line_types.notice = { + renderNotice: (msg, current_user, room, inst, e) => { + const data = msg.ffz_data; + let content = this.line_types.notice.renderContent(msg, current_user, room, inst, e); + + if ( ! data.icon ) + return content; + + if ( typeof content === 'string' ) + content = e('span', {}, content); + + content.ffz_icon = e('span', { + className: `${data.icon} tw-mg-r-05` + }); + + return content; + }, + + renderContent: (msg, current_user, room, inst, e) => { + const data = msg.ffz_data; + if ( data.renderer ) + try { + return data.renderer(data, inst, e); + } catch(err) { + this.log.capture(err); + this.log.error('Error using custom renderer for notice:', err); + return `Error rendering notice.` + } + + const text = data.i18n ? this.i18n.t(data.i18n, data.messgae, data) : data.message; + + if ( data.markdown ) { + const md = getMD(); + if ( ! md ) { + awaitMD().then(() => inst.forceUpdate()); + return 'Loading...'; + } + + return e('span', { + dangerouslySetInnerHTML: { + __html: getMD().renderInline(text) + } + }); + } + + if ( data.tokenize ) { + const tokens = data.ffz_tokens = data.ffz_tokens || this.chat.tokenizeMessage({ + message: text, + id: msg.id, + user: msg.user + }, current_user); + + return this.chat.renderTokens(tokens, e); + } + + return text; + } + }; + this.line_types.hype = { renderNotice: (msg, current_user, room, inst, e) => { const setting = this.chat.context.get('chat.hype.message-style'); @@ -1058,7 +1118,7 @@ other {# messages were deleted by a moderator.} // The preamble timestamp, - t.actions.renderInline(msg, this.props.showModerationIcons, current_user, current_room, e, this), + msg.ffz_no_actions ? null : t.actions.renderInline(msg, this.props.showModerationIcons, current_user, current_room, e, this), this.renderInlineHighlight ? this.renderInlineHighlight() : null, hl_position === 2 ? highlight_tags : null, @@ -1121,7 +1181,7 @@ other {# messages were deleted by a moderator.} ? e('span', { className: 'chat-line__timestamp' }, t.chat.formatTime(msg.timestamp)) : null; - const actions = t.actions.renderInline(msg, this.props.showModerationIcons, current_user, current_room, e, this); + const actions = msg.ffz_no_actions ? null : t.actions.renderInline(msg, this.props.showModerationIcons, current_user, current_room, e, this); if ( is_raw ) { notice.ffz_target.unshift( @@ -1187,7 +1247,7 @@ other {# messages were deleted by a moderator.} } // Check for hover actions, as those require we wrap the output in a few extra elements. - const hover_actions = (user && msg.id) + const hover_actions = (user && msg.id && ! msg.ffz_no_actions) ? t.actions.renderHover(msg, this.props.showModerationIcons, current_user, current_room, e, this) : null;