怠惰聚圖&下載

自定義規則,透過CSS/XPath選擇器,定位圈選出要下載的DOM image對象,進行下載壓縮打包,也能聚集所有圖片到當前頁面裡。

As of 2023-04-08. See the latest version.

// ==UserScript==
// @name               怠惰聚圖&下載
// @name:zh-CN         怠惰聚图&下载
// @name:zh-TW         怠惰聚圖&下載
// @version            0.2
// @description        自定義規則,透過CSS/XPath選擇器,定位圈選出要下載的DOM image對象,進行下載壓縮打包,也能聚集所有圖片到當前頁面裡。
// @description:zh-CN  自定义规则,透过CSS/XPath选择器,定位圈选出要下载的DOM image对象,进行下载压缩打包,也能聚集所有图片到当前页面里。
// @description:zh-TW  自定義規則,透過CSS/XPath選擇器,定位圈選出要下載的DOM image對象,進行下載壓縮打包,也能聚集所有圖片到當前頁面裡。
// @author             tony0809
// @match              *://*/*
// @exclude            *hcaptcha*
// @exclude            *iframe*
// @exclude            *addthis*
// @exclude            *youtube*
// @exclude            *google*
// @icon               data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAMAAABEpIrGAAAAtFBMVEVEREAAAABEREBEREAuuaIzqpkvr58yiHgzhnY6kYM7mYI4kog4s6A2tpo0r544ooo2nY40sJw0sp00rZg1sJw0sZ0zsp01sZw0sZw0rZk1sJw1sZw1rZk0sp00rpk1sp01sZs1rpk1rpk0sp01spw1sJ00rZo1rZk1rpo0r5k0sJw0sZ01r5o0sZw0rpk0sp01sZ01sJw1spw1r5k1sp01sZw0sZ01sZw1rpk1sp01sZw1r5pUzpTcAAAAOXRSTlMAAAUGCw8QERIUFxgbHB0hIieqq6yvtLa3vb/AwMHCxMXFxsfIyc3Oz+zt7vDx8fL4+Pn5+vr7/v7AEFI4AAABR0lEQVR42qWT11bDMAxApbhsmm5KKVD2KiUQwpD1///FkZcSmh4e8FOsex3LsgymPy6ISkRyA4FlWMRXomIyQOg/SbyKAmSOE2Ip02UOYxf/DILNjOOEWLnAEAqio0MMAzJjTAZh1p0SrYCIuu0cMZc9JbENXLa1NWGdI1nWeQuXGPzBTdzC8ws52UI5MwchrA/VTOuTEP9fFyTG7E+R9q8JLsbW1UHzU8HHrCuU1fyToDlB43xRqMUgfc+/KA77fZrWSH/47zPlzOcpJxG8u32r/OEg5SRCqO/WTeT3+5oTuP4LxrXnd5F7waZ+wM5c+NVeujMRrDYMYueE+VK5E5r3uzOb7TbvHH7f/1pP/L8nU9u38Nwyy8OZdjfwY+ZnmPinp28y3mglNeERDJYy/9A3GYVS+GMPMB+uyL67/qO68Mb8MurBD7foVTtvIbtnAAAAAElFTkSuQmCC
// @license            MIT
// @namespace          https://greasyfork.org/users/20361
// @grant              GM_xmlhttpRequest
// @grant              GM.xmlHttpRequest
// @grant              unsafeWindow
// @require            https://cdn.jsdelivr.net/npm/[email protected]/dist/jszip.min.js
// ==/UserScript==

