-
-
Save dmtri/22208d811886c6c50fe98e4ea4da5570 to your computer and use it in GitHub Desktop.
TamperMonkey Display Popup Channel Desc Youtube
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// ==UserScript== | |
// @name YouTube channel description popup on hover | |
// @namespace http://tampermonkey.net/ | |
// @version 0.1 | |
// @description YouTube channel description popup on channel name hover! | |
// @author @dmtri | |
// @match https://www.youtube.com/* | |
// @icon | |
// @grant none | |
// @license MIT | |
// ==/UserScript== | |
(function () { | |
'use strict'; | |
const MAGIC_NUMBER = 1500; | |
let allEventHandlers = []; | |
const wait = (ms) => new Promise((resolve) => setTimeout(resolve, ms)); | |
const debounce = (mainFunction, delay) => { | |
let timer; | |
return function (...args) { | |
clearTimeout(timer); | |
timer = setTimeout(() => { | |
mainFunction(...args); | |
}, delay); | |
}; | |
}; | |
const initTag = '.youtube-popup-desc-init'; | |
const profileIdentifierUrlContainer = '#container.ytd-channel-name'; | |
// get the url from element with profileIdentifier | |
const getProfileUrl = (profileMetaDataElement) => { | |
const anchor = profileMetaDataElement.getElementsByTagName('a')[0]; | |
return anchor.href; | |
}; | |
const clearEvents = (profileMetaDataElement) => { | |
// clear all existing event handlers | |
allEventHandlers.forEach((handler) => { | |
profileMetaDataElement.forEach((element) => { | |
element.removeEventListener('mouseenter', handler); | |
}); | |
}); | |
allEventHandlers = []; | |
}; | |
// display a popup when hover over the profileMetaData element | |
const init = (force = false) => { | |
if (!force && document.querySelector(initTag)) { | |
return; | |
} | |
const profileMetaDataElement = document.querySelectorAll(profileIdentifierUrlContainer); | |
if (!profileMetaDataElement || !profileMetaDataElement.length) { | |
return; | |
} | |
clearEvents(profileMetaDataElement); | |
const inlineHandler = async (e) => { | |
const element = e.target; | |
let isValidGesture = true; | |
const mouseLeaveHandler = () => { | |
isValidGesture = false; | |
}; | |
element.removeEventListener('mouseleave', mouseLeaveHandler); | |
element.addEventListener('mouseleave', mouseLeaveHandler); | |
await wait(1500); | |
// valid gesture meaning a mouse enter event | |
// is not followed by a mouse leave event | |
// within 1.5 seconds | |
handler(element, isValidGesture); | |
}; | |
allEventHandlers.push(inlineHandler); | |
profileMetaDataElement.forEach((element) => { | |
element.addEventListener('mouseenter', inlineHandler); | |
}); | |
// append init tag to document | |
const initTagElement = document.createElement('div'); | |
initTagElement.classList.add(initTag.replace('.', '')); | |
document.body.appendChild(initTagElement); | |
// init when scrolled down on home page | |
const grid = '#contents.ytd-rich-grid-renderer'; | |
const callback = (mutations) => { | |
mutations.forEach((mutation) => { | |
if (mutation.type === 'childList') { | |
debouncedInit(true); | |
} | |
}); | |
}; | |
let observer; | |
if (observer) observer.disconnect(); | |
observer = new MutationObserver(callback); | |
const targetNode = document.querySelector(grid); | |
const config = { childList: true, subtree: true }; | |
observer.observe(targetNode, config); | |
}; | |
const debouncedInit = debounce(init, MAGIC_NUMBER); | |
const handler = async (profileMetaDataElement, isValidGesture) => { | |
if (!isValidGesture) return; | |
// display a native dialog element | |
const dialog = document.createElement('dialog'); | |
const url = getProfileUrl(profileMetaDataElement); | |
let desc = ''; | |
// append a spinner next to a channel name, then close it after 3 sec | |
const spinner = document.createElement('div'); | |
spinner.innerHTML = 'loading...'; | |
profileMetaDataElement.appendChild(spinner); | |
setTimeout(() => { | |
spinner.remove(); | |
}, 3000); | |
await fetch(url) | |
.then((response) => response.text()) | |
.then((html) => { | |
// <meta property="og:description" content="This is a place for all the things that are awesome on stream. "> | |
const parser = new DOMParser(); | |
const doc = parser.parseFromString(html, 'text/html'); | |
const meta = doc.querySelector('meta[property="og:description"]'); | |
desc = !meta ? (desc = 'No desc available') : meta.getAttribute('content'); | |
}); | |
dialog.innerHTML = ` | |
<div> | |
<h2>${desc}</h2> | |
<h1> | |
<a href="${url}">${url}</h1> | |
</h1> | |
</div> | |
`; | |
document.body.appendChild(dialog); | |
dialog.showModal(); | |
setTimeout(() => { | |
dialog.close(); | |
}, 3000); | |
}; | |
// while there is no init tag in the document, keep trying to init | |
const tryInit = () => { | |
if (!document.querySelector(initTag)) { | |
setTimeout(() => { | |
init(); | |
tryInit(); | |
}, MAGIC_NUMBER); | |
} | |
}; | |
tryInit(); | |
})(); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment