Jump to content

Recommended Posts


Posted

I rarely (if ever) use YouTube these days.  But these are all of the userscripts and userstyles I was using.

A couple of these may need updated, not really sure, but the all seemed to work as of a few weeks ago when I last visited YouTube.

Posted

YouTube 01 - Autoplay, CC, Annotations, Stable Volume, Ambient Mode, Home

// ==UserScript==
// @name - YouTube 01 - Autoplay, CC, Annotations, Stable Volume, Ambient Mode, Home
// @version 1.0~1.0~1.1
// @match https://www.youtube.com/*
// @match https://m.youtube.com/*
// @match https://www.youtube-nocookie.com/*
// @match https://music.youtube.com/*
// @grant none
// @run-at document-start
// ==/UserScript==

// start with Playlist Autoplay ON
let ytintervalPA = setInterval(() => {
    var ytplayerPA = document.getElementById("movie_player");
    if(ytplayerPA.getCurrentTime() <= 5) {
        if (document.querySelector('[title="Autoplay is off"]').getAttribute('aria-pressed') !== 'false') {
            document.querySelector('[title="Autoplay is off"]').click();
        }
    }
}, 1000);

// start with Closed Captions OFF
let ytintervalCC = setInterval(() => {
    var ytplayerCC = document.getElementById("movie_player");
    if(ytplayerCC.getCurrentTime() <= 5) {
        if (document.querySelector('[aria-keyshortcuts="c"]').getAttribute('aria-pressed') !== 'false') {
            document.querySelector('[aria-keyshortcuts="c"]').click();
        }
    }
}, 1000);

// start with Annotations, Stable Volume, and Ambient Mode OFF
(function TurnOFF(){
    try {
        var ytplayer = document.getElementById("movie_player");

        if(ytplayer.getCurrentTime() <= 5) {

            var settings_button = document.querySelector(".ytp-settings-button");
            settings_button.click(); settings_button.click();

            var all_labels = document.getElementsByClassName("ytp-menuitem-label");
            for (var i = 0; i < all_labels.length; i++) {

                if ((all_labels.innerHTML == "Annotations") && (all_labels.parentNode.getAttribute("aria-checked") == "true")) {
                    all_labels.click(); }

                if ((all_labels.innerHTML == "Stable Volume") && (all_labels.parentNode.getAttribute("aria-checked") == "true")) {
                    all_labels.click(); }

                if ((all_labels.innerHTML == "Ambient mode") && (all_labels.parentNode.getAttribute("aria-checked") == "true")) {
                    all_labels.click(); }

            }
        }
    } catch (e) {}
    setTimeout(TurnOFF,2500);
})();

// clean homepage
var st=document.createElement("STYLE");
st.textContent="[page-subtype=home],[tab-identifier=FEwhat_to_watch]{display: none}";
document.getElementsByTagName("HEAD")[0].appendChild(st);

Posted

YouTube 02 - Workaround and H264ify

// ==UserScript==
// @name - YouTube 02 - Workaround and H264ify
// @version 2025-02-27
// @match https://www.youtube.com/*
// @match *://*.youtube.com/*
// @match *://*.youtube-nocookie.com/*
// @match *://*.youtu.be/*
// @run-at document-start
// @grant none
// ==/UserScript==

(function () {
    "use strict";

    const h264ifyEnable = true;
    const h264ifyBlock60fps = false;

    const originalGetContext = HTMLCanvasElement.prototype.getContext;
    HTMLCanvasElement.prototype.getContext = function (contextType) {
        if (contextType === "webgl" || contextType === "webgl2") {
            console.log("WebGL is disabled by Tampermonkey");
            return null;
        }
        return originalGetContext.apply(this, arguments);
    };

    if (!h264ifyEnable) {
        return;
    }

    function override() {
        var videoElem = document.createElement('video');
        var origCanPlayType = videoElem.canPlayType.bind(videoElem);
        videoElem.__proto__.canPlayType = makeModifiedTypeChecker(origCanPlayType);

        var mse = window.MediaSource;
        if (mse === undefined) return;
        var origIsTypeSupported = mse.isTypeSupported.bind(mse);
        mse.isTypeSupported = makeModifiedTypeChecker(origIsTypeSupported);
    }

    function makeModifiedTypeChecker(origChecker) {
        return function (type) {
            if (!type) return '';

            var disallowed_types = ['webm', 'vp8', 'vp9', 'av01'];
            for (var i = 0; i < disallowed_types.length; i++) {
                if (type.includes(disallowed_types)) return '';
            }

            if (h264ifyBlock60fps) {
                var match = /framerate=(\d+)/.exec(type);
                if (match && parseInt(match[1], 10) > 30) return '';
            }

            return origChecker(type);
        };
    }

    override();
})();

Posted

YouTube 03 - Time Indicators

// ==UserScript==
// @name - YouTube 03 - Time Indicators
// @version 2.3
// @match https://www.youtube.com/*
// @grant none
// ==/UserScript==

