YouTube Sidebar Playlists Loader

Load and show YouTube playlists in the sidebar on hover

  1. // ==UserScript==
  2. // @name YouTube Sidebar Playlists Loader
  3. // @namespace http://tampermonkey.net/
  4. // @version 1.1.2
  5. // @description Load and show YouTube playlists in the sidebar on hover
  6. // @author Ahmad H.
  7. // @match *://www.youtube.com/*
  8. // @grant GM_xmlhttpRequest
  9. // @connect www.youtube.com
  10. // @compatible ECMAScript 2020+ (ES11+)
  11. // @license MIT
  12. // ==/UserScript==
  13.  
  14. (function () {
  15. 'use strict';
  16.  
  17. // Run code after the page loads
  18. window.addEventListener('load', function () {
  19. const sidebar = document.querySelector('#sections'); // Sidebar container
  20. console.log("--- Is YT sidebar found ---", !!sidebar);
  21. const itemsSection = sidebar?.querySelector('#section-items');
  22.  
  23. if (!itemsSection) {
  24. console.log("==== Not Adding Custom Playlist Link ====");
  25. return;
  26. }
  27.  
  28. // Create a new "Playlists" item in the sidebar
  29. const playlistLink = document.createElement('a');
  30. playlistLink.innerText = 'My Playlists';
  31. playlistLink.style.cursor = 'pointer';
  32. playlistLink.style.fontSize = "1.4rem";
  33. playlistLink.style.lineHeight = "2rem";
  34. playlistLink.style.fontWeight = "400";
  35. playlistLink.style.padding = '8px 12px 8px 58px';
  36. playlistLink.style.color = '#ddd';
  37. playlistLink.style.background = 'rgba(0,0,0,0)';
  38. playlistLink.style.display = 'block';
  39. itemsSection.appendChild(playlistLink);
  40.  
  41. // Create submenu container for playlists
  42. const submenu = document.createElement('div.sub_menu_playlist');
  43. submenu.style.display = 'none';
  44. submenu.style.position = 'absolute';
  45. submenu.style.background = 'rgba(0,0,0,0.8)';
  46. submenu.style.border = '1px solid #aaa';
  47. submenu.style.padding = '10px';
  48. submenu.style.zIndex = '9999';
  49. submenu.style.borderRadius = '10px';
  50. document.body.appendChild(submenu);
  51.  
  52. // Function to fetch playlists
  53. const fetchPlaylists = () => {
  54. console.log('Start fetchPlaylists:');
  55. GM_xmlhttpRequest({
  56. method: 'GET',
  57. url: 'https://www.youtube.com/feed/playlists',
  58. onload: function (response) {
  59. console.log('after fetcing playlist data');
  60. const ytInitialDataMatch = response.responseText.match(/ytInitialData = ({.*?});<\/script>/);
  61. if (ytInitialDataMatch && ytInitialDataMatch[1]) {
  62. const ytInitialData = JSON.parse(ytInitialDataMatch[1]);
  63. const playlists = ytInitialData.contents.twoColumnBrowseResultsRenderer.tabs[0].tabRenderer.content.richGridRenderer.contents;
  64.  
  65. // Clear existing submenu items
  66. while (submenu.firstChild) {
  67. submenu.removeChild(submenu.firstChild);
  68. }
  69.  
  70. playlists.forEach((item, index) => {
  71. const playlistInfo = item.richItemRenderer?.content?.lockupViewModel?.metadata?.lockupMetadataViewModel;
  72. const title = playlistInfo?.title?.content;
  73. let url = playlistInfo?.metadata?.contentMetadataViewModel?.metadataRows[2]?.metadataParts[0]?.text?.commandRuns[0]?.onTap?.innertubeCommand?.commandMetadata?.webCommandMetadata?.url;
  74. url = url || playlistInfo?.metadata?.contentMetadataViewModel?.metadataRows[1]?.metadataParts[0]?.text?.commandRuns[0]?.onTap?.innertubeCommand?.commandMetadata?.webCommandMetadata?.url;
  75.  
  76. if (!url || !title) {
  77. console.warn(`!! URL not found for ${title}`);
  78. return;
  79. }
  80.  
  81. if (title && url) {
  82. const playlistItem = document.createElement('a');
  83. playlistItem.href = `https://www.youtube.com${url}`;
  84. playlistItem.target = '_blank';
  85. playlistItem.textContent = title;
  86. playlistItem.style.display = 'block';
  87. playlistItem.style.color = '#f1f1f1';
  88. playlistItem.style.padding = '5px';
  89. playlistItem.style.fontSize = "1.4rem";
  90. playlistItem.style.lineHeight = "2rem";
  91. playlistItem.style.fontWeight = "400";
  92. if (index < playlists.length - 1) {
  93. playlistItem.style.borderBottom = "1px solid #ccc";
  94. }
  95. playlistItem.style.textDecoration = 'none';
  96.  
  97. playlistItem.addEventListener('mouseover', () => {
  98. playlistItem.style.color = 'yellow';
  99. });
  100. playlistItem.addEventListener('mouseout', () => {
  101. playlistItem.style.color = '#f1f1f1';
  102. });
  103.  
  104. submenu.appendChild(playlistItem);
  105. }
  106. });
  107. }
  108. }
  109. });
  110. };
  111.  
  112. fetchPlaylists();
  113.  
  114. // Toggle submenu visibility
  115. playlistLink.addEventListener('mouseenter', () => {
  116. playlistLink.style.backgroundColor = 'rgba(255, 255, 255, 0.1)';
  117. playlistLink.style.borderRadius = '10px';
  118. submenu.style.display = 'block'; // Show the submenu
  119. const rect = playlistLink.getBoundingClientRect();
  120. submenu.style.left = `${rect.right + 10}px`; // Position submenu to the right of the link
  121. submenu.style.top = `${rect.top}px`;
  122. });
  123.  
  124. playlistLink.addEventListener('mouseleave', () => {
  125. playlistLink.style.backgroundColor = 'rgba(0, 0, 0, 0)';
  126. playlistLink.style.borderRadius = '0';
  127. setTimeout(() => {
  128. if (!submenu.matches(':hover')) submenu.style.display = 'none';
  129. }, 200);
  130. });
  131.  
  132. submenu.addEventListener('mouseleave', () => {
  133. submenu.style.display = 'none';
  134. });
  135.  
  136. submenu.addEventListener('mouseenter', () => {
  137. submenu.style.display = 'block';
  138. });
  139. });
  140. })();