// ==UserScript== // @name YouTube Premium Logo & Ads Remove // @namespace https://github.com/shajon-dev/youtube-premium // @version 1.2 // @description Replaces the logo with a positioned Premium logo, removes "Get Premium" upsells, and sanitizes ad content. // @author SHAH MAKHDUM SHAJON // @match *://*.youtube.com/* // @grant GM_addStyle // @grant unsafeWindow // @run-at document-start // @homepageURL https://github.com/shajon-dev/youtube-premium // @downloadURL https://raw.githubusercontent.com/shajon-dev/youtube-premium/refs/heads/main/youtube-premium.user.js // @updateURL https://raw.githubusercontent.com/shajon-dev/youtube-premium/refs/heads/main/youtube-premium.user.js // @license Apache-2.0 // ==/UserScript== (function (global) { 'use strict'; /** * Configuration and Assets */ const CONFIG = { checkInterval: 1500, selectors: { upsells: [ 'ytd-guide-entry-renderer:has(a[href="/premium"])', 'ytd-guide-entry-renderer:has(a[href="/paid_memberships"])', 'ytd-mini-guide-entry-renderer[aria-label="YouTube Premium"]', 'ytd-compact-link-renderer:has(a[href="/premium"])', 'ytd-compact-link-renderer:has(a[href="/paid_memberships"])', 'ytd-banner-promo-renderer', 'ytd-statement-banner-renderer', '#offer-module', 'ytd-mealbar-promo-renderer' ], ads: [ '#offer-module', '#promotion-shelf', '.ytd-merch-shelf-renderer', '#masthead-ad', 'ytd-ad-slot-renderer', 'ytd-rich-item-renderer:has(> #content > ytd-ad-slot-renderer)', '#shorts-inner-container > .ytd-shorts:has(ytd-ad-slot-renderer)', '.ytShortsSuggestedActionViewModelStaticHost', '.ytp-suggested-action', '.ytd-watch-next-secondary-results-renderer > ytd-ad-slot-renderer' ] }, xpaths: { sidebarPremium: "/html/body/ytd-app/div[1]/tp-yt-app-drawer/div[2]/div/div[2]/div[2]/ytd-guide-renderer/div[1]/ytd-guide-section-renderer[4]/div/ytd-guide-entry-renderer[1]" } }; // Premium Logo SVG Data const SVG_SOURCE = ` `; const LOGO_LIGHT = "data:image/svg+xml;base64," + btoa(SVG_SOURCE.replace('currentColor', '#212121')); const LOGO_DARK = "data:image/svg+xml;base64," + btoa(SVG_SOURCE.replace('currentColor', '#FFFFFF')); /** * Main YouTube Enhancement Class * Handles Logo Replacement, UI Cleaning, and Network Interception */ class YouTubeEnhancer { constructor() { this.win = global.unsafeWindow || window; this.init(); } init() { this.injectStyles(); this.deployNetworkInterceptors(); this.enforceEnvironmentConstants(); this.startObservers(); } /** * Injects all necessary CSS for Logo replacement and Element hiding. */ injectStyles() { const hideSelectors = [...CONFIG.selectors.upsells, ...CONFIG.selectors.ads].join(',\n'); const css = ` /* --- LOGO REPLACEMENT --- */ ytd-topbar-logo-renderer #logo-icon, ytd-topbar-logo-renderer #logo svg, ytd-masthead #masthead-logo svg { display: none !important; visibility: hidden !important; opacity: 0 !important; } ytd-topbar-logo-renderer { display: flex !important; flex-direction: row !important; align-items: center !important; } ytd-topbar-logo-renderer #logo, ytd-masthead #masthead-logo a { display: block !important; width: 130px !important; height: 24px !important; background-repeat: no-repeat !important; background-size: contain !important; background-position: left center !important; padding: 0 !important; background-image: url('${LOGO_DARK}') !important; } html:not([dark]) ytd-topbar-logo-renderer #logo, html:not([dark]) ytd-masthead #masthead-logo a { background-image: url('${LOGO_LIGHT}') !important; } ytd-topbar-logo-renderer #country-code { display: block !important; visibility: visible !important; opacity: 1 !important; font-size: 11px !important; color: #AAA !important; position: relative !important; top: -12px !important; margin-left: -5px !important; white-space: nowrap !important; } /* --- CLEANUP & SANITIZATION --- */ ${hideSelectors} { display: none !important; visibility: hidden !important; opacity: 0 !important; } `; if (typeof GM_addStyle === 'function') { GM_addStyle(css); } else { const style = document.createElement('style'); style.textContent = css; (document.head || document.documentElement).appendChild(style); } } /** * Hooks into JSON.parse, Fetch, and XHR to sanitize data before it hits the UI. */ deployNetworkInterceptors() { const self = this; // 1. JSON.parse Interceptor const originalParse = this.win.JSON.parse; this.win.JSON.parse = function (...args) { const result = originalParse.apply(this, args); return self.sanitizeObject(result); }; // 2. Fetch API Interceptor const originalFetch = this.win.fetch; this.win.fetch = async function (input, init) { const response = await originalFetch(input, init); const url = (typeof input === 'string') ? input : input?.url; if (url && (url.includes('/player') || url.includes('/watch') || url.includes('playlist'))) { const clone = response.clone(); try { let data = await clone.json(); data = self.sanitizeObject(data); return new Response(JSON.stringify(data), { status: response.status, statusText: response.statusText, headers: response.headers }); } catch (err) { return response; } } return response; }; // 3. XHR Interceptor const originalOpen = this.win.XMLHttpRequest.prototype.open; this.win.XMLHttpRequest.prototype.open = function (method, url) { this._targetUrl = url; return originalOpen.apply(this, arguments); }; const originalSend = this.win.XMLHttpRequest.prototype.send; this.win.XMLHttpRequest.prototype.send = function (body) { const xhr = this; const oldOnReady = xhr.onreadystatechange; xhr.onreadystatechange = function () { if (xhr.readyState === 4 && xhr._targetUrl && /\/(player|watch|get_watch)/.test(xhr._targetUrl)) { try { let data = JSON.parse(xhr.responseText); data = self.sanitizeObject(data); Object.defineProperty(xhr, 'responseText', { value: JSON.stringify(data) }); Object.defineProperty(xhr, 'response', { value: JSON.stringify(data) }); } catch (e) { /* Non-JSON response */ } } if (oldOnReady) oldOnReady.apply(this, arguments); }; return originalSend.apply(this, arguments); }; } /** * Recursive object sanitization to remove ad placements. */ sanitizeObject(obj) { if (!obj || typeof obj !== 'object') return obj; if (obj.playerResponse) { delete obj.playerResponse.adPlacements; delete obj.playerResponse.playerAds; } if (obj.adPlacements) delete obj.adPlacements; if (obj.playerAds) delete obj.playerAds; return obj; } /** * Locks global variables used by ad scripts. */ enforceEnvironmentConstants() { const definitions = [ { path: 'ytInitialPlayerResponse.adPlacements', value: undefined }, { path: 'ytInitialPlayerResponse.playerAds', value: undefined }, { path: 'google_ad_status', value: 1 } ]; definitions.forEach(def => this.defineDeepProperty(this.win, def.path.split('.'), def.value)); } defineDeepProperty(target, pathParts, value) { const prop = pathParts.shift(); if (pathParts.length > 0) { if (!target[prop]) target[prop] = {}; this.defineDeepProperty(target[prop], pathParts, value); } else { Object.defineProperty(target, prop, { get: () => value, set: () => {}, configurable: false }); } } /** * DOM Clean-up Logic (Backup for CSS). * Handles JS-based link removal and XPath targets. */ cleanDOM() { // JS Fallback for generic links document.querySelectorAll('a[href="/premium"], a[href="/paid_memberships"]').forEach(link => { const container = link.closest('ytd-guide-entry-renderer') || link.closest('ytd-compact-link-renderer') || link.closest('ytd-mini-guide-entry-renderer'); if (container) container.style.display = 'none'; }); // XPath Specific Removal const result = document.evaluate(CONFIG.xpaths.sidebarPremium, document, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null); const element = result.singleNodeValue; if (element && element.style.display !== "none") { element.style.display = "none"; } } /** * Initializes Observers and Intervals. */ startObservers() { const runChecks = () => this.cleanDOM(); window.addEventListener('load', runChecks); setInterval(runChecks, CONFIG.checkInterval); const observer = new MutationObserver((mutations) => { let shouldCheck = false; for (const mutation of mutations) { if (mutation.addedNodes.length) shouldCheck = true; } if (shouldCheck) runChecks(); }); observer.observe(document.body || document.documentElement, { childList: true, subtree: true }); } } // Bootstrap if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', () => new YouTubeEnhancer()); } else { new YouTubeEnhancer(); } })(window);