// ==UserScript== // @name Transparent Twitch Chat // @description Why decide between missing a PogChamp or sacrificing precious screen space, when you can have the best of both worlds! // @version 1.5.0 // @namespace https://chylex.com // @homepageURL https://github.com/chylex/Transparent-Twitch-Chat // @supportURL https://github.com/chylex/Transparent-Twitch-Chat/issues // @downloadURL https://github.com/chylex/Transparent-Twitch-Chat/raw/master/dist/TransparentTwitchChat.user.js // @include https://www.twitch.tv/* // @run-at document-end // @grant GM_getValue // @grant GM_setValue // @noframes // ==/UserScript== const settings = { globalSwitch: true, chatWidth: 350, chatFilters: "", playerPosition: "center center", hideTimestamps: true, grayTheme: false, hideHeader: true, hideChatInput: false, hidePinnedCheer: false, hideConversations: false, transparentChat: true, smoothTextShadow: false, chatLeftSide: false, backgroundOpacity: 30, hideBadgeTurbo: true, hideBadgePrime: true, hideBadgeSubscriber: true, hideBadgeVIP: false, hideBadgeSubGift: false, hideBadgeBitCheer: false, hideBadgeLeader: false, badgeOpacity: 85 }; if (typeof GM_getValue !== "undefined"){ for(let key in settings){ settings[key] = GM_getValue(key, settings[key]); } } const isFirefox = navigator.userAgent.includes(" Gecko/") || "mozFullScreen" in document; function tryRemoveElement(ele){ if (ele && ele.parentNode){ ele.parentNode.removeChild(ele); } } function onSettingsUpdated(){ generateCustomCSS(); refreshChatFilters(); if (typeof GM_setValue !== "undefined"){ for(let key in settings){ GM_setValue(key, settings[key]); } } } // Styles function generateCustomCSS(){ if (!settings.globalSwitch){ tryRemoveElement(document.getElementById("chylex-ttc-style-custom")); return; } const wa = ":not(.ttcwa)"; // selector priority workaround const rcol = ".right-column--theatre"; const rcolBlur = ".right-column--theatre:not(:hover)"; const isTheatre = ".ttc-theatre"; const fullWidth = ".ttc-rcol-collapsed"; const fullScreen = ".ttc-player-fullscreen"; const isChatLeft = settings.chatLeftSide && settings.transparentChat; let style = document.getElementById("chylex-ttc-style-custom"); if (!style){ style = document.createElement("style"); style.id = "chylex-ttc-style-custom"; } style.innerHTML = ` ${rcolBlur} .chat-list__lines .simplebar-track.vertical {visibility:hidden!important} ${isTheatre} .side-nav {display:none!important} ${isFirefox ? ` ${rcol} .video-chat__message-list-wrapper:not(.video-chat__message-list-wrapper--unsynced) {overflow-y:hidden!important} ` : ``} ${isTheatre} .video-player video {object-position:${settings.playerPosition == "#opposite-chat" ? (isChatLeft ? "center right" : "center left") : settings.playerPosition}!important} ${rcol}${wa}, ${rcol} .channel-root__right-column${wa} {width:${settings.chatWidth}px!important} ${rcol} .chat-shell__expanded {min-width:0!important} ${rcol} .video-chat {flex-basis:auto!important} ${rcol} .video-chat__header {display:none!important} ${rcol} .room-picker {width:${settings.chatWidth}px} ${rcol} .hidden {display:none} ${rcol} .video-chat__sync-button {width:${Math.max(0, settings.chatWidth - 40)}px;z-index:10;background-color:#b8b5c0!important} #root[data-a-page-loaded-name="VideoWatchPage"] ${rcolBlur}:not(.right-column--collapsed) .right-column__toggle-visibility { display: none !important; } ${settings.hideTimestamps ? ` ${rcol} .vod-message__header div[class*="ScAttachedTooltipWrapper-"] {display:none!important} ` : ``} ${settings.hideHeader ? ` ${rcolBlur} .stream-chat-header {display:none!important} ${rcolBlur}:not(.right-column--collapsed) .right-column__toggle-visibility {display:none!important} ` : ``} ${settings.hideChatInput ? ` ${rcolBlur} .chat-input {display:none!important} ` : ``} ${settings.hidePinnedCheer ? ` .channel-leaderboard {display:none} ` : ``} ${settings.transparentChat ? ` body:not(${fullScreen}) .persistent-player--theatre {width:100%!important} body:not(${fullWidth}):not(${fullScreen}) .persistent-player--theatre .top-bar,body:not(${fullWidth}):not(${fullScreen}) .persistent-player--theatre div[data-a-target="player-controls"] {padding-right:${settings.chatWidth}px} body:not(${fullWidth}):not(${fullScreen}) .persistent-player--theatre .player-overlay-background > div {right:${settings.chatWidth}px!important} ${rcolBlur} .channel-root__right-column${wa} {background:rgba(14, 12, 19, ${settings.backgroundOpacity * 0.01})!important} ${rcolBlur} .channel-root__right-column${wa} > div, ${rcol} .chat-room {background:transparent!important} ${rcolBlur} .chylex-ttc-chat-container {color:#ece8f3!important} ${rcolBlur} .rooms-header, ${rcolBlur} .leaderboard-header-tabbed-layout {background:transparent!important} ${rcolBlur} .chat-input {opacity:0.6} ${rcolBlur} .chylex-ttc-chat-container {${settings.smoothTextShadow ? `text-shadow:0 0 2px rgba(0,0,0,0.86328125), -1px 0 1px rgba(0,0,0,0.3984375), 0 -1px 1px rgba(0,0,0,0.3984375), 1px 0 1px rgba(0,0,0,0.3984375), 0 1px 1px rgba(0,0,0,0.3984375);` : `text-shadow:-1px 0 0 rgba(0,0,0,0.6640625), 0 -1px 0 rgba(0,0,0,0.6640625), 1px 0 0 rgba(0,0,0,0.6640625), 0 1px 0 rgba(0,0,0,0.6640625);`}}${rcolBlur} .chat-author__display-name, ${rcolBlur} .vod-message__timestamp {${settings.smoothTextShadow ? `text-shadow:-1px 0 1px rgba(0,0,0,0.3984375), 0 -1px 1px rgba(0,0,0,0.3984375), 1px 0 1px rgba(0,0,0,0.3984375), 0 1px 1px rgba(0,0,0,0.3984375);` : `text-shadow:-1px 0 0 rgba(0,0,0,0.53125), 0 -1px 0 rgba(0,0,0,0.53125), 1px 0 0 rgba(0,0,0,0.53125), 0 1px 0 rgba(0,0,0,0.53125);`}}${rcolBlur} .chat-line__message--mention-recipient {text-shadow:none} ${rcolBlur} .chat-line__message a {color:#cdb9f5!important} ${rcolBlur} .user-notice-line {background-color:rgba(31, 31, 35, 0.45)!important} ${rcolBlur} .user-notice-line--highlighted {border-left-color:transparent!important} .whispers--theatre-mode .whispers-threads-box__container:not(.whispers-threads-box__container--open):not(:hover) {opacity:${Math.max(0.1, settings.backgroundOpacity * 0.01)}} ` : ` body:not(${fullWidth}):not(${fullScreen}) .persistent-player--theatre {width:calc(100% - ${settings.chatWidth}px)!important} body:not(${fullScreen}) .persistent-player--theatre .player-streamstatus {margin-right:20px!important} `} .whispers--theatre-mode.whispers--right-column-expanded-beside { right: ${settings.chatWidth}px !important; } ${settings.hideConversations ? ` .whispers--theatre-mode${wa} {display:none!important} .video-player__container--theatre-whispers, .highwind-video-player__container--theatre-whispers {bottom:1px!important; // allows hiding player controls in fullscreen by moving cursor all the way down}` : ``} ${isChatLeft ? ` ${rcol}${wa}, ${rcol} .chat-list__lines .simplebar-track.vertical {left:0!important;right:auto!important} ${rcol} .channel-root__right-column${wa} > div {border-left:none!important;border-right:var(--border-width-default) solid var(--color-border-base)!important} body:not(${fullWidth}):not(${fullScreen}) .persistent-player--theatre .top-bar {padding-left:${settings.chatWidth + 10}px;padding-right:0} body:not(${fullWidth}):not(${fullScreen}) .persistent-player--theatre div[data-a-target="player-controls"] {padding-left:${settings.chatWidth}px;padding-right:0} body:not(${fullWidth}):not(${fullScreen}) .persistent-player--theatre .player-overlay-background > div {left:${settings.chatWidth}px!important;right:0!important} .whispers--theatre-mode.whispers--right-column-expanded-beside {right:0px!important} ${rcol} .right-column__toggle-visibility {transform:rotate(180deg)!important} ${rcol}.right-column--collapsed .right-column__toggle-visibility {left:0.5rem} ` : ``} ${settings.grayTheme ? ` ${rcol} [data-a-target="chat-input"] {background-color:#0e0e0e!important;border-color:#2b2b2b!important} ${rcol} [data-a-target="chat-input"]:focus {border-color:#787878!important;box-shadow:0 0 6px -2px #787878!important} ${rcol} [data-a-target="chat-send-button"] {background-color:#2b2b2b!important;border:1px solid #000000!important} ${rcol} [data-a-target="chat-send-button"]:active, ${rcol} [data-a-target="chat-send-button"]:focus {box-shadow:0 0 6px 0 #787878!important} ` : ``} ${rcolBlur} a[data-a-target="chat-badge"] {opacity:${settings.badgeOpacity / 100};${settings.badgeOpacity === 0 ? `display:none!important;` : ``}} ${settings.hideBadgeTurbo ? ` ${rcol} .chat-badge[alt="Turbo"] {display:none} ` : ``} ${settings.hideBadgePrime ? ` ${rcol} .chat-badge[alt="Prime Gaming"] {display:none} ` : ``} ${settings.hideBadgeSubscriber ? ` ${rcol} .chat-badge[alt~="Subscriber"] {display:none} ` : ``} ${settings.hideBadgeVIP ? ` ${rcol} .chat-badge[alt="VIP"] {display:none} ` : ``} ${settings.hideBadgeSubGift ? ` ${rcol} .chat-badge[alt*="Gift Subs"] {display:none} ` : ``} ${settings.hideBadgeBitCheer ? ` ${rcol} .chat-badge[alt~="cheer"] {display:none} ` : ``} ${settings.hideBadgeLeader ? ` ${rcol} .chat-badge[alt*="Bits Leader"], ${rcol} .chat-badge[alt*="Gifter Leader"] {display:none} ` : ``} #chylex-ttc-settings-btn {margin-left:${settings.chatWidth - 50}px} `; document.head.appendChild(style); } function generateSettingsCSS(){ if (document.getElementById("chylex-ttc-style-settings")){ return; } const style = document.createElement("style"); style.id = "chylex-ttc-style-settings"; style.innerHTML = ` #chylex-ttc-settings-btn {display:none;width:2.5em;height:2.5em;position:absolute;bottom:105px;margin-left:290px;z-index:9;cursor:pointer;fill:rgba(255,255,255,0.6640625)} .video-chat #chylex-ttc-settings-btn {bottom:18px} #chylex-ttc-settings-btn svg {width:100%;height:100%} #chylex-ttc-settings-btn:hover {fill:#fff} .right-column--theatre:hover #chylex-ttc-settings-btn {display:block} #chylex-ttc-settings-modal {position:absolute;left:50%;top:50%;width:890px;height:296px;margin-left:-445px;margin-top:-148px;z-index:10000;background-color:rgba(17,17,17,0.796875)} #chylex-ttc-settings-modal-header {display:flex;justify-content:center;gap:12px;padding:14px 0 13px;background-color:rgba(0,0,0,0.59765625)} #chylex-ttc-settings-modal-header input {flex:0 0 auto} #chylex-ttc-settings-modal-header h2 {flex:0 0 auto;color:rgba(255,255,255,0.9296875);font-size:24px;margin:0} #chylex-ttc-settings-modal .ttc-flex-container {display:flex;flex-direction:row;justify-content:space-between;padding:8px 4px} #chylex-ttc-settings-modal p {color:rgba(255,255,255,0.86328125);font-size:14px;margin-top:8px;padding:0 9px} #chylex-ttc-settings-modal p:first-of-type {margin:0 0 4px} #chylex-ttc-settings-modal .player-menu__section {padding:0 12px 2px} #chylex-ttc-settings-modal .player-menu__header {margin-bottom:0;color:rgba(255,255,255,0.6640625)} #chylex-ttc-settings-modal .player-menu__item {display:flex;align-items:center;margin:2px 0 9px;padding-left:1px} #chylex-ttc-settings-modal .player-menu__item.ttc-setting-small-margin {margin-bottom:7px} #chylex-ttc-settings-modal .switch {font-size:10px;height:17px;line-height:17px} #chylex-ttc-settings-modal .switch span {width:27px} #chylex-ttc-settings-modal .switch.active span {left:25px} #chylex-ttc-settings-modal .switch::before, #chylex-ttc-settings-modal .switch::after {width:26px;box-sizing:border-box} #chylex-ttc-settings-modal .switch::before {text-align:left;padding-left:5px} #chylex-ttc-settings-modal .switch::after {text-align:right;padding-right:3px} #chylex-ttc-settings-modal input[type="text"] {width:100%;padding:1px 4px} #chylex-ttc-settings-modal input[type="range"] {width:100%} #chylex-ttc-settings-modal select {width:100%;padding:1px 0} #chylex-ttc-settings-modal output {color:rgba(255,255,255,0.796875);display:inline-block;flex:0 0 auto;padding-left:5px;text-align:right} #chylex-ttc-settings-modal .editable:hover {cursor:pointer;text-decoration:underline} `; document.head.appendChild(style); } // Filters function getNodeText(node){ if (node.nodeType === Node.TEXT_NODE){ return node.nodeValue; } if (node.nodeType === Node.ELEMENT_NODE){ if (node.tagName === "IMG"){ return node.getAttribute("alt") || ""; } else{ let text = ""; for(let child of node.childNodes){ text += getNodeText(child); } return text; } } return ""; } var filtersRegex = null; var filtersObserver = new MutationObserver(function(mutations){ for(let mutation of mutations){ for(let added of mutation.addedNodes){ let text; const classes = added.classList; if (classes.contains("chat-line__message")){ const nodes = Array.from(added.childNodes); const colon = nodes.findIndex(node => node.tagName === "SPAN" && node.innerText === ": "); text = nodes.slice(colon+1).map(getNodeText).join(""); } else{ text = getNodeText(added.querySelector(".qa-mod-message") || added); } if (filtersRegex.test(text)){ classes.add("hidden"); } } } }); function refreshChatFilters(){ const chat = document.querySelector(".chat-scrollable-area__message-container, .video-chat__message-list-wrapper ul"); if (!chat){ return false; } const filters = (settings.chatFilters || "").split(",").map(entry => entry.trim()).filter(entry => !!entry); if (filters.length === 0){ filtersRegex = null; } else{ const options = filters.map(entry => entry.replace(/[.+?^${}()|[\]\\]/g, "\\$&").replace(/\*/g, "(?:\\S*)").replace(/\s+/g, "\\s+")).join("|"); filtersRegex = new RegExp("(?:^|[^a-z0-9])(?:"+options+")(?:$|[^a-z0-9])", "i"); } if (filtersRegex && settings.globalSwitch){ filtersObserver.observe(chat, { childList: true }); } else{ filtersObserver.disconnect(); } return true; } // Helpers var classObserverCallback = function(mutations){ for(let mutation of mutations){ const classes = mutation.target.classList; if (classes.contains("right-column")){ document.body.classList.toggle("ttc-theatre", classes.contains("right-column--theatre")); document.body.classList.toggle("ttc-rcol-collapsed", classes.contains("right-column--collapsed")); } } }; var classObserver = new MutationObserver(classObserverCallback); function setupClassHelpers(){ const col = document.querySelector(".right-column"); if (!col){ return false; } classObserver.observe(col, { attributes: true, attributeFilter: [ "class" ] }); classObserverCallback([ { target: col } ]); return true; } function setupFullscreenHelper(){ for(let event of [ "fullscreenchange", "webkitfullscreenchange", "mozfullscreenchange", "msfullscreenchange" ]){ if ("on" + event in document){ document.addEventListener(event, function(){ document.body.classList.toggle("ttc-player-fullscreen", document.fullscreenElement || document.webkitFullscreenElement || document.mozFullScreenElement || document.msFullscreenElement); }); break; } } } // Settings function debounce(func, wait){ let timeout = -1; return function(){ window.clearTimeout(timeout); timeout = window.setTimeout(func, wait); }; } function createSettingsModal(){ tryRemoveElement(document.getElementById("chylex-ttc-settings-modal")); const generateOptionBase = function(title, item, extra){ extra = extra || {}; return `

