Greasy Fork is available in English.

YouTube - Recommend certain Categories only on home page

Selects desired categories on Youtube Homepage and hides undesired ones. To minimise distraction and increase productivity.

// ==UserScript==
// @name         YouTube - Recommend certain Categories only on home page
// @namespace    http://tampermonkey.net/
// @version      1.0.11
// @description  Selects desired categories on Youtube Homepage and hides undesired ones. To minimise distraction and increase productivity.
// @author       lookjaklook
// @match        *://*.youtube.com
// @match        *://*.youtube.com/
// @match        *://*.youtube.com/?*
// @match        *://youtube.com
// @match        *://youtube.com/
// @match        *://youtube.com/?*
// @icon         https://www.google.com/s2/favicons?sz=64&domain=youtube.com
// @grant        none
// @license MIT
// ==/UserScript==

var categories = ['programming', 'development', 'computer science',
                  'electronic circuits', 'physics',
                  'mathematics', 'algorithms',
                  'python', 'c++']; // Here you can choose the categories you wish to see and to be chosen automatically

/*=== Main functionality that runs at start ===*/

main();

/*=== Main functionality functions ===*/
function main() {
    console.log('YOUTUBE_CATEGORIES: started');

    hideHomeRecommendedContent(true);
    removeAllIrrelevantCategoriesFromDom({runAgainAfterMillisec: 100});
    makeSureToSwitchCategoryFromDefaultToDesired({runAgainAfterMillisec: 1000});
}


function removeAllIrrelevantCategoriesFromDom({runAgainAfterMillisec, turnOffDebug}) {
    waitForElm('yt-chip-cloud-chip-renderer, ytm-chip-cloud-chip-renderer').then((domResult) => {
        const allCategoriesDomArr = domResult.multiple;
        const hiddenCategoryNames = [];

        allCategoriesDomArr.forEach((categoryDom) => {
            const categoryName = normalizeName(categoryDom.innerText);
            let isCategoryRelevant = false;

            categories.some((category) => {
                const relevantCategoryName = normalizeName(category);
                isCategoryRelevant = categoryName.includes(relevantCategoryName);

                return isCategoryRelevant;
            });

            categoryDom.style.display = (isCategoryRelevant ? '' : 'none');

            if (!isCategoryRelevant)
                hiddenCategoryNames.push(categoryName);
        });

        if (!turnOffDebug)
            console.log(`YOUTUBE_CATEGORIES: hidden categories: ${hiddenCategoryNames}`);

        if (runAgainAfterMillisec)
            setTimeout(function() {removeAllIrrelevantCategoriesFromDom({runAgainAfterMillisec: runAgainAfterMillisec, turnOffDebug: true})}, runAgainAfterMillisec);
    });
}


var isStartedCategorySwitch = false;
function makeSureToSwitchCategoryFromDefaultToDesired({runAgainAfterMillisec}) {
    waitForElm(getSelectedCategoryDomCssSelectorStr()).then((domResult) => {
        if (runAgainAfterMillisec)
            setTimeout(() =>
                       makeSureToSwitchCategoryFromDefaultToDesired({runAgainAfterMillisec: runAgainAfterMillisec}),
                       runAgainAfterMillisec);

        let isDefaultCategorySelected;
        const selectedCategoryDomArr = domResult.multiple;

        if (selectedCategoryDomArr.length < 1 || isStartedCategorySwitch)
            return;

        isDefaultCategorySelected = true;

        Array.from(selectedCategoryDomArr).some((selectedCategoryDom) => {
            const selectedCategoryName = normalizeName(selectedCategoryDom.innerText);
            isDefaultCategorySelected = (selectedCategoryName === 'all');

            return (!isDefaultCategorySelected);
        });

        if (!isDefaultCategorySelected)
            return;

        console.log('YOUTUBE_CATEGORIES: current category is "all", switching category');

        startCategorySwitch();
    })
}


