Useful Twitch Toggles

Attempt at making shortcuts for Twitch. "w" is set to make viewable screen bigger, and "p" is set to toggle chat popped out into seperate window.

您需要先安装一个扩展,例如 篡改猴Greasemonkey暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴Userscripts ,之后才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。

您需要先安装用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==UserScript==
// @name         Useful Twitch Toggles
// @description  Attempt at making shortcuts for Twitch. "w" is set to make viewable screen bigger, and "p" is set to toggle chat popped out into seperate window.
// @version      6
// @namespace    twitch.popoutchat.and.fullscreen
// @author       Snorlaxing
// @match        http*://*.twitch.tv/*
// @match        http*://twitch.tv/*
// @match        http*://*twitch.tv
// @match        http*://*.twitch.tv/*
// @exclude      https://www.twitch.tv/popout/*/chat?popout=
// @exclude      https://www.twitch.tv/popout/*/chat?popout=
// @icon         https://static.twitchcdn.net/assets/favicon-32-e29e246c157142c94346.png
// @grant        none
// ==/UserScript==


function wait(time) {
    return new Promise(resolve => {
        setTimeout(resolve, time);
    });
}

async function fixMarginTopAfterLoad(time, el, amt) {
    await wait(time);
    el.style.marginTop = amt;

}

