Soundcloud Album Art Downloader+

Allows you to download album art on the Soundcloud website.

// ==UserScript==
// @name         Soundcloud Album Art Downloader+
// @namespace    Wizzergod
// @version      1.0.1
// @description  Allows you to download album art on the Soundcloud website.
// @author       Wizzergod
// @icon         https://www.google.com/s2/favicons?sz=64&domain=soundcloud.com
// @license MIT
// @include      *://*.soundcloud.*/*
// @include      *://soundcloud.*/*
// @match        *://*.soundcloud.*/*
// @match        *://soundcloud.*/*
// @grant        GM_download
// @grant        GM_info
// @grant        GM_notification
// ==/UserScript==

(function() {
    'use strict';

    setInterval(function() {
        const sizes = { 't500x500': '500x500', 'original': 'original size' };
        const regexp = /t\d{3}x\d{3}/gi;

        const modals = document.querySelectorAll('.modal__content:not(.appeared)');
        modals.forEach(handleModal);

        function handleModal(modal) {
            let imageURL = null;

            const imageFull = modal.querySelector('.sc-artwork-40x, .sc-artwork-64x, .sc-artwork-174x, .sc-artwork-184x');
            if (imageFull) {
                imageURL = getComputedStyle(imageFull).backgroundImage;
            }
            imageURL = /url\("(.+)"\)/.exec(imageURL)[1];

            const imageContent = modal.querySelector('.imageContent');
            if (imageContent) {
                while (imageContent.firstChild) {
                    imageContent.removeChild(imageContent.firstChild);
                }

                const img = document.createElement('img');
                img.style.width = '500px';
                img.style.height = '500px';
                img.style.marginBottom = '15px';
                img.src = imageURL.replace(regexp, 't500x500');
                imageContent.appendChild(img);

                Object.keys(sizes).forEach(function(size) {
                    const url = imageURL.replace(regexp, size);
                    checkImage(url, function(exists) {
                        if (exists) {
                            imageContent.appendChild(makeButton(url, sizes[size]));
                        }
                    });
                });
            }
        }

        function checkImage(url, callback) {
            const xhr = new XMLHttpRequest();
            xhr.open('HEAD', url, true);
            xhr.onreadystatechange = function() {
                if (xhr.readyState === 4) {
                    callback(xhr.status === 200);
                }
            };
            xhr.send();
        }

        function makeButton(url, sizeLabel) {
            const btn = document.createElement('button');
            btn.style.margin = '10px auto 0 auto';
            btn.style.display = 'block';
            btn.style.width = '100%';
            btn.className = 'sc-button sc-button-medium sc-button-responsive';
            btn.textContent = 'Download ' + sizeLabel;
            btn.addEventListener('click', function(e) {
                e.preventDefault();
                download(url);
            });
            return btn;
        }

        function download(url) {
            url = url.split('?')[0];
            if (!url.startsWith('http')) {
                url = window.location.protocol + (url.startsWith('//') ? '' : '//') + url;
            }

            const fileName = url.split('/').pop();

            const options = {
                url: url,
                name: fileName,
                onerror: function(e) {
                    logError('Download failed. Reason: ' + e.error);
                }
            };

            GM_download(options);
        }

        function logError(message) {
            const details = {
                title: GM_info.script.name,
                text: message,
            };

            GM_notification(details);
            console.error(`%c${GM_info.script.name}%c: ${message}`, 'font-weight: bold', 'font-weight: normal');
        }
    }, 250);
})();