(function() {
    'use strict';
    let timeDisplay;
    let lastRenderedText = '';
    let showEndTime = localStorage.getItem('yt-player-remaining-time-mode') === 'true';

    function createTimeDisplayElement() {
        const timeDisplayElement = document.createElement('span');

        timeDisplayElement.style.display = 'inline-block';
        timeDisplayElement.style.marginLeft = '10px';
        timeDisplayElement.style.color = '#ddd';
        timeDisplayElement.style.cursor = 'pointer';
        timeDisplayElement.title = 'Click to toggle between remaining time and end time';

        timeDisplayElement.addEventListener('click', () => {
            event.stopPropagation();
            showEndTime = !showEndTime;
            localStorage.setItem('yt-player-remaining-time-mode', showEndTime);
        });

        return timeDisplayElement;
    }

    function formatTimeDisplay(videoElement) {
        if (showEndTime) {
            const endTime = new Date(Date.now() + (videoElement.duration - videoElement.currentTime) * 1000 / videoElement.playbackRate);
            return `(${endTime.toLocaleTimeString([], { hour: 'numeric', minute: '2-digit' })})`;
        } else {
            const timeRemaining = (videoElement.duration - videoElement.currentTime) / videoElement.playbackRate;
            const hours = Math.floor(timeRemaining / 3600);
            const minutes = Math.floor((timeRemaining % 3600) / 60);
            const seconds = Math.floor(timeRemaining % 60);
            return `(${hours > 0 ? `${hours}:` : ''}${minutes.toString().padStart(2, '0')}:${seconds.toString().padStart(2, '0')})`;
        }
    }

    function displayRemainingTime() {
        const videoElement = document.querySelector('video');
        const isLive = document.querySelector('.ytp-time-display')?.classList.contains('ytp-live');
        const miniplayerUI = document.querySelector('.ytp-miniplayer-ui');
        const isMiniplayerVisible = miniplayerUI && getComputedStyle(miniplayerUI).display !== 'none';
        const currentTime = videoElement?.currentTime;
        const timeContainer = document.querySelector(
            isMiniplayerVisible ? '.ytp-miniplayer-ui .ytp-time-contents' : '.ytp-chrome-controls .ytp-time-contents'
        );

        if (!videoElement || isLive || !timeContainer || isNaN(videoElement.duration)) {
            if (timeDisplay) {
                timeDisplay.remove();
                timeDisplay = null;
            }
            requestAnimationFrame(displayRemainingTime);
            return;
        }

        if (!timeDisplay) {
            timeDisplay = createTimeDisplayElement();
            timeContainer.appendChild(timeDisplay);
        }

        if (!timeContainer.contains(timeDisplay)) {
            timeDisplay.remove();
            timeContainer.appendChild(timeDisplay);
        }

        const text = formatTimeDisplay(videoElement);

        if (text !== lastRenderedText) {
            timeDisplay.textContent = text;
            lastRenderedText = text;
        }

        requestAnimationFrame(displayRemainingTime);
    }

    function initRemainingCounter() {
        const timeContainer = document.querySelector('.ytp-time-contents');

        if (timeContainer) {
            timeDisplay = createTimeDisplayElement();
            timeContainer.appendChild(timeDisplay);
            requestAnimationFrame(displayRemainingTime);
            observer.disconnect();
        }
    }

    function checkVideoExists() {
        const videoElement = document.querySelector('video');

        if (videoElement) {
            initRemainingCounter();
        }
    }

    const observer = new MutationObserver(checkVideoExists);
    observer.observe(document.body, { childList: true, subtree: true });
})();

Posted

YouTube 04 - Remove FS Title, Chat/Comments, Provider/Rating, Ticket Shelf

// ==UserScript==
// @name - YouTube 04 - Remove FS Title, Chat/Comments, Provider/Rating, Ticket Shelf
// @version 1.0.1
// @match https://*.youtube.com/*
// @grant none
// ==/UserScript==

(function() {
    'use strict';

    const removeCommentsSection = () => {
        // Select the comments section by class
        const commentsSection = document.querySelector('.ytd-comments, #chat, #bottom-row, #always-shown, #ticket-shelf, .ytPlayerOverlayVideoDetailsRendererHost, .ytp-overlay-inline-container, .yt-notification-action-renderer');
        if (commentsSection) {
            commentsSection.parentNode.remove();
        }
    };

    removeCommentsSection();

    // Observe DOM changes to handle dynamically loaded content
    const observer = new MutationObserver(() => {
        removeCommentsSection();
    });

    observer.observe(document.body, {
        childList: true,
        subtree: true
    });
})();

Posted

YouTube 05 - Fullscreen Scroll Disabler

// ==UserScript==
// @name - YouTube 05 - Fullscreen Scroll Disabler
// @version 1.0.2
// @match *://www.youtube.com/*
// @grant none
// ==/UserScript==

