您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
按画师名划分文件夹下载Pixiv原图,支持多图Zip/Gif。
当前为
// ==UserScript== // @name Pixiv Downloader // @namespace https://greasyfork.org/zh-CN/scripts/432150 // @version 0.2.1 // @description 按画师名划分文件夹下载Pixiv原图,支持多图Zip/Gif。 // @author ruaruarua // @match https://www.pixiv.net/* // @icon https://www.google.com/s2/favicons?domain=pixiv.net // @grant GM_xmlhttpRequest // @grant GM_download // @connect i.pximg.net // @require https://cdnjs.cloudflare.com/ajax/libs/jszip/3.7.1/jszip.min.js // @require https://cdnjs.cloudflare.com/ajax/libs/gif.js/0.2.0/gif.js // ==/UserScript== (async function () { //根据个人需求修改以下两项 const GIF_FLAG = false; //需要保存GIF请设置为true const GIF_QUALITY = 5; //数值越小,GIF质量越好 const workerStr = await fetch('https://cdnjs.cloudflare.com/ajax/libs/gif.js/0.2.0/gif.worker.js') .then(res=>res.blob()); const gifConfig = { workers: 3, quality: GIF_QUALITY, workerScript: URL.createObjectURL(workerStr) }; class PixivDownloader { constructor(pixivID) { this.pixivID = pixivID; this.artworksURL = 'https://www.pixiv.net/artworks/' + pixivID; this.data = {}; this.author = ''; this.illustTitle = ''; this.illustType = -1; this.authorFormatted = ''; this.illustTitleFormatted = ''; this.pageCount = 0; this.imgList = []; this.gifSrc = ''; this.gifSrcInfo = {}; } _toGIF(blob, self, path, resolve, reject) { let gifPromiseList = []; let imgList = []; zip.folder(`${self.pixivID}`) .loadAsync(blob) .then((zip) => { let gif = new GIF(gifConfig); let i = 0; zip.forEach((relativePath, file) => { gifPromiseList[i] = new Promise((loadResolve, loadReject) => { i += 1; const image = new Image(); imgList.push(image); image.onload = () => { loadResolve(); }; file.async('blob') .then((blob) => { const objectURL = URL.createObjectURL(blob); image.src = objectURL; }); }); }); Promise.all(gifPromiseList) .then(() => { zip.remove(`${self.pixivID}`); imgList.forEach((e, i) => { gif.addFrame(e, { delay: self.gifSrcInfo.frames[i].delay }); }); gif.on('finished', (blob) => { self._save(blob, path, resolve, reject); }); gif.render(); }); }) } _save(blob, path, resolve, reject) { const imgUrl = URL.createObjectURL(blob); GM_download({ url: imgUrl, name: path, onerror: (error) => { console.log('Error: ',error); if (reject) { reject(); } }, onload: () => { console.log('Download complete: ' + path); URL.revokeObjectURL(imgUrl); if (resolve) { resolve(); } } }); } startDownload(path = '', dlSrc = '', onprogressCallback = null, resolve = null, reject = null) { let self = this; GM_xmlhttpRequest({ url: dlSrc, method: 'GET', headers: { referer: 'https://www.pixiv.net' }, responseType: 'blob', onprogress: (e) => { if (typeof onprogressCallback == 'function') { onprogressCallback(e); } }, onload: function (res) { if (path.slice(-3) != 'gif') { self._save(res.response, path, resolve, reject); } else { if (typeof onprogressCallback == 'function') { onprogressCallback(res); } self._toGIF(res.response, self, path, resolve, reject); } }, onerror: function () { console.log('XmlhttpRequest failed: ' + dlSrc); if (reject) { reject(); } } }); } _replaceInvalidChar(picInfo = '') { if (picInfo) { let newpicInfo = ''; for (let i = 0; i < picInfo.length; i++) { const char = picInfo.charAt(i); switch (char) { case '\\': case '/': case ':': case '?': case '|': newpicInfo += '-'; break; case '"': newpicInfo += "'"; break; case '<': newpicInfo += "["; break; case '>': newpicInfo += "]"; break; default: newpicInfo += char; } } return newpicInfo; } } async _initProps(htmlText) { this.data = JSON.parse(htmlText.match(/"meta-preload-data" content='(.*)'>/)[1]); if (this.data.illust) { this.author = this.data.illust[this.pixivID].userName; this.illustTitle = this.data.illust[this.pixivID].illustTitle; this.authorFormatted = this._replaceInvalidChar(this.author); this.illustTitleFormatted = this._replaceInvalidChar(this.illustTitle); this.illustType = this.data.illust[this.pixivID].illustType; if (this.illustType == 0 || this.illustType == 1) { this.pageCount = this.data.illust[this.pixivID].pageCount; let firstimgSrc = this.data.illust[this.pixivID].urls.original; let baseSrc = { srcPrefix: firstimgSrc.slice(0, firstimgSrc.indexOf('_') + 2), srcSuffix: firstimgSrc.slice(-4) }; for (let i = 0; i < this.pageCount; i++) { const imgSrc = baseSrc.srcPrefix + i + baseSrc.srcSuffix; this.imgList.push(imgSrc); } return this; } if (this.illustType == 2) { const metaURL = `https://www.pixiv.net/ajax/illust/${this.pixivID}/ugoira_meta?lang=zh`; this.gifSrcInfo = await fetch(metaURL) .then(res => res.json()) .then(res => res.body) .catch(() => ''); if (/img-zip-ugoira/.test(this.gifSrcInfo.originalSrc)) { return this; } else { throw new Error('Fail to parse gif src.'); } } } throw new Error('Fail to parse html.'); } fetchData() { return fetch(this.artworksURL) .then(res => res.text()) .then(htmlText => this._initProps(htmlText)) } download(onprogressCallback) { let basePath = this.authorFormatted + '/' + this.authorFormatted + '_' + this.illustTitleFormatted + '_' + this.pixivID; basePath = basePath.replace('./','/'); if (this.illustType == 0 || this.illustType == 1) { let promiseList = []; if (this.imgList.length > 0) { this.imgList.forEach((imgSrc, i) => { promiseList[i] = new Promise((resolve, reject) => { const path = basePath + '_p' + i + imgSrc.slice(-4); this.startDownload(path, imgSrc, onprogressCallback, resolve, reject); }); }) } return Promise.all(promiseList); } if (this.illustType == 2) { return new Promise((resolve, reject) => { if (GIF_FLAG) { basePath += '.gif'; } else { basePath += '.zip'; } this.startDownload(basePath, this.gifSrcInfo.originalSrc, onprogressCallback, resolve, reject); }); } } } function isLinkMatch(link = null) { const artworkReg = /artworks\/([0-9]+)$/; const isHrefMatch = artworkReg.exec(link.href); if (isHrefMatch) { if (link.getAttribute('data-gtm-value') || link.classList.contains('gtm-illust-recommend-thumbnail-link') || link.classList.contains('gtm-discover-user-recommend-thumbnail') || link.classList.contains('work')) { return isHrefMatch[1]; } if (window.location.href.indexOf('bookmark_new_illust') != -1 && link.getAttribute('class')) { return isHrefMatch[1]; } } else { const activityReg = /illust_id=([0-9]+)/; const isActivityMatch = activityReg.exec(link.href); if (isActivityMatch && link.classList.contains('work')) { return isActivityMatch[1]; } } return false; } function createDlBtn(isMain = false, pixivID = 0) { const dlBtn = document.createElement('a'); dlBtn.setAttribute('href', 'javascript:void(0)'); dlBtn.setAttribute('pixivID', pixivID); dlBtn.classList.add('dl-btn'); if (!isMain) { dlBtn.classList.add('dl-sub-pic-btn'); dlBtn.setAttribute('style', 'position:absolute; left:0; bottom:0; z-index:1; display:inline-block; width:32px; height:32px; margin:0; padding:0; cursor:pointer; background-color: rgba(255,255,255,0.5);'); } else { dlBtn.classList.add('dl-pic-btn'); dlBtn.setAttribute('style', 'display:inline-block; width:32px; height:32px; margin:0; padding:0; margin-left:10px; cursor:pointer; background-color: transparent;'); } if (pixivStorage.has(pixivID)) { dlBtn.innerHTML = "<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 1024 1024\"><path fill=\"green\" d=\"M406.656 706.944 195.84 496.256a32 32 0 1 0-45.248 45.248l256 256 512-512a32 32 0 0 0-45.248-45.248L406.592 706.944z\"></path></svg>" } else { dlBtn.innerHTML = '<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 1024 1024\"><path d=\"M160 832h704a32 32 0 1 1 0 64H160a32 32 0 1 1 0-64zm384-253.696 236.288-236.352 45.248 45.248L508.8 704 192 387.2l45.248-45.248L480 584.704V128h64v450.304z\"></path></svg>' } return dlBtn; } function keyboardHandler(e) { // ctrl + d if (e.ctrlKey && e.key == 'd') { const dlBtn = rootNode.querySelector('.dl-pic-btn'); if (dlBtn) { e.preventDefault(); if (!e.repeat) { dlBtn.firstElementChild.dispatchEvent(new MouseEvent('click', { "bubbles": true })); } } } } function handleDownload(dlBtn = null, pixivID = '') { function onprogressCallback(e) { if (e.loaded && e.total != -1) { dlBtn.innerHTML = '<span style="line-height: 32px; font-size: 15px; color: green;">' + Math.round((e.loaded / e.total) * 100) + '</span>'; } else { dlBtn.innerHTML = '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1024 1024"><path fill="green" d="M224 160a64 64 0 0 0-64 64v576a64 64 0 0 0 64 64h576a64 64 0 0 0 64-64V224a64 64 0 0 0-64-64H224zm0-64h576a128 128 0 0 1 128 128v576a128 128 0 0 1-128 128H224A128 128 0 0 1 96 800V224A128 128 0 0 1 224 96z"></path><path fill="green" d="M384 416a64 64 0 1 0 0-128 64 64 0 0 0 0 128zm0 64a128 128 0 1 1 0-256 128 128 0 0 1 0 256z"></path><path fill="green" d="M480 320h256q32 0 32 32t-32 32H480q-32 0-32-32t32-32zm160 416a64 64 0 1 0 0-128 64 64 0 0 0 0 128zm0 64a128 128 0 1 1 0-256 128 128 0 0 1 0 256z"></path><path fill="green" d="M288 640h256q32 0 32 32t-32 32H288q-32 0-32-32t32-32z"></path></svg>'; } } new PixivDownloader(pixivID) .fetchData() .then(downloader => downloader.download(onprogressCallback)) .then(() => { dlBtn.innerHTML = "<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 1024 1024\"><path fill=\"green\" d=\"M406.656 706.944 195.84 496.256a32 32 0 1 0-45.248 45.248l256 256 512-512a32 32 0 0 0-45.248-45.248L406.592 706.944z\"></path></svg>"; pixivStorage.add(pixivID); localStorage.pixivDownloader = JSON.stringify([...pixivStorage]); }) .catch((err) => { dlBtn.innerHTML = '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1024 1024"><path fill="orange" d="M512 64a448 448 0 1 1 0 896 448 448 0 0 1 0-896zm0 832a384 384 0 0 0 0-768 384 384 0 0 0 0 768zm48-176a48 48 0 1 1-96 0 48 48 0 0 1 96 0zm-48-464a32 32 0 0 1 32 32v288a32 32 0 0 1-64 0V288a32 32 0 0 1 32-32z"></path></svg>'; if (err) { console.log(err); } }); } function observerCallback() { const regMatch = /artworks\/([0-9]+)$/; const isArtworksPage = regMatch.exec(window.location.href); if (isArtworksPage && !document.body.querySelector('.dl-pic-btn')) { const addFavousBtn = document.body.querySelector('.gtm-main-bookmark') || null; if (addFavousBtn) { const handleBar = addFavousBtn.parentElement.parentElement; const dlBtnWrap = addFavousBtn.parentElement.cloneNode(); dlBtnWrap.appendChild(createDlBtn(true, isArtworksPage[1])); handleBar.appendChild(dlBtnWrap); document.addEventListener('keydown', keyboardHandler); } } const picLists = rootNode.querySelectorAll('a'); picLists.forEach((link) => { const pixivID = isLinkMatch(link); if (pixivID && !link.querySelector('.dl-sub-pic-btn')) { link.appendChild(createDlBtn(false, pixivID)); } }) } localStorage.pixivDownloader = localStorage.pixivDownloader || JSON.stringify([]); let pixivStorage = new Set(JSON.parse(localStorage.pixivDownloader)); const rootNode = document.body; const config = { attributes: false, childList: true, subtree: true }; const rootObserver = new MutationObserver(observerCallback); rootObserver.observe(rootNode, config); rootNode.addEventListener('click', (e) => { if (/svg|path/i.test(e.target.tagName)) { let findDlBtn = e.target.closest('.dl-btn'); if (findDlBtn) { const pixivID = findDlBtn.getAttribute('PixivID'); e.stopPropagation(); handleDownload(findDlBtn, pixivID); } } }); let zip = new JSZip(); })();