Soundcloud Downloader Clean

*This version no longer works due to Soundcloud API changes - will update soon* - An ad-less, multilingual, clean Soundcloud downloader with robust code. Adds a 'Download' button in the toolbar of all single track views.

目前為 2019-12-28 提交的版本,檢視 最新版本

// ==UserScript==
// @name         Soundcloud Downloader Clean
// @namespace    https://openuserjs.org/users/webketje
// @version      0.1.1
// @description  *This version no longer works due to Soundcloud API changes - will update soon* - An ad-less, multilingual, clean Soundcloud downloader with robust code. Adds a 'Download' button in the toolbar of all single track views.
// @author       webketje
// @license      MIT
// @icon         https://a-v2.sndcdn.com/assets/images/sc-icons/favicon-2cadd14bdb.ico
// @homepageURL  https://gist.github.com/webketje/8cd2e6ae8a86dbe0533c5d2c612c42c6
// @supportURL   https://gist.github.com/webketje/8cd2e6ae8a86dbe0533c5d2c612c42c6
// @noframes
// @match        https://soundcloud.com/*
// @grant        unsafeWindow
// @require      https://cdn.jsdelivr.net/npm/[email protected]/dist/FileSaver.min.js
// ==/UserScript==

/* globals saveAs */

(function() {
    'use strict';

    var win = unsafeWindow || window;
    var containerSelector = '.listenEngagement__footer .sc-button-toolbar';

    /**
    *   @desc Log to console only if debug is true
    */
    function log() {
      var stamp  = new Date().toLocaleString(),
          args   = [].slice.call(arguments),
          prefix = ['SCDLC', stamp, '-'];
      scdl.debug && console.log(prefix.concat(args).join(' '));
    };

    /**
    *   @desc There is no other way to retrieve a Soundcloud client_id than by spying on existing requests.
    *         We temporarily patch the XHR.send method to retrieve the url passed to it.
    *   @param restoreIfTrue - restores the original prototype method when true is returned
    *   @param onRestore - a function to exec when the restoreIfTrue condition is met
    */
    function patchXHR(restoreIfTrue, onRestore) {
      var originalXHR = win.XMLHttpRequest.prototype.open;

      win.XMLHttpRequest.prototype.open = function() {
        originalXHR.apply(this, arguments);
        var restore = restoreIfTrue.apply(this, arguments);
        if (restore) {
          win.XMLHttpRequest.prototype.open = originalXHR;
          onRestore(restore);
        }
      };
    };

    var scdl = {
      debug: false,
      client_id: '',
      dlButtonId: 'scdlc-btn'
    };

    scdl.getTrackName = function(trackJSON) {
      return [
        trackJSON.user.username,
        trackJSON.title
      ].join(' - ');
    };

    scdl.getStreamURL = function(url, onresolve, onerror) {
      var xhr = new XMLHttpRequest();
      xhr.onload = function() {
        var trackJSON = JSON.parse(xhr.responseText);
        onresolve(trackJSON.errors || !trackJSON.stream_url ? false : {
          stream_url: trackJSON.stream_url + '?client_id=' + this.client_id,
          track_name: this.getTrackName(trackJSON)
        });
      }.bind(this);
      xhr.onerror = function() {
        onerror(false);
      };
      xhr.open('GET', 'https://api.soundcloud.com/resolve?url=' + encodeURIComponent(url) + '&client_id=' + this.client_id);
      xhr.send();
    };

    scdl.button = {
      label: {
        en: 'Download',
        es: 'Descargar',
        fr: 'Télécharger',
        nl: 'Download',
        de: 'Download',
        pl: 'Ściągnij',
        it: 'Scaricare',
        pt_BR: 'Baixar',
        sv: 'Ladda ner'
      },
      download: function(e) {
        e.preventDefault();
        saveAs(e.target.href, e.target.dataset.title);
      },
      render: function(href, title, onClick) {
        var label = scdl.button.label[document.documentElement.lang];
        var a = document.createElement('a');
        a.className = "sc-button";
        a.href = href;
        a.id = scdl.dlButtonId;
        a.textContent = label;
        a.dataset.title = title + '.mp3';
        a.setAttribute('download', title + '.mp3');
        a.target = '_blank';
        a.onclick = onClick;
        a.style.marginLeft = '5px';
        a.style.cssFloat = 'left';
        return a;
      },
      attach:function() {
        this.remove();
        var f = document.querySelector(containerSelector);
        if (f)
          f.insertAdjacentElement('afterend', this.render.apply(this, arguments));
      },
      remove: function() {
        var btn = document.getElementById(scdl.dlButtonId);
        if (btn)
          btn.parentNode.removeChild(btn);
      }
    };

    scdl.parseClientIdFromURL = function(url) {
      var search = /client_id=([\w\d]+)&*/;
      return url && url.match(search) && url.match(search)[1];
    };

    scdl.getClientID = function(onClientIDFound) {
      patchXHR(function(method, url) {
        return scdl.parseClientIdFromURL(url);
      }, onClientIDFound);
    };

    scdl.load = function(url) {
      // for now only make available for single track pages
      if (/^(\/(you|stations|discover|stream|upload|search|settings))/.test(win.location.pathname)) {
        scdl.button.remove();
        return;
      }

      scdl.getStreamURL(url,
        function onSuccess(result) {
          if (!result) {
            scdl.button.remove();
          } else {
            log('Detected valid Soundcloud artist track URL. Requesting info...');
            scdl.button.attach(
              result.stream_url,
              result.track_name,
              scdl.button.download
            );
          }
        },
        scdl.button.remove
      );
    };

    // patch front-end navigation
    ['pushState','replaceState','forward','back','go'].forEach(function(event) {
      var tmp = win.history.pushState;
      win.history[event] = function() {
        tmp.apply(win.history, arguments);
        scdl.load(win.location.href);
      }
    });

    scdl.getClientID(function(id) {
      log('Found Soundcloud client id:', id, '. Initializing...');
      scdl.client_id = id;
      scdl.load(win.location.href);
    });
})();

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址