您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
The original fullsize images downloader for gelbooru
当前为
// ==UserScript== // @name ImageBoard Downloader // @description The original fullsize images downloader for gelbooru // @namespace https://greasyfork.org/users/155308 // @include https://gelbooru.com* // @version 0.0.3 // @grant GM_xmlhttpRequest // ==/UserScript== /* 0.0.3 + gelbooru downloader */ var clog = console.log; var RANDOM = Math.floor(Math.random()*1e6 + 1e6); (function(){ var userOptions = initOptions(); userOptions.set({ 'maxTagsInName': 8, 'tagsDelim': ' ', }); var imageBoard = initImageBoard(); newCssClasses(); //------------------------------------------------------------------------------------// //------------------------------------- SITE LIST ------------------------------------// function initSiteList() { var retVal = { get current(){return this.currentObj.val;}, get gelbooru(){return this.data.gelbooru.val;}, get rule34(){return this.data.rule34.val;}, data: { 'gelbooru': { val: null, regexp: /gelbooru/, init: function(){ this.val = this.val || initGelbooruObject(); }, name: 'gelbooru', }, 'rule34': { val: null, regexp: /rule34/, init: function(){ //this.val = this.val || initRule34Object();// TODO }, name: 'rule34', }, }, val: function(type){ if( type === undefined || type === null ) return this.current; else if( this.data[type] ) return this.data[type].val; else return null; }, init: function(type){ if( type === undefined || type === null ) this.initCurrent(); else if( this.data[type] ) this.data[type].init(); }, getSiteType: function(url){ url = url || window.location.href; for( var key in this.data ) { if( this.data[key].regexp.test(url) ) return key; } console.error("no site object found for this host"); return null; }, initCurrent: function(){ if( !this.currentObj ) { var type = this.getSiteType(); if( !type ) return; this.currentObj = this.data[type]; } this.currentObj.init(); }, }; retVal.init(); return retVal; } //------------------------------------- SITE LIST ------------------------------------// //------------------------------------------------------------------------------------// //------------------------------------ IMAGE BOARD -----------------------------------// function initImageBoard( d ) { var imgBrdCl = initImageBoardClasses(d), imgBrdDt = initImageBoardDataset(d), siteList = initSiteList(), imgBrdDw = initImageBoardDownloader(d), imgBrdId = 'image-board-div-' + RANDOM; var retVal = { get siteList(){return siteList;}, get imgBrdCl(){return imgBrdCl;}, get imgBrdDt(){return imgBrdDt;}, get imgBrdId(){return imgBrdId;}, get imgBrdDw(){return imgBrdDw;}, get images(){return this.data.images;}, get downloader(){return this.data.downloader;}, data: { 'images': { list: null, init: function( doc, type ){ clog("init"); siteList.init(type); imgBrdDt.init(doc); imgBrdCl.init(doc); this.list = this.list || []; this.doc = doc || document; var thumbs = this.doc.querySelectorAll('img.preview'), siteObj = siteList.val(type), isPost = siteObj.isPost(), imgD; function addNewImage( list, img, isPost ) { list.push({}); var imgD = last(list); imgD.state = 'empty'; imgD.index = list.length - 1; imgD.type = siteObj.name; if( isPost ) siteObj.setImageDataDoc(imgD); else siteObj.setImageDataThumb( imgD, thumb ); imgBrdDt.val( img, 'index', imgD.index); imgBrdCl.addClass( img, 'counted' ); return imgD; } if( isPost ) { var img = siteObj.getPostImage(); if( img && !imgBrdCl.hasClass( img, 'counted') ) { imgD = addNewImage( this.list, img, true ); if( imgD.state === 'ready' ) { siteObj.createDiv( imgBrdId, this.doc); imgBrdDw.init(imgBrdId, this.doc); setReadyImage( imgD, imgBrdCl, imgBrdDt, imgBrdDw ); } } } for( var i = 0, len = thumbs.length, thumb; i < len; ++i ) { thumb = thumbs[i]; if( imgBrdCl.hasClass( thumb, 'counted' ) ) continue; addNewImage( this.list, thumb ); } }, getEmpty: function(){ var empty = []; for( var i = 0; i < this.list.length; ++i ) { if( this.list[i].state === 'empty' ) empty.push(i); } return empty; }, fix: function() { var empty = this.getEmpty(); for( var i = 0, idx, imgD; i < empty.length; ++i ) { idx = empty[i]; imgD = this.list[idx]; imgD.state = 'busy'; this.getImageData(imgD); } }, getImageData: function(imgD) { GM_xmlhttpRequest({ url: imgD.postUrl, method: 'GET', context: { 'index': imgD.index, }, onload: xhrImageData, }); }, }, 'downloader': { init: function(doc, type){ clog("downloader init.."); siteList.init(type); var siteObj = siteList.val(type); siteObj.createDiv( imgBrdId, doc); imgBrdDw.init(imgBrdId, doc); }, isActive: function(){ return imgBrdDw && imgBrdDw.isActive() || false; }, activate: function(doc){ clog("activate"); doc = doc || document; imgBrdCl.init(doc); var thumbs = imgBrdCl.queryAll('counted'); for( var i = 0, len = thumbs.length, thumb, a; i < len; ++i ) { thumb = thumbs[i]; a = thumb.parentNode; if( !imgBrdCl.hasClass(thumb, 'ready' ) ) continue; else if( !imgBrdCl.hasClass( a, 'downloadAttach' ) ) { a.addEventListener('click', handleDownloadEvent, false); imgBrdCl.addClass( a, 'downloadAttach' ); } imgBrdCl.addClass( a, 'downloadActive' ); } imgBrdDw.downloadOn(); }, deactivate: function(doc){ clog("deactivate"); doc = doc || document; imgBrdCl.init(doc); var activ = imgBrdCl.queryAll('downloadActive'); clog("active.length: ", activ.length); for( var i = 0, len = activ.length; i < len; ++i ) imgBrdCl.removeClass( activ[i], 'downloadActive' ); imgBrdDw.downloadOff(); }, downloadAll: function(){ imgBrdDw.downloadAll.click();// =) }, }, }, init: function(doc){ for( var key in this.data ) this.data[key].init(doc); }, fix: function(){ this.data.images.fix(); }, }; retVal.init(d); retVal.fix(); return retVal; } //------------------------------------ IMAGE BOARD -----------------------------------// //------------------------------------------------------------------------------------// //----------------------------------- XRH IMAGE DATA ---------------------------------// function xhrImageData(xhr) { if( xhr.status !== 200 ) { console.error("xhr.status: ", xhr.status, xhr.statusText ); console.error("index: ", xhr.context ? xhr.context.index : null); console.error("postUrl: ", this.url ); return; } var imgD = imageBoard.images.list[xhr.context.index]; if( !imgD || imgD.state === 'ready' ) { console.error("invalid context: ", imgD); return; } var siteObj = imageBoard.siteList.val(imgD.type); if( !siteObj ) { console.error("invalid site type: ", imgD.type); return; } var doc = document.implementation.createHTMLDocument(""); doc.documentElement.innerHTML = xhr.response; siteObj.setImageDataDoc(imgD, doc); clog("xhrImageData[" + imgD.index + "].state : " + imgD.state); if( imgD.state === 'ready' ) { setReadyImage( imgD ); } } //function setReadyImage( imgD ) function setReadyImage( imgD, imgBrdCl, imgBrdDt, imgBrdDw ) { if( (!imgBrdCl || !imgBrdDt || !imgBrdDw) && imageBoard ) { imgBrdCl = imageBoard.imgBrdCl; imgBrdDt = imageBoard.imgBrdDt; imgBrdDw = imageBoard.imgBrdDw; } var thumb = imgBrdDt.query('index', imgD.index + ''); imgBrdCl.addClass( thumb, 'ready' ); imgBrdDt.val( thumb, 'source', imgD.source ); if( imgD.bytes ) imgBrdDt.val( thumb, 'bytes', imgD.bytes ); imgBrdDw.total += 1; if( imageBoard && imageBoard.downloader.isActive() ) { imageBoard.downloader.activate(); } } //----------------------------------- XRH IMAGE DATA ---------------------------------// //------------------------------------------------------------------------------------// //-------------------------------------- GELBOORU ------------------------------------// function initGelbooruObject() { var retVal = { name: 'gelbooru', setImageDataThumb: function( imgD, thumb ){ if( thumb && imgD ) { imgD.thumbSource = thumb.dataset.original; imgD.postUrl = thumb.parentNode.href; imgD.postId = thumb.parentNode.id.slice(1); } }, getPostImage: function(doc){ doc = doc || document; return doc.querySelector('#image'); }, setImageDataDoc: function( imgD, doc ){ if( !imgD || imgD.state === 'ready' ) return 1; doc = doc || document; var errN = 0, img = doc.querySelector('#image'); if( img ) { // lowresSource imgD.lowresSource = img.src; // size imgD.width = img.dataset.originalWidth; imgD.height = img.dataset.originalHeight; }else{ console.error("[setImageDataDoc] no image found"); ++errN; } // highresSource var link = doc.querySelector('#tag-list a[href*="gelbooru.com//images/"]'); if( link ) imgD.source = link.href; else if( imgD.lowresSource ) imgD.source = imgD.lowresSource; else{ console.error("[setImageDataDoc] no image source found"); return 2; } if( !imgD.lowresSource ) imgD.lowresSource = imgD.source; // tags this.setImageDataTags( imgD, doc ); // name var tagsLen = imgD.tags.length, uLen = userOptions.val('maxTagsInName'), tagsDelim = userOptions.val('tagsDelim'); imgD.name = ''; for( var i = 0; i < tagsLen && i < uLen; ++i ) imgD.name += imgD.tags[i] + tagsDelim; imgD.name += imgD.postId; // extension var pathname = getLocation( imgD.source, 'pathname' ); imgD.extension = pathname.match(/\.([a-z\d]+)$/)[1]; imgD.state = 'ready'; //clog("imgD[" + imgD.index + "]: ", imgD.source, imgD.state); return 0; }, setImageDataTags: function( imgD, doc ){ doc = doc || document; this.tagTypes = this.tagTypes || ['character', 'copyright', 'artist', 'general']; var obj = {}; imgD.tags = imgD.tags || []; imgD.tags.length = 0; for( var i = 0, tagType, objTag; i < this.tagTypes.length; ++i ) { tagType = this.tagTypes[i]; obj[tagType] = this._getTags(doc, tagType); objTag = obj[tagType]; if( objTag && objTag.length > 0 ) { for( var k = 0; k < objTag.length; ++k ) imgD.tags.push( this._getTagName(objTag[k]) ); } } }, isPost: function( url ){ return this._isPostView(url); }, getPostId: function( postUrl ){ return this._getPostViewId(postUrl); }, getPostUrl: function( postId ){ return 'https://gelbooru.com/index.php?page=post&s=view&id=' + postId;}, _getTagName: function( tagElm ){ return tagElm.querySelectorAll('a')[1].innerText.trim().replace(/\s+/g, '_'); }, _getTags: function( doc, type ){ doc = doc || document; return doc.querySelectorAll('li.tag-type-' + type); }, _isPostViewKeys: function( keys ){ return keys['s'] === 'view' && keys['page'] === 'post'; }, _isPostListKeys: function( keys ){ return keys['s'] === 'list' && keys['page'] === 'post'; }, _isPostView: function( url ){ var srch = getLocation( url, 'search' ), keys = getSearchObject( srch ); return this._isPostViewKeys( keys ); }, _getPostViewId: function( postUrl ){ var srch = getLocation( postUrl, 'search' ), keys = getSearchObject( srch ); if( this._isPostViewKeys( keys ) ) return keys['id']; return null; }, createDiv: function(id, doc){ doc = doc || document; var div = doc.querySelector('#' + id); if( div ) return div; div = document.createElement('div'); var insertPlace; if( this.isPost() ) { insertPlace = doc.querySelector('h4 > a#showCommentBox'); if( insertPlace ) insertPlace = insertPlace.parentNode; else return null; }else{ insertPlace = doc.querySelector('div.thumbnail-preview'); if( insertPlace ) { insertPlace = insertPlace.parentNode; insertPlace = insertPlace.children[1]; }else return null; } div.setAttribute('id', id); return insertPlace.parentNode.insertBefore( div, insertPlace); }, }; return retVal; } //-------------------------------------- GELBOORU ------------------------------------// //------------------------------------------------------------------------------------// //-------------------------------------- DATASET -------------------------------------// function initImageBoardDataset(d) { var retVal = { data: { source: 'data-image-board-source', index: 'data-image-board-index', extension: 'data-image-board-extension', bytes: 'data-image-board-bytes', }, val: function(elm, propName, v){ if( this.data[propName] ) { if( v !== undefined ) elm.setAttribute(this.data[propName], v); else return elm.getAttribute(this.data[propName]); } return null; }, init: function(doc){ this.doc = doc || document; }, getSelector: function(propName, v){ var sel = this.data[propName]; if( sel ) { if( v !== undefined ) { var pos = v.indexOf('='); if( pos > -1 && pos < 2 ) sel += v; else sel += '="' + v + '"'; } return '[' + sel + ']'; } return null; }, query: function(propName, v){ var sel = this.getSelector(propName, v); if( sel ) return this.doc.querySelector(sel); return null; }, queryAll: function(propName, v){ var sel = this.getSelector(propName, v); if( sel ) return this.doc.querySelectorAll(sel); return null; }, }; retVal.init(d); return retVal; } //-------------------------------------- DATASET -------------------------------------// //------------------------------------------------------------------------------------// //-------------------------------------- CLASSES -------------------------------------// function initImageBoardClasses(d) { var retVal = { get counted(){return this.data.counted;}, get viewActive(){return this.data.viewActive;}, get viewAttach(){return this.data.viewAttach;}, get ready(){return this.data.ready;}, get downloaded(){return this.data.downloaded;}, get downloadAttach(){ return this.data.downloadAttach;}, get downloadActive(){ return this.data.downloadActive;}, data: { counted: 'image-board-counted', viewAttach: 'image-board-attach-view-event', viewActive: 'image-board-active-for-view', ready: 'image-board-has-original-source', downloaded: 'image-board-downloaded-original', downloadAttach: 'image-board-attach-download-event', downloadActive: 'image-board-active-for-download', }, hasClass: function(elm, propName){ if( this.data[propName] ) return hasClass(elm, this.data[propName]); return false; }, addClass: function(elm, propName){ if( this.data[propName] ) addClass(elm, this.data[propName]); }, removeClass: function(elm, propName){ if( this.data[propName] ) removeClass(elm, this.data[propName]); }, toggleClass: function(elm, newPropName, oldPropName){ if( oldPropName && !this.data[oldPropName] ) return; else if( !newPropName || !this.data[newPropName] ) return; toggleClass( elm, this.data[newPropName], this.data[oldPropName] ); }, queryAll: function(propName){ if( this.data[propName] ) return this.doc.querySelectorAll('.' + this.data[propName]); return null; }, init: function(doc){this.doc = doc || document;}, }; retVal.init(d); return retVal; } //-------------------------------------- CLASSES -------------------------------------// //------------------------------------------------------------------------------------// //------------------------------------- DOWNLOADER -----------------------------------// function initImageBoardDownloader(d) { var iter = { total : 0, done: 0, }; var retVal = { get total(){return iter.total;}, set total(n){ iter.total = n; this.downloadAllHtml(iter.total, iter.done); }, get done(){return iter.done;}, set done(n){ iter.done = n; this.downloadAllHtml(iter.total, iter.done); }, id: { downloader: 'image-board-downloader-' + RANDOM, downloadAll: 'image-board-download-all-' + RANDOM, downloadSwitch: 'image-board-download-switch-' + RANDOM, }, 'class': { on: 'image-board-downloader-on', off: 'image-board-downloader-off', active: 'image-board-downloader-active', }, init: function(id, doc){ doc = doc || document; var div = doc.querySelector('div#' + id); if( !div ) { console.error("[initImageBoardDownloader] can't init div"); return; } this.val = div.querySelector('#' + this.id.downloader ); if( !this.val ) { this.val = document.createElement('div'); this.val.setAttribute('id', this.id.downloader); var html = '' + '<button id="' + this.id.downloadSwitch + '" class="' + this.class.off + '">Download Mode</button>' + '<button id="' + this.id.downloadAll + '" class="">Download All (0)</button>' + ''; this.val.innerHTML = html; this.val = div.appendChild(this.val); } this.downloadAll = this.val.querySelector('#' + this.id.downloadAll); if( !this.downloadAll.classList.contains(this.class.active) ) { this.downloadAll.addEventListener('click', handleDownloadAllEvent, false); this.downloadAll.classList.add(this.class.active); } this.downloadSwitch = this.val.querySelector('#' + this.id.downloadSwitch); if( !this.downloadSwitch.classList.contains(this.class.active) ) { activateKeyboard(); this.downloadSwitch.addEventListener('click', handleDownloadSwitchEvent, false); this.downloadSwitch.classList.add(this.class.active); } return this.val; }, downloadAllHtml: function( total, loaded ){ if( !this.val ) return; this.downloadAll.innerHTML = '' + 'Download All (' + (loaded ? loaded + ' / ': '') + (total ? total : 0) + ')'; }, downloadOn: function(){ toggleClass( this.downloadSwitch, this.class.on, this.class.off ); }, downloadOff: function(){ toggleClass( this.downloadSwitch, this.class.off, this.class.on ); }, isActive: function(){ if( this.downloadSwitch ) return this.downloadSwitch.classList.contains(this.class.on); return false; }, }; return retVal; } //------------------------------------- DOWNLOADER -----------------------------------// //------------------------------------------------------------------------------------// //------------------------------------ DOWNLOADER-2 ----------------------------------// function handleDownloadEvent(event) { if( !imageBoard.imgBrdCl.hasClass( this, 'downloadActive' ) ) return; event.preventDefault(); var thumb = event.target, index = imageBoard.imgBrdDt.val( thumb, 'index' ), imgD = imageBoard.images.list[index]; downloadFile( imgD ); } function handleDownloadAllEvent(event) { var list = imageBoard.images.list; for( var i = 0, len = list.length, imgD; i < len; ++i ) { imgD = list[i]; downloadFile( imgD ); } } function fileDownloader( name, resource ) { var a = document.createElement('a'), body = document.body || document.getElementsByTagName('body')[0]; a.setAttribute('download', name); a.href = resource; body.appendChild(a); a.click(); body.removeChild(a); } function downloadFile( imgD ) { if( imgD.state !== 'ready' || imgD.downloadState === 'downloaded' || imgD.downloadState === 'inProgress' ) return; imgD.downloadState = 'inProgress'; var hostname = getLocation(imgD.source, 'hostname'); if( hostname === window.location.hostname ) { imageBoardDownloader( imgD, imgD.source ); return; } GM_xmlhttpRequest({ url: imgD.source, method: 'GET', context: { 'index': imgD.index, }, responseType: 'blob', onload: blibBlobDownloader, }); } function blibBlobDownloader( xhr ) { if( xhr.status !== 200 ) { console.error("xhr.status: ", xhr.status, xhr.statusText); console.erorr("url: " + this.url); return; } var wndURL = window.webkitURL || window.URL, resource = wndURL.createObjectURL(xhr.response), imgD = imageBoard.images.list[xhr.context.index]; imageBoardDownloader( imgD, resource ); wndURL.revokeObjectURL( resource ); } function imageBoardDownloader( imgD, resource ) { var name = imgD.name + '.' + imgD.extension; fileDownloader( name, resource ); var thumb = imageBoard.imgBrdDt.query('index', imgD.index + ''); imageBoard.imgBrdCl.addClass( thumb, 'downloaded' ); imgD.downloadState = 'downloaded'; imageBoard.imgBrdDw.done += 1; } function handleDownloadSwitchEvent(event) { if( imageBoard.imgBrdDw.isActive() ) imageBoard.downloader.deactivate(); else imageBoard.downloader.activate(); } function activateKeyboard() { window.addEventListener('keydown', handleKeyboardEvent, false); } function deactivateKeyboard() { window.removeEventListener('keydown', handleKeyboardEvent, false); } function handleKeyboardEvent(event) { var charCode = event.keyCode || event.which, str = String.fromCharCode(charCode); if( str === 'd' || str === 'D' ) { handleDownloadSwitchEvent(); } /* else if( str === 'v' || str === 'V' ) { handleViewerSwitchEvent();// dummy function } else if( str === 's' || str === 'S' ) { if( imageBoard ) { imageBoard.init(); imageBoard.fix(); } } */ } //------------------------------------ DOWNLOADER-2 ----------------------------------// //------------------------------------------------------------------------------------// function handleViewerSwitchEvent(event){}// TODO //------------------------------------------------------------------------------------// //------------------------------------ USER OPTIONS ----------------------------------// function initOptions() { function _setDef(){this.val = this.def;} var retVal = { data: { 'maxTagsInName': { val: null, def: 10, setDef: _setDef, }, 'tagsDelim': { val: null, def: '-', setDef: _setDef, }, /* 'autoRunSource': { val: null, def: true, setDef: _setDef, }, 'autoRunDownloadOn': { val: null, def: false, setDef: _setDef, }, */ }, val: function( opt, v ) { if( this.data[opt] ) { if( v === undefined ) return this.data[opt].val; else this.data[opt].val = v; }else return null; }, setDefs: function(){ for( var key in this.data ) this.data[key].setDef(); }, set: function(obj){ for( var key in obj ) this.val(key, obj[key]); }, }; retVal.setDefs(); return retVal; } //------------------------------------ USER OPTIONS ----------------------------------// //------------------------------------------------------------------------------------// function newCssClasses() { addCssClass(` #image-board-downloader-${RANDOM} { position: relative; text-align: right; top: 2px; right: 10px; bottom: 2px; } #image-board-downloader-${RANDOM} > button { margin: 3px 10px; color: #fff; font-weight: bold; width: 180px; border: 0px; padding: 5px; background: #0773fb; } #image-board-downloader-${RANDOM} > button:hover { background: #fbb307; } .image-board-downloader-off::after { content: " [off]"; } .image-board-downloader-on::after { content: " [on]"; } .image-board-has-original-source { border-bottom: 5px solid green !important; } .image-board-active-for-download { cursor: default; } `); } function addCssClass(cssClass) { var style = document.createElement('style'), head = document.querySelector('head'); style.type = 'text/css'; if( style.styleSheets ) style.styleSheets.cssText = cssClass; else style.appendChild(document.createTextNode(cssClass)); return head.appendChild(style); } function addClass( elm, name ) { if( elm && name ) elm.classList.add(name); } function removeClass( elm, name ) { if( elm && name ) elm.classList.remove(name); } function hasClass( elm, name ) { if( elm && name ) return elm.classList.contains(name); return false; } function toggleClass( elm, newClass, oldClass ) { if( !elm || !newClass ) return; if( oldClass ) { elm.classList.remove(oldClass); elm.classList.add(newClass); } else if( elm.classList.contains(newClass) ) elm.classList.remove(newClass); else elm.classList.add(newClass); } function getLocation( url, attr ) { if( !attr ) return null; url = url || window.location.href; this.link = this.link || document.createElement('a'); this.link.href = url; return this.link[attr]; } function getSearchObject( search ) { var keys = {} if( search ) { search = search.replace(/^\?/, ''); search.split('&').forEach(function(item){ item = item.split('='); keys[item[0]] = item[1]; }); } return keys; } function last( arr ) { if( arr && arr.length > 0 ) return arr[arr.length-1]; return null; } })();