您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Convert your music playlists between YouTube & Spotify with a single click.
当前为
// ==UserScript== // @name YouTube / Spotify Playlists Converter // @version 1.0 // @description Convert your music playlists between YouTube & Spotify with a single click. // @author bobsaget1990 // @match https://www.youtube.com/* // @match https://music.youtube.com/* // @match https://open.spotify.com/* // @grant GM_setValue // @grant GM_getValue // @grant GM.xmlHttpRequest // @grant GM_registerMenuCommand // @grant GM_unregisterMenuCommand // @connect spotify.com // @connect youtube.com // @connect artemislena.eu // @connect api.song.link // @connect accounts.google.com // @icon64 https://i.imgur.com/zjGIQn4.png // @compatible chrome // @compatible edge // @compatible firefox // @license GNU GPLv3 // @namespace https://greasyfork.org/users/1254768 // ==/UserScript== (async () => { // UI FUNCTIONS: function createUI() { // Remove existing UI const oldUI = document.querySelector('div.floating-div'); if (!!oldUI) oldUI.remove(); const floatingDiv = document.createElement('div'); floatingDiv.classList.add('floating-div'); const centerDiv = document.createElement('div'); centerDiv.classList.add('center-div'); const cancelButton = document.createElement('button'); cancelButton.classList.add('cancel-button'); cancelButton.textContent = 'Cancel'; function reload() { if (confirm(`Cancel conversion?`)) { location.reload(); } } cancelButton.onclick = reload; const closeButton = document.createElement('button'); closeButton.classList.add('close-button'); closeButton.innerHTML = '×'; // Unicode character for the close symbol closeButton.onclick = reload; // Add UI to the page document.body.appendChild(floatingDiv); // CSS const css = ` .floating-div { position: fixed; top: 50%; left: 50%; transform: translate(-50%, -50%); z-index: 9999; width: 400px; height: auto; display: none; flex-direction: column; justify-content: space-between; align-items: center; border-radius: 10px; box-shadow: 0 0 0 1px #3a3a3a; background-color: #0f0f0f; line-height: 50px; } .center-div span { display: block; height: 30px; margin: 10px; font-family: 'Roboto', sans-serif; font-size: 14px; color: white; opacity: 0.3; } .floating-div .cancel-button { width: auto; height: 30px; padding-left: 25px; padding-right: 25px; margin-top: 20px; margin-bottom: 20px; background-color: white; color: #0f0f0f; border-radius: 50px; border: unset; font-family: 'Roboto', sans-serif; font-size: 16px; } .cancel-button:hover { box-shadow: inset 0px 0px 0 2000px rgba(0,0,0,0.25); } .cancel-button:active { box-shadow: inset 0px 0px 0 2000px rgba(0,0,0,0.5); } .close-button { position: absolute; top: 10px; right: 10px; width: 25px; height: 25px; border-radius: 50%; background-color: #393939; color: #7e7e7e; border: unset; font-family: math; font-size: 17px; text-align: center; } .close-button:hover { box-shadow: inset 0px 0px 0 2000px rgba(255,255,255,0.05); } .close-button:active { box-shadow: inset 0px 0px 0 2000px rgba(255,255,255,0.1); } `; // Add the CSS to the page const style = document.createElement('style'); style.textContent = css; document.head.appendChild(style); return { floatingDiv: floatingDiv, centerDiv: centerDiv, cancelButton: cancelButton, closeButton: closeButton }; } function generateSpans(spanTextContent) { let spans = []; for (let i = 0; i < spanTextContent.length; i++) { let span = document.createElement("span"); span.textContent = spanTextContent[i]; span.classList.add(`op-${i + 1}`); spans.push(span); } return spans; } // GLOBALS: let address = window.location.href; const userAgent = navigator.userAgent + ",gzip(gfe)"; var SPOTIFY_AUTH_TOKEN, SPOTIFY_USER_ID; const APIs = { // Converter: platformConverter: 'https://api.song.link/v1-alpha.1/links?url=', // YouTube: ytUserId: 'https://www.youtube.com/account', ytMusicId: 'https://music.youtube.com/youtubei/v1/search?key=&prettyPrint=false', ytGetPlaylist: 'https://yt.artemislena.eu/api/v1/playlists/', ytCreatePlaylist: 'https://www.youtube.com/youtubei/v1/playlist/create?key=&prettyPrint=false', // Spotify: spotifyUserId: 'https://api.spotify.com/v1/me', spotifyToken: 'https://open.spotify.com/', spotifyPlaylistContent: 'https://api.spotify.com/v1/playlists/playlistId/tracks', spotifyCreatePlaylist: 'https://api.spotify.com/v1/users/userId/playlists', spotifyAddPlaylist: 'https://api.spotify.com/v1/playlists/playlistId/tracks' }; let SAPISIDHASH = await GM_getValue('SAPISIDHASH'); if (address.includes('youtube.com')) { try { SAPISIDHASH = await getSApiSidHash('https://www.youtube.com'); GM_setValue('SAPISIDHASH', SAPISIDHASH); } catch (error) {} } console.log(SAPISIDHASH); let goBackFragment = '#go_back_fragment'; let autoRunFragment = '#sty_autorun'; if (address.includes(goBackFragment)) window.location.href = await GM_getValue('backUrl'); // MENU SETUP: let YtS_ID, StY_ID; const callback = () => { // If playlist is YouTube if (/list=.{34}/.test(address)) { YtS_ID = GM_registerMenuCommand('🔄 YouTube to Spotify 🔄', YtS); } else { GM_unregisterMenuCommand(YtS_ID); } // If playlist is Spotify if (/playlist\/.{22}/.test(address)) { StY_ID = GM_registerMenuCommand('🔄 Spotify to YouTube 🔄', StY); } else { GM_unregisterMenuCommand(StY_ID); } }; callback(); // Register/unregister menu functions on address change, and auto run logic let autoRunConditions; const observer = new MutationObserver(() => { autoRunConditions = document.querySelector('[data-testid="entityTitle"]') && GM_getValue('backUrl'); if (autoRunConditions) { GM_setValue('backUrl', undefined); // Clear backUrl StY(); } if (location.href !== address) { address = location.href; callback(); } }); observer.observe(document, { subtree: true, childList: true }); // YouTube to Spotify let ytUserId; async function YtS() { try { // Get the title of the YouTube playlist let yt_playlistTitle = document.querySelector('.metadata-wrapper yt-formatted-string') || document.querySelector('#header .title'); if (address.includes('watch?v=')) yt_playlistTitle = document.querySelector('#header-description a[href*="playlist?list="]') || document.querySelector('#tab-renderer .subtitle'); yt_playlistTitle = yt_playlistTitle.innerText; console.log(yt_playlistTitle); // Yser confirmation if (confirm(`Convert "${yt_playlistTitle}" to Spotify?`)) { // Unregister the menu command GM_unregisterMenuCommand(YtS_ID); // Create the UI let UI = createUI(); // Define the operations and generate spans let operations = [`Getting Spotify tokens`, `Getting YouTube playlist info`, `Converting songs using Songlink's API`, `Adding playlist to Spotify`]; let spans = generateSpans(operations); // Append the UI elements to floating div UI.centerDiv.append(...spans); UI.floatingDiv.appendChild(UI.centerDiv); UI.floatingDiv.appendChild(UI.cancelButton); UI.floatingDiv.appendChild(UI.closeButton); // Display the floating div UI.floatingDiv.style.display = 'flex'; // Start the first operation: Getting Spotify tokens UI.centerDiv.querySelector('.op-1').style.opacity = 1; const spotifyTokens = await getSpotifyTokens(); // Set the Spotify user ID and auth token SPOTIFY_USER_ID = spotifyTokens.usernameId; SPOTIFY_AUTH_TOKEN = spotifyTokens.accessToken; // Update the Spotify playlist changes API // APIs.spotifyPlaylistChanges = APIs.spotifyPlaylistChanges.replace('usernameId', SPOTIFY_USER_ID); // Mark the first operation as complete UI.centerDiv.querySelector('.op-1').textContent += ' ✅'; // Start the second operation: Getting YouTube playlist info UI.centerDiv.querySelector('.op-2').style.opacity = 1; const yt_playlistId = address.match(/list=(.{34})/)[1]; console.log(yt_playlistId); // Get the YouTube playlist content const yt_playlistTrackTitles = await getYtPlaylistContent(yt_playlistId); console.log(yt_playlistTrackTitles); // Get YouTube User ID (for multiple accounts) ytUserId = await getYtUserId(); console.log(ytUserId); // Initialize an array to store the YouTube music IDs const yt_musicIds = []; // Loop through the YouTube playlist track titles for (const [index, entry] of yt_playlistTrackTitles.entries()) { // Update the UI with the progress UI.centerDiv.querySelector('.op-2').textContent = `${operations[1]} (${index + 1}/${yt_playlistTrackTitles.length})`; // Get the YouTube music ID and add it to the array yt_musicIds.push(await getYtMusicId(entry)); } // Mark the second operation as complete UI.centerDiv.querySelector('.op-2').textContent += ' ✅'; // Start the third operation: Converting songs using Songlink's API UI.centerDiv.querySelector('.op-3').style.opacity = 1; // Initialize arrays to store the Spotify track IDs and tracks not found const spotify_trackIds = []; const tracksNotFound = []; // Loop through the YouTube music IDs for (const [index, entry] of yt_musicIds.entries()) { // Update the UI with the progress UI.centerDiv.querySelector('.op-3').textContent = `${operations[2]} (${index + 1}/${yt_playlistTrackTitles.length})`; // Convert the platform and get the Spotify track ID const spotify_trackId = await convertPlatform(entry); // If the Spotify track ID is found, add it to the array // Otherwise, add the track title to the tracks not found array if (spotify_trackId) { spotify_trackIds.push(spotify_trackId); } else { tracksNotFound.push(yt_playlistTrackTitles[index]); console.log('NOT FOUND ON SPOTIFY:', yt_playlistTrackTitles[index]); } } // Mark the third operation as complete UI.centerDiv.querySelector('.op-3').textContent += ' ✅'; console.log(spotify_trackIds); // Start the fourth operation: Adding playlist to Spotify UI.centerDiv.querySelector('.op-4').style.opacity = 1; // Create the Spotify playlist const spotify_playlistId = await createSpotifyPlaylist(yt_playlistTitle); console.log(spotify_playlistId); // Add the tracks to the Spotify playlist await addToSpotifyPlaylist(spotify_playlistId, spotify_trackIds); // Mark the fourth operation as complete UI.centerDiv.querySelector('.op-4').textContent += ' ✅'; // Set the cancel button's onclick event to open the Spotify playlist UI.cancelButton.onclick = () => { window.open(`https://open.spotify.com/playlist/${spotify_playlistId.replace('spotify:playlist:','')}`); }; // Set the cancel button's onclick event to remove floatingDiv UI.closeButton.onclick = () => { UI.floatingDiv.remove(); }; // Style the cancel button UI.cancelButton.style.backgroundColor = '#1ed55f'; UI.cancelButton.textContent = 'Open in Spotify!'; // Re-register the menu command YtS_ID = GM_registerMenuCommand('🔄 YouTube to Spotify 🔄', YtS); } } catch (error) { console.log('🔄🔄🔄', error); // Error handling: if (error.message == APIs.spotifyToken) alert('⛔ Could not get Spotify token: Make sure you are signed in to Spotify and try again..'); if (error.message == APIs.spotifyUserId) alert('⛔ Could not get Spotify User ID: Make sure you are signed in to Spotify and try again..'); if (error.message == APIs.ytGetPlaylist) alert('⛔ Could not get playlist info: Make sure the playlist is not set to "Private" and try again..'); if (error.message == APIs.spotifyCreatePlaylist) alert('⛔ Could not create Spotify playlist..'); if (error.message == APIs.spotifyAddPlaylist) alert('⛔ Could not add songs to Spotify playlist..'); } } // Spotify to YouTube async function StY() { try { // SAPISIDHASH check if (SAPISIDHASH == undefined) { alert(`To collect a token this page will be redirected to YouTube then back here after you click 'Ok' (this will only be needed once), please make sure you are signed in to YouTube for this to work..`); GM_setValue('backUrl', address + autoRunFragment); // Add autorun fragment throw new Error('SAPISIDHASH not found'); } // Get the title of the Spotify playlist let spotify_playlistTitle = document.querySelector('[data-testid="entityTitle"]').innerText; console.log(spotify_playlistTitle); // Confirm with the user if they want to convert the playlist to YouTube if (confirm(`Convert playlist "${spotify_playlistTitle}" to YouTube?`)) { // Unregister the menu command GM_unregisterMenuCommand(StY_ID); // Create the UI let UI = createUI(); // Define the operations and generate spans let operations = [`Getting Spotify tokens`, `Getting Spotify playlist info`, `Converting songs using Songlink's API`, `Adding playlist to YouTube`]; let spans = generateSpans(operations); // Append the UI elements to floating div UI.centerDiv.append(...spans); UI.floatingDiv.appendChild(UI.centerDiv); UI.floatingDiv.appendChild(UI.cancelButton); UI.floatingDiv.appendChild(UI.closeButton); // Display the floating div UI.floatingDiv.style.display = 'flex'; // Start the first operation: Getting Spotify tokens UI.centerDiv.querySelector('.op-1').style.opacity = 1; const spotifyTokens = await getSpotifyTokens(); console.log(spotifyTokens); // Set the Spotify user ID and auth token SPOTIFY_USER_ID = spotifyTokens.usernameId; SPOTIFY_AUTH_TOKEN = spotifyTokens.accessToken; // Mark the first operation as complete UI.centerDiv.querySelector('.op-1').textContent += ' ✅'; // Start the second operation: Getting Spotify playlist info UI.centerDiv.querySelector('.op-2').style.opacity = 1; const spotify_playlistId = address.match(/playlist\/(.{22})/)[1]; console.log(spotify_playlistId); // Get the Spotify playlist content const spotify_trackIds = await getSpotifyPlaylistContent(spotify_playlistId); if (spotify_trackIds.length == 0) throw new Error('Empty playlist'); // Update the UI with the progress and mark the operation as complete UI.centerDiv.querySelector('.op-2').textContent += ` (${spotify_trackIds.length}/${spotify_trackIds.length}) ✅`; // Start the third operation: Converting songs using Songlink's API UI.centerDiv.querySelector('.op-3').style.opacity = 1; // Initialize an array to store the YouTube track IDs and tracks not found const yt_trackIds = []; const tracksNotFound = []; // Loop through the Spotify track IDs for (const [index, entry] of spotify_trackIds.entries()) { // Update the UI with the progress UI.centerDiv.querySelector('.op-3').textContent = `${operations[2]} (${index + 1}/${spotify_trackIds.length})`; // Convert the platform and get the YouTube track ID let yt_trackId = await convertPlatform(entry.replace('spotify:track:', '')); if (yt_trackId) { yt_trackId = yt_trackId.replace('YOUTUBE_VIDEO::', ''); // Add the YouTube track ID to the array yt_trackIds.push(yt_trackId); } else { // TODO } } // Mark the third operation as complete UI.centerDiv.querySelector('.op-3').textContent += ' ✅'; console.log(yt_trackIds); // Start the fourth operation: Adding playlist to YouTube UI.centerDiv.querySelector('.op-4').style.opacity = 1; // Create the YouTube playlist ytUserId = await getYtUserId(); const yt_playlistId = await createYtPlaylist(spotify_playlistTitle, yt_trackIds); console.log(ytUserId); console.log(yt_playlistId); // Mark the fourth operation as complete UI.centerDiv.querySelector('.op-4').textContent += ' ✅'; // Set the cancel button's onclick event to open the YouTube playlist UI.cancelButton.onclick = () => { window.open(`https://www.youtube.com/playlist?list=${yt_playlistId}`); }; // Set the cancel button's onclick event to remove floatingDiv UI.closeButton.onclick = () => { UI.floatingDiv.remove(); }; // Style the cancel button UI.cancelButton.style.backgroundColor = '#ff0000'; UI.cancelButton.style.color = '#ffffff'; UI.cancelButton.textContent = 'Open in YouTube!'; // Re-register the menu command StY_ID = GM_registerMenuCommand('🔄 Spotify to YouTube 🔄', StY); } } catch (error) { console.log('🔄🔄🔄', error); // Error handling: if (error.message == 'SAPISIDHASH not found') window.location.href = 'https://www.youtube.com/#go_back_fragment'; if (error.message == 'Empty playlist') alert('⛔ Could not get playlist info: The playlist is empty!'); if (error.message == APIs.spotifyToken) alert('⛔ Could not get Spotify token: Make sure you are signed in to Spotify and try again..'); if (error.message == APIs.spotifyUserId) alert('⛔ Could not get Spotify User ID: Make sure you are signed in to Spotify and try again..'); if (error.message == APIs.spotifyPlaylistContent) alert('⛔ Could not get playlist info..'); //if (!error.message.includes(APIs.ytUserId)) alert('⛔ Could not get YouTube User ID: Make sure you are signed in to YouTube and try again..'); if (error.message == APIs.ytCreatePlaylist) alert('⛔ Could not create YouTube playlist..'); } } // YOUTUBE FUNCTIONS: async function getYtPlaylistContent(playlistId) { const response = await GM.xmlHttpRequest({ method: "GET", url: `${APIs.ytGetPlaylist}${playlistId}`, }); if (response.status == 200) { const responseJson = JSON.parse(response.responseText); const videoTitles = responseJson.videos.map(video => video.title); return videoTitles; } else { console.log(response); throw new Error(response.finalUrl.slice(0, -34)); } } async function getYtUserId() { // For multiple accounts const response = await GM.xmlHttpRequest({ method: "GET", url: APIs.ytUserId, }); if (response.finalUrl == APIs.ytUserId) { const responseText = response.responseText; const userId = responseText.match(/myaccount\.google\.com\/u\/(\d)/); if (userId) return userId[1]; return 0; } else { console.log(response); throw new Error(response.finalUrl); } } async function getYtMusicId(title) { const response = await GM.xmlHttpRequest({ method: "POST", url: APIs.ytMusicId, headers: { "content-type": "application/json", "x-goog-authuser": ytUserId }, data: JSON.stringify({ "context": { "client": { "userAgent": userAgent, "clientName": "WEB_REMIX", "clientVersion": "1.20240117.01.01" } }, "query": title, "params": "EgWKAQIIAWoKEAMQBBAKEBEQEA%3D%3D" // Songs only }) }); if (response.status == 200) { const searchResults = response.responseText; const id = searchResults.match(/(?:videoId":")(.+?)(?:")/); if (id) return id[1]; console.log(`${title} NOT FOUND AS SONG ONLY`); return null; } else { console.log(response); throw new Error(response.finalUrl); } } async function createYtPlaylist(playlistTitle, videoIds) { const response = await GM.xmlHttpRequest({ method: "POST", url: APIs.ytCreatePlaylist, "headers": { "accept": "*/*", "accept-language": "en-US,en;q=0.9", "authorization": `SAPISIDHASH ${await GM_getValue('SAPISIDHASH')}`, "content-type": "application/json", "x-goog-authuser": ytUserId, "x-origin": "https://www.youtube.com", "x-youtube-bootstrap-logged-in": "true" }, data: JSON.stringify({ "context": { "client": { "userAgent": userAgent, "clientName": "WEB", "clientVersion": "2.20240123.06.00", } }, "title": playlistTitle, "videoIds": videoIds }) }); if (response.status == 200) { const responseJson = JSON.parse(response.responseText); const playlistId = responseJson.playlistId; return playlistId; } else { console.log(response); throw new Error(response.finalUrl); } } async function getSApiSidHash(origin) { // https://gist.github.com/eyecatchup/2d700122e24154fdc985b7071ec7764a function sha1(str) { return window.crypto.subtle.digest("SHA-1", new TextEncoder("utf-8").encode(str)).then(buf => { return Array.prototype.map.call(new Uint8Array(buf), x => (('00' + x.toString(16)).slice(-2))).join(''); }); } const TIMESTAMP_MS = Date.now(); const digest = await sha1(`${TIMESTAMP_MS} ${document.cookie.split('SAPISID=')[1].split('; ')[0]} ${origin}`); return `${TIMESTAMP_MS}_${digest}`; } // SPOTIFY FUNCTIONS: async function getSpotifyPlaylistContent(playlistId) { // For multiple accounts let requestUrl = APIs.spotifyPlaylistContent.replace('playlistId', playlistId); const limit = 100; const offset = 0; const params = `?offset=${offset}&limit=${limit}`; let next = requestUrl + params; let trackIds = []; while (next) { // Keep looping until next page is null const response = await GM.xmlHttpRequest({ method: "GET", url: next, headers: { 'Authorization': `Bearer ${SPOTIFY_AUTH_TOKEN}`, } }); if (response.status == 200) { const responseText = response.responseText; const responseJson = JSON.parse(responseText); next = responseJson.next; // Next page of tracks trackIds = trackIds.concat((responseJson.items.map(item => item.track.uri))); } else { console.log(response); throw new Error(APIs.spotifyPlaylistContent); } } console.log(trackIds); return trackIds; } async function createSpotifyPlaylist(playlistTitle) { let requestUrl = APIs.spotifyCreatePlaylist.replace('userId', SPOTIFY_USER_ID); console.log(requestUrl); console.log(playlistTitle); const playlistData = JSON.stringify({ name: playlistTitle, description: '', public: false, }); const response = await GM.xmlHttpRequest({ method: "POST", url: requestUrl, headers: { 'Authorization': `Bearer ${SPOTIFY_AUTH_TOKEN}`, 'Content-Type': 'application/json', }, data: playlistData }); if (response.status == 201) { const responseText = response.responseText; const responseJson = JSON.parse(responseText); const playlistId = responseJson.uri.replace('spotify:playlist:', ''); console.log(responseJson.uri); return playlistId; } else { console.log(response); throw new Error(APIs.spotifyCreatePlaylist); } } async function addToSpotifyPlaylist(playlistId, trackIds) { let requestUrl = APIs.spotifyAddPlaylist.replace('playlistId', playlistId); while (trackIds.length) { // Keep looping until array is empty const trackData = JSON.stringify({ uris: trackIds.splice(0, 100), // Get first 100 tracks }); const response = await GM.xmlHttpRequest({ method: "POST", url: requestUrl, headers: { 'Authorization': `Bearer ${SPOTIFY_AUTH_TOKEN}`, 'Content-Type': 'application/json', }, data: trackData }); if (response.status == 201) { const responseText = response.responseText; const responseJson = JSON.parse(responseText); console.log(responseJson); } else { console.log(response); throw new Error(APIs.spotifyAddPlaylist); } } } async function getSpotifyTokens() { const tokenResponse = await GM.xmlHttpRequest({ method: "GET", url: APIs.spotifyToken }); let accessToken; if (tokenResponse.status == 200) { const tokenResponseText = await tokenResponse.responseText; const parser = new DOMParser(); const htmlDoc = parser.parseFromString(tokenResponseText, 'text/html'); accessToken = JSON.parse(htmlDoc.querySelector('script#session').innerHTML).accessToken; } else { console.log(tokenResponse); throw new Error(tokenResponse.finalUrl); } const usernameResponse = await GM.xmlHttpRequest({ method: 'GET', url: APIs.spotifyUserId, headers: { 'Authorization': `Bearer ${accessToken}` } }); if (usernameResponse.status == 200) { const usernameId = JSON.parse(usernameResponse.responseText).id; return { usernameId: usernameId, accessToken: accessToken }; } else { console.log(usernameResponse); throw new Error(usernameResponse.finalUrl); } } // PLATFORM CONVERTER FUNCTION: async function convertPlatform(id) { const idIsYoutube = id.length == 11; const idIsSpotify = id.length == 22; let url; if (idIsYoutube) { url = `https://www.youtube.com/watch?v=${id}`; } if (idIsSpotify) { url = `https://open.spotify.com/track/${id}`; } const response = await GM.xmlHttpRequest({ method: "GET", url: `${APIs.platformConverter}${url}` }); if (response.status == 200) { const platformsResponse = JSON.parse(response.responseText); const spotifyAvailable = platformsResponse.linksByPlatform.hasOwnProperty('spotify'); const ytAvailble = platformsResponse.linksByPlatform.hasOwnProperty('youtube'); if (idIsYoutube && spotifyAvailable) { const spotifyTrackId = platformsResponse.linksByPlatform.spotify.nativeAppUriDesktop; return spotifyTrackId; } else if (idIsSpotify && ytAvailble) { const ytVideoId = platformsResponse.linksByPlatform.youtube.entityUniqueId; return ytVideoId; } else { return null; } } else { console.log(response); throw new Error(response.finalUrl); } } })();