// ==UserScript== // @name YouTube Web Tweaks // @version 4.2.0 // @description This script optimizes YouTube's performance by modified configs, shorts redirect and much more! // @author Magma_Craft // @license MIT // @match *://www.youtube.com/* // @namespace https://greasyfork.org/en/users/933798 // @icon https://www.youtube.com/favicon.ico // @unwrap // @run-at document-end // @unwrap // @grant none // @require https://cdnjs.cloudflare.com/ajax/libs/arrive/2.4.1/arrive.min.js // @downloadURL https://update.greasyfork.icu/scripts/447802/YouTube%20Web%20Tweaks.user.js // @updateURL https://update.greasyfork.icu/scripts/447802/YouTube%20Web%20Tweaks.meta.js // ==/UserScript== // Enable strict mode to catch common coding mistakes "use strict"; // Define the flags to assign to the EXPERIMENT_FLAGS object const flagsToAssign = { // Standard tweaks (YT config editor + Disable animations) IS_TABLET: true, DISABLE_YT_IMG_DELAY_LOADING: true, polymer_verifiy_app_state: false, desktop_delay_player_resizing: false, web_animated_actions: false, web_animated_like: false, web_animated_like_lazy_load: false, render_unicode_emojis_as_small_images: true, smartimation_background: false, kevlar_refresh_on_theme_change: false, // Disable cinematics (aka ambient lighting) kevlar_measure_ambient_mode_idle: false, kevlar_watch_cinematics_invisible: false, web_cinematic_theater_mode: false, web_cinematic_fullscreen: false, enable_cinematic_blur_desktop_loading: false, kevlar_watch_cinematics: false, web_cinematic_masthead: false, web_watch_cinematics_preferred_reduced_motion_default_disabled: false }; const updateFlags = () => { // Check if the EXPERIMENT_FLAGS object exists in the window.yt.config_ property chain const expFlags = window?.yt?.config_?.EXPERIMENT_FLAGS; // If EXPERIMENT_FLAGS is not found, exit the function if (!expFlags) return; // Assign the defined flags to the EXPERIMENT_FLAGS object Object.assign(expFlags, flagsToAssign); }; // Create a MutationObserver that calls the updateFlags function when changes occur in the document's subtree const mutationObserver = new MutationObserver(updateFlags); mutationObserver.observe(document, { subtree: true, childList: true }); // Fully replace shorts links with regular videos /** * Shorts URL redirect. * * This is called on initial visit only. Successive navigations * are managed by modifying the YouTube Desktop application. */ (function(){ /** @type {string} */ var path = window.location.pathname; if (0 == path.search("/shorts")) { // Extract the video ID from the shorts link and redirect. /** @type {string} */ var id = path.replace(/\/|shorts|\?.*/g, ""); window.location.replace("https://www.youtube.com/watch?v=" + id); } })(); /** * YouTube Desktop Shorts remover. * * If the initial URL was not a shorts link, traditional redirection * will not work. This instead modifies video elements to replace them with * regular links. */ (function(){ /** * @param {string} selector (CSS-style) of the element * @return {Promise} */ async function querySelectorAsync(selector) { while (null == document.querySelector(selector)) { // Pause for a frame and let other code go on. await new Promise(r => requestAnimationFrame(r)); } return document.querySelector(selector); } /** * Small toolset for interacting with the Polymer * YouTube Desktop application. * * @author Taniko Yamamoto * @version 1.0 */ class YtdTools { /** @type {string} Page data updated event */ static EVT_DATA_UPDATE = "yt-page-data-updated"; /** @type {Element} Main YT Polymer manager */ static YtdApp; /** @type {bool} */ static hasInitialLoaded = false; /** @return {Promise} */ static async isPolymer() { /** @return {Promise} */ function waitForBody() // nice hack lazy ass { return new Promise(r => { document.addEventListener("DOMContentLoaded", function a(){ document.removeEventListener("DOMContentLoaded", a); r(); }); }); } await waitForBody(); if ("undefined" != typeof document.querySelector("ytd-app")) { this.YtdApp = document.querySelector("ytd-app"); return true; } return false; } /** @async @return {Promise} */ static waitForInitialLoad() { var updateEvent = this.EVT_DATA_UPDATE; return new Promise((resolve, reject) => { if (!this.isPolymer()) { reject("Not Polymer :("); } function _listenerCb() { document.removeEventListener(updateEvent, _listenerCb); resolve(); } document.addEventListener(updateEvent, _listenerCb); }); } /** @return {string} */ static getPageType() { return this.YtdApp.data.page; } } class ShortsTools { /** @type {MutationObserver} */ static mo = new MutationObserver(muts => { muts.forEach(mut => { Array.from(mut.addedNodes).forEach(node => { if (node instanceof HTMLElement) { this.onMutation(node); } }); }); }); /** @return {void} */ static watchForShorts() { /* this.mo.observe(YtdTools.YtdApp, { childList: true, subtree: true }); */ var me = this; YtdTools.YtdApp.arrive("ytd-video-renderer, ytd-grid-video-renderer", function() { me.onMutation(this); // This is literally the worst hack I ever wrote, but it works ig... (new MutationObserver(function(){ if (me.isShortsRenderer(this)) { me.onMutation(this); } }.bind(this))).observe(this, {"subtree": true, "childList": true, "characterData": "true"}); }); } /** @return {void} */ static stopWatchingForShorts() { this.mo.disconnect(); } /** * @param {HTMLElement} node * @return {void} */ static onMutation(node) { if (node.tagName.search("VIDEO-RENDERER") > -1 && this.isShortsRenderer(node)) { this.transformShortsRenderer(node); } } /** @return {bool} */ static isShortsRenderer(videoRenderer) { return "WEB_PAGE_TYPE_SHORTS" == videoRenderer?.data?.navigationEndpoint?.commandMetadata?.webCommandMetadata?.webPageType; } /** @return {string} */ static extractLengthFromA11y(videoData) { // A11y = {title} by {creator} {date} {*length*} {viewCount} - play Short // tho hopefully this works in more than just English var a11yTitle = videoData.title.accessibility.accessibilityData.label; var publishedTimeText = videoData.publishedTimeText.simpleText; var viewCountText = videoData.viewCountText.simpleText; var isolatedLengthStr = a11yTitle.split(publishedTimeText)[1].split(viewCountText)[0] .replace(/\s/g, ""); var numbers = isolatedLengthStr.split(/\D/g); var string = ""; // Remove all empties before iterating it for (var i = 0; i < numbers.length; i++) { if ("" === numbers[i]) { numbers.splice(i, 1); i--; } } for (var i = 0; i < numbers.length; i++) { // Lazy 0 handling idc im tired if (1 == numbers.length) { string += "0:"; if (1 == numbers[i].length) { string += "0" + numbers[i]; } else { string += numbers[i]; } break; } if (0 != i) string += ":"; if (0 != i && 1 == numbers[i].length) string += "0"; string += numbers[i]; } return string; } /** * @param {HTMLElement} videoRenderer * @return {void} */ static transformShortsRenderer(videoRenderer) { /** @type {string} */ var originalOuterHTML = videoRenderer.outerHTML; /** @type {string} */ var lengthText = videoRenderer.data?.lengthText?.simpleText ?? this.extractLengthFromA11y(videoRenderer.data); /** @type {string} */ var lengthA11y = videoRenderer.data?.lengthText?.accessibility?.accessibilityData?.label ?? ""; /** @type {string} */ var originalHref = videoRenderer.data.navigationEndpoint.commandMetadata.webCommandMetadata.url; var href = "/watch?v=" + originalHref.replace(/\/|shorts|\?.*/g, ""); var reelWatchEndpoint = videoRenderer.data.navigationEndpoint.reelWatchEndpoint; var i; videoRenderer.data.thumbnailOverlays.forEach((a, index) =>{ if ("thumbnailOverlayTimeStatusRenderer" in a) { i = index; } }); // Set the thumbnail overlay style videoRenderer.data.thumbnailOverlays[i].thumbnailOverlayTimeStatusRenderer.style = "DEFAULT"; delete videoRenderer.data.thumbnailOverlays[i].thumbnailOverlayTimeStatusRenderer.icon; // Set the thumbnail overlay text videoRenderer.data.thumbnailOverlays[i].thumbnailOverlayTimeStatusRenderer.text.simpleText = lengthText; // Set the thumbnail overlay accessibility label videoRenderer.data.thumbnailOverlays[i].thumbnailOverlayTimeStatusRenderer.text.accessibility.accessibilityData.label = lengthA11y; // Set the navigation endpoint metadata (used for middle click) videoRenderer.data.navigationEndpoint.commandMetadata.webCommandMetadata.webPageType = "WEB_PAGE_TYPE_WATCH"; videoRenderer.data.navigationEndpoint.commandMetadata.webCommandMetadata.url = href; videoRenderer.data.navigationEndpoint.watchEndpoint = { "videoId": reelWatchEndpoint.videoId, "playerParams": reelWatchEndpoint.playerParams, "params": reelWatchEndpoint.params }; delete videoRenderer.data.navigationEndpoint.reelWatchEndpoint; //var _ = videoRenderer.data; videoRenderer.data = {}; videoRenderer.data = _; // Sometimes the old school data cycle trick fails, // however this always works. var _ = videoRenderer.cloneNode(); _.data = videoRenderer.data; for (var i in videoRenderer.properties) { _[i] = videoRenderer[i]; } videoRenderer.insertAdjacentElement("afterend", _); videoRenderer.remove(); } } /** * Sometimes elements are reused on page updates, so fix that * * @return {void} */ function onDataUpdate() { var videos = document.querySelectorAll("ytd-video-renderer, ytd-grid-video-renderer"); for (var i = 0, l = videos.length; i < l; i++) if (ShortsTools.isShortsRenderer(videos[i])) { ShortsTools.transformShortsRenderer(videos[i]); } } /** * I hope she makes lotsa spaghetti :D * @async @return {Promise} */ async function main() { // If not Polymer, nothing happens if (await YtdTools.isPolymer()) { ShortsTools.watchForShorts(); document.addEventListener("yt-page-data-updated", onDataUpdate); } } main(); })(); // Other tweaks to be added (CSS codes, skip ads, etc...) (function() { let css = ` /* Remove 'Shorts' tab */ #endpoint.yt-simple-endpoint.ytd-guide-entry-renderer.style-scope[title="Shorts"], a.yt-simple-endpoint.style-scope.ytd-mini-guide-entry-renderer[title="Shorts"] { display: none !important; } /* Remove filter categories on search results and playlists to make the UI less usable on low-entry machines */ ytd-item-section-renderer.style-scope.ytd-section-list-renderer[page-subtype="playlist"] > #header.ytd-item-section-renderer > ytd-feed-filter-chip-bar-renderer { display: none !important; } div#chip-bar.style-scope.ytd-search-header-renderer > yt-chip-cloud-renderer.style-scope.ytd-search-header-renderer > div#container.style-scope.yt-chip-cloud-renderer { display: none !important; } /* Remove all annoyances (excludes 'YT TV and Premium' banners) */ ytd-action-companion-ad-renderer, ytd-display-ad-renderer, ytd-video-masthead-ad-advertiser-info-renderer, ytd-video-masthead-ad-primary-video-renderer, ytd-in-feed-ad-layout-renderer, ytd-ad-slot-renderer, yt-about-this-ad-renderer, yt-mealbar-promo-renderer, ytd-ad-slot-renderer, ytd-in-feed-ad-layout-renderer, .ytd-video-masthead-ad-v3-renderer, div#root.style-scope.ytd-display-ad-renderer.yt-simple-endpoint, div#sparkles-container.style-scope.ytd-promoted-sparkles-web-renderer, div#main-container.style-scope.ytd-promoted-video-renderer, #player-ads, .ytwPanelAdHeaderImageLockupViewModelHost, ytd-ads-engagement-panel-content-renderer, #content.ytd-ads-engagement-panel-content-renderer, ytd-engagement-panel-section-list-renderer[target-id="engagement-panel-ads"], ad-slot-renderer, ytm-promoted-sparkles-web-renderer, masthead-ad, #masthead-ad, ytd-video-quality-promo-renderer, #yt-lang-alert-container, .YtmPaidContentOverlayHost, .ytd-primetime-promo-renderer, ytd-brand-video-singleton-renderer, #yt-feedback, #yt-hitchhiker-feedback, ytd-merch-shelf-renderer, ytd-enforcement-message-view-model, div[is-shared-heimdall], tp-yt-iron-overlay-backdrop.opened, ytd-promoted-sparkles-web-renderer, ytd-text-image-no-button-layout-renderer, #cinematics.ytd-watch-flexy { display: none !important } #movie_player.ad-showing video { filter: blur(100px) opacity(0.25) grayscale(0.5); } #movie_player.ad-showing .ytp-title, #movie_player.ad-showing .ytp-title-channel, .ytp-visit-advertiser-link, .ytp-ad-visit-advertiser-button, ytmusic-app:has(#movie_player.ad-showing) ytmusic-player-bar :is(.title, .subtitle) { filter: blur(4px) opacity(0.5) grayscale(0.5); transition: 0.05s filter linear; } :is(#movie_player.ad-showing .ytp-title,#movie_player.ad-showing .ytp-title-channel,.ytp-visit-advertiser-link,.ytp-ad-visit-advertiser-button,ytmusic-app:has(#movie_player.ad-showing) ytmusic-player-bar :is(.title,.subtitle)):is(:hover,:focus-within) { filter: none; } .ytp-suggested-action-badge { visibility: hidden !important; } ytd-watch-metadata.ytd-watch-flexy { padding-bottom: 36px !important; } /* Disable infinite scrolling (partially broken when YouTube changes their backend) */ #related ytd-compact-video-renderer, #related ytd-compact-playlist-renderer, #related ytd-compact-radio-renderer, #related ytd-compact-movie-renderer, #related yt-lockup-view-model, #related ytd-channel-renderer, #related ytd-continuation-item-renderer, #related #continuations { display: none !important } #related ytd-compact-video-renderer:nth-of-type(1), #related yt-lockup-view-model:nth-of-type(1), #related ytd-compact-video-renderer:nth-of-type(2), #related yt-lockup-view-model:nth-of-type(2), #related ytd-compact-video-renderer:nth-of-type(3), #related yt-lockup-view-model:nth-of-type(3), #related ytd-compact-video-renderer:nth-of-type(4), #related yt-lockup-view-model:nth-of-type(4), #related ytd-compact-video-renderer:nth-of-type(5), #related yt-lockup-view-model:nth-of-type(5), #related ytd-compact-video-renderer:nth-of-type(6), #related yt-lockup-view-model:nth-of-type(6), #secondary #related ytd-compact-video-renderer:nth-of-type(7), #secondary #related yt-lockup-view-model:nth-of-type(7), #secondary #related ytd-compact-video-renderer:nth-of-type(8), #secondary #related yt-lockup-view-model:nth-of-type(8), #secondary #related ytd-compact-video-renderer:nth-of-type(9), #secondary #related yt-lockup-view-model:nth-of-type(9), #secondary #related ytd-compact-video-renderer:nth-of-type(10), #secondary #related yt-lockup-view-model:nth-of-type(10) { display: flex !important } /* More tweaks to the UI (this was meant for older browsers without uBlock Origin) */ #secondary.ytd-watch-grid { width: 402px !important; min-width: 300px !important } ytd-watch-flexy[default-layout][reduced-top-margin] #primary.ytd-watch-flexy, ytd-watch-flexy[default-layout][reduced-top-margin] #secondary.ytd-watch-flexy { padding-top: var(--ytd-margin-6x) !important } ytd-watch-metadata[title-headline-xs] h1.ytd-watch-metadata, ytd-watch-metadata[title-headline-m] h1.ytd-watch-metadata { font-size: 2rem !important; line-height: 2.8rem !important } ytd-search ytd-video-renderer, ytd-search ytd-channel-renderer, ytd-search ytd-playlist-renderer, ytd-search ytd-radio-renderer, ytd-search ytd-movie-renderer, ytd-video-renderer.style-scope.ytd-item-section-renderer, ytd-playlist-renderer.style-scope.ytd-item-section-renderer, ytd-search .lockup.ytd-item-section-renderer { margin-top: 16px !important } ytd-compact-video-renderer.style-scope.ytd-item-section-renderer, #related .lockup.ytd-item-section-renderer { margin-top: 8px !important }`; if (typeof GM_addStyle !== "undefined") { GM_addStyle(css); } else { let styleNode = document.createElement("style"); styleNode.appendChild(document.createTextNode(css)); (document.querySelector("head") || document.documentElement).appendChild(styleNode); } })(); var oldHref = document.location.href; if (window.location.href.indexOf('youtube.com/shorts') > -1) { window.location.replace(window.location.toString().replace('/shorts/', '/watch?v=')); } window.onload = function() { var bodyList = document.querySelector("body") var observer = new MutationObserver(function(mutations) { mutations.forEach(function(mutation) { if (oldHref != document.location.href) { oldHref = document.location.href; console.log('location changed!'); if (window.location.href.indexOf('youtube.com/shorts') > -1) { window.location.replace(window.location.toString().replace('/shorts/', '/watch?v=')); } } }); }); var config = { childList: true, subtree: true }; observer.observe(bodyList, config); }; (() => { let popupState = 0; let popupElement = null; const rate = 1; const Promise = (async () => { })().constructor; const PromiseExternal = ((resolve_, reject_) => { const h = (resolve, reject) => { resolve_ = resolve; reject_ = reject }; return class PromiseExternal extends Promise { constructor(cb = h) { super(cb); if (cb === h) { /** @type {(value: any) => void} */ this.resolve = resolve_; /** @type {(reason?: any) => void} */ this.reject = reject_; } } }; })(); const insp = o => o ? (o.polymerController || o.inst || o || 0) : (o || 0); let vload = null; const fastSeekFn = HTMLVideoElement.prototype.fastSeek || null; const addEventListenerFn = HTMLElement.prototype.addEventListener; if (!addEventListenerFn) return; const removeEventListenerFn = HTMLElement.prototype.removeEventListener; if (!removeEventListenerFn) return; const ytPremiumPopupSelector = 'yt-mealbar-promo-renderer.style-scope.ytd-popup-container:not([hidden])'; const DEBUG = 0; const rand = (a, b) => a + Math.random() * (b - a); const log = DEBUG ? console.log.bind(console) : () => 0; //$0.$['dismiss-button'].click() const ytPremiumPopupClose = function () { const popup = document.querySelector(ytPremiumPopupSelector); if (popup instanceof HTMLElement) { if (HTMLElement.prototype.closest.call(popup, '[hidden]')) return; const cnt = insp(popup); const btn = cnt.$ ? cnt.$['dismiss-button'] : 0; if (btn instanceof HTMLElement && HTMLElement.prototype.closest.call(btn, '[hidden]')) return; btn && btn.click(); } } //div.video-ads.ytp-ad-module const clickSkip = function () { // ytp-ad-skip-button const isAdsContainerContainsButton = document.querySelector('.video-ads.ytp-ad-module button'); if (isAdsContainerContainsButton) { const btnFilter = e => HTMLElement.prototype.matches.call(e, ".ytp-ad-overlay-close-button, .ytp-ad-skip-button-modern, .ytp-ad-skip-button") && !HTMLElement.prototype.closest.call(e, '[hidden]'); const btns = [...document.querySelectorAll('.video-ads.ytp-ad-module button[class*="ytp-ad-"]')].filter(btnFilter); console.log('# of ads skip btns', btns.length); if (btns.length !== 1) return; const btn = btns[0]; if (btn instanceof HTMLElement) { btn.click(); } } }; const adsEndHandlerHolder = function (evt) { adsEndHandler && adsEndHandler(evt); } let adsEndHandler = null; const videoPlayingHandler = async function (evt) { try { if (!evt || !evt.target || !evt.isTrusted || !(evt instanceof Event)) return; const video = evt.target; const checkPopup = popupState === 1; popupState = 0; const popupElementValue = popupElement; popupElement = null; if (video.duration < 0.8) return; await vload.then(); if (!video.isConnected) return; const ytplayer = HTMLElement.prototype.closest.call(video, 'ytd-player, ytmusic-player'); if (!ytplayer || !ytplayer.is) return; const ytplayerCnt = insp(ytplayer); const player_ = await (ytplayerCnt.player_ || ytplayer.player_ || ytplayerCnt.playerApi || ytplayer.playerApi || 0); if (!player_) return; if (typeof ytplayerCnt.getPlayer === 'function' && !ytplayerCnt.getPlayer()) { await new Promise(r => setTimeout(r, 40)); } const playerController = await ytplayerCnt.getPlayer() || player_; if (!video.isConnected) return; if ('getPresentingPlayerType' in playerController && 'getDuration' in playerController) { const ppType = await playerController.getPresentingPlayerType(); log('m02a', ppType); if (ppType === 1 || typeof ppType !== 'number') return; // ads shall be ppType === 2 // const progressState = player_.getProgressState(); // log('m02b', progressState); // if(!progressState) return; // const q = progressState.duration; // if (popupState === 1) console.debug('m05b:ytPremiumPopup', document.querySelector(ytPremiumPopupSelector)) const q = video.duration; const ytDuration = await playerController.getDuration(); log('m02c', q, ytDuration, Math.abs(ytDuration - q)); if (q > 0.8 && ytDuration > 2.5 && Math.abs(ytDuration - q) > 1.4) { try { log('m02s', 'fastSeek', q); video.muted = true; const w = Math.round(rand(582, 637) * rate); const sq = q - w / 1000; adsEndHandler = null; const expired = Date.now() + 968; removeEventListenerFn.call(video, 'ended', adsEndHandlerHolder, false); removeEventListenerFn.call(video, 'suspend', adsEndHandlerHolder, false); removeEventListenerFn.call(video, 'durationchange', adsEndHandlerHolder, false); addEventListenerFn.call(video, 'ended', adsEndHandlerHolder, false); addEventListenerFn.call(video, 'suspend', adsEndHandlerHolder, false); addEventListenerFn.call(video, 'durationchange', adsEndHandlerHolder, false); adsEndHandler = async function (evt) { adsEndHandler = null; removeEventListenerFn.call(video, 'ended', adsEndHandlerHolder, false); removeEventListenerFn.call(video, 'suspend', adsEndHandlerHolder, false); removeEventListenerFn.call(video, 'durationchange', adsEndHandlerHolder, false); if (Date.now() < expired) { const delay = Math.round(rand(92, 117)); await new Promise(r => setTimeout(r, delay)); Promise.resolve().then(() => { clickSkip(); }).catch(console.warn); checkPopup && Promise.resolve().then(() => { const currentPopup = document.querySelector(ytPremiumPopupSelector); if (popupElementValue ? currentPopup === popupElementValue : currentPopup) { ytPremiumPopupClose(); } }).catch(console.warn); } }; if (fastSeekFn) fastSeekFn.call(video, sq); else video.currentTime = sq; } catch (e) { console.warn(e); } } } } catch (e) { console.warn(e); } }; document.addEventListener('loadedmetadata', async function (evt) { try { if (!evt || !evt.target || !evt.isTrusted || !(evt instanceof Event)) return; const video = evt.target; if (video.nodeName !== "VIDEO") return; if (video.duration < 0.8) return; if (!video.matches('.video-stream.html5-main-video')) return; popupState = 0; vload = new PromiseExternal(); popupElement = document.querySelector(ytPremiumPopupSelector); removeEventListenerFn.call(video, 'playing', videoPlayingHandler, { passive: true, capture: false }); addEventListenerFn.call(video, 'playing', videoPlayingHandler, { passive: true, capture: false }); popupState = 1; let trial = 6; await new Promise(resolve => { let io = new IntersectionObserver(entries => { if (trial-- <= 0 || (entries && entries.length >= 1 && video.matches('ytd-player video, ytmusic-player video'))) { resolve(); io.disconnect(); io = null; } }); io.observe(video); }); vload.resolve(); } catch (e) { console.warn(e); } }, true); })(); Object.defineProperties(document, { /*'hidden': {value: false},*/ 'webkitHidden': {value: false}, 'visibilityState': {value: 'visible'}, 'webkitVisibilityState': {value: 'visible'} }); setInterval(function(){ document.dispatchEvent( new KeyboardEvent( 'keyup', { bubbles: true, cancelable: true, keyCode: 143, which: 143 } ) ); }, 60000); // // What is this userscript trying to address? // When playing a video, only a small part of the video loads, and the subsequent // parts do not load afterward. (function () { "use strict"; 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); }; })();