Skip to content

Instantly share code, notes, and snippets.

@lbmaian
Last active April 17, 2024 11:41
Show Gist options
  • Star 15 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save lbmaian/94824cef728917a53d3c6e6ea885469c to your computer and use it in GitHub Desktop.
Save lbmaian/94824cef728917a53d3c6e6ea885469c to your computer and use it in GitHub Desktop.
YouTube - Hide Live Chat By Default
// ==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);
}
});
})();
@lbmaian
Copy link
Author

lbmaian commented Feb 8, 2022

Continuing off from https://gist.github.com/LazyMammal/1c60c45e9df26602f688d025f3b20f0c?permalink_comment_id=3656586#gistcomment-3656586

Changes in new version (0.5): use onurlchange hook if available instead of polling for url changes, logging stuff

@lbmaian
Copy link
Author

lbmaian commented Jul 19, 2022

0.7: handle live chat button being in shadow DOM, add updateURL/downloadURL
(also revert change in 0.6/0.6.5 that waited until page was visible before polling)

@lbmaian
Copy link
Author

lbmaian commented Aug 21, 2022

0.7.1: support yet another new YT watch page layout

@lbmaian
Copy link
Author

lbmaian commented Nov 2, 2022

0.7.3: retry with exponential backoff, ignore playlist watch pages

@lbmaian
Copy link
Author

lbmaian commented Dec 5, 2022

0.8: better way of monitoring for navigation to watch pages, prep work for moving toward a non-polling approach

@lbmaian
Copy link
Author

lbmaian commented Dec 18, 2022

0.9: revamped to a non-polling approach to find live chat, and a faster method to toggle live chat

@lbmaian
Copy link
Author

lbmaian commented Dec 19, 2022

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

@lbmaian
Copy link
Author

lbmaian commented Dec 20, 2022

0.10: revamped to now work with internal navigation b/w watch pages (with some edge cases that are prob YT bugs)

@lbmaian
Copy link
Author

lbmaian commented Dec 22, 2022

0.11.1: revamped again with a new method that should work on any YT page regardless of internal navigation

@lbmaian
Copy link
Author

lbmaian commented Dec 23, 2022

0.11.5: simplify some logging, fix button text edge case

@lbmaian
Copy link
Author

lbmaian commented Dec 23, 2022

0.12: found a simpler way to accomplish the same thing (without requiring observing for chat element or hooking into an internal method)

@lbmaian
Copy link
Author

lbmaian commented Feb 3, 2023

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

@lbmaian
Copy link
Author

lbmaian commented Dec 8, 2023

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment