Youtube - Copy short URL to clipboard

Copy short URL to clipboard on video title click

  1. // ==UserScript==
  2. // @name Youtube - Copy short URL to clipboard
  3. // @namespace lleaff
  4. // @supportURL https://gist.github.com/lleaff/48db35c180ab1b684a0f2c7d9c493244
  5. // @match https://www.youtube.com/*
  6. // @version 2
  7. // @run-at document-end
  8. // @grant GM_setClipboard
  9. // @noframes
  10. // @description Copy short URL to clipboard on video title click
  11. // ==/UserScript==
  12.  
  13. const TEXT = {
  14. CLICK_TO_COPY_VID_URL_TO_CLIPBOARD: 'Click to copy short URL',
  15. };
  16.  
  17. function sleep(ms) {
  18. return new Promise((resolve) => setTimeout(resolve, ms))
  19. }
  20. async function waitForSelector(selector, { intervalMs, root=document }) {
  21. while(true) {
  22. const element = root.querySelector(selector)
  23. if (element) {
  24. return element;
  25. }
  26. await sleep(intervalMs)
  27. }
  28. }
  29. async function waitForSelectorAll(selector, { intervalMs, root=document }) {
  30. while(true) {
  31. const element = root.querySelectorAll(selector)
  32. if (element.length > 0) {
  33. return [...element];
  34. }
  35. await sleep(intervalMs)
  36. }
  37. }
  38.  
  39. function getResetable({ callbackIn, callbackOut, timeout }) {
  40. let timeoutId;
  41. return () => {
  42. callbackIn();
  43. clearTimeout(timeoutId);
  44. timeoutId = setTimeout(() => {
  45. callbackOut();
  46. timeoutId = null;
  47. }, timeout);
  48. };
  49. }
  50.  
  51. function setupYTTooltip({ element, text }) {
  52. element.setAttribute('title', text)
  53. }
  54.  
  55. const extractYoutubeVidId = url => {
  56. const match = url.match(/v=([^&]+)/);
  57. return match && match[1];
  58. };
  59. const getShortVidUrl = (url) => `https://youtu.be/${extractYoutubeVidId(url.search || url)}`;
  60.  
  61. function setupClickToPushToClipboardElement({ element: el, text }) {
  62. const color = {
  63. initial: el.style.color || null,
  64. active: '#aaa'
  65. };
  66. const opacity = {
  67. initial: el.style.opacity || null,
  68. active: '0.9'
  69. };
  70.  
  71. function setActiveStyle() {
  72. el.style.color = color.active;
  73. el.style.opacity = opacity.active;
  74. }
  75. function setInactiveStyle() {
  76. el.style.color = color.initial;
  77. el.style.opacity = opacity.initial;
  78. }
  79.  
  80. const animate = getResetable({ callbackIn: setActiveStyle,
  81. callbackOut: setInactiveStyle,
  82. timeout: 0.5e3
  83. });
  84. el.addEventListener('click', ev => {
  85. console.log('Clicked video title 🖱️')
  86. GM_setClipboard(text);
  87. animate();
  88. ev.preventDefault();
  89. });
  90.  
  91. setupYTTooltip({ element: el,
  92. text: TEXT.CLICK_TO_COPY_VID_URL_TO_CLIPBOARD });
  93.  
  94. return el;
  95. }
  96.  
  97. /**
  98. * Click on video title to copy short url to clipboard
  99. */
  100. async function setupClickToCopyVidTitle(url) {
  101. const innerTitleEl = await waitForSelector('h1.title:not([hidden]', { intervalMs: 50, });
  102. if (innerTitleEl) {
  103. setupClickToPushToClipboardElement({ element: innerTitleEl,
  104. text: getShortVidUrl(url),
  105. });
  106. }
  107. }
  108.  
  109. /**
  110. * Executed on every new page load
  111. */
  112. async function main() {
  113. /* Watching video */
  114. if (location.href.match(/\/watch\?/)) {
  115. await setupClickToCopyVidTitle(location);
  116. }
  117. }
  118.  
  119.  
  120. main();
  121. /* Structured Page Fragment api, Youtube's lib to avoid full-page refreshes*/
  122. document.addEventListener("spfdone", main);