Lofter(乐乎)原图下载 修改版

批量下载Lofter图片,修改重写自 兰陵笑笑生的Lofter原图查看下载

// ==UserScript==
// @name         Lofter(乐乎)原图下载 修改版
// @namespace    LofterSpiderFix
// @license      原作者保留
// @version      1.1.0
// @author       Kaesinol
// @author       兰陵笑笑生
// @homepage     https://gist.github.com/kaixinol/3652d2484de12818e9b402808db30801
// @description  批量下载Lofter图片,修改重写自 兰陵笑笑生的Lofter原图查看下载
// @match        https://*.lofter.com/post/*
// @grant        GM_xmlhttpRequest
// @connect      https://*.126.net
// @connect      https://*.lf1*.net
// @require      https://update.greasyfork.org/scripts/473358/1237031/JSZip.js
// ==/UserScript==

(() => {
  "use strict";

  /** 注入样式 */
  const injectStyle = () => {
    if (!document.getElementById("spidercss")) {
      const style = document.createElement("style");
      style.id = "spidercss";
      style.textContent = `
        #spiderboprt {
          position: fixed;
          top: 7px;
          right: 15px;
          margin: 0 5px 0 0;
          z-index: 9999999999999;
        }
        #spiderboprt a,
        #spiderboprt em {
          height: 23px;
          line-height: 23px;
          float: left;
          background: url(//l.bst.126.net/rsc/img/control/operatenew24.png?005) no-repeat;
        }
        #spiderboprt a {
          padding: 0 2px 0 0;
          cursor: pointer;
          text-decoration: none;
          background-position: right 0;
        }
        #spiderboprt a:hover em,
        #spiderboprt em {
          color: #fff;
          padding: 0 5px 0 26px;
          white-space: nowrap;
          font-weight: 400;
          font-style: normal;
        }
        #spiderboprt em {
          background-position: 0 -750px;
          font-size: 12px;
        }
        #spiderboprt a:hover {
          background-position: right -870px;
        }
        #spiderboprt a:hover em {
          background-position: 0 -780px;
        }
      `;
      document.head.appendChild(style);
    }
  };

  /** 创建下载按钮 */
  const createButton = () => {
    if (!document.getElementById("spiderboprt")) {
      const scope = document.getElementById("control_frame");
      if (scope) {
        scope.style.right = "77px";
        const btn = document.createElement("div");
        btn.id = "spiderboprt";
        btn.innerHTML = `<a><em>下载</em></a>`;
        scope.parentNode.insertBefore(btn, scope.nextSibling);
      }
    }
  };

  /** 获取所有图片和视频 */
  const getAllMediaUrls = () => {
    const imgs = [...document.getElementsByClassName("imgclasstag")].map(img =>
      img.getAttribute("bigimgsrc")?.match(/(.*?)\.(jpg|png|jpeg|gif)/i)?.[0]
    ).filter(Boolean);

    const videos = [...document.getElementsByTagName("video")].map(v => v.src);

    return [...imgs, ...videos];
  };

  /** 获取 URL 后缀名 */
  const getFileExtension = (url) => {
    const cleanUrl = url.split("?")[0];
    return cleanUrl.match(/\.([a-zA-Z0-9]+)$/)?.[1] ?? "";
  };

  /** 保存文件 */
  const saveFile = (blob, filename) => {
    const url = URL.createObjectURL(blob);
    const link = document.createElement("a");
    link.href = url;
    link.download = filename;
    document.body.appendChild(link);
    link.click();
    link.remove();
    URL.revokeObjectURL(url);
  };

  /** 使用 GM_xmlhttpRequest 下载并返回 blob/arraybuffer */
  const fetchResource = (url, responseType = "blob") =>
    new Promise((resolve, reject) => {
      GM_xmlhttpRequest({
        method: "GET",
        url,
        responseType,
        headers: {
          "sec-fetch-site": "cross-site",
          referer: location.href,
        },
        onload: (xhr) => resolve(xhr.response),
        onerror: reject,
      });
    });

  /** 下载逻辑 */
  const downloadMedia = async (urls) => {
    if (!urls.length) return;

    const username = location.href.match(/https:\/\/(.+?)\.lofter\.com/)?.[1] ?? "user";
    const postId = location.href.match(/post\/(.+)/)?.[1] ?? "post";
    const safeTitle = document.title.replace(/[\\/:*?"<>|]/g, "_"); // 避免非法文件名

    if (urls.length > 2) {
      // 多图打包
      const zip = new JSZip();
      await Promise.all(urls.map(async (url, index) => {
        const fileExt = getFileExtension(url);
        const data = await fetchResource(url, "arraybuffer");
        zip.file(`${username} - ${safeTitle} - ${postId}_${index + 1}.${fileExt}`, data);
      }));

      const content = await zip.generateAsync({ type: "blob" });
      saveFile(content, `${username} - ${postId}.zip`);
    } else {
      // 单/双图直接保存
      for (const url of urls) {
        const fileExt = getFileExtension(url);
        const filename = `${username} - ${safeTitle} - ${postId}.${fileExt}`;
        const blob = await fetchResource(url, "blob");
        saveFile(blob, filename);
      }
    }
  };

  /** 按钮绑定事件 */
  const bindClickHandler = () => {
    const btn = document.getElementById("spiderboprt");
    if (btn) {
      btn.onclick = async () => {
        const urls = getAllMediaUrls();
        await downloadMedia(urls);
      };
    }
  };

  /** 初始化 */
  const init = () => {
    injectStyle();
    createButton();
    bindClickHandler();
  };

  document.readyState === "loading"
    ? document.addEventListener("DOMContentLoaded", init)
    : init();

})();