diff --git a/package.json b/package.json index 22f2ce56..39e62d68 100755 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "frankerfacez", "author": "Dan Salvato LLC", - "version": "4.36.2", + "version": "4.37.0", "description": "FrankerFaceZ is a Twitch enhancement suite.", "private": true, "license": "Apache-2.0", @@ -12,9 +12,9 @@ "dev": "cross-env NODE_OPTIONS=--openssl-legacy-provider webpack-dev-server --config webpack.web.dev.js", "dev:prod": "cross-env NODE_OPTIONS=--openssl-legacy-provider webpack-dev-server --config webpack.web.dev.prod.js", "build": "pnpm build:prod", - "build:stats": "cross-env NODE_ENV=production webpack --config webpack.web.prod.js --json > stats.json", - "build:prod": "cross-env NODE_ENV=production webpack --config webpack.web.prod.js", - "build:dev": "pnpm clean && webpack --config webpack.web.dev.js", + "build:stats": "cross-env NODE_OPTIONS=--openssl-legacy-provider NODE_ENV=production webpack --config webpack.web.prod.js --json > stats.json", + "build:prod": "cross-env NODE_OPTIONS=--openssl-legacy-provider NODE_ENV=production webpack --config webpack.web.prod.js", + "build:dev": "pnpm clean && cross-env NODE_OPTIONS=--openssl-legacy-provider webpack --config webpack.web.dev.js", "font": "pnpm font:edit", "font:edit": "fontello-cli --cli-config fontello.client.json edit", "font:save": "fontello-cli --cli-config fontello.client.json save && pnpm font:update", diff --git a/src/entry.js b/src/entry.js index ca7b8c21..7b3ae680 100644 --- a/src/entry.js +++ b/src/entry.js @@ -2,7 +2,7 @@ 'use strict'; (() => { // Don't run on certain sub-domains. - if ( /^(?:localhost\.rig|blog|im|chatdepot|tmi|api|brand|dev)\./.test(location.hostname) ) + if ( /^(?:localhost\.rig|blog|im|chatdepot|tmi|api|brand|dev|gql|passport)\./.test(location.hostname) ) return; const DEBUG = localStorage.ffzDebugMode == 'true' && document.body.classList.contains('ffz-dev'), diff --git a/src/modules/chat/actions/components/edit-copy.vue b/src/modules/chat/actions/components/edit-copy.vue new file mode 100644 index 00000000..1dfbedfd --- /dev/null +++ b/src/modules/chat/actions/components/edit-copy.vue @@ -0,0 +1,29 @@ + + + \ No newline at end of file diff --git a/src/modules/chat/actions/types.jsx b/src/modules/chat/actions/types.jsx index 4de79288..da02fa29 100644 --- a/src/modules/chat/actions/types.jsx +++ b/src/modules/chat/actions/types.jsx @@ -83,6 +83,51 @@ export const edit_overrides = { } +// ============================================================================ +// Copy to Clipboard +// ============================================================================ + +export const copy_message = { + presets: [{ + appearance: { + type: 'icon', + icon: 'ffz-i-docs' + } + }], + + defaults: { + format: '{{user.displayName}}: {{message.text}}' + }, + + editor: () => import(/* webpackChunkName: 'main-menu' */ './components/edit-copy.vue'), + + required_context: ['user', 'message'], + + title: 'Copy Message', + description: 'Allows you to quickly copy a chat message to your clipboard.', + + can_self: true, + + tooltip(data) { + const msg = this.replaceVariables(data.options.format, data); + + return [ + (
{ // eslint-disable-line react/jsx-key + this.i18n.t('chat.actions.copy_message', 'Copy Message') + }
), + (
{ // eslint-disable-line react/jsx-key + msg + }
) + ]; + }, + + click(event, data) { + const msg = this.replaceVariables(data.options.format, data); + navigator.clipboard.writeText(msg); + } +} + + // ============================================================================ // Open URL // ============================================================================ diff --git a/src/sites/shared/player.jsx b/src/sites/shared/player.jsx index 20425591..f3ec0a95 100644 --- a/src/sites/shared/player.jsx +++ b/src/sites/shared/player.jsx @@ -559,6 +559,15 @@ export default class PlayerBase extends Module { }, changed: val => this.css_tweaks.toggle('player-hide-mouse', val) }); + + this.settings.add('player.single-click-pause', { + default: false, + ui: { + path: 'Player > General >> Playback', + title: "Pause/Unpause the player by clicking.", + component: 'setting-check-box' + } + }); } async onEnable() { @@ -643,8 +652,6 @@ export default class PlayerBase extends Module { onShortcut(e) { - this.log.info('Compressor Hotkey', e); - for(const inst of this.Player.instances) this.compressPlayer(inst, e); } @@ -825,10 +832,14 @@ export default class PlayerBase extends Module { if ( ! this._ffz_click_handler ) this._ffz_click_handler = this.ffzClickHandler.bind(this); + if ( ! this._ffz_dblclick_handler ) + this._ffz_dblclick_handler = this.ffzDblClickHandler.bind(this); + if ( ! this._ffz_menu_handler ) this._ffz_menu_handler = this.ffzMenuHandler.bind(this); on(cont, 'wheel', this._ffz_scroll_handler); + on(cont, 'dblclick', this._ffz_dblclick_handler); on(cont, 'mousedown', this._ffz_click_handler); on(cont, 'contextmenu', this._ffz_menu_handler); } @@ -853,23 +864,60 @@ export default class PlayerBase extends Module { this._ffz_menu_handler = null; } + if ( this._ffz_dblclick_handler ) { + off(cont, 'dblclick', this._ffz_dblclick_handler); + this._ffz_dblclick_handler = null; + } + this._ffz_listeners = false; } + cls.prototype.ffzDelayPause = function() { + if ( this._ffz_pause_timer ) + clearTimeout(this._ffz_pause_timer); + + const player = this.props?.mediaPlayerInstance; + if (! player.isPaused()) + this._ffz_pause_timer = setTimeout(() => { + const player = this.props?.mediaPlayerInstance; + if (!player.isPaused()) + player.pause(); + }, 500); + } + + cls.prototype.ffzDblClickHandler = function(event) { + if ( ! event ) + return; + + if ( this._ffz_pause_timer ) + clearTimeout(this._ffz_pause_timer); + } + cls.prototype.ffzClickHandler = function(event) { if ( ! event ) return; const vol_scroll = t.settings.get('player.volume-scroll'), gain_scroll = t.settings.get('player.gain.scroll'), + click_pause = t.settings.get('player.single-click-pause'), wants_rmb = wantsRMB(vol_scroll) || wantsRMB(gain_scroll); + // Left Click + if (click_pause && event.button === 0) { + if (! event.target || ! event.target.classList.contains('click-handler')) + return; + + this.ffzDelayPause(); + } + + // Right Click if ( wants_rmb && event.button === 2 ) { this.ffz_rmb = true; this.ffz_scrolled = false; } + // Middle Click if ( ! t.settings.get('player.mute-click') || event.button !== 1 ) return; diff --git a/src/sites/twitch-twilight/modules/channel.jsx b/src/sites/twitch-twilight/modules/channel.jsx index 3751ae8b..af212516 100644 --- a/src/sites/twitch-twilight/modules/channel.jsx +++ b/src/sites/twitch-twilight/modules/channel.jsx @@ -79,7 +79,7 @@ export default class Channel extends Module { changed: () => this.updateLinks() }); - this.settings.add('channel.hosting.enable', { + /*this.settings.add('channel.hosting.enable', { default: true, ui: { path: 'Channel > Behavior >> Hosting', @@ -87,8 +87,7 @@ export default class Channel extends Module { component: 'setting-check-box' }, changed: val => ! val && this.InfoBar.each(el => this.updateBar(el)) - }); - + });*/ this.ChannelPanels = this.fine.define( 'channel-panels', @@ -116,7 +115,7 @@ export default class Channel extends Module { {childNodes: true, subtree: true}, 1 ); - const strip_host = resp => { + /*const strip_host = resp => { if ( this.settings.get('channel.hosting.enable') ) return; @@ -130,7 +129,7 @@ export default class Channel extends Module { }; this.apollo.registerModifier('UseHosting', strip_host, false); - this.apollo.registerModifier('PlayerTrackingContextQuery', strip_host, false); + this.apollo.registerModifier('PlayerTrackingContextQuery', strip_host, false);*/ } onEnable() { @@ -162,7 +161,7 @@ export default class Channel extends Module { this.InfoBar.on('unmount', this.removeBar, this); this.InfoBar.each(el => this.updateBar(el)); - this.subpump.on(':pubsub-message', this.onPubSub, this); + //this.subpump.on(':pubsub-message', this.onPubSub, this); this.router.on(':route', this.checkNavigation, this); this.checkNavigation(); @@ -230,7 +229,7 @@ export default class Channel extends Module { } } - setHost(channel_id, channel_login, target_id, target_login) { + /*setHost(channel_id, channel_login, target_id, target_login) { const topic = `stream-chat-room-v1.${channel_id}`; this.subpump.inject(topic, { @@ -272,7 +271,7 @@ export default class Channel extends Module { event.message.data.num_viewers = 0; event.markChanged(); } - } + }*/ updateSubscription(login) { @@ -441,8 +440,8 @@ export default class Channel extends Module { }); }*/ - if ( ! this.settings.get('channel.hosting.enable') && props.hostLogin ) - this.setHost(props.channelID, props.channelLogin, null, null); + //if ( ! this.settings.get('channel.hosting.enable') && props.hostLogin ) + // this.setHost(props.channelID, props.channelLogin, null, null); this.updateSubscription(props.channelLogin); this.updateMetadata(el); @@ -492,10 +491,10 @@ export default class Channel extends Module { live_since: props.liveSince }, props, - hosted: { + /*hosted: { login: props.hostLogin, display_name: props.hostDisplayName - }, + },*/ el, getViewerCount: () => { const thing = cont.querySelector('p[data-a-target="animated-channel-viewers-count"]'), diff --git a/src/sites/twitch-twilight/modules/chat/emote_menu.jsx b/src/sites/twitch-twilight/modules/chat/emote_menu.jsx index 0da82e64..6e6be4a7 100644 --- a/src/sites/twitch-twilight/modules/chat/emote_menu.jsx +++ b/src/sites/twitch-twilight/modules/chat/emote_menu.jsx @@ -188,6 +188,15 @@ export default class EmoteMenu extends Module { } }); + this.settings.add('chat.emote-menu.clear-search', { + default: false, + ui: { + path: 'Chat > Emote Menu >> General', + title: 'Reset search when closing the Emote Menu.', + component: 'setting-check-box' + } + }); + this.settings.add('chat.emote-menu.enabled', { default: true, ui: { @@ -1078,6 +1087,7 @@ export default class EmoteMenu extends Module { reducedPadding: t.chat.context.get('chat.emote-menu.reduced-padding'), combineTabs: t.chat.context.get('chat.emote-menu.combine-tabs'), showSearch: t.chat.context.get('chat.emote-menu.show-search'), + clearSearch: t.chat.context.get('chat.emote-menu.clear-search'), tone: t.settings.provider.get('emoji-tone', null) } @@ -1226,6 +1236,7 @@ export default class EmoteMenu extends Module { t.chat.context.on('changed:chat.emote-menu.show-heading', this.updateSettingState, this); t.chat.context.on('changed:chat.emote-menu.combine-tabs', this.updateSettingState, this); t.chat.context.on('changed:chat.emote-menu.show-search', this.updateSettingState, this); + t.chat.context.on('changed:chat.emote-menu.clear-search', this.updateSettingState, this); t.chat.context.on('changed:chat.emote-menu.tall', this.updateSettingState, this); window.ffz_menu = this; @@ -1241,6 +1252,7 @@ export default class EmoteMenu extends Module { t.chat.context.off('changed:chat.emote-menu.reduced-padding', this.updateSettingState, this); t.chat.context.off('changed:chat.emote-menu.combine-tabs', this.updateSettingState, this); t.chat.context.off('changed:chat.emote-menu.show-search', this.updateSettingState, this); + t.chat.context.off('changed:chat.emote-menu.clear-search', this.updateSettingState, this); t.chat.context.off('changed:chat.emote-menu.tall', this.updateSettingState, this); if ( window.ffz_menu === this ) @@ -1256,6 +1268,7 @@ export default class EmoteMenu extends Module { reducedPadding: t.chat.context.get('chat.emote-menu.reduced-padding'), combineTabs: t.chat.context.get('chat.emote-menu.combine-tabs'), showSearch: t.chat.context.get('chat.emote-menu.show-search'), + clearSearch: t.chat.context.get('chat.emote-menu.clear-search'), tall: t.chat.context.get('chat.emote-menu.tall') }); } @@ -2309,6 +2322,13 @@ export default class EmoteMenu extends Module { return; } + if ( ! this.props.visible && old_props.visible ) { + if ( this.state.clearSearch ) { + this.setState(this.filterState('', this.state)); + return; + } + } + const cd = this.props.channel_data, old_cd = old_props.channel_data, cd_diff = cd?.user !== old_cd?.user || cd?.channel !== old_cd?.channel, diff --git a/src/sites/twitch-twilight/modules/chat/index.js b/src/sites/twitch-twilight/modules/chat/index.js index 1b43f35c..62d5f28b 100644 --- a/src/sites/twitch-twilight/modules/chat/index.js +++ b/src/sites/twitch-twilight/modules/chat/index.js @@ -201,7 +201,7 @@ export default class ChatHook extends Module { this.ChatController = this.fine.define( 'chat-controller', - n => n.hostingHandler && n.onRoomStateUpdated, + n => n.parseOutgoingMessage && n.onRoomStateUpdated && n.renderNotifications, Twilight.CHAT_ROUTES ); @@ -656,6 +656,15 @@ export default class ChatHook extends Module { component: 'setting-check-box' } }); + + this.settings.add('chat.input.show-elevate-your-message', { + default: true, + ui: { + path: 'Chat > Input >> Appearance', + title: 'Allow the "Elevate Your Message" button to be displayed.', + component: 'setting-check-box' + } + }); } get currentChat() { @@ -941,6 +950,9 @@ export default class ChatHook extends Module { this.updateMentionCSS(); }); + this.chat.context.getChanges('chat.input.show-elevate-your-message', val => + this.css_tweaks.toggleHide('elevate-your-message', ! val)); + this.updateChatCSS(); this.updateColors(); this.updateLineBorders(); @@ -2441,7 +2453,7 @@ export default class ChatHook extends Module { return old_points.call(i, e); } - const old_host = this.onHostingEvent; + /*const old_host = this.onHostingEvent; this.onHostingEvent = function (e, _t) { t.emit('tmi:host', e, _t); return old_host.call(i, e, _t); @@ -2451,7 +2463,7 @@ export default class ChatHook extends Module { this.onUnhostEvent = function (e, _t) { t.emit('tmi:unhost', e, _t); return old_unhost.call(i, e, _t); - } + }*/ const old_add = this.addMessage; this.addMessage = function(e) { diff --git a/src/sites/twitch-twilight/modules/css_tweaks/index.js b/src/sites/twitch-twilight/modules/css_tweaks/index.js index da1cbf50..323eb3bc 100644 --- a/src/sites/twitch-twilight/modules/css_tweaks/index.js +++ b/src/sites/twitch-twilight/modules/css_tweaks/index.js @@ -26,6 +26,7 @@ const CLASSES = { 'modview-hide-info': '.modview-player-widget__hide-stream-info', 'community-highlights': '.community-highlight-stack__card', + 'elevate-your-message': '.chat-input__input-icons button[aria-label="ElevatedMessage"]', 'prime-offers': '.top-nav__prime', 'discover-luna': '.top-nav__external-link[data-a-target="try-presto-link"]', @@ -38,7 +39,7 @@ const CLASSES = { 'player-event-bar': '.channel-root .live-event-banner-ui__header', 'player-rerun-bar': '.channel-root__player-container div.tw-c-text-overlay:not([data-a-target="hosting-ui-header"])', - 'pinned-cheer': '.pinned-cheer,.pinned-cheer-v2,.channel-leaderboard', + 'pinned-cheer': '.pinned-cheer,.pinned-cheer-v2,.channel-leaderboard,.channel-leaderboard-marquee', 'whispers': 'body .whispers-open-threads,.tw-core-button[data-a-target="whisper-box-button"],.whispers__pill', 'dir-live-ind': '.live-channel-card[data-ffz-type="live"] .tw-channel-status-text-indicator, article[data-ffz-type="live"] .tw-channel-status-text-indicator', diff --git a/src/sites/twitch-twilight/modules/dashboard.js b/src/sites/twitch-twilight/modules/dashboard.js index 9e68298d..2c1cdd95 100644 --- a/src/sites/twitch-twilight/modules/dashboard.js +++ b/src/sites/twitch-twilight/modules/dashboard.js @@ -54,7 +54,7 @@ export default class Dashboard extends Module { this.settings.updateContext({ channel: get('props.channelLogin', inst), channelID: get('props.channelID', inst), - hosting: !! inst.props?.hostedChannel?.id + //hosting: !! inst.props?.hostedChannel?.id }); } @@ -62,7 +62,7 @@ export default class Dashboard extends Module { this.settings.updateContext({ channel: null, channelID: null, - hosting: false + //hosting: false }); } diff --git a/src/sites/twitch-twilight/modules/directory/following.jsx b/src/sites/twitch-twilight/modules/directory/following.jsx deleted file mode 100644 index 99ce9efc..00000000 --- a/src/sites/twitch-twilight/modules/directory/following.jsx +++ /dev/null @@ -1,249 +0,0 @@ -'use strict'; - -// ============================================================================ -// Following Page -// ============================================================================ - -import {SiteModule} from 'utilities/module'; -import {createElement} from 'utilities/dom'; -import {get} from 'utilities/object'; - -import {createPopper} from '@popperjs/core'; -import {makeReference} from 'utilities/tooltip'; - -export default class Following extends SiteModule { - constructor(...args) { - super(...args); - - this.inject('site.fine'); - this.inject('site.router'); - this.inject('site.apollo'); - this.inject('site.css_tweaks'); - - this.inject('i18n'); - this.inject('settings'); - - this.settings.add('directory.following.group-hosts', { - default: true, - - ui: { - path: 'Directory > Following @{"description": "**Note:** These settings do not currently work due to changes made by Twitch to how the directory works."} >> Hosts', - title: 'Group Hosts', - description: 'Only show a given hosted channel once in the directory.', - component: 'setting-check-box' - }, - - changed: () => { - this.apollo.maybeRefetch('FollowedIndex_CurrentUser'); - this.apollo.maybeRefetch('FollowingHosts_CurrentUser'); - } - }); - - this.settings.add('directory.following.host-menus', { - default: 1, - - ui: { - path: 'Directory > Following >> Hosts', - title: 'Hosted Channel Menus', - description: 'Display a menu to select which channel to visit when clicking a hosted channel in the directory.', - - component: 'setting-select-box', - - data: [ - {value: 0, title: 'Disabled'}, - {value: 1, title: 'When Multiple are Hosting'}, - {value: 2, title: 'Always'} - ] - }, - - changed: () => this.parent.DirectoryCard.forceUpdate() - }); - - this.hosts = new WeakMap; - } - - modifyLiveUsers(res, path = 'followedLiveUsers') { - const followed_live = get(`data.currentUser.${path}`, res); - if ( ! followed_live ) - return; - - if ( followed_live.nodes ) - followed_live.nodes = this.parent.processNodes(followed_live.nodes); - - else if ( followed_live.edges ) - followed_live.edges = this.parent.processNodes(followed_live.edges); - - return res; - } - - modifyLiveHosts(res) { - const blocked_games = this.settings.provider.get('directory.game.blocked-games', []), - do_grouping = this.settings.get('directory.following.group-hosts'), - edges = get('data.currentUser.followedHosts.nodes', res); - - if ( ! edges || ! edges.length ) - return res; - - this.hosts = new WeakMap(); - const out = []; - - for(const edge of edges) { - if ( ! edge ) - continue; - - const node = edge.node || edge, - hosted = node.hosting, - stream = hosted && hosted.stream; - - if ( ! stream || stream.game && blocked_games.includes(stream.game.name) ) - continue; - - if ( ! stream.viewersCount ) { - if ( ! do_grouping || ! this.hosts[hosted.login] ) - out.push(edge); - continue; - } - - const store = stream.viewersCount = new Number(stream.viewersCount || 0); - - store.createdAt = stream.createdAt; - store.title = stream.title; - //store.game = stream.game; - - if ( do_grouping ) { - const host_nodes = this.hosts[hosted.login]; - if ( host_nodes ) { - host_nodes.push(node); - this.hosts.set(store, host_nodes); - - } else { - this.hosts.set(store, this.hosts[hosted.login] = [node]); - out.push(edge); - } - - } else - out.push(edge); - } - - res.data.currentUser.followedHosts.nodes = out; - return res; - } - - onEnable() { - document.body.addEventListener('click', this.destroyHostMenu.bind(this)); - } - - destroyHostMenu(event) { - if (!event || ! this.hostMenu || event && event.target && event.target.closest('.ffz-channel-selector-outer') === null && Date.now() > this.hostMenuBuffer) { - this.hostMenuPopper && this.hostMenuPopper.destroy(); - this.hostMenu && this.hostMenu.remove(); - this.hostMenuPopper = this.hostMenu = undefined; - } - } - - showHostMenu(inst, channels, event) { - if (this.settings.get('directory.following.host-menus') === 0 || this.settings.get('directory.following.host-menus') === 1 && channels.length < 2) return; - - event.preventDefault(); - event.stopPropagation(); - - this.hostMenuPopper && this.hostMenuPopper.destroy(); - - this.hostMenu && this.hostMenu.remove(); - - const simplebarContentChildren = []; - - // Hosted Channel Header - simplebarContentChildren.push(

