diff --git a/PlexMusicFixer/.idea/.gitignore b/PlexMusicFixer/.idea/.gitignore
new file mode 100644
index 0000000..ea7ed09
--- /dev/null
+++ b/PlexMusicFixer/.idea/.gitignore
@@ -0,0 +1,5 @@
+# Default ignored files
+/shelf/
+/workspace.xml
+# Editor-based HTTP Client requests
+/httpRequests/
diff --git a/PlexMusicFixer/.idea/PlexMusicFixer.iml b/PlexMusicFixer/.idea/PlexMusicFixer.iml
new file mode 100644
index 0000000..0b872d8
--- /dev/null
+++ b/PlexMusicFixer/.idea/PlexMusicFixer.iml
@@ -0,0 +1,12 @@
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/PlexMusicFixer/.idea/modules.xml b/PlexMusicFixer/.idea/modules.xml
new file mode 100644
index 0000000..dcd99ff
--- /dev/null
+++ b/PlexMusicFixer/.idea/modules.xml
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/PlexMusicFixer/.idea/vcs.xml b/PlexMusicFixer/.idea/vcs.xml
new file mode 100644
index 0000000..2e3f692
--- /dev/null
+++ b/PlexMusicFixer/.idea/vcs.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/PlexMusicFixer/userscript.js b/PlexMusicFixer/userscript.js
new file mode 100644
index 0000000..5106a8f
--- /dev/null
+++ b/PlexMusicFixer/userscript.js
@@ -0,0 +1,214 @@
+// ==UserScript==
+// @name PlexMusicFixer
+// @namespace http://tampermonkey.net/
+// @version 0.1
+// @description Swaps the order of the artist and album in the Plex Music player to match the order in the library view
+// @author Isaac Shoebottom
+// @match https://app.plex.tv/desktop/
+// @icon https://www.google.com/s2/favicons?sz=64&domain=plex.tv
+// @grant none
+// ==/UserScript==
+
+"use strict"
+
+let musicLibraryNames = [
+ "Music",
+]
+// How often should scripts run, in milliseconds
+let interval = 1e2
+// Keep track of all intervals
+let intervalIds = []
+let decidedIntervalIds = []
+
+function isMusicPage() {
+ // Find the current library name
+ // Library title has the class selector "PageHeaderTitle-title"
+ let nodes = document.querySelectorAll("[class*=\"PageHeaderTitle-title\"]")
+
+ if (nodes.length === 0) {
+ console.log("No library name found")
+ return false
+ }
+
+ let libraryName = nodes.item(0).innerText
+ return musicLibraryNames.includes(libraryName)
+}
+
+function decidePage() {
+ // Clear all intervals
+ for (let id of intervalIds) {
+ clearInterval(id)
+ }
+
+ if (!isMusicPage()) {
+ console.log("Not music page")
+ return
+ }
+
+ let url = window.location.href
+ if (url.includes("com.plexapp.plugins.library")) {
+ console.log("Library page")
+ let id = setInterval(libraryPage, interval)
+ intervalIds.push(id)
+ } else if (url.includes("details?key=%2Flibrary%2Fmetadata")) {
+ console.log("Details page")
+ let id = setInterval(albumPage, interval)
+ intervalIds.push(id)
+ }
+ let id = setInterval(alwaysCheck, interval)
+ intervalIds.push(id)
+
+ for (let id of decidedIntervalIds) {
+ clearInterval(id)
+ }
+}
+
+function swapCards(cards) {
+ // For each card, get all html elements, and swap the second and third elements
+ for (let card of cards) {
+ // Check if the card has already been swapped
+ if (card.swap) {
+ continue
+ }
+
+ let elements = card.childNodes
+ let artist = elements.item(1)
+ let album = elements.item(2)
+ card.insertBefore(album, artist)
+
+ console.log("Swapped artist: " + artist.innerText + " and album: " + album.innerText)
+
+ // Album has isSecondary
+ let secondaryClass = album.className.split(" ").find((className) => className.includes("isSecondary"))
+
+ // Remove isSecondary from album
+ album.classList.remove(secondaryClass)
+
+ // Add isSecondary to artist
+ artist.classList.add(secondaryClass)
+
+ // Add a swap property to the card, so we can check if it's already been swapped
+ card.swap = true
+ }
+}
+
+function libraryPage() {
+ // Select all divs with the attribute data-testid="cellItem"
+ let cards = document.querySelectorAll("[data-testid=\"cellItem\"]")
+ if (cards.length === 0) {
+ console.log("No cards found")
+ return
+ }
+ swapCards(cards)
+}
+
+function albumPage() {
+ let metadata = document.querySelectorAll("[data-testid=\"metadata-top-level-items\"]")
+ // Two divs down from metadata is the container for the artist and album
+ let container = metadata.item(0).childNodes.item(0).childNodes.item(0)
+ if (container.swap) {
+ return
+ }
+
+ // Check if the container has two children, so there isn't null errors
+ if (container.childNodes.length < 2) {
+ console.log("Not on album page")
+ return
+ }
+ let artist = container.childNodes.item(0)
+ let album = container.childNodes.item(1)
+ // Check if the artist and album are what we're looking for, so we don't swap the wrong elements
+ if (
+ artist.attributes.getNamedItem("data-testid").value !== "metadata-title" ||
+ album.attributes.getNamedItem("data-testid").value !== "metadata-subtitle"
+ ) {
+ console.log("Not on album page")
+ return
+ }
+
+ container.insertBefore(album, artist)
+ console.log("Swapped artist: " + artist.innerText + " and album: " + album.innerText)
+
+ let newArtist = document.createElement("h2")
+ let newAlbum = document.createElement("h1")
+ newArtist.innerHTML = artist.innerHTML
+ newAlbum.innerHTML = album.innerHTML
+
+ // Copy all attributes from album to newArtist
+ for (let attr of album.attributes) {
+ newArtist.setAttribute(attr.name, attr.value)
+ }
+ // Copy all attributes from artist to newAlbum
+ for (let attr of artist.attributes) {
+ newAlbum.setAttribute(attr.name, attr.value)
+ }
+
+ artist.replaceWith(newArtist)
+ album.replaceWith(newAlbum)
+
+ container.swap = true
+}
+
+function alwaysCheck() {
+ soundtrackCheck()
+ playerCheck()
+}
+
+function soundtrackCheck() {
+ // Select for elements with the title="Soundtracks" attribute
+ let soundtracks = document.querySelectorAll("[title=\"Soundtracks\"]")
+ if (soundtracks.length !== 0) {
+ // Get holder of soundtrack cards
+ let root = soundtracks.item(0).parentNode.parentNode.parentNode
+ let cardHolder = root.lastElementChild.lastElementChild.lastElementChild
+ swapCards(cardHolder.childNodes)
+ }
+}
+
+function playerCheck() {
+ // Mini player
+ let player = document.querySelectorAll("[class*=\"PlayerControlsMetadata-container\"]")
+ if (player.length !== 0) {
+ let playerMetadata = player.item(0)
+ let holder = playerMetadata.childNodes.item(1)
+ swapPlayer(holder)
+ }
+ // Big player
+ let bigPlayer = document.querySelectorAll("[class*=\"AudioVideoFullMusic-titlesContainer\"]")
+ if (bigPlayer.length !== 0) {
+ let playerMetadata = bigPlayer.item(0)
+ let holder = playerMetadata.childNodes.item(1)
+ swapPlayer(holder)
+ }
+}
+
+function swapPlayer(holder) {
+ if (holder.swap) {
+ return
+ }
+ let artist = holder.childNodes.item(0)
+ let dash = holder.childNodes.item(1)
+ let album = holder.childNodes.item(2)
+ // Swap artist and album
+ // Remove all children
+ holder.removeChild(artist)
+ holder.removeChild(dash)
+ holder.removeChild(album)
+ // Add back in the correct order
+ holder.appendChild(album)
+ holder.appendChild(dash)
+ holder.appendChild(artist)
+ holder.swap = true
+}
+
+// "Main"
+// On href change, run decidePage
+let href = ""
+const observer = new MutationObserver(() => {
+ if (window.location.href !== href) {
+ href = window.location.href
+ console.log("Deciding page")
+ decidedIntervalIds.push(setInterval(decidePage, interval))
+ }
+})
+observer.observe(document, { childList: true, subtree: true })
\ No newline at end of file