Youtube live, Simple Chat Stylizer

Display chat window on stream screen and apply custom stylesheet

当前为 2021-02-04 提交的版本,查看 最新版本

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

You will need to install an extension such as Tampermonkey to install this script.

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

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

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

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

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

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

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

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

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

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

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

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

// ==UserScript==
// @name         Youtube live, Simple Chat Stylizer
// @name:ja      Youtube live, シンプルチャットスタイル
// @name:ru      Youtube live, простой стилизатор чата
// @name:tw      YouTube直播, 简单的聊天风格器
// @description  Display chat window on stream screen and apply custom stylesheet
// @description:ja チャットウィンドウを配信画面上に配置し、カスタムスタイルを適応する
// @description:ru Разместите окно чата на экране доставки и настройте свой собственный стиль
// @description:tw 在流屏幕上显示聊天窗口并应用自定义样式表
// @namespace    http://tampermonkey.net/
// @version      1.3.8
// @author       You
// @match        https://www.youtube.com/*
// @grant        none
// @run-at       document-end
// @nowrap
// ==/UserScript==

(function() {
    'use strict';
    const configAreaID = "simple-chat-stylizer-config";
    const configKey = "simple-chat-stylizer";
    const SINGLE_WINDOW_PARAMS = "toolber=no,menubar=no,scrollbar=no,titlebar=no,location=no,directories=no,status=no,resizable=yes";
    const CHAT_CONNECTION_TIMEOUT = 60 * 1000; // 1 min

    // config object
    var c = {
        backgroundColor: "#fff0",
        chatFontSize: "13",
        isEnableChatOutline: true,
        chatOutlineColor: "#036",
        isHideAuthorName: false,
        isAuthorNameRightSide: true,
        authorNameMaxWidth: "100",
        isHideThumbnail: false,
        isHideBadge: false,
        isHideHeader: true,
        isHideFooter: false,
        isHideCommonEmotes: true,
        isMemberOnly: false,
        isReloadWhenChatClogged: false,
        isFeverEmotes: false,
        disableChatFlickers: false,
        superChatViewType: "history-header-message",
        isFixModerator: true,
        moderatorChatTimeout: "20",
        windowWidth: "430",
        windowHeight: "720",
        chatWindowPosition: "right-top",
        windowOpacity: "0.9",
        enableFilter: true,
        chatFilter: "",
    };
    var isEnableFiltering = false;
    function isNoTranslate() {
        return isEnableFiltering || c.isFeverEmotes;
    }

    window.addEventListener("load", onLoaded);
    return;

    function onLoaded() {
        if (location.host == "www.youtube.com") {
            // チャットウィンドウ
            if (location.pathname.indexOf("/live_chat") == 0) {
                if (window == window.parent || window.parent.location.host != "www.youtube.com") return;

                loadConfig();
                addLiveChatStyle();

                setTimeout(updateChatWindowHeight, 100);
                setTimeout(setupChatObservation, 100);

                window.addEventListener("focus", focusInputField);
                window.addEventListener("mouseover", fixPlayerConflict);
            }
            // iframe
            else if (location.pathname.indexOf("/embed") == 0) {
                return;
            } else {
                // チャットウィンドウからも呼び出すのでグローバルにも定義する
                window.updateChatWindowHeight = updateChatWindowHeight;

                loadConfig();
                addConfigArea();
                addFloatWindowStyle();
                updateDynamicLayout();

                window.addEventListener("focus", focusInputField);
                window.addEventListener("resize", updateDynamicLayout);
                window.addEventListener("yt-set-theater-mode-enabled", updateDynamicLayout);
            }
        }
    }

    /** *****************
     * Config
     * ******************/
    function addConfigArea() {
        // console.log("addConfigArea()");
        if (document.querySelector(`#${configAreaID}`)) return;

        const localize = {
            ja: {
                configHeader: "Youbute live, シンプルチャットスタイル 設定",
                backgroundColor: "背景色",
                fontSize: "文字サイズ",
                chatOutline: "文字枠を有効化する",
                chatOutlineColor: "文字枠の色",
                hideAuthorName: "チャット投稿者名を非表示にする",
                authorNameIsRight: "投稿者名を右側に表示する",
                autnorNameMaxWidth: "投稿者名の最大横幅(長すぎる場合は省略します)",
                hideThumbnail: "ユーザーアイコンを非表示にする",
                hideBadge: "メンバーバッヂを非表示にする",
                hideHeader: "ヘッダーを非表示にする",
                hideFooter: "フッターを非表示にする (チャットができなくなります)",
                hideCommonEmotes: "チャンネル専用絵文字以外を非表示にする",
                isFeverEmotes: "絵文字のみのチャットを背景に流す",
                memberOnly: "メンバーチャットのみ表示する",
                reloadWhenChatClogged : "チャットが詰まったら更新する",
                disableChatFlickers: "チャットのちらつきを低減する(勢いが早い、NGフィルタが多い場合に有効)",
                superChatViewType: "スーパーチャットの表示方法",
                superChat: {
                    all: "全て表示",
                    historyMessage: "履歴 + チャットメッセージ",
                    history: "履歴のみ",
                    headerMessage: "チャットのみ",
                    message: "チャットメッセージのみ",
                    none: "全て表示しない",
                },
                highlightModerator: "モデレーターを強調表示する(オーナーは常時)",
                highlightTimeout: "強調表示する時間 (秒)",
                windowWidth: "チャットウィンドウの横幅 (デフォルトはpx / 比率で指定したい場合、%を追加)",
                windowHeight: "チャットウィンドウの高さ (デフォルトはpx / 比率で指定したい場合、%を追加)",
                chatWindowPosition: "チャットウィンドウの位置",
                position: {
                    rightTop: "右上",
                    rightBottom: "右下",
                    leftTop: "左上",
                    leftBottom: "左下",
                },
                windowOpacity: "チャットウィンドウの透明度 (0~1で指定/0は透明)",
                chatFilter: "チャットフィルタ (正規表現が使えます/一行に一項目)",
                save: "保存",
            },
            en: {
                configHeader: "Simple chat stylizer Config",
                backgroundColor: "Background color",
                fontSize: "Font size",
                chatOutline: "Enabled chat outline",
                chatOutlineColor: "Chat outline color",
                hideAuthorName: "Hide author name",
                authorNameIsRight: "Author name is display to right side",
                autnorNameMaxWidth: "Max width of author name(If too wide, omit the part)",
                hideThumbnail: "Hide user icon",
                hideBadge: "Hide member badge icon",
                hideHeader: "Hide header panel",
                hideFooter: "Hide footer panel (you can't chat)",
                hideCommonEmotes: "Hide emotes except for channel",
                isFeverEmotes: "Animate on background if chat is emotes only",
                memberOnly: "Display member chat only",
                reloadWhenChatClogged : "Reload Chat window when connection clagged",
                disableChatFlickers: "Decrement chat flickers (It's effective if chat flow is fast or use many chat filter)",
                superChatViewType: "View type of super chat",
                superChat: {
                    all: "All",
                    historyMessage: "History + Message only",
                    history: "History only",
                    headerMessage: "Chat only",
                    message: "Message only",
                    none: "Not display all",
                },
                highlightModerator: "Highlight moderator chat (Owner chat is always)",
                highlightTimeout: "Time period that highlight (sec)",
                windowWidth: "Width of Chat window (Default unit is px / Append '%' if you want to use ratio)",
                windowHeight: "Height of Chat window (Default unit is px / Append '%' if you want to use ratio)",
                chatWindowPosition: "Position of Chat window",
                position: {
                    rightTop: "Right Top",
                    rightBottom: "Right Bottom",
                    leftTop: "Left Top",
                    leftBottom: "Left Bottom",
                },
                windowOpacity: "Opacity of Chat window (Set value of 0 to 1 / 0 is transport)",
                chatFilter: "Chat filter (Can use regix / Input a filter each one line)",
                save: "Save",
            },
           ru: {
                configHeader: "Simple chat stylizer Config",
                backgroundColor: "Background color",
                fontSize: "Font size",
                chatOutline: "Enabled chat outline",
                chatOutlineColor: "Chat outline color",
                hideAuthorName: "Hide author name",
                authorNameIsRight: "Author name is display to right side",
                autnorNameMaxWidth: "Max width of author name(If too wide, omit the part)",
                hideThumbnail: "Hide user icon",
                hideBadge: "Hide member badge icon",
                hideHeader: "Hide header panel",
                hideFooter: "Hide footer panel (you can't chat)",
                hideCommonEmotes: "Hide emotes except for channel",
                isFeverEmotes: "Animate on background if chat is emotes only",
                memberOnly: "Display member chat only",
                reloadWhenChatClogged : "Reload Chat window when connection clagged",
                disableChatFlickers: "Decrement chat flickers (It's effective if chat flow is fast or use many chat filter)",
                superChatViewType: "View type of super chat",
                superChat: {
                    all: "All",
                    historyMessage: "History + Message only",
                    history: "History only",
                    headerMessage: "Chat only",
                    message: "Message only",
                    none: "Not display all",
                },
                highlightModerator: "Highlight moderator chat (Owner chat is always)",
                highlightTimeout: "Time period that highlight (sec)",
                windowWidth: "Width of Chat window (Default unit is px / Append '%' if you want to use ratio)",
                windowHeight: "Height of Chat window (Default unit is px / Append '%' if you want to use ratio)",
                chatWindowPosition: "Position of Chat window",
                position: {
                    rightTop: "Right Top",
                    rightBottom: "Right Bottom",
                    leftTop: "Left Top",
                    leftBottom: "Left Bottom",
                },
                windowOpacity: "Opacity of Chat window (Set value of 0 to 1 / 0 is transport)",
                chatFilter: "Chat filter (Can use regix / Input a filter each one line)",
                save: "Save",
            },
            tw: {
                configHeader: "简单聊天样式器的设置",
                backgroundColor: "背景颜色",
                fontSize: "字体大小",
                chatOutline: "启用聊天大纲",
                chatOutlineColor: "聊天轮廓颜色",
                hideAuthorName: "隐藏作者姓名",
                authorNameIsRight: "作者姓名显示在右侧",
                autnorNameMaxWidth: "作者姓名的最大宽度 (如果太宽,则省略该部分)",
                hideThumbnail: "隐藏用户图标",
                hideBadge: "隐藏会员徽章图标",
                hideHeader: "隐藏标题面板",
                hideFooter: "隐藏页脚面板 (您无法聊天)",
                hideCommonEmotes: "隐藏频道以外的表情",
                isFeverEmotes: "如果聊天仅是表情,则在背景上进行动画处理",
                memberOnly: "仅显示会员聊天",
                reloadWhenChatClogged : "连接中断时重新加载聊天窗口",
                disableChatFlickers: "减少聊天闪烁 (如果聊天流程快速或使用许多聊天过滤器,则非常有效)",
                superChatViewType: "查看超级聊天的类型",
                superChat: {
                    all: "所有",
                    historyMessage: "历史记录 + 仅消息",
                    history: "仅历史",
                    headerMessage: "仅聊天",
                    message: "仅留言",
                    none: "不显示全部",
                },
                highlightModerator: "突出显示主持人聊天 (始终是所有者聊天)",
                highlightTimeout: "突出显示的时间段 (秒)",
                windowWidth: "聊天窗口的宽度 (默认单位是 px / 如果要使用比例,请附加“%”)",
                windowHeight: "聊天窗口的高度 (默认单位是 px / 如果要使用比例,请附加“%”)",
                chatWindowPosition: "聊天窗口的位置",
                position: {
                    rightTop: "右上",
                    rightBottom: "右下",
                    leftTop: "左上",
                    leftBottom: "左下",
                },
                windowOpacity: "聊天窗口的不透明度 (设定值 0 到 1 /0 是运输)",
                chatFilter: "聊天过滤器 (可以使用正则表达式 / 每行输入一个过滤器)",
                save: "保存",
            }
        }

        const t = localize[window.navigator.language] || localize.en,
              config = document.querySelector("#" + configAreaID) || document.createElement("div");
        config.innerHTML = `
        <form id="simple-chat-stylizer-config">
            <div style="text-align: center;">${t.configHeader}</div>
            <input type="text" name="backgroundColor" id="scs-backgroundColor">
            <label for="scs-backgroundColor">${t.backgroundColor}</label><br />
            <input type="text" name="chatFontSize" id="scs-chatFontSize">
            <label for="scs-chatFontSize">${t.fontSize}</label><br />
            <input type="checkbox" name="isEnableChatOutline" id="scs-isEnableChatOutline">
            <label for="scs-isEnableChatOutline">${t.chatOutline}</label><br />
            <input type="text" name="chatOutlineColor" id="scs-chatOutlineColor">
            <label for="scs-chatOutlineColor">${t.chatOutlineColor}</label><br />
            <input type="checkbox" name="isHideAuthorName" id="scs-isHideAuthorName">
            <label for="scs-isHideAuthorName">${t.hideAuthorName}</label><br />
            <input type="checkbox" name="isAuthorNameRightSide" id="scs-isAuthorNameRightSide">
            <label for="scs-isAuthorNameRightSide">${t.authorNameIsRight}</label><br />
            <input type="text" name="authorNameMaxWidth" id="scs-authorNameMaxWidth">
            <label for="scs-authorNameMaxWidht">${t.autnorNameMaxWidth}</label><br />
            <input type="checkbox" name="isHideThumbnail" id="scs-isHideThumbnail">
            <label for="scs-isHideThumbnail">${t.hideThumbnail}</label><br />
            <input type="checkbox" name="isHideBadge" id="scs-isHideBadge">
            <label for="scs-isHideBadge">${t.hideBadge}</label><br />
            <input type="checkbox" name="isHideHeader" id="scs-isHideHeader">
            <label for="scs-isHideHeader">${t.hideHeader}</label><br />
            <input type="checkbox" name="isHideFooter" id="scs-isHideFooter">
            <label for="scs-isHideFooter">${t.hideFooter}</label><br />
            <input type="checkbox" name="isHideCommonEmotes" id="scs-isHideCommonEmotes">
            <label for="scs-isHideCommonEmotes">${t.hideCommonEmotes}</label><br />
            <input type="checkbox" name="isFeverEmotes" id="scs-isFeverEmotes">
            <label for="scs-isFeverEmotes">${t.isFeverEmotes}</label><br />
            <input type="checkbox" name="isMemberOnly" id="scs-isMemberOnly">
            <label for="scs-isMemberOnly">${t.memberOnly}</label><br />
            <input type="checkbox" name="isReloadWhenChatClogged" id="scs-isReloadWhenChatClogged">
            <label for="scs-isReloadWhenChatClogged">${t.reloadWhenChatClogged}</label><br />
            <input type="checkbox" name="disableChatFlickers" id="scs-disableChatFlickers">
            <label for="scs-disableChatFlickers">${t.disableChatFlickers}</label><br />
            <label for="scs-superChatViewType" for="scs-superChatViewType" class="for-select">${t.superChatViewType}</label>
            <select type="dropdown" name="superChatViewType" id="scs-superChatViewType" value="history-header-message">
                <option value="history-header-message">${t.superChat.all}</option>
                <option value="history-message">${t.superChat.historyMessage}</option>
                <option value="history">${t.superChat.history}</option>
                <option value="header-message">${t.superChat.headerMessage}</option>
                <option value="message">${t.superChat.message}</option>
                <option value="none">${t.superChat.none}</option>
            </select><br />
            <input type="checkbox" name="isFixModerator" id="scs-isFixModerator">
            <label for="scs-isFixModerator">${t.highlightModerator}</label><br />
            <input type="text" name="moderatorChatTimeout" id="scs-moderatorChatTimeout">
            <label for="scs-moderatorChatTimeout">${t.highlightTimeout}</label><br />
            <input type="text" name="windowWidth" id="scs-windowWidth">
            <label for="scs-widowWidth">${t.windowWidth}</label><br />
            <input type="text" name="windowHeight" id="scs-windowHeight">
            <label for="scs-windowHeight">${t.windowHeight}</label><br />
            <label for="scs-chatWindowPosition" for="scs-chatWindowPosition" class="for-select">${t.chatWindowPosition}</label>
            <select type="dropdown" name="chatWindowPosition" id="scs-chatWindowPosition" value="right-top">
                <option value="right-top">${t.position.rightTop}</option>
                <option value="right-bottom">${t.position.rightBottom}</option>
                <option value="left-top">${t.position.leftTop}</option>
                <option value="left-bottom">${t.position.leftBottom}</option>
            </select><br />
            <input type="text" name="windowOpacity" id="scs-windowOpacity">
            <label for="scs-windowOpacity">${t.windowOpacity}</label><br />
            <input type="checkbox" name="enableFilter" id="scs-enableFilter">
            <label for="scs-enableFilter">${t.chatFilter}</label><br />
            <textarea name="chatFilter" value="" id="scs-chatFilter"></textarea>
            <button style="width: unset;" id="simple-chat-stylizer-config-save">${t.save}</button>
        </form>
        `;

        var waitToLoadWatchPageTimer = setInterval(() => {
            if (!document.querySelector("ytd-watch-flexy")) {
                return;
            }

            clearInterval(waitToLoadWatchPageTimer);

            document.querySelector("ytd-watch-flexy").insertBefore(config, document.querySelector("#columns"));

            // フォームの値を設定から復元する
            var $params = document.querySelectorAll(`#${configAreaID} input, #${configAreaID} select, #${configAreaID} textarea`);
            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;
                    }
                }
            }

            config.querySelector("textarea").addEventListener("input", textareaOnInput);
            config.querySelector("textarea").addEventListener("focus", textareaOnInput);
            config.querySelector("#simple-chat-stylizer-config-save").addEventListener("click", saveButtonOnClick);
        }, 500);
    }

    // 設定を読み込む
    function loadConfig() {
        Object.assign(c, JSON.parse(localStorage[configKey] || "{}"));

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

    // フォームの設定を保存する
    function updateConfigFromForm() {
        var $params = document.querySelectorAll(`#${configAreaID} input, #${configAreaID} select, #${configAreaID} textarea`);
        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(ev) {
        ev.preventDefault();
        ev.stopPropagation();
        ev.stopImmediatePropagation();

        updateConfigFromForm();

        addFloatWindowStyle();
        document.querySelector("#chatframe").contentWindow.location.reload();

        return false;
    }

    function textareaOnInput() {
        this.style.height = "1px";
        this.style.height = (this.scrollHeight) + "px";
    }

    /** *****************
     * Chat Filtering
     * ******************/
    // コメント監視を開始
    function setupChatObservation() {
        var lastPostedTime = new Date().getTime();
        const filterRegex = new RegExp(c.chatFilter.split(/[\r\n]+/)
                                  .filter((x) => (x || "").trim("\n").trim("\r") != "")
                                  .map((x) => "(" + x + ")").join("|"), "i"),
              emotesOnlyRegex = /^[\s\n\r]*$/;
        // const transrator = `^[\[\(\{(「【『](${c.language})[\]\)\})」】』].{3,}`;
        // console.log("initChatFilter()", filter);

        const observer = new MutationObserver((mutations) => {
            const items = document.querySelector("#item-scroller.yt-live-chat-item-list-renderer"),
                  chatWindowHeight = items.offsetHeight;
            mutations.forEach((mutation) => {
                if (mutation && !mutation.addedNodes) return;

                mutation.addedNodes.forEach((chat) => {
                    // console.log(chat);
                    if (chat.nodeName != "YT-LIVE-CHAT-TEXT-MESSAGE-RENDERER") return;

                    // 強調表示から返ってきたら非表示にする
                    if (chat.hasAttribute("highlighted_")) {
                        chat.setAttribute("hidden_");
                        return;
                    }

                    // モデレータチャット
                    const author = chat.getAttribute("author-type");
                    if (author == "owner" || (c.isFixModerator && author == "moderator")) {
                        highlightChat(chat);
                        return;
                    }

                    // チャットフィルタ
                    const msg = chat.querySelector("#message");

                    if (c.isFeverEmotes && msg && msg.innerText.match(emotesOnlyRegex) && author != "owner" && author != "moderator") {
                        chat.setAttribute("hidden_", "");
                        var emotes = msg.querySelectorAll(".emoji");
                        emotes.forEach(e => {
                            var startY = Math.random() * chatWindowHeight * 0.25;
                            e.style.setProperty("--fever-height", `-${chatWindowHeight - startY}px`);
                            e.style.setProperty("--fever-width", `${40 + Math.random() * 30}px`);
                            e.setAttribute("fever_", "");
                            e.setAttribute(`fever${Math.floor(Math.random() * 5) + 1}_`, "");
                            e.style.left = (5 + Math.random() * 90) + "%";
                            e.style.bottom = startY + "px";

                            items.appendChild(e);
                        });
                        setTimeout(() => emotes.forEach(e => e.remove()), 4000);
                    } else if (isEnableFiltering && msg && msg.innerText.match(filterRegex)) {
                        chat.setAttribute("hidden_", "");1
                        // console.log("filtered", chat.innerText);
                    } else {
                        // チャット要素は使いまわされてる
                        // @see https://greasyfork.org/ja/scripts/409664-chat-filter-for-youtube-live/code
                        chat.removeAttribute("hidden_");
                    }
                });

                // 一定時間チャットがなければ、更新する
                if (c.isReloadWhenChatClogged && new Date().getTime() - lastPostedTime > CHAT_CONNECTION_TIMEOUT) {
                    location.reload();
                }
                lastPostedTime = new Date().getTime();
            });
        });

        setInterval(() => {
            if (c.isReloadWhenChatClogged && new Date().getTime() - lastPostedTime > CHAT_CONNECTION_TIMEOUT) {
                location.reload();
            }
        }, 30 * 1000);

        observer.observe(document.querySelector("#items.yt-live-chat-item-list-renderer"), { childList: true });
    }

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

        const localize = {
            ja: {
                toggleAutoFocus: "チャット入力欄に自動フォーカス",
                toggleLeft: "左側表示を切り替え",
                toggleBottom: "下側表示を切り替え",
                toggleFiltering: "チャットフィルタを有効",
                toggleHeader: "ヘッダーの表示を切り替え",
                toggleChat: "チャットの表示を切り替え",
                toggleFooter: "フッターを表示を切り替え",
                popup: "ポップアップ",
                reload: "更新",
                close: "閉じる",
            },
            en: {
                toggleAutoFocus: "Toggle auto focus to input field",
                toggleLeft: "Toggle left position",
                toggleBottom: "Toggle bottom position",
                toggleFiltering: "Toggle chat filtering",
                toggleHeader: "Toggle header",
                toggleChat: "Toggle chat",
                toggleFooter: "Toggle footer",
                popup: "Popup Single bordered window",
                reload: "Reload",
                close: "Close",
            },
            ru: {
                toggleAutoFocus: "Toggle auto focus to input field",
                toggleLeft: "Toggle left position",
                toggleBottom: "Toggle bottom position",
                toggleFiltering: "Toggle chat filtering",
                toggleHeader: "Toggle header",
                toggleChat: "Toggle chat",
                toggleFooter: "Toggle footer",
                popup: "Popup Single bordered window",
                reload: "Reload",
                close: "Close",
            },
            tw: {
                toggleAutoFocus: "将自动对焦切换到输入字段",
                toggleLeft: "切换左位置",
                toggleBottom: "切换底部位置",
                toggleFiltering: "切换聊天过滤",
                toggleHeader: "切换标题",
                toggleChat: "切换聊天",
                toggleFooter: "切换页脚",
                popup: "弹出式单边框窗口",
                reload: "刷新",
                close: "关",
            }
        };

        // toggle buttons
        const t = localize[window.navigator.language] || localize.en,
              buttons = document.querySelector("#chat-buttons-container") || document.createElement("div");
        buttons.id = "chat-buttons-container";
        buttons.innerHTML = `
<div id="chat-focus-button" class="chat-toggle-button" title="${t.toggleAutoFocus}">
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" viewBox="0 0 512 512" style="width: 16px; height: 16px; opacity: 1;" xml:space="preserve">
<g>
	<polygon points="48.002,48 128.002,48 128.002,0 0.002,0 0.002,128 48.002,128 	"></polygon>
	<polygon points="383.998,0 383.998,48 463.998,48 463.998,128 511.998,128 511.998,0 	"></polygon>
	<polygon points="463.998,464 383.998,464 383.998,512 511.998,512 511.998,384 463.998,384 	"></polygon>
	<polygon points="48.002,384 0.002,384 0.002,512 128.002,512 128.002,464 48.002,464 	"></polygon>
	<path d="M204.959,176.07c-0.484-1.461-1.461-2.43-3.156-2.43H171.24c-1.695,0-2.664,0.969-3.149,2.43l-56.758,160.086
		c-0.484,1.454,0,2.422,1.695,2.422h31.774c1.695,0,2.672-0.726,3.156-2.422l9.219-28.867h57.726l9.453,28.867
		c0.493,1.696,1.461,2.422,3.157,2.422h31.531c1.703,0,2.187-0.969,1.703-2.422L204.959,176.07z M167.116,276.969l18.679-57.726
		h0.726l18.68,57.726H167.116z"></path>
	<path d="M381.037,242.039H321.85c-0.969,0-1.454-0.485-1.454-1.453v-34.688c0-0.969,0.485-1.453,1.454-1.453h71.07
		c1.454,0,2.422-0.969,2.422-2.422V176.07c0-1.461-0.969-2.43-2.422-2.43H287.17c-1.461,0-2.43,0.969-2.43,2.43v160.086
		c0,1.454,0.969,2.422,2.43,2.422h30.797c1.461,0,2.43-0.969,2.43-2.422v-62.094c0-0.976,0.485-1.461,1.454-1.461h59.187
		c1.453,0,2.422-0.969,2.422-2.422v-25.711C383.459,243.016,382.49,242.039,381.037,242.039z"></path>
</g>
</svg>
</div>
<div id="chat-left-button" class="chat-toggle-button" title="${t.toggleLeft}">
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" viewBox="0 0 512 512" style="width: 16px; height: 16px; opacity: 1;" xml:space="preserve">
<g>
	<path d="M154.52,265.848l90.964,69.014c2.329,1.766,4.674,2.702,6.78,2.702c2.148,0,4.022-0.974,5.276-2.741
		c1.199-1.688,1.807-3.99,1.807-6.844v-26.424c0-6.952,5.656-12.608,12.607-12.608h75.036c8.705,0,15.788-7.085,15.788-15.788
		v-34.313c0-8.703-7.083-15.788-15.788-15.788h-75.036c-6.951,0-12.607-5.656-12.607-12.608v-26.425
		c0-7.065-3.659-9.584-7.082-9.584c-2.106,0-4.451,0.936-6.78,2.702l-90.964,69.014c-3.416,2.59-5.297,6.087-5.297,9.849
		C149.223,259.762,151.103,263.259,154.52,265.848z""></path>
	<path d="M256,0C114.842,0,0.002,114.84,0.002,256S114.842,512,256,512c141.158,0,255.998-114.84,255.998-256
		S397.158,0,256,0z M256,66.785c104.334,0,189.216,84.879,189.216,189.215S360.334,445.215,256,445.215S66.783,360.336,66.783,256
		S151.667,66.785,256,66.785z""></path>
</g>
</svg>
</div>
<div id="chat-bottom-button" class="chat-toggle-button" title="${t.toggleBottom}">
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" viewBox="0 0 512 512" style="width: 16px; height: 16px; opacity: 1;" xml:space="preserve">
<g>
	<path d="M256,0C114.842,0,0.002,114.84,0.002,256S114.842,512,256,512c141.158,0,255.998-114.84,255.998-256
		S397.158,0,256,0z M256,66.785c104.334,0,189.216,84.879,189.216,189.215S360.334,445.215,256,445.215S66.783,360.336,66.783,256
		S151.667,66.785,256,66.785z"></path>
	<path d="M246.151,357.482c2.591,3.416,6.087,5.299,9.849,5.299c3.762,0,7.257-1.883,9.848-5.295l69.014-90.97
		c2.665-3.513,3.393-6.945,2.046-9.655c-1.347-2.713-4.518-4.208-8.93-4.208h-26.424c-6.953,0-12.609-5.652-12.609-12.604v-75.035
		c0-8.707-7.082-15.792-15.788-15.792h-34.312c-8.706,0-15.788,7.084-15.788,15.792v75.035c0,6.952-5.656,12.604-12.609,12.604
		h-26.422c-4.412,0-7.586,1.495-8.93,4.208c-1.347,2.71-0.621,6.142,2.046,9.658L246.151,357.482z"></path>
</g>
</svg>
</div>
<div id="chat-filter-button" class="chat-toggle-button" title="${t.toggleFiltering}">
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" viewBox="0 0 512 512" style="width: 16px; height: 16px; opacity: 1;" xml:space="preserve">
<g>
	<polygon points="4.263,0 4.263,85.338 202.063,238.938 202.063,512 309.937,443.726 309.937,238.938 507.737,85.338
		507.737,0 	"></polygon>
</g>
</svg>
</div>
<div id="chat-header-button" class="chat-toggle-button" title="${t.toggleHeader}">
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" viewBox="0 0 512 512" style="width: 16px; height: 16px; opacity: 1;" xml:space="preserve">
<g>
	<path d="M502.325,307.303l-39.006-30.805c-6.215-4.908-9.665-12.429-9.668-20.348c0-0.084,0-0.168,0-0.252
		c-0.014-7.936,3.44-15.478,9.667-20.396l39.007-30.806c8.933-7.055,12.093-19.185,7.737-29.701l-17.134-41.366
		c-4.356-10.516-15.167-16.86-26.472-15.532l-49.366,5.8c-7.881,0.926-15.656-1.966-21.258-7.586
		c-0.059-0.06-0.118-0.119-0.177-0.178c-5.597-5.602-8.476-13.36-7.552-21.225l5.799-49.363
		c1.328-11.305-5.015-22.116-15.531-26.472L337.004,1.939c-10.516-4.356-22.646-1.196-29.701,7.736l-30.805,39.005
		c-4.908,6.215-12.43,9.665-20.349,9.668c-0.084,0-0.168,0-0.252,0c-7.935,0.014-15.477-3.44-20.395-9.667L204.697,9.675
		c-7.055-8.933-19.185-12.092-29.702-7.736L133.63,19.072c-10.516,4.356-16.86,15.167-15.532,26.473l5.799,49.366
		c0.926,7.881-1.964,15.656-7.585,21.257c-0.059,0.059-0.118,0.118-0.178,0.178c-5.602,5.598-13.36,8.477-21.226,7.552
		l-49.363-5.799c-11.305-1.328-22.116,5.015-26.472,15.531L1.939,174.996c-4.356,10.516-1.196,22.646,7.736,29.701l39.006,30.805
		c6.215,4.908,9.665,12.429,9.668,20.348c0,0.084,0,0.167,0,0.251c0.014,7.935-3.44,15.477-9.667,20.395L9.675,307.303
		c-8.933,7.055-12.092,19.185-7.736,29.701l17.134,41.365c4.356,10.516,15.168,16.86,26.472,15.532l49.366-5.799
		c7.882-0.926,15.656,1.965,21.258,7.586c0.059,0.059,0.118,0.119,0.178,0.178c5.597,5.603,8.476,13.36,7.552,21.226l-5.799,49.364
		c-1.328,11.305,5.015,22.116,15.532,26.472l41.366,17.134c10.516,4.356,22.646,1.196,29.701-7.736l30.804-39.005
		c4.908-6.215,12.43-9.665,20.348-9.669c0.084,0,0.168,0,0.251,0c7.936-0.014,15.478,3.44,20.396,9.667l30.806,39.007
		c7.055,8.933,19.185,12.093,29.701,7.736l41.366-17.134c10.516-4.356,16.86-15.168,15.532-26.472l-5.8-49.366
		c-0.926-7.881,1.965-15.656,7.586-21.257c0.059-0.059,0.119-0.119,0.178-0.178c5.602-5.597,13.36-8.476,21.225-7.552l49.364,5.799
		c11.305,1.328,22.117-5.015,26.472-15.531l17.134-41.365C514.418,326.488,511.258,314.358,502.325,307.303z M281.292,329.698
		c-39.68,16.436-85.172-2.407-101.607-42.087c-16.436-39.68,2.407-85.171,42.087-101.608c39.68-16.436,85.172,2.407,101.608,42.088
		C339.815,267.771,320.972,313.262,281.292,329.698z"></path>
</g>
</svg>
</div>
<div id="chat-chat-button" class="chat-toggle-button" title="${t.toggleChat}">
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" viewBox="0 0 512 512" style="width: 16px; height: 16px; opacity: 1;" xml:space="preserve">
<g>
	<path d="M352.705,62.572h-193.41C71.321,62.572,0,133.893,0,221.855c0,87.986,71.321,159.307,159.295,159.307h152.17
		c22.76,0,29.872,10.569,29.872,19.22c0,12.791-6.649,24.796-22.748,36.268c-9.969,7.101-2.128,12.779,5.69,12.779
		c101.678,0,187.72-110.942,187.72-227.574C512,133.893,440.691,62.572,352.705,62.572z M135.054,252.109
		c-16.722,0-30.254-13.543-30.254-30.254s13.531-30.242,30.254-30.242c16.7,0,30.232,13.531,30.232,30.242
		S151.755,252.109,135.054,252.109z M256,252.109c-16.699,0-30.254-13.543-30.254-30.254s13.555-30.242,30.254-30.242
		c16.7,0,30.254,13.531,30.254,30.242S272.7,252.109,256,252.109z M376.946,252.109c-16.699,0-30.23-13.543-30.23-30.254
		s13.531-30.242,30.23-30.242c16.723,0,30.254,13.531,30.254,30.242S393.668,252.109,376.946,252.109z"></path>
</g>
</svg>
</div>
<div id="chat-footer-button" class="chat-toggle-button" title="${t.toggleFooter}">
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" width="512px" height="512px" viewBox="0 0 512 512" style="width: 16px; height: 16px; opacity: 1;" xml:space="preserve">
<g>
	<path d="M498.781,74.344c-3.531-9.313-8.906-18.234-16.125-26.297c-14.797-16.5-31.359-28.594-48.813-36.484
		C416.391,3.656,398.109,0,380.031,0c-27.688,0-54.797,8.5-78.719,23.156s-44.75,35.484-59.938,60.609l-0.047,0.078L13.219,472.609
		l-0.031,0.016c-3.219,5.594-5.141,11.313-5.172,17.344c0,2.719,0.422,5.5,1.391,8.172c1.438,4.016,4.219,7.766,7.906,10.234
		c3.656,2.5,8,3.641,12.109,3.625c2.938,0,5.766-0.563,8.453-1.469c4.031-1.438,7.719-3.688,11.109-6.656s6.484-6.625,9.281-10.969
		c0.344-0.516,0.844-1.313,1.563-2.453c5.297-8.422,21.766-34.922,36.953-59.359c14.094-22.656,27.031-43.5,28.828-46.406
		c0.156-0.156,0.516-0.516,0.813-0.672l0.406-0.172c0.125-0.031,0.25-0.063,0.516-0.063c0.25,0,0.688,0.031,1.406,0.25
		s1.75,0.625,3.125,1.438l0.156,0.125l0.188,0.078c9.813,5.484,19,9.438,27.672,12.016c8.656,2.594,16.797,3.828,24.375,3.828
		c9.594,0.016,18.297-2,25.703-5.484c5.547-2.625,10.375-6.031,14.391-9.844c6.047-5.781,10.359-12.453,13.203-19.141
		c2.828-6.719,4.281-13.422,4.313-19.906c-0.031-4.453-0.656-8.828-2.703-13.281c-1.031-2.203-2.5-4.453-4.594-6.484
		c-1.844-1.781-4.266-3.281-6.938-4.188c0.219-0.641,0.703-1.219,0.938-1.391l0.094-0.063c0.047,0,0.141,0.016,0.328,0.063
		c5.109,1.453,14.141,3.922,25.281,6.031c11.156,2.109,24.422,3.859,38.281,3.875c10-0.016,20.328-0.922,30.422-3.484
		c10.063-2.531,19.953-6.781,28.641-13.531c7.906-6.156,14.219-13.281,18.641-21.125c4.422-7.828,6.906-16.469,6.891-25.219
		c0.016-5.719-1.047-11.438-3.141-16.844c-3.156-8.156-8.609-15.563-15.875-21.734c-7.266-6.203-16.328-11.266-27.078-15.109
		c-2.688-0.938-4.734-2.531-5.906-3.969c-0.594-0.719-0.953-1.406-1.141-1.859c0-0.016,0-0.016,0-0.031
		c0.438-0.219,1.172-0.484,2.281-0.734c1.328-0.297,3.156-0.516,5.516-0.5c3-0.016,6.844,0.344,11.516,1.266v-0.016
		c11.609,2.625,22.953,3.844,33.859,3.844c36.313,0,67.875-13.5,90.406-33.844c11.25-10.172,20.297-22.063,26.594-34.953
		c6.281-12.875,9.797-26.781,9.797-40.859C503.984,93.375,502.313,83.656,498.781,74.344z"></path>
</g>
</svg>
</div>
<div id="chat-popup-button" class="chat-toggle-button" on_ title="${t.popup}">
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" width="512px" height="512px" viewBox="0 0 512 512" style="width: 16px; height: 16px; opacity: 1;" xml:space="preserve">
<g>
	<path d="M280.781,144.391l42.047,59.125c-57.813,65.688-217.281,145.766-217.281,145.766
		c161.422,12.406,285.594-40.672,285.594-40.672l42.047,68.313L512,144.391H280.781z"></path>
	<polygon points="296.453,393.547 296.453,418.984 68.297,418.984 68.297,93.031 364.75,93.031 364.75,24.734 0,24.734
		0,487.266 364.75,487.266 364.75,418.563 349.375,393.547 	"></polygon>
</g>
</svg>
</div>
<div id="chat-reload-button" class="chat-toggle-button" on_ title="${t.reload}">
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" viewBox="0 0 512 512" style="width: 16px; height: 16px; opacity: 1;" xml:space="preserve">
<g>
	<path d="M446.025,92.206c-40.762-42.394-97.487-69.642-160.383-72.182c-15.791-0.638-29.114,11.648-29.752,27.433
		c-0.638,15.791,11.648,29.114,27.426,29.76c47.715,1.943,90.45,22.481,121.479,54.681c30.987,32.235,49.956,75.765,49.971,124.011
		c-0.015,49.481-19.977,94.011-52.383,126.474c-32.462,32.413-76.999,52.368-126.472,52.382
		c-49.474-0.015-94.025-19.97-126.474-52.382c-32.405-32.463-52.368-76.992-52.382-126.474c0-3.483,0.106-6.938,0.302-10.364
		l34.091,16.827c3.702,1.824,8.002,1.852,11.35,0.086c3.362-1.788,5.349-5.137,5.264-8.896l-3.362-149.834
		c-0.114-4.285-2.88-8.357-7.094-10.464c-4.242-2.071-9.166-1.809-12.613,0.738L4.008,182.45c-3.05,2.221-4.498,5.831-3.86,9.577
		c0.61,3.759,3.249,7.143,6.966,8.974l35.722,17.629c-1.937,12.166-3.018,24.602-3.018,37.279
		c-0.014,65.102,26.475,124.31,69.153,166.944C151.607,465.525,210.8,492.013,275.91,492
		c65.095,0.014,124.302-26.475,166.937-69.146c42.678-42.635,69.167-101.842,69.154-166.944
		C512.014,192.446,486.844,134.565,446.025,92.206z"></path>
</g>
</svg>
</div>
<div id="chat-toggle-button" class="chat-toggle-button" on_ title="${t.close}">
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" viewBox="0 0 512 512" style="width: 16px; height: 16px; opacity: 1;" xml:space="preserve">
<g>
	<polygon points="512,52.535 459.467,0.002 256.002,203.462 52.538,0.002 0,52.535 203.47,256.005 0,459.465
		52.533,511.998 256.002,308.527 459.467,511.998 512,459.475 308.536,256.005 	"></polygon>
</g>
</svg>
</div>
`;
        document.querySelector("yt-live-chat-app > #contents").appendChild(buttons);

        document.querySelector("#chat-focus-button").onclick = focusButtonOnClick;
        document.querySelector("#chat-left-button").onclick = leftButtonOnClick;
        document.querySelector("#chat-filter-button").onclick = filterButtonOnClick;
        document.querySelector("#chat-bottom-button").onclick = bottomButtonOnClick;
        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;

        // 強調表示
        var highlight = document.createElement("div");
        highlight.id = "highlight-chat-container";
        document.querySelector("yt-live-chat-item-list-renderer #contents").appendChild(highlight);

        var stylesheet = "";

        // チャット
        stylesheet += `
                       yt-live-chat-renderer.yt-live-chat-app { --scrollbar-width: 0 !important; }
                       #message.yt-live-chat-text-message-renderer,
                       #message.yt-live-chat-paid-message-renderer { font-weight: bold; font-size: ${appendUnit(c.chatFontSize)}; }
                       yt-live-chat-header-renderer, yt-live-chat-renderer,
                       yt-live-chat-message-input-renderer, yt-live-chat-ticker-renderer { background: ${c.backgroundColor}; }
                       yt-live-chat-server-error-message { background: #fff; }
                       #item-scroller.yt-live-chat-item-list-renderer { scrollbar-width: none; }
                       #item-scroller.yt-live-chat-item-list-renderer::-webkit-scrollbar { display: none !important; }
                       #item-scroller.yt-live-chat-item-list-renderer #items.yt-live-chat-item-list-renderer[no_translate_],
                       #item-scroller.animated.yt-live-chat-item-list-renderer #items.yt-live-chat-item-list-renderer[no_translate_]
                           { transform: translateY(0) !important; }
                       yt-live-chat-text-message-renderer { transition: height .2s linear; }
                       yt-live-chat-text-message-renderer[hidden_] { height: 0; opacity: 0; padding: 0; }
`;

        // チャットちらつき低減
        if (c.disableChatFlickers) {
            stylesheet += `
#items.yt-live-chat-item-list-renderer yt-live-chat-text-message-renderer:nth-last-child(1),
#items.yt-live-chat-item-list-renderer yt-live-chat-text-message-renderer:nth-last-child(2),
#items.yt-live-chat-item-list-renderer yt-live-chat-text-message-renderer:nth-last-child(3)
{ height: 0; opacity: 0; padding: 0; margin: 0; }
`;
        }

        // 文字枠をつける
        if (c.isEnableChatOutline) {
            stylesheet += `
               #message.yt-live-chat-text-message-renderer {
                   color: #ffffff; letter-spacing : 3px;
                   text-shadow : 2px 2px 1px ${c.chatOutlineColor}, -2px 2px 1px ${c.chatOutlineColor},
                                 2px -2px 1px ${c.chatOutlineColor}, -2px -2px 1px ${c.chatOutlineColor},
                                 2px 0px 1px ${c.chatOutlineColor}, 0px  2px 1px ${c.chatOutlineColor},
                                 -2px  0px 1px ${c.chatOutlineColor}, 0px -2px 1px ${c.chatOutlineColor};
               }
               #message.yt-live-chat-paid-message-renderer {
                   color: #ffffff; letter-spacing : 1px; line-height: 1.1;
                   text-shadow : 1px 1px 1px ${c.chatOutlineColor}, -1px 1px 1px ${c.chatOutlineColor},
                                 1px -1px 1px ${c.chatOutlineColor}, -1px -1px 1px ${c.chatOutlineColor},
                                 1px 0px 1px ${c.chatOutlineColor}, 0px  1px 1px ${c.chatOutlineColor},
                                 -1px  0px 1px ${c.chatOutlineColor}, 0px -1px 1px ${c.chatOutlineColor};
               }`;
        }
        // ユーザー名非表示
        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; height: 0; min-height: 0; }`;
        if (c.isHideChat) {
            document.querySelector("#contents").setAttribute("hidechat_", "");
        } else {
            document.querySelector("#chat-chat-button").setAttribute("on_", "");
        }
        // メンバーチャットのみ
        if (c.isMemberOnly) {
            stylesheet += `#items.yt-live-chat-item-list-renderer yt-live-chat-text-message-renderer[author-type=''] { display: none; }`;
        }
        // チャンネル用絵文字以外非表示
        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; }";
            }
        }

        const chat = window.parent.document.querySelector("#chat");
        // チャットウィンドウ左側
        if (c.chatWindowPosition.indexOf("left") >= 0) {
            document.querySelector("#chat-left-button").setAttribute("on_", "");
            chat && chat.setAttribute("leftside_", "");
        } else {
            document.querySelector("#chat-left-button").removeAttribute("on_");
            chat && chat.removeAttribute("leftside_", "");
        }
        // チャットウィンドウ下側
        if (c.chatWindowPosition.indexOf("bottom") >= 0) {
            document.querySelector("#chat-bottom-button").setAttribute("on_", "");
            chat && chat.setAttribute("bottomside_", "");
        } else {
            document.querySelector("#chat-bottom-button").removeAttribute("on_");
            chat && chat.removeAttribute("bottomside_");
        }
        // チャットフィルタ
        if (c.enableFilter) {
            document.querySelector("#chat-filter-button").setAttribute("on_", "");
            isEnableFiltering = true;
        }

        if (isNoTranslate()) {
            document.querySelector("#items.yt-live-chat-item-list-renderer").setAttribute("no_translate_", "");
        } else {
            document.querySelector("#items.yt-live-chat-item-list-renderer").removeAttribute("no_translate_");
        }

        // ログインユーザー名
        stylesheet += "#input-container yt-live-chat-author-chip { display: none; }";

        // スパチャ時ポップアップ
        stylesheet += "yt-live-chat-product-picker-renderer { background-color: #fff; }";

        // チャット入力欄
        stylesheet += `#input.yt-live-chat-message-input-renderer { background-color: #fffa; font-weight: bold; margin-top: -4px; margin-bottom: 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; }`;

        // チャットハイライト
        stylesheet += `#highlight-chat-container {
                       position: absolute;
                       bottom: 0; left: 0; right: 0;
                       background: #fff;
                       z-index: 2020; }`;

        // コントロールボタン
        stylesheet += `body {
                pointer-events: all;
            }
            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: 2px;
                cursor: pointer;
                margin-left: 3px;
                z-index: 2021;
            }
            yt-live-chat-app .chat-toggle-button svg {
                vertical-align: middle;
                fill: #fff;
            }
            yt-live-chat-app .chat-toggle-button[on_] {
                background: #1E90FF;
            }`;

        // スーパーチャット表示
        // 履歴非表示
        if (c.superChatViewType.indexOf("history") < 0) {
            stylesheet += `#ticker.yt-live-chat-renderer { display: none; }`;
        }
        // チャットヘッダー非表示
        if (c.superChatViewType.indexOf("header") < 0) {
            stylesheet += `
                           #content.yt-live-chat-paid-message-renderer { border-radius: 2px; }
                           #header.yt-live-chat-paid-message-renderer,
                           #price-column.yt-live-chat-paid-sticker-renderer,
                           #header-subtext.yt-live-chat-membership-item-renderer { display: none; }
                           #author-photo.yt-live-chat-membership-item-renderer,
                           #author-photo.yt-live-chat-membership-item-renderer img,
                           #author-photo.yt-live-chat-paid-sticker-renderer,
                           #author-photo.yt-live-chat-paid-sticker-renderer img,
                           #sticker.yt-live-chat-paid-sticker-renderer img {
                               width: 32px; height: 32px; }
`;
        }
        // チャットメッセージ非表示
        if (c.superChatViewType.indexOf("message") < 0) {
            stylesheet += `yt-live-chat-paid-message-renderer,
                           yt-live-chat-membership-item-renderer,
                           yt-live-chat-paid-sticker-renderer { display: none; }`;
        }

        // ガイドライン非表示
        stylesheet += "yt-live-chat-viewer-engagement-message-renderer { display: none; }";

        // 絵文字フィーバー
        stylesheet += `
.emoji.yt-live-chat-text-message-renderer[fever_] {
    position: absolute;
    display: inline-block;
    bottom: 0;
    width: 32px; height: 32px;
    z-index: -1;
    opacity: 0;
    animation: emotesFeverHorizontal 1s alternate infinite ease-in-out,
               emotesFeverVertical 4s alternate 1 ease-in;
}
.emoji.yt-live-chat-text-message-renderer[fever1_] {
    animation-delay: 0s, 0s;
}
.emoji.yt-live-chat-text-message-renderer[fever2_] {
    animation-delay: -0.4s, 0s;
}
.emoji.yt-live-chat-text-message-renderer[fever3_] {
    animation-delay: -0.8s, 0s;
}
.emoji.yt-live-chat-text-message-renderer[fever4_] {
    animation-delay: -1.2s, 0s;
}
.emoji.yt-live-chat-text-message-renderer[fever5_] {
    animation-delay: -1.6s, 0s;
}
@keyframes emotesFeverHorizontal {
    100% { transform: translateX(var(--fever-width)) rotateZ(4deg); }
}
@keyframes emotesFeverVertical {
    0% { opacity: 0; }
    10% { opacity: 0.9; }
    80% { opacity: 0.6; }
    100% { opacity: 0; transform: translateY(var(--fever-height)); }
}
`;

        // スタイルを適応する
        var style = document.createElement("style");
        style.type ="text/css";
        style.innerHTML = stylesheet;
        document.querySelector("head").appendChild(style);
    }

    // チャットウィンドウのみから使用する
    // 設定ポップアップとチャットウィンドウとの干渉を解決する
    var fixPlayerConflictTimer;
    function fixPlayerConflict() {
        var chat = window.parent.document.querySelector("#chatframe");
        if (!chat) return;

        var button = window.parent.document.querySelector(".ytp-settings-button");
        if (button && button.getAttribute("aria-expanded") == "true") {
            chat.style.pointerEvents = "none";
            chat.style.opacity = 0;

            // setInterval を多重起動しない
            clearTimeout(fixPlayerConflictTimer);
            fixPlayerConflictTimer = setInterval(fixPlayerConflict, 500);
        } else {
            chat.style.pointerEvents = "all";
            chat.style.opacity = c.windowOpacity;
            clearTimeout(fixPlayerConflictTimer);
        }
    }

    // 動的にレイアウトを調整する
    // ウィンドウのリサイズなどに合わせて呼び出す
    function updateDynamicLayout() {
        if (document.querySelector("[is-watch-page]") && location.href.indexOf("singleborderwindow_") >= 0) {
            updateSingleBorderStyle();
        }
        updateChatWindowHeight();
    }

    // チャットウィンドウの高さを調整する
    function updateChatWindowHeight() {
        // チャットウィンドウ内からでも動くようにする
        if (window != window.parent) {
            window.parent.updateChatWindowHeight && window.parent.updateChatWindowHeight();
            return;
        }

        var height = 0;
        const player = document.querySelector("#player-theater-container"),
              controls = document.querySelector(".ytp-chrome-bottom");
        if (player) {
            height += player.clientHeight;
        }
        if (controls) {
            height -= controls.clientHeight;
        }

        var chatframe = document.querySelector("#chatframe");
        if (chatframe) {
            chatframe.style.maxHeight = height + "px";
        }
        var chat = document.querySelector("#chat");
        if (chat) {
            chat.style.maxHeight = height + "px";
        }
    }

    function focusButtonOnClick() {
        this.toggleAttribute("on_");
    }

    function filterButtonOnClick() {
        isEnableFiltering = this.toggleAttribute("on_");

        if (isNoTranslate()) {
            document.querySelector("#items.yt-live-chat-item-list-renderer").setAttribute("no_translate_", "");
        } else {
            document.querySelector("#items.yt-live-chat-item-list-renderer").removeAttribute("no_translate_");
        }
    }

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

    function bottomButtonOnClick() {
        if (window.parent.document.querySelector("#chat").toggleAttribute("bottomside_")) {
            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);
        if (window.parent != window) {
            parent.close();
        }

        return false;
    }

    function reloadButtonOnClick() {
        location.reload();

        return false;
    }

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

        return false;
    }

    // チャットを強調表示する
    function highlightChat(chat) {
        chat.removeAttribute("hidden_");
        chat.setAttribute("highlighted_");
        document.querySelector("#highlight-chat-container").appendChild(chat);

        setTimeout(function () {
            chat.setAttribute("hidden_");
            document.querySelector("#items.yt-live-chat-item-list-renderer").appendChild(chat);
        }, c.moderatorChatTimeout * 1000);
    }

    /** *****************
     * 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

        var stylesheet = "";

        stylesheet += `
        ytd-watch-flexy[flexy_][js-panel-height_][fullscreen] #player-theater-container.ytd-watch-flexy {
            min-height: unset;
        }
        ytd-watch-flexy[flexy_][js-panel-height_][fullscreen] #chat,
        ytd-watch-flexy[flexy_][js-panel-height_][theater] #chat {
            transition: opacity .2s linear;
            min-height: unset;
        }
        ytd-watch-flexy[flexy_][js-panel-height_][fullscreen] #chat.ytd-watch-flexy:not([collapsed]).ytd-watch-flexy,
        ytd-watch-flexy[flexy_][js-panel-height_][theater] #chat.ytd-watch-flexy:not([collapsed]).ytd-watch-flexy {
            position: absolute; border: 0;
            right: 8px;
            width: ${appendUnit(c.windowWidth)};
            height: 100%;
            min-height: 0px;
            top: calc(var(--ytd-watch-flexy-masthead-height));
            z-index: 2019; /* #masthead is 2020 */
            box-sizing: border-box;
            margin: 0;
            pointer-events: none;
        }
        ytd-app[masthead-hidden_] ytd-watch-flexy[flexy_][js-panel-height_][fullscreen] #chat.ytd-watch-flexy:not([collapsed]).ytd-watch-flexy,
        ytd-app[masthead-hidden_] ytd-watch-flexy[flexy_][js-panel-height_][theater] #chat.ytd-watch-flexy:not([collapsed]).ytd-watch-flexy {
            top: 0;
        }
        ytd-watch-flexy[flexy_][js-panel-height_][fullscreen] #chat.ytd-watch-flexy[leftside_]:not([collapsed]).ytd-watch-flexy,
        ytd-watch-flexy[flexy_][js-panel-height_][theater] #chat.ytd-watch-flexy[leftside_]:not([collapsed]).ytd-watch-flexy {
            left: 8px;
            rignt: unset;
        }
        ytd-watch-flexy[flexy_][js-panel-height_][fullscreen] #chat.ytd-watch-flexy:not([collapsed]).ytd-watch-flexy #chatframe,
        ytd-watch-flexy[flexy_][js-panel-height_][theater] #chat.ytd-watch-flexy:not([collapsed]).ytd-watch-flexy #chatframe {
            position: absolute;
            top: 0;
            height: ${appendUnit(c.windowHeight)};
            padding: 8px;
            box-sizing: border-box;
            pointer-events: all;
            opacity: ${c.windowOpacity};
        }
        ytd-watch-flexy[flexy_][js-panel-height_][fullscreen] #chat.ytd-watch-flexy[bottomside_]:not([collapsed]).ytd-watch-flexy #chatframe,
        ytd-watch-flexy[flexy_][js-panel-height_][theater] #chat.ytd-watch-flexy[bottomside_]:not([collapsed]).ytd-watch-flexy #chatframe {
            top: unset;
            bottom: 0;
        }
        ytd-watch-flexy[flexy_][js-panel-height_][fullscreen] #chat.ytd-watch-flexy[collapsed].ytd-watch-flexy #chatframe,
        ytd-watch-flexy[flexy_][js-panel-height_][theater] #chat.ytd-watch-flexy[collapsed].ytd-watch-flexy #chatframe {
            height: 0;
        }
