Skip to content

Instantly share code, notes, and snippets.

@abstraction
Last active January 22, 2025 04:36
Show Gist options
  • Save abstraction/ae389154ba2952fbe7b3d635ce9e037c to your computer and use it in GitHub Desktop.
Save abstraction/ae389154ba2952fbe7b3d635ce9e037c to your computer and use it in GitHub Desktop.

Revisions

  1. abstraction revised this gist Jan 22, 2025. 1 changed file with 1 addition and 0 deletions.
    1 change: 1 addition & 0 deletions ytcreme.user.js
    Original file line number Diff line number Diff line change
    @@ -11,6 +11,7 @@

    /**
    * This script modifies YouTube videos' visibility based on percentage rating or view count.
    * Companion: https://microsoftedge.microsoft.com/addons/detail/thumbnail-rating-bar-for-/mglepphnjnfcljjafdgafoipiakakbin
    * If `shouldRemove` is true, it completely removes videos, otherwise, it lowers their opacity.
    */

  2. abstraction revised this gist Jan 22, 2025. 1 changed file with 89 additions and 83 deletions.
    172 changes: 89 additions & 83 deletions ytcreme.user.js
    Original file line number Diff line number Diff line change
    @@ -9,124 +9,130 @@
    // @grant none
    // ==/UserScript==

    // Companion to: https://microsoftedge.microsoft.com/addons/detail/thumbnail-rating-bar-for-/mglepphnjnfcljjafdgafoipiakakbin
    /**
    * This script modifies YouTube videos' visibility based on percentage rating or view count.
    * If `shouldRemove` is true, it completely removes videos, otherwise, it lowers their opacity.
    */

    // Flag to control whether to remove videos or just lower their opacity
    let shouldRemove = true; // Set this to `true` to remove the videos, `false` to lower opacity

    // Function to process videos and apply opacity or remove them
    /**
    * Process all videos by checking their percentage ratings and view counts.
    * Videos with low ratings or views are either removed or have reduced opacity.
    */
    function processVideos() {
    // Determine if we are on the main video page or the recommendation page
    const isWatchPage = window.location.pathname.includes('/watch');
    try {
    // Determine if we are on the main video page or the recommendation page
    const isWatchPage = window.location.pathname.includes('/watch');

    // Select the appropriate video item container based on the page
    const videoItemSelector = isWatchPage
    ? 'ytd-compact-video-renderer.ytd-item-section-renderer' // For recommendation page
    : 'ytd-rich-item-renderer.ytd-rich-grid-renderer'; // For main grid page
    // Select the appropriate video item container based on the page
    const videoItemSelector = isWatchPage
    ? 'ytd-compact-video-renderer.ytd-item-section-renderer' // For recommendation page
    : 'ytd-rich-item-renderer.ytd-rich-grid-renderer'; // For main grid page

    // Get all the elements with the class 'ytrb-percentage' (less accurate, sometimes not present)
    // const percentageElements = document.querySelectorAll('.ytrb-percentage');
    // Get all elements with the 'ytrb-tooltip' containing the percentage
    const percentageElements = document.querySelectorAll('ytrb-tooltip');

    // Get all the elements with the 'ytrb-tooltip' containing the percentage (more accurate)
    const percentageElements = document.querySelectorAll('ytrb-tooltip');
    if (!percentageElements.length) return; // Early exit if no percentage elements are found

    percentageElements.forEach(percentageElement => {
    percentageElements.forEach(percentageElement => {
    // Extract percentage value from tooltip content
    const percentageText = percentageElement.querySelector('div')?.textContent.trim();
    const percentageMatch = percentageText?.match(/(\d+\.?\d*)%/);

    // Get the percentage value from the text content (to be used with less accurate ytrb-percentage)
    // const percentage = parseFloat(percentageElement.textContent);
    if (!percentageMatch) return;

    // Extract the percentage value from the tooltip content (to be used with more accurate ytrb-tooltop)
    const percentageText = percentageElement.querySelector('div').textContent.trim();
    const percentageMatch = percentageText.match(/(\d+\.?\d*)%/);
    const percentage = parseFloat(percentageMatch[1]);

    if (!percentageMatch) return; // (to be used with more accurate ytrb-tooltop)

    const percentage = parseFloat(percentageMatch[1]); // (to be used with more accurate ytrb-tooltop)

    // If the percentage is less than 99, modify the parent grid item
    if (percentage < 99) {
    // Find the video grid item by bubbling up
    const videoGridItem = percentageElement.closest(videoItemSelector); // Dynamically selected based on page type

    if (videoGridItem) {
    if (shouldRemove) {
    // Remove the video item from DOM completely
    videoGridItem.remove();
    } else {
    // Apply opacity to the entire video grid item
    videoGridItem.style.opacity = 0.05; // Full opacity effect applied to the entire item
    }
    }
    }

    // Check if the view count in #metadata-line is less than 1000 views
    const metadataLine = percentageElement.closest(videoItemSelector).querySelector('#metadata-line');

    if (metadataLine) {
    const viewText = metadataLine.textContent.trim();

    // If the metadata line does not contain the word 'views', it's likely a live video
    if (!viewText.includes('views')) {
    // Process video if percentage is below 99
    if (percentage < 99) {
    const videoGridItem = percentageElement.closest(videoItemSelector);
    if (videoGridItem) {
    if (shouldRemove) {
    // Remove the live video item from DOM completely
    videoGridItem.remove();
    } else {
    // Reduce opacity for live videos
    videoGridItem.style.opacity = 0.05;
    }
    }
    } else {
    // Extract the view count using a regular expression
    const viewMatch = viewText.match(/(\d+\.?\d*)\s*(k|m|b)?\s*views/i);

    if (viewMatch) {
    let viewCount = parseFloat(viewMatch[1]); // Get the numeric part of the view count
    const isK = viewMatch[2] && viewMatch[2].toLowerCase() === 'k'; // Check if it's in "k" format
    const isM = viewMatch[2] && viewMatch[2].toLowerCase() === 'm'; // Check if it's in "m" format
    const isB = viewMatch[2] && viewMatch[2].toLowerCase() === 'b'; // Check if it's in "b" format (billions)

    // Convert based on the "k" (thousands), "m" (millions), or "b" (billions)
    if (isK) {
    viewCount *= 1000; // Multiply by 1000 for "k"
    } else if (isM) {
    viewCount *= 1000000; // Multiply by 1 million for "m"
    } else if (isB) {
    viewCount *= 1000000000; // Multiply by 1 billion for "b"
    }

    // Check if the video has fewer than 1000 views or is a live stream
    const metadataLine = percentageElement.closest(videoItemSelector)?.querySelector('#metadata-line');
    if (metadataLine) {
    const viewText = metadataLine.textContent.trim();

    // If the metadata line does not contain the word 'views', it's likely a live video
    if (!viewText.includes('views')) {
    const videoGridItem = percentageElement.closest(videoItemSelector);
    if (videoGridItem) {
    if (shouldRemove) {
    videoGridItem.remove();
    } else {
    videoGridItem.style.opacity = 0.05;
    }
    }
    } else {
    // Extract and convert view count to numeric value
    const viewMatch = viewText.match(/(\d+\.?\d*)\s*(k|m|b)?\s*views/i);
    if (viewMatch) {
    let viewCount = parseFloat(viewMatch[1]);
    const multiplier = { k: 1000, m: 1_000_000, b: 1_000_000_000 };
    const suffix = viewMatch[2]?.toLowerCase();
    if (multiplier[suffix]) {
    viewCount *= multiplier[suffix];
    }

    // If the video has less than 1000 views, apply the toggle logic
    if (viewCount < 1000) {
    const videoGridItem = percentageElement.closest(videoItemSelector);
    if (videoGridItem) {
    if (shouldRemove) {
    // Remove the low-view video item from DOM completely
    videoGridItem.remove();
    } else {
    // Reduce opacity for low-view videos
    videoGridItem.style.opacity = 0.05;
    // If the video has fewer than 1000 views, apply the toggle logic
    if (viewCount < 1000) {
    const videoGridItem = percentageElement.closest(videoItemSelector);
    if (videoGridItem) {
    if (shouldRemove) {
    videoGridItem.remove();
    } else {
    videoGridItem.style.opacity = 0.05;
    }
    }
    }
    }
    }
    }
    }
    });
    });
    } catch (error) {
    console.error('Error processing videos:', error);
    }
    }

    // Initialize the MutationObserver to monitor DOM changes (infinite scroll)
    const observer = new MutationObserver(() => {
    processVideos(); // Run the video processing function on every DOM mutation
    });
    /**
    * Initialize the MutationObserver to monitor DOM changes (e.g., infinite scroll).
    */
    const observer = new MutationObserver(debounce(processVideosTwice, 500)); // Debouncing to reduce the frequency of calls

    // Configure the observer to track added nodes in the DOM
    observer.observe(document.body, {
    childList: true,
    subtree: true
    });

    // Initial processing of videos on page load
    processVideos();
    /**
    * Debounce function to limit the rate at which a function is invoked.
    * @param {Function} func - The function to debounce
    * @param {number} wait - Time in milliseconds to wait before invoking the function
    * @returns {Function} - A debounced version of the provided function
    */
    function debounce(func, wait) {
    let timeout;
    return function() {
    clearTimeout(timeout);
    timeout = setTimeout(() => func.apply(this, arguments), wait);
    };
    }

    function processVideosTwice (){
    // Initial processing of videos on page load
    processVideos();
    // Do it one again
    setTimeout(processVideos, 1000);
    }

    processVideosTwice();
  3. abstraction created this gist Jan 22, 2025.
    132 changes: 132 additions & 0 deletions ytcreme.user.js
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,132 @@
    // ==UserScript==
    // @name YTCreme
    // @namespace http://tampermonkey.net/
    // @version 2025-01-22
    // @description YouTube better, like an elite.
    // @author abstraction
    // @match https://www.youtube.com/*
    // @icon https://www.google.com/s2/favicons?sz=64&domain=youtube.com
    // @grant none
    // ==/UserScript==

    // Companion to: https://microsoftedge.microsoft.com/addons/detail/thumbnail-rating-bar-for-/mglepphnjnfcljjafdgafoipiakakbin

    // Flag to control whether to remove videos or just lower their opacity
    let shouldRemove = true; // Set this to `true` to remove the videos, `false` to lower opacity

    // Function to process videos and apply opacity or remove them
    function processVideos() {
    // Determine if we are on the main video page or the recommendation page
    const isWatchPage = window.location.pathname.includes('/watch');

    // Select the appropriate video item container based on the page
    const videoItemSelector = isWatchPage
    ? 'ytd-compact-video-renderer.ytd-item-section-renderer' // For recommendation page
    : 'ytd-rich-item-renderer.ytd-rich-grid-renderer'; // For main grid page

    // Get all the elements with the class 'ytrb-percentage' (less accurate, sometimes not present)
    // const percentageElements = document.querySelectorAll('.ytrb-percentage');

    // Get all the elements with the 'ytrb-tooltip' containing the percentage (more accurate)
    const percentageElements = document.querySelectorAll('ytrb-tooltip');

    percentageElements.forEach(percentageElement => {

    // Get the percentage value from the text content (to be used with less accurate ytrb-percentage)
    // const percentage = parseFloat(percentageElement.textContent);

    // Extract the percentage value from the tooltip content (to be used with more accurate ytrb-tooltop)
    const percentageText = percentageElement.querySelector('div').textContent.trim();
    const percentageMatch = percentageText.match(/(\d+\.?\d*)%/);

    if (!percentageMatch) return; // (to be used with more accurate ytrb-tooltop)

    const percentage = parseFloat(percentageMatch[1]); // (to be used with more accurate ytrb-tooltop)

    // If the percentage is less than 99, modify the parent grid item
    if (percentage < 99) {
    // Find the video grid item by bubbling up
    const videoGridItem = percentageElement.closest(videoItemSelector); // Dynamically selected based on page type

    if (videoGridItem) {
    if (shouldRemove) {
    // Remove the video item from DOM completely
    videoGridItem.remove();
    } else {
    // Apply opacity to the entire video grid item
    videoGridItem.style.opacity = 0.05; // Full opacity effect applied to the entire item
    }
    }
    }

    // Check if the view count in #metadata-line is less than 1000 views
    const metadataLine = percentageElement.closest(videoItemSelector).querySelector('#metadata-line');

    if (metadataLine) {
    const viewText = metadataLine.textContent.trim();

    // If the metadata line does not contain the word 'views', it's likely a live video
    if (!viewText.includes('views')) {
    const videoGridItem = percentageElement.closest(videoItemSelector);
    if (videoGridItem) {
    if (shouldRemove) {
    // Remove the live video item from DOM completely
    videoGridItem.remove();
    } else {
    // Reduce opacity for live videos
    videoGridItem.style.opacity = 0.05;
    }
    }
    } else {
    // Extract the view count using a regular expression
    const viewMatch = viewText.match(/(\d+\.?\d*)\s*(k|m|b)?\s*views/i);

    if (viewMatch) {
    let viewCount = parseFloat(viewMatch[1]); // Get the numeric part of the view count
    const isK = viewMatch[2] && viewMatch[2].toLowerCase() === 'k'; // Check if it's in "k" format
    const isM = viewMatch[2] && viewMatch[2].toLowerCase() === 'm'; // Check if it's in "m" format
    const isB = viewMatch[2] && viewMatch[2].toLowerCase() === 'b'; // Check if it's in "b" format (billions)

    // Convert based on the "k" (thousands), "m" (millions), or "b" (billions)
    if (isK) {
    viewCount *= 1000; // Multiply by 1000 for "k"
    } else if (isM) {
    viewCount *= 1000000; // Multiply by 1 million for "m"
    } else if (isB) {
    viewCount *= 1000000000; // Multiply by 1 billion for "b"
    }

    // If the video has less than 1000 views, apply the toggle logic
    if (viewCount < 1000) {
    const videoGridItem = percentageElement.closest(videoItemSelector);
    if (videoGridItem) {
    if (shouldRemove) {
    // Remove the low-view video item from DOM completely
    videoGridItem.remove();
    } else {
    // Reduce opacity for low-view videos
    videoGridItem.style.opacity = 0.05;
    }
    }
    }
    }
    }
    }
    });
    }

    // Initialize the MutationObserver to monitor DOM changes (infinite scroll)
    const observer = new MutationObserver(() => {
    processVideos(); // Run the video processing function on every DOM mutation
    });

    // Configure the observer to track added nodes in the DOM
    observer.observe(document.body, {
    childList: true,
    subtree: true
    });

    // Initial processing of videos on page load
    processVideos();