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.
YouTube better, like an elite.
// ==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