-
-
Save lbmaian/94824cef728917a53d3c6e6ea885469c to your computer and use it in GitHub Desktop.
// ==UserScript== | |
// @name YouTube - Hide Live Chat By Default | |
// @namespace https://gist.github.com/lbmaian/94824cef728917a53d3c6e6ea885469c | |
// @downloadURL https://gist.github.com/lbmaian/94824cef728917a53d3c6e6ea885469c/raw/youtube-hide-livechat.user.js | |
// @updateURL https://gist.github.com/lbmaian/94824cef728917a53d3c6e6ea885469c/raw/youtube-hide-livechat.user.js | |
// @version 0.14 | |
// @description Hide live chat by default on live streams | |
// @author lbmaian | |
// @match https://www.youtube.com/* | |
// @exclude https://www.youtube.com/embed/* | |
// @icon https://www.youtube.com/favicon.ico | |
// @run-at document-start | |
// @grant none | |
// ==/UserScript== | |
(function() { | |
'use strict'; | |
const DEBUG = false; | |
const logContext = '[YouTube - Hide Live Chat]'; | |
var debug; | |
if (DEBUG) { | |
debug = function(...args) { | |
console.debug(logContext, ...args); | |
} | |
} else { | |
debug = function(...args) {} | |
} | |
function log(...args) { | |
console.log(logContext, ...args); | |
} | |
function warn(...args) { | |
console.warn(logContext, ...args); | |
} | |
function error(...args) { | |
console.error(logContext, ...args); | |
} | |
// Note: Following all relies on YT internals. | |
function updateChatData(data, collapsed) { | |
if (DEBUG) { | |
debug('data (before)', window.structuredClone(data)); | |
} | |
const liveChatRenderer = data.liveChatRenderer; | |
if (liveChatRenderer) { // if no live chat despite #chat existing, e.g. "Live chat replay is not available for this video." | |
const expandedByDefault = liveChatRenderer.initialDisplayState === 'LIVE_CHAT_DISPLAY_STATE_EXPANDED'; | |
if (expandedByDefault && collapsed) { | |
if (collapsed) { | |
log('hiding live chat'); | |
} | |
debug('data.liveChatRenderer.initialDisplayState:', liveChatRenderer.initialDisplayState, | |
'=>', 'LIVE_CHAT_DISPLAY_STATE_COLLAPSED'); | |
liveChatRenderer.initialDisplayState = 'LIVE_CHAT_DISPLAY_STATE_COLLAPSED'; | |
} | |
const toggleButtonRenderer = liveChatRenderer.showHideButton?.toggleButtonRenderer; | |
if (toggleButtonRenderer) { | |
if (expandedByDefault) { | |
debug('data.liveChatRenderer.showHideButton.toggleButtonRenderer.defaultText/toggledText swapped'); | |
[toggleButtonRenderer.defaultText, toggleButtonRenderer.toggledText] = | |
[toggleButtonRenderer.toggledText, toggleButtonRenderer.defaultText]; | |
} | |
const isToggled = !collapsed; | |
if (DEBUG && toggleButtonRenderer.isToggled !== isToggled) { | |
debug('data.liveChatRenderer.showHideButton.toggleButtonRenderer.isToggled', toggleButtonRenderer.isToggled, | |
'=>', isToggled); | |
} | |
toggleButtonRenderer.isToggled = isToggled; | |
} | |
if (DEBUG) { | |
debug('data (updated)', window.structuredClone(data)); | |
} | |
return expandedByDefault; | |
} else { | |
return false; | |
} | |
} | |
// Navigating to YouTube watch page can happen via AJAX rather than new page load. | |
// We can monitor this with YT's custom yt-page-data-fetched event, | |
// which conveniently also fires even for new/refreshed pages. | |
// yt-navigate-finish would also work (evt.detail.detail) but yt-page-data-fetched fires earlier. | |
document.addEventListener('yt-page-data-fetched', evt => { | |
debug('Navigated to', evt.detail.pageData.url); | |
debug(evt); | |
const conversationBar = evt.detail.pageData.response?.contents?.twoColumnWatchNextResults?.conversationBar; | |
debug('yt-page-data-fetched pageData.response contents.twoColumnWatchNextResults.conversationBar (corresponds to #chat.data)', | |
conversationBar); | |
// If response doesn't include conversationBar, there won't be a #chat element at all. | |
if (conversationBar) { | |
// If #chat element isn't created yet, default collapsed to true. | |
// Else keep current collapsed status between pages. | |
// TODO: sometimes for new pages when chat doesn't exist yet, this apparently happens too late? | |
// (chat already initialized with old data) and chat thus remains open? | |
// Detect & fix this - use chat.parentComponent (ytd-watch-flexy)'s updatePageData_ or ytd-app's onYtPageDataFetched? | |
const chat = document.getElementById('chat'); | |
let collapsed; | |
if (chat) { | |
collapsed = chat.collapsed; | |
log('existing #chat', chat, 'collapsed:', collapsed); | |
} else { | |
log('no existing #chat, defaulting collapsed: true'); | |
collapsed = true; | |
} | |
updateChatData(conversationBar, collapsed); | |
} | |
}); | |
})(); |
0.10: revamped to now work with internal navigation b/w watch pages (with some edge cases that are prob YT bugs)
0.11.1: revamped again with a new method that should work on any YT page regardless of internal navigation
0.11.5: simplify some logging, fix button text edge case
0.12: found a simpler way to accomplish the same thing (without requiring observing for chat element or hooking into an internal method)
0.13: use a different event that fires a bit earlier
Also some comment/logging to try to investigate the rare case of chat not being hidden when starting from a page without chat (e.g. home page) then internally navigating a page with chat (watch page)?
Also changed the script name, so it may not auto-update and instead require uninstallation + reinstallation
014: Update to handle a new experimental YT change that replaces the "Show chat"/"Hide chat" button toggle with a "Show chat replay" button plus an "X" button in the top-right of the chat box
It's crazy that you have to go to such lengths just to close the forced live chat. I gave up figuring it out myself, thanks.
0.9.1: more reliable method to toggle live chat
edit: Still not working reliably and I think I figured out what's probably going on: the #chat element is being reused when internally navigating to another watch page, which can toggle the chat again after the collapsed check...
which also explains why the old polling-based method sometimes worked since the collapsed check could happen after the toggling