(async () => {
    "use strict";
    const options = { //true 開啟,false 關閉
        enable: 0, //0為白名單模式根據自訂義規則啟用,1為全局啟用
        one: false, //單線程下載
        default: "img[src]" //預設CSS/Xpath選擇器/javascript代碼
        //default: "js;return [...document.images];"
    };
    const siteUrl = location.href;
    let siteData = {};
    let globalImgArray = [];
    let customTitle = null;
    //自定義站點規則
    const customData = [{
        name: "小黃書/8色人體攝影 xchina.co/8se.me",//按數字鍵1、Enter插入圖片,按0、Enter、Enter壓縮打包下載
        reg: /(xchina|8se)\.(co|me)\/photo\/id/, //網址正則匹配
        imgs: "js;let numP=fun.geT('div[target][title]').match(/\\d+/)[0];let max=Math.ceil(numP/18);return fun.getImg('img.cr_only',max,2,['_600x0','']);",
        insertImg: "//div[div[@class='photos']]", //清空元素內容把所有圖片插入到此元素
        customTitle: "let s=document.title.split('-');let title='';if(/未分/.test(s[1])){title+=s[0].trim()}else{title+=s[1].trim()+' - ';title+=s[0].trim()}return title;",
        css: "body{overflow:unset!important}.photos>div.item,.jquery-modal.blocker.current,.slider-ad,.article.ad,.pager>.tips,body>footer~*:not([id^='pv-']):not([class^='pv-']):not(.pagetual_tipsWords):not(.customPicDownloadMsg):not(#customPicDownload),.photoMask,.banner_ad{display: none!important;}",
        category: "nsfw2"
    }, {
        name: "Women Legs Gallery - www.nlegs.com", //專用下載腳本https://greasyfork.org/scripts/463123
        enable: 0, //0該站禁用
        reg: /www\.nlegs\.com\/girls\//,
        imgs: "img[src^=blob]",
        category: "nsfw1"
    }, {
        name: "Hit-x-Hot www.hitxhot.org",
        reg: /(www\.)?hitxhot\.(com|org)\/gallerys\/.+\.html/,
        imgs: "js;let max=fun.geT('h1').match(/\\d+$/)[0];return fun.getImg('.contentme img',max);",
        insertImg: ".contentme",
        customTitle: "return document.title.split('|')[0].slice(10).trim()",
        category: "nsfw2"
    }, {
        name: "秀人集 www.xiuren5.com",
        reg: /www\.xiuren\d+\.com\/\w+\/\d+\.html/i,
        imgs: "js;let max=fun.geT('.page a:last-child',2);return fun.getImg('.content>p img[alt]',max,3);",
        insertImg: "//div[p[img[@alt and @title]]]",
        customTitle: "return fun.geT('.item_title>h1');",
        category: "nsfw1"
    }, {
        name: "HotAsiaGirl hotgirl.asia 分頁模式",
        reg: /hotgirl\.asia\/.+\//,
        include: ".galeria_img",
        imgs: "js;if(fun.ge('.CustomPictureDownloadImage')){return [...fun.gae('.CustomPictureDownloadImage')]}else{return fun.getImgP('.galeria_img>img','.pagination a[href]')}",
        insertImg: ".main-content",
        customTitle: "return fun.geT('h3');",
        category: "nsfw2"
    }, {
        name: "HotAsiaGirl hotgirl.asia 幻燈片模式",
        reg: /hotgirl\.asia\/.+\//,
        include: "#carouselImageIndicators",
        imgs: "js;if(fun.ge('.CustomPictureDownloadImage')){return[...fun.gae('.CustomPictureDownloadImage')]}else{return[...fun.gae('#carouselImageIndicators img')]}",
        insertImg: ".main-content",
        customTitle: "return fun.geT('h3');",
        category: "nsfw2"
    }, {
        name: "MrCong.com",
        reg: /mrcong\.com\/.+\//,
        imgs: "js;let max=fun.geT('.page-link>*:last-child');return fun.getImg('.entry img[decoding]',max,4)",
        insertImg: "//p[img[@decoding]]",
        customTitle: "return fun.geT('h1');",
        category: "nsfw1"
    }, {
        name: "萌图社 www.446m.com",
        reg: /www\.\d+m\.com\/index\.php\/\w+\/\d+\.html/,
        imgs: ".post-item .img",
        customTitle: "return document.title.slice(0,-6)",
        category: "nsfw1"
    }, {
        name: "秀色女神 www.xsnvshen.co", //需搭配東方永頁機大圖規則
        reg: /www\.xsnvshen\.co\/album\/\d+/,
        imgs: ".longConWhite>img",
        customTitle: "return fun.geT('h1')",
        category: "nsfw1"
    }, {
        name: "Everia.club",
        reg: /everia\.club/,
        imgs: ".wp-block-image img",
        customTitle: "return fun.geT('h1')",
        category: "nsfw2"
    }, {
        name: "NongMo.Zone www.ilovexs.com",
        reg: /www\.ilovexs\.com\/post_id\/\d+\//,
        imgs: ".separator img",
        customTitle: "return fun.geT('h1')",
        category: "nsfw2"
    }, {
        name: "凸凹吧/撸女吧/女优吧/撸哥吧/欲女吧 www.tuao.one,www.63mm.cc,www.97mm.cc,www.luge8.co,luge8.co",
        reg: /(www\.)?(tuao8?|tumm|\d+mm|luge8?)\.[a-z]{2,3}\/(post|web)\//,
        imgs: "js;let max=fun.geT('.next-page',2);return fun.getImg('.entry img[title]',max);",
        customTitle: "return fun.geT('h1.title');",
        category: "nsfw2"
    }, {
        name: "Asian To Lick asiantolick.com",
        reg: /asiantolick\.com\/post/,
        imgs: "js;let arr=[];[...fun.gae('div[data-src]')].forEach(e=>{arr.push(e.dataset.src)});return arr;",
        insertImg: ".spotlight-group",
        customTitle: "return fun.geT('h1');",
        category: "nsfw2"
    }, {
        name: "goddess247.com",
        reg: /goddess247\.com\/.+\//,
        imgs: ".elementor-widget-container p img[alt]",
        customTitle: "return fun.title('-');",
        category: "nsfw1"
    }, {
        name: "www.4kup.net",
        reg: /www\.4kup\.net\/.+\.html/,
        imgs: "js;let arr=[];[...fun.gae('a.thumb-photo')].forEach(a=>{arr.push(a.href)});return arr;",
        insertImg: "#gallery",
        customTitle: "return fun.geT('h1');",
        next: ".next-page",
        category: "nsfw2"
    }, {
        name: "牛C网导航|就爱你导航 niuc2.com",
        reg: /niuc2\.com\/\d+\.html/,
        imgs: "js;let max=fun.geT('.page-nav>*:last-child',3);return fun.getImg(\"[class*='wp-image']\",max,5);",
        insertImg: "//p[a[img[@decoding]]]",
        customTitle: "return document.title.split('|')[0].trim()",
        css: ".post-apd{display: none!important;}",
        category: "nsfw2"
    }, {
        name: "H漫畫貼圖 - 7mmtv.sx",
        reg: /7mmtv\.sx\/.*hcomic/,
        imgs: "js;return Large_cgurl;",
        customTitle: "return fun.title('-');",
        category: "hcomic"
    }, {
        name: "嗨皮漫畫 m.happymh.com",
        reg: /m\.happymh\.com\/reads/,
        imgs: "js;let lps=location.pathname.split('/'),mangaCode=lps[2],id=lps[3],apiUrl=`https://m.happymh.com/v2.0/apis/manga/read?code=${mangaCode}&cid=${id}`;return fetch(apiUrl).then(res=>res.text()).then(res=>{let jsonData=JSON.parse(res);let srcs=jsonData.data.scans;let arr=[];for(let i in srcs){arr.push(srcs[i].url)}return arr});",
        customTitle: "let lps=location.pathname.split('/'),mangaCode=lps[2],id=lps[3],apiUrl=`https://m.happymh.com/v2.0/apis/manga/read?code=${mangaCode}&cid=${id}`;return fetch(apiUrl).then(res=>res.text()).then(res=>{let jsonData=JSON.parse(res);return jsonData.data.manga_name+' - '+jsonData.data.chapter_name});",
        insertImg: "//article[div[contains(@id,'imageLoader')]]",
        next: "//a[span[text()='下一話' or text()='下一话']]",
        category: "comic"
    }, {
        name: "COLAMANHUA www.colamanhua.com", //下載不了...Picviewer CE+可以
        enable: 0,
        reg: /www\.colamanhua\.com\/manga-.+\.html$/,
        imgs: ".mh_comicpic img[src^=blob]",
        one: true,
        category: "comic"
    }, {
        name: "8Comic無限動漫 www.comicabc.com",
        reg: /(a|www)\.(comicabc|twobili)\.com\/(ReadComic|online)/,
        imgs: "js;let code=[...document.scripts].find(s=>s.innerHTML.search(/ge\\(e\\)/)>-1).innerHTML;let cM=code.match(/ge\\([^.]+\\.src\\s?=\\s?([^;]+)/);let keyCode=cM[1];let arr=[];for(let i=1;i<=ps;i++){let r='('+i+')';let src='https:'+fun.run(keyCode.replace(/\\(pp?\\)/g,r));arr.push(src)}return arr;",
        customTitle: "let t=document.title.split(' ')[0];return`${t}-第${ch}集`;",
        next: "//button[text()='下一集']",
        category: "comic"
    }, {
        name: "8Comic無限動漫手機版 m.comicbus.com",
        reg: /8\.twobili\.com\/comic\/insurance/,
        imgs: "js;let arr=[];for(let i=1;i<=ps;i++){let imgSrc='https://img'+ss(c,4,2)+'.8comic.com/'+ss(c,6,1)+'/'+ti+'/'+ss(c,0,4)+'/'+nn([i])+'_'+ss(c,mm([i])+10,3,f)+'.jpg';arr.push(imgSrc)}return arr;",
        customTitle: "let t=document.title.split(' ')[0];let n=fun.geT('#chapter');return t+' - '+n;",
        next: "#nextvol",
        category: "comic"
    }, {
        name: "DM5/極速 分頁模式 www.dm5.com",
        reg: /(www|tel|en|cnc|hk|m)?\.?(dm5|1kkk)\.(com|cn)\/(m|ch|vol|other)[-_0-9]+\//,
        include: "#chapterpager",
        imgs: "js;let get=async()=>{if(!mkey){var mkey=''}let arr=[];for(let i=1;i<=DM5_IMAGE_COUNT;i++){fun.show(`獲取資料中(${i}/${DM5_IMAGE_COUNT})`);let apiUrl=location.origin+DM5_CURL+'chapterfun.ashx'+`?cid=${DM5_CID}&page=${i}&key=${mkey}&language=1>k=6&_cid=${DM5_CID}&_mid=${DM5_MID}&_dt=${DM5_VIEWSIGN_DT}&_sign=${DM5_VIEWSIGN}`,res=await fetch(apiUrl),resText=await res.text(),src=await fun.run(resText)[0];arr.push(src)}return arr};return get();",
        insertImg: "#cp_img",
        customTitle: "return fun.title('_', 2);",
        next: "//a[text()='下一章']",
        category: "comic"
    }, {
        name: "DM5/極速 條漫模式 https://www.dm5.com/manhua-moutianchengweimoshen/",
        reg: /(www|tel|en|cnc|hk|m)?\.?(dm5|1kkk)\.(com|cn)\/(m|ch|vol|other)[-_0-9]+\//,
        include: "#barChapter",
        imgs: "#barChapter>img",
        customTitle: "return fun.title('_', 2);",
        next: "//a[text()='下一章']",
        category: "comic"
    }, {
        name: "DM5/極速 手機版 m.dm5.com",
        reg: /(www|tel|en|cnc|hk|m)?\.?(dm5|1kkk)\.(com|cn)\/(m|ch|vol|other)[-_0-9]+\//,
        include: "//script[contains(text(),'newImgs')]",
        imgs: "js;return newImgs",
        insertImg: "#cp_img", //清空元素內容把所有圖片插入到此元素
        customTitle: "return fun.title('_', 2);",
        next: "//a[text()='下一章']",
        category: "comic"
    }];
    const fun = {
        ge: (e, d) => {
            if (/^\//.test(e)) {
                return (d || document).evaluate(e, (d || document), null, XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue;
            } else {
                return (d || document).querySelector(e);
            }
        },
        gae: (e, d) => {
            if (/^\//.test(e)) {
                let nodes = [];
                let results = (d || document).evaluate(e, (d || document), null, XPathResult.ANY_TYPE, null);
                let node;
                while (node = results.iterateNext()) {
                    nodes.push(node);
                }
                return nodes;
            } else {
                return (d || document).querySelectorAll(e);
            }
        },
        geT: (ele, mode = 1) => {
            if (mode == 1) {
                return fun.ge(ele).innerText;
            } else if (mode == 2) {
                return fun.ge(ele).previousElementSibling.innerText;
            } else if (mode == 3) {
                return fun.ge(ele).previousElementSibling.previousElementSibling.innerText;
            }
        },
        run: code => new Function("return " + code)(),
        html: str => new DOMParser().parseFromString(str, 'text/html'),
        title: (str, mode = 1) => {
            let s = document.title.split(str);
            if (mode == 1) {
                return s[0].replace(/,$/, '').trim();
            } else if (mode == 2) {
                return s[0] + str + s[1].replace(/,$/, '').trim();
            }
            return '參數錯誤';
        },
        show: text => {
            let msg = fun.ge(".customPicDownloadMsg");
            if (fun.ge(".customPicDownloadMsg[style]")) {
                msg.removeAttribute('style');
            }
            msg.innerText = text;
        },
        checkDataset: img => {
            if (img.tagName == "IMG" || img.tagName == "DIV") {
                const setArr = ["data-src", "data-original", "data-url", "data-thumb", "data-lazy-src", "data-lazyload", "data-lazyload-src"];
                //for (let i in setArr) {
                for (let i = 0; i < setArr.length; i++) {
                    let imgSrc = img.getAttribute(setArr[i]);
                    if (imgSrc) {
                        return {
                            ok: true,
                            src: imgSrc
                        };
                    }
                }
            }
            return {
                ok: false
            };
        },
        getImg: async (img, maxPage = 1, mode = 1, rText = [null, null]) => {
            fun.show("獲取元素中");
            let imgsArray = [];
            let html = url => fetch(url).then(res => res.text());
            let resArr = [];
            resArr.push(html(siteUrl));
            if (maxPage > 1) {
                for (let i = 2; i <= maxPage; i++) {
                    if (mode == 1) {
                        //.html => .html?page=2 第二頁
                        resArr.push(html(siteUrl + "?page=" + i));
                    } else if (mode == 2) {
                        //.html ==> /2.html 第二頁
                        resArr.push(html(siteUrl.slice(0, -5) + '/' + i + '.html'));
                    } else if (mode == 3) {
                        //.html ==> _1.html  第二頁
                        resArr.push(html(siteUrl.slice(0, -5) + '_' + (i -1) + '.html'));
                    } else if (mode == 4) {
                        //【/ ==> /2/】  第二頁
                        resArr.push(html(siteUrl.slice(0, -1) + '/' + i + '/'));
                    } else if (mode == 5) {
                        //.html ==> -2.html  第二頁
                        resArr.push(html(siteUrl.slice(0, -5) + '-' + i + '.html'));
                    }
                }
            }
            await Promise.all(resArr).then((htmls) => {
                for (let i = 0; i < htmls.length; i++) {
                    let doc = fun.html(htmls[i]);
                    let imgs = [...fun.gae(img, doc)];
                    for (let i = 0; i < imgs.length; i++) {
                        if (rText[0]) {
                            imgs[i].src = imgs[i].src.replace(rText[0], rText[1]);
                        }
                        imgsArray.push(imgs[i]);
                    }
                }
            });
            imgsArray = imgsArray.filter(i => i);
            //imgsArray = imgsArray.filter(i => i.tagName == "IMG");
            return imgsArray;
        },
        getImgP: async (img, paginationA, rText = [null, null] ) => {//從頁碼條抓鏈接
            fun.show("獲取元素中");
            let imgsArray = [];
            let html = url => fetch(url).then(res => res.text());
            let resArr = [];
            resArr.push(html(siteUrl));
            let links = fun.gae(paginationA);
            for (let i = 0; i < links.length; i++) {
                resArr.push(html(links[i].href));
            }
            await Promise.all(resArr).then((htmls) => {
                for (let i = 0; i < htmls.length; i++) {
                    let doc = fun.html(htmls[i]);
                    debug("getImgP DOM", doc);
                    let imgs = [...fun.gae(img, doc)];
                    for (let i = 0; i < imgs.length; i++) {
                        if (rText[0]) {
                            imgs[i].src = imgs[i].src.replace(rText[0], rText[1]);
                        }
                        imgsArray.push(imgs[i]);
                    }
                }
            });
            imgsArray = imgsArray.filter(i => i);
            return imgsArray;
        },
        insertImg: (imgsArray, ele) => {
            let srcArr = [];
            //for (let i in imgsArray) {
            for (let i = 0; i < imgsArray.length; i++) {
                let imgSrc;
                let check = fun.checkDataset(imgsArray[i]);
                if (imgsArray[i].tagName == 'IMG' && check.ok) {
                    srcArr.push(check.src);
                } else if (imgsArray[i].tagName == 'IMG') {
                    srcArr.push(imgsArray[i].src);
                } else if (/^http/.test(imgsArray[i])) {
                    srcArr.push(imgsArray[i]);
                } else {
                    debug("insertImg格式錯誤!", imgsArray[i]);
                    return;
                }
            }
            let fragment = new DocumentFragment();
            //for (let i in srcArr) {
            for (let i = 0; i < srcArr.length; i++) {
                let img = new Image();
                img.className = "CustomPictureDownloadImage";
                img.src = srcArr[i];
                fragment.appendChild(img);
            }
            let E = fun.ge(ele);
            if (E) {
                E.innerHTML = "";
                E.appendChild(fragment);
            } else {
                debug("用來定位插入的元素不存在");
            }
        },
        css: css => {
            let style = document.createElement("style");
            style.type = "text/css";
            style.id = "CustomPictureDownloadStyle";
            style.innerHTML = css;
            document.head.appendChild(style);
        }
    };
    const debug = (str, obj = "", title = "debug") => {
        console.log(
            `%c[Custom Picture Download] ${title}:`,
            'background-color: #C9FFC9;',
            str, obj
        );
    };
    let nsfw1Data = customData.filter(item => item.category == "nsfw1"); //列出寫真站
    let nsfw2Data = customData.filter(item => item.category == "nsfw2"); //列出老司機站
    let comicData = customData.filter(item => item.category == "comic"); //列出普漫站
    let hcomicData = customData.filter(item => item.category == "hcomic"); //列出H漫站
    let noneData = customData.filter(item => item.category == "none"); //列出未分類
    //for (let i in customData) {
    for (let i = 0; i < customData.length; i++) {
        if (customData[i].reg.test(siteUrl)) {
            options.enable = 1;
            if (customData[i].enable == 0) {
                options.enable = 0;
                debug("禁用");
                return;
            }
            if (customData[i].imgs) {
                options.default = customData[i].imgs;
                debug(`CSS/Xpath/JS選擇器:${options.default}`);
            }
            if (customData[i].one) {
                options.one = customData[i].one;
                debug("單線程下載");
            }
            let titleCode = customData[i].customTitle;
            if (titleCode) {
                customTitle = await new Function("fun", '"use strict";' + titleCode)(fun);
                debug(`自定義標題:${customTitle}`);
            }
            let include = customData[i].include;
            if (include) {
                if (!fun.ge(include)) {
                    options.enable = 0;
                    console.clear();
                    debug("頁面沒有包含必須元素,跳出本次循環繼續遍歷");
                    continue;
                }
            }
            let exclude = customData[i].exclude;
            if (exclude) {
                if (fun.ge(exclude)) {
                    options.enable = 0;
                    console.clear();
                    debug("頁面包含排除元素,跳出本次循環繼續遍歷");
                    continue;
                }
            }
            let next = customData[i].next;
            if (next) {
                document.addEventListener('keydown', (event) => {
                    let key = window.event ? event.keyCode : event.which;
                    if (key == 39) {
                        let n = fun.ge(next);
                        if (n) n.click();
                    }
                });
            }
            let css = customData[i].css;
            if (css) {
                fun.css(css);
            }
            siteData = customData[i];
            break;
        }
    }
    if (siteData.reg) {
        debug("列出寫真站", nsfw1Data);
        debug("列出老司機站", nsfw2Data);
        debug("列出普漫站", comicData);
        debug("列出H漫站", hcomicData);
        debug("列出未分類", noneData);
        debug("全局此站資料", siteData);
    }
    let promiseBlobArray = [];
    let downloadNum = 0;
    var _GM_xmlhttpRequest;
    if (typeof GM_xmlhttpRequest != "undefined") {
        _GM_xmlhttpRequest = GM_xmlhttpRequest;
    } else if (typeof GM != "undefined" && typeof GM.xmlHttpRequest != "undefined") {
        _GM_xmlhttpRequest = GM.xmlHttpRequest;
    }

    const ge = css => document.querySelector(css);
    const gae = css => document.querySelectorAll(css);
    const gx = xpath => document.evaluate(xpath, document, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue;
    const gax = xpath => {
        let nodes = [];
        let results = document.evaluate(xpath, document, null, XPathResult.ANY_TYPE, null);
        let node;
        while (node = results.iterateNext()) {
            nodes.push(node);
        }
        return nodes;
    };

    const getNum = (i) => {
        let n = parseInt(i) + 1;
        let picNum = n;
        if (i < 9) {
            picNum = "000" + n;
        } else if (i < 99) {
            picNum = "00" + n;
        } else if (i < 999) {
            picNum = "0" + n;
        }
        return picNum;
    };

    const showMsg = (text, time = 1000) => {
        ge(".customPicDownloadMsg").removeAttribute("style");
        ge(".customPicDownloadMsg").innerText = text;
        setTimeout(() => {
            ge(".customPicDownloadMsg").innerText = "none";
            ge(".customPicDownloadMsg").style = "display:none";
        }, time);
    };

    const checkDataset = (img) => {
        if (img.tagName == 'IMG') {
            const setArr = ["data-src", "data-original", "data-url", "data-thumb", "data-lazy-src", "data-lazyload", "data-lazyload-src"];
            //for (let i in setArr) {
            for (let i = 0; i < setArr.length; i++) {
                let imgSrc = img.getAttribute(setArr[i]);
                if (imgSrc) {
                    return {
                        ok: true,
                        src: imgSrc
                    };
                }
            }

        }
        return {
            ok: false
        };
    };

    const getData = (srcUrl, picNum, imgsNum) => {
        return new Promise((resolve) => {
            const getMsg = (text) => {
                downloadNum += 1;
                ge(".customPicDownloadMsg").innerText = `第${downloadNum}/${imgsNum}張下載${text}`;
            };
            _GM_xmlhttpRequest({
                method: "GET",
                url: srcUrl,
                responseType: "blob",
                headers: {
                    referer: location.origin + "/"
                },
                onload: (data) => {
                    let blob = data.response;
                    if (/^image/.test(blob.type)) {
                        resolve({
                            blob: blob,
                            picNum: picNum
                        });
                        getMsg("完成");
                    } else {
                        resolve({
                            error: "下載錯誤",
                            picNum: picNum,
                            src: srcUrl,
                            blob: blob
                        });
                        getMsg("錯誤");
                    }
                },
                onerror: (error) => {
                    resolve({
                        error: "下載錯誤",
                        picNum: picNum,
                        src: srcUrl
                    });
                    getMsg("錯誤");
                }
            });
        });
    };

    const imgZipDownload = async () => {
        if (/\d+/.test(ge(".customPicDownloadMsg").innerText)) {
            alert("下載&壓縮中請勿重複操作!");
            return;
        }
        let selector = await prompt("請輸入自訂CSS/Xpath選擇器:\n範例:img#TheImg OR //img[@id='TheImg']\n也能使用JS代碼自己生成的IMG元素陣列\n範例:js;return [...document.images];", options.default);
        let imgs;
        if (!selector || selector === "") {
            showMsg("已取消");
            return;
        } else if (selector.length < 3) {
            showMsg("字數小於3已取消");
            return;
        } else if (/^js;/.test(selector)) {
            imgs = await new Function("fun", '"use strict";' + selector.slice(3))(fun);
            debug("JSimgs:", imgs);
        } else if (/^\//.test(selector)) {
            imgs = [...gax(selector)];
        } else {
            imgs = [...gae(selector)];
        }
        imgs = imgs.filter(item => item); //去除空、無用、重複
        globalImgArray = imgs;
        let titleText = await prompt("請輸入自訂壓縮檔資料夾名稱", (customTitle || document.title));
        if (imgs[0] && titleText != null && titleText != "") {
            debug("imgZipDownload():", imgs);
            const imgsNum = imgs.length;
            let title = titleText;
            const zip = new JSZip();
            const zipFolder = zip.folder(`${title} [${imgsNum}P]`);
            ge(".customPicDownloadMsg").removeAttribute("style");
            ge(".customPicDownloadMsg").innerText = "0101010101...";
            //for (let i in imgs) {
            for (let i = 0; i < imgs.length; i++) {
                let n = parseInt(i) + 1;
                let picNum = getNum(i);
                let promiseBlob;
                let imgSrc;
                let check = fun.checkDataset(imgs[i]);
                if (imgs[i].tagName == 'IMG' && check.ok) {
                    imgSrc = check.src;
                } else if (imgs[i].tagName == 'IMG') {
                    imgSrc = imgs[i].src;
                } else if (/^http/.test(imgs[i])) {
                    imgSrc = imgs[i];
                } else {
                    debug("格式錯誤!", imgs[i]);
                    continue;
                }
                if (options.one) {
                    promiseBlob = await getData(imgSrc, picNum, imgsNum);
                } else {
                    promiseBlob = getData(imgSrc, picNum, imgsNum);
                }
                promiseBlobArray.push(promiseBlob);
            }
            debug("PromiseBlobArray:", promiseBlobArray);

            Promise.all(promiseBlobArray).then((data) => {
                debug("PromiseAllData:", data);
                let blobDataArray = data.filter(item => item.blob); //成功下載
                let errorDataArray = data.filter(item => item.error); //下載錯誤
                debug("NewDataArray:", blobDataArray);
                debug("ErrorDataArray:", errorDataArray);
                if (blobDataArray[0]) {
                    //for (let i in blobDataArray) {
                    for (let i = 0; i < blobDataArray.length; i++) {
                        let n = parseInt(i) + 1;
                        let ex = blobDataArray[i].blob.type.split("/")[1];
                        let fileName = `${blobDataArray[i].picNum}P.${ex}`;
                        //console.log(`第${n}/${blobDataArray.length}張,檔案名:${fileName},大小:${parseInt(blobDataArray[i].blob.size / 1024)} Kb`);
                        zipFolder.file(fileName, blobDataArray[i].blob, {
                            binary: true
                        });
                    }
                    zip.generateAsync({
                        type: "blob"
                    }, (metadata) => {
                        ge(".customPicDownloadMsg").innerText = "壓縮進度: " + metadata.percent.toFixed(2) + " %";
                    }).then(data => {
                        promiseBlobArray = [];
                        downloadNum = 0;
                        debug("ZIP壓縮檔數據:", data);
                        ge(".customPicDownloadMsg").innerText = "none";
                        ge(".customPicDownloadMsg").style = "display:none";
                        let a = document.createElement("a");
                        a.href = URL.createObjectURL(data);
                        a.download = `${title} [${imgsNum}P].zip`;
                        document.body.appendChild(a);
                        a.click();
                        a.remove();
                        URL.revokeObjectURL(data);
                    });
                } else {
                    promiseBlobArray = [];
                    downloadNum = 0;
                    showMsg("下載失敗數據為空...");
                }
            });
        } else {
            showMsg("無圖或壓縮檔名稱為null", 3000);
        }
    };

    const copyImgSrcText = async () => {
        let selector = await prompt("請輸入自訂CSS/Xpath選擇器:\n範例:img#TheImg OR //img[@id='TheImg']\n也能使用JS代碼自己生成的IMG元素陣列\n範例:js;return [...document.images];", options.default);
        let imgs;
        let imgSrcArray = [];
        if (!selector || selector === "") {
            showMsg("已取消");
            return;
        } else if (selector.length < 3) {
            showMsg("字數小於3已取消");
            return;
        } else if (/^js;/.test(selector)) {
            imgs = await new Function("fun", '"use strict";' + selector.slice(3))(fun);
            debug("JSimgs:", imgs);
        } else if (/^\//.test(selector)) {
            imgs = [...gax(selector)];
        } else {
            imgs = [...gae(selector)];
        }
        imgs = imgs.filter(item => item); //去除空、無用、重複
        if (siteData.insertImg) {
            debug("insertImg():", imgs);
        } else {
            debug("CopyImgSrcText():", imgs);
        }
        if (imgs[0]) {
            //for (let i in imgs) {
            for (let i = 0; i < imgs.length; i++) {
                let imgSrc;
                let check = fun.checkDataset(imgs[i]);
                if (imgs[i].tagName == 'IMG' && check.ok) {
                    imgSrc = check.src;
                } else if (imgs[i].tagName == 'IMG') {
                    imgSrc = imgs[i].src;
                } else if (/^http/.test(imgs[i])) {
                    imgSrcArray = [...imgs].filter(item => item);
                    break;
                } else {
                    debug("格式錯誤!", imgs[i]);
                    continue;
                }
                imgSrcArray.push(imgSrc);
            }
        } else {
            showMsg("沒有任何圖片元素...");
            return;
        }
        globalImgArray = imgSrcArray;
        if (siteData.insertImg) {
            fun.insertImg(globalImgArray, siteData.insertImg);
            showMsg("已插入全部圖片");
            return;
            /*
            let yes = confirm("已經插入所有圖片了,還要將網址複製到剪貼簿嗎?");
            if (!yes) {
                showMsg("已取消");
                return;
            }*/
        }
        imgSrcArray.push(customTitle || document.title);
        debug("imgSrcArray:", imgSrcArray);
        let str = imgSrcArray.join("\n");
        console.log(str);
        copyToClipboard(str);
        showMsg(`圖片網址已複製(${imgs.length})`);
        imgSrcArray = null;
    };

    const copyToClipboard = (textToCopy) => {
        if (navigator.clipboard && window.isSecureContext) {
            return navigator.clipboard.writeText(textToCopy);
        } else {
            let textArea = document.createElement("textarea");
            textArea.value = textToCopy;
            textArea.style.position = "absolute";
            textArea.style.opacity = 0;
            textArea.style.left = "-999999px";
            textArea.style.top = "-999999px";
            document.body.appendChild(textArea);
            textArea.focus();
            textArea.select();
            return new Promise((res, rej) => {
                document.execCommand('copy') ? res() : rej();
                textArea.remove();
            });
        }
    };
    const oneSwitch = () => {
        if (options.one) {
            options.one = false;
            showMsg("啟用多線程");
        } else {
            options.one = true;
            showMsg("啟用單線程");
        }
    };

    const addCustomPicDownloadButton = () => {
        let img = new Image();
        img.id = "customPicDownload";
        img.src = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAMAAABEpIrGAAACFlBMVEVEREAAAABEREBEREBUXnFTXXBTXG5VXXJUXHFUW28grV0grV0hrl4hrV0hq10iql0oomAhrV2BiZiAiJd/h5V+h5Uiq16OlaKNlKGMk5+Mkp+KkZ89p28nq2Hp9+/n9u3t8PHs7/Dg9Ojp7O3d8+bo7O3o6+zn6+3m6uzl6evX8OLV8OHi5ujh5efO7dze4uTe4ePb3+LY3eDX29/W2t7T19u35Mu25Mr01lqw4sbz1Vmv4sXA1NWs4cPw0lnv0lmp4MCk3r3ozVuh3buu0M6vz83kylugysefycadx8OexsOdxsOF0qZ6zp5pyJK1p2S0pmOsoGRWv4SLlKEmuZpBsnc5t3AmuJmQi2ont5kmtpcptJiOiWg0tWwytGorr5Yqr5YsrpUws2kqrJRNmpMusmcsqpIuqJMssWkqsWQqsGUnsGUzoI8lsGGAf2wkr2Ekr2BzfI0irl8hrl5ye4x9fGogrWAhrV03mIwgrF9xeoshq10gql8dp2ccp2cdpmkcnIUanIYcm4QRoIUTnoURn4QdmYM/h4QcmIMSnYMXmoMWmoIbloIclYJDgYIYl4EaloAek4IfkYFqbm0jjoBqbW0ni4ApiX9Hd35kanBjaXBhaG9MbnpOanhOaXhPaHdDbXZHanZGanRLZnZJZ3RSYnVMZXRPY3ROY3VSYXRQYnRTYHRVX3NUX3RSYHNUXnNTXXJTXXFME6frAAAAHnRSTlMAAAUGZ2dqeHh7lrzNzc7O3O/09PT19fn5+fn5/f4hZOrvAAAB1UlEQVR42oWTzYoTQRSFv6q6RqOoqBicTTCKK1cOURBGBV9AXCgu9Vl8FF/ApcshI4qKgrtBByZK0HSGmMxkMrF/qq6LTjptErGX5zu3uu65dY3TYCh9ag2UNPsfjnmpZW4MgBbaIy/6PCnxvL7gEQhJ7AruAqifcZ96EHDX67ngKgCJn/k3B6AC1O9Cj9ri/2mBsZIf9iLi0rMFDhhjbO6NsL9er+CQG3pYMT+XuGVmuH9SzPF7S/UONDfUHl44/6A24wfdpMgsSO6tP52ffxjF6cUT056MBdxf/cVRzP6PiQG8KhZMJedhewik0QT43RsBQUEwLufZx53Td85p7wCwEu+Fs6qAGBsA1aO3XUZbG/EAsAJJlM/MGgOoDltdYNTam3JIOlVmOaj2N/sAjHf9lGdBqwACmmi0FU/TOWxfEY483o/9tcuvJBXU+90PaZHf5NtVFzTL+N7bqD4GS9DRmzl3btJWk2WFIKradqX8LfudU3VKBuKd0vwc+HCjNDJhkJXfB6jq+wZJBSAT5MmKN1/la7sJ+jmsiyQrdkZo9N819FNnXcSZFTt1rGnXhl/G6a26IP/YOXczi5prQmFY3snbkzNSXGyBV+28p+nNF+vn2h97PvHD5SOHkgAAAABJRU5ErkJggg==";
        img.setAttribute("title", "左鍵:進行下載打包壓縮\n中鍵:切換單多線程\n右鍵:複製圖片網址和標題或插入所有圖片");
        img.oncontextmenu = () => false;
        img.addEventListener("click", () => {
            imgZipDownload();
        });
        img.addEventListener("mousedown", (event) => {
            if (event.button == 1) {
                event.preventDefault();
                oneSwitch();
            }
            if (event.button == 2) {
                copyImgSrcText();
            }
        });
        document.body.appendChild(img);
    };

    const addCustomPicDownloadMsg = () => {
        let div = document.createElement("div");
        div.className = "customPicDownloadMsg";
        div.style = "display:none";
        div.innerText = "none";
        document.body.appendChild(div);
    };

    const addGlobalStyle = css => {
        let style = document.createElement("style");
        style.type = "text/css";
        style.innerHTML = css;
        document.head.appendChild(style);
    };

    const css = `
#customPicDownload {
    width: 32px!important;
    height: 32px!important;
    position: fixed!important;
    bottom: 20px!important;
    left: 20px!important;
    z-index:2147483647;
    opacity: 0.8!important;
}
.customPicDownloadMsg {
    font-family: Arial,sans-serif!important;
    font-size: 26px;
    font-weight: bold;
    text-align: center;
    line-height: 50px;
    color: #ffffff;
    width: 280px;
    height: 50px;
    top: 30%;
    left: 50%;
    margin-left: -140px;
    background-color: #000;
    border: 1px solid #303030;
    border-radius: 10px;
    position: fixed;
    z-index:2147483647;
    opacity: 0.7;
}
.CustomPictureDownloadImage {
    width: auto !important;
    height: auto !important;
    max-width: 100% !important;
    display: block !important;
    margin: 0 auto !important
}
    `;

    if (options.enable == 1) {
        addCustomPicDownloadButton();
        addCustomPicDownloadMsg();
        addGlobalStyle(css);
        document.addEventListener('keydown', (e) => {
            switch (e.keyCode) {
                case 96: //數字鍵0
                    imgZipDownload();
                    break;
                case 97: //數字鍵1
                    copyImgSrcText();
                    break;
            }
        });
    }

})();