// ==UserScript== // @name YouTube Web Tweaks // @version 3.2.5 // @description This script optimizes YouTube's performance by modified configs, anti-shorts 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-start // @unwrap // @inject-into page // @allFrames true // @grant none // @require https://cdnjs.cloudflare.com/ajax/libs/arrive/2.4.1/arrive.min.js // @downloadURL none // ==/UserScript== // Modifiying yt.config flags to optimize loading times and disables animations (function() { window['yt'] = window['yt'] || {}; yt['config_'] = yt.config_ || {}; yt.config_['EXPERIMENT_FLAGS'] = yt.config_.EXPERIMENT_FLAGS || {}; var iv = setInterval(function() { yt.config_.EXPERIMENT_FLAGS.polymer_verifiy_app_state = false; yt.config_.EXPERIMENT_FLAGS.desktop_delay_player_resizing = false; yt.config_.EXPERIMENT_FLAGS.warm_load_nav_start_web = false; yt.config_.EXPERIMENT_FLAGS.web_animated_like = false; yt.config_.EXPERIMENT_FLAGS.web_animated_like_lazy_load = false; yt.config_.EXPERIMENT_FLAGS.render_unicode_emojis_as_small_images = true; yt.config_.EXPERIMENT_FLAGS.kevlar_refresh_on_theme_change = false; //disable reload when changing theme yt.config_.EXPERIMENT_FLAGS.kevlar_watch_cinematics = false; }, 1); var to = setTimeout(function() { clearInterval(iv); }, 1000) })(); ((__CONTEXT01__) => { const win = this instanceof Window ? this : window; // Create a unique key for the script and check if it is already running const hkey_script = 'ikkaorpwuzvt'; if (win[hkey_script]) throw new Error('Duplicated Userscript Calling'); // avoid duplicated scripting win[hkey_script] = true; /** @type {globalThis.PromiseConstructor} */ const Promise = ((async () => { })()).constructor; const cleanContext = async (win) => { const waitFn = requestAnimationFrame; // shall have been binded to window try { let mx = 16; // MAX TRIAL const frameId = 'vanillajs-iframe-v1' let frame = document.getElementById(frameId); let removeIframeFn = null; if (!frame) { frame = document.createElement('iframe'); frame.id = 'vanillajs-iframe-v1'; frame.sandbox = 'allow-same-origin'; // script cannot be run inside iframe but API can be obtained from iframe let n = document.createElement('noscript'); // wrap into NOSCRPIT to avoid reflow (layouting) n.appendChild(frame); while (!document.documentElement && mx-- > 0) await new Promise(waitFn); // requestAnimationFrame here could get modified by YouTube engine const root = document.documentElement; root.appendChild(n); // throw error if root is null due to exceeding MAX TRIAL removeIframeFn = (setTimeout) => { const removeIframeOnDocumentReady = (e) => { e && win.removeEventListener("DOMContentLoaded", removeIframeOnDocumentReady, false); win = null; setTimeout(() => { n.remove(); n = null; }, 200); } if (document.readyState !== 'loading') { removeIframeOnDocumentReady(); } else { win.addEventListener("DOMContentLoaded", removeIframeOnDocumentReady, false); } } } while (!frame.contentWindow && mx-- > 0) await new Promise(waitFn); const fc = frame.contentWindow; if (!fc) throw "window is not found."; // throw error if root is null due to exceeding MAX TRIAL const { requestAnimationFrame, cancelAnimationFrame, getComputedStyle, setInterval, clearInterval, setTimeout, clearTimeout } = fc; const res = { requestAnimationFrame, cancelAnimationFrame, getComputedStyle, setInterval, clearInterval, setTimeout, clearTimeout }; for (let k in res) res[k] = res[k].bind(win); // necessary res.animate = fc.Element.prototype.animate; if (removeIframeFn) Promise.resolve(res.setTimeout).then(removeIframeFn); return res; } catch (e) { console.warn(e); return null; } }; cleanContext(win).then(__CONTEXT02__ => { const { requestAnimationFrame, cancelAnimationFrame, getComputedStyle, setInterval, clearInterval, setTimeout, clearTimeout } = __CONTEXT02__; const { animate } = __CONTEXT02__; const { frames, defineProperty, window, CDATASection, ProcessingInstruction, FocusEvent } = __CONTEXT01__; const ENABLE_NATIVE_CONSTRUCTOR_CHECK = false; let cids = {}; function cleanCId(k) { Promise.resolve().then(() => clearInterval(cids[k])); } Object.defineProperty = function (o, p, opts) { if (arguments.length !== 3) return defineProperty.apply(this, arguments); if (o instanceof Window) { if (p === 'getComputedStyle') return; if (p === 'Promise' && (p in o)) return; // WaterFox Classic if (p === 'customElements' || p === 'Polymer') { if (p in o) return; // duplicate declaration? } const value = opts.value; if (value) { opts.writable = true; opts.configurable = true; opts.enumerable = true; } if (p === 'ytInitialPlayerResponse' || p === 'playerResponse') { // Firefox Chatroom? TBC } else { console.log(923, 'window[p]=', p, opts); } return defineProperty.call(this, o, p, opts); } const nativeConstructorCheck = ENABLE_NATIVE_CONSTRUCTOR_CHECK ? (o.constructor + "").indexOf('native code') > 0 : true; if (p.startsWith('__shady_')) { const { get, value } = opts; if (!get) { o[p] = value; return; } if (p === '__shady_native_eventPhase') { // Event -> __shady_native_eventPhase return defineProperty.call(this, o, p, opts); } let constructor = o instanceof Node ? Node : o instanceof DocumentFragment ? DocumentFragment : o instanceof Document ? Document : null; if (!constructor) { let constructorName = (o.constructor || 0).name; if (constructorName === 'Node') { constructor = Node; } } if (constructor && opts && (typeof opts.get === 'function')) { if (!(p in o.constructor.prototype) && !(p in o)) { defineProperty.call(this, o.constructor.prototype, p, opts); } return; } console.log(926, o, p, opts, !!constructor, !!opts, !!(typeof opts.get === 'function')) // return; } if ((p in o) && nativeConstructorCheck) { if (o instanceof Text) return; if (o instanceof Comment) return; if (CDATASection && o instanceof CDATASection) return; if (ProcessingInstruction && o instanceof ProcessingInstruction) return; if (o instanceof Event) return; if (FocusEvent && o instanceof FocusEvent) return; } return defineProperty.call(this, o, p, opts); } const asserter = (f) => Promise.resolve().then(() => console.assert(f(), f + "")); const setVJS = () => { if (window.Promise !== Promise) window.Promise = Promise; if (window.getComputedStyle !== getComputedStyle) window.getComputedStyle = getComputedStyle; if (Element.prototype.animate !== animate) Element.prototype.animate = animate; if (window.requestAnimationFrame !== requestAnimationFrame) window.requestAnimationFrame = requestAnimationFrame if (window.cancelAnimationFrame !== cancelAnimationFrame) window.cancelAnimationFrame = cancelAnimationFrame }; const finishFn = () => { cids.finish = 0; setVJS(); try { document.getElementById('zihrS').remove(); } catch (e) { } cleanCId('timeVJS'); if (document.isConnected === false) return; setTimeout(() => { if (document.isConnected === false) return; asserter(() => window.Promise === Promise); asserter(() => window.getComputedStyle === getComputedStyle); asserter(() => Element.prototype.animate === animate); asserter(() => window.requestAnimationFrame === requestAnimationFrame); asserter(() => window.cancelAnimationFrame === cancelAnimationFrame); }, 800); }; function fastenFinishFn() { if (cids.finish > 0) { clearInterval(cids.finish); cids.finish = setTimeout(finishFn, 40); } } function preFinishFn() { let mo = new MutationObserver(function () { Promise.resolve().then(fastenFinishFn) mo.disconnect(); mo.takeRecords(); mo = null; }); mo.observe(document, { subtree: true, childList: true }); return setTimeout(finishFn, 400); } cids.timeVJS = setInterval(() => { if (!cids.finish && ('Polymer' in window)) cids.finish = preFinishFn(); setVJS(); }, 1); let isInnerFrame = false; try { isInnerFrame = window !== top && window.document.domain === top.document.domain; } catch (e) { } if (!isInnerFrame) { console.groupCollapsed( "%cYouTube Native - Vanilla Engine (Experimental)", "background-color: #e0005a ; color: #ffffff ; font-weight: bold ; padding: 4px ;" ); console.log("Script is loaded."); console.log("This is an experimental script."); console.log("If you found any issue in using YouTube, please disable this script to check whether the issue is due to this script or not."); console.groupEnd(); } }); })({ frames, defineProperty: Object.defineProperty, window, CDATASection, ProcessingInstruction, FocusEvent }); // Auto redirect shorts to watch page (credit goes to YukisCoffee and Fuim) 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); }; /** * 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(); })(); // CSS adjustments and other tweaks to apply (including removal of "Video paused. Continue watching?" popup) (function() { ApplyCSS(); function ApplyCSS() { var styles = document.createElement("style"); styles.innerHTML=` /* Hide Shorts button in sidebar */ #endpoint.yt-simple-endpoint.ytd-guide-entry-renderer.style-scope[title="Shorts"] { display: none !important; } a.yt-simple-endpoint.style-scope.ytd-mini-guide-entry-renderer[title="Shorts"] { display: none !important; } /* Remove ambient light on watch page + YouTube TV popup */ cinematics.ytd-watch-flexy { display: none !important; } ytd-popup-container > tp-yt-paper-dialog > ytd-mealbar-promo-renderer, ytd-popup-container > tp-yt-paper-dialog > yt-mealbar-promo-renderer:has-text(/Claim Offer|Join now|Not Now|YouTube TV|live TV|Live TV|movies|sports|Try it free|unlimited DVR|watch NFL/) { display: none !important; }` document.head.appendChild(styles); } })(); (function () { 'use strict'; const wm = new WeakSet(); const removeAdsSlot = async (grid) => { const td = grid.data; if (td && !wm.has(td)) { const md = Object.assign({}, td); md.contents = md.contents.filter(content => { let isadSlotRenderer = ((((content || 0).richItemRenderer || 0).content || 0).adSlotRenderer || null) !== null; return isadSlotRenderer ? false : true; }); wm.add(md); grid.data = md; } } customYtElements.whenRegistered('ytd-rich-grid-renderer', (proto) => { proto.dataChanged = ((dataChanged) => { return function () { removeAdsSlot(this); return dataChanged.apply(this, arguments); } })(proto.dataChanged) }); })(); 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);