// ==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.1.0
// @author You
// @match https://www.youtube.com/*
// @grant none
// @nowrap
// @namespace http://tampermonkey.net/
// ==/UserScript==
(function() {
'use strict';
var floatStyleID = "yt-live-chat-float-on-screen-stylesheet";
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;
loadConfig();
setTimeout(function () {
if (location.pathname.indexOf("/live_chat") == 0) {
addLiveChatStyle();
} else {
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;
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</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="isHideFooter">
<label>hide footer panel (you can't chat)</label><br />
<input type="checkbox" name="isHideHeader" checked>
<label>hide header</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</label><br />
<input type="text" name="windowWidth" value="430">
<label>window width</label><br />
<input type="text" name="windowHeight" value="720">
<label>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</label><br />
<input type="text" name="windowRight" value="10">
<label>window position from right/left</label><br />
<input type="text" name="windowOpacity" value="0.9">
<label>window opacity</label><br />
<input type="checkbox" name="windowIsFromBottom">
<label>window position is form bottom</label><br />
<input type="checkbox" name="windowIsFromLeft">
<label>window position is from left</label><br />
<button style="width: unset;" id="simple-chat-stylizer-config-save">Save</button><label />
<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 = popupSingleBorderWindow;
// 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("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 addLiveChatStyle() {
console.log("addLiveChatStyle() : ", location.href);
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-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; }";
}
if (c.isHideFooter) {
stylesheet += "#panel-pages { display: none !important; }";
}
if (c.isHideHeader) {
stylesheet += "yt-live-chat-header-renderer { display: none !important; }";
}
// always replace show hide button
// if (c.isHideToggleButton) {
stylesheet += "#show-hide-button.ytd-live-chat-frame { visibility: hidden; }";
// }
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: #fffa; }";
// chat input field style
stylesheet += "yt-live-chat-text-input-field-renderer { background-color: #fffa; font-weight: bold; }";
// 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; }";
// reload button style
stylesheet += `yt-live-chat-app #chat-reload-button {
display: inline-block;
position: absolute; right: 40px; bottom: 0;
color: #fff; background: #1E90FF; opacity: 0;
padding: 3px;
transition: opacity .2s linear; cursor: pointer;
z-index: 10001; }`;
stylesheet += "yt-live-chat-app:hover #chat-reload-button { opacity: 1; }";
// toggle button
stylesheet += `yt-live-chat-app #chat-toggle-button {
display: inline-block;
position: absolute; right: 0; bottom: 0;
color: #fff; background: #1E90FF; opacity: 0;
padding: 3px;
transition: opacity .2s linear; cursor: pointer;
z-index: 10001; }`;
stylesheet += "yt-live-chat-app:hover #chat-toggle-button { opacity: 1; }";
// apply style
var $style = document.createElement("style");
$style.innerText = stylesheet;
document.body.appendChild($style);
// fix moderator chat
var $container = document.createElement("div");
$container.id = "moderator-chat-container";
document.querySelector("yt-live-chat-item-list-renderer #contents").appendChild($container);
// reload button
var $reload = document.querySelector("#chat-reload-button") || document.createElement("div");
$reload.id = "chat-reload-button";
$reload.innerText = "Reload";
$reload.onclick = reloadButtonOnClick;
document.querySelector("yt-live-chat-app > #contents").appendChild($reload);
// toggle button
var $toggle = document.querySelector("#chat-toggle-button") || document.createElement("div");
$toggle.id = "chat-toggle-button";
$toggle.innerText = "Toggle";
$toggle.onclick = toggleButtonOnClick;
document.querySelector("yt-live-chat-app > #contents").appendChild($toggle);
setInterval(observeModerator, 2000);
}
function reloadButtonOnClick() {
location.reload();
return false;
}
function toggleButtonOnClick() {
window.top.document.querySelector("#show-hide-button #button").click();
return false;
}
function saveButtonOnClick() {
updateConfigFromForm();
addFloatWindowStyle();
document.querySelector("#chatframe").contentWindow.location.reload();
return false;
}
function appendModeratorChat($chat) {
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]);
}
}
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 stylesheet = "";
stylesheet += "ytd-watch-flexy[flexy_][js-panel-height_][theater] #chat.ytd-watch-flexy:not([collapsed]).ytd-watch-flexy { position: fixed; border: 0;"
+ (c.windowIsFromLeft ? "left" : "right") + ": " + c.windowRight + "px; "
+ "width: " + c.windowWidth + "px;"
+ "height: " + c.windowHeight + "px; }";
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 + "px); }";
}
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 + "px; }";
}
// 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 background
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; }";
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 + `px; }`;
var $old = document.querySelector("#" + floatStyleID);
if ($old) $old.remove();
var $style = document.createElement("style");
$style.id = floatStyleID;
$style.innerText = stylesheet;
document.body.appendChild($style);
// console.log(stylesheet);
}
function popupSingleBorderWindow() {
window.open(location.href.split("#")[0] + (location.href.indexOf("&singleborderwindow_=") >= 0 ? "" : "&singleborderwindow_="), location.href, SINGLE_WINDOW_PARAMS);
return false;
}
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);
}
})();