${title}
${item}
`; }; const prepareOptionEvent = function(option, setupCallback){ window.setTimeout(function(){ const ele = document.getElementById("ttc-opt-" + option); setupCallback(ele); }, 1); }; const updateOption = function(option, value){ settings[option] = value; onSettingsUpdated(); }; // Concrete option types const generateToggle = function(title, option, floatLeft){ prepareOptionEvent(option, function(ele){ ele.addEventListener("click", function(){ updateOption(option, ele.checked); }); }); return generateOptionBase(title, `
`, floatLeft ? { floatLeft: true } : {}); }; const generateTxtbox = function(title, option, cfg){ prepareOptionEvent(option, function(ele){ ele.addEventListener("input", debounce(function(){ updateOption(option, ele.value); }, cfg.wait)); }); return generateOptionBase(title, ``); }; const generateSelect = function(title, option, cfg){ prepareOptionEvent(option, function(ele){ ele.addEventListener("input", function(){ updateOption(option, ele.value); }); }); const initialOption = settings[option]; const optionElements = Object.keys(cfg).map(function(key){ return ``; }); return generateOptionBase(title, ``); }; const generateSlider = function(title, option, cfg){ prepareOptionEvent(option, function(ele) { const regenerate = debounce(onSettingsUpdated, cfg.wait); function setSliderValue(value) { settings[option] = value; document.getElementById("ttc-optval-" + option).value = value + cfg.text; regenerate(); } ele.addEventListener("input", function() { setSliderValue(ele.value, parseInt(ele.value, 10)); }); if (cfg.editable) { ele.nextElementSibling.classList.add("editable"); ele.nextElementSibling.addEventListener("click", function() { let customValue = prompt("Set custom value:", settings[option]); if (customValue === null) { return; } customValue = customValue.trim(); if (customValue.endsWith(cfg.text)) { customValue = customValue.slice(0, -cfg.text.length).trim(); } if (/^\d+$/.test(customValue) === false) { alert("Invalid value."); return; } setSliderValue(parseInt(customValue, 10)); ele.value = customValue; }); } }); return generateOptionBase(title, ` ${settings[option]}${cfg.text} `, { itemClasses: "ttc-setting-small-margin" }); }; // Generate modal const modal = document.createElement("div"); modal.id = "chylex-ttc-settings-modal"; modal.innerHTML = `

Transparent Twitch Chat

General

${generateSlider("Chat Width", "chatWidth", { min: 250, max: 800, step: 25, wait: 500, width: 48, text: "px", editable: true })} ${generateTxtbox("Chat Filters", "chatFilters", { wait: 500, placeholder: "Example: kappa, *abc*" })} ${generateSelect("Player Position", "playerPosition", { "#opposite-chat": "Opposite of Chat", "top left": "Top Left", "top center": "Top Center", "top right": "Top Right", "center left": "Center Left", "center center": "Center", "center right": "Center Right", "bottom left": "Bottom Left", "bottom center": "Bottom Center", "bottom right": "Bottom Right" })} ${generateToggle("Hide Timestamps", "hideTimestamps", true)} ${generateToggle("Gray Theme", "grayTheme")}

Transparency

${generateToggle("Transparent Chat", "transparentChat")} ${generateToggle("Smooth Text Shadow", "smoothTextShadow")} ${generateToggle("Chat on Left Side", "chatLeftSide")} ${generateSlider("Background Opacity", "backgroundOpacity", { min: 0, max: 100, step: 5, wait: 100, width: 42, text: "%" })}

Elements

${generateToggle("Hide Chat Header", "hideHeader")} ${generateToggle("Hide Chat Input", "hideChatInput")} ${generateToggle("Hide Pinned Cheer", "hidePinnedCheer")} ${generateToggle("Hide Whispers", "hideConversations")}

Badges

${generateToggle("Hide Subscriber Badge", "hideBadgeSubscriber")} ${generateToggle("Hide Prime Badge", "hideBadgePrime")} ${generateToggle("Hide Turbo Badge", "hideBadgeTurbo")} ${generateToggle("Hide VIP Badge", "hideBadgeVIP")}

Badges

${generateToggle("Hide Sub Gift Badge", "hideBadgeSubGift")} ${generateToggle("Hide Bit Cheer Badge", "hideBadgeBitCheer")} ${generateToggle("Hide Gift/Bit Leader Badge", "hideBadgeLeader")} ${generateSlider("Badge Opacity", "badgeOpacity", { min: 0, max: 100, step: 5, wait: 100, width: 42, text: "%" })}
`; document.body.appendChild(modal); document.getElementById("ttc-opt-global").addEventListener("click", function(e){ settings.globalSwitch = e.currentTarget.checked; onSettingsUpdated(); }); modal.addEventListener("click", function(e){ e.stopPropagation(); }); } function insertSettingsButton(){ const container = document.querySelector("[data-test-selector='chat-room-component-layout'] > div, .video-chat"); if (!container){ return false; } container.classList.add("chylex-ttc-chat-container"); tryRemoveElement(document.getElementById("chylex-ttc-settings-btn")); tryRemoveElement(document.getElementById("chylex-ttc-settings-modal")); const button = document.createElement("div"); button.id = "chylex-ttc-settings-btn"; button.innerHTML = ''; container.appendChild(button); button.addEventListener("click", function(e){ if (!document.getElementById("chylex-ttc-settings-modal")){ createSettingsModal(); e.stopPropagation(); } }); if (isFirefox && container.classList.contains("video-chat")){ const wrapper = document.querySelector(".video-chat__message-list-wrapper"); const unsynced = "video-chat__message-list-wrapper--unsynced"; wrapper.addEventListener("wheel", function(e){ if (e.deltaY < 0){ wrapper.classList.add(unsynced); } }); wrapper.addEventListener("keydown", function(e){ if (e.keyCode === 38 || e.keyCode === 33){ // up arrow || page up wrapper.classList.add(unsynced); } }); } return true; } // Setup var prevAddress = null; var rehookInterval = null; window.setInterval(function(){ if (location.href != prevAddress){ prevAddress = location.href; var hooks = [ refreshChatFilters, setupClassHelpers, insertSettingsButton ]; window.clearInterval(rehookInterval); rehookInterval = window.setInterval(function(){ for(let index = hooks.length - 1; index >= 0; index--){ if (hooks[index]()){ hooks.splice(index, 1); } } if (hooks.length === 0){ window.clearInterval(rehookInterval); rehookInterval = null; } }, 250); } }, 1000); document.body.addEventListener("click", function(){ tryRemoveElement(document.getElementById("chylex-ttc-settings-modal")); }); setupFullscreenHelper(); generateSettingsCSS(); generateCustomCSS();