NOTICE: By continued use of this site you understand and agree to the binding Terms of Service and Privacy Policy.
// ==UserScript== // @name Cobalt Tools (SoundCloud Direct Downloader) // @description Integrate a download button for SoundCloud tracks and open original cover art. // @icon https://raw.githubusercontent.com/exyezed/cobalt-tools/refs/heads/main/extras/cobalt-tools.png // @version 1.1 // @author exyezed // @namespace https://github.com/exyezed/cobalt-tools/ // @supportURL https://github.com/exyezed/cobalt-tools/issues // @license MIT // @match https://soundcloud.com/* // @grant GM.xmlHttpRequest // @connect exyezed.vercel.app // ==/UserScript== (function() { 'use strict'; function isDiscoverPage() { return window.location.pathname === '/discover'; } function isSetUrl(url) { return /\/[^\/]+\/sets\//.test(url); } function isSetUrlForListenArtwork(url) { return /\/sets\//.test(url) && !url.includes('?in='); } function createSvgElement(color, size = 16) { const svgElement = document.createElementNS("http://www.w3.org/2000/svg", "svg"); svgElement.setAttribute("viewBox", "0 0 448 512"); svgElement.setAttribute("width", size.toString()); svgElement.setAttribute("height", size.toString()); svgElement.style.transition = "0.2s"; svgElement.style.fill = color; const path = document.createElementNS("http://www.w3.org/2000/svg", "path"); path.setAttribute("d", "M378.1 198.6L249.5 341.4c-6.1 6.7-14.7 10.6-23.8 10.6l-3.5 0c-9.1 0-17.7-3.8-23.8-10.6L69.9 198.6c-3.8-4.2-5.9-9.8-5.9-15.5C64 170.4 74.4 160 87.1 160l72.9 0 0-128c0-17.7 14.3-32 32-32l64 0c17.7 0 32 14.3 32 32l0 128 72.9 0c12.8 0 23.1 10.4 23.1 23.1c0 5.7-2.1 11.2-5.9 15.5zM64 352l0 64c0 17.7 14.3 32 32 32l256 0c17.7 0 32-14.3 32-32l0-64c0-17.7 14.3-32 32-32s32 14.3 32 32l0 64c0 53-43 96-96 96L96 512c-53 0-96-43-96-96l0-64c0-17.7 14.3-32 32-32s32 14.3 32 32z"); svgElement.appendChild(path); return svgElement; } function addDownloadIcon() { if (isDiscoverPage()) return; const volumeControl = document.querySelector('.playControls__volume'); if (!volumeControl || document.querySelector('.playControls__cobalt')) return; const iconWrapper = document.createElement('div'); iconWrapper.className = 'playControls__cobalt playControls__control'; iconWrapper.style.cssText = ` display: flex; align-items: center; justify-content: center; width: 24px; height: 24px; cursor: pointer; `; const svgElement = createSvgElement("#333", 16); iconWrapper.appendChild(svgElement); volumeControl.parentNode.insertBefore(iconWrapper, volumeControl.nextSibling); iconWrapper.addEventListener('click', (e) => { e.preventDefault(); e.stopPropagation(); handleDownload(svgElement); }); iconWrapper.addEventListener('mouseenter', () => { svgElement.style.fill = "#f50"; }); iconWrapper.addEventListener('mouseleave', () => { svgElement.style.fill = "#333"; }); } function addDownloadIconToTiles() { if (isDiscoverPage()) return; const tiles = document.querySelectorAll('.playableTile__artwork'); tiles.forEach(tile => { if (!tile.querySelector('.playableTile__cobalt')) { const artworkLink = tile.querySelector('.playableTile__artworkLink'); if (artworkLink) { const href = artworkLink.getAttribute('href'); if (isSetUrl(href)) return; const iconWrapper = document.createElement('div'); iconWrapper.className = 'playableTile__cobalt'; iconWrapper.style.cssText = ` position: absolute; top: 8px; right: 8px; z-index: 3; cursor: pointer; padding: 4px; background-color: rgba(0, 0, 0, 0.9); border-radius: 4px; `; const svgElement = createSvgElement("#ffffff", 14); iconWrapper.appendChild(svgElement); tile.appendChild(iconWrapper); iconWrapper.addEventListener('click', (e) => { e.preventDefault(); e.stopPropagation(); const artworkLink = tile.querySelector('.playableTile__artworkLink'); if (artworkLink) { const href = artworkLink.getAttribute('href'); const trackUrl = href.startsWith('http') ? href : `https://soundcloud.com${href}`; handleDownload(svgElement, trackUrl); } }); iconWrapper.addEventListener('mouseenter', () => { svgElement.style.fill = "#f50"; }); iconWrapper.addEventListener('mouseleave', () => { svgElement.style.fill = "#ffffff"; }); } } }); } function addDownloadIconToSoundCoverArt() { if (isDiscoverPage()) return; const coverArts = document.querySelectorAll('.sound__coverArt'); coverArts.forEach(coverArt => { if (!coverArt.querySelector('.sound__coverArt__cobalt')) { const href = coverArt.getAttribute('href'); if (isSetUrl(href)) return; const iconWrapper = document.createElement('div'); iconWrapper.className = 'sound__coverArt__cobalt'; iconWrapper.style.cssText = ` position: absolute; top: 8px; right: 8px; z-index: 3; cursor: pointer; padding: 4px; background-color: rgba(0, 0, 0, 0.9); border-radius: 4px; `; const svgElement = createSvgElement("#ffffff", 14); iconWrapper.appendChild(svgElement); coverArt.appendChild(iconWrapper); iconWrapper.addEventListener('click', (e) => { e.preventDefault(); e.stopPropagation(); const href = coverArt.getAttribute('href'); const trackUrl = href.startsWith('http') ? href : `https://soundcloud.com${href}`; handleDownload(svgElement, trackUrl); }); iconWrapper.addEventListener('mouseenter', () => { svgElement.style.fill = "#f50"; }); iconWrapper.addEventListener('mouseleave', () => { svgElement.style.fill = "#ffffff"; }); } }); } function addDownloadIconToListenArtwork() { if (isDiscoverPage()) return; const artworkWrapper = document.querySelector('.listenArtworkWrapper__artwork'); if (!artworkWrapper) return; const isSetUrl = isSetUrlForListenArtwork(window.location.href); const hasDownloadButton = !isSetUrl; addOriginalArtButton(artworkWrapper, hasDownloadButton); if (hasDownloadButton && !artworkWrapper.querySelector('.listenArtworkWrapper__cobalt')) { const iconWrapper = document.createElement('div'); iconWrapper.className = 'listenArtworkWrapper__cobalt'; iconWrapper.style.cssText = ` position: absolute; top: 12px; right: 12px; z-index: 3; cursor: pointer; padding: 6px; background-color: rgba(0, 0, 0, 0.9); border-radius: 4px; `; const svgElement = createSvgElement("#ffffff", 18); iconWrapper.appendChild(svgElement); artworkWrapper.appendChild(iconWrapper); iconWrapper.addEventListener('click', (e) => { e.preventDefault(); e.stopPropagation(); handleDownloadURL(svgElement); }); iconWrapper.addEventListener('mouseenter', () => { svgElement.style.fill = "#f50"; }); iconWrapper.addEventListener('mouseleave', () => { svgElement.style.fill = "#ffffff"; }); } } function addOriginalArtButton(artworkWrapper, hasDownloadButton) { if (artworkWrapper.querySelector('.listenArtworkWrapper__originalCoverArt')) return; const originalArtWrapper = document.createElement('div'); originalArtWrapper.className = 'listenArtworkWrapper__originalCoverArt'; originalArtWrapper.style.cssText = ` position: absolute; top: 12px; right: ${hasDownloadButton ? '54px' : '12px'}; z-index: 4; cursor: pointer; padding: 6px; background-color: rgba(0, 0, 0, 0.9); border-radius: 4px; `; const originalArtSvg = document.createElementNS("http://www.w3.org/2000/svg", "svg"); originalArtSvg.setAttribute("viewBox", "0 0 512 512"); originalArtSvg.setAttribute("width", "18"); originalArtSvg.setAttribute("height", "18"); originalArtSvg.style.fill = "#ffffff"; const path = document.createElementNS("http://www.w3.org/2000/svg", "path"); path.setAttribute("d", "M0 96C0 60.7 28.7 32 64 32l384 0c35.3 0 64 28.7 64 64l0 320c0 35.3-28.7 64-64 64L64 480c-35.3 0-64-28.7-64-64L0 96zM323.8 202.5c-4.5-6.6-11.9-10.5-19.8-10.5s-15.4 3.9-19.8 10.5l-87 127.6L170.7 297c-4.6-5.7-11.5-9-18.7-9s-14.2 3.3-18.7 9l-64 80c-5.8 7.2-6.9 17.1-2.9 25.4s12.4 13.6 21.6 13.6l96 0 32 0 208 0c8.9 0 17.1-4.9 21.2-12.8s3.6-17.4-1.4-24.7l-120-176zM112 192a48 48 0 1 0 0-96 48 48 0 1 0 0 96z"); originalArtSvg.appendChild(path); originalArtWrapper.appendChild(originalArtSvg); artworkWrapper.appendChild(originalArtWrapper); originalArtWrapper.addEventListener('click', (e) => { e.preventDefault(); e.stopPropagation(); openOriginalCoverArt(); }); originalArtWrapper.addEventListener('mouseenter', () => { originalArtSvg.style.fill = "#f50"; }); originalArtWrapper.addEventListener('mouseleave', () => { originalArtSvg.style.fill = "#ffffff"; }); } function handleDownload(svgElement, trackUrl) { let fullUrl; if (trackUrl) { fullUrl = trackUrl; } else { const trackLink = document.querySelector('a.playbackSoundBadge__titleLink'); if (!trackLink) { showError(svgElement); return; } const href = trackLink.getAttribute('href'); fullUrl = href.startsWith('http') ? href : `https://soundcloud.com${href}`; } console.log('Track URL:', fullUrl); showLoading(svgElement); const apiUrl = `https://exyezed.vercel.app/api/cobalt/soundcloud/${encodeURIComponent(fullUrl)}`; GM.xmlHttpRequest({ method: "GET", url: apiUrl, onload: function(response) { try { const data = JSON.parse(response.responseText); if (data.url) { showSuccess(svgElement); setTimeout(() => { const downloadLink = document.createElement('a'); downloadLink.href = data.url; downloadLink.download = data.title || 'soundcloud_track'; document.body.appendChild(downloadLink); downloadLink.click(); document.body.removeChild(downloadLink); console.log('Download started'); }, 1000); } else { console.error('Download URL not found in the response'); showError(svgElement); } } catch (e) { console.error('Error parsing response:', e); showError(svgElement); } }, onerror: function(error) { console.error('Error downloading track:', error); showError(svgElement); } }); } function handleDownloadURL(svgElement) { const currentURL = window.location.href; console.log('Current URL:', currentURL); showLoading(svgElement); const apiUrl = `https://exyezed.vercel.app/api/cobalt/soundcloud/${encodeURIComponent(currentURL)}`; GM.xmlHttpRequest({ method: "GET", url: apiUrl, onload: function(response) { try { const data = JSON.parse(response.responseText); if (data.url) { showSuccess(svgElement); setTimeout(() => { const downloadLink = document.createElement('a'); downloadLink.href = data.url; downloadLink.download = data.title || 'soundcloud_track'; document.body.appendChild(downloadLink); downloadLink.click(); document.body.removeChild(downloadLink); console.log('Download started'); }, 1000); } else { console.error('Download URL not found in the response'); showError(svgElement); } } catch (e) { console.error('Error parsing response:', e); showError(svgElement); } }, onerror: function(error) { console.error('Error downloading track:', error); showError(svgElement); } }); } function openOriginalCoverArt() { const selectors = [ '.fullHero__artwork .image__full', '.listenArtworkWrapper__artwork .image__full', '.listenArtworkWrapper__artwork .sc-artwork' ]; let artworkElement = null; for (const selector of selectors) { artworkElement = document.querySelector(selector); if (artworkElement) break; } if (artworkElement) { console.log('Artwork element found:', artworkElement); let backgroundImage = window.getComputedStyle(artworkElement).backgroundImage; if (!backgroundImage || backgroundImage === 'none') { backgroundImage = artworkElement.getAttribute('src'); console.log('Using src attribute:', backgroundImage); } else { console.log('Using background-image:', backgroundImage); } let originalUrl = extractAndCleanUrl(backgroundImage); if (originalUrl) { console.log('Opening URL:', originalUrl); window.open(originalUrl, '_blank'); } else { console.error('Could not extract cover art URL from:', backgroundImage); } } else { console.error('Could not find artwork element. Tried selectors:', selectors); } } function extractAndCleanUrl(input) { const urlMatch = input.match(/https?:\/\/[^"']*?sndcdn\.com\/[^"')]+/); if (urlMatch) { let url = urlMatch[0]; url = url.split('?')[0]; url = url.replace(/-t\d+x\d+/, '-original'); if (!/\.(jpg|jpeg|png|gif)$/i.test(url)) { url += '.jpg'; } return url; } return null; } function showLoading(svgElement) { while (svgElement.firstChild) { svgElement.removeChild(svgElement.firstChild); } svgElement.setAttribute("viewBox", "0 0 512 512"); const originalColor = svgElement.style.fill; svgElement.style.fill = originalColor === "#ffffff" ? "#ffffff" : "#f50"; const secondaryPath = document.createElementNS("http://www.w3.org/2000/svg", "path"); secondaryPath.setAttribute("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"); secondaryPath.style.opacity = "0.4"; svgElement.appendChild(secondaryPath); const primaryPath = document.createElementNS("http://www.w3.org/2000/svg", "path"); primaryPath.setAttribute("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"); svgElement.appendChild(primaryPath); svgElement.style.animation = 'spin 1s linear infinite'; } function showSuccess(svgElement) { while (svgElement.firstChild) { svgElement.removeChild(svgElement.firstChild); } svgElement.style.animation = ''; const originalColor = svgElement.style.fill; svgElement.style.fill = originalColor === "#ffffff" ? "#ffffff" : "#f50"; svgElement.setAttribute("viewBox", "0 0 512 512"); const successPath = document.createElementNS("http://www.w3.org/2000/svg", "path"); successPath.setAttribute("d", "M256 512A256 256 0 1 0 256 0a256 256 0 1 0 0 512zM369 209L241 337c-9.4 9.4-24.6 9.4-33.9 0l-64-64c-9.4-9.4-9.4-24.6 0-33.9s24.6-9.4 33.9 0l47 47L335 175c9.4-9.4 24.6-9.4 33.9 0s9.4 24.6 0 33.9z"); svgElement.appendChild(successPath); setTimeout(() => resetIcon(svgElement), 2000); } function showError(svgElement) { while (svgElement.firstChild) { svgElement.removeChild(svgElement.firstChild); } svgElement.style.animation = ''; const originalColor = svgElement.style.fill; svgElement.style.fill = originalColor === "#ffffff" ? "#ffffff" : "#333"; svgElement.setAttribute("viewBox", "0 0 512 512"); const errorPath = document.createElementNS("http://www.w3.org/2000/svg", "path"); errorPath.setAttribute("d", "M256 512A256 256 0 1 0 256 0a256 256 0 1 0 0 512zM175 175c9.4-9.4 24.6-9.4 33.9 0l47 47 47-47c9.4-9.4 24.6-9.4 33.9 0s9.4 24.6 0 33.9l-47 47 47 47c9.4 9.4 9.4 24.6 0 33.9s-24.6 9.4-33.9 0l-47-47-47 47c-9.4 9.4-24.6 9.4-33.9 0s-9.4-24.6 0-33.9l47-47-47-47c-9.4-9.4-9.4-24.6 0-33.9z"); svgElement.appendChild(errorPath); setTimeout(() => resetIcon(svgElement), 2000); } function resetIcon(svgElement) { while (svgElement.firstChild) { svgElement.removeChild(svgElement.firstChild); } const originalColor = svgElement.getAttribute('data-original-color') || "#333"; svgElement.style.fill = originalColor; svgElement.setAttribute("viewBox", "0 0 448 512"); svgElement.style.animation = ''; const originalPath = document.createElementNS("http://www.w3.org/2000/svg", "path"); originalPath.setAttribute("d", "M378.1 198.6L249.5 341.4c-6.1 6.7-14.7 10.6-23.8 10.6l-3.5 0c-9.1 0-17.7-3.8-23.8-10.6L69.9 198.6c-3.8-4.2-5.9-9.8-5.9-15.5C64 170.4 74.4 160 87.1 160l72.9 0 0-128c0-17.7 14.3-32 32-32l64 0c17.7 0 32 14.3 32 32l0 128 72.9 0c12.8 0 23.1 10.4 23.1 23.1c0 5.7-2.1 11.2-5.9 15.5zM64 352l0 64c0 17.7 14.3 32 32 32l256 0c17.7 0 32-14.3 32-32l0-64c0-17.7 14.3-32 32-32s32 14.3 32 32l0 64c0 53-43 96-96 96L96 512c-53 0-96-43-96-96l0-64c0-17.7 14.3-32 32-32s32 14.3 32 32z"); svgElement.appendChild(originalPath); } const styleSheet = document.createElement('style'); styleSheet.textContent = ` @keyframes spin { from { transform: rotate(0deg); } to { transform: rotate(360deg); } } .sc-classic .playControls__control, .sc-classic .playControls__control:not(:first-child) { margin-right: 12px; } .playableTile__cobalt, .listenArtworkWrapper__cobalt, .listenArtworkWrapper__originalCoverArt { display: flex; align-items: center; justify-content: center; } .playableTile__cobalt:hover, .listenArtworkWrapper__cobalt:hover, .listenArtworkWrapper__originalCoverArt:hover { background-color: rgba(0, 0, 0, 0.9); } .playableTile__cobalt:hover svg, .listenArtworkWrapper__cobalt:hover svg, .listenArtworkWrapper__originalCoverArt:hover svg { fill: #f50 !important; } .playableTile__cobalt svg, .listenArtworkWrapper__cobalt svg, .listenArtworkWrapper__originalCoverArt svg { fill: #ffffff !important; } .playControls__cobalt svg { width: 16px; height: 16px; } .playableTile__cobalt svg { width: 14px; height: 14px; } .listenArtworkWrapper__cobalt svg, .listenArtworkWrapper__originalCoverArt svg { width: 18px; height: 18px; } .sound__coverArt__cobalt { display: flex; align-items: center; justify-content: center; } .sound__coverArt__cobalt:hover { background-color: rgba(0, 0, 0, 0.9); } .sound__coverArt__cobalt:hover svg { fill: #f50 !important; } .sound__coverArt__cobalt svg { fill: #ffffff !important; width: 14px; height: 14px; } `; document.head.appendChild(styleSheet); if (!isDiscoverPage()) { addDownloadIcon(); addDownloadIconToTiles(); addDownloadIconToSoundCoverArt(); addDownloadIconToListenArtwork(); } const observer = new MutationObserver((mutations) => { if (!isDiscoverPage()) { for (let mutation of mutations) { if (mutation.type === 'childList') { addDownloadIcon(); addDownloadIconToTiles(); addDownloadIconToSoundCoverArt(); addDownloadIconToListenArtwork(); } } } }); observer.observe(document.body, { childList: true, subtree: true }); let lastUrl = location.href; new MutationObserver(() => { const url = location.href; if (url !== lastUrl) { lastUrl = url; if (isDiscoverPage()) { document.querySelectorAll('.playableTile__cobalt, .playControls__cobalt, .sound__coverArt__cobalt, .listenArtworkWrapper__cobalt').forEach(el => el.remove()); } else { addDownloadIcon(); addDownloadIconToTiles(); addDownloadIconToSoundCoverArt(); addDownloadIconToListenArtwork(); } } }).observe(document, {subtree: true, childList: true}); console.log('Cobalt Tools (SoundCloud Direct Downloader) is running'); })();