// ==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://gf.qytechs.cn/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);
}
}
})();