Add and Remove Songs from Library on YouTube Music

Adds or removes all songs in the current playlist to/from your library with auto-scroll, counters, a tooltip, and a female TTS voice notification when completed.

  1. // ==UserScript==
  2. // @name Add and Remove Songs from Library on YouTube Music
  3. // @version 1.7
  4. // @license MIT
  5. // @description Adds or removes all songs in the current playlist to/from your library with auto-scroll, counters, a tooltip, and a female TTS voice notification when completed.
  6. // @author Casket Pizza
  7. // @match https://music.youtube.com/*
  8. // @grant none
  9. // @namespace https://greasyfork.org/users/1374050
  10. // ==/UserScript==
  11.  
  12. (function() {
  13. 'use strict';
  14.  
  15. let songCounter = 0;
  16. let counterElement;
  17.  
  18. // Scroll to bottom of the page to load all songs
  19. async function scrollToBottom() {
  20. return new Promise((resolve) => {
  21. let lastScrollHeight = 0;
  22. const interval = setInterval(() => {
  23. window.scrollTo(0, document.body.scrollHeight); // Scroll to bottom
  24. let newScrollHeight = document.body.scrollHeight;
  25.  
  26. if (newScrollHeight !== lastScrollHeight) {
  27. lastScrollHeight = newScrollHeight; // Update last height
  28. } else {
  29. clearInterval(interval); // Stop scrolling when no more new songs load
  30. resolve();
  31. }
  32. }, 1000); // Check every 1 second
  33. });
  34. }
  35.  
  36. // Function to add "+" and "-" buttons next to the search bar
  37. function addButtons() {
  38. const searchBar = document.querySelector('ytmusic-search-box');
  39. if (!searchBar) return; // Ensure search bar exists
  40.  
  41. const existingPlusButton = document.getElementById('addAllToLibraryButton');
  42. const existingMinusButton = document.getElementById('removeAllFromLibraryButton');
  43. if (existingPlusButton || existingMinusButton) return; // Avoid adding multiple buttons
  44.  
  45. // Create "+" button to add all songs
  46. const plusButton = document.createElement('button');
  47. plusButton.id = 'addAllToLibraryButton';
  48. plusButton.innerHTML = '+';
  49. plusButton.style.fontSize = '20px';
  50. plusButton.style.marginLeft = '10px';
  51. plusButton.style.cursor = 'pointer';
  52. plusButton.style.background = 'none';
  53. plusButton.style.border = 'none';
  54. plusButton.style.color = 'white';
  55. plusButton.title = 'Click to add all songs to library'; // Tooltip for Add
  56.  
  57. // Create "-" button to remove all songs
  58. const minusButton = document.createElement('button');
  59. minusButton.id = 'removeAllFromLibraryButton';
  60. minusButton.innerHTML = '-';
  61. minusButton.style.fontSize = '20px';
  62. minusButton.style.marginLeft = '10px';
  63. minusButton.style.cursor = 'pointer';
  64. minusButton.style.background = 'none';
  65. minusButton.style.border = 'none';
  66. minusButton.style.color = 'white';
  67. minusButton.title = 'Click to remove all songs from library'; // Tooltip for Remove
  68.  
  69. // Insert the buttons next to the search bar
  70. searchBar.parentNode.insertBefore(plusButton, searchBar.nextSibling);
  71. searchBar.parentNode.insertBefore(minusButton, plusButton.nextSibling);
  72.  
  73. // Add counter display in bottom-right corner
  74. counterElement = document.createElement('div');
  75. counterElement.id = 'songCounter';
  76. counterElement.style.position = 'fixed';
  77. counterElement.style.bottom = '80px'; // Move it up to avoid overlap with the media player
  78. counterElement.style.right = '20px';
  79. counterElement.style.fontSize = '16px';
  80. counterElement.style.color = 'white';
  81. counterElement.style.background = '#333';
  82. counterElement.style.padding = '10px';
  83. counterElement.style.borderRadius = '5px';
  84. counterElement.style.display = 'none'; // Hidden initially
  85. document.body.appendChild(counterElement);
  86.  
  87.  
  88. // Add event listener for "+" button to add all songs to the library
  89. plusButton.addEventListener('click', async function() {
  90. await scrollToBottom(); // Scroll to the bottom to load all songs
  91. songCounter = 0; // Reset counter
  92. counterElement.innerHTML = `Songs added: ${songCounter}`;
  93. counterElement.style.display = 'block'; // Show counter
  94.  
  95. var song = document.body.querySelectorAll(".dropdown-trigger.style-scope.ytmusic-menu-renderer");
  96.  
  97. for (var i = 0; i < song.length; i++) {
  98. song[i].click();
  99. var dropdown = document.body.querySelector("ytmusic-menu-popup-renderer[slot='dropdown-content']");
  100.  
  101. if (dropdown != undefined) {
  102. var addSong = dropdown.querySelector("tp-yt-paper-listbox#items")
  103. .querySelector("ytmusic-toggle-menu-service-item-renderer.style-scope.ytmusic-menu-popup-renderer");
  104.  
  105. if (addSong != null) {
  106. var actualAddSong = addSong.querySelector('yt-formatted-string.text.style-scope.ytmusic-toggle-menu-service-item-renderer');
  107.  
  108. if (actualAddSong != null && actualAddSong.innerHTML == 'Save to library') {
  109. addSong.click();
  110. songCounter++; // Increase counter
  111. counterElement.innerHTML = `Songs added: ${songCounter}`;
  112. console.log(`Song ${songCounter} saved to library`);
  113. await new Promise(r => setTimeout(r, 200)); // Wait for the action to complete
  114. }
  115. }
  116. }
  117.  
  118. await new Promise(r => setTimeout(r, 100)); // Avoid overloading the system
  119. }
  120.  
  121. // Play TTS notification when the process is complete
  122. playTTS("Songs added.");
  123. });
  124.  
  125. // Add event listener for "-" button to remove all songs from the library
  126. minusButton.addEventListener('click', async function() {
  127. await scrollToBottom(); // Scroll to the bottom to load all songs
  128. songCounter = 0; // Reset counter
  129. counterElement.innerHTML = `Songs removed: ${songCounter}`;
  130. counterElement.style.display = 'block'; // Show counter
  131.  
  132. var song = document.body.querySelectorAll(".dropdown-trigger.style-scope.ytmusic-menu-renderer");
  133.  
  134. for (var i = 0; i < song.length; i++) {
  135. song[i].click();
  136. var dropdown = document.body.querySelector("ytmusic-menu-popup-renderer[slot='dropdown-content']");
  137.  
  138. if (dropdown != undefined) {
  139. var removeSong = dropdown.querySelector("tp-yt-paper-listbox#items")
  140. .querySelector("ytmusic-toggle-menu-service-item-renderer.style-scope.ytmusic-menu-popup-renderer");
  141.  
  142. if (removeSong != null) {
  143. var actualRemoveSong = removeSong.querySelector('yt-formatted-string.text.style-scope.ytmusic-toggle-menu-service-item-renderer');
  144.  
  145. if (actualRemoveSong != null && actualRemoveSong.innerHTML == 'Remove from library') {
  146. removeSong.click();
  147. songCounter++; // Increase counter
  148. counterElement.innerHTML = `Songs removed: ${songCounter}`;
  149. console.log(`Song ${songCounter} removed from library`);
  150. await new Promise(r => setTimeout(r, 200)); // Wait for the action to complete
  151. }
  152. }
  153. }
  154.  
  155. await new Promise(r => setTimeout(r, 100)); // Avoid overloading the system
  156. }
  157.  
  158. // Play TTS notification when the process is complete
  159. playTTS("Songs removed.");
  160. });
  161. }
  162.  
  163. // Function to use TTS to say custom message with a female voice
  164. function playTTS(message) {
  165. const utterance = new SpeechSynthesisUtterance(message);
  166. utterance.lang = 'en-US';
  167. utterance.pitch = 1;
  168. utterance.rate = 1;
  169.  
  170. // Select a female voice if available
  171. const voices = speechSynthesis.getVoices();
  172. const femaleVoice = voices.find(voice => voice.name.includes('Female') || voice.gender === 'female' || voice.name.includes('Samantha')); // Adjust for common female names
  173.  
  174. if (femaleVoice) {
  175. utterance.voice = femaleVoice;
  176. }
  177.  
  178. speechSynthesis.speak(utterance);
  179. }
  180.  
  181. // Run the addButtons function when the page is loaded
  182. window.addEventListener('load', function() {
  183. // Wait for voices to be loaded and then add the buttons
  184. speechSynthesis.onvoiceschanged = addButtons;
  185. });
  186.  
  187. // Reset counter and hide it after each button click
  188. window.addEventListener('beforeunload', function() {
  189. songCounter = 0;
  190. if (counterElement) {
  191. counterElement.style.display = 'none'; // Hide the counter on navigation/reload
  192. }
  193. });
  194. })();