(function() {
    'use strict';

    let currentPage = location.href;

    // listen for changes
    setInterval(function()
                {
        if (currentPage != location.href)
        {
            // page has changed, set new page as 'current'
            currentPage = location.href;
            console.log('Url has changed, reloading script');
            setup();

            if (poppedOut) {
                //attempt to change popOut to new chat
                var regexp = /(https?:\/\/www\.twitch\.tv\/)(\w+)\w*/g;
                var url = window.location.href;
                var result = [...url.matchAll(regexp)];
                var newUrl = result[0][1] + 'popout/' + result[0][2] + '/chat?popout='
                chatWindow.document.location.href = newUrl;
            }
        }
    }, 500);

    //globals
    var $ = window.jQuery;
    var fullScreenToggle = 'w';
    var popOutToggle = 'p';
    var toggles = [popOutToggle, fullScreenToggle];
    var poppedOut = false;
    var theatreMode = false;
    var mainWindow = window;
    var chatWindow = null;
    var closeChatIfLeavingStreamingChannel = true;

    //elements for theatre mode
    var previousMaxHeight = null;
    var topNav = null;
    var leftSideNav = null;
    var expandChatButton = null;
    var videoDiv = null;
    var mainPlayer = null;
    var persistentPlayer = null;
    var rootInfo = null;
    var onStreamer = false;

    //tracking for keyboard inputs
    var newKeyPressed = false;
    var singleLetter = true;
    var keyTimer = null;

    /*
    * This function is called whenever a key press is recorded.
    */
    function catchKeys(e) {
        //call to determine whether you are actually typing or pressing toggle key.
        registerToggleKeyPress(e);
    }


    /*
    * This function fills in all the global variables and handles closing chat if move to home page.
    * Change closeChatIfLeavingStreamingChannel to false, if you do not want chat to auto close when
    * you move to twitch home page or similar.
    */
    function setup() {

        console.log('Running script setup');
        window.removeEventListener("keydown", catchKeys, true);
        if (window.location.pathname.split('/').length > 1 && document.querySelectorAll('.channel-root--watch').length > 0) {

            onStreamer = true;
            console.log('On twitch streamer page, continuing setup');
            previousMaxHeight = document.querySelector('.persistent-player').style.maxHeight;
            topNav = document.querySelector('[data-a-target="top-nav-container"]');
            leftSideNav = document.querySelector('[data-test-selector="side-nav"]');
            expandChatButton = document.querySelector('.toggle-visibility__right-column');
            videoDiv = document.querySelector('.video-player__container--resize-calc');
            mainPlayer = document.querySelector('div.channel-root__player');
            persistentPlayer = document.querySelector('.persistent-player');
            rootInfo = document.querySelector('.channel-root__info');
            //document.addEventListener("keydown", catchKeys, true);
            window.addEventListener("keydown", catchKeys, false);

        } else {
            console.log('On home or other twitch page, no need to setup');
            onStreamer = false;
            if (poppedOut && chatWindow != null && closeChatIfLeavingStreamingChannel) {
                chatWindow.close();
            }
        }
    }


    /*
    * This function determines whether a single keyboard key was pressed or whether
    * user was typing.
    */
    function registerToggleKeyPress(e) {
        if (e.target != null) {
            //tpying into chat
            if (e.target.getAttribute('data-a-target') == 'chat-input') {
                return;
            }

            //tpying into input field
            try {
                if (isInputOrText(e.target)) {
                    return;
                }
            } catch (ex) {
                console.log(ex);
            }
        }

        //Otherwise, record time between keys until typing stops.
        if (keyTimer != null) {
            clearInterval(keyTimer);
            singleLetter = false;
        }
        newKeyPressed = true;
        keyTimer = setInterval(function() {
            if (!newKeyPressed) {

                clearInterval(keyTimer);
                if (singleLetter && toggles.includes(e.key)) {
                    console.log('Registered toggle key ' + e.key + ' pressed');
                    handleToggles(e);
                }

                keyTimer = null;
                singleLetter = true;
            }
            newKeyPressed = false;
        }, 400);
    }

    /*
    * This function handles the individual toggles.
    */
    function handleToggles(e) {
        if (e.key === fullScreenToggle && onStreamer) {
            if (theatreMode) {
                console.log('exit theatre mode');
                exitTheatreMode();
            } else if (!theatreMode) {
                console.log('enter theatre mode');
                enterTheatreMode();
            }
        } else if (e.key === popOutToggle && onStreamer) {
            if (!poppedOut && chatWindow == null) {
                popOutChat();
            } else {
                showChatOnSide();
            }
        }
    }

    function isInputOrText(element) {
        var tagName = element.tagName.toLowerCase();
        if (tagName === 'textarea') return true;
        if (tagName === 'input') return true;
        if (element.getAttribute('type') != null) {
            var type = element.getAttribute('type').toLowerCase(),
            // if any of these input types is not supported by a browser, it will behave as input type text.
            inputTypes = ['text', 'password', 'number', 'email', 'tel', 'url', 'search', 'date', 'datetime', 'datetime-local', 'time', 'month', 'week']
            return inputTypes.indexOf(type) >= 0;
        }
        return false;
    }

    setup();


    function exitTheatreMode() {

        topNav.removeAttribute("style");
        topNav.style.height = '5rem';
        leftSideNav.removeAttribute("style");
        expandChatButton.removeAttribute("style");
        expandChatButton.classList.add("kLMGYG");
        mainPlayer.classList.add('channel-root__player');
        mainPlayer.removeAttribute("style");
        videoDiv.removeAttribute("style");
        theatreMode = false;

        //Fixplayer
        persistentPlayer.style.top = '0px'
        persistentPlayer.style.left = '0px'
        persistentPlayer.style.maxHeight = previousMaxHeight;
        persistentPlayer.style.position = 'absolute';
        persistentPlayer.style.overflow = 'hidden';
        persistentPlayer.style.zIndex = '1';
        persistentPlayer.style.height = 'auto';
        persistentPlayer.style.transition = 'transform 0.5s ease 0s';
        persistentPlayer.style.transformOrigin = 'center top';
        persistentPlayer.style.transform = 'scale(1)';
    }

    function enterTheatreMode() {

        //If chat window is showing, close
        var right = document.querySelector('div.channel-root__right-column');
        if (right.classList.contains('channel-root__right-column--expanded')) {
            document.querySelector('[aria-label="Collapse Chat"]').click();
        }

        topNav.style.display = "none ";
        leftSideNav.style.display = "none";
        expandChatButton.style.display = 'none';
        expandChatButton.classList.remove("kLMGYG");
        mainPlayer.classList.remove('channel-root__player');
        mainPlayer.style.height = 'calc(100vh)';
        persistentPlayer.style.maxHeight = 'calc(100vh)';
        videoDiv.style.maxHeight = '100%';
        fixMarginTopAfterLoad(500, rootInfo, '0px');
        persistentPlayer.style.display = 'flex';
        persistentPlayer.style.height = '100%';
        persistentPlayer.style.flexDirection = 'column';
        persistentPlayer.style.justifyContent = 'center';
        persistentPlayer.style.backgroundColor = 'black';
        theatreMode = true;
    }

    function waitForElm(selector) {
        return new Promise(resolve => {
            if (document.querySelector(selector)) {
                return resolve(document.querySelector(selector));
            }

            const observer = new MutationObserver(mutations => {
                if (document.querySelector(selector)) {
                    observer.disconnect();
                    resolve(document.querySelector(selector));
                }
            });

            // If you get "parameter 1 is not of type 'Node'" error, see https://stackoverflow.com/a/77855838/492336
            observer.observe(document.body, {
                childList: true,
                subtree: true
            });
        });
    }

    function popOutChat() {

        //Get right expand element
        var right = document.querySelector('div.channel-root__right-column');

        //Get site details for popup
        var regexp = /(https?:\/\/www\.twitch\.tv\/)(\w+)\w*/g;
        var url = window.location.href;
        var result = [...url.matchAll(regexp)];
        var newUrl = result[0][1] + 'popout/' + result[0][2] + '/chat?popout='
        chatWindow = window.open(newUrl,'chat','toolbar=0,status=0,height=500,width=400');
        poppedOut = true;

        //Close side panel if open
        if (right.classList.contains('channel-root__right-column--expanded')) {
            document.querySelector('[aria-label="Collapse Chat"]').click();
        }

        console.log('Chat Popped Out');
        //Track new window
        trackNewWindow();
    }

    function showChatOnSide() {
        //if already poppedOut
        if (poppedOut && chatWindow != null) {
            //close chatWindow
            chatWindow.close();
        }

        //Open right Side
        var right = document.querySelector('div.channel-root__right-column');
        if (!right.classList.contains('channel-root__right-column--expanded')) {
            document.querySelector('[aria-label="Expand Chat"]').click();
        }

        console.log('Chat closed and opened in side panel');

    }

    function trackNewWindow() {
        var timer = setInterval(function() {
            if(chatWindow.closed) {
                clearInterval(timer);
                poppedOut = false;
                chatWindow = null;
            }
        }, 1000);
    }
})();