YouTube channel description popup on hover

YouTube channel description popup on channel name hover!

目前為 2023-09-08 提交的版本,檢視 最新版本

  1. // ==UserScript==
  2. // @name YouTube channel description popup on hover
  3. // @namespace http://tampermonkey.net/
  4. // @version 0.1
  5. // @description YouTube channel description popup on channel name hover!
  6. // @author @dmtri
  7. // @match https://www.youtube.com/*
  8. // @icon
  9. // @grant none
  10. // @license MIT
  11. // ==/UserScript==
  12.  
  13. (function () {
  14. 'use strict';
  15. const initTag = '.youtube-popup-desc-init';
  16.  
  17. // a tampermonkey script to get the profile details
  18. // upon hover a channel profile on youtube (after 1.5 sec)
  19. // and then display the channel desc in a popup
  20.  
  21. const profileIdentifierUrlContainer = '#container.ytd-channel-name';
  22.  
  23. // get the url from element with profileIdentifier
  24. const getProfileUrl = (profileMetaDataElement) => {
  25. const anchor = profileMetaDataElement.getElementsByTagName('a')[0];
  26. return anchor.href;
  27. };
  28.  
  29. const wait = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
  30.  
  31. // display a popup when hover over the profileMetaData element
  32. const init = (force = false) => {
  33. if (!force && document.querySelector(initTag)) {
  34. return;
  35. }
  36.  
  37. const profileMetaDataElement = document.querySelectorAll(profileIdentifierUrlContainer);
  38. if (!profileMetaDataElement || !profileMetaDataElement.length) {
  39. return;
  40. }
  41. profileMetaDataElement.forEach((element) => {
  42. element.addEventListener('mouseenter', async () => {
  43. let isValidGesture = true;
  44.  
  45. const mouseLeaveHandler = () => {
  46. isValidGesture = false;
  47. };
  48. element.removeEventListener('mouseleave', mouseLeaveHandler);
  49. element.addEventListener('mouseleave', mouseLeaveHandler);
  50.  
  51. await wait(1500);
  52. // valid gesture meaning a mouse enter event
  53. // is not followed by a mouse leave event
  54. // within 1.5 seconds
  55. handler(element, isValidGesture);
  56. });
  57. });
  58.  
  59. // append init tag to document
  60. const initTagElement = document.createElement('div');
  61. initTagElement.classList.add(initTag.replace('.', ''));
  62. document.body.appendChild(initTagElement);
  63.  
  64. // try init when popstate event happen
  65. window.addEventListener('popstate', () => {
  66. tryInit(true);
  67. });
  68. };
  69.  
  70. const handler = async (profileMetaDataElement, isValidGesture) => {
  71. if (!isValidGesture) return;
  72.  
  73. // display a native dialog element
  74. const dialog = document.createElement('dialog');
  75. const url = getProfileUrl(profileMetaDataElement);
  76.  
  77. let desc = '';
  78.  
  79. // append a spinner next to a channel name, then close it after 3 sec
  80. const spinner = document.createElement('div');
  81. spinner.innerHTML = 'loading...';
  82. profileMetaDataElement.appendChild(spinner);
  83. setTimeout(() => {
  84. spinner.remove();
  85. }, 3000);
  86.  
  87. await fetch(url)
  88. .then((response) => response.text())
  89. .then((html) => {
  90. // <meta property="og:description" content="This is a place for all the things that are awesome on stream. ">
  91. const parser = new DOMParser();
  92. const doc = parser.parseFromString(html, 'text/html');
  93. const meta = doc.querySelector('meta[property="og:description"]');
  94.  
  95. desc = !meta ? (desc = 'No desc available') : meta.getAttribute('content');
  96. });
  97.  
  98. dialog.innerHTML = `
  99. <div>
  100. <h2>${desc}</h2>
  101. <h1>
  102. <a href="${url}">${url}</h1>
  103. </h1>
  104. </div>
  105. `;
  106. document.body.appendChild(dialog);
  107. dialog.showModal();
  108.  
  109. setTimeout(() => {
  110. dialog.close();
  111. }, 3000);
  112. };
  113.  
  114. // while there is no init tag in the document, keep trying to init
  115. const tryInit = (force = false) => {
  116. if (!document.querySelector(initTag)) {
  117. setTimeout(() => {
  118. init(force);
  119. tryInit(force);
  120. }, 1500);
  121. }
  122. };
  123. tryInit();
  124. })();