YouTube Hide Chat by Default

Hides chat on YouTube live streams by default

Чтобы установить этот скрипт, вы сначала должны установить расширение браузера, например Tampermonkey, Greasemonkey или Violentmonkey.

Чтобы установить этот скрипт, вы сначала должны установить расширение браузера, например Tampermonkey или Violentmonkey.

Чтобы установить этот скрипт, вы сначала должны установить расширение браузера, например Tampermonkey или Violentmonkey.

Чтобы установить этот скрипт, вы сначала должны установить расширение браузера, например Tampermonkey или Userscripts.

Чтобы установить этот скрипт, сначала вы должны установить расширение браузера, например Tampermonkey.

Чтобы установить этот скрипт, вы должны установить расширение — менеджер скриптов.

(у меня уже есть менеджер скриптов, дайте мне установить скрипт!)

Чтобы установить этот стиль, сначала вы должны установить расширение браузера, например Stylus.

Чтобы установить этот стиль, сначала вы должны установить расширение браузера, например Stylus.

Чтобы установить этот стиль, сначала вы должны установить расширение браузера, например Stylus.

Чтобы установить этот стиль, сначала вы должны установить расширение — менеджер стилей.

Чтобы установить этот стиль, сначала вы должны установить расширение — менеджер стилей.

Чтобы установить этот стиль, сначала вы должны установить расширение — менеджер стилей.

(у меня уже есть менеджер стилей, дайте мне установить скрипт!)

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