YouTube Hide Chat by Default

Hides chat on YouTube live streams by default

θα χρειαστεί να εγκαταστήσετε μια επέκταση όπως το Tampermonkey, το Greasemonkey ή το Violentmonkey για να εγκαταστήσετε αυτόν τον κώδικα.

θα χρειαστεί να εγκαταστήσετε μια επέκταση όπως το Tampermonkey ή το Violentmonkey για να εγκαταστήσετε αυτόν τον κώδικα.

θα χρειαστεί να εγκαταστήσετε μια επέκταση όπως το Tampermonkey ή το Violentmonkey για να εγκαταστήσετε αυτόν τον κώδικα.

θα χρειαστεί να εγκαταστήσετε μια επέκταση όπως το Tampermonkey ή το Userscripts για να εγκαταστήσετε αυτόν τον κώδικα.

You will need to install an extension such as Tampermonkey to install this script.

Θα χρειαστεί να εγκαταστήσετε μια επέκταση διαχείρισης κώδικα χρήστη για να εγκαταστήσετε αυτόν τον κώδικα.

(Έχω ήδη έναν διαχειριστή κώδικα χρήστη, επιτρέψτε μου να τον εγκαταστήσω!)

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

(Έχω ήδη έναν διαχειριστή στυλ χρήστη, επιτρέψτε μου να τον εγκαταστήσω!)

// ==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);
})();