// ==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       2.0.0
// @namespace     https://greasyfork.org/users/424058
// @icon          https://greasyfork.org/vite/assets/blacklogo96-e0c2c761.png
// @match         https://greasyfork.org/*/scripts/*
// @match         https://sleazyfork.org/*/scripts/*
// @compatible    Chrome
// @compatible    Opera
// @compatible    Firefox
// @run-at        document-end
// @grant         GM_addStyle
// @noframes
// ==/UserScript==
/* jshint esversion: 8 */
(function() {
  'use strict';
  const i18n = {
    download: 'download',
    downloadWithoutInstalling: 'downloadWithoutInstalling',
    failedToDownload: 'failedToDownload',
  };
  const translate = (function() {
    const userLang = location.pathname.split('/')[1];
    const strings = {
      'en': {
        [i18n.download]: 'Download ⇩',
        [i18n.downloadWithoutInstalling]: 'Download without installing',
        [i18n.failedToDownload]:
          'Failed to download the script. There is might be more info in the browser console'
      },
      'ru': {
        [i18n.download]: 'Скачать ⇩',
        [i18n.downloadWithoutInstalling]: 'Скачать не устанавливая',
        [i18n.failedToDownload]:
          'Не удалось скачать скрипт. Больше информации может быть в консоли браузера'
      },
      'zh-CN': {
        [i18n.download]: '下载 ⇩',
        [i18n.downloadWithoutInstalling]: '下载此脚本',
        [i18n.failedToDownload]: '无法下载此脚本'
      },
    };
    return id => (strings[userLang] || strings.en)[id] || strings.en[id];
  }());
  const installBtn = document.querySelector('a.install-link');
  const installArea = document.querySelector('div#install-area');
  const installHelpLink = document.querySelector('a.install-help-link');
  const suggestions = document.querySelector('div#script-feedback-suggestion');
  const libraryRequire = document.querySelector('div#script-content > p > code');
  // if a script is detected
  if (installBtn && installArea && installHelpLink) {
    mountScriptDownloadButton(installBtn, installArea, installHelpLink);
  } else if (suggestions && libraryRequire) { // or maybe a library
    mountLibraryDownloadButton(suggestions, libraryRequire);
  }
  function mountScriptDownloadButton(
    installBtn,
    installArea,
    installHelpLink,
  ) {
    if (!installBtn.href) throw new Error('script href is not found');
    // https://img.icons8.com/pastel-glyph/64/ffffff/download.png
    // array to fold the string in a code editor
    const downloadIconBase64 = [
      'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEAAAABACAYAAACqaX',
      'HeAAAABmJLR0QA/wD/AP+gvaeTAAABgUlEQVR4nO3ZTU6DUAAE4HnEk+jWG3TrHV',
      'wY3XoEt23cGleamtRtTbyPS3sCV0bXjptHRAIEsM/hZ76kCZRHGaZAGwDMzMzMbJ',
      '6CasMkMwBncXYbQvhSZZEgecEf56ocmWrDAA4L00eqEMoCBsEFqAOouQB1ADUXoA',
      '6g5gLUAdRcgDqAmgtQB1BzAeoAakkLIHlN8pPkDcnWd59IBpK3cd1VyoxJkfwo3P',
      'V5KJZAcllYtiy8H+LY3HvKjKlPgU1h+hLAuulIiMvWcWzVZ4xL/Dbv+Nsjyax8BM',
      'Sx96Wxm3jzdLwaSliVCpjezucqzmuSfKuZJkvXi0moORKqTOebL2tRwnR3PtdQwv',
      'R3PldRgmznlc8GA4DTOPscQqAqy6x1+X8+6Ke5yfNxIE9z6/TN1+XCM4inuQ165Z',
      'vHz04DF6AOoOYC1AHUXIA6gNpBz/UWJK/2muTvFn1W6lvASXyNXpdTYJcsxf69th',
      '3Y5QjYAiCA485x/tcLgCd1CDMzMzMbum8+xtkWw6QCvwAAAABJRU5ErkJggg==',
    ].join('');
    GM_addStyle([`
      .GF-DSB__script-download-button {
        position: relative;
        padding: 0.5em 1.38em;
        cursor: pointer;
        border: none;
        background: #0F750F;
        transition: box-shadow 0.2s;
      }
      .GF-DSB__script-download-button:hover,
      .GF-DSB__script-download-button:focus {
        box-shadow: 0 8px 16px 0 rgb(0 0 0 / 20%), 0 6px 20px 0 rgb(0 0 0 / 19%);
      }
      .GF-DSB__script-download-icon {
        position: absolute;
      }
      .GF-DSB__script-download-icon--download {
        width: 30px;
        height: 30px;
        top: 4px;
        left: 7px;
      }
      .GF-DSB__script-download-icon--loading,
      .GF-DSB__script-download-icon--loading:after {
        border-radius: 50%;
        width: 16px;
        height: 16px;
      }
      .GF-DSB__script-download-icon--loading {
        top: 8px;
        left: 11px;
        border-top: 3px solid rgba(255, 255, 255, 0.2);
        border-right: 3px solid rgba(255, 255, 255, 0.2);
        border-bottom: 3px solid rgba(255, 255, 255, 0.2);
        border-left: 3px solid #ffffff;
        transform: translateZ(0);
        object-position: -99999px;
        animation: GF-DSB__script-download-loading-icon 1.1s infinite linear;
      }
      @keyframes GF-DSB__script-download-loading-icon {
        0% {
          transform: rotate(0deg);
        }
        100% {
          transform: rotate(360deg);
        }
      }
    `][0]);
    const b = document.createElement('a');
    const bIcon = document.createElement('img');
    b.href = '#';
    b.title = translate(i18n.downloadWithoutInstalling);
    b.draggable = false;
    b.className = 'GF-DSB__script-download-button';
    bIcon.src = downloadIconBase64;
    bIcon.draggable = false;
    bIcon.className =
      'GF-DSB__script-download-icon GF-DSB__script-download-icon--download';
    installHelpLink.style.position = 'relative'; // shadows bugfix
    b.appendChild(bIcon);
    installArea.insertBefore(b, installHelpLink);
    b.addEventListener('click', clicksHandler);
    b.addEventListener('auxclick', e => e.button === 1 && clicksHandler(e));
    // against doubleclicks
    let isFetchingAllowed = true;
    async function clicksHandler(ev) {
      ev.preventDefault();
      setTimeout(() => b === document.activeElement && b.blur(), 250);
      if (isFetchingAllowed === false) return;
      isFetchingAllowed = false;
      bIcon.className =
        'GF-DSB__script-download-icon GF-DSB__script-download-icon--loading';
      try {
        await downloadScript({
          href: installBtn.href,
          name: installBtn.dataset.scriptName,
        });
      } catch (e) {
        console.error(e);
        alert(`${translate(i18n.failedToDownload)}: \n${e}`);
      } finally {
        setTimeout(() => {
          isFetchingAllowed = true;
          bIcon.className =
            'GF-DSB__script-download-icon GF-DSB__script-download-icon--download';
        }, 300);
      }
    }
  }
  function mountLibraryDownloadButton(suggestions, libraryRequire) {
    const [
      libraryHref,
      libraryName,
    ] = libraryRequire.innerText.match(
      /\/\/ @require (https:\/\/.+\/scripts\/\d+.+\/code\/(.*)\.js\?version=\d+)/
    ).slice(1);
    // this probably is completely useless but whatever
    if (!libraryHref) throw new Error('library href is not found');
    GM_addStyle([`
      .GF-DSB__library-download-button {
        transition: box-shadow 0.2s;
      }
      .GF-DSB__library-download-button--loading {
        animation: GF-DSB__loading-text 1s infinite linear;
      }
      @keyframes GF-DSB__loading-text {
        50% {
          opacity: 0.4;
        }
      }
    `][0]);
    const b = document.createElement('a');
    b.href = '#';
    b.draggable = false;
    b.innerText = translate(i18n.download);
    b.className = 'GF-DSB__library-download-button';
    suggestions.appendChild(b);
    b.addEventListener('click', clicksHandler);
    b.addEventListener('auxclick', e => e.button === 1 && clicksHandler(e));
    // against doubleclicks
    let isFetchingAllowed = true;
    async function clicksHandler(ev) {
      ev.preventDefault();
      setTimeout(() => b === document.activeElement && b.blur(), 250);
      if (isFetchingAllowed === false) return;
      isFetchingAllowed = false;
      b.className =
        'GF-DSB__library-download-button GF-DSB__library-download-button--loading';
      try {
        await downloadScript({
          href: libraryHref,
          name: libraryName,
          isLibrary: true,
        });
      } catch (e) {
        console.error(e);
        alert(`${translate(i18n.failedToDownload)}: \n${e}`);
      } finally {
        setTimeout(() => {
          isFetchingAllowed = true;
          b.className = 'GF-DSB__library-download-button';
        }, 300);
      }
    }
  }
  // utils --------------------------------------------------------------------
  async function downloadScript({
    href,
    name,
    isLibrary = false,
  } = {}) {
    if (!href) throw new Error('href is missing');
    const blob = await (await fetch(href)).blob();
    const url = window.URL.createObjectURL(blob);
    const a = document.createElement('a');
    a.href = url;
    a.download = `${name || Date.now()}${!isLibrary ? '.user' : ''}.js`;
    document.body.appendChild(a); // is needed due to firefox bug
    a.click();
    a.remove();
    window.URL.revokeObjectURL(url);
  }
}());