diff --git a/package.json b/package.json index e3582ada..5b2c1e77 100755 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "frankerfacez", "author": "Dan Salvato LLC", - "version": "4.17.12", + "version": "4.17.13", "description": "FrankerFaceZ is a Twitch enhancement suite.", "license": "Apache-2.0", "scripts": { diff --git a/src/modules/chat/index.js b/src/modules/chat/index.js index 88f8b8ea..69e63ea7 100644 --- a/src/modules/chat/index.js +++ b/src/modules/chat/index.js @@ -13,6 +13,7 @@ import {timeout, has, glob_to_regex, escape_regex, split_chars} from 'utilities/ import Badges from './badges'; import Emotes from './emotes'; import Emoji from './emoji'; +import Overrides from './overrides'; import Room from './room'; import User from './user'; @@ -40,6 +41,7 @@ export default class Chat extends Module { this.inject(Emotes); this.inject(Emoji); this.inject(Actions); + this.inject(Overrides); this._link_info = {}; diff --git a/src/modules/chat/overrides.js b/src/modules/chat/overrides.js new file mode 100644 index 00000000..3e4a0e6e --- /dev/null +++ b/src/modules/chat/overrides.js @@ -0,0 +1,133 @@ +'use strict'; + +// ============================================================================ +// Name and Color Overrides +// ============================================================================ + +import Module from 'utilities/module'; + + +export default class Overrides extends Module { + constructor(...args) { + super(...args); + + this.inject('settings'); + this.color_cache = null; + this.name_cache = null; + } + + onEnable() { + this.settings.provider.on('changed', this.onProviderChange, this); + } + + onProviderChange(key) { + if ( key === 'overrides.colors' ) + this.loadColors(); + else if ( key === 'overrides.names' ) + this.loadNames(); + } + + get colors() { + if ( ! this.color_cache ) + this.loadColors(); + + return this.color_cache; + } + + get names() { + if ( ! this.name_cache ) + this.loadNames(); + + return this.name_cache; + } + + loadColors() { + let old_keys, + loaded = true; + if ( ! this.color_cache ) { + loaded = false; + this.color_cache = {}; + old_keys = new Set; + } else + old_keys = new Set(Object.keys(this.color_cache)); + + for(const [key, val] of Object.entries(this.settings.provider.get('overrides.colors', {}))) { + old_keys.delete(key); + if ( this.color_cache[key] !== val ) { + this.color_cache[key] = val; + if ( loaded ) + this.emit(':changed', key, 'color', val); + } + } + + for(const key of old_keys) { + this.color_cache[key] = undefined; + if ( loaded ) + this.emit(':changed', key, 'color', undefined); + } + } + + loadNames() { + let old_keys, + loaded = true; + if ( ! this.name_cache ) { + loaded = false; + this.name_cache = {}; + old_keys = new Set; + } else + old_keys = new Set(Object.keys(this.name_cache)); + + for(const [key, val] of Object.entries(this.settings.provider.get('overrides.names', {}))) { + old_keys.delete(key); + if ( this.name_cache[key] !== val ) { + this.name_cache[key] = val; + if ( loaded ) + this.emit(':changed', key, 'name', val); + } + } + + for(const key of old_keys) { + this.name_cache[key] = undefined; + if ( loaded ) + this.emit(':changed', key, 'name', undefined); + } + } + + getColor(id) { + if ( this.colors[id] != null ) + return this.colors[id]; + + return null; + } + + getName(id) { + if ( this.names[id] != null ) + return this.names[id]; + + return null; + } + + setColor(id, color) { + if ( this.colors[id] !== color ) { + this.colors[id] = color; + this.settings.provider.set('overrides.colors', this.colors); + this.emit(':changed', id, 'color', color); + } + } + + setName(id, name) { + if ( this.names[id] !== name ) { + this.names[id] = name; + this.settings.provider.set('overrides.names', this.names); + this.emit(':changed', id, 'name', name); + } + } + + deleteColor(id) { + this.setColor(id, undefined); + } + + deleteName(id) { + this.setName(id, undefined); + } +} \ No newline at end of file diff --git a/src/modules/main_menu/components/faq-page.vue b/src/modules/main_menu/components/faq-page.vue deleted file mode 100644 index b4ccce06..00000000 --- a/src/modules/main_menu/components/faq-page.vue +++ /dev/null @@ -1,20 +0,0 @@ - - - \ No newline at end of file diff --git a/src/modules/main_menu/components/feedback-page.vue b/src/modules/main_menu/components/feedback-page.vue deleted file mode 100644 index b15ee672..00000000 --- a/src/modules/main_menu/components/feedback-page.vue +++ /dev/null @@ -1,20 +0,0 @@ - - - \ No newline at end of file diff --git a/src/modules/main_menu/components/legal-page.vue b/src/modules/main_menu/components/legal-page.vue deleted file mode 100644 index 9f741b76..00000000 --- a/src/modules/main_menu/components/legal-page.vue +++ /dev/null @@ -1,20 +0,0 @@ - - - \ No newline at end of file diff --git a/src/modules/main_menu/components/md-page.vue b/src/modules/main_menu/components/md-page.vue new file mode 100644 index 00000000..1d8a0d17 --- /dev/null +++ b/src/modules/main_menu/components/md-page.vue @@ -0,0 +1,36 @@ + + + \ No newline at end of file diff --git a/src/modules/main_menu/index.js b/src/modules/main_menu/index.js index f386310e..d3bf7bbf 100644 --- a/src/modules/main_menu/index.js +++ b/src/modules/main_menu/index.js @@ -64,12 +64,21 @@ export default class MainMenu extends Module { this.settings.addUI('faq', { path: 'Home > FAQ @{"profile_warning": false}', - component: 'faq-page' + component: 'md-page', + key: 'faq' }); + /*this.settings.addUI('privacy', { + path: 'Home > Privacy @{"profile_warning": false}', + component: 'md-page', + key: 'privacy', + force_seen: true + });*/ + this.settings.addUI('feedback', { path: 'Home > Feedback @{"profile_warning": false}', - component: 'feedback-page' + component: 'md-page', + key: 'feedback' }); this.settings.addUI('feedback.log', { @@ -96,7 +105,8 @@ export default class MainMenu extends Module { this.settings.addUI('legal', { path: 'Home > Legal @{"sort": 1000}', - component: 'legal-page', + component: 'md-page', + key: 'legal', force_seen: true }); diff --git a/src/sites/twitch-twilight/modules/chat/index.js b/src/sites/twitch-twilight/modules/chat/index.js index 9777f083..9fdb905c 100644 --- a/src/sites/twitch-twilight/modules/chat/index.js +++ b/src/sites/twitch-twilight/modules/chat/index.js @@ -246,7 +246,7 @@ export default class ChatHook extends Module { this.GiftBanner = this.fine.define( 'gift-banner', - n => n.getBannerText && n.handleCountdownEnd && n.getRemainingTime, + n => n.getBannerText && n.onGiftMoreClick, Twilight.CHAT_ROUTES ); diff --git a/src/sites/twitch-twilight/modules/chat/line.js b/src/sites/twitch-twilight/modules/chat/line.js index d8ce5fe5..80b70406 100644 --- a/src/sites/twitch-twilight/modules/chat/line.js +++ b/src/sites/twitch-twilight/modules/chat/line.js @@ -33,6 +33,7 @@ export default class ChatLine extends Module { this.inject(RichContent); this.inject('chat.actions'); + this.inject('chat.overrides'); this.ChatLine = this.fine.define( 'chat-line', @@ -46,18 +47,6 @@ export default class ChatLine extends Module { Twilight.CHAT_ROUTES ); - /*this.ChatRoomLine = this.fine.define( - 'chat-room-line', - n => n.renderMessageBody && n.props && ! n.onExtensionNameClick && has(n.props, 'hasModPermissions'), - Twilight.CHAT_ROUTES - );*/ - - /*this.ChatRoomContainer = this.fine.define( - 'chat-room-container', - n => n.renderPlaceholders && n.sendRoomMessage && n.props && n.props.channel, - Twilight.CHAT_ROUTES - );*/ - this.WhisperLine = this.fine.define( 'whisper-line', n => n.props && n.props.message && n.props.reportOutgoingWhisperRendered @@ -65,6 +54,8 @@ export default class ChatLine extends Module { } async onEnable() { + this.on('chat.overrides:changed', id => this.updateLinesByUser(id), this); + this.chat.context.on('changed:chat.emoji.style', this.updateLines, this); this.chat.context.on('changed:chat.bits.stack', this.updateLines, this); this.chat.context.on('changed:chat.badges.style', this.updateLines, this); @@ -99,121 +90,6 @@ export default class ChatLine extends Module { FFZRichContent = this.rich_content && this.rich_content.RichContent; - /*this.ChatRoomLine.ready(cls => { - const old_render = cls.prototype.render; - - cls.prototype.render = function() { try { - this._ffz_no_scan = true; - - const msg = t.chat.standardizeMessage(this.props.message), - is_action = msg.is_action, - - user = msg.user, - color = t.parent.colors.process(user.color), - show_deleted = t.chat.context.get('chat.filtering.show-deleted'); - - let show, show_class; - - if ( show_deleted ) { - show = true; - show_class = msg.deleted; - } else { - show = this.state && this.state.shouldShowDeletedBody || ! msg.deleted; - show_class = false; - } - - const u = t.site.getUser(), - r = {id: null, login: null}; - - if ( u ) { - u.moderator = this.props.hasModPermissions; - } - - // Find the parent element. - const parent = this._ffz_parent = this._ffz_parent || t.fine.searchParent(this, - n => (n.props && n.props.banStatusData && n.props.channelID) || - (n.renderPlaceholders && n.sendRoomMessage && n.props && n.props.channel), 50); - - if ( parent != null ) { - r.id = parent.props.channelID; - r.login = parent.props.channelLogin; - } - - const tokens = msg.ffz_tokens = msg.ffz_tokens || t.chat.tokenizeMessage(msg, u, r), - rich_content = FFZRichContent && t.chat.pluckRichContent(tokens, msg), - bg_css = msg.mentioned && msg.mention_color ? t.parent.inverse_colors.process(msg.mention_color) : null; - - if ( ! this.ffz_user_click_handler ) - this.ffz_user_click_handler = this.props.onUsernameClick; - - const cls = `chat-line__message${show_class ? ' ffz--deleted-message' : ''}`, - out = (tokens.length || ! msg.ffz_type) ? [ - this.props.showTimestamps && e('span', { - className: 'chat-line__timestamp' - }, t.chat.formatTime(msg.timestamp)), - this.renderModerationIcons(), - //t.actions.renderInline(msg, this.props.showModerationIcons, u, r, e), - e('span', { - className: 'chat-line__message--badges' - }, t.chat.badges.render(msg, e)), - e('span', { - className: 'chat-line__username notranslate', - role: 'button', - style: { color }, - onClick: this.ffz_user_click_handler - }, [ - e('span', { - className: 'chat-author__display-name' - }, user.displayName), - user.isIntl && e('span', { - className: 'chat-author__intl-login' - }, ` (${user.login})`) - ]), - e('span', null, is_action ? ' ' : ': '), - show ? - e('span', { - className: 'message', - style: is_action ? { color } : null - }, t.chat.renderTokens(tokens, e)) - : - e('span', { - className: 'chat-line__message--deleted' - }, e('a', { - href: '', - onClick: this.showDeleted - }, t.i18n.t('chat.message-deleted', ''))), - - show && rich_content && e(FFZRichContent, rich_content) - ] : null; - - if ( ! out ) - return null; - - return e('div', { - className: `${cls}${msg.mentioned ? ' ffz-mentioned' : ''}${bg_css ? ' ffz-custom-color' : ''}`, - style: {backgroundColor: bg_css}, - 'data-room-id': r.id, - 'data-room': r.login, - 'data-user-id': user.id, - 'data-user': user.login && user.login.toLowerCase() - }, out); - - } catch(err) { - t.log.capture(err, { - extra: { - props: this.props - } - }); - - return old_render.call(this); - } }; - - // Do this after a short delay to hopefully reduce the chance of React - // freaking out on us. - setTimeout(() => this.ChatRoomLine.forceUpdate()); - });*/ - - this.WhisperLine.ready(cls => { const old_render = cls.prototype.render; @@ -227,19 +103,22 @@ export default class ChatLine extends Module { is_action = msg.is_action, user = msg.user, - color = t.parent.colors.process(user.color), + raw_color = t.overrides.getColor(user.id) || user.color, + color = t.parent.colors.process(raw_color), tokens = msg.ffz_tokens = msg.ffz_tokens || t.chat.tokenizeMessage(msg, null, null), - contents = t.chat.renderTokens(tokens, e); + contents = t.chat.renderTokens(tokens, e), + + override_name = t.overrides.getName(user.id); return e('div', {className: 'thread-message__message'}, e('div', {className: 'tw-pd-x-1 tw-pd-y-05'}, [ e('span', { - className: 'thread-message__message--user-name notranslate', + className: `thread-message__message--user-name notranslate${override_name ? ' ffz--name-override' : ''}`, style: { color } - }, user.displayName), + }, override_name || user.displayName), e('span', null, is_action ? ' ' : ': '), e('span', { className: 'message', @@ -290,7 +169,9 @@ export default class ChatLine extends Module { is_action = msg.messageType === types.Action, user = msg.user, - color = t.parent.colors.process(user.color); + raw_color = t.overrides.getColor(user.id) || user.color, + + color = t.parent.colors.process(raw_color); let mod_mode = this.props.deletedMessageDisplay; let show, show_class, mod_action = null; @@ -422,6 +303,19 @@ other {# messages were deleted by a moderator.} this.ffz_user_click_handler = this.openViewerCard || this.usernameClickHandler; //event => event.ctrlKey ? this.usernameClickHandler(event) : t.viewer_cards.openCard(r, user, event); } + + const user_block = [ + e('span', { + className: 'chat-author__display-name' + }, user.displayName), + user.isIntl && e('span', { + className: 'chat-author__intl-login' + }, ` (${user.login})`) + ]; + + const override_name = t.overrides.getName(user.id); + + let cls = `chat-line__message${show_class ? ' ffz--deleted-message' : ''}`, out = (tokens.length || ! msg.ffz_type) ? [ this.props.showTimestamps && e('span', { @@ -432,19 +326,19 @@ other {# messages were deleted by a moderator.} className: 'chat-line__message--badges' }, t.chat.badges.render(msg, e)), e('span', { - className: 'chat-line__username notranslate', + className: `chat-line__username notranslate${override_name ? ' ffz--name-override tw-relative tw-tooltip-wrapper' : ''}`, role: 'button', style: { color }, onClick: this.ffz_user_click_handler, onContextMenu: t.actions.handleUserContext - }, [ + }, override_name ? [ e('span', { - className: 'chat-author__display-name' - }, user.displayName), - user.isIntl && e('span', { - className: 'chat-author__intl-login' - }, ` (${user.login})`) - ]), + className: 'chat-author__display_name' + }, override_name), + e('div', { + className: 'tw-tooltip tw-tooltip--down tw-tooltip--align-center' + }, user_block) + ] : user_block), e('span', null, is_action ? ' ' : ': '), show ? e('span', { @@ -859,6 +753,23 @@ other {# messages were deleted by a moderator.} } + updateLinesByUser(id, login) { + for(const inst of this.ChatLine.instances) { + const msg = inst.props.message, + user = msg?.user; + if ( user && (id && id == user.id) || (login && login == user.login) ) + inst.forceUpdate(); + } + + for(const inst of this.WhisperLine.instances) { + const msg = inst.props.message?._ffz_message, + user = msg?.user; + if ( user && (id && id == user.id) || (login && login == user.login) ) + inst.forceUpdate(); + } + } + + maybeUpdateLines() { if ( this.chat.context.get('chat.rich.all-links') ) this.updateLines(); @@ -881,14 +792,6 @@ other {# messages were deleted by a moderator.} } } - /*for(const inst of this.ChatRoomLine.instances) { - const msg = inst.props.message; - if ( msg ) { - msg.ffz_tokens = null; - msg.mentioned = msg.mention_color = null; - } - }*/ - for(const inst of this.WhisperLine.instances) { const msg = inst.props.message; if ( msg && msg._ffz_message ) @@ -897,7 +800,6 @@ other {# messages were deleted by a moderator.} this.ChatLine.forceUpdate(); this.ExtensionLine.forceUpdate(); - //this.ChatRoomLine.forceUpdate(); this.WhisperLine.forceUpdate(); this.emit('chat:updated-lines'); diff --git a/src/sites/twitch-twilight/modules/css_tweaks/styles/chat-rows.scss b/src/sites/twitch-twilight/modules/css_tweaks/styles/chat-rows.scss index a4f39507..0c4f3d0f 100644 --- a/src/sites/twitch-twilight/modules/css_tweaks/styles/chat-rows.scss +++ b/src/sites/twitch-twilight/modules/css_tweaks/styles/chat-rows.scss @@ -33,6 +33,7 @@ } } +.video-chat__message-list-wrapper li:nth-child(2n+0) .ffz--points-highlight, .ffz--points-highlight:nth-child(2n+0) { background-color: var(--ffz-channel-color-30); } \ No newline at end of file diff --git a/src/sites/twitch-twilight/modules/featured_follow.js b/src/sites/twitch-twilight/modules/featured_follow.js index 911ec8d9..85acbfa7 100644 --- a/src/sites/twitch-twilight/modules/featured_follow.js +++ b/src/sites/twitch-twilight/modules/featured_follow.js @@ -109,7 +109,7 @@ export default class FeaturedFollow extends Module { follows = {}; for (const user of ap_data.data.users) { - if ( ! user.id ) + if ( ! user || ! user.id ) continue; follows[user.id] = { diff --git a/src/sites/twitch-twilight/modules/menu_button.jsx b/src/sites/twitch-twilight/modules/menu_button.jsx index f2be8470..b2d4c8fa 100644 --- a/src/sites/twitch-twilight/modules/menu_button.jsx +++ b/src/sites/twitch-twilight/modules/menu_button.jsx @@ -18,7 +18,7 @@ export default class MenuButton extends SiteModule { this.inject('i18n'); this.inject('settings'); this.inject('site.fine'); - this.inject('addons'); + //this.inject('addons'); this.should_enable = true; this._pill_content = null; @@ -158,13 +158,15 @@ export default class MenuButton extends SiteModule { if ( this.has_strings ) return this.i18n.formatNumber(this.i18n.new_strings + this.i18n.changed_strings); - if ( DEBUG && this.addons.has_dev ) + const addons = this.resolve('addons'); + + if ( DEBUG && addons.has_dev ) return this.i18n.t('site.menu_button.dev', 'dev'); - if ( DEBUG && ! this.addons.has_dev ) + if ( DEBUG && ! addons.has_dev ) return this.i18n.t('site.menu_button.main-dev', 'm-dev'); - if ( ! DEBUG && this.addons.has_dev ) + if ( ! DEBUG && addons.has_dev ) return this.i18n.t('site.menu_button.addon-dev', 'a-dev'); return null; @@ -263,7 +265,8 @@ export default class MenuButton extends SiteModule { if ( el ) el.remove(); - const pill = this.formatPill(), + const addons = this.resolve('addons'), + pill = this.formatPill(), extra_pill = this.formatExtraPill(); el = (
{this.i18n.t('site.menu_button.main-dev-desc', 'You are running a developer build of FrankerFaceZ.')}
)} - {this.addons.has_dev && (
+ {addons.has_dev && (
{this.i18n.t('site.menu_button.addon-dev-desc', 'You have loaded add-on data from a local development server.')}
)}
)} diff --git a/src/sites/twitch-twilight/modules/video_chat/index.jsx b/src/sites/twitch-twilight/modules/video_chat/index.jsx index 94c2090c..ce82e920 100644 --- a/src/sites/twitch-twilight/modules/video_chat/index.jsx +++ b/src/sites/twitch-twilight/modules/video_chat/index.jsx @@ -283,13 +283,13 @@ export default class VideoChatHook extends Module { return (
{this.props.hideTimestamp || (
-