UNPKG

spottydl

Version:

NodeJS Spotify Downloader without any API Keys or Authentication

157 lines (156 loc) 7.56 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.getPlaylist = exports.getAlbum = exports.getTrack = void 0; const Util_1 = require("./Util"); const axios_1 = __importDefault(require("axios")); const ytmusic_api_1 = __importDefault(require("ytmusic-api")); const ytm = new ytmusic_api_1.default(); // Private methods const get_album_playlist = async (playlistId) => { // Get the Track ID for every track by scraping from an unlisted Youtube playlist let properUrl = `https://m.youtube.com/playlist?list=${playlistId}`; let resp = await axios_1.default.get(properUrl); // let resp = await axios.get(properUrl, {headers: { 'User-Agent':'Mozilla/5.0 (Windows NT 10.0; rv:100.0) Gecko/20100101 Firefox/100.0' }}) // Scrape json inside script tag let ytInitialData = JSON.parse(/(?:window\["ytInitialData"\])|(?:ytInitialData) =.*?({.*?});/s.exec(resp.data)?.[1] || '{}'); let listData = ytInitialData.contents.twoColumnBrowseResultsRenderer.tabs[0].tabRenderer.content.sectionListRenderer .contents[0].itemSectionRenderer.contents[0].playlistVideoListRenderer; return listData.contents; }; /** * Get the Track details of the given Spotify Track URL * @param {string} url Track URL ex `https://open.spotify.com/track/...` * @returns {Track} <Track> if success, `string` if failed */ const getTrack = async (url = '') => { try { let linkData = (0, Util_1.checkLinkType)(url); let properURL = (0, Util_1.getProperURL)(linkData.id, linkData.type); let sp = await axios_1.default.get(properURL); let info = /<script id="initial-state" type="text\/plain">(.*?)<\/script>/s.exec(sp.data); // Decode the base64 data, then parse as json... info[1] matches the encoded data let spData = JSON.parse(Buffer.from(decodeURIComponent(info[1]), 'base64').toString('utf8')); // Assign necessary items to a variable let spTrk = spData.entities.items[`spotify:${linkData.type}:${linkData.id}`]; let tags = { title: spTrk.name, // artist: tempartist, artist: spTrk.otherArtists.items.length == 0 ? spTrk.firstArtist.items[0].profile.name : spTrk.firstArtist.items[0].profile.name + ', ' + spTrk.otherArtists.items.map((i) => i?.profile?.name).join(', '), // artist: trk.data.entity.artists.map((i: any) => i.name).join(', '), // year: spData.data.entity.releaseDate, year: `${spTrk.albumOfTrack.date.year}-${spTrk.albumOfTrack.date.month}-${spTrk.albumOfTrack.date.day}`, // album: spData.album.name || undefined, album: spTrk.albumOfTrack.name, id: 'ID', // Get largest resolution albumCoverURL: spTrk.albumOfTrack.coverArt.sources.slice(-1)[0].url, //trackNumber: spData.track_number || undefined trackNumber: spTrk.trackNumber }; await ytm.initialize(); let yt_trk = await ytm.searchSongs(`${tags.title} - ${tags.artist}`); tags.id = yt_trk[0].videoId; return tags; } catch (err) { return `Caught: ${err.name} | ${err.message}`; } }; exports.getTrack = getTrack; /** * Get the Album details of the given Spotify Album URL * @param {string} url Album URL ex `https://open.spotify.com/album/...` * @returns {Album} <Album> if success, `string` if failed */ const getAlbum = async (url = '') => { try { let linkData = (0, Util_1.checkLinkType)(url); let properURL = (0, Util_1.getProperURL)(linkData.id, linkData.type); let sp = await axios_1.default.get(properURL); let info = /<script id="initial-state" type="text\/plain">(.*?)<\/script>/s.exec(sp.data); let spData = JSON.parse(Buffer.from(decodeURIComponent(info[1]), 'base64').toString('utf8')); // Assign necessary items to a variable let spTrk = spData.entities.items[`spotify:${linkData.type}:${linkData.id}`]; let tags = { name: spTrk.name, artist: spTrk.artists.items.map((e) => e.profile.name).join(', '), year: `${spTrk.date.year}-${spTrk.date.month}-${spTrk.date.day}`, tracks: [], // albumCoverURL: spTrk.coverArt.sources[0].url albumCoverURL: spTrk.coverArt.sources.slice(-1)[0].url }; // Search the album await ytm.initialize(); let alb = await ytm.searchAlbums(`${tags.artist} - ${tags.name}`); let yt_tracks = await get_album_playlist(alb[0].playlistId); // Get track ids from youtube spTrk.tracks.items.forEach((i, n) => { tags.tracks.push({ title: i.track.name, id: yt_tracks[n].playlistVideoRenderer.videoId, trackNumber: i.track.trackNumber }); }); return tags; } catch (err) { return `Caught: ${err.name} | ${err.message}`; } }; exports.getAlbum = getAlbum; /** * Get the Playlist details of the given Spotify Playlist URL * @param {string} url Playlist URL ex `https://open.spotify.com/playlist/...` * @returns {Playlist} <Playlist> if success, `string` if failed */ const getPlaylist = async (url = '') => { try { let linkData = (0, Util_1.checkLinkType)(url); let properURL = (0, Util_1.getProperURL)(linkData.id, linkData.type); let sp = await axios_1.default.get(properURL); let info = /<script id="initial-state" type="text\/plain">(.*?)<\/script>/s.exec(sp.data); let spData = JSON.parse(Buffer.from(decodeURIComponent(info[1]), 'base64').toString('utf8')); // Assign necessary items to a variable let spPlaylist = spData.entities.items[`spotify:${linkData.type}:${linkData.id}`]; // Initialize YTMusic await ytm.initialize(); let tags = { name: spPlaylist.name, owner: spPlaylist.ownerV2.data.name, description: spPlaylist?.description, followerCount: spPlaylist.followers, trackCount: spPlaylist.content.totalCount, tracks: spPlaylist.content.items.map(async (trk) => { let trackTitle = trk.itemV2.data.name; let trackArtists = trk.itemV2.data.artists.items.map((i) => i.profile.name).join(', '); let yt_trk = await ytm.searchSongs(`${trackTitle} - ${trackArtists}`); return { title: trackTitle, artist: trackArtists, // year: Does not exist when scraping album: trk.itemV2.data.albumOfTrack.name, id: yt_trk[0].videoId, // albumCoverURL: trk.itemV2.data.albumOfTrack.coverArt.sources[0].url, albumCoverURL: trk.itemV2.data.albumOfTrack.coverArt.sources.slice(-1)[0].url, trackNumber: trk.itemV2.data.trackNumber }; }), playlistCoverURL: spPlaylist.images.items[0].sources[0].url }; // Search the tracks from youtube concurrently await Promise.all(tags.tracks).then((items) => { tags.tracks = items; }); return tags; } catch (err) { return `Caught: ${err.name} | ${err.message}`; } }; exports.getPlaylist = getPlaylist;