(function() {
    'use strict';

    // Store references to hidden elements and their previous display values
    let hiddenElements = [];

    // Store previous overflow values so we can restore them
    let previousOverflow = { html: '', body: '' };
    let styleElementId = 'ytfs-scrollblocker-style';
    let listenersAdded = false;

    // Event listeners for fullscreen change
    document.addEventListener('fullscreenchange', toggleScrollAndContent, false);
    document.addEventListener('webkitfullscreenchange', toggleScrollAndContent, false); // Older WebKit
    document.addEventListener('mozfullscreenchange', toggleScrollAndContent, false);    // Firefox

    // Also handle when the user presses F (YouTube's fullscreen toggle) or when the player switches programmatically.
    // We'll run the toggle function once on script load to handle pages that already are fullscreen (rare).
    setTimeout(toggleScrollAndContent, 500);

    function toggleScrollAndContent() {
        if (isFullScreen()) {
            enterFullscreenMode();
        } else {
            exitFullscreenMode();
        }
    }

    function isFullScreen() {
        return !!(document.fullscreenElement || document.webkitFullscreenElement || document.mozFullScreenElement);
    }

    // ---------- Fullscreen enter/exit ----------

    function enterFullscreenMode() {
        // Disable scroll input
        disableScroll();

        // Hide content below the #single-column-container (existing behavior)
        removeContentBelowContainer();

        // Hide ytp fullscreen grid main content(s)
        hideBySelectorAll('.ytp-fullscreen-grid-main-content');

        // Hide expand button(s)
        hideBySelectorAll('.ytp-fullscreen-grid-expand-button.ytp-button');
    }

    function exitFullscreenMode() {
        // Re-enable scrolling
        enableScroll();

        // Restore previously hidden elements
        restoreHiddenElements();
    }

    // ---------- Hiding / restoring helpers ----------

    function hideBySelectorAll(selector) {
        const nodes = document.querySelectorAll(selector);
        nodes.forEach(node => {
            // Avoid hiding same node twice
            if (!hiddenElements.some(entry => entry.el === node)) {
                hiddenElements.push({ el: node, prevDisplay: node.style.display || '' });
                node.style.display = 'none';
            }
        });
    }

    // Remove all content below #single-column-container (keeps previous behavior but stores prev display)
    function removeContentBelowContainer() {
        const container = document.getElementById('single-column-container');
        if (container) {
            let nextSibling = container.nextElementSibling;
            while (nextSibling) {
                if (!hiddenElements.some(entry => entry.el === nextSibling)) {
                    hiddenElements.push({ el: nextSibling, prevDisplay: nextSibling.style.display || '' });
                    nextSibling.style.display = 'none';
                }
                nextSibling = nextSibling.nextElementSibling;
            }
        }
    }

    function restoreHiddenElements() {
        hiddenElements.forEach(entry => {
            try {
                entry.el.style.display = entry.prevDisplay;
            } catch (err) {
                // element might have been removed from DOM — ignore
            }
        });
        hiddenElements = [];
    }

    // ---------- Scroll blocking ----------

    function preventScroll(e) {
        // Only prevent if in fullscreen (defensive)
        if (!isFullScreen()) return;
        e.preventDefault();
        e.stopPropagation();
        return false;
    }

    function preventKeyScroll(e) {
        if (!isFullScreen()) return;
        // Keys that can scroll the page
        const blockedKeys = [
            'PageUp', 'PageDown', 'Home', 'End', ' '
        ];
        if (blockedKeys.includes(e.key)) {
            // Allow if user is focused in an input/textarea/contenteditable
            const target = e.target;
            const tag = target && target.tagName ? target.tagName.toLowerCase() : '';
            const isEditable = target && (target.isContentEditable || tag === 'input' || tag === 'textarea' || target.getAttribute('role') === 'textbox');
            if (!isEditable) {
                e.preventDefault();
                e.stopPropagation();
                return false;
            }
        }
    }

    function disableScroll() {
        if (listenersAdded) return; // don't add twice

        // Save previous overflow values
        previousOverflow.html = document.documentElement.style.overflow || '';
        previousOverflow.body = document.body.style.overflow || '';

        // Force hide overflow to block scrollbar-based scrolling as a robust fallback
        try {
            document.documentElement.style.overflow = 'hidden';
            document.body.style.overflow = 'hidden';
        } catch (err) {
            // ignore if not allowed
        }

        // Insert style to hide scrollbars (visual)
        if (!document.getElementById(styleElementId)) {
            const style = document.createElement('style');
            style.id = styleElementId;
            style.type = 'text/css';
            style.appendChild(document.createTextNode(
                'html, body { overscroll-behavior: none !important; height: 100% !important; } ' +
                '::-webkit-scrollbar { display: none !important; width: 0 !important; height: 0 !important; }'
            ));
            (document.head || document.documentElement).appendChild(style);
        }

        // Add wheel & touchmove listeners (passive: false to allow preventDefault)
        window.addEventListener('wheel', preventScroll, { passive: false, capture: true });
        window.addEventListener('touchmove', preventScroll, { passive: false, capture: true });

        // Keydown to block space/arrow/page keys
        window.addEventListener('keydown', preventKeyScroll, { passive: false, capture: true });

        listenersAdded = true;
    }

    function enableScroll() {
        if (!listenersAdded) {
            // still attempt to clean up style/overflow even if listeners weren't marked as added
            cleanupOverflowAndStyle();
            return;
        }

        window.removeEventListener('wheel', preventScroll, { capture: true });
        window.removeEventListener('touchmove', preventScroll, { capture: true });
        window.removeEventListener('keydown', preventKeyScroll, { capture: true });

        cleanupOverflowAndStyle();

        listenersAdded = false;
    }

    function cleanupOverflowAndStyle() {
        // Restore previous overflow values
        try {
            document.documentElement.style.overflow = previousOverflow.html || '';
            document.body.style.overflow = previousOverflow.body || '';
        } catch (err) {
            // ignore
        }

        // Remove style element that hid scrollbars
        const style = document.getElementById(styleElementId);
        if (style && style.parentNode) {
            style.parentNode.removeChild(style);
        }
    }

    // Keep things tidy if the user navigates or the page rebuilds heavy parts of the DOM:
    // If the fullscreen element is removed unexpectedly, make sure we restore.
    const observer = new MutationObserver(() => {
        if (!isFullScreen() && (hiddenElements.length > 0 || listenersAdded)) {
            // Force restore if DOM changed and we are no longer fullscreen
            exitFullscreenMode();
        }
    });

    observer.observe(document.documentElement, { childList: true, subtree: true });

    // Clean up on unload
    window.addEventListener('beforeunload', () => {
        try { observer.disconnect(); } catch (e) {}
        enableScroll();
    });

})();

Posted

YouTube 06 - Disable AutoPause

// ==UserScript==
// @name - YouTube 06 - Disable AutoPause
// @version 2025.07.08.001
// @match https://www.youtube.com/*
// @exclude /^https?://\S+\.(txt|png|jpg|jpeg|gif|xml|svg|manifest|log|ini)[^\/]*$/
// @run-at document-start
// @grant none
// @unwrap
// @allFrames true
// @inject-into page
// ==/UserScript==