- {this.i18n.t('directory.hosted', 'Hosted Channel')} -

); - - // Hosted Channel Content - simplebarContentChildren.push( this.parent.hijackUserClick(e, inst.props.channelLogin, this.destroyHostMenu.bind(this))} // eslint-disable-line react/jsx-no-bind - > -
-
- {inst.props.channelDisplayName} -
-

- {inst.props.channelDisplayName} -

-
-
); - - // Hosting Channels Header - simplebarContentChildren.push(

- {this.i18n.t('directory.hosting', 'Hosting Channels')} -

); - - // Hosting Channels Content - for (const channel of channels) { - simplebarContentChildren.push( this.parent.hijackUserClick(e, channel.login, this.destroyHostMenu.bind(this))} // eslint-disable-line react/jsx-no-bind - > -
-
- {channel.displayName} -
-

- {channel.displayName} -

-
-
); - } - - this.hostMenu = (
-
-
- {simplebarContentChildren} -
-
-
); - - const root = (document.body.querySelector('#root>div') || document.body); - root.appendChild(this.hostMenu); - - this.hostMenuPopper = createPopper( - makeReference(event.clientX - 60, event.clientY - 60), - this.hostMenu, - { - placement: 'bottom-start', - modifiers: { - flip: { - enabled: false - } - } - } - ); - - this.hostMenuBuffer = Date.now() + 50; - } - - updateChannelCard(inst) { - const card = this.fine.getChildNode(inst); - - if ( ! card ) - return; - - const login = inst.props.channelLogin, - hosting = inst.props.channelLinkTo && inst.props.channelLinkTo.state.content === 'live_host' && this.hosts && this.hosts[login]; - - if ( hosting && this.settings.get('directory.following.group-hosts') ) { - const host_data = this.hosts[login]; - - const title_link = card.querySelector('a[data-test-selector="preview-card-titles__primary-link"]'), - thumbnail_link = card.querySelector('a[data-a-target="preview-card-image-link"]'); - - if ( title_link ) - title_link.addEventListener('click', this.showHostMenu.bind(this, inst, host_data)); - - if ( thumbnail_link ) - thumbnail_link.addEventListener('click', this.showHostMenu.bind(this, inst, host_data)); - } - } -} diff --git a/src/sites/twitch-twilight/modules/directory/index.jsx b/src/sites/twitch-twilight/modules/directory/index.jsx index 354916bf..9ed24f67 100644 --- a/src/sites/twitch-twilight/modules/directory/index.jsx +++ b/src/sites/twitch-twilight/modules/directory/index.jsx @@ -48,7 +48,6 @@ export default class Directory extends SiteModule { this.inject('i18n'); this.inject('settings'); - //this.inject(Following); this.inject(Game); this.DirectoryCard = this.elemental.define( diff --git a/src/sites/twitch-twilight/modules/host_button/index.js b/src/sites/twitch-twilight/modules/host_button/index.js deleted file mode 100644 index f854509c..00000000 --- a/src/sites/twitch-twilight/modules/host_button/index.js +++ /dev/null @@ -1,379 +0,0 @@ -'use strict'; - -// ============================================================================ -// Host Button -// ============================================================================ - -import Module from 'utilities/module'; -import {get} from 'utilities/object'; -import {createElement} from 'utilities/dom'; - -const HOST_ERRORS = { - COMMAND_EXECUTION: { - key: 'command-execution', - text: 'There was an error executing the host command. Please try again later.', - }, - CHAT_CONNECTION: { - key: 'chat-connection', - text: 'There was an issue connecting to chat. Please try again later.', - } -}; - -export default class HostButton extends Module { - constructor(...args) { - super(...args); - - this.should_enable = true; - - this.inject('site'); - this.inject('site.fine'); - this.inject('site.chat'); - this.inject('site.twitch_data'); - this.inject('i18n'); - this.inject('metadata'); - this.inject('settings'); - - this.settings.add('metadata.host-button', { - default: true, - - ui: { - path: 'Channel > Metadata >> Player', - title: 'Host Button', - description: 'Show a host button with the current hosted channel in the tooltip.', - component: 'setting-check-box' - }, - - changed: () => { - const ffz_user = this.site.getUser(), - userLogin = ffz_user && ffz_user.login; - - if (userLogin) - this.joinChannel(userLogin); - - this.metadata.updateMetadata('host'); - } - }); - } - - isChannelHosted(channelLogin) { - return this._last_hosted_channel === channelLogin; - } - - sendHostUnhostCommand(channel) { - if (!this._chat_con) { - this._host_error = HOST_ERRORS.CHAT_CONNECTION; - this._host_updating = false; - return; - } - - const ffz_user = this.site.getUser(), - userLogin = ffz_user && ffz_user.login; - - const commandData = {channel: userLogin, username: channel}; - - this._host_updating = true; - this.metadata.updateMetadata('host'); - - this._host_feedback = setTimeout(() => { - if (this._last_hosted_channel === null) { - this._host_error = HOST_ERRORS.COMMAND_EXECUTION; - this._host_updating = false; - this.metadata.updateMetadata('host'); - } - }, 3000); - - if (this.isChannelHosted(channel)) { - this._chat_con.commands.unhost.execute(commandData); - } else { - this._chat_con.commands.host.execute(commandData); - } - } - - joinChannel(channel) { - if (this._chat_con) { - if (this.settings.get('metadata.host-button') && !this._chat_con.session.channelstate[`#${channel}`]) { - this._chat_con.joinChannel(channel); - } - } - } - - hookIntoChatConnection(inst) { - const userLogin = inst.props.currentUserLogin; - - if (this._chat_con) { - this.joinChannel(userLogin); - return; - } - - this.on('tmi:host', e => { - if (e.channel.substring(1) !== userLogin) return; - - clearTimeout(this._host_feedback); - this._host_error = false; - this._last_hosted_channel = e.target; - - this._host_updating = false; - this.metadata.updateMetadata('host'); - }); - - this.on('tmi:unhost', e => { - if (e.channel.substring(1) !== userLogin) return; - - clearTimeout(this._host_feedback); - this._host_error = false; - this._last_hosted_channel = null; - - this._host_updating = false; - this.metadata.updateMetadata('host'); - }); - - const chatServiceClient = inst.client; - - this._chat_con = chatServiceClient; - if (this.settings.get('metadata.host-button')) - this.joinChannel(userLogin); - } - - onEnable() { - this.on('i18n:update', () => this.metadata.updateMetadata('host')); - - this.metadata.definitions.host = { - order: 150, - border: true, - button: true, - fade_in: true, - modview: true, - - disabled: () => this._host_updating || this._host_error, - - click: data => { - if ( data.channel ) - this.sendHostUnhostCommand(data.channel.login); - }, - - popup: async (data, tip) => { - const vue = this.resolve('vue'), - _host_options_vue = import(/* webpackChunkName: "host-options" */ './host-options.vue'), - _autoHosts = this.fetchAutoHosts(), - _autoHostSettings = this.fetchAutoHostSettings(); - - const [, host_options_vue, autoHosts, autoHostSettings] = await Promise.all([vue.enable(), _host_options_vue, _autoHosts, _autoHostSettings]); - - this._auto_host_tip = tip; - tip.element.classList.remove('tw-pd-1'); - tip.element.classList.add('ffz-balloon--lg'); - vue.component('host-options', host_options_vue.default); - return this.buildAutoHostMenu(vue, autoHosts, autoHostSettings, data.channel); - }, - - label: data => { - const ffz_user = this.site.getUser(); - - if ( ! this.settings.get('metadata.host-button') || ! ffz_user || ! data.channel || data.channel.login === ffz_user.login ) - return; - - if ( data.channel.video && ! this.isChannelHosted(data.channel.login) ) - return; - - if ( this._host_updating ) - return this.i18n.t('metadata.host-button.updating', 'Updating...'); - - return (this._last_hosted_channel && this.isChannelHosted(data.channel && data.channel.login)) - ? this.i18n.t('metadata.host-button.unhost', 'Unhost') - : this.i18n.t('metadata.host-button.host', 'Host'); - }, - - tooltip: () => { - if (this._host_error) { - return this.i18n.t( - `metadata.host-button.tooltip.error.${this._host_error.key}`, - this._host_error.text); - } else { - return this.i18n.t('metadata.host-button.tooltip', - 'Currently hosting: {channel}', - { - channel: this._last_hosted_channel || this.i18n.t('metadata.host-button.tooltip.none', 'None') - }); - } - } - }; - - this.metadata.updateMetadata('host'); - - this.chat.ChatService.ready((cls, instances) => { - for(const inst of instances) - this.hookIntoChatConnection(inst); - }) - - this.chat.ChatService.on('mount', this.hookIntoChatConnection, this); - } - - buildAutoHostMenu(vue, hosts, autoHostSettings, data) { - this._current_channel_id = data.id; - this.activeTab = this.activeTab || 'auto-host'; - - const vueEl = new vue.Vue({ - el: createElement('div'), - render: h => this.vueHostMenu = h('host-options', { - hosts, - autoHostSettings, - activeTab: this.activeTab, - - addedToHosts: this.currentRoomInHosts(), - addToAutoHosts: () => this.addCurrentRoomToHosts(), - rearrangeHosts: event => this.rearrangeHosts(event.oldIndex, event.newIndex), - removeFromHosts: event => this.removeUserFromHosts(event), - setActiveTab: tab => { - this.vueHostMenu.data.activeTab = this.activeTab = tab; - }, - updatePopper: () => { - if (this._auto_host_tip) this._auto_host_tip.update(); - }, - updateCheckbox: e => { - const t = e.target; - let setting = t.dataset.setting, - state = t.checked; - - if ( setting === 'enabled' ) - setting = 'isEnabled'; - else if ( setting === 'teamHost' ) - setting = 'willAutohostTeam'; - else if ( setting === 'strategy' ) - state = state ? 'RANDOM' : 'ORDERED'; - else if ( setting === 'deprioritizeVodcast' ) { - setting = 'willPrioritizeAutohost'; - } - - this.updateAutoHostSetting(setting, state); - } - }) - }); - - return vueEl.$el; - } - - async fetchAutoHosts() { - const user = this.site.getUser(); - if ( ! user ) - return; - - const result = await this.twitch_data.queryApollo( - await import(/* webpackChunkName: 'host-options' */ './autohost_list.gql'), - { - id: user.id - }, - { - fetchPolicy: 'network-only' - } - ); - - return this.autoHosts = get('data.user.autohostChannels.nodes', result); - } - - async fetchAutoHostSettings() { - const user = this.site.getUser(); - if ( ! user ) - return; - - const result = await this.twitch_data.queryApollo( - await import(/* webpackChunkName: 'host-options' */ './autohost_settings.gql'), - { - id: user.id - }, - { - fetchPolicy: 'network-only' - } - ); - - return this.autoHostSettings = get('data.user.autohostSettings', result); - } - - queueHostUpdate() { - if (this._host_update_timer) clearTimeout(this._host_update_timer); - - this._host_update_timer = setTimeout(() => { - this._host_update_timer = undefined; - this.updateAutoHosts(this.autoHosts); - }, 1000); - } - - rearrangeHosts(oldIndex, newIndex) { - const host = this.autoHosts.splice(oldIndex, 1)[0]; - this.autoHosts.splice(newIndex, 0, host); - - this.queueHostUpdate(); - } - - currentRoomInHosts() { - return this.getAutoHostIDs(this.autoHosts).includes(this._current_channel_id); - } - - addCurrentRoomToHosts() { - const newHosts = this.autoHosts.slice(0); - newHosts.push({ id: this._current_channel_id}); - - this.updateAutoHosts(newHosts); - } - - removeUserFromHosts(event) { - const id = event.target.closest('.ffz--host-user').dataset.id; - - const newHosts = []; - for (let i = 0; i < this.autoHosts.length; i++) { - if (this.autoHosts[i].id != id) newHosts.push(this.autoHosts[i]); - } - - this.updateAutoHosts(newHosts); - } - - getAutoHostIDs(hosts) { // eslint-disable-line class-methods-use-this - const ids = []; - if (hosts) { - for (let i = 0; i < hosts.length; i++) { - ids.push(hosts[i].id); - } - } - return ids; - } - - async updateAutoHosts(newHosts) { - const user = this.site.getUser(); - if ( ! user ) - return; - - const autoHosts = this.getAutoHostIDs(newHosts); - - const result = await this.twitch_data.mutate({ - mutation: await import(/* webpackChunkName: 'host-options' */ './autohost_list_mutate.gql'), - variables: { - userID: user.id, - channelIDs: autoHosts - } - }); - - this.autoHosts = get('data.setAutohostChannels.user.autohostChannels.nodes', result); - if (this.vueHostMenu) { - this.vueHostMenu.data.hosts = this.autoHosts; - this.vueHostMenu.data.addedToHosts = this.currentRoomInHosts(); - } - } - - async updateAutoHostSetting(setting, newValue) { - const user = this.site.getUser(); - if ( ! user ) - return; - - const result = await this.twitch_data.mutate({ - mutation: await import(/* webpackChunkName: 'host-options' */ './autohost_settings_mutate.gql'), - variables: { - userID: user.id, - [setting]: newValue - } - }); - - this.autoHostSettings = get('data.updateAutohostSettings.user.autohostSettings', result); - if (this.vueHostMenu) { - this.vueHostMenu.data.autoHostSettings = this.autoHostSettings; - } - } -} \ No newline at end of file diff --git a/src/sites/twitch-twilight/modules/layout.js b/src/sites/twitch-twilight/modules/layout.js index 79eade88..6615f71d 100644 --- a/src/sites/twitch-twilight/modules/layout.js +++ b/src/sites/twitch-twilight/modules/layout.js @@ -172,7 +172,7 @@ export default class Layout extends Module { }); this.settings.add('layout.portrait-extra-height', { - requires: ['context.new_channel', 'context.squad_bar', 'context.hosting', 'context.ui.theatreModeEnabled', 'player.theatre.no-whispers', 'whispers.show', 'layout.minimal-navigation'], + requires: ['context.new_channel', 'context.squad_bar', /*'context.hosting',*/ 'context.ui.theatreModeEnabled', 'player.theatre.no-whispers', 'whispers.show', 'layout.minimal-navigation'], process(ctx) { let height = 0; if ( ctx.get('context.ui.theatreModeEnabled') ) { @@ -192,8 +192,8 @@ export default class Layout extends Module { height += ctx.get('context.new_channel') ? 1 : 5; - if ( ctx.get('context.hosting') ) - height += 4; + /*if ( ctx.get('context.hosting') ) + height += 4;*/ } return height; diff --git a/src/sites/twitch-twilight/modules/player.jsx b/src/sites/twitch-twilight/modules/player.jsx index 8e0a323e..1fdf17ec 100644 --- a/src/sites/twitch-twilight/modules/player.jsx +++ b/src/sites/twitch-twilight/modules/player.jsx @@ -196,7 +196,7 @@ export default class Player extends PlayerBase { checkCarousel(inst) { - if ( this.settings.get('channel.hosting.enable') ) + /*if ( this.settings.get('channel.hosting.enable') ) return; if ( inst.props?.playerType === 'channel_home_carousel' ) { @@ -211,7 +211,7 @@ export default class Player extends PlayerBase { events = inst.props.playerEvents; this.stopPlayer(player, events, inst); - } + }*/ } diff --git a/src/sites/twitch-twilight/styles/main.scss b/src/sites/twitch-twilight/styles/main.scss index 0f3a0321..bdefc036 100644 --- a/src/sites/twitch-twilight/styles/main.scss +++ b/src/sites/twitch-twilight/styles/main.scss @@ -9,7 +9,7 @@ @import 'fixes'; -@import 'host_options'; +//@import 'host_options'; @import 'featured_follow'; @import 'mod_card'; @import 'easteregg'; \ No newline at end of file