Spotify to YouTube Redirector

Redirects Spotify track links to YouTube, using the omijn/yt2spotify converter endpoint.

  1. // ==UserScript==
  2. // @name Spotify to YouTube Redirector
  3. // @namespace http://tampermonkey.net/
  4. // @version 1.3
  5. // @description Redirects Spotify track links to YouTube, using the omijn/yt2spotify converter endpoint.
  6. // @author CL Using backend from ytm2spotify by omijn
  7. // @match *://*/*
  8. // @grant GM_xmlhttpRequest
  9. // @grant GM_getValue
  10. // @grant GM_setValue
  11. // @grant GM_registerMenuCommand
  12. // @connect *
  13. // @license MIT
  14. // ==/UserScript==
  15.  
  16. (function() {
  17. 'use strict';
  18.  
  19. // --- Configuration ---
  20. // Default API URL for the yt2spotify converter service.
  21. // This points to the '/convert' endpoint.
  22. const DEFAULT_CONVERTER_API_URL = 'https://ytm2spotify.com//convert'; // Updated API URL to include /convert
  23.  
  24. let converterApiUrl = GM_getValue('converterApiUrl', DEFAULT_CONVERTER_API_URL);
  25.  
  26. // --- Helper Functions ---
  27.  
  28. function showNotification(message, isError = false) {
  29. const notificationId = 'spotify-yt-redirector-notification';
  30. let notificationElement = document.getElementById(notificationId);
  31.  
  32. if (!notificationElement) {
  33. notificationElement = document.createElement('div');
  34. notificationElement.id = notificationId;
  35. Object.assign(notificationElement.style, {
  36. position: 'fixed',
  37. top: '20px',
  38. right: '20px',
  39. padding: '15px 20px',
  40. borderRadius: '8px',
  41. color: 'white',
  42. zIndex: '99999',
  43. fontSize: '16px',
  44. boxShadow: '0 4px 12px rgba(0,0,0,0.2)',
  45. opacity: '0',
  46. transition: 'opacity 0.5s ease-in-out, transform 0.3s ease-in-out',
  47. transform: 'translateX(100%)',
  48. fontFamily: 'Arial, sans-serif'
  49. });
  50. document.body.appendChild(notificationElement);
  51. }
  52.  
  53. notificationElement.textContent = message;
  54. notificationElement.style.backgroundColor = isError ? '#e74c3c' : '#2ecc71'; // Red for error, Green for success
  55.  
  56. // Animate in
  57. setTimeout(() => {
  58. notificationElement.style.opacity = '1';
  59. notificationElement.style.transform = 'translateX(0)';
  60. }, 50);
  61.  
  62. // Automatically hide after some time
  63. setTimeout(() => {
  64. notificationElement.style.opacity = '0';
  65. notificationElement.style.transform = 'translateX(100%)';
  66. }, isError ? 7000 : 4000);
  67. }
  68.  
  69.  
  70. function getYouTubeLink(spotifyUrl, callback) {
  71. if (!converterApiUrl) {
  72. showNotification('Converter API URL is not configured. Please set it via the script menu if the default is incorrect.', true);
  73. console.error('Spotify to YouTube Redirector: Converter API URL not configured or empty.');
  74. callback(null, 'Configuration error: Converter API URL not set.');
  75. return;
  76. }
  77.  
  78. showNotification('Converting Spotify link to YouTube...');
  79. console.log(`Attempting to convert Spotify URL: ${spotifyUrl} using API: ${converterApiUrl}`);
  80.  
  81. // Construct the API request URL
  82. // The backend expects 'to_service=youtube_ytm' for YouTube conversion.
  83. const apiUrl = `${converterApiUrl}?url=${encodeURIComponent(spotifyUrl)}&to_service=youtube_ytm`;
  84.  
  85. GM_xmlhttpRequest({
  86. method: 'GET',
  87. url: apiUrl,
  88. timeout: 15000,
  89. onload: function(response) {
  90. try {
  91. if (response.status >= 200 && response.status < 300) {
  92. const data = JSON.parse(response.responseText);
  93. if (data && data.results && data.results.length > 0 && data.results[0].url) {
  94. console.log('Conversion successful. YouTube URL:', data.results[0].url);
  95. callback(data.results[0].url, null);
  96. } else if (data && data.manual_search_link) {
  97. console.log('Direct link not found, using manual search link:', data.manual_search_link);
  98. callback(data.manual_search_link, null);
  99. }
  100. else {
  101. console.error('Conversion failed: Invalid response structure from API. Expected "results[0].url" or "manual_search_link". Response:', data);
  102. callback(null, 'Invalid response structure from converter API.');
  103. }
  104. } else {
  105. console.error(`Conversion failed: API request error. Status: ${response.status}`, response.responseText);
  106. callback(null, `Converter API request failed (Status: ${response.status}). Check API endpoint.`);
  107. }
  108. } catch (e) {
  109. console.error('Conversion failed: Error parsing API response. Is it valid JSON?', e, response.responseText);
  110. callback(null, 'Error parsing converter API response.');
  111. }
  112. },
  113. onerror: function(error) {
  114. 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);
  115. callback(null, 'Network error or CORS issue with converter API.');
  116. },
  117. ontimeout: function() {
  118. console.error('Conversion failed: API request timed out.');
  119. callback(null, 'Converter API request timed out.');
  120. }
  121. });
  122. }
  123.  
  124. // --- Event Listener ---
  125. document.addEventListener('click', function(event) {
  126. let targetElement = event.target;
  127. while (targetElement && targetElement.tagName !== 'A') {
  128. targetElement = targetElement.parentElement;
  129. }
  130.  
  131. if (targetElement && targetElement.href) {
  132. const url = targetElement.href;
  133. // Regex to identify Spotify track links
  134. // Example: https://open.spotify.com/track/TRACK_ID_HERE
  135. const spotifyTrackRegex = /^https?:\/\/open\.spotify\.com\/(?:[a-zA-Z]{2}-[a-zA-Z]{2}\/)?track\/([a-zA-Z0-9]+)/;
  136.  
  137. if (spotifyTrackRegex.test(url)) {
  138. event.preventDefault();
  139. event.stopPropagation();
  140.  
  141. console.log('Spotify track link clicked:', url);
  142.  
  143. getYouTubeLink(url, function(youtubeLink, error) {
  144. if (youtubeLink) {
  145. showNotification(`Redirecting to YouTube: ${youtubeLink}`);
  146. window.location.href = youtubeLink;
  147. } else {
  148. showNotification(`Error: ${error || 'Could not convert link.'} Opening original Spotify link.`, true);
  149. setTimeout(() => {
  150. window.open(url, '_blank');
  151. }, 2000);
  152. }
  153. });
  154. }
  155. }
  156. }, true); // Use capture phase
  157.  
  158. // --- Configuration Menu ---
  159. GM_registerMenuCommand('Set Converter API URL', function() {
  160. const newUrl = prompt('Enter the full URL for your converter API (e.g., https://ytm2spotify.com//convert):', GM_getValue('converterApiUrl', DEFAULT_CONVERTER_API_URL));
  161. if (newUrl === null) return; // User cancelled
  162.  
  163. if (newUrl.trim() === '') {
  164. GM_setValue('converterApiUrl', DEFAULT_CONVERTER_API_URL);
  165. converterApiUrl = DEFAULT_CONVERTER_API_URL;
  166. showNotification(`Converter API URL reset to default: ${DEFAULT_CONVERTER_API_URL}`);
  167. } else {
  168. converterApiUrl = newUrl.trim();
  169. GM_setValue('converterApiUrl', converterApiUrl);
  170. showNotification(`Converter API URL updated to: ${converterApiUrl}`);
  171. }
  172. });
  173.  
  174. // Initial check and notification
  175. console.log('Spotify to YouTube Redirector script loaded (v1.3).');
  176. if (GM_getValue('converterApiUrl', DEFAULT_CONVERTER_API_URL) === 'http://localhost/your_converter_path/convert') {
  177. showNotification('Spotify Redirector: API URL might be an old placeholder. Current default is for yt2spotify. Configure via script menu if needed.', true);
  178. } else {
  179. showNotification('Spotify to YouTube Redirector active.');
  180. }
  181.  
  182. })();