花瓣下载素材 - 星星专属

给花瓣的图加上“下载”按钮,方便下载

// ==UserScript==
// @name         花瓣下载素材 - 星星专属
// @namespace    http://tampermonkey.net/
// @version      0.1.1
// @description  给花瓣的图加上“下载”按钮,方便下载
// @author       snow
// @license      MIT
// @match        *://huaban.com/*
// @match        *://hbimg.huabanimg.com/*
// @grant        GM_download
// @grant        GM_xmlhttpRequest
// @grant        GM_notification
// @grant        GM_registerMenuCommand
// @grant        GM_setValue
// @grant        GM_getValue
// @connect      gd-hbimg.huaban.com
// ==/UserScript==
/*
 *
 *
 * 本脚本参考了潘志城_Neo的花瓣 - 添加下载按钮 相关实现思路,在此基础上修改,在此表示感谢。
 * 特别感谢以下资源提供的灵感:
 * - Greasy Fork社区的相关用户脚本
 * - 各类花瓣网辅助工具的开源实现
 *
 * 本脚本为个人学习研究用途开发,仅用于提升用户体验,
 * 无意侵犯任何网站权益。如涉及侵权,请及时联系,
 * 我将立即删除相关代码。
 *
 * 本脚本完全免费,仅供个人使用,禁止用于任何商业用途。
 * 使用本脚本产生的一切后果由使用者自行承担。
 */
