Created
June 30, 2021 13:26
-
-
Save skinkade/9d5c7a11ff65dd76e82d7b72e5560bff to your computer and use it in GitHub Desktop.
Export Spotify Playlist to CSV
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// While browsing a playlist in your browser, | |
// i.e. browser address is https://open.spotify.com/playlist/{playlistId}, | |
// copy/paste these functions into the browser console, | |
// then run exportCurrentPlaylist() | |
// It should prompt a download for: | |
// [Playlist name] - [Playlist ID].csv | |
// Containing: | |
// Song, Artist(s), Album, Song Link, Main Artist Link, Album Link | |
// Disclaimer: written quickly by a non-JS dev | |
// Note: should probably work within the desktop client via Electron inspector (untested) | |
function getCurrentPlaylistId() { | |
return document | |
.querySelector('[data-testid="playlist-page"]') | |
.getAttribute('data-test-uri') | |
.split(':')[2]; | |
} | |
function getAccessToken() { | |
const config = JSON.parse(document.getElementById('config').textContent); | |
if (config['accessTokenExpirationTimestampMs'] <= Date.now()) { | |
throw "Access token has expired. Refresh page to get a new one." | |
} | |
return config['accessToken']; | |
} | |
async function getPlaylistInfo(playlistId, accessToken) { | |
const url = `https://api.spotify.com/v1/playlists/${playlistId}`; | |
const response = await fetch(url, { | |
method: 'GET', | |
headers: { | |
'Authorization': `Bearer ${accessToken}` | |
} | |
}); | |
return response.json(); | |
} | |
async function getTracks(playlistId, accessToken, next) { | |
throw 'Not implemented'; | |
} | |
function processTrackData(tracks) { | |
let processed = []; | |
tracks.forEach(t => { | |
let info = {}; | |
const track = t['track']; | |
info['song'] = track['name']; | |
info['songLink'] = track['external_urls']['spotify']; | |
const album = track['album']; | |
info['album'] = album['name']; | |
info['albumLink'] = album['external_urls']['spotify']; | |
const artists = album['artists']; | |
info['artists'] = artists.map(a => a['name']).join(', '); | |
info['mainArtistLink'] = artists[0]['external_urls']['spotify']; | |
processed.push(info); | |
}) | |
return processed; | |
} | |
async function getExportData() { | |
const playlistId = getCurrentPlaylistId(); | |
const accessToken = getAccessToken(); | |
const playlistInfo = await getPlaylistInfo(playlistId, accessToken); | |
let exportData = {}; | |
exportData['playlistId'] = playlistId; | |
exportData['playlistName'] = playlistInfo['name']; | |
// TODO: for >100 tracks, use getTracks() to fetch the rest | |
exportData['tracks'] = processTrackData(playlistInfo['tracks']['items']); | |
return exportData; | |
} | |
// May or may not be sufficient for this purpose | |
function csvEscapeValue(str) { | |
return ['"', str, '"'].join(''); | |
} | |
function generateTrackCsv(tracks) { | |
let rows = []; | |
rows.push([ | |
'Song', | |
'Artist(s)', | |
'Album', | |
'Song Link', | |
'Main Artist Link', | |
'Album Link' | |
].join(',')); | |
tracks.forEach(track => { | |
const info = [ | |
csvEscapeValue(track['song']), | |
csvEscapeValue(track['artists']), | |
csvEscapeValue(track['album']), | |
track['songLink'], | |
track['mainArtistLink'], | |
track['albumLink'] | |
] | |
.join(','); | |
rows.push(info); | |
}) | |
// Don't know if we need the \r for Windows, | |
// but adding it doesn't create problems on Linux | |
return rows.join('\r\n'); | |
} | |
function downloadCsv(basename, contents) { | |
let a = document.createElement('a'); | |
a.download = basename + '.csv'; | |
// Needs encoding because of newlines | |
a.href = `data:text/csv,` + encodeURIComponent(contents); | |
a.click(); | |
} | |
async function exportCurrentPlaylist() { | |
const exportData = await getExportData(); | |
const nameAndId = [ | |
exportData['playlistName'], | |
exportData['playlistId'] | |
].join(' - '); | |
const csvContent = generateTrackCsv(exportData['tracks']); | |
downloadCsv(nameAndId, csvContent); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment