// ==UserScript==
// @name Spotify to YouTube Redirector
// @namespace http://tampermonkey.net/
// @version 1.3
// @description Redirects Spotify track links to YouTube, using the omijn/yt2spotify converter endpoint.
// @author CL Using backend from ytm2spotify by omijn
// @match *://*/*
// @grant GM_xmlhttpRequest
// @grant GM_getValue
// @grant GM_setValue
// @grant GM_registerMenuCommand
// @connect *
// @license MIT
// ==/UserScript==
(function() {
'use strict';
// --- Configuration ---
// Default API URL for the yt2spotify converter service.
// This points to the '/convert' endpoint.
const DEFAULT_CONVERTER_API_URL = 'https://ytm2spotify.com//convert'; // Updated API URL to include /convert
let converterApiUrl = GM_getValue('converterApiUrl', DEFAULT_CONVERTER_API_URL);
// --- Helper Functions ---
function showNotification(message, isError = false) {
const notificationId = 'spotify-yt-redirector-notification';
let notificationElement = document.getElementById(notificationId);
if (!notificationElement) {
notificationElement = document.createElement('div');
notificationElement.id = notificationId;
Object.assign(notificationElement.style, {
position: 'fixed',
top: '20px',
right: '20px',
padding: '15px 20px',
borderRadius: '8px',
color: 'white',
zIndex: '99999',
fontSize: '16px',
boxShadow: '0 4px 12px rgba(0,0,0,0.2)',
opacity: '0',
transition: 'opacity 0.5s ease-in-out, transform 0.3s ease-in-out',
transform: 'translateX(100%)',
fontFamily: 'Arial, sans-serif'
});
document.body.appendChild(notificationElement);
}
notificationElement.textContent = message;
notificationElement.style.backgroundColor = isError ? '#e74c3c' : '#2ecc71'; // Red for error, Green for success
// Animate in
setTimeout(() => {
notificationElement.style.opacity = '1';
notificationElement.style.transform = 'translateX(0)';
}, 50);
// Automatically hide after some time
setTimeout(() => {
notificationElement.style.opacity = '0';
notificationElement.style.transform = 'translateX(100%)';
}, isError ? 7000 : 4000);
}
function getYouTubeLink(spotifyUrl, callback) {
if (!converterApiUrl) {
showNotification('Converter API URL is not configured. Please set it via the script menu if the default is incorrect.', true);
console.error('Spotify to YouTube Redirector: Converter API URL not configured or empty.');
callback(null, 'Configuration error: Converter API URL not set.');
return;
}
showNotification('Converting Spotify link to YouTube...');
console.log(`Attempting to convert Spotify URL: ${spotifyUrl} using API: ${converterApiUrl}`);
// Construct the API request URL
// The backend expects 'to_service=youtube_ytm' for YouTube conversion.
const apiUrl = `${converterApiUrl}?url=${encodeURIComponent(spotifyUrl)}&to_service=youtube_ytm`;
GM_xmlhttpRequest({
method: 'GET',
url: apiUrl,
timeout: 15000,
onload: function(response) {
try {
if (response.status >= 200 && response.status < 300) {
const data = JSON.parse(response.responseText);
if (data && data.results && data.results.length > 0 && data.results[0].url) {
console.log('Conversion successful. YouTube URL:', data.results[0].url);
callback(data.results[0].url, null);
} else if (data && data.manual_search_link) {
console.log('Direct link not found, using manual search link:', data.manual_search_link);
callback(data.manual_search_link, null);
}
else {
console.error('Conversion failed: Invalid response structure from API. Expected "results[0].url" or "manual_search_link". Response:', data);
callback(null, 'Invalid response structure from converter API.');
}
} else {
console.error(`Conversion failed: API request error. Status: ${response.status}`, response.responseText);
callback(null, `Converter API request failed (Status: ${response.status}). Check API endpoint.`);
}
} catch (e) {
console.error('Conversion failed: Error parsing API response. Is it valid JSON?', e, response.responseText);
callback(null, 'Error parsing converter API response.');
}
},
onerror: function(error) {
console.error('Conversion failed: Network error or CORS issue with API. Ensure the API URL is correct and allows cross-origin requests if necessary.', error);
callback(null, 'Network error or CORS issue with converter API.');
},
ontimeout: function() {
console.error('Conversion failed: API request timed out.');
callback(null, 'Converter API request timed out.');
}
});
}
// --- Event Listener ---
document.addEventListener('click', function(event) {
let targetElement = event.target;
while (targetElement && targetElement.tagName !== 'A') {
targetElement = targetElement.parentElement;
}
if (targetElement && targetElement.href) {
const url = targetElement.href;
// Regex to identify Spotify track links
// Example: https://open.spotify.com/track/TRACK_ID_HERE
const spotifyTrackRegex = /^https?:\/\/open\.spotify\.com\/(?:[a-zA-Z]{2}-[a-zA-Z]{2}\/)?track\/([a-zA-Z0-9]+)/;
if (spotifyTrackRegex.test(url)) {
event.preventDefault();
event.stopPropagation();
console.log('Spotify track link clicked:', url);
getYouTubeLink(url, function(youtubeLink, error) {
if (youtubeLink) {
showNotification(`Redirecting to YouTube: ${youtubeLink}`);
window.location.href = youtubeLink;
} else {
showNotification(`Error: ${error || 'Could not convert link.'} Opening original Spotify link.`, true);
setTimeout(() => {
window.open(url, '_blank');
}, 2000);
}
});
}
}
}, true); // Use capture phase
// --- Configuration Menu ---
GM_registerMenuCommand('Set Converter API URL', function() {
const newUrl = prompt('Enter the full URL for your converter API (e.g., https://ytm2spotify.com//convert):', GM_getValue('converterApiUrl', DEFAULT_CONVERTER_API_URL));
if (newUrl === null) return; // User cancelled
if (newUrl.trim() === '') {
GM_setValue('converterApiUrl', DEFAULT_CONVERTER_API_URL);
converterApiUrl = DEFAULT_CONVERTER_API_URL;
showNotification(`Converter API URL reset to default: ${DEFAULT_CONVERTER_API_URL}`);
} else {
converterApiUrl = newUrl.trim();
GM_setValue('converterApiUrl', converterApiUrl);
showNotification(`Converter API URL updated to: ${converterApiUrl}`);
}
});
// Initial check and notification
console.log('Spotify to YouTube Redirector script loaded (v1.3).');
if (GM_getValue('converterApiUrl', DEFAULT_CONVERTER_API_URL) === 'http://localhost/your_converter_path/convert') {
showNotification('Spotify Redirector: API URL might be an old placeholder. Current default is for yt2spotify. Configure via script menu if needed.', true);
} else {
showNotification('Spotify to YouTube Redirector active.');
}
})();