(function () {
  "use strict";

  // 所有图片
  var allImages = [];
  // 按钮样式
  var btnStyleText =
    "border:0; color:#ffffff ;background-color: rgb(26 179 125 / 75%);border-radius:8px;padding:3px 12px;cursor:pointer;pointer-events:all;";
  var interval = null;

  var defaultSetting = {
    prefix: "HB", // 前缀
    show_notification: true, // 是否显示通知消息
    rename: false, // 是否重命名
    show_source_img: true, // 是否显示大图
    show_img_title: false, // 是否显示图片标题
    download_type: "gm_download", // 下载方式
  };

  // 配置信息
  var setting = GM_getValue("setting");
  if (!setting) {
    setting = Object.assign({}, defaultSetting);
  } else {
    setting = Object.assign({}, defaultSetting, setting);
  }
  GM_setValue("setting", setting);
  // 主函数
  function main() {
    document.body.addEventListener("click", function (e) {
      // 点击img标签的时候才尝试添加下载按钮
      if ((e, e.target.tagName === "IMG")) {
        addDonwloadBtnToPreivew();
      }
    });
    // 网页滚动的时候,检测图片是否有添加下载按钮,没有就添加
    document.addEventListener("scroll", throttle(addDownloadBtn, 300));

    addDownloadBtn();
    interval = setInterval(() => {
      if (allImages.length === 0) {
        addDownloadBtn();
      } else {
        clearInterval(interval);
      }
    }, 1500);
  }
  main();

  /**
   * 添加下载按钮(如果有按钮,就不添加)
   */
  function addDownloadBtn() {
    if (document.URL.includes("pins")) {
      addDonwloadBtnToPreivew();
    } else {
      if (!document.URL.includes("user")) {
        addDownloadBtnToDiscovery();
        addDonwloadAllBtn();
      }
    }
  }


  // 修改后的下载全部功能
function addDonwloadAllBtn() {
    // 创建下载按钮
    var downloadBtn = document.createElement("button");
    downloadBtn.innerText = "下载全部";
    downloadBtn.style.cssText = btnStyleText + 
        "position: fixed;" +
        "right: 20px;" +
        "top: 30%;" +
        "border-radius: 12px;" +
        "padding: 9px 12px;" +
        "z-index: 1000;" +
        "cursor: pointer;";
    downloadBtn.className = "neo_add_btn";
    
    // 添加点击事件
    downloadBtn.addEventListener("click", function() {
        // 显示确认对话框
        if (confirm("确定要下载本页所有图片吗?这可能需要一些时间。")) {
            downloadAllImages();
        }
    });

    // 添加到页面
    document.body.appendChild(downloadBtn);
}

// 改进的下载所有图片函数
function downloadAllImages() {


        // 获取包含所有图片的外层容器
    const container = document.querySelector(
      ".infinite-scroll-component__outerdiv"
    );
    console.log(container,'////////////////////////////////////////////')

    if (!container) {
      console.error("未找到 infinite-scroll-component__outerdiv 元素");
      return;
    }

    // 获取所有img元素
    const images = container.querySelectorAll("img");
    if (images.length === 0) {
      console.log("未找到任何图片");
      return;
    }

    console.log(`找到 ${images.length} 张图片,开始下载...`);

    // 显示开始下载通知
    show_notification({
        text: `开始下载 ${images.length} 张图片`,
        title: "下载任务开始",
        timeout: 3000
    });

    // 遍历所有图片并下载
    images.forEach((img, index) => {
        // 延迟执行,避免浏览器同时发起过多请求
        setTimeout(() => {
            const pinInfo = img.parentNode.href.split("/");
            const imgInfo = {
                title: img.getAttribute("alt") || `图片_${index + 1}`,
                src: img.getAttribute("src").replace("_fw240webp", ""),
                pin: pinInfo[pinInfo.length - 1]
            };
            
            // 下载单张图片
            downloadImage(imgInfo);
            
            // 显示进度通知
            if ((index + 1) % 5 === 0 || index + 1 === images.length) {
                show_notification({
                    text: `已下载 ${index + 1}/${images.length} 张图片`,
                    title: "下载进度",
                    timeout: 2000
                });
            }
        }, index * 1000); // 每1秒下载一张,避免触发网站的防爬机制
    });
}


  // 单张图片下载函数
  function downloadImage(url, index) {
    console.log(`正在下载第 ${index} 张图片: ${url}`);

    // 创建一个隐藏的a标签用于下载
    const a = document.createElement("a");
    a.href = url;
    a.download = `image_${index}.jpg`; // 可以自定义文件名
    a.style.display = "none";

    document.body.appendChild(a);
    a.click();
    document.body.removeChild(a);

    console.log(`第 ${index} 张图片下载完成`);
  }

  // 执行下载
  function addDownloadBtnToDiscovery() {
    // Add loading indicator
    var loadingIndicator = document.createElement("div");
    loadingIndicator.innerText = "脚本加载中...";
    loadingIndicator.style.cssText = `
    position: fixed;
    top: 200px;
    left: 50%;
    transform: translateX(-50%);
    background: rgba(0, 0, 0, 0.7);
    color: white;
    padding: 15px 25px;
    border-radius: 8px;
    box-shadow: 0 4px 12px rgba(0,0,0,0.15);
    z-index: 9999;
    font-size: 16px;
    display: flex;
    align-items: center;
    justify-content: center;
`;
    document.body.appendChild(loadingIndicator);

    allImages = document.querySelectorAll(".transparent-img-bg.hb-image");
    allImages.forEach((dom) => {
      var pinInfo = dom.parentNode.href.split("/");
      // 图片标题和样式
      var imgInfo = {
        title: dom.getAttribute("alt"),
        src: dom.getAttribute("src"),
        pin: pinInfo[pinInfo.length - 1],
      };
      // 和包含图片的a标签同级的节点
      var tempList = dom.parentNode.parentNode.childNodes;
      // 图片dom
      var imgNode = tempList[tempList.length - 1];
      // 与图片父级a标签同级,并处于上方的元素
      var lookNode = tempList[tempList.length - 2];

      lookNode.setAttribute("hidden", true);
      lookNode.className = "";
      lookNode.style.cssText =
        "position: absolute;bottom: 8px; right: 8px; display: flex; flex-direction: row;align-items: center;z-index:1";
      // 添加鼠标悬停时的样式
      lookNode.parentNode.addEventListener("mouseover", function () {
        lookNode.removeAttribute("hidden");
      });

      // 移除鼠标悬停时的样式
      lookNode.parentNode.addEventListener("mouseout", function () {
        lookNode.setAttribute("hidden", true);
      });
      if (lookNode.querySelectorAll(".neo_add").length === 0) {
        var btnContainer = document.createElement("div");
        btnContainer.style = "display:flex;";

        if (setting.show_source_img) {
          // 添加打开大图按钮
          var sourceBtn = document.createElement("div");
          sourceBtn.className = "neo_add_source";
          sourceBtn.innerText = "大图";
          sourceBtn.addEventListener("click", () => {
            window.open(imgInfo.src.replace("_fw240webp", ""));
          });

          sourceBtn.style.cssText = btnStyleText + "margin-left:3px;";
          btnContainer.appendChild(sourceBtn);
        }
        // 添加下载图片按钮
        var downloadBtn = document.createElement("div");
        downloadBtn.className = "neo_add";
        downloadBtn.innerText = "下载";
        downloadBtn.addEventListener("click", () => {
          downloadImage(imgInfo);
        });

        downloadBtn.style.cssText = btnStyleText + "margin-left:3px;";
        btnContainer.appendChild(downloadBtn);
        lookNode.insertBefore(btnContainer, null);
        // 添加图片标题
        if (setting.show_img_title) {
          var domTitle = document.createElement("div");
          domTitle.innerText = imgInfo.title;
          domTitle.title = imgInfo.title;
          domTitle.style.cssText =
            "padding-left:5px;text-overflow: ellipsis;white-space: nowrap;overflow: hidden; color: rgba(30,32,35,.65);height:3em;";
          dom.parentNode.parentNode.parentNode.appendChild(domTitle);
        }
      }
    });
    // Remove loading indicator when done
    setTimeout(() => {
      loadingIndicator.remove();
    }, 1000);
  }

  function addDonwloadBtnToPreivew() {
    var newBtn = document.createElement("button");
    newBtn.innerText = "画板";
    newBtn.style.cssText =
      btnStyleText + "border-radius:12px;padding:9px 12px;margin-left:10px;";
    newBtn.className = "neo_add_btn";
    newBtn.addEventListener("click", function () {
      window.open("https://huaban.com/space", "_blank");
    });

    var downloadBtn = document.createElement("button");
    downloadBtn.innerText = "下载";
    downloadBtn.style.cssText =
      btnStyleText + "border-radius:12px;padding:9px 12px;margin-left:10px;";
    downloadBtn.className = "neo_add_btn";
    downloadBtn.addEventListener("click", function () {
      download();
    });

    function download() {
      var imgDom = document.querySelector("#pin_detail div img");
      var pinInfo = document.URL.split("/");
      var imgInfo = {};
      imgInfo.title = imgDom.alt;
      imgInfo.src = imgDom.src;
      imgInfo.pin = pinInfo[pinInfo.length - 1];
      downloadImage(imgInfo);
    }

    // 创建提示文字元素
    var tipElement = document.createElement("div");
    tipElement.innerHTML =
      "<span style='color:#f56c6c;font-size:12px;'>无水印素材可直接下载, ⚠️水印素材先点采集,再统一去画板里面下载</span>";
    tipElement.style.cssText =
      "position:absolute;right:0;top:-23px;background:linear-gradient(120deg, #a1c4fd 0%, #c2e9fb 100%);padding:10px 8px;border-radius:12px;";
    var count = 0;
    var maxCount = 8;
    var interval = setInterval(function () {
      var btnDom = document.querySelector("#pin_detail div button");
      if (btnDom) {
        clearInterval(interval);
        var neoAddDom = document.querySelector(
          "#pin_detail div button.neo_add_btn"
        );
        if (neoAddDom) return;

        // 确保pin_detail有relative定位
        var pinDetail = document.querySelector("#pin_detail");
        if (pinDetail) {
          pinDetail.style.position = "relative";
          // 添加按钮和提示
          btnDom.parentNode.appendChild(downloadBtn);
          btnDom.parentNode.appendChild(newBtn);
          pinDetail.appendChild(tipElement);
        }
      }
      if (count >= maxCount) {
        clearInterval(interval);
      } else {
        count++;
      }
    }, 1000);
  }

  /**
   * 下载图片
   * @param {Object} imgInfo src:图片链接; title:图片标题
   */

  function sanitizeFilename(name) {
    return name.replace(/[\\/:*?"<>|]/g, "_");
  }
  function downloadImage(imgInfo) {
    const extension = getExtensionFromUrl(imgInfo.src);
    let imgTitle = imgInfo.title ? sanitizeFilename(imgInfo.title) : "无标题";

    if (setting.rename) {
      imgTitle =
        (setting.prefix ? setting.prefix + "-" : "") +
        formatDate(new Date()) +
        "-" +
        imgInfo.pin;
    }

    imgInfo.src = imgInfo.src.replace(/(_fw\d+.*|_sq\d+.*)/, "");

    show_notification({
      text: imgTitle,
      title: "图片已添加下载",
      timeout: 2000,
    });

    switch (setting.download_type) {
      case "gm_download":
        imageDownload_with_gm_download(imgInfo.src, imgTitle, extension);
        break;
      case "fetch":
        imageDownload_with_fetch(imgInfo.src, imgTitle, extension);
        break;
      case "xhr":
        imageDownload_with_Xhr_download(imgInfo.src, imgTitle, extension);
        break;
      case "xmlhttpRequest":
        imageDownload_with_xmlhttpRequest_download(imgInfo.src, imgTitle);
        break;
      default:
        imageDownload_with_Xhr_download(imgInfo.src, imgTitle, extension);
        break;
    }
  }

  function show_notification(item) {
    if (setting.show_notification) {
      GM_notification(item);
    }
  }
  function throttle(cb, wait = 300) {
    var last = 0;
    return function () {
      var now = new Date().getTime();
      if (now - last > wait) {
        cb.call(this);
        last = new Date().getTime();
      }
    };
  }

  //格式化时间
  function formatDate(dat) {
    //获取年月日,时间
    var year = dat.getFullYear();
    var mon =
      dat.getMonth() + 1 < 10 ? "0" + (dat.getMonth() + 1) : dat.getMonth() + 1;
    var data = dat.getDate() < 10 ? "0" + dat.getDate() : dat.getDate();
    var newDate = year + mon + data;
    return newDate;
  }

  /**
   * 用fecth下载图片
   */
  function imageDownload_with_fetch(src, title) {
    const extension = getExtensionFromUrl(src);
    fetch(src)
      .then((response) => response.blob())
      .then((blob) => {
        const url = URL.createObjectURL(blob);
        const a = document.createElement("a");
        a.href = url;
        a.download = title + extension;
        document.body.appendChild(a);
        a.click();
        URL.revokeObjectURL(url);
        document.body.removeChild(a);
      })
      .catch((error) => {
        show_notification({
          text: title + "\n" + error,
          title: "下载出错",
          timeout: 5000,
        });
        console.error(error);
      });
  }
  function getExtensionFromUrl(url) {
    const match = url.match(/\.(jpeg|jpg|png|gif|webp|bmp)/);
    return match ? "." + match[1] : ".png"; // 默认使用.jpg
  }
  /**
   * 用GM_download 下载图片
   */
  function imageDownload_with_gm_download(src, title) {
    const extension = getExtensionFromUrl(src);
    GM_download({
      url: src,
      name: title + extension,
      onload: function () {
        show_notification({
          text: title,
          title: "图片已完成下载",
          timeout: 5000,
        });
      },
      onerror: function (error) {
        show_notification({
          text: title + "\n" + src,
          title: "下载出错",
          timeout: 5000,
        });
        console.error(error);
      },
    });
  }
})();