Greasy Fork镜像 还支持 简体中文。

ImageDownloaderLib.la2

window.close()

目前為 2025-07-31 提交的版本,檢視 最新版本

此腳本不應該直接安裝,它是一個供其他腳本使用的函式庫。欲使用本函式庫,請在腳本 metadata 寫上: // @require https://update.gf.qytechs.cn/scripts/539610/1632994/ImageDownloaderLibla2.js

/*
 * Dependencies:
 *
 * GM_info(optional)
 * Docs: https://violentmonkey.github.io/api/gm/#gm_info
 * 
 * GM_xmlhttpRequest(optional)
 * Docs: https://violentmonkey.github.io/api/gm/#gm_xmlhttprequest
 *
 * JSZIP
 * Github: https://github.com/Stuk/jszip
 * CDN: https://unpkg.com/[email protected]/dist/jszip.min.js
 *
 * FileSaver
 * Github: https://github.com/eligrey/FileSaver.js
 * CDN: https://unpkg.com/[email protected]/dist/FileSaver.min.js
 */

;const ImageDownloader = (({ JSZip, saveAs }) => {
  let maxNum = 0;
  let promiseCount = 0;
  let fulfillCount = 0;
  let isErrorOccurred = false;
  let worker = null;

  // elements
  let startNumInputElement = null;
  let endNumInputElement = null;
  let downloadButtonElement = null;
  let panelElement = null;

  // svg icons
  const externalLinkSVG = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="currentcolor" width="16" height="16"><path fill-rule="evenodd" d="M10.604 1h4.146a.25.25 0 01.25.25v4.146a.25.25 0 01-.427.177L13.03 4.03 9.28 7.78a.75.75 0 01-1.06-1.06l3.75-3.75-1.543-1.543A.25.25 0 0110.604 1zM3.75 2A1.75 1.75 0 002 3.75v8.5c0 .966.784 1.75 1.75 1.75h8.5A1.75 1.75 0 0014 12.25v-3.5a.75.75 0 00-1.5 0v3.5a.25.25 0 01-.25.25h-8.5a.25.25 0 01-.25-.25v-8.5a.25.25 0 01.25-.25h3.5a.75.75 0 000-1.5h-3.5z"></path></svg>`;
  const reloadSVG = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="currentcolor" width="16" height="16"><path fill-rule="evenodd" d="M8 2.5a5.487 5.487 0 00-4.131 1.869l1.204 1.204A.25.25 0 014.896 6H1.25A.25.25 0 011 5.75V2.104a.25.25 0 01.427-.177l1.38 1.38A7.001 7.001 0 0114.95 7.16a.75.75 0 11-1.49.178A5.501 5.501 0 008 2.5zM1.705 8.005a.75.75 0 01.834.656 5.501 5.501 0 009.592 2.97l-1.204-1.204a.25.25 0 01.177-.427h3.646a.25.25 0 01.25.25v3.646a.25.25 0 01-.427.177l-1.38-1.38A7.001 7.001 0 011.05 8.84a.75.75 0 01.656-.834z"></path></svg>`;

  // initialization
  function init({
    maxImageAmount,
    getImagePromises,
    title = `package_${Date.now()}`,
    imageSuffix = 'jpg',
    zipOptions = {},
    positionOptions = {}
  }) {
    // assign value
    maxNum = maxImageAmount;

    // setup UI
    setupUI(positionOptions);

    // setup update notification
    setupUpdateNotification();

    // add click event listener to download button
    downloadButtonElement.onclick = function () {
      if (!isOKToDownload()) return;

      this.disabled = true;
      this.textContent = "Processing";
      this.style.backgroundColor = '#aaa';
      this.style.cursor = 'not-allowed';
      
      // Create Web Worker for background processing
      const workerCode = `
        self.onmessage = async function(e) {
          const { startNum, endNum, getImagePromises, title, imageSuffix, zipOptions } = e.data;
          const JSZip = (${JSZip.toString()})();
          const images = [];
          
          // Download images in chunks
          for (let num = startNum; num <= endNum; num += 4) {
            const from = num;
            const to = Math.min(num + 3, endNum);
            try {
              const result = await Promise.all(getImagePromises(from, to));
              images.push(...result);
              postMessage({ type: 'progress', current: num, total: endNum });
            } catch (error) {
              postMessage({ type: 'error', error });
              return;
            }
          }
          
          // Create zip file
          JSZip.defaults.date = new Date(Date.now() - (new Date()).getTimezoneOffset() * 60000);
          const zip = new JSZip();
		  let newtitle = title.replace(/(\?|\~|\/|\:)/gi,  function ($0, $1) {
		      return {
		      '?':'?',
		      '~':'~',
		      '/':'/',
		      ':':':',
		      }[$1];
		      });
          const zipTitle = newtitle.replaceAll(/\\|\*|\"|\<|\>|\|/g, '_');
          const folder = zip.folder(zipTitle);
          
          images.forEach((image, index) => {
            const filename = \`\${String(index + 1).padStart(4, '0')}.\${imageSuffix}\`;
            folder.file(filename, image, zipOptions);
          });
          
          // Generate zip and send back to main thread
          const content = await zip.generateAsync({ type: "blob" }, (metadata) => {
            postMessage({ type: 'zipping', percent: metadata.percent });
          });
          
          postMessage({ type: 'complete', content, zipTitle });
        };
      `;
      
      const blob = new Blob([workerCode], { type: 'application/javascript' });
      worker = new Worker(URL.createObjectURL(blob));
      
      worker.onmessage = function(e) {
        const data = e.data;
        switch(data.type) {
          case 'progress':
            const progress = Math.round((data.current / data.total) * 100);
            downloadButtonElement.innerHTML = 'Downloading<br>(' + progress + '%)';
            break;
          case 'zipping':
            downloadButtonElement.innerHTML = 'Zipping<br>(' + data.percent.toFixed() + '%)';
            break;
          case 'error':
            console.error(data.error);
            downloadButtonElement.textContent = 'Error Occurred';
            downloadButtonElement.style.backgroundColor = 'red';
            break;
          case 'complete':
            saveAs(data.content, data.zipTitle + '.zip');
            downloadButtonElement.textContent = "Completed";
            setTimeout(function() { window.close(); }, 3000);
            worker.terminate();
            break;
        }
      };
      
      worker.postMessage({
        startNum: Number(startNumInputElement.value),
        endNum: Number(endNumInputElement.value),
        getImagePromises: getImagePromises,
        title: title,
        imageSuffix: imageSuffix,
        zipOptions: zipOptions
      });
    }
  }

  // setup UI
  function setupUI(positionOptions) {
    // common input element style
    const inputElementStyle = `
      box-sizing: content-box;
      padding: 1px 2px;
      width: 40%;
      height: 26px;

      border: 1px solid #aaa;
      border-radius: 4px;

      font-family: 'Consolas', 'Monaco', 'Microsoft YaHei';
      text-align: center;
    `;

    // create start number input element
    startNumInputElement = document.createElement('input');
    startNumInputElement.id = 'ImageDownloader-StartNumInput';
    startNumInputElement.style = inputElementStyle;
    startNumInputElement.type = 'text';
    startNumInputElement.value = 1;

    // create end number input element
    endNumInputElement = document.createElement('input');
    endNumInputElement.id = 'ImageDownloader-EndNumInput';
    endNumInputElement.style = inputElementStyle;
    endNumInputElement.type = 'text';
    endNumInputElement.value = maxNum;

    // prevent keyboard input from being blocked
    startNumInputElement.onkeydown = function(e) { e.stopPropagation(); };
    endNumInputElement.onkeydown = function(e) { e.stopPropagation(); };

    // create 'to' span element
    const toSpanElement = document.createElement('span');
    toSpanElement.id = 'ImageDownloader-ToSpan';
    toSpanElement.textContent = 'to';
    toSpanElement.style = `
      margin: 0 6px;
      color: black;
      line-height: 1;
      word-break: keep-all;
      user-select: none;
    `;

    // create download button element
    downloadButtonElement = document.createElement('button');
    downloadButtonElement.id = 'ImageDownloader-DownloadButton';
    downloadButtonElement.textContent = 'Download';
    downloadButtonElement.style = `
      margin-top: 8px;
      width: 128px;
      height: 48px;

      display: flex;
      justify-content: center;
      align-items: center;

      font-size: 14px;
      font-family: 'Consolas', 'Monaco', 'Microsoft YaHei';
      color: #fff;
      line-height: 1.2;

      background-color: #0984e3;
      border: none;
      border-radius: 4px;
      cursor: pointer;
    `;

    // create range input container element
    const rangeInputContainerElement = document.createElement('div');
    rangeInputContainerElement.id = 'ImageDownloader-RangeInputContainer';
    rangeInputContainerElement.style = `
      display: flex;
      justify-content: center;
      align-items: baseline;
    `;

    // create panel element
    panelElement = document.createElement('div');
    panelElement.id = 'ImageDownloader-Panel';
    panelElement.style = `
      position: fixed;
      top: 72px;
      left: 72px;
      z-index: 999999999;

      box-sizing: border-box;
      padding: 8px;
      width: 146px;
      height: 106px;

      display: flex;
      flex-direction: column;
      justify-content: center;
      align-items: baseline;

      font-size: 14px;
      font-family: 'Consolas', 'Monaco', 'Microsoft YaHei';
      letter-spacing: normal;

      background-color: #f1f1f1;
      border: 1px solid #aaa;
      border-radius: 4px;
    `;

    // modify panel position according to 'positionOptions'
    for (const [key, value] of Object.entries(positionOptions)) {
      if (key === 'top' || key === 'bottom' || key === 'left' || key === 'right') {
        panelElement.style[key] = value;
      }
    }

    // assemble and then insert into document
    rangeInputContainerElement.appendChild(startNumInputElement);
    rangeInputContainerElement.appendChild(toSpanElement);
    rangeInputContainerElement.appendChild(endNumInputElement);
    panelElement.appendChild(rangeInputContainerElement);
    panelElement.appendChild(downloadButtonElement);
    document.body.appendChild(panelElement);
  }

  // setup update notification
  async function setupUpdateNotification() {
    if (typeof GM_info === 'undefined' || typeof GM_xmlhttpRequest === 'undefined') return;

    // get local version
    const localVersion = Number(GM_info.script.version);

    // get latest version
    const scriptID = (GM_info.script.homepageURL || GM_info.script.homepage).match(/scripts\/(\d+)-/)?.groups?.id;
    const scriptURL = 'https://update.gf.qytechs.cn/scripts/' + scriptID + '/raw.js';
    const latestVersionString = await new Promise(function(resolve) {
      GM_xmlhttpRequest({
        method: 'GET',
        url: scriptURL,
        responseType: 'text',
        onload: function(res) { 
          const match = res.response.match(/@version\s+([0-9\.]+)/);
          resolve(match ? match[1] : null);
        }
      });
    });
    const latestVersion = Number(latestVersionString);

    if (Number.isNaN(localVersion) || Number.isNaN(latestVersion)) return;
    if (latestVersion <= localVersion) return;

    // show update notification
    const updateLinkElement = document.createElement('a');
    updateLinkElement.id = 'ImageDownloader-UpdateLink';
    updateLinkElement.href = scriptURL.replace('raw.js', 'raw.user.js');
    updateLinkElement.innerHTML = 'Update to V' + latestVersionString + externalLinkSVG;
    updateLinkElement.style = `
      position: absolute;
      bottom: -38px;
      left: -1px;

      display: flex;
      justify-content: space-around;
      align-items: center;

      box-sizing: border-box;
      padding: 8px;
      width: 146px;
      height: 32px;

      font-size: 14px;
      font-family: 'Consolas', 'Monaco', 'Microsoft YaHei';
      text-decoration: none;
      color: white;

      background-color: #32CD32;
      border-radius: 4px;
    `;
    updateLinkElement.onclick = function() {
      setTimeout(function() {
        updateLinkElement.removeAttribute('href');
        updateLinkElement.innerHTML = 'Please Reload' + reloadSVG;
        updateLinkElement.style.cursor = 'default';
      }, 1000);
    };

    panelElement.appendChild(updateLinkElement);
  }

  // check validity of page nums from input
  function isOKToDownload() {
    const startNum = Number(startNumInputElement.value);
    const endNum = Number(endNumInputElement.value);

    if (Number.isNaN(startNum) || Number.isNaN(endNum)) { 
      alert("请正确输入数值\nPlease enter page number correctly."); 
      return false; 
    }
    if (!Number.isInteger(startNum) || !Number.isInteger(endNum)) { 
      alert("请正确输入数值\nPlease enter page number correctly."); 
      return false; 
    }
    if (startNum < 1 || endNum < 1) { 
      alert("页码的值不能小于1\nPage number should not smaller than 1."); 
      return false; 
    }
    if (startNum > maxNum || endNum > maxNum) { 
      alert("页码的值不能大于" + maxNum + "\nPage number should not bigger than " + maxNum + "."); 
      return false; 
    }
    if (startNum > endNum) { 
      alert("起始页码的值不能大于终止页码的值\nNumber of start should not bigger than number of end."); 
      return false; 
    }

    return true;
  }

  // handle promise fulfilled
  function fulfillHandler(res) {
    if (!isErrorOccurred) {
      fulfillCount++;
      downloadButtonElement.innerHTML = 'Processing<br>(' + fulfillCount + '/' + promiseCount + ')';
    }

    return res;
  }

  // handle promise rejected
  function rejectHandler(err) {
    isErrorOccurred = true;
    console.error(err);

    downloadButtonElement.textContent = 'Error Occurred';
    downloadButtonElement.style.backgroundColor = 'red';

    return Promise.reject(err);
  }

  return { init, fulfillHandler, rejectHandler };
})(window);

QingJ © 2025

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