GreasyFork: download script button

If you have a script manager and you want to download some script without installing it, this script will help

As of 2021-12-24. See the latest version.

// ==UserScript==
// @name          GreasyFork: download script button
// @description   If you have a script manager and you want to download some script without installing it, this script will help
// @author        Konf
// @version       1.2.2
// @namespace     https://greatest.deepsurf.us/users/424058
// @icon          https://www.google.com/s2/favicons?domain=greatest.deepsurf.us&sz=32
// @include       /^http(s|):\/\/(sleaz|greas)yfork\.org\/[a-zA-Z_-]*\/scripts\/\d.*$/
// @compatible    Chrome
// @compatible    Opera
// @compatible    Firefox
// @run-at        document-end
// @grant         GM_addStyle
// @noframes
// ==/UserScript==

/* jshint esversion: 6 */
/* eslint-disable no-multi-spaces */

(function() {
  'use strict';

  const langStrings = (function() {
    const userLang = location.pathname.split('/')[1];
    const langStringsList = {
      'en': {
        Dl: 'Download without installing',
        failedDl: 'Failed to download the script. ' +
          'More info might be in the browser console'
      },
      'zh-CN': {
        Dl: '下载此脚本',
        failedDl: '无法下载此脚本'
      },
      'ru': {
        Dl: 'Скачать не устанавливая',
        failedDl: 'Не удалось скачать скрипт. ' +
          'Больше информации может быть в консоли браузера'
      }
    };

    return function(id) {
      const userStrings = langStringsList[userLang];
      const enStrings = langStringsList.en;

      return (userStrings || enStrings)[id] || enStrings[id];
    }
  }());

  // https://img.icons8.com/pastel-glyph/64/ffffff/download.png
  // array because tampermonkey can't roll up a string in the code editor
  const downloadIcon = ['data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAE',
  'AAAABACAYAAACqaXHeAAAABmJLR0QA/wD/AP+gvaeTAAABgUlEQVR4nO3ZTU6DUAAE4HnE',
  'k+jWG3TrHVwY3XoEt23cGleamtRtTbyPS3sCV0bXjptHRAIEsM/hZ76kCZRHGaZAGwDMzM',
  'zMbJ6CasMkMwBncXYbQvhSZZEgecEf56ocmWrDAA4L00eqEMoCBsEFqAOouQB1ADUXoA6g',
  '5gLUAdRcgDqAmgtQB1BzAeoAakkLIHlN8pPkDcnWd59IBpK3cd1VyoxJkfwo3PV5KJZAcl',
  'lYtiy8H+LY3HvKjKlPgU1h+hLAuulIiMvWcWzVZ4xL/Dbv+Nsjyax8BMSx96Wxm3jzdLwa',
  'SliVCpjezucqzmuSfKuZJkvXi0moORKqTOebL2tRwnR3PtdQwvR3PldRgmznlc8GA4DTOP',
  'scQqAqy6x1+X8+6Ke5yfNxIE9z6/TN1+XCM4inuQ165ZvHz04DF6AOoOYC1AHUXIA6gNpB',
  'z/UWJK/2muTvFn1W6lvASXyNXpdTYJcsxf69th3Y5QjYAiCA485x/tcLgCd1CDMzMzMbum',
  '8+xtkWw6QCvwAAAABJRU5ErkJggg=='].join('');

  const b = document.createElement('button'); // downloadBtn
  const bIcon = document.createElement('div');
  const bParent = document.body.querySelector('div#install-area');
  const bNeighbour = document.body.querySelector('a.install-help-link');
  const installBtn = document.body.querySelector('a.install-link');

  if (!bParent || !bNeighbour || !installBtn) return;

  GM_addStyle([`
    .tm-dsb-downloadBtn {
      width: 44px;
      height: 38px;
      position: relative;
      padding: 0;
      vertical-align: bottom;
      cursor: pointer;
      border: none;
      outline: none;
      background: #0F750F;
      transition: box-shadow 0.2s;
    }

    .tm-dsb-downloadBtn:hover,
    .tm-dsb-downloadBtn:focus {
      box-shadow: 0 8px 16px 0 rgb(0 0 0 / 20%), 0 6px 20px 0 rgb(0 0 0 / 19%);
    }


    .tm-dsb-downloadBtn__icon {
      position: absolute;
    }

    .tm-dsb-downloadBtn__icon--download {
      width: 34px;
      height: 34px;
      top: 2px;
      left: 5px;
      background: url(${downloadIcon});
      background-size: contain;
    }

    .tm-dsb-downloadBtn__icon--loading,
    .tm-dsb-downloadBtn__icon--loading:after {
      border-radius: 50%;
      width: 16px;
      height: 16px;
    }

    .tm-dsb-downloadBtn__icon--loading {
      top: 6px;
      left: 9px;
      border-top: 5px solid rgba(255, 255, 255, 0.2);
      border-right: 5px solid rgba(255, 255, 255, 0.2);
      border-bottom: 5px solid rgba(255, 255, 255, 0.2);
      border-left: 5px solid #ffffff;
      transform: translateZ(0);
      animation: loading 1.1s infinite linear;
    }

    @keyframes loading {
      0% {
        transform: rotate(0deg);
      }
      100% {
        transform: rotate(360deg);
      }
    }
  `][0]);

  b.title = langStrings('Dl');
  b.className = 'tm-dsb-downloadBtn';
  bIcon.className = 'tm-dsb-downloadBtn__icon tm-dsb-downloadBtn__icon--download';
  bNeighbour.style.position = 'relative'; // shadow bugfix
  bParent.insertBefore(b, bNeighbour);
  b.appendChild(bIcon);

  let fetchingDelay = false;

  b.addEventListener('click', () => {
    setTimeout(() => {
      if (b === document.activeElement) b.blur();
    }, 250);

    if (fetchingDelay) return;
    fetchingDelay = true;
    bIcon.className = 'tm-dsb-downloadBtn__icon tm-dsb-downloadBtn__icon--loading';

    fetch(installBtn.href)
      .then(res => res.blob())
      .then(blob => {
        const url = window.URL.createObjectURL(blob);
        const a = document.createElement('a');

        a.href = url;
        a.download = `${installBtn.dataset.scriptName || Date.now()}.user.js`;
        document.body.appendChild(a); // needed due to firefox bug
        a.click();
        a.remove();

        setTimeout(closeFetchUX, 300);
        window.URL.revokeObjectURL(url);
      })
      .catch(e => {
        setTimeout(closeFetchUX, 300);
        alert(`${langStrings('failedDl')}: \n${e}`);
     });
  });


  // utils -------------------------------------------------------------

  function closeFetchUX() {
    fetchingDelay = false;
    bIcon.className = 'tm-dsb-downloadBtn__icon tm-dsb-downloadBtn__icon--download';
  }

  // --------------------------------------------------------------------

})();