Apple Music Playlist Sorting

sort songs in the playlist by recently added

  1. // ==UserScript==
  2. // @name Apple Music Playlist Sorting
  3. // @namespace https://music.apple.com/*
  4. // @version 1.0.0
  5. // @description sort songs in the playlist by recently added
  6. // @author kavoye
  7. // @match *://music.apple.com/*
  8. // @icon data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==
  9. // @unwrap
  10. // @require http://code.jquery.com/jquery-3.7.1.min.js
  11. // @grant none
  12. // @license MIT
  13. // ==/UserScript==
  14.  
  15. /* globals jQuery, $, waitForKeyElements */
  16.  
  17. (function () {
  18. 'use strict';
  19.  
  20. function pageBlur(applied = true) {
  21. const scrollablePage = document.querySelector('#scrollable-page');
  22.  
  23. if (scrollablePage && applied) {
  24. scrollablePage.style.filter = 'blur(100px)';
  25. } else {
  26. scrollablePage.style.filter = 'none';
  27. }
  28. }
  29.  
  30. function scrollPageToTop() {
  31. const scrollablePalge = document.querySelector('#scrollable-page');
  32.  
  33. setTimeout(() => {
  34. scrollablePalge.scrollTop = 0;
  35. }, 0);
  36. }
  37.  
  38. function reverseAndSortChildElements() {
  39. const parentDiv = document.querySelector('#scrollable-page > main > div > div:nth-child(2) > div > div');
  40.  
  41. if (!parentDiv) {
  42. console.error('Parent div not found.');
  43. return;
  44. }
  45.  
  46. // Hide the parent div to prevent layout shifts
  47. parentDiv.style.display = 'none';
  48.  
  49. // Get the child elements within the parent div
  50. const childElements = Array.from(parentDiv.children);
  51. childElements.reverse();
  52.  
  53. // Reverse the child elements and move the first element to the end
  54. childElements.unshift(childElements[childElements.length - 1]);
  55. childElements.pop();
  56.  
  57. // Clear the parent div
  58. parentDiv.innerHTML = '';
  59.  
  60. // Re-append the sorted child elements to the parent div
  61. for (const child of childElements) {
  62. parentDiv.appendChild(child);
  63. }
  64.  
  65. // Show the parent div again
  66. parentDiv.style.display = 'block';
  67. }
  68.  
  69. function sortButtonFunctionality() {
  70. const styleButton = document.querySelector("#scrollable-page > main > div > div:nth-child(1) > div > div > div > div > div > button")
  71. const contextMenuButton = document.querySelector("#scrollable-page > main > div > div:nth-child(1) > div > div > div.secondary-actions.svelte-d0m3dm > div")
  72.  
  73. if (!contextMenuButton) {
  74. console.error('Original button not found.');
  75. return;
  76. }
  77.  
  78. // Create a new button with extended functionality
  79. const newButton = document.createElement('button');
  80. newButton.textContent = 'Sort';
  81.  
  82. // Copy styles from the styleButton to the newButton
  83. const computedStyle = window.getComputedStyle(styleButton);
  84. for (const property of computedStyle) {
  85. newButton.style[property] = computedStyle[property];
  86. }
  87.  
  88. // Add event listener to the newButton
  89. newButton.addEventListener('click', loadAllSongs);
  90.  
  91. // Insert the new button after the context menu button
  92. contextMenuButton.parentNode.insertBefore(newButton, contextMenuButton.previousSibling);
  93. }
  94.  
  95. function scrollToElement() {
  96. const element = document.querySelector("#scrollable-page > main > div > div:nth-child(4)");
  97.  
  98. if (element) {
  99. // Scroll down quickly in a loop until the element becomes visible
  100. const scrollInterval = setInterval(() => {
  101. element.scrollIntoView({ behavior: 'auto' });
  102. if (!document.body.contains(element)) {
  103. clearInterval(scrollInterval);
  104. scrollPageToTop();
  105. removeTextOverlay()
  106. pageBlur(false);
  107. waitForFooterElement();
  108. }
  109.  
  110. }, 0);
  111. }
  112.  
  113. }
  114.  
  115. function addTextOverlay() {
  116. const textOverlay = document.createElement('div');
  117. textOverlay.id = 'text-overlay';
  118. textOverlay.style.position = 'fixed';
  119. textOverlay.style.top = '0';
  120. textOverlay.style.left = '0';
  121. textOverlay.style.width = '115%';
  122. textOverlay.style.height = '100%';
  123. textOverlay.style.display = 'flex';
  124. textOverlay.style.justifyContent = 'center';
  125. textOverlay.style.alignItems = 'center';
  126. textOverlay.style.zIndex = '2';
  127. document.body.appendChild(textOverlay);
  128.  
  129. const textElement = document.createElement('p');
  130. textElement.textContent = 'Loading all songs in the playlist...';
  131. textElement.style.color = 'white';
  132. textElement.style.filter = '0.1'
  133. textElement.style.fontSize = '26px';
  134. textElement.style.fontWeight = 'bold';
  135. textOverlay.appendChild(textElement);
  136.  
  137. }
  138.  
  139. function removeTextOverlay() {
  140. const textOverlay = document.querySelector('#text-overlay');
  141. if (textOverlay) {
  142. textOverlay.remove();
  143. }
  144. }
  145.  
  146. function waitForFooterElement() {
  147. const footerTextElemet = document.querySelector('#scrollable-page > main > div > div:nth-child(3) > div > div > div.footer-body.svelte-eed3da > p');
  148.  
  149. var checkExist = setInterval(function () {
  150. if ($(footerTextElemet).length) {
  151. clearInterval(checkExist);
  152. reverseAndSortChildElements();
  153. }
  154. }, 0);
  155. }
  156.  
  157. function loadAllSongs() {
  158. const loadingIcon = document.querySelector('#scrollable-page > main > div > div:nth-child(3) > div > div > div:nth-child(1) > p');
  159.  
  160. if (!document.body.contains(loadingIcon)) {
  161. pageBlur();
  162. addTextOverlay();
  163. scrollToElement();
  164. } else {
  165. reverseAndSortChildElements();
  166. }
  167. }
  168.  
  169. function actionFunction(jNode) {
  170. sortButtonFunctionality();
  171. console.log("Sort button loaded.")
  172. }
  173.  
  174. waitForKeyElements("#scrollable-page > main > div > div:nth-child(2) > div > div > div.songs-list__header.svelte-1qne0gs.songs-list__header--is-visible", actionFunction);
  175.  
  176. /*--- waitForKeyElements(): A utility function, for Greasemonkey scripts,
  177. that detects and handles AJAXed content.
  178.  
  179. Usage example:
  180.  
  181. waitForKeyElements (
  182. "div.comments"
  183. , commentCallbackFunction
  184. );
  185.  
  186. //--- Page-specific function to do what we want when the node is found.
  187. function commentCallbackFunction (jNode) {
  188. jNode.text ("This comment changed by waitForKeyElements().");
  189. }
  190.  
  191. IMPORTANT: This function requires your script to have loaded jQuery.
  192. */
  193. function waitForKeyElements (
  194. selectorTxt, /* Required: The jQuery selector string that
  195. specifies the desired element(s).
  196. */
  197. actionFunction, /* Required: The code to run when elements are
  198. found. It is passed a jNode to the matched
  199. element.
  200. */
  201. bWaitOnce, /* Optional: If false, will continue to scan for
  202. new elements even after the first match is
  203. found.
  204. */
  205. iframeSelector /* Optional: If set, identifies the iframe to
  206. search.
  207. */
  208. ) {
  209. var targetNodes, btargetsFound;
  210.  
  211. if (typeof iframeSelector == "undefined")
  212. targetNodes = $(selectorTxt);
  213. else
  214. targetNodes = $(iframeSelector).contents ()
  215. .find (selectorTxt);
  216.  
  217. if (targetNodes && targetNodes.length > 0) {
  218. btargetsFound = true;
  219. /*--- Found target node(s). Go through each and act if they
  220. are new.
  221. */
  222. targetNodes.each ( function () {
  223. var jThis = $(this);
  224. var alreadyFound = jThis.data ('alreadyFound') || false;
  225.  
  226. if (!alreadyFound) {
  227. //--- Call the payload function.
  228. var cancelFound = actionFunction (jThis);
  229. if (cancelFound)
  230. btargetsFound = false;
  231. else
  232. jThis.data ('alreadyFound', true);
  233. }
  234. } );
  235. }
  236. else {
  237. btargetsFound = false;
  238. }
  239.  
  240. //--- Get the timer-control variable for this selector.
  241. var controlObj = waitForKeyElements.controlObj || {};
  242. var controlKey = selectorTxt.replace (/[^\w]/g, "_");
  243. var timeControl = controlObj [controlKey];
  244.  
  245. //--- Now set or clear the timer as appropriate.
  246. if (btargetsFound && bWaitOnce && timeControl) {
  247. //--- The only condition where we need to clear the timer.
  248. clearInterval (timeControl);
  249. delete controlObj [controlKey]
  250. }
  251. else {
  252. //--- Set a timer, if needed.
  253. if ( ! timeControl) {
  254. timeControl = setInterval ( function () {
  255. waitForKeyElements ( selectorTxt,
  256. actionFunction,
  257. bWaitOnce,
  258. iframeSelector
  259. );
  260. },
  261. 300
  262. );
  263. controlObj [controlKey] = timeControl;
  264. }
  265. }
  266. waitForKeyElements.controlObj = controlObj;
  267. }
  268.  
  269. })();

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址