diff --git a/package.json b/package.json index b03afc6e..74686196 100755 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "frankerfacez", "author": "Dan Salvato LLC", - "version": "4.20.56", + "version": "4.20.57", "description": "FrankerFaceZ is a Twitch enhancement suite.", "license": "Apache-2.0", "scripts": { diff --git a/src/modules/chat/actions/components/edit-timeout.vue b/src/modules/chat/actions/components/edit-timeout.vue index c11e12a8..34454809 100644 --- a/src/modules/chat/actions/components/edit-timeout.vue +++ b/src/modules/chat/actions/components/edit-timeout.vue @@ -7,11 +7,11 @@ @@ -32,8 +32,48 @@ \ No newline at end of file diff --git a/src/modules/chat/actions/types.jsx b/src/modules/chat/actions/types.jsx index ebad2d14..565dede5 100644 --- a/src/modules/chat/actions/types.jsx +++ b/src/modules/chat/actions/types.jsx @@ -1,6 +1,7 @@ 'use strict'; import {createElement} from 'utilities/dom'; +import {durationForChat} from 'utilities/time'; // ============================================================================ diff --git a/src/modules/metadata.jsx b/src/modules/metadata.jsx index c4498438..8a559437 100644 --- a/src/modules/metadata.jsx +++ b/src/modules/metadata.jsx @@ -12,7 +12,7 @@ import {duration_to_string, durationForURL} from 'utilities/time'; import Tooltip from 'utilities/tooltip'; import Module from 'utilities/module'; -const CLIP_URL = /^https:\/\/[^/]+\.twitch\.tv\/.+?\.mp4$/; +const CLIP_URL = /^https:\/\/[^/]+\.(?:twitch\.tv|twitchcdn\.net)\/.+?\.mp4(?:\?.*)?$/; export default class Metadata extends Module { constructor(...args) { @@ -265,6 +265,9 @@ export default class Metadata extends Module { if ( ! src || ! CLIP_URL.test(src) ) return; + if ( this.settings.get('metadata.clip-download.force') ) + return src; + const user = this.resolve('site').getUser?.(), is_self = user?.id == data.channel.id; diff --git a/src/sites/twitch-twilight/modules/channel.jsx b/src/sites/twitch-twilight/modules/channel.jsx index aad732ea..e65806e4 100644 --- a/src/sites/twitch-twilight/modules/channel.jsx +++ b/src/sites/twitch-twilight/modules/channel.jsx @@ -502,7 +502,7 @@ export default class Channel extends Module { updateRoot(el) { const root = this.fine.getReactInstance(el); - let channel = null, state = root?.return?.memoizedState, i = 0; + let channel = null, state = root?.return?.return?.return?.memoizedState, i = 0; while(state != null && channel == null && i < 50 ) { state = state?.next; channel = state?.memoizedState?.current?.previousData?.result?.data?.user; diff --git a/src/sites/twitch-twilight/modules/chat/index.js b/src/sites/twitch-twilight/modules/chat/index.js index e28f0638..5754683d 100644 --- a/src/sites/twitch-twilight/modules/chat/index.js +++ b/src/sites/twitch-twilight/modules/chat/index.js @@ -364,11 +364,16 @@ export default class ChatHook extends Module { }); this.settings.add('chat.points.allow-highlight', { - default: true, + default: 2, ui: { path: 'Chat > Channel Points >> Appearance', title: 'Highlight the message in chat when someone redeems Highlight My Message.', - component: 'setting-check-box' + component: 'setting-select-box', + data: [ + {value: 0, title: 'Disabled'}, + {value: 1, title: 'Twitch Style'}, + {value: 2, title: 'FFZ Style'} + ] } }); diff --git a/src/sites/twitch-twilight/modules/chat/input.jsx b/src/sites/twitch-twilight/modules/chat/input.jsx index b60a5027..ce164913 100644 --- a/src/sites/twitch-twilight/modules/chat/input.jsx +++ b/src/sites/twitch-twilight/modules/chat/input.jsx @@ -92,7 +92,6 @@ export default class Input extends Module { Twilight.CHAT_ROUTES ); - this.EmoteSuggestions = this.fine.define( 'tab-emote-suggestions', n => n && n.getMatchedEmotes, @@ -296,8 +295,8 @@ export default class Input extends Module { const t = this; const originalOnKeyDown = inst.onKeyDown, - originalOnMessageSend = inst.onMessageSend, - old_resize = inst.resizeInput; + originalOnMessageSend = inst.onMessageSend; + //old_resize = inst.resizeInput; inst.resizeInput = function(msg) { if ( msg ) { @@ -308,13 +307,6 @@ export default class Input extends Module { i = Math.ceil((inst.chatInputRef.scrollHeight - t) / height), a = Math.min(1 + i, 4); - window._style = style; - window._height = height; - window.t = t; - window.i = i; - window.a = a; - console.log('resize', height, t, i, a) - inst.setState({ numInputRows: a }); diff --git a/src/sites/twitch-twilight/modules/chat/line.js b/src/sites/twitch-twilight/modules/chat/line.js index 4b669d50..fc7c7949 100644 --- a/src/sites/twitch-twilight/modules/chat/line.js +++ b/src/sites/twitch-twilight/modules/chat/line.js @@ -349,6 +349,11 @@ export default class ChatLine extends Module { let mod_mode = this.props.deletedMessageDisplay; let show, show_class, mod_action = null; + const highlight_mode = t.chat.context.get('chat.points.allow-highlight'), + highlight = highlight_mode > 0 && msg.ffz_type === 'points' && msg.ffz_reward && isHighlightedReward(msg.ffz_reward), + twitch_highlight = highlight && highlight_mode == 1, + ffz_highlight = highlight && highlight_mode == 2; + if ( ! this.props.isCurrentUserModerator && mod_mode == 'DETAILED' ) mod_mode = 'LEGACY'; @@ -532,7 +537,7 @@ other {# messages were deleted by a moderator.} : null, show ? e('span', { - className:'message', + className:`message ${twitch_highlight ? 'chat-line__message-body--highlighted' : ''}`, style: is_action ? { color } : null }, t.chat.renderTokens(tokens, e, (reply_mode !== 0 && has_replies) ? this.props.reply : null)) : @@ -813,10 +818,7 @@ other {# messages were deleted by a moderator.} t.i18n.formatNumber(getRewardCost(msg.ffz_reward)) ]); - const can_highlight = t.chat.context.get('chat.points.allow-highlight'), - highlight = can_highlight && isHighlightedReward(msg.ffz_reward); - - cls = `ffz-notice-line ffz--points-line tw-pd-l-1 tw-pd-y-05 tw-pd-r-2${highlight ? ' ffz--points-highlight' : ''}${show_class ? ' ffz--deleted-message' : ''}${twitch_clickable ? ' tw-relative' : ''}`; + cls = `ffz-notice-line ffz--points-line tw-pd-l-1 tw-pd-y-05 tw-pd-r-2${ffz_highlight ? ' ffz-custom-color ffz--points-highlight' : ''}${show_class ? ' ffz--deleted-message' : ''}${twitch_clickable ? ' tw-relative' : ''}`; out = [ e('div', {className: 'tw-c-text-alt-2'}, [ out ? null : t.actions.renderInline(msg, this.props.showModerationIcons, u, r, e), diff --git a/src/sites/twitch-twilight/modules/chat/settings_menu.jsx b/src/sites/twitch-twilight/modules/chat/settings_menu.jsx index 90fd5605..914ee71d 100644 --- a/src/sites/twitch-twilight/modules/chat/settings_menu.jsx +++ b/src/sites/twitch-twilight/modules/chat/settings_menu.jsx @@ -24,6 +24,12 @@ export default class SettingsMenu extends Module { n => n.renderUniversalOptions && n.onBadgesChanged, Twilight.CHAT_ROUTES ); + + /*this.ChatIdentityContainer = this.fine.define( + 'chat-identity-container', + n => n.hideChatIdentityMenu && n.toggleBalloonRef, + Twilight.CHAT_ROUTES + );*/ } async onEnable() { diff --git a/src/sites/twitch-twilight/modules/css_tweaks/index.js b/src/sites/twitch-twilight/modules/css_tweaks/index.js index 82e80ccc..20d1a2d5 100644 --- a/src/sites/twitch-twilight/modules/css_tweaks/index.js +++ b/src/sites/twitch-twilight/modules/css_tweaks/index.js @@ -14,6 +14,7 @@ const CLASSES = { //'unfollow': '.follow-btn__follow-btn--following,.follow-btn--following', 'top-discover': '.navigation-link[data-a-target="discover-link"]', 'side-nav': '.side-nav,#sideNav', + 'side-nav-viewers': '.side-nav-card__live-status', 'side-rec-channels': '.side-nav .recommended-channels,.side-nav .side-nav-section + .side-nav-section:not(.online-friends)', //'side-rec-friends': '.side-nav .recommended-friends', 'side-friends': '.side-nav .online-friends', @@ -144,6 +145,16 @@ export default class CSSTweaks extends Module { } }); + this.settings.add('layout.side-nav.hide-viewers', { + default: false, + ui: { + path: 'Appearance > Layout >> Side Navigation', + title: 'Hide Channel View Counts', + component: 'setting-check-box' + }, + changed: val => this.toggleHide('side-nav-viewers', val) + }); + this.settings.add('layout.side-nav.show-avatars', { default: true, ui: { @@ -409,6 +420,7 @@ export default class CSSTweaks extends Module { } onEnable() { + this.toggleHide('side-nav-viewers', this.settings.get('layout.side-nav.hide-viewers')); this.toggle('hide-native-uptime', this.settings.get('metadata.uptime.no-native')); this.toggle('hide-native-viewers', this.settings.get('metadata.viewers.no-native')); this.toggle('chat-fix', this.settings.get('layout.use-chat-fix')); diff --git a/src/sites/twitch-twilight/modules/css_tweaks/styles/portrait.scss b/src/sites/twitch-twilight/modules/css_tweaks/styles/portrait.scss index bed67c65..cf4042d4 100644 --- a/src/sites/twitch-twilight/modules/css_tweaks/styles/portrait.scss +++ b/src/sites/twitch-twilight/modules/css_tweaks/styles/portrait.scss @@ -66,7 +66,7 @@ .right-column { display: unset !important; position: fixed !important; - z-index: 10000; + z-index: 4000; bottom: 0 !important; left: 0 !important; right: 0 !important; diff --git a/src/sites/twitch-twilight/modules/mod-view.jsx b/src/sites/twitch-twilight/modules/mod-view.jsx new file mode 100644 index 00000000..73db6635 --- /dev/null +++ b/src/sites/twitch-twilight/modules/mod-view.jsx @@ -0,0 +1,235 @@ +'use strict'; + +// ============================================================================ +// Mod View Module +// ============================================================================ + +import Module from 'utilities/module'; +import { Color } from 'utilities/color'; +import {debounce} from 'utilities/object'; +import {createElement, ClickOutside, setChildren} from 'utilities/dom'; + + +export default class ModView extends Module { + constructor(...args) { + super(...args); + + this.inject('i18n'); + this.inject('settings'); + this.inject('site.channel'); + this.inject('site.css_tweaks'); + this.inject('site.fine'); + this.inject('site.elemental'); + this.inject('site.router'); + this.inject('site.twitch_data'); + this.inject('metadata'); + this.inject('socket'); + + this.should_enable = true; + + this._cached_channel = null; + + this.Root = this.elemental.define( + 'mod-view-root', '.moderation-view-page', + ['mod-view'], + {attributes: true}, 1 + ); + + this.ModInfoBar = this.elemental.define( + 'mod-info-bar', '.modview-player-widget__stream-info .simplebar-content', + ['mod-view'], + {childNodes: true, subtree: true}, 1 + ); + + this.checkRoot = debounce(this.checkRoot, 250); + } + + onEnable() { + this.Root.on('mount', this.updateRoot, this); + this.Root.on('mutate', this.updateRoot, this); + this.Root.on('unmount', this.removeRoot, this); + this.Root.each(el => this.updateRoot(el)); + + this.ModInfoBar.on('mount', this.updateBar, this); + this.ModInfoBar.on('mutate', this.updateBar, this); + this.ModInfoBar.on('unmount', this.removeBar, this); + this.ModInfoBar.each(el => this.updateBar(el)); + + this.router.on(':route', this.checkNavigation, this); + this.checkNavigation(); + } + + updateSubscription(login) { + if ( this._subbed_login === login ) + return; + + if ( this._subbed_login ) { + this.socket.unsubscribe(this, `channel.${this._subbed_login}`); + this._subbed_login = null; + } + + if ( login ) { + this.socket.subscribe(this, `channel.${login}`); + this._subbed_login = login; + } + } + + checkNavigation() { + if ( this.router.current_name === 'mod-view' ) { + this.channel.updateChannelColor(); + this.checkRoot(); + } + } + + checkRoot() { + this.Root.each(el => this.updateRoot(el)); + } + + updateRoot(el) { + const root = this.fine.getReactInstance(el); + + let channel = null, state = root?.child?.memoizedState, i = 0; + while(state != null && channel == null && i < 50 ) { + state = state?.next; + channel = state?.memoizedState?.current?.previousData?.result?.data?.user; + i++; + } + + if ( channel?.id && this._cached_channel != channel.id ) { + this._cached_channel = channel.id; + this.updateSubscription(channel.login); + + this.getChannelColor(el, channel.id).then(color => { + this.channel.updateChannelColor(color); + this.settings.updateContext({ + channelColor: color + }); + }).catch(() => { + this.channel.updateChannelColor(); + this.settings.updateContext({ + channelColor: null + }); + }); + + this.settings.updateContext({ + channel: channel.login, + channelID: channel.id + }); + + } else + this.removeRoot(); + + } + + getChannelColor(el, channel_id, no_promise) { + const cache = el._ffz_color_cache = el._ffz_color_cache || {}; + if ( channel_id === cache.channel_id ) { + if ( Date.now() - cache.saved < 60000 ) { + if ( no_promise ) + return cache.data; + return Promise.resolve(cache.data); + } + } + + return new Promise(async (s, f) => { + if ( cache.updating ) { + cache.updating.push([s, f]); + return ; + } + + cache.channel_id = channel_id; + cache.updating = [[s,f]]; + let data, err; + + try { + data = await this.twitch_data.getChannelColor(channel_id); + } catch(error) { + data = null; + err = error; + } + + const waiters = cache.updating; + cache.updating = null; + + if ( cache.channel_id !== channel_id ) { + err = new Error('Outdated'); + cache.channel_id = null; + cache.data = null; + cache.saved = 0; + for(const pair of waiters) + pair[1](err); + + return; + } + + cache.data = data; + cache.saved = Date.now(); + + for(const pair of waiters) + err ? pair[1](err) : pair[0](data); + }); + } + + removeRoot() { + this._cached_channel = null; + this.updateSubscription(); + this.channel.updateChannelColor(); + this.settings.updateContext({ + channel: null, + channelID: null, + channelColor: null + }); + } + + updateBar(el) { + const container = el.closest('.modview-player-widget__stream-info'), + root = container && this.fine.getReactInstance(container); + + let channel = null, state = root?.return?.memoizedState, i = 0; + while(state != null && channel == null && i < 50 ) { + state = state?.next; + channel = state?.memoizedState?.current?.previousData?.result?.data?.channel; + i++; + } + + const bcast = channel?.lastBroadcast, + title = bcast?.title, + game = bcast?.game; + + if ( channel?.id && channel.id != this._cached_channel ) + this.checkRoot(); + + if ( title != el._cached_title || game?.id != el._cached_game ) { + el._cached_title = title; + el._cached_game = game?.id; + + this.settings.updateContext({ + category: game?.name, + categoryID: game?.id, + title + }); + } + } + + removeBar(el) { + this.settings.updateContext({ + category: null, + categoryID: null, + title: null + }); + + if ( el._ffz_cont ) + el._ffz_cont.classList.remove('ffz--meta-tray'); + + el._ffz_cont = null; + if ( el._ffz_meta_timers ) { + for(const val of Object.values(el._ffz_meta_timers)) + clearTimeout(val); + + el._ffz_meta_timers = null; + } + + el._ffz_update = null; + } + +} \ No newline at end of file diff --git a/src/sites/twitch-twilight/styles/menu_button.scss b/src/sites/twitch-twilight/styles/menu_button.scss index db8583bf..96405750 100644 --- a/src/sites/twitch-twilight/styles/menu_button.scss +++ b/src/sites/twitch-twilight/styles/menu_button.scss @@ -1,17 +1,22 @@ .ffz-top-nav { .tw-pill { - padding: .4rem; + border-radius: 1000px; + padding: 0.3rem 0.8em; + font-size: 75%; + background-color: var(--color-background-pill); + color: var(--color-text-overlay); + white-space: nowrap; } .ffz-menu__pill { top: -.5rem; - right: 0; + right: -.5rem; pointer-events: none; } .ffz-menu__extra-pill { - bottom: -.5rem; - right: 0; + bottom: -.75rem; + right: -.5rem; pointer-events: none; } diff --git a/src/std-components/balloon.vue b/src/std-components/balloon.vue index 1a620f1b..c17d2c8f 100644 --- a/src/std-components/balloon.vue +++ b/src/std-components/balloon.vue @@ -1,7 +1,7 @@