(function (__Promise__) {
  'use strict';

  /** @type {globalThis.PromiseConstructor} */
  const Promise = (async () => { })().constructor; // YouTube hacks Promise in WaterFox Classic and "Promise.resolve(0)" nevers resolve.

  const youThereDataHashMapPauseDelay = new WeakMap();
  const youThereDataHashMapPromptDelay = new WeakMap();
  const youThereDataHashMapLactThreshold = new WeakMap();
  const websiteName = 'YouTube';
  let noDelayLogUntil = 0;

  const insp = o => o ? (o.polymerController || o.inst || o || 0) : (o || 0);
  const indr = o => insp(o).$ || o.$ || 0;

  function delayLog(...args) {
    if (Date.now() < noDelayLogUntil) return;
    noDelayLogUntil = Date.now() + 280; // avoid duplicated delay log in the same time ticker
    console.log(...args);
  }

  function defineProp1(youThereData, key, retType, constVal, fGet, fSet, hashMap) {
    Object.defineProperty(youThereData, key, {
      enumerable: true,
      configurable: true,
      get() {
        Promise.resolve(new Date).then(fGet).catch(console.warn);
        const ret = constVal;
        return retType === 2 ? `${ret}` : ret;
      },
      set(newValue) {
        const oldValue = hashMap.get(this);
        Promise.resolve([oldValue, newValue, new Date]).then(fSet).catch(console.warn);
        hashMap.set(this, newValue);
        return true;
      }
    });
  }

  function defineProp2(youThereData, key, qKey) {
    Object.defineProperty(youThereData, key, {
      enumerable: true,
      configurable: true,
      get() {
        const r = this[qKey];
        if ((r || 0).length >= 1) r.length = 0;
        return r;
      },
      set(nv) {
        return true;
      }
    });
  }

  function hookYouThereData(youThereData) {
    if (!youThereData || youThereDataHashMapPauseDelay.has(youThereData)) return;
    const retPauseDelay = youThereData.playbackPauseDelayMs;
    const retPromptDelay = youThereData.promptDelaySec;
    const retLactThreshold = youThereData.lactThresholdMs;
    const tenPU = Math.floor(Number.MAX_SAFE_INTEGER * 0.1);
    const mPU = Math.floor(tenPU / 1000);

    if ('playbackPauseDelayMs' in youThereData && retPauseDelay >= 0 && retPauseDelay < 4 * tenPU) {
      youThereDataHashMapPauseDelay.set(youThereData, retPauseDelay);
      const retType = typeof retPauseDelay === 'string' ? 2 : +(typeof retPauseDelay === 'number');
      if (retType >= 1) {
        defineProp1(youThereData, 'playbackPauseDelayMs', retType, 5 * tenPU, d => {
          delayLog(`${websiteName} is trying to pause video...`, d.toLocaleTimeString());
        }, args => {
          const [oldValue, newValue, d] = args;
          console.log(`${websiteName} is trying to change value 'playbackPauseDelayMs' from ${oldValue} to ${newValue} ...`, d.toLocaleTimeString());
        }, youThereDataHashMapPauseDelay);
      }
      if (typeof ((youThereData.showPausedActions || 0).length) === 'number' && !youThereData.tvTyh) {
        youThereData.tvTyh = [];
        defineProp2(youThereData, 'showPausedActions', 'tvTyh');
      }
    }

    if ('promptDelaySec' in youThereData && retPromptDelay >= 0 && retPromptDelay < 4 * mPU) {
      youThereDataHashMapPromptDelay.set(youThereData, retPromptDelay);
      const retType = typeof retPromptDelay === 'string' ? 2 : +(typeof retPromptDelay === 'number');
      // lact -> promptDelaySec -> showDialog -> playbackPauseDelayMs -> pause
      if (retType >= 1) {
        defineProp1(youThereData, 'promptDelaySec', retType, 5 * mPU, d => {
          delayLog(`${websiteName} is trying to pause video...`, d.toLocaleTimeString());
        }, args => {
          const [oldValue, newValue, d] = args;
          console.log(`${websiteName} is trying to change value 'promptDelaySec' from ${oldValue} to ${newValue} ...`, d.toLocaleTimeString());
        }, youThereDataHashMapPromptDelay);

      }
    }

    if ('lactThresholdMs' in youThereData && retLactThreshold >= 0 && retLactThreshold < 4 * tenPU) {
      youThereDataHashMapLactThreshold.set(youThereData, retLactThreshold);
      const retType = typeof retLactThreshold === 'string' ? 2 : +(typeof retLactThreshold === 'number');
      // lact -> promptDelaySec -> showDialog -> playbackPauseDelayMs -> pause
      if (retType >= 1) {
        defineProp1(youThereData, 'lactThresholdMs', retType, 5 * tenPU, d => {
          // console.log(`${websiteName} is trying to pause video...`, d.toLocaleTimeString());
        }, args => {
          const [oldValue, newValue, d] = args;
          console.log(`${websiteName} is trying to change value 'lactThresholdMs' from ${oldValue} to ${newValue} ...`, d.toLocaleTimeString());
        }, youThereDataHashMapLactThreshold);
      }
    }

  }

  let detectionOfYouThereRenderer = false;
  let detectionOfYouThereData = false;

  function messageHook() {

    const listOfMessages = new Set();

    const pageMgrElm = document.querySelector('#page-manager') || 0;
    try {
      const playerData = pageMgrElm && (insp(pageMgrElm).data || pageMgrElm.data || insp(pageMgrElm).__data || pageMgrElm.__data || 0);
      const response = playerData.playerResponse;
      if (response) listOfMessages.add(response.messages);
    } catch (e) { }

    const playerElm = document.querySelector('#ytd-player') || 0;
    const playerApi = playerElm && (insp(playerElm).player_ || playerElm.player_ || insp(playerElm).player || playerElm.player || 0);
    if (playerApi && typeof playerApi.getPlayerResponse === 'function') {
      try {
        const response = playerApi.getPlayerResponse();
        if (response) listOfMessages.add(response.messages);
      } catch (e) { }
    }

    const moviePlayerElm = document.querySelector('#movie_player') || 0;
    const moviePlayerApi = moviePlayerElm && (insp(moviePlayerElm).getPlayerResponse ? insp(moviePlayerElm) : moviePlayerElm);
    if (moviePlayerApi && typeof moviePlayerApi.getPlayerResponse === 'function') {
      try {
        const response = moviePlayerApi.getPlayerResponse();
        if (response) listOfMessages.add(response.messages);
      } catch (e) { }
    }

    const youThereDataSet = new Set();
    for (const messages of listOfMessages) {
      if (messages && messages.length > 0) {
        for (const message of messages) {
          if (message.youThereRenderer) {
            if (!detectionOfYouThereRenderer) {
              detectionOfYouThereRenderer = true;
              console.log('Detected message.youThereRenderer');
            }
            let youThereData = null;
            try {
              youThereData = message.youThereRenderer.configData.youThereData;
            } catch (e) { }
            if (youThereData) youThereDataSet.add(youThereData);
            youThereData = null;
            break;
          }
        }
      }
    }
    if (youThereDataSet.size > 0) {
      if (!detectionOfYouThereData) {
        detectionOfYouThereData = true;
        console.log('Detected youThereData');
      }
      for (const youThereData of youThereDataSet) {
        hookYouThereData(youThereData);
      }
      youThereDataSet.clear();
    }

  }

  // e.performDataUpdate -> f.playerData = a.playerResponse;
  // youthereDataChanged_(playerData.messages)
  // youthereDataChanged_ -> b.youThereRenderer && fFb(this.youThereManager_, b.youThereRenderer)
  // a.youThereData_ = b.configData.youThereData;
  // a.youThereData_.playbackPauseDelayMs
  function onPageFinished() {
    if (arguments.length === 1) noDelayLogUntil = Date.now() + 3400; // no delay log for video changes
    Promise.resolve(0).then(() => {

      messageHook();

      const ytdFlexyElm = document.querySelector('ytd-watch-flexy') || 0;
      const ytdFlexyCnt = insp(ytdFlexyElm);

      if (ytdFlexyCnt) {
        const youThereManager_ = ytdFlexyCnt.youThereManager_ || ytdFlexyElm.youThereManager_ || 0;
        const youThereData_ = (youThereManager_ || 0).youThereData_ || 0;
        if (youThereData_) hookYouThereData(youThereData_);
        const f = ytdFlexyCnt.youthereDataChanged_;
        if (typeof f === 'function' && !f.lq2S7) {
          console.log('detected ytdFlexyCnt.youthereDataChanged_');
          ytdFlexyCnt.youthereDataChanged_ = (function (f) {
            return function () {
              console.log('youthereDataChanged_()');
              const ret = f.apply(this, arguments);
              onPageFinished();
              return ret;
            }
          })(f);
          ytdFlexyCnt.youthereDataChanged_.lq2S7 = 1;
        }
      }

    }).catch(console.warn)
  }
  document.addEventListener('yt-page-data-updated', onPageFinished, false);
  document.addEventListener('yt-navigate-finish', onPageFinished, false);
  document.addEventListener('spfdone', onPageFinished, false);

})(Promise);

