Greasy Fork is available in English.

YouTube Web Tweaks

This script optimizes YouTube's performance by modified configs, shorts redirect and much more!

// ==UserScript==
// @name         YouTube Web Tweaks
// @version      4.1.3
// @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
// ==/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,
  kevlar_watch_cinematics: 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 });

// Other adjustments to be tweaked (Re-adding Explore tab, redirecting shorts to watch, wtc...)
function waitForElm(selector) {
    return new Promise(resolve => {
        if (document.querySelector(selector)) {
            return resolve(document.querySelector(selector));
        }

        const observer = new MutationObserver(mutations => {
            if (document.querySelector(selector)) {
                resolve(document.querySelector(selector));
                observer.disconnect();
            }
        });

        observer.observe(document.body, {
            childList: true,
            subtree: true
        });
    });
}

    function restoreTrending() {

        var trendingData = {
            "navigationEndpoint": {
                "clickTrackingParams": "CBwQtSwYASITCNqYh-qO_fACFcoRrQYdP44D9Q==",
                "commandMetadata": {
                    "webCommandMetadata": {
                        "url": "/feed/explore",
                        "webPageType": "WEB_PAGE_TYPE_BROWSE",
                        "rootVe": 6827,
                        "apiUrl": "/youtubei/v1/browse"
                    }
                },
                "browseEndpoint": {
                    "browseId": "FEtrending"
                }
            },
            "icon": {
                "iconType": "EXPLORE"
            },
            "trackingParams": "CBwQtSwYASITCNqYh-qO_fACFcoRrQYdP44D9Q==",
            "formattedTitle": {
                "simpleText": "Explore"
            },
            "accessibility": {
                "accessibilityData": {
                    "label": "Explore"
                }
            },
            "isPrimary": true
        };

        var guidetemplate = `<ytd-guide-entry-renderer class="style-scope ytd-guide-section-renderer" is-primary="" line-end-style="none"><!--css-build:shady--><a id="endpoint" class="yt-simple-endpoint style-scope ytd-guide-entry-renderer" tabindex="-1" role="tablist"><tp-yt-paper-item role="tab" class="style-scope ytd-guide-entry-renderer" tabindex="0" aria-disabled="false"><!--css-build:shady--><yt-icon class="guide-icon style-scope ytd-guide-entry-renderer" disable-upgrade=""></yt-icon><yt-img-shadow height="24" width="24" class="style-scope ytd-guide-entry-renderer" disable-upgrade=""></yt-img-shadow><yt-formatted-string class="title style-scope ytd-guide-entry-renderer"><!--css-build:shady--></yt-formatted-string><span class="guide-entry-count style-scope ytd-guide-entry-renderer"></span><yt-icon class="guide-entry-badge style-scope ytd-guide-entry-renderer" disable-upgrade=""></yt-icon><div id="newness-dot" class="style-scope ytd-guide-entry-renderer"></div></tp-yt-paper-item></a><yt-interaction class="style-scope ytd-guide-entry-renderer"><!--css-build:shady--><div class="stroke style-scope yt-interaction"></div><div class="fill style-scope yt-interaction"></div></yt-interaction></ytd-guide-entry-renderer>`;
        document.querySelector(`#items > ytd-guide-entry-renderer:nth-child(2)`).data = trendingData;

        var miniguidetemplate = `<ytd-mini-guide-entry-renderer class="style-scope ytd-mini-guide-section-renderer" is-primary="" line-end-style="none"><!--css-build:shady--><a id="endpoint" class="yt-simple-endpoint style-scope ytd-guide-entry-renderer" tabindex="-1" role="tablist"><tp-yt-paper-item role="tab" class="style-scope ytd-guide-entry-renderer" tabindex="0" aria-disabled="false"><!--css-build:shady--><yt-icon class="guide-icon style-scope ytd-guide-entry-renderer" disable-upgrade=""></yt-icon><yt-img-shadow height="24" width="24" class="style-scope ytd-guide-entry-renderer" disable-upgrade=""></yt-img-shadow><yt-formatted-string class="title style-scope ytd-guide-entry-renderer"><!--css-build:shady--></yt-formatted-string><span class="guide-entry-count style-scope ytd-guide-entry-renderer"></span><yt-icon class="guide-entry-badge style-scope ytd-guide-entry-renderer" disable-upgrade=""></yt-icon><div id="newness-dot" class="style-scope ytd-guide-entry-renderer"></div></tp-yt-paper-item></a><yt-interaction class="style-scope ytd-guide-entry-renderer"><!--css-build:shady--><div class="stroke style-scope yt-interaction"></div><div class="fill style-scope yt-interaction"></div></yt-interaction></ytd-guide-entry-renderer>`;
        document.querySelector(`#items > ytd-mini-guide-entry-renderer:nth-child(2)`).data = trendingData;

    }


waitForElm("#items.ytd-guide-section-renderer").then((elm) => {
    restoreTrending();
});

waitForElm("#items.ytd-mini-guide-section-renderer").then((elm) => {
    restoreTrending();
});


(function() {
let css = `
/* Remove Shorts, Trending, Podcasts and Shopping buttons to make the sidebar less almost prior to late 2022 */
#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;
}

#endpoint.yt-simple-endpoint.ytd-guide-entry-renderer.style-scope[title="Trending"] {
display: none !important;
}

#endpoint.yt-simple-endpoint.ytd-guide-entry-renderer.style-scope[title="Podcasts"] {
display: none !important;
}

ytd-guide-entry-renderer > a[href*="/channel/UCkYQyvc_i9hXEo4xic9Hh2g"] {
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;
}`;
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);
};
 
// 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<Element>}
 */
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 <kirasicecreamm@gmail.com>
 * @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<bool>} */
    static async isPolymer()
    {
        /** @return {Promise<void>} */
        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<void|string>} */
    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<void>}
 */
async function main()
{
    // If not Polymer, nothing happens
    if (await YtdTools.isPolymer())
    {
        ShortsTools.watchForShorts();
 
        document.addEventListener("yt-page-data-updated", onDataUpdate);
    }
}
 
main();
 
})();

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);