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.

// ==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);
    }
})();