// ==UserScript== // @name Plex web album and artist name swap (test vers) // @namespace http://tampermonkey.net/ // @version 2024-02.2 // @description Swaps the album and artist names so the album name is on top // @author frondonson // @license MIT // @match https://app.plex.tv/desktop/* // @match http://192.168.1.70:32400/web/* // @icon https://www.google.com/s2/favicons?sz=64&domain=plex.tv // @grant none // @downloadURL none // ==/UserScript== //I haven't found an element that is unique to a music library so anything we wait for will trigger on any recommended or library tab so the waiting method doesn't work //So, we need a lightweight check to figure out when we open a tab with square album covers and run the init function setInterval(checkIfMusic, 100); function checkIfMusic() { //These checks work on any recomended or library page, since there is no diffrence (that I could find) between media types except poster aspect ratio let onLibraryPage = document.querySelector('.DirectoryListPageContent-pageContentScroller-O3oHlt') != null; let onRecomendedPage = document.querySelector('.DirectoryHubsPageContent-pageContentScroller-jceJrG') != null; let isMusicLibrary = hasSquarePosters(); if(onLibraryPage && isMusicLibrary) { initLibrary(); } if(onRecomendedPage && isMusicLibrary) { initRecomended(); } } function initLibrary() { //Direct parrent of album [data-testid="cellItem"] divs let allAlbumsParent = document.querySelector('div.DirectoryListPageContent-pageContentScroller-O3oHlt>div'); //Set up mutation observer var config = { attributes: false, childList: true, subtree: false }; var observer = new MutationObserver(handleLibraryMutations); observer.observe(allAlbumsParent, config); //Do first batch of albums from page load let firstLoadAlbumDivs = allAlbumsParent.querySelectorAll('div[data-testid="cellItem"]'); doSwaps(firstLoadAlbumDivs); } function initRecomended() { //Parrent of ribbon divs let allRecomendedParent = document.querySelector('div.DirectoryHubsPageContent-pageContentScroller-jceJrG>div'); //Set up mutation observer, subtree true becase albums are spread out in several parent divs var config = { attributes: false, childList: true, subtree: true }; var observer = new MutationObserver(handleRecomendedMutations); observer.observe(allRecomendedParent, config); //Do first batch of albums from page load let firstLoadAlbumDivs = allRecomendedParent.querySelectorAll('div[data-testid="cellItem"]'); doSwaps(firstLoadAlbumDivs); } function handleLibraryMutations(mutations) { let newAlbumDivs = mutations[0].target.children; doSwaps(newAlbumDivs); } function handleRecomendedMutations(mutations) { let newAlbumDivs = mutations[0].target.querySelectorAll('div[data-testid="cellItem"]'); doSwaps(newAlbumDivs); } function doSwaps(nodeList) { //parrent.children is a HTMLcollection not a nodeList, use iterable for-each //https://stackoverflow.com/questions/35969974/foreach-is-not-a-function-error-with-javascript-array for (const albumDiv of nodeList) { if(albumDiv.childElementCount < 3 || albumDiv.lastChild.nodeName == ('SPAN')) { continue; } if(albumDiv.classList.contains('SwapAlbumNameUserScript-swapped')) { continue; } let artistNode = albumDiv.children[1]; //Classlist- MetadataPosterCardTitle-centeredSingleLineTitle-EuZHlc MetadataPosterCardTitle-singleLineTitle-lPd1B2 MetadataPosterCardTitle-title-ImAmGu Link-default-bdWb1S Link-isHrefLink-nk7Aiq let albumNode = albumDiv.children[2]; //Classlist- MetadataPosterCardTitle-centeredSingleLineTitle-EuZHlc MetadataPosterCardTitle-singleLineTitle-lPd1B2 MetadataPosterCardTitle-title-ImAmGu MetadataPosterCardTitle-isSecondary-gGuBpd Link-default-bdWb1S Link-isHrefLink-nk7Aiq artistNode.classList.add('MetadataPosterCardTitle-isSecondary-gGuBpd'); albumNode.classList.remove('MetadataPosterCardTitle-isSecondary-gGuBpd'); //Swap positions - (x, y) insert node X before node Y albumDiv.insertBefore(albumNode, artistNode); albumDiv.classList.add('SwapAlbumNameUserScript-swapped'); } } function hasSquarePosters() { let poster = document.querySelector('.MetadataPosterListItem-card-BfbXw7.PosterCard-card-BRB1k_'); if(poster == null) { return false; } return (poster.clientHeight == poster.clientWidth); }