#!/bin/node // Add your details here const SERVER_ADDRESS = "https://jellyfin.example.com"; // Don't leave a trailing slash!!! const API_KEY = ""; // Get this from Administration > API Keys const USER_NAME = "user"; const LIBRARY_NAME = "Music"; if (SERVER_ADDRESS.includes("example") || API_KEY.trim() === "" || USER_NAME.trim() === "" || LIBRARY_NAME.trim() === "") { console.log("Details are invalid, check the details at the top of this script file! (Lines 3 - 7)"); return; } const fs = require("fs"); let outText = ""; let albumCount = 0; let songCount = 0; class StrippedFunkyArray { constructor() { this.items = {}; this.itemKeys = []; this.undefIndex = 9999; } set(key, item, regenerate = true) { this.items[key] = item; if (regenerate) { this.itemKeys = Object.keys(this.items); } return item; } get(key) { return this.items[key]; } } async function apiRequest(url) { const res = await fetch(`${SERVER_ADDRESS}/${url}`, { headers: { Authorization: `Mediabrowser Token="${API_KEY}"` } }); return await res.json(); } function getLargestFromNumericKey(keys) { let largest = 0; let next = 0; for (const key of keys) { next = parseInt(key); if (next > largest) { largest = next; } } return largest + 1; } console.log("=============================================") console.log("@ Holly's Jellyfin Library Expoter for VotV @"); console.log("@ It shouldn't have been this hard :( @"); console.log("=============================================\n"); (async () => { console.log("Fetching users..."); const users = await apiRequest("Users"); let userId = ""; for (const user of users) { if (user.Name === USER_NAME) { userId = user.Id; break; } } if (userId === "") { console.log(`Could not find user "${USER_NAME}" on the server.`); return; } console.log(`Found user "${USER_NAME}", ID = ${userId}.`); console.log(`Fetching Libraries for ${userId}...`); const libraries = await apiRequest(`Items?userId=${userId}`); let libraryId = ""; for (const library of libraries.Items) { if (library.Type !== "CollectionFolder") { continue; } if (library.Name === LIBRARY_NAME) { libraryId = library.Id; } } if (libraryId === "") { console.log(`Could not find library "${LIBRARY_NAME}" for user ${userId} on the server.`) } console.log(`Found library "${LIBRARY_NAME}", ID = ${libraryId}`); console.log("Fetching library media..."); const media = await apiRequest(`Items?userId=${userId}&recursive=true&parentId=${libraryId}`); console.log("Exporting..."); const groupedSongs = new StrippedFunkyArray(); for (const item of media.Items) { if (item.Type !== "Audio") { if (item.Type === "MusicAlbum") { albumCount++; } continue; } let albumArray = groupedSongs.get(item.AlbumId); if (albumArray == null) { albumArray = groupedSongs.set(item.AlbumId, new StrippedFunkyArray()); } albumArray.set(item.IndexNumber ?? albumArray.undefIndex++, item); } for (const albumArrayKey of groupedSongs.itemKeys) { const albumArray = groupedSongs.get(albumArrayKey); const largestIndexNumber = getLargestFromNumericKey(albumArray.itemKeys); for (let i = 1; i < largestIndexNumber; i++) { if (albumArray.items[i] != null) { const item = albumArray.items[i]; outText += `${outText.length === 0 ? "" : "\n"}${item.Name} - ${item.Album ?? item.AlbumArtist}\n${SERVER_ADDRESS}/Items/${item.Id}/Download?api_key=${API_KEY}`; songCount++; } } } fs.writeFileSync("online.txt", outText); console.log(`Exported ${songCount} songs from ${albumCount} albums to online.txt`); })();