// ==UserScript==
// @name YouTube Hide Chat by Default
// @namespace https://skoshy.com
// @version 0.8.0
// @description Hides chat on YouTube live streams by default
// @author Stefan K.
// @match https://www.youtube.com/*
// @grant GM.getValue
// @grant GM.setValue
// @icon https://youtube.com/favicon.ico
// ==/UserScript==
const scriptId = "youtube-hide-chat-by-default";
const CHANNELS_BLOCKLIST = [
// you can place channel IDs here to block them from hiding their chat automatically
// example: 'UCTSCjjnCuAPHcfQWNNvULTw'
];
const isInIframe = () => window.top !== window.self;
const UNIQUE_ID = (function getUniqueId() {
if (isInIframe()) {
const capturedUniqueId = new URL(window.location.href).searchParams.get(`${scriptId}-unique-id`);
if (!capturedUniqueId) {
throw new Error(`Unique ID was not properly passed to iFrame: ${window.location.href}`);
}
log('Running in an iFrame, grabbed unique ID from URL', capturedUniqueId, window.location.href);
return capturedUniqueId;
}
return Math.floor(Math.random()*1000000);
})();
function log(...toLog) {
console.log(`[${scriptId}]:`, ...toLog);
}
const StorageClass = (scriptId, uniqueId, allowedKeys) => {
(async function updateSubStorageIds() {
const subStorageKey = `${scriptId}_base_subStorageIds`;
const subStorageIds = JSON.parse((await GM.getValue(subStorageKey)) || '{}');
console.log({subStorageIds});
await GM.setValue(subStorageKey, JSON.stringify({
...subStorageIds,
[uniqueId]: {
dateCreated: Date.now(),
},
}));
const newSubStorageIds = (await GM.getValue(subStorageKey)) || {};
console.log('Set the value for subStorageIds', newSubStorageIds);
})();
const setVal = async (key, val) => {
if (!allowedKeys.includes(key)) {
throw new Error('Key not allowed');
}
await GM.setValue(`${scriptId}_${uniqueId}_${key}`, val);
}
const getVal = async (key) => {
if (!allowedKeys.includes(key)) {
throw new Error('Key not allowed');
}
return GM.getValue(`${scriptId}_${uniqueId}_${key}`);
};
return { setVal, getVal };
};
const { setVal, getVal } = StorageClass(scriptId, UNIQUE_ID, ['lastVidThatHidChat']);
(function() {
"use strict";
// - if youtube decides to use a new button type, add it here
const buttonSelectors = ["button"];
const mutationObserverSelectors = [...buttonSelectors, 'iframe'];
function getRootUrlSearchParams() {
return new URL(window.location.href).searchParams;
}
function getCurrentVideoId() {
const v = getRootUrlSearchParams().get('v');
if (v) {
log('Got Video ID from URL Search Params', v);
return v;
}
if (isInIframe()) {
// if not the parent frame, then get it from the passed in iframe url params
const passedThroughId = getRootUrlSearchParams().get(`${scriptId}-current-video-id`);
log('Not parent frame, getting video ID from passed through ID', passedThroughId);
return passedThroughId;
}
return null;
}
function getCurrentVideoChannelId() {
const channelId = document.querySelector('a[aria-label="About"][href*="channel/"]')?.getAttribute('href')?.match(/\/channel\/(.+)[\/$]/)?.[1];
if (channelId) {
return channelId;
}
if (isInIframe()) {
// if not the parent frame, then get it from the passed in iframe url params
const passedThroughId = getRootUrlSearchParams().get(`${scriptId}-current-channel-id`);
log('Not parent frame, getting channel ID from passed through ID', passedThroughId);
if (passedThroughId === 'null' || !passedThroughId) {
log('ERROR: There\'s a problem parsing the Channel ID, blocklist functionality will not work', passedThroughId);
return null;
}
return passedThroughId;
}
return null;
}
function findAncestorOfElement(el, findFunc) {
let currentEl = el;
while (currentEl?.parentElement) {
const result = findFunc(currentEl.parentElement);
if (result) {
return currentEl.parentElement;
}
currentEl = currentEl.parentElement;
}
return undefined;
}
function isHideChatButton(node) {
const youtubeLiveChatAppAncestor = findAncestorOfElement(node, (parentEl) => {
return parentEl.tagName === 'YT-LIVE-CHAT-APP';
});
if (!youtubeLiveChatAppAncestor) {
return false;
}
return (node.getAttribute('aria-label') === 'Close');
}
function addedNodeHandler(node) {
if (!node.matches) return;
if (node.matches('iframe')) {
handleAddedIframe(node);
return;
}
if (
!buttonSelectors.some(b => node.matches(b))
) {
return;
}
if (isHideChatButton(node)) {
log(`Found a hide-chat button`, node);
const currentVid = getCurrentVideoId();
const currentChannelId = getCurrentVideoChannelId();
const lastVidThatHidChat = getVal('lastVidThatHidChat');
if (lastVidThatHidChat === currentVid) {
log(`Already automatically triggered to hide chat for this video`, { lastVidThatHidChat, currentVid, currentChannelId });
return;
}
if (CHANNELS_BLOCKLIST.includes(currentChannelId)) {
log(`Channel in blocklist`, { lastVidThatHidChat, currentVid, currentChannelId });
return;
}
log(`Attempting to hide the chat by default`, { lastVidThatHidChat, currentVid, currentChannelId });
setVal('lastVidThatHidChat', currentVid);
node.click();
}
}
function handleAddedIframe(node) {
if (node.getAttribute(`${scriptId}-modified-src`)) {
return;
}
const url = new URL(node.src);
url.searchParams.set(`${scriptId}-unique-id`, UNIQUE_ID);
url.searchParams.set(`${scriptId}-current-video-id`, getCurrentVideoId());
url.searchParams.set(`${scriptId}-current-channel-id`, getCurrentVideoChannelId());
log('New iFrame URL', url.toString());
node.src = url.toString();
node.setAttribute(`${scriptId}-modified-src`, true);
}
/*
const bodyObserver = new MutationObserver(function(mutations) {
mutations.forEach(function(mutation) {
const newNodes = [];
mutation.addedNodes.forEach(addedNode => {
newNodes.push(addedNode);
// it might be text node or comment node which don't have querySelectorAll
if (addedNode.querySelectorAll) {
mutationObserverSelectors.forEach(bs => {
addedNode.querySelectorAll(bs).forEach((n) => {
newNodes.push(n);
});
});
}
});
newNodes.forEach(n => addedNodeHandler(n));
});
});
*/
setInterval(() =>
Array.from(
document.querySelectorAll(mutationObserverSelectors.join(', '))
).forEach(n => addedNodeHandler(n))
, 3000);
/*
bodyObserver.observe(document, {
attributes: true,
childList: true,
subtree: true,
characterData: true
});
*/
log('Initialized', UNIQUE_ID, window.location.href);
})();