YouTube Hide Chat by Default

Hides chat on YouTube live streams by default

目前为 2022-11-02 提交的版本。查看 最新版本

// ==UserScript==
// @name         YouTube Hide Chat by Default
// @namespace    https://skoshy.com
// @version      0.6.1
// @description  Hides chat on YouTube live streams by default
// @author       Stefan K.
// @match        https://*.youtube.com/*
// @grant        none
// @icon         https://youtube.com/favicon.ico
// ==/UserScript==

(function() {
  "use strict";
  const scriptId = "youtube-hide-chat-by-default";

  // configurable vars
  // - if youtube decides to use a new button type, add it here
  const buttonSelectors = ["button"];
  // - for different languages for the HIDE CHAT text, add them here
  const hideChatTexts = [
    'HIDE CHAT', // english
    'HIDE CHAT REPLAY', // english, chat replay
    'OCULTAR CHAT', // spanish
    'MASQUER LA CONVERSATION PAR CHAT', // french
    'MASQUER LE CLAVARDAGE', // french canada
    'NASCONDI CHAT', // italian
    'OCULTAR CONVERSA', // portuguese
    'চ্চাট লুকুৱাওক', // bengali
    'च्याट लुकाउनुहोस्', // nepali
    'चैट छिपाएं', // hindi
    'チャットを非表示', // japanese
    '隐藏聊天记录', // zh-Hans-CN
    '隱藏即時通訊', // zh-Hant-TW and zh-Hant-HK
  ];

  const nodeIdString = `${scriptId}-id`;

  function log(...toLog) {
    console.log(`[${scriptId}]:`, ...toLog);
  }

  function getUrlSearchParams() {
    return new URLSearchParams(document.location.search.substring(1));
  }

  function isHideChatButton(node) {
    return hideChatTexts.includes(node.innerText.toUpperCase().trim());
  }

  function getNodeId(node) {
    let nodeId = node.getAttribute(nodeIdString);

    return nodeId;
  }

  function setAndGetNodeId(node, setIdTo) {
    const originalNodeId = getNodeId(node);

    const nodeId = setIdTo ? setIdTo : Math.random().toString();
    node.setAttribute(nodeIdString, nodeId);

    return { originalNodeId, nodeId };
  }

  function addedNodeHandler(node) {
    if (
      !node.matches ||
      !buttonSelectors.some(b => node.matches(b))
    ) {
      return;
    }

    if (isHideChatButton(node)) {
      log(`Found a hide-chat button`, node);

      const { nodeId, originalNodeId } = setAndGetNodeId(node, getUrlSearchParams().get('v'));

      if (originalNodeId === nodeId) return; // we've already automatically triggered hide chat

      log(`Hid the chat by default`, document.querySelector('yt-live-chat-message-input-renderer'));

      const tryToHideChatDetails = {
        interval: null,
        triedAttempts: 0,
      };

      tryToHideChatDetails.interval = setInterval(() => {
        tryToHideChatDetails.triedAttempts++;

        if (document.querySelector('ytd-live-chat-frame iframe')?.offsetHeight ?? 0 > 0) {
          log('Clicking again because live chat still there');
          node.click();
          tryToHideChatDetails.triedAttempts = 0;
        }

        if (tryToHideChatDetails.triedAttempts > 6) {
          clearInterval(tryToHideChatDetails.interval);
        }
      }, 250);

      return;
    }
  }

  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) {
          buttonSelectors.forEach(bs => {
            addedNode.querySelectorAll(bs).forEach((n) => {
              newNodes.push(n);
            });
          });
        }
      });

      newNodes.forEach(n => addedNodeHandler(n));
    });
  });

  setInterval(() =>
    Array.from(
      document.querySelectorAll(buttonSelectors.join(', '))
    ).forEach(n => addedNodeHandler(n))
  , 3000);

  bodyObserver.observe(document.body, {
    attributes: true,
    childList: true,
    subtree: true,
    characterData: true
  });
})();