Skip to content

Instantly share code, notes, and snippets.

@lleaff
Last active February 17, 2022 16:10
Show Gist options
  • Save lleaff/48db35c180ab1b684a0f2c7d9c493244 to your computer and use it in GitHub Desktop.
Save lleaff/48db35c180ab1b684a0f2c7d9c493244 to your computer and use it in GitHub Desktop.
Youtube Userscript: Copy short URL to clipboard on video title click
// ==UserScript==
// @name Youtube - Copy short URL to clipboard
// @namespace lleaff
// @supportURL https://gist.github.com/lleaff/48db35c180ab1b684a0f2c7d9c493244#comments
// @match https://www.youtube.com/*
// @version 2
// @run-at document-end
// @grant GM_setClipboard
// @noframes
// @description Copy short URL to clipboard on video title click
// ==/UserScript==
const TEXT = {
CLICK_TO_COPY_VID_URL_TO_CLIPBOARD: 'Click to copy short URL',
};
function sleep(ms) {
return new Promise((resolve) => setTimeout(resolve, ms))
}
async function waitForSelector(selector, { intervalMs, root=document }) {
while(true) {
const element = root.querySelector(selector)
if (element) {
return element;
}
await sleep(intervalMs)
}
}
async function waitForSelectorAll(selector, { intervalMs, root=document }) {
while(true) {
const element = root.querySelectorAll(selector)
if (element.length > 0) {
return [...element];
}
await sleep(intervalMs)
}
}
function getResetable({ callbackIn, callbackOut, timeout }) {
let timeoutId;
return () => {
callbackIn();
clearTimeout(timeoutId);
timeoutId = setTimeout(() => {
callbackOut();
timeoutId = null;
}, timeout);
};
}
function setupYTTooltip({ element, text }) {
element.setAttribute('title', text)
}
const extractYoutubeVidId = url => {
const match = url.match(/v=([^&]+)/);
return match && match[1];
};
const getShortVidUrl = (url) => `https://youtu.be/${extractYoutubeVidId(url.search || url)}`;
function setupClickToPushToClipboardElement({ element: el, text }) {
const color = {
initial: el.style.color || null,
active: '#aaa'
};
const opacity = {
initial: el.style.opacity || null,
active: '0.9'
};
function setActiveStyle() {
el.style.color = color.active;
el.style.opacity = opacity.active;
}
function setInactiveStyle() {
el.style.color = color.initial;
el.style.opacity = opacity.initial;
}
const animate = getResetable({ callbackIn: setActiveStyle,
callbackOut: setInactiveStyle,
timeout: 0.5e3
});
el.addEventListener('click', ev => {
console.log('Clicked video title 🖱️')
GM_setClipboard(text);
animate();
ev.preventDefault();
});
setupYTTooltip({ element: el,
text: TEXT.CLICK_TO_COPY_VID_URL_TO_CLIPBOARD });
return el;
}
/**
* Click on video title to copy short url to clipboard
*/
async function setupClickToCopyVidTitle(url) {
const innerTitleEl = await waitForSelector('h1.title:not([hidden]', { intervalMs: 50, });
if (innerTitleEl) {
setupClickToPushToClipboardElement({ element: innerTitleEl,
text: getShortVidUrl(url),
});
}
}
/**
* Executed on every new page load
*/
async function main() {
/* Watching video */
if (location.href.match(/\/watch\?/)) {
await setupClickToCopyVidTitle(location);
}
}
main();
/* Structured Page Fragment api, Youtube's lib to avoid full-page refreshes*/
document.addEventListener("spfdone", main);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment