Youtube live, Simple Chat Stylizer

Display chat window on stream screen and apply custom stylesheet

От 14.11.2020. Виж последната версия.

// ==UserScript==
// @name         Youtube live, Simple Chat Stylizer
// @name:ja      Youtube live, シンプルチャットスタイル
// @description  Display chat window on stream screen and apply custom stylesheet
// @description:ja チャットウィンドウを配信画面上に配置し、カスタムスタイルを適応する
// @version      1.2.0
// @author       You
// @match        https://www.youtube.com/*
// @grant        none
// @nowrap
// @namespace http://tampermonkey.net/
// ==/UserScript==

(function() {
    'use strict';
    var configAreaID = "simple-chat-stylizer-config";
    var configKey = "simple-chat-stylizer";
    var SINGLE_WINDOW_PARAMS = "toolber=no,menubar=no,scrollbar=no,titlebar=no,location=no,directories=no,status=no,resizable=yes";

    var c = {};
    var isSingleBorder = false;

    setTimeout(function () {
        if (location.host == "www.youtube.com") {
            if (location.pathname.indexOf("/live_chat") == 0) {
                loadConfig();
                addLiveChatStyle();

                setInterval(observeFocus, 1000);
            } else if (location.pathname.indexOf("/embed") == 0) {
                // blank
            } else {
                loadConfig();
                if (!document.querySelector("#" + configAreaID)) {
                    addConfigArea();
                    updateConfigFromForm();
                }
                addFloatWindowStyle();

                if (document.querySelector("[is-watch-page]") && location.href.indexOf("singleborderwindow_") >= 0) {
                    isSingleBorder = true;
                    setTimeout(refreshSingleBorderStyle, 2000);
                    window.addEventListener("resize", refreshSingleBorderStyle);
                }
            }
        }

    }, 3000);

    return;

    /** *****************
     * Config
     * ******************/
    function addConfigArea() {
        console.log("addConfigArea()");

        var $container = document.querySelector("#" + configAreaID) || document.createElement("div");
        $container.innerHTML = `
        <form id="simple-chat-stylizer-config">
            <div style="text-align: center;">Simple chat stylizer config</div>
            <input type="text" name="backgroundColor" value="#fff8">
            <label>background color</label><br />
            <input type="text" name="chatFontSize" value="13">
            <label>font size</label><br />
            <input type="checkbox" name="isEnableCharFrame">
            <label>enable chat frame</label><br />
            <input type="checkbox" name="isHideAuthorName" checked>
            <label>hide author name</label><br />
            <input type="checkbox" name="isAuthorNameRightSide" checked>
            <label>author name is display to right side</label><br />
            <input type="text" name="authorNameMaxWidth" value="100">
            <label>author name max width (px)</label><br />
            <input type="checkbox" name="isHideThumbnail">
            <label>hide user thumbnail</label><br />
            <input type="checkbox" name="isHideBadge">
            <label>hide badge icon</label><br />
            <input type="checkbox" name="isHideHeader" checked>
            <label>hide header</label><br />
            <input type="checkbox" name="isHideFooter">
            <label>hide footer panel (you can't chat)</label><br />
            <input type="checkbox" name="isHideCommonEmotes" checked>
            <label>hide common emotes</label><br />
            <input type="checkbox" name="isFixModerator" checked>
            <label>highlight moderator</label><br />
            <input type="text" name="moderatorChatTimeout" value="20">
            <label>time period that moderator chat is display (sec)</label><br />
            <input type="text" name="windowWidth" value="430">
            <label>window width (px/%)</label><br />
            <input type="checkbox" name="isWindowWidthRatio">
            <label>use ratio for window width (%)</label><br />
            <input type="text" name="windowHeight" value="720">
            <label>window height (px/%)</label><br />
            <input type="checkbox" name="isWindowHeightRatio">
            <label>use ratio for window height (%)</label><br />
            <input type="checkbox" name="isFullHeightInFullscreen">
            <label>full height in fullscreen</label><br />
            <input type="text" name="windowTop" value="10">
            <label>window position from top/bottom (px/%)</label><br />
            <input type="checkbox" name="isWindowTopRatio">
            <label>use ratio for window top position (%)</label><br />
            <input type="checkbox" name="windowIsFromBottom">
            <label>window position is form bottom</label><br />
            <input type="text" name="windowRight" value="10">
            <label>window position from right/left (px/%)</label><br />
            <input type="checkbox" name="isWindowRightRatio">
            <label>use ratio in window right position (%)</label><br />
            <input type="checkbox" name="windowIsFromLeft">
            <label>window position is from left</label><br />
            <input type="text" name="windowOpacity" value="0.9">
            <label>window opacity</label><br />
            <button style="width: unset;" id="simple-chat-stylizer-config-save">Save</button>
            <button style="width: unset;" id="simple-chat-stylizer-single-border-button">Single Bordered Window</button>
        </form>
        `;

        document.querySelector("ytd-watch-flexy").insertBefore($container, document.querySelector("#columns"));
        document.querySelector("#simple-chat-stylizer-config-save").onclick = saveButtonOnClick;
        document.querySelector("#simple-chat-stylizer-single-border-button").onclick = singleBorderWindowButtonOnClick;

        // apply config data
        var $params = document.querySelectorAll("#" + configAreaID + " input");
        for (var i = 0; i < $params.length; i++) {
            var $p = $params[i];
            var value = c[$p.name];

            if (typeof value != "undefined") {
                if ($p.type == "checkbox") {
                    $p.checked = !!value;
                } else {
                    $p.value = value;
                }
            }
        }
    }

    function loadConfig() {
        c = JSON.parse(localStorage[configKey] || "{}");

        console.log("loaded config", c);
    }

    function updateConfigFromForm() {
        var $params = document.querySelectorAll("#" + configAreaID + " input");
        for (var i = 0; i < $params.length; i++) {
            var $p = $params[i];
            var value = c[$p.name];

            if ($p.type == "checkbox") {
                c[$p.name] = $p.checked;
            } else {
                c[$p.name] = $p.value;
            }
        }

        localStorage[configKey] = JSON.stringify(c);

        console.log("updated config", c);
    }

    function saveButtonOnClick() {
        updateConfigFromForm();
        addFloatWindowStyle();
        document.querySelector("#chatframe").contentWindow.location.reload();

        return false;
    }

    function singleBorderWindowButtonOnClick() {
        popupSingleBorderWindow();

        return false;
    }

    /** *****************
     * Chat
     * ******************/
    function observeFocus() {
        if (document.querySelector("#chat-focus-button[on_]")) {
            document.querySelector("#input").focus();
        }
    }

    function addLiveChatStyle() {
        console.log("addLiveChatStyle() : ", location.href);

        // toggle buttons
        var $container = document.querySelector("#chat-buttons-container") || document.createElement("div");
        $container.id = "chat-buttons-container";
        $container.innerHTML = `
<div id="chat-focus-button" class="chat-toggle-button">Focus</div>
<div id="chat-left-button" class="chat-toggle-button">Left</div>
<div id="chat-header-button" class="chat-toggle-button">Header</div>
<div id="chat-chat-button" class="chat-toggle-button">Chat</div>
<div id="chat-footer-button" class="chat-toggle-button">Footer</div>
<div id="chat-popup-button" class="chat-toggle-button" on_>Popup</div>
<div id="chat-reload-button" class="chat-toggle-button" on_>Reload</div>
<div id="chat-toggle-button" class="chat-toggle-button" on_>Close</div>
`;
        document.querySelector("yt-live-chat-app > #contents").appendChild($container);

        document.querySelector("#chat-focus-button").onclick = focusButtonOnClick;
        document.querySelector("#chat-left-button").onclick = leftButtonOnClick;
        document.querySelector("#chat-header-button").onclick = headerButtonOnClick;
        document.querySelector("#chat-chat-button").onclick = chatButtonOnClick;
        document.querySelector("#chat-footer-button").onclick = footerButtonOnClick;
        document.querySelector("#chat-popup-button").onclick = popupButtonOnClick;
        document.querySelector("#chat-reload-button").onclick = reloadButtonOnClick;
        document.querySelector("#chat-toggle-button").onclick = toggleButtonOnClick;

        // fix moderator chat
        var $highlight = document.createElement("div");
        $highlight.id = "moderator-chat-container";
        document.querySelector("yt-live-chat-item-list-renderer #contents").appendChild($highlight);

        var stylesheet = "";

        // bold, font-size
        stylesheet += "#message.yt-live-chat-text-message-renderer { font-weight: bold; font-size: " + c.chatFontSize +"px; }";
        stylesheet += "yt-live-chat-header-renderer, yt-live-chat-renderer, yt-live-chat-message-input-renderer, yt-live-chat-ticker-renderer { background: " + c.backgroundColor + " !important; }";
        stylesheet += "#item-scroller { scrollbar-width: none; }";

        if (c.isEnableCharFrame) {
            stylesheet += `#message.yt-live-chat-text-message-renderer {
               color: #ffffff; letter-spacing : 4px;
               text-shadow : 2px 2px 1px #003366, -2px 2px 1px #003366, 2px -2px 1px #003366, -2px -2px 1px #003366,
                             2px 0px 1px #003366, 0px  2px 1px #003366, -2px  0px 1px #003366, 0px -2px 1px #003366;
            }`;
        }

        if (c.isHideAuthorName) {
            stylesheet += "#content #author-name.yt-live-chat-author-chip { display: none; }";
        } else if (c.isAuthorNameRightSide) {
            stylesheet += "#content #author-name.yt-live-chat-author-chip { position: absolute; right: 10px; top: 0px; opacity: 0.7; transform: scale(0.8); }";
        } else {
            stylesheet += "#content #author-name.yt-live-chat-author-chip { max-width: " + c.authorNameMaxWidth + "px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }";
        }

        if (c.isHideThumbnail) {
            stylesheet += "#author-photo { display: none !important; }";
        }

        if (c.isHideBadge) {
            stylesheet += "#chat-badges { display: none !important; }";
        }

        stylesheet += "#panel-pages[hide_] { display: none !important; }";
        if (c.isHideFooter) {
            document.querySelector("#panel-pages").setAttribute("hide_", "");
        } else {
            document.querySelector("#chat-footer-button").setAttribute("on_", "");
        }

        stylesheet += "yt-live-chat-header-renderer[hide_] { display: none !important; }";
        if (c.isHideHeader) {
            document.querySelector("yt-live-chat-header-renderer").setAttribute("hide_", "");
        } else {
            document.querySelector("#chat-header-button").setAttribute("on_", "");
        }

        stylesheet += "#contents[hidechat_] #ticker, #contents[hidechat_] #separator, #contents[hidechat_] #chat { display: none !important; }";
        if (c.isHideChat) {
            document.querySelector("#contents").setAttribute("hidechat_", "");
        } else {
            document.querySelector("#chat-chat-button").setAttribute("on_", "");
        }

        if (c.isHideCommonEmotes) {
            stylesheet += "yt-emoji-picker-renderer #category-buttons { display: none !important; }";
            var emoteCategories = ["UCkszU2WH9gy1mb0dV-11UJg/CIW60IPp_dYCFcuqTgodEu4IlQ", "😀", "🐵", "🍇", "🌍", "🎃", "👓", "🏧"];
            for (var i = 0; i < emoteCategories.length; i++) {
                stylesheet += "[aria-activedescendant='" + emoteCategories[i] + "'] { display: none; }";
            }
        }

        // your name style
        stylesheet += "#input-container yt-live-chat-author-chip { display: none; }";

        // super chat background style
        stylesheet += "yt-live-chat-product-picker-renderer { background-color: #fff; }";

        // chat input field style
        stylesheet += `#input.yt-live-chat-message-input-renderer {
                           background-color: #fffa; font-weight: bold; margin-top: -4px; }
                       #buttons.yt-live-chat-message-input-renderer { position: absolute; right: 20px; top: 0; }
                       #message-buttons.yt-live-chat-message-input-renderer { display: none; }
                       #input-panel.yt-live-chat-renderer::after { display: none; }
                       #avatar.yt-live-chat-message-input-renderer { margin-top: -4px; }`;

        // moderator/owner chats is fixed to bottom in chat window style
        stylesheet += `#moderator-chat-container {
                       position: absolute; bottom: 0; background: #fff; left: 0; right: 0; z-index: 10000; }`;

        // toggle buttons style
        stylesheet += `yt-live-chat-app #chat-buttons-container {
                position: absolute; right: 0; bottom: 0;
                opacity: 0;
                transition: opacity .2s linear;
            }
            yt-live-chat-app:hover #chat-buttons-container {
                opacity: 1;
            }
            yt-live-chat-app .chat-toggle-button {
                display: inline-block;
                color: #fff; background: #8f8f8f;
                padding: 1px 5px;
                cursor: pointer;
                margin-left: 3px;
                z-index: 20000;
            }
            yt-live-chat-app .chat-toggle-button[on_] {
                background: #1E90FF;
            }`;

        // apply style
        var $style = document.createElement("style");
        $style.innerText = stylesheet;
        document.body.appendChild($style);

        setInterval(observeModerator, 2000);
    }

    function focusButtonOnClick() {
        document.querySelector("#chat-focus-button").toggleAttribute("on_");
    }

    function leftButtonOnClick() {
        if (window.parent.document.querySelector("#chat").toggleAttribute("leftside_")) {
            this.setAttribute("on_", "");
        } else {
            this.removeAttribute("on_");
        }
    }

    function headerButtonOnClick() {
        if (document.querySelector("yt-live-chat-header-renderer").toggleAttribute("hide_")) {
            this.removeAttribute("on_");
        } else {
            this.setAttribute("on_", "");
        }

        return false;
    }

    function chatButtonOnClick() {
        if (document.querySelector("yt-live-chat-app > #contents").toggleAttribute("hidechat_")) {
            this.removeAttribute("on_");
        } else {
            this.setAttribute("on_", "");
        }

        return false;
    }

    function footerButtonOnClick() {
        if (document.querySelector("#panel-pages").toggleAttribute("hide_")) {
            this.removeAttribute("on_");
        } else {
            this.setAttribute("on_", "");
        }

        return false;
    }

    function popupButtonOnClick() {
        popupSingleBorderWindow(window.parent.location.href);

        return false;
    }

    function reloadButtonOnClick() {
        location.reload();

        return false;
    }

    function toggleButtonOnClick() {
        window.top.document.querySelector("#show-hide-button #button").click();

        return false;
    }

    function appendModeratorChat($chat) {
        // $chat = $chat.cloneNode();
        document.querySelector("#moderator-chat-container").appendChild($chat);
        setTimeout(function () {
            $chat.remove();
        }, c.moderatorChatTimeout * 1000);
    }

    function observeModerator() {
        var $chats;
        if (c.isFixModerator) {
            $chats = document.querySelectorAll("#items yt-live-chat-text-message-renderer[author-type='moderator'], #items yt-live-chat-text-message-renderer[author-type='owner']");
        } else {
            $chats = document.querySelectorAll("#items yt-live-chat-text-message-renderer[author-type='owner']");
        }
        for (var i = 0; i < $chats.length; i++) {
            appendModeratorChat($chats[i]);
        }
    }

    /** *****************
     * Float Window
     * ******************/
    function addFloatWindowStyle() {
        console.log("addFloatWindowStyle()", location.href);

        // #chat default selector
        // ytd-watch-flexy[flexy_][js-panel-height_] #chat.ytd-watch-flexy:not([collapsed]).ytd-watch-flexy

        // video controls height is 36px

        var rightUnit = c.isWindowRightRatio ? "%" : "px";
        var topUnit = c.isWindowTopRatio ? "%" : "px";
        var widthUnit = c.isWindowWidthRatio ? "%" : "px";
        var heightUnit = c.isWindowHeightRatio ? "%" : "px";

        var stylesheet = "";

        stylesheet += `ytd-watch-flexy[flexy_][js-panel-height_][theater] #chat.ytd-watch-flexy:not([collapsed]).ytd-watch-flexy {
            position: fixed; border: 0;`
            + "right: " + c.windowRight + rightUnit + "; "
            + "width: " + c.windowWidth + widthUnit + ";"
            + "height: " + c.windowHeight + heightUnit + "; }";
        stylesheet += "ytd-watch-flexy[flexy_][js-panel-height_][theater] #chat.ytd-watch-flexy[leftside_]:not([collapsed]).ytd-watch-flexy {"
            + "left: " + c.windowRight + rightUnit + "; }"

        if (c.windowIsFromLeft) {
            document.querySelector("ytd-watch-flexy").setAttribute("leftside_", "");
            document.querySelector("#chat-left-button").setAttribute("on_", "");
        }
        if (!c.windowIsFromBottom) {
            stylesheet += "ytd-watch-flexy[flexy_][js-panel-height_][theater] #chat.ytd-watch-flexy:not([collapsed]).ytd-watch-flexy { top: calc(60px + " + c.windowTop + topUnit + "); }";
        }
        if (c.isFullHeightInFullscreen) {
            stylesheet += `ytd-watch-flexy[flexy_][js-panel-height_][fullscreen] #chat.ytd-watch-flexy:not([collapsed]).ytd-watch-flexy {
                               top: 10px; bottom: 36px; height: unset; min-height: unset; max-height: unset; }`;
        } else {
            stylesheet += "ytd-watch-flexy[flexy_][js-panel-height_][fullscreen] #chat.ytd-watch-flexy:not([collapsed]).ytd-watch-flexy { "
                + (c.windowIsFromBottom ? "bottom" : "top") + ": " + c.windowTop + topUnit + "; }";
        }
        // stylesheet += "#show-hide-button paper-button[aria-pressed='false'] { display: none !important; }";
        stylesheet += "ytd-watch-flexy[flexy_][js-panel-height_] #chat.ytd-watch-flexy[collapsed] { height: unset; }";

        // show hide button
        stylesheet += "#chat:not([collapsed]) #show-hide-button { display: none; }"
        stylesheet += "#show-hide-button.ytd-live-chat-frame > ytd-toggle-button-renderer.ytd-live-chat-frame { background: " + c.backgroundColor + "; transition: background .2s ease; }";
        stylesheet += "#show-hide-button.ytd-live-chat-frame > ytd-toggle-button-renderer.ytd-live-chat-frame:hover { background: #fffc; }";

        // config
        stylesheet += "#simple-chat-stylizer-config { max-height: 12px; transition: max-height .25s ease-in; overflow: hidden; max-width: 1500px; margin: 0 auto; font-size: 12px; }";
        stylesheet += "#simple-chat-stylizer-config:hover { max-height: 1000px; }";
        stylesheet += "#simple-chat-stylizer-config input { width: 30px; text-align: center; margin-right: 8px; }";

        // single border window
        stylesheet += "ytd-app[singleborderwindow_] { --ytd-app-fullerscreen-scrollbar-width: 17px; --ytd-masthead-height: 0px !important; }";
        stylesheet += `ytd-app[singleborderwindow_] ytd-watch-flexy[flexy_][js-panel-height_] #chat.ytd-watch-flexy:not([collapsed]).ytd-watch-flexy {
                           bottom: 36px; height: unset; min-height: unset; max-height: ` + c.windowHeight + heightUnit + `; }`;

        var $old = document.querySelector("#yt-live-chat-float-on-screen-stylesheet");
        if ($old) $old.remove();

        var $style = document.createElement("style");
        $style.id = "yt-live-chat-float-on-screen-stylesheet";
        $style.innerText = stylesheet;
        document.body.appendChild($style);
    }

    /** *****************
     * Single Bordered Window
     * ******************/
    var refreshSingleBorderSytleTimer = 0;
    function refreshSingleBorderStyle() {
        refreshSingleBorderSytleTimer = clearTimeout(refreshSingleBorderSytleTimer);
        refreshSingleBorderSytleTimer = setTimeout(function () {
            document.body.classList.add("no-scroll");

            var $app = document.querySelector("ytd-app");
            $app.setAttribute("masthead-hidden_", "");
            $app.setAttribute("scrolling_", "");
            $app.setAttribute("singleborderwindow_", "");

            var $player = document.querySelector("#movie_player");
            $player.classList.add("ytd-fullscreen");
            $player.classList.add("ytd-big-mode");

            var $flexy = document.querySelector("ytd-watch-flexy");
            $flexy.setAttribute("fullscreen", "");
        }, 500);
    }

    function popupSingleBorderWindow(url) {
        url = url || location.href;
        window.open(url.split("#")[0] + (url.indexOf("&singleborderwindow_=") >= 0 ? "" : "&singleborderwindow_="), url, SINGLE_WINDOW_PARAMS);
    }
})();
长期地址
遇到问题?请前往 GitHub 提 Issues,或加Q群1031348184

赞助商

Fishcpy

广告

Rainyun

注册一下就行

Rainyun

一年攒够 12 元