egon-r 3 anos atrás
commit
da8968685a
9 arquivos alterados com 162 adições e 0 exclusões
  1. 2 0
      .gitignore
  2. BIN
      128.png
  3. 38 0
      README.md
  4. 17 0
      manifest.json
  5. 8 0
      package.json
  6. 63 0
      src/playlist_content_script.js
  7. 6 0
      src/popup.html
  8. 20 0
      src/popup.js
  9. 8 0
      yarn.lock

+ 2 - 0
.gitignore

@@ -0,0 +1,2 @@
+.idea/**
+node_modules/**

BIN
128.png


+ 38 - 0
README.md

@@ -0,0 +1,38 @@
+# YTM Export
+A chrome extension to download youtube music playlist metadata as JSON.  
+
+## Usage
+- Navigate to any youtube music playlist, the URL should start with `https://music.youtube.com/playlist`
+- Open the YTM Export extension and click on "Download"
+- Wait until the script scrolls to the bottom of your playlist
+
+## Output
+```
+{
+  "playlist_data": {
+    "playlist_title": "All",
+    "songs": [
+      {
+        "ytWatchParam": "watch?v=abc1234",
+        "title": "title",
+        "artist": "artist",
+        "album": "album"
+      },
+      {
+        "ytWatchParam": "watch?v=abc1234",
+        "title": "title",
+        "artist": "artist",
+        "album": "album"
+      },
+      ...
+      ...
+    ]
+  }
+}
+```
+
+## Install
+1. Clone the repository
+2. Navigate to [chrome://extensions/](chrome://extensions/)
+3. Enable Developer Mode and click `Load unpacked` 
+4. Select the directory containing `manifest.json`

+ 17 - 0
manifest.json

@@ -0,0 +1,17 @@
+{
+  "manifest_version": 3,
+  "name": "YTM Export",
+  "description": "Youtube Music Playlist Exporter",
+  "version": "1.0",
+  "action": {
+    "default_popup": "src/popup.html",
+    "default_icon": "128.png"
+  },
+  "permissions": ["activeTab", "scripting"],
+  "content_scripts": [
+    {
+      "matches": ["https://music.youtube.com/playlist*"],
+      "js": ["src/playlist_content_script.js"]
+    }
+  ]
+}

+ 8 - 0
package.json

@@ -0,0 +1,8 @@
+{
+  "name": "chrome-ytm-export",
+  "version": "1.0.0",
+  "license": "MIT",
+  "devDependencies": {
+    "chrome-types": "^0.1.155"
+  }
+}

+ 63 - 0
src/playlist_content_script.js

@@ -0,0 +1,63 @@
+chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
+    if(message.action === "get_playlist_data") {
+        (async () => {
+            await scrollThroughPlaylist()
+            console.log("done scrolling")
+            const playlistData = getPlaylistData()
+            console.log("found " + playlistData.songs.length + " songs!")
+            sendResponse({playlist_data: playlistData})
+        })()
+    }
+    return true // keeps the message channel open until async sendResponse()
+})
+
+
+async function scrollThroughPlaylist() {
+    let songCounts = [0]
+    songCounts.push(getPlaylistData().songs.length)
+
+    while (songCounts[0] < songCounts[1]) {
+        console.log(songCounts)
+        await new Promise(resolve => setTimeout(resolve, 500))
+        document.getElementsByTagName("ytmusic-playlist-shelf-renderer")[0].scrollIntoView({
+            behavior: "smooth",
+            block: "end"
+        })
+        await new Promise(resolve => setTimeout(resolve, 3500))
+
+        songCounts.push(getPlaylistData().songs.length)
+        songCounts.shift()
+    }
+}
+
+
+function getPlaylistData() {
+    let playlistData = []
+
+    const playlistTitle = document.getElementsByClassName("metadata")[0].getElementsByTagName("h2")[0].innerText
+    const playlistContent = document.getElementById("contents")
+    const playlistItems = playlistContent.getElementsByTagName("ytmusic-responsive-list-item-renderer")
+    for(item of playlistItems) {
+        try {
+            const titleElement = item.getElementsByClassName("title")[0].getElementsByTagName("a")[0]
+            const secondaryColumns = item.getElementsByClassName("secondary-flex-columns")[0].getElementsByClassName("flex-column")
+            const ytWatchParam = titleElement.getAttribute("href").split("&", 1)[0]
+            const title = titleElement.innerText
+            const artist = secondaryColumns[0].innerText
+            const album = secondaryColumns[1].innerText
+            playlistData.push({
+                ytWatchParam: ytWatchParam,
+                title: title,
+                artist: artist,
+                album: album
+            })
+        } catch (e) {
+            console.error(e)
+        }
+    }
+
+    return {
+        playlist_title: playlistTitle,
+        songs: playlistData
+    }
+}

+ 6 - 0
src/popup.html

@@ -0,0 +1,6 @@
+<html>
+<body>
+    <button id="exportPlaylistJson">Download Playlist (JSON)</button>
+    <script src="popup.js"></script>
+</body>
+</html>

+ 20 - 0
src/popup.js

@@ -0,0 +1,20 @@
+document.getElementById("exportPlaylistJson").addEventListener("click", async () => {
+    chrome.tabs.query({active: true}).then(tabs => {
+        chrome.tabs.sendMessage(tabs[0].id, {
+            action: "get_playlist_data"
+        }).then(response => {
+            console.log(response)
+            downloadObjectAsJson(response, response.playlist_data.playlist_title)
+        })
+    })
+})
+
+function downloadObjectAsJson(exportObj, exportName){
+    const dataStr = "data:text/json;charset=utf-8," + encodeURIComponent(JSON.stringify(exportObj, null, 2));
+    const downloadAnchorNode = document.createElement('a');
+    downloadAnchorNode.setAttribute("href",     dataStr);
+    downloadAnchorNode.setAttribute("download", exportName + ".json");
+    document.body.appendChild(downloadAnchorNode); // required for firefox
+    downloadAnchorNode.click();
+    downloadAnchorNode.remove();
+}

+ 8 - 0
yarn.lock

@@ -0,0 +1,8 @@
+# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
+# yarn lockfile v1
+
+
+chrome-types@^0.1.155:
+  version "0.1.155"
+  resolved "https://registry.yarnpkg.com/chrome-types/-/chrome-types-0.1.155.tgz#5a3384c9d1737ee74f615f5d6c8e0dbb971f5bdd"
+  integrity sha512-KxmD4FN4jE4aO5shqVsRRZgreBL7YzwC2EodK9PazZ+wUx5dlas1X0uehLevnt+n+DESDx+Rr73exJgosuJChw==