您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Display chat window on stream screen and apply custom stylesheet
当前为
// ==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.3.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(); setTimeout(initChatFilter, 1000); 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; /** ***************** * Chat Filtering * ******************/ function initChatFilter() { if (!c.enableFilter) return; var filter = new RegExp(c.chatFilter.split(/[\r\n/]+/).filter((x) => (x || "").trim("\n").trim("\r") != "").map((x) => "(" + x + ")").join("|")); console.log("initChatFilter()", filter); var lastChat; var hiddenHeight = 0; const observer = new MutationObserver((mutations) => { mutations.forEach((mutation) => { if (!mutation.addedNodes) return; mutation.addedNodes.forEach((chat) => { // console.log(e); var msg = chat.querySelector("#message"); if (!msg) return; if (msg.innerText.match(filter)) { chat.setAttribute("hidden_", ""); // console.log("filtered", chat.innerText); } lastChat = chat; }); }); }); observer.observe(document.querySelector("#chat #items"), { attributes: false, childList: true, characterData: false }); } /** ***************** * 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 /> <input type="checkbox" name="enableFilter"> <label>chat filter (regex)</label><br /> <textarea name="chatFilter" rows="3" cols="20" value="" ></textarea><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, #${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; } } } } function loadConfig() { c = JSON.parse(localStorage[configKey] || "{}"); console.log("loaded config", c); } function updateConfigFromForm() { var $params = document.querySelectorAll(`#${configAreaID} input, #${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() { 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; } yt-live-chat-header-renderer, yt-live-chat-renderer, yt-live-chat-message-input-renderer, yt-live-chat-ticker-renderer { background: ${c.backgroundColor}; } #item-scroller { scrollbar-width: none; } yt-live-chat-text-message-renderer { transition: height 1s ease, padding 1s ease; } yt-live-chat-text-message-renderer[hidden_] { height: 0; padding: 0; opacity: 0; }`; 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; }"; } } if (c.windowIsFromLeft) { document.querySelector("#chat-left-button").setAttribute("on_", ""); } // 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}; } ytd-watch-flexy[flexy_][js-panel-height_][theater] #chat.ytd-watch-flexy[leftside_]:not([collapsed]).ytd-watch-flexy { left: ${c.windowRight + rightUnit}; rignt: unset; } `; if (c.windowIsFromLeft) { document.querySelector("#chat").setAttribute("leftside_", ""); } 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; } #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 { max-height: 12px; transition: max-height .25s ease-in; overflow: hidden; max-width: 1500px; margin: 0 auto; font-size: 12px; } #simple-chat-stylizer-config:hover { max-height: 1000px; } #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; } 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); } })();