花瓣 - 添加下载按钮

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

目前為 2024-10-09 提交的版本,檢視 最新版本

您需要先安裝使用者腳本管理器擴展,如 TampermonkeyGreasemonkeyViolentmonkey 之後才能安裝該腳本。

You will need to install an extension such as Tampermonkey to install this script.

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyUserscripts 後才能安裝該腳本。

你需要先安裝一款使用者腳本管理器擴展,比如 Tampermonkey,才能安裝此腳本

您需要先安裝使用者腳本管理器擴充功能後才能安裝該腳本。

(我已經安裝了使用者腳本管理器,讓我安裝!)

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

(我已經安裝了使用者樣式管理器,讓我安裝!)

// ==UserScript==
// @name         花瓣 - 添加下载按钮
// @namespace    http://tampermonkey.net/
// @version      0.5.4
// @description  给花瓣的图加上“下载”按钮,方便下载
// @author       潘志城_Neo
// @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==

;(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: false, // 是否显示大图
        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))

        // 添加设置选项
        setSettingMenu()

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

    /**
   * 添加设置选项
   */
    function setSettingMenu() {
        var menuCommandSetting = GM_registerMenuCommand(
            "设置",
            function (e) {
                addMenu()
            },
            "S"
        )
        }

    // 插入菜单到页面
    function addMenu() {
        var domMenu = document.getElementById("neo_huaban_menu")
        if (domMenu !== null) {
            return
        }
        domMenu = document.createElement("div")
        domMenu.id = "neo_huaban_menu"
        domMenu.style =
            "z-index:2333; width:252px; min-height:120px; display:flex; flex-direction: column; position:fixed; top:50%; left: 50%; transform:translate(-50%,-50%); border-radius:8px; overflow:hidden; background:white;box-shadow: 2px 2px 6px 1px #5668577a;"
        var domHtml = `
      <div class="title" style="padding:7px; text-align:center; background-color:#1AB37D;color:white;cursor:default;">
        设置
      </div>
      <div class="content" style="display: flex; flex-direction: column; padding:15px; flex:1;">
        <div>
          <div style="display:flex; align-items:center;">
            <div style="display:flex; align-items:center;">
              <span style="display: inline-block;">重命名</span>
              <input type="checkbox" style="margin-left:5px;cursor:pointer;" class="rename" ${
                setting.rename ? "checked" : ""
        }>
            </div>
            <div style="display:flex; align-items:center;">
              <span style="margin-left: 13px; outline: none;" >前缀</span>
              <input style="margin-left:5px; outline:none; width:100px; height: 50%; border-radius: 4px; border: 1px solid #adadadd1;" class="prefix" value="${
                setting.prefix
        }">
            </div>
          </div>
          <div style="font-size: 0.8em; color: #7a7a7a; margin-left: 20px;">
            <div >格式:前缀-年月日-pid</div>
            <div>示例:HB-20230216-<a title="https://huaban.com/pins/5073719443=>最后那串数字就是pid" href="https://huaban.com/pins/5073719443" target="_blank">5073719443</a></div>
          </div>
        </div>
        <div style="height:1px; width:100%; background: #cdd3ce47; margin: 7px;"></div>
        <div>
          <div style="display:flex; align-items:center;">
            <span style="display: inline-block;">显示提示信息</span>
            <input type="checkbox" style="margin-left:5px;cursor:pointer;" class="show_notification" ${
              setting.show_notification ? "checked" : ""
        }>
          </div>
          <div style="font-size: 0.8em; color: #7a7a7a; margin-left: 20px;">
            <div>只在360极速浏览器有用。</div>
          </div>
        </div>
        <div style="height:1px; width:100%; background: #cdd3ce47; margin: 7px;"></div>
        <div style="display:flex; align-items:center;">
            <span style="display: inline-block;">显示大图按钮</span>
            <input type="checkbox" style="margin-left:5px;cursor:pointer;" class="show_source_img" ${
              setting.show_source_img ? "checked" : ""
        }>
        </div>
        <div style="height:1px; width:100%; background: #cdd3ce47; margin: 7px;"></div>
        <div style="display:flex; align-items:center;">
            <span style="display: inline-block;">显示图片标题</span>
            <input type="checkbox" style="margin-left:5px;cursor:pointer;" class="show_img_title" ${
              setting.show_img_title ? "checked" : ""
        }>
        </div>
        <div style="height:1px; width:100%; background: #cdd3ce47; margin: 7px;"></div>
        <div style="display:flex; align-items:center;">
            <span style="display: inline-block;margin-right:5px;">下载方式</span>
            <select class="download_type">
              <option value="gm_download" ${
                setting.download_type === "gm_download" ? "selected" : ""
        }>gm_download</option>
              <option value="fetch" ${setting.download_type} ${
                setting.download_type === "fetch" ? "selected" : ""
        } >fetch</option>
              <option value="xhr" ${
                setting.download_type === "xhr" ? "selected" : ""
        }>xhr</option>
               <option value="xmlhttpRequest" ${
                setting.download_type === "xmlhttpRequest" ? "selected" : ""
        }>xmlhttpRequest</option>
            </select>
        </div>
        <div style="color: #7a7a7a; text-align: center; margin-top: 8px;">
            <span>刷新页面后生效</span>
            <span class="reload_window" style="background-color: #1ab37d; color: white; border-radius: 5px; padding: 2px 6px 3px; cursor: pointer;">立即刷新</span>
        </div>
      </div>
      <div style="display: flex; justify-content: space-between; text-align: center; background-color: #ebebeb; color: #333; cursor: pointer; align-items: center;">
          <div class="reset" style="width:50%; text-align:center;">重置设置</div>
          <div class="close" style="width:50%; text-align:center; background-color:#d0d0d0;">
            关闭
          </div>
      </div>
      `
    domMenu.innerHTML = domHtml
        document.body.appendChild(domMenu)

        // 设置-添加事件
        document
            .querySelector("#neo_huaban_menu .content .rename")
            .addEventListener("change", function (e) {
            if (e.target.checked) {
                e.target.removeAttribute("checked")
                setting.rename = true
            } else {
                e.target.setAttribute("checked", true)
                setting.rename = false
            }
            GM_setValue("setting", setting)
        })
        // 关闭按钮
        document
            .querySelector("#neo_huaban_menu .close")
            .addEventListener("click", function (e) {
            removeMenu()
        })

        // 修改前缀
        var dom_prefix = document
        .querySelector("#neo_huaban_menu .prefix")
        .addEventListener("change", function (e) {
            setting.prefix = dom_prefix.value
            GM_setValue("setting", setting)
        })

        // 显示通知消息
        document
            .querySelector("#neo_huaban_menu .content .show_notification")
            .addEventListener("change", function (e) {
            if (e.target.checked) {
                e.target.removeAttribute("checked")
                setting.show_notification = true
            } else {
                e.target.setAttribute("checked", true)
                setting.show_notification = false
            }
            GM_setValue("setting", setting)
        })

        // 显示大图
        document
            .querySelector("#neo_huaban_menu .content .show_source_img")
            .addEventListener("change", function (e) {
            if (e.target.checked) {
                e.target.removeAttribute("checked")
                setting.show_source_img = true
            } else {
                e.target.setAttribute("checked", true)
                setting.show_source_img = false
            }
            GM_setValue("setting", setting)
        })

        // 显示图片标题
        document
            .querySelector("#neo_huaban_menu .content .show_img_title")
            .addEventListener("change", function (e) {
            if (e.target.checked) {
                e.target.removeAttribute("checked")
                setting.show_img_title = true
            } else {
                e.target.setAttribute("checked", true)
                setting.show_img_title = false
            }
            GM_setValue("setting", setting)
        })

        // 修改下载方式
        var dom_download_type = document.querySelector(
            "#neo_huaban_menu .download_type"
        )
        addEventListener("change", function (e) {
            setting.download_type = dom_download_type.value
            GM_setValue("setting", setting)
        })

        // 重置设置
        document
            .querySelector("#neo_huaban_menu .reset")
            .addEventListener("click", function (e) {
            setting = Object.assign({}, defaultSetting)
            GM_setValue("setting", setting)
        })
        // 立即刷新
        document
            .querySelector("#neo_huaban_menu .reload_window")
            .addEventListener("click", function (e) {
            location.reload();
        })
    }

    // 从页面中移除菜单
    function removeMenu() {
        var domMenu = document.getElementById("neo_huaban_menu")
        if (domMenu) {
            domMenu.remove()
        }
    }

    /**
   * 添加下载按钮(如果有按钮,就不添加)
   */
    function addDownloadBtn() {
        // if(document.URL.includes('discovery') || document.URL.includes('domains') || document.URL.includes('boards') || document.URL.includes('follow') || document.URL.includes('search')){
        //     addDownloadBtnToDiscovery()
        // }
        if (document.URL.includes("pins")) {
            addDonwloadBtnToPreivew()
        } else {
            if (!document.URL.includes("user")) {
                addDownloadBtnToDiscovery()
            }
        }
    }

    function addDownloadBtnToDiscovery() {
        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)
                }
            }
        })
    }
    function addDonwloadBtnToPreivew() {
        var newBtn = document.createElement("button")
        newBtn.innerText = "下载"
        newBtn.style.cssText = btnStyleText + "border-radius:12px;padding:9px 12px;"
        newBtn.className = "neo_add_btn"
        newBtn.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 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
                }
                btnDom.parentNode.appendChild(newBtn)
            }
            if (count >= maxCount) {
                clearInterval(interval)
            } else {
                count++
            }
        }, 1000)
        }

    /**
   * 下载图片
   * @param {Object} imgInfo src:图片链接; title:图片标题
   */
    function downloadImage(imgInfo) {
        //替换文件名中不能有的字符
        var sign_list = ["\\*", "\\'", '\\"', "<", ">", "\\?", "\\.", "\\|", "\\/"]
        for (var i = 0; i < sign_list.length; i++) {
            var reg = "/" + sign_list[i] + "/g"
            var title = imgInfo.title
            if (title) {
                imgInfo.title = imgInfo.title.replace(eval(reg), "_")
            } else {
                imgInfo.title = "无标题"
            }
        }

        imgInfo.src = imgInfo.src.replace(/_fw240.*/, "")
        imgInfo.src = imgInfo.src.replace(/_fw658.*/, "")
        var imgTitle = imgInfo.title
        if (setting.rename) {
            imgTitle =
                (setting.prefix ? setting.prefix + "-" : "") +
                formatDate(new Date()) +
                "-" +
                imgInfo.pin
        }
        show_notification({
            text: imgTitle,
            title: "图片已添加下载",
            timeout: 2000,
        })
        switch (setting.download_type) {
            case "gm_download":
                imageDownload_with_gm_download(imgInfo.src, imgTitle)
                break
            case "fetch":
                imageDownload_with_fetch(imgInfo.src, imgTitle)
                break
            case "xhr":
                imageDownload_with_Xhr_download(imgInfo.src,imgTitle)
                break
            case "xmlhttpRequest":
                imageDownload_with_xmlhttpRequest_download(imgInfo.src,imgTitle)
                break
            default:
                imageDownload_with_Xhr_download(imgInfo.src, imgTitle)
                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) {
        fetch(src)
            .then((response) => response.blob())
            .then((blob) => {
            // 2. 创建Blob对象

            // 3. 创建URL
            const url = URL.createObjectURL(blob)

            // 4. 创建下载链接
            const a = document.createElement("a")
            a.href = url
            a.download = title

            // 5. 模拟点击下载
            document.body.appendChild(a)
            a.click()

            // 清理创建的URL对象
            URL.revokeObjectURL(url)
            document.body.removeChild(a)
        })
            .catch((error) => {
            //下载出错,右下角弹窗通知。
            show_notification({
                text: title + "\n" + error,
                title: "下载出错",
                timeout: 5000,
            })
            console.error(error)
        })

    }

    /**
   * 用GM_download 下载图片
   */
    function imageDownload_with_gm_download(src, title) {
        //启用油猴的增强下载函数,可跨域
        GM_download({
            url: src,
            name: title,
            onprogress: function () {
                if (setting.show_notification) {
                    var isNotice = false
                    return function () {
                        if (!isNotice) {
                            show_notification({
                                text: title,
                                title: "图片已添加下载",
                                timeout: 2000,
                            })
                            isNotice = true
                        }
                    }
                }
            },
            onload: function () {
                //下载完成之后,右下角弹窗通知。
                show_notification({
                    text: title,
                    title: "图片已完成下载",
                    timeout: 5000,
                })
            },
            onerror: function () {
                //下载出错,右下角弹窗通知。
                show_notification({
                    text: title + "\n" + imgInfo.src,
                    title: "下载出错",
                    timeout: 5000,
                })
                console.error(error)
            },
        })
    }
    /**
    * 用原始的ajax方法下载
    */
    function imageDownload_with_Xhr_download(src,title) {
        var xhr = new XMLHttpRequest();
        xhr.open('GET', src, true);
        xhr.responseType = 'blob';

        xhr.onload = function() {
            if (xhr.status === 200) {
                // 创建一个Blob对象
                var blob = xhr.response;
                // 创建一个URL对象
                var url = window.URL.createObjectURL(blob);
                // 创建一个下载链接
                var a = document.createElement('a');
                a.href = url;
                a.download = title;
                // 将链接添加到页面并模拟点击进行下载
                document.body.appendChild(a);
                a.click();
                // 释放URL对象
                window.URL.revokeObjectURL(url);
                a.remove()
            }
        };
        // 错误处理
        xhr.onerror = function() {
            //下载出错,右下角弹窗通知。
            show_notification({
                text: title + "\n" + src,
                title: "下载出错",
                timeout: 5000,
            })
            console.error(error)
        };

        xhr.send();
    }

    /**
    * 用 GM_xmlhttpRequest 方式下载
    */
    function imageDownload_with_xmlhttpRequest_download(src,title){
        GM_xmlhttpRequest({
            method: 'GET',
            url: src,
            responseType: 'blob',
            onload: function(response) {
                const blob = new Blob([response.response], {type: response.response.type});
                const contentType = response.response.type;
                let extension = '';

                // 根据内容类型确定扩展名
                switch(contentType) {
                    case 'image/jpeg':
                        extension = '.jpg';
                        break;
                    case 'image/png':
                        extension = '.png';
                        break;
                    case 'image/gif':
                        extension = '.gif';
                        break;
                    default:
                        extension = '.jpg'; // 默认使用 .jpg
                }

                // 创建下载链接并添加扩展名
                const url = window.URL.createObjectURL(blob);
                const a = document.createElement('a');
                a.style.display = 'none';
                a.href = url;
                a.download = title + extension;
                document.body.appendChild(a);
                a.click();
                window.URL.revokeObjectURL(url);
            },
            onerror: function(error) {
                //下载出错,右下角弹窗通知。
                show_notification({
                    text: title + "\n" + imgInfo.src,
                    title: "下载出错",
                    timeout: 5000,
                })
                console.error(error)
            }
        });
    }
})()