var currSelectedCategory;
function startCategorySwitch() {
    if (isStartedCategorySwitch)
        return;

    currSelectedCategory = null;
    isStartedCategorySwitch = true;
    hideHomeRecommendedContent(true);
    selectRandomRecommendedCategoryUntilSucceed();
}


function selectRandomRecommendedCategoryUntilSucceed() {
    const categorySelectionInterval = setInterval(selectUnselectedCategory, 10)

    function selectUnselectedCategory() {
        if (currSelectedCategory && isSelectedCategoryNameMatches(currSelectedCategory))
            return;

        let desiredCategory;

        do {
            desiredCategory = normalizeName(categories.random());
        } while (isSelectedCategoryNameMatches(desiredCategory));

        if (isSelectedCategoryNameMatches(desiredCategory))
            return;

        currSelectedCategory = desiredCategory;
        const isDesiredCategorySelected = selectCategory(desiredCategory).isCategoryFoundAndClicked;

        if (!isDesiredCategorySelected) {
            currSelectedCategory = null;
            return;
        }

        const timeUntilOldCategoryContentIsReplacedByNew = 2000;
        setTimeout(() => hideHomeRecommendedContent(false), timeUntilOldCategoryContentIsReplacedByNew);
        isStartedCategorySwitch = false;
        clearInterval(categorySelectionInterval);
    }
}


function selectCategory(category) {
    const returnObject = {isCategoryFoundAndClicked: true};
    const xpathTextLowerCase = "translate(., 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', 'abcdefghijklmnopqrstuvwxyz')";
    const xpathCategoryForDesktop = '//yt-chip-cloud-chip-renderer[contains(' + xpathTextLowerCase + ', "' + category + '")]';
    const xpathCategoryForMobile = xpathCategoryForDesktop.replace('//yt-', '//ytm-');
    let categoryDom = getElementByXpath(xpathCategoryForDesktop);

    if (!categoryDom)
        categoryDom = getElementByXpath(xpathCategoryForMobile);

    if (categoryDom) {
        categoryDom.click();
        console.log(`YOUTUBE_CATEGORIES: clicked on desired category "${category}"`);
        returnObject.isCategoryFoundAndClicked = true;
    }
    else
        returnObject.isCategoryFoundAndClicked = false;

    return returnObject;
}

function isSelectedCategoryNameMatches(categoryName) {
    const selectedCategoryDomArr = getSelectedCategoryDomArr();

    selectedCategoryDomArr.forEach((selectedCategoryDom) => {
        const selectedCategoryName = normalizeName(selectedCategoryDom.innerText);

        if (selectedCategoryName.includes(categoryName))
            return true;
    });

    return false;
}

function hideHomeRecommendedContent(isHide) {
    waitForElm('ytd-two-column-browse-results-renderer #contents, .rich-grid-renderer-contents').then((domResult) => {
        const homeContentDom = domResult.single;

        homeContentDom.style.visibility = isHide ? 'hidden' : '';
    });
}

function getSelectedCategoryDomArr() {
    return document.querySelectorAll(getSelectedCategoryDomCssSelectorStr());
}

function getSelectedCategoryDomCssSelectorStr() {
    return 'yt-chip-cloud-chip-renderer.iron-selected, ytm-chip-cloud-chip-renderer.selected';
}

/*=== Extensions ===*/

function waitForElm(selector) {
    return new Promise(resolve => {
        if (document.querySelector(selector)) {
            return resolve({single: document.querySelector(selector), multiple: document.querySelectorAll(selector)});
        }

        const observer = new MutationObserver(mutations => {
            if (document.querySelector(selector)) {
                resolve({single: document.querySelector(selector), multiple: document.querySelectorAll(selector)});
                observer.disconnect();
            }
        });

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


function getElementByXpath(path) {
  return document.evaluate(path, document, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue;
}

function normalizeName(text) {
    return text.toLowerCase().trim();
}

Array.prototype.random = function () {
  return this[Math.floor((Math.random()*this.length))];
}