`;

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

        // config
        stylesheet += `
#simple-chat-stylizer-config {
    font-size: 12px; max-height: 2rem; max-width: 1500px; margin: 0 auto;
    overflow: hidden; transition: max-height .25s ease-in-out; }
#simple-chat-stylizer-config:hover { max-height: 1000px; transition-delay: .5s; }
#simple-chat-stylizer-config input {
    display: inline-block;
    width: 60px; min-height: 20px;
    text-align: center; vertical-align: middle; box-sizing: border-box;
    margin: 0 8px 0 0; }
#simple-chat-stylizer-config label {
    display: inline-block; min-height: 20px; vertical-align: middle; box-sizing: border-box; }
#simple-chat-stylizer-config label.for-select { margin-left: 68px; margin-right: 8px; }
#simple-chat-stylizer-config textarea { min-height: 2rem; max-width: 1000px; min-width: 100px; width: 100%; display: block; }
`;

        // single border window
        stylesheet += `ytd-app[singleborderwindow_] { --ytd-app-fullerscreen-scrollbar-width: 17px; --ytd-masthead-height: 0px !important; }`;

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

        var style = document.createElement("style");
        style.type = "text/css";
        style.id = "yt-live-chat-float-on-screen-stylesheet";
        style.innerHTML = stylesheet;
        document.querySelector("head").appendChild(style);
    }

    // チャット入力欄にフォーカスする
    function focusInputField() {
        var frame = document.querySelector("#chatframe"), input;
        if (frame && frame.contentDocument.querySelector("#chat-focus-button[on_]")) {
            input = frame.querySelector("yt-live-chat-text-input-field-renderer #input[contenteditable]");
        } else if (document.querySelector("#chat-focus-button[on_]")) {
            input = document.querySelector("yt-live-chat-text-input-field-renderer #input[contenteditable]");
        }

        if (input) {
            setTimeout(() => input.focus(), 50);
        }
    }

    function appendUnit(value) {
        if (value.match(/(%|cm|mm|Q|in|pt|px|em|ex|rem|ch|lh|vm|vh|vmin|vmax)/)) {
            return value;
        } else {
            return value + "px";
        }
    }

    /** *****************
     * Single Bordered Window
     * ******************/
    var updateSingleBorderSytleTimer = 0;
    function updateSingleBorderStyle() {
        updateSingleBorderSytleTimer = clearTimeout(updateSingleBorderStyle);
        updateSingleBorderSytleTimer = 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", "");
        }, 300);
    }

    function popupSingleBorderWindow(url) {
        url = url || location.href;
        window.open(url.split("#")[0] + (url.indexOf("&singleborderwindow_") >= 0 ? "" : "&singleborderwindow_="), url, SINGLE_WINDOW_PARAMS);
    }
})();