Posted

YouTube 07 - Disable AutoPlay (Allow Playlist Autoplay)

// ==UserScript==
// @name - YouTube 07 - Disable AutoPlay (Allow Playlist Autoplay)
// @version 0.0.6.1
// @include *youtube.com*
// @grant none
// @noframes
// ==/UserScript==

function isPlayingPlaylist(){
  var params = new URLSearchParams(window.location.search);
  return params.has('list')
}

const pauseVideo = () => !isPlayingPlaylist() && document.querySelector('.html5-video-player').pauseVideo()

function stopAutoPlay(){
  const interval = setInterval(() => {
    if(!document.querySelector('video') || !document.querySelector('.html5-video-player')) return
      pauseVideo()
  }, 50)

  document.addEventListener('mousedown', () => {
      if(interval){
        clearInterval(interval)
      }
    },
    {once: true}
  )
  document.addEventListener('keydown', e => {
      if(interval){
        clearInterval(interval)
      }
    },
    {once: true}
  )
}

stopAutoPlay()


window.addEventListener('yt-navigate-finish', stopAutoPlay)

Posted

YouTube 08 - Add Playlist Autoplay Button

// ==UserScript==
// @name - YouTube 08 - Add Playlist Autoplay Button
// @version 2.0.9
// @match https://www.youtube.com/*
// @require https://cdn.jsdelivr.net/gh/cyfung1031/userscript-supports@8fac46500c5a916e6ed21149f6c25f8d1c56a6a3/library/ytZara.js
// @require https://cdn.jsdelivr.net/gh/cyfung1031/userscript-supports@c2b707e4977f77792042d4a5015fb188aae4772e/library/nextBrowserTick.min.js
// @run-at document-start
// @unwrap
// @inject-into page
// @noframes
// ==/UserScript==

(async () => {

  const { insp, indr, isYtHidden } = ytZara;
  const Promise = (async () => { })().constructor;

  let debug = false
  const elementCSS = {
    parent: [
      '#playlist-action-menu[autoplay-container="1"] .top-level-buttons', // Playlist parent area in general.
      'ytd-playlist-panel-renderer[playlist-type] #playlist-action-menu[autoplay-container="2"]' // Playlist parent area for Mixes.
    ],
    cssId: 'YouTube-Prevent-Playlist-Autoplay-Style', // ID for the Style element to be injected into the page.
    buttonOn: 'YouTube-Prevent-Playlist-Autoplay-Button-On',
    buttonContainer: 'YouTube-Prevent-Playlist-Autoplay-Button-Container',
    buttonBar: 'YouTube-Prevent-Playlist-Autoplay-Button-Bar',
    buttonCircle: 'YouTube-Prevent-Playlist-Autoplay-Button-Circle'
  }
  const prefix = 'YouTube Prevent Playlist Autoplay:'
  const localStorageProperty = 'YouTubePreventPlaylistAutoplayStatus'
  // Get current autoplay setting from local storage.
  let autoplayStatus = loadAutoplayStatus()
  let transition = false
  let navigateStatus = -1;
  let fCounter = 0;

  // Instead of writing the same log function prefix throughout
  // the code, this function automatically applies the prefix.
  const customLog = (...inputs) => console.log(prefix, ...inputs)

  // Functions to get/set if you have autoplay off or on.
  // This applies to localStorage of the domain, so
  // clearing that will clear the stored value.
  function loadAutoplayStatus() {
    if (debug) customLog('Loading autoplay status.')
    return window.localStorage.getItem(localStorageProperty) === 'true'
  }

  function saveAutoplayStatus() {
    if (debug) customLog('Saving autoplay status.')
    window.localStorage.setItem(localStorageProperty, autoplayStatus)
  }

  // Ancient, common function for adding a style to the page.
  function addStyle(id, css) {
    if (document.getElementById(id) !== null) {
      if (debug) customLog('CSS has already been applied.')
      return
    }
    const head = document.head || document.getElementsByTagName('head')[0]
    if (!head) {
      if (debug) customLog('document.head is missing.')
      return
    }
    const style = document.createElement('style')
    style.id = id
    style.textContent = css
    head.appendChild(style)
  }

  // Sets the ability to autoplay based on the user's current setting,
  // then sets the state of all autoplay toggle switches in the page.
  function setAssociatedAutoplay() {
    const manager = getManager()
    if (!manager) {
      if (debug) customLog('Manager is missing.')
      return
    }
    if (typeof manager.canAutoAdvance_ !== 'boolean') {
      customLog('manager.canAutoAdvance_ is not boolean');
    } else {
      if (navigateStatus !== 1) manager.canAutoAdvance_ = !!autoplayStatus;
    }
    for (const b of document.body.getElementsByClassName(elementCSS.buttonContainer)) {
      b.classList.toggle(elementCSS.buttonOn, autoplayStatus)
      b.setAttribute('title', `Autoplay is ${autoplayStatus ? 'on' : 'off'}`)
    }
  }

  // Toggles the ability to autoplay, then sets the rest
  // and stores the current status of autoplay locally.
  function toggleAutoplay(e) {
    e.stopPropagation()
    if (transition) {
      if (debug) customLog('Button is transitioning.')
      e.preventDefault()
      return
    }
    autoplayStatus = !autoplayStatus
    setAssociatedAutoplay()
    saveAutoplayStatus()
    if (debug) customLog('Autoplay toggled to:', autoplayStatus)
  }

  // Retrieves the current playlist manager to adjust and use.
  function getManager() {
    return insp(document.querySelector('yt-playlist-manager'));
  }

  // Playlists cannot autoplay if the variable "canAutoAdvance_" is set to false.
  // It is messy to toggle back since various functions switch it.
  // Luckily, all attempts to set it to true are done through the same function.
  // By replacing this function, autoplay can be controlled by the user.
  function interceptManagerForAutoplay() {
    const manager = getManager()
    if (!manager) {
      if (debug) customLog('Manager is missing.')
      return
    }
    if (manager.interceptedForAutoplay) return
    manager.interceptedForAutoplay = true
    addStyle(elementCSS.cssId, elementCSS.styleText)
    if (debug) customLog('Autoplay is now controlled.')
  }

  const transitionOn = () => {
    transition = true;
    // container.style.pointerEvents = 'none';
  }
  const transitionOff = () => {
    transition = false;
    // container.style.pointerEvents = '';
  }

  const moButtonAttachment = new MutationObserver((entries) => {
    for (const entry of entries) {
      const { target, previousSibling, removedNodes } = entry;
      if (removedNodes.length >= 1 && target.isConnected === true && previousSibling && previousSibling.isConnected === true) {
        for (const elem of removedNodes) {
          if (elem.classList.contains(`${elementCSS.buttonContainer}`) && elem.isConnected === false) {
            target.insertBefore(elem, previousSibling.nextSibling);
          }
        }
      }
    }
  })

  function appendButtonContainer(domElement) {
    if (!domElement || !(domElement instanceof Element) || !elementCSS.buttonContainer || domElement.querySelector(`.${elementCSS.buttonContainer}`)) return;
    const container = document.createElement('div')
    container.classList.add(elementCSS.buttonContainer)
    container.classList.toggle(elementCSS.buttonOn, autoplayStatus)
    container.setAttribute('title', `Autoplay is ${autoplayStatus ? 'on' : 'off'}`)
    container.addEventListener('click', toggleAutoplay, false)
    // if (debug && e) container.event = [...e]

    const bar = document.createElement('div')
    bar.classList.add(elementCSS.buttonBar)
    container.appendChild(bar)

    const circle = document.createElement('div')
    circle.classList.add(elementCSS.buttonCircle)
    // Use the transition as the cooldown.
    circle.addEventListener('transitionrun', transitionOn, { passive: true, capture: false });
    circle.addEventListener('transitionend', transitionOff, { passive: true, capture: false });
    circle.addEventListener('transitioncancel', transitionOff, { passive: true, capture: false });
    container.appendChild(circle)

    domElement.appendChild(container)
    if (debug) customLog('Button added.')

    moButtonAttachment.observe(domElement, { childList: true, subtree: false }); // re-adding after removal

  }

  function appendButtonContainerToMenu(menu) {
    if (!menu || !(menu instanceof Element)) return;
    const headers = menu.querySelectorAll('.top-level-buttons:not([hidden])')
    if (headers.length >= 1) {
      for (const header of headers) {
        // add button to each matched header, ignore those have been proceeded without re-rendering.
        appendButtonContainer(header);
      }
      menu.setAttribute('autoplay-container', '1');
    } else {
      // add button to the menu if no header is found, ignore those have been proceeded without re-rendering.
      appendButtonContainer(menu);
      menu.setAttribute('autoplay-container', '2');
    }
  }

  const ytReady = new Promise(_resolve => {
    document.addEventListener('yt-action', async function () {
      const resolve = _resolve;
      _resolve = null;
      if (!resolve) return;
      await customElements.whenDefined('yt-playlist-manager').then();
      await new Promise(resolve => setTimeout(resolve, 100));
      resolve();
    }, { once: true, passive: true, capture: true });
  })

  async function setupMenu(menu) {
    if (!(menu instanceof Element)) return;
    await ytReady.then();

    // YouTube can have multiple variations of the playlist UI hidden in the page.
    // For instance, the sidebar and corner playlists. They also misuse IDs,
    // whereas they can appear multiple times in the same page.
    // This isolates one potentially visible instance.
    if (isYtHidden(menu)) {
      // the menu is invalid
      menu.removeAttribute('autoplay-container');
    } else {
      interceptManagerForAutoplay()
      appendButtonContainerToMenu(menu);
      setAssociatedAutoplay() // set canAutoAdvance_ when the page is loaded.
    }
  }

  function onNavigateStart() { // navigation endpoint is clicked
    // canAutoAdvance_ will become false in onYtNavigateStart_
    navigateStatus = 1;
    if (fCounter > 1e9) fCounter = 9;
    fCounter++;
  }

  function onNavigateCache() {
    navigateStatus = 1;
    if (fCounter > 1e9) fCounter = 9;
    fCounter++;
  }

  function onNavigateFinish() {
    // canAutoAdvance_ will become true in onYtNavigateFinish_
    navigateStatus = 2;
    if (fCounter > 1e9) fCounter = 9;
    fCounter++;
    const t = fCounter;
    interceptManagerForAutoplay()
    setTimeout(() => {
      if (t !== fCounter) return;
      if (navigateStatus === 2) {
        // canAutoAdvance_ has become true in onYtNavigateFinish_
        setAssociatedAutoplay();  // set canAutoAdvance_ to true or false as per preferred setting
      }
    }, 100);
  }

  const attrMo = new MutationObserver((entries) => {
    // the state of DOM is being changed, expand/collaspe state, rendering after dataChanged, etc.
    let m = new Set();
    for (const entry of entries) {
      m.add(entry.target); // avoid proceeding the same element target
    }
    m.forEach((target) => {
      if (target && target.isConnected === true) { // ensure the DOM is valid and attached to the document
        setupMenu(indr(target)['playlist-action-menu']); // add the button to the menu, if applicable
      }
    });
    m.clear();
    m = null;
  });

  // listen events on the script execution in document-start
  document.addEventListener('yt-navigate-start', onNavigateStart, false);
  document.addEventListener('yt-navigate-cache', onNavigateCache, false);
  document.addEventListener('yt-navigate-finish', onNavigateFinish, false);

  elementCSS.styleText = `
        ${elementCSS.parent.join(', ')} {
            align-items: center;
        }
        .${elementCSS.buttonContainer} {
            position: relative;
            height: 20px;
            width: 36px;
            cursor: pointer;
            margin-left: 8px;
        }
        .${elementCSS.buttonContainer} .${elementCSS.buttonBar} {
            position: absolute;
            top: calc(50% - 7px);
            height: 14px;
            width: 36px;
            background-color: var(--paper-toggle-button-unchecked-bar-color, #000000);
            border-radius: 8px;
            opacity: 0.4;
        }
        .${elementCSS.buttonContainer} .${elementCSS.buttonCircle} {
            position: absolute;
            left: 0;
            height: 20px;
            width: 20px;
            background-color: var(--paper-toggle-button-unchecked-button-color, var(--paper-grey-50));
            border-radius: 50%;
            box-shadow: 0 1px 5px 0 rgba(0, 0, 0, 0.6);
            transition: left linear .08s, background-color linear .08s;
        }
        .${elementCSS.buttonContainer}.${elementCSS.buttonOn} .${elementCSS.buttonCircle} {
            position: absolute;
            left: calc(100% - 20px);
            background-color: var(--paper-toggle-button-checked-button-color, var(--primary-color));
        }
      `;

  if (!document.documentElement) await ytZara.docInitializedAsync(); // wait for document.documentElement is provided

  await ytZara.promiseRegistryReady(); // wait for YouTube's customElement Registry is provided (old browser only)

  const cProto = await ytZara.ytProtoAsync('ytd-playlist-panel-renderer'); // wait for customElement registration

  if (cProto.attached145 || cProto.setupPlaylistActionMenu145) {
    console.warn('YouTube Playlist Autoplay Button cannot inject JS code to ytd-playlist-panel-renderer');
    return;
  }

  cProto.attached145 = cProto.attached;
  cProto.setupPlaylistActionMenu145 = function () {
    nextBrowserTick(() => { // avoid blocking the DOM tree rendering
      const hostElement = this.hostElement;
      if (!hostElement || hostElement.isConnected !== true) return;
      attrMo.observe(hostElement, {
        attributes: true,
        attributeFilter: [
          'has-playlist-buttons', 'has-toolbar', 'hidden', 'playlist-type', 'within-miniplayer', 'hide-header-text'
        ]
      });
      setupMenu(indr(this)['playlist-action-menu']); // add the button to the menu which is just attached to Dom Tree, if applicable
    });
  }
  cProto.attached = function () {
    try {
      this.setupPlaylistActionMenu145();
    } finally {
      const f = this.attached145;
      return f ? f.apply(this, arguments) : void 0;
    }
  }

  if (debug) customLog('Initialized.')

})();

Posted

YouTube 09 - Disable Inline Playback

// ==UserScript==
// @name - YouTube 09 - Disable Inline Playback
// @version 2.6.1
// @match *://www.youtube.com/*
// @grant none
// ==/UserScript==

(function() {
    'use strict';

    // Allow strings for HTML/CSS/etc. trusted injections
    if (window.trustedTypes && window.trustedTypes.createPolicy && !window.trustedTypes.defaultPolicy) {
        window.trustedTypes.createPolicy('default', {
            createHTML: string => string,
            createScriptURL: string => string,
            createScript: string => string
        });
    }

    // Hide video preview on hover
    var sheet = document.createElement('style');
    sheet.innerHTML = `
        ytd-video-preview { display: none !important; }
        .ytd-thumbnail-overlay-loading-preview-renderer { display: none !important; }
        .ytp-inline-preview-ui { display: none !important; }
        #preview { display: none !important; }
        ytd-thumbnail[now-playing] ytd-thumbnail-overlay-time-status-renderer.ytd-thumbnail { display: flex !important; }
        ytd-thumbnail[is-preview-loading] ytd-thumbnail-overlay-time-status-renderer.ytd-thumbnail { display: flex !important; }
        #mouseover-overlay { display: none !important; }
        animated-thumbnail-overlay-view-model { display: none !important; }
    `;

    document.head.appendChild(sheet);
})();

Posted

YouTube 10 - Remove Shorts / Popular / Also Watched / New To You / Explore

// ==UserScript==
// @name - YouTube 10 - Remove Shorts / Popular / Also Watched / New To You / Explore
// @version 2025.4.4.0.1
// @match https://*.youtube.com/*
// @match https://m.youtube.com/*
// @grant none
// @run-at document-start
// ==/UserScript==

(function () {
    'use strict';

    const hideHistoryShorts = false;
    const debug = false;

    const commonSelectors = [
        'a[href*="/shorts/"]',
        '[is-shorts]',
        'yt-chip-cloud-chip-renderer:has(a[href*="/shorts/"])',
        'ytd-reel-shelf-renderer',
        'ytd-thumbnail-overlay-time-status-renderer[overlay-style="SHORTS"]',
        '#guide [title="Shorts"]',
        '.ytd-mini-guide-entry-renderer[title="Shorts"]',
        '.ytd-mini-guide-entry-renderer[aria-label="Shorts"]',
        'grid-shelf-view-model',
        'ytd-shelf-renderer',
        'ytd-movie-renderer',
        'ytd-channel-renderer',
        '#spinner-container',
    ];

    const mobileSelectors = [
        '.pivot-shorts',
        'ytm-reel-shelf-renderer',
        'ytm-search ytm-video-with-context-renderer [data-style="SHORTS"]',
    ];

    const feedSelectors = [
        'ytd-browse[page-subtype="subscriptions"] ytd-grid-video-renderer [overlay-style="SHORTS"]',
        'ytd-browse[page-subtype="subscriptions"] ytd-video-renderer [overlay-style="SHORTS"]',
        'ytd-browse[page-subtype="subscriptions"] ytd-rich-item-renderer [overlay-style="SHORTS"]',
    ];
    const channelSelectors = ['yt-tab-shape[tab-title="Shorts"]'];
    const historySelectors = ['ytd-browse[page-subtype="history"] ytd-reel-shelf-renderer'];

    function removeElementsBySelectors(selectors) {
        selectors.forEach((selector) => {
            try {
                const elements = document.querySelectorAll(selector);
                elements.forEach((element) => {
                    if (element.dataset.removedByScript) return;
                    let parent = element.closest(
                        'ytd-video-renderer, ytd-grid-video-renderer, ytd-compact-video-renderer, ytd-rich-item-renderer, ytm-video-with-context-renderer'
                    );
                    if (!parent) parent = element;
                    parent.remove();
                    parent.dataset.removedByScript = 'true';
                    if (debug) console.log(`Removed element: ${parent}`);
                });
            } catch (error) {
                if (debug) console.warn(`Error processing selector: ${selector}`, error);
            }
        });
    }

    function removeElements() {
        const currentUrl = window.location.href;
        if (debug) console.log('Current URL:', currentUrl);

        if (currentUrl.includes('m.youtube.com')) {
            removeElementsBySelectors(mobileSelectors);
        }
        if (currentUrl.includes('/feed/subscriptions')) {
            removeElementsBySelectors(feedSelectors);
        }
        //if (currentUrl.includes('/channel') || currentUrl.includes('/@')) {
        //    removeElementsBySelectors(channelSelectors);
        //}
        if (hideHistoryShorts && currentUrl.includes('/feed/history')) {
            removeElementsBySelectors(historySelectors);
        }

        removeElementsBySelectors(commonSelectors);
    }

    function debounce(func, delay) {
        let timeout;
        return (...args) => {
            clearTimeout(timeout);
            timeout = setTimeout(() => func.apply(this, args), delay);
        };
    }

    const debouncedRemoveElements = debounce(removeElements, 300);

    function init() {
        if (debug) console.log('Remove YouTube Shorts script activated');
        removeElements();

        const isFirefox = navigator.userAgent.includes('Firefox');
        if (isFirefox) {
            window.addEventListener('popstate', removeElements);
        } else {
            document.addEventListener('yt-navigate-finish', removeElements);
        }

        const observer = new MutationObserver(debouncedRemoveElements);
        observer.observe(document.body, { childList: true, subtree: true });
    }

    if (document.readyState === 'loading') {
        document.addEventListener('DOMContentLoaded', init);
    } else {
        init();
    }
})();

Posted

YouTube 11 - Remove Most Replayed Graph

// ==UserScript==
// @name - YouTube 11 - Remove Most Replayed Graph
// @version 0.1.1
// @match *://*.youtube.com/*
// ==/UserScript==

// Block YouTube's "Most Replayed" Graph

(function() {
    'use strict';

    // Function to remove the Most Replayed Feature
    function blockMostReplayedFeature() {
        var mostReplayedElement = document.querySelector('.ytp-heat-map-chapter');
        if (mostReplayedElement) {
            mostReplayedElement.remove();
        }
    }

    // Run the function when the page is fully loaded
    window.addEventListener('load', blockMostReplayedFeature);

    // Also, run the function when the page changes dynamically (e.g., when navigating to a new video)
    var observer = new MutationObserver(blockMostReplayedFeature);
    observer.observe(document.body, { subtree: true, childList: true });

    // Modify styles
    function addGlobalStyle(css) {
        var head, style;
        head = document.getElementsByTagName('head')[0];
        if (!head) { return; }
        style = document.createElement('style');
        style.type = 'text/css';
        style.innerHTML = css;
        head.appendChild(style);
    }

    addGlobalStyle('.ytp-tooltip-title { display: none !important; }');
    addGlobalStyle('.ytp-tooltip-text { top: 45px !important; }');

})();

Posted

YouTube 12 - Search Dropdown List

// ==UserScript==
// @name - YouTube 12 - Search Dropdown List
// @version 1.0
// @match https://*.youtube.com/*
// @run-at document-end
// ==/UserScript==

(function() {
    'use strict';

    // *** Customize your search strings here ***
    const searchStrings = [
        "full movie",
        "full episode sitcom",
        "action movie",
        "adventure movie",
        "animation movie",
        "comedy movie",
        "documentary movie",
        "drama movie",
        "fantasy movie",
        "horror movie",
        "mystery movie",
        "psychological thriller movie",
        "romance movie",
        "science fiction movie",
        "supernatural movie",
        "thriller movie",
        "noir film",
        "noir supernatural",
        "noir tech",
        "superhero movies",
        "supervillain movies",
        "vampire movies",
        "werewolf movies",
        "witch movies",
        "holiday movie"
    ];

    function addDropdown() {
        const searchInput = document.querySelector('input');
        if (!searchInput) return;

        // Create the dropdown element
        const dataList = document.createElement('datalist');
        dataList.id = 'search-suggestions';

        searchStrings.forEach(str => {
            const option = document.createElement('option');
            option.value = str;
            dataList.appendChild(option);
        });

        // Append the datalist to the body (or a more suitable parent if needed, body is generally fine)
        document.body.appendChild(dataList);

        // Link the input to the datalist
        searchInput.setAttribute('list', 'search-suggestions');

    }

    // Run the function when the page finishes loading or navigates
    // YouTube uses dynamic loading, so we also use a listener for navigation changes
    addDropdown();
    document.addEventListener('yt-navigate-finish', addDropdown);

})();

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now
  • Recently Browsing   0 members

    • No registered users viewing this page.
×
×
  • Create New...