Last active
January 22, 2025 04:36
-
-
Save abstraction/ae389154ba2952fbe7b3d635ce9e037c to your computer and use it in GitHub Desktop.
YouTube better, like an elite.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| // ==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== | |
| /** | |
| * 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. | |
| */ | |
| // 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 | |
| /** | |
| * 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() { | |
| 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 | |
| // Get all elements with the 'ytrb-tooltip' containing the percentage | |
| const percentageElements = document.querySelectorAll('ytrb-tooltip'); | |
| if (!percentageElements.length) return; // Early exit if no percentage elements are found | |
| percentageElements.forEach(percentageElement => { | |
| // Extract percentage value from tooltip content | |
| const percentageText = percentageElement.querySelector('div')?.textContent.trim(); | |
| const percentageMatch = percentageText?.match(/(\d+\.?\d*)%/); | |
| if (!percentageMatch) return; | |
| const percentage = parseFloat(percentageMatch[1]); | |
| // Process video if percentage is below 99 | |
| if (percentage < 99) { | |
| const videoGridItem = percentageElement.closest(videoItemSelector); | |
| if (videoGridItem) { | |
| if (shouldRemove) { | |
| videoGridItem.remove(); | |
| } else { | |
| videoGridItem.style.opacity = 0.05; | |
| } | |
| } | |
| } | |
| // 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 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 (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 | |
| }); | |
| /** | |
| * 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(); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment