Skip to content

Instantly share code, notes, and snippets.

@vaaski
Last active October 22, 2022 14:14
Show Gist options
  • Select an option

  • Save vaaski/b503fc9687776f09992bc604b82a035a to your computer and use it in GitHub Desktop.

Select an option

Save vaaski/b503fc9687776f09992bc604b82a035a to your computer and use it in GitHub Desktop.
Clean up unnecessary YouTube UI junk
// ==UserScript==
// @name youtube cleanup
// @version 1
// @description cleans up youtube
// @author vaaski
// @match https://youtube.com/*
// @match https://*.youtube.com/*
// @grant none
// @run-at document-end
// @namespace https://gist.github.com/vaaski/b503fc9687776f09992bc604b82a035a
// @updateURL https://gist.github.com/vaaski/b503fc9687776f09992bc604b82a035a/raw
// @downloadURL https://gist.github.com/vaaski/b503fc9687776f09992bc604b82a035a/raw
// @icon https://www.google.com/s2/favicons?sz=64&domain=youtube.com
// ==/UserScript==
!(async () => {
const toTitleSelector = (/** @type { string } */ s) => `[title='${s}']`
const removeByTitle = [
"Library",
"Your videos",
"Your movies",
"Purchases",
"Originals",
"YouTube Music",
]
const removeBySelector = [
"#guide-wrapper #footer", // footer in sidebar with copyright stuff
"#sections>ytd-guide-section-renderer:nth-child(2)", // list of subscriptions section in sidebar
"ytd-feed-filter-chip-bar-renderer", // "chip filters up top"
"#voice-search-button", // self-explanatory
...removeByTitle.map(toTitleSelector),
]
// "[is-post] img:not([width])" are the (often animated) pictures in "YouTube posts"
const customCSS = `
${removeBySelector.join()},
[is-post] img:not([width]) {
display: none !important;
}`
/**
* get an element with querySelector or wait for it using MutationObserver
* @param {string} selector a css selector
* @param {HTMLElement} parent the parent in which to search for
* @returns {Promise<HTMLElement>}
*/
const getElement = (selector, parent = document.querySelector("ytd-app") ?? document.body) => {
return new Promise((res) => {
/** @type { HTMLElement | null } */
let query = parent.querySelector(selector)
if (query) return res(query)
console.log(`waiting for ${selector}`)
let timeout = 0
const observer = new MutationObserver((list, obs) => {
for (const mutation of list) {
if (mutation.type === "childList") {
query = parent.querySelector(selector)
if (query) {
obs.disconnect()
clearTimeout(timeout)
return res(query)
}
}
}
})
observer.observe(parent, { childList: true, subtree: true })
timeout = setTimeout(() => {
observer.disconnect()
console.log(`observer for ${selector} timed out`)
return res(document.createElement("div"))
}, 5e3)
})
}
const customStyles = document.createElement("style")
customStyles.innerHTML = customCSS
document.body.appendChild(customStyles)
const showMore = await getElement(toTitleSelector("Show more"))
showMore.click()
const showFewer = await getElement(toTitleSelector("Show fewer"))
showFewer.remove()
const likedVideos = await getElement(toTitleSelector("Liked videos"))
const library = await getElement("#section-items")
library.insertBefore(likedVideos, library.childNodes[1])
const kids = await getElement(toTitleSelector("YouTube Kids"))
/** @type { HTMLElement | null } */
const moreFromYoutube = kids.closest("ytd-guide-section-renderer")
if (moreFromYoutube?.style) moreFromYoutube.style.display = "none"
const trending = await getElement(toTitleSelector("Trending"))
/** @type { HTMLElement | null } */
const explore = trending.closest("ytd-guide-section-renderer")
if (explore?.style) explore.style.display = "none"
})()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment