NOTICE: By continued use of this site you understand and agree to the binding Terms of Service and Privacy Policy.
// ==UserScript== // @name Spotify Enhancer (Spotify to YouTube) // @description Easily find and open YouTube videos for any Spotify track with a single click. // @icon https://raw.githubusercontent.com/exyezed/spotify-enhancer/refs/heads/main/extras/spotify-enhancer.png // @version 1.2 // @author exyezed // @namespace https://github.com/exyezed/spotify-enhancer/ // @supportURL https://github.com/exyezed/spotify-enhancer/issues // @license MIT // @match https://open.spotify.com/* // @grant GM_openInTab // @grant GM_setValue // @grant GM_getValue // ==/UserScript== (function() { 'use strict'; const CACHE_DURATION = 7 * 24 * 60 * 60 * 1000; const spinnerSVG = ` <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512" class="spinner-icon" style=" width: 36px; height: 36px; margin-left: 15px; vertical-align: middle; cursor: pointer; transition: transform 0.2s ease; display: none; "> <defs> <style>.fa-secondary{opacity:.4}</style> </defs> <path class="fa-secondary" fill="#FFFFFF" d="M0 256C0 114.9 114.1 .5 255.1 0C237.9 .5 224 14.6 224 32c0 17.7 14.3 32 32 32C150 64 64 150 64 256s86 192 192 192c69.7 0 130.7-37.1 164.5-92.6c-3 6.6-3.3 14.8-1 22.2c1.2 3.7 3 7.2 5.4 10.3c1.2 1.5 2.6 3 4.1 4.3c.8 .7 1.6 1.3 2.4 1.9c.4 .3 .8 .6 1.3 .9s.9 .6 1.3 .8c5 2.9 10.6 4.3 16 4.3c11 0 21.8-5.7 27.7-16c-44.3 76.5-127 128-221.7 128C114.6 512 0 397.4 0 256z"/> <path class="fa-primary" fill="#FFFFFF" d="M224 32c0-17.7 14.3-32 32-32C397.4 0 512 114.6 512 256c0 46.6-12.5 90.4-34.3 128c-8.8 15.3-28.4 20.5-43.7 11.7s-20.5-28.4-11.7-43.7c16.3-28.2 25.7-61 25.7-96c0-106-86-192-192-192c-17.7 0-32-14.3-32-32z"/> </svg> `; const youtubeIconSVG = ` <svg xmlns="http://www.w3.org/2000/svg" width="48" height="48" viewBox="0 0 24 24" class="youtube-icon" style=" margin-left: 10px; vertical-align: middle; cursor: pointer; transition: transform 0.2s ease; "> <path fill="#FF0033" d="M21.58,7.19c-0.23-0.86-0.91-1.54-1.77-1.77C18.25,5,12,5,12,5S5.75,5,4.19,5.42C3.33,5.65,2.65,6.33,2.42,7.19C2,8.75,2,12,2,12s0,3.25,0.42,4.81c0.23,0.86,0.91,1.54,1.77,1.77C5.75,19,12,19,12,19s6.25,0,7.81-0.42c0.86-0.23,1.54-0.91,1.77-1.77C22,15.25,22,12,22,12S22,8.75,21.58,7.19z"/> <polygon fill="#FFFFFF" points="10,15 15,12 10,9"/> </svg> `; function insertSVGIconNextToH1() { if (!window.location.href.includes('/track/')) { return; } const h1Elements = document.querySelectorAll('h1'); const iconContainer = document.createElement('div'); iconContainer.style.display = 'inline-block'; iconContainer.style.position = 'relative'; h1Elements.forEach(h1 => { if (!h1.querySelector('.youtube-icon')) { iconContainer.innerHTML = youtubeIconSVG + spinnerSVG; const youtubeIcon = iconContainer.querySelector('.youtube-icon'); const spinnerIcon = iconContainer.querySelector('.spinner-icon'); const styleTag = document.createElement('style'); styleTag.textContent = ` .youtube-icon:hover, .spinner-icon:hover { transform: scale(1.1); } @keyframes spin { 0% { transform: rotate(0deg); } 100% { transform: rotate(360deg); } } .spinner-icon { animation: spin 1s linear infinite; } `; document.head.appendChild(styleTag); const trackId = extractTrackId(); youtubeIcon.addEventListener('click', () => { youtubeIcon.style.display = 'none'; spinnerIcon.style.display = 'inline-block'; fetchYouTubeLink(trackId, youtubeIcon, spinnerIcon); }); h1.appendChild(iconContainer); } }); } function extractTrackId() { const urlMatch = window.location.href.match(/track\/([a-zA-Z0-9]+)/); return urlMatch ? urlMatch[1] : null; } function fetchYouTubeLink(trackId, youtubeIcon, spinnerIcon) { if (!trackId) { console.error('No track ID found'); return; } const cachedData = GM_getValue(`youtube_link_${trackId}`); if (cachedData && (Date.now() - cachedData.timestamp) < CACHE_DURATION) { GM_openInTab(cachedData.youtube_url, { active: true }); youtubeIcon.style.display = 'inline-block'; spinnerIcon.style.display = 'none'; return; } fetch(`https://spotapis.vercel.app/track/${trackId}`) .then(response => response.json()) .then(data => { if (data.youtube_url) { GM_setValue(`youtube_link_${trackId}`, { youtube_url: data.youtube_url, timestamp: Date.now() }); GM_openInTab(data.youtube_url, { active: true }); } else { console.error('No YouTube URL found'); } }) .catch(error => { console.error('Error fetching YouTube link:', error); }) .finally(() => { youtubeIcon.style.display = 'inline-block'; spinnerIcon.style.display = 'none'; }); } function removeYouTubeIcon() { const iconContainer = document.querySelector('.youtube-icon').parentNode; if (iconContainer) { iconContainer.remove(); } } function handleURLChange() { removeYouTubeIcon(); insertSVGIconNextToH1(); } const observer = new MutationObserver((mutations) => { mutations.forEach((mutation) => { if (mutation.addedNodes.length) { insertSVGIconNextToH1(); } }); }); observer.observe(document.body, { childList: true, subtree: true }); const urlObserver = new MutationObserver((mutations) => { mutations.forEach((mutation) => { if (mutation.type === 'childList') { handleURLChange(); } }); }); urlObserver.observe(document.querySelector('title'), { subtree: true, characterData: true, childList: true }); insertSVGIconNextToH1(); console.log('Spotify Enhancer (Spotify to YouTube) is running'); })();