您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
The original fullsize images downloader for gelbooru, rule34, yande.re, donmai, sankakucomplex imageboards
当前为
// ==UserScript== // @name ImageBoard Downloader // @description The original fullsize images downloader for gelbooru, rule34, yande.re, donmai, sankakucomplex imageboards // @namespace https://greasyfork.org/users/155308 // @include https://gelbooru.com* // @include https://rule34.xxx* // @include https://yande.re* // @include *://*.donmai.us* // @include *://*.sankakucomplex.com* // @include *://behoimi.org* // @version 0.0.10 // @grant GM_xmlhttpRequest // ==/UserScript== /* 0.0.10 + behoimi downloader 0.0.9 + 'Shift+I' - (re)initializes imageBoard (usefull for the imageboards with continuous/unlimited paginators) * fix yande.re jpeg image extension 0.0.8 + sankaku downloader - chan.sankakucomplex.com - idol.sankakucomplex.com 0.0.7 + 'Shift+D' - toggle the Download Mode on/off + donmai downloader - safebooru.donmai.us - danbooru.donmai.us - sonohara.donmai.us - hijiribe.donmai.us 0.0.6 + yande.re downloader + download jpeg image on yande.re [false] 0.0.5 + rule34 downloader + add the imageboard name to the image name [true] 0.0.3 + gelbooru downloader + maximum tags in the image name [10] + tags delimeter in the image name ['-'] */ if( window.self !== window.top ) return; var RANDOM = '1681238';//Math.floor(Math.random()*1e6 + 1e6); console.log('start..'); (function(){ function blank(){} var clog = console.log; clog = blank; var userOptions = initOptions(); var methodsObject = initMethodsObject(); userOptions.set({ 'maxTagsInName': 10, 'tagsDelim': '-', 'addImgBrdName': true,// add the imageboard name to the image name 'downloadJPEG': false,// download jpeg image on yande.re when exists }); 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;}, get yandere(){return this.data['yande.re'].val;}, data: { 'gelbooru': { val: null, regexp: /gelbooru/, get needXHR(){return this.val.needXHR;}, style: { color: '#fff', width: '180px', background: '#0773fb', backgroundHover: '#fbb307', colorHover: '#fff', }, init: function(){ this.val = this.val || initGelbooruObject(); }, name: 'gelbooru', }, 'rule34': { val: null, regexp: /rule34/, get needXHR(){return this.val.needXHR;}, style: { color: '#fff', width: '180px', background: '#84AE83', backgroundHover: '#A4CEA3', colorHover: '#fff', }, init: function(){ this.val = this.val || initRule34Object(); }, name: 'rule34', }, 'yande.re': { val: null, regexp: /yande\.re/, get needXHR(){return this.val.needXHR;}, style: { color: '#ee8887', width: '180px', background: '#222', backgroundHover: '#444', colorHover: '#ee8887', }, init: function(){ this.val = this.val || initYandereObject(); }, name: 'yande.re', }, 'donmai': { val: null, regexp: /donmai/, get needXHR(){return this.val.needXHR;}, style: { color: '#0073ff', width: '180px', background: '#f5f5ff', backgroundHover: '#f5f5ff', colorHover: '#80b9ff', }, init: function(){ this.val = this.val || initDonmaiObject(); }, name: 'donmai', }, 'sankaku': { val: null, regexp: /sankaku/, get needXHR(){return this.val.needXHR;}, style: { color: '#ff761c', width: '180px', background: '', backgroundHover: '', colorHover: '#666', }, init: function(){ this.val = this.val || initSankakuObject(); }, name: 'sankaku', }, 'behoimi': { val: null, regexp: /behoimi/, get needXHR(){return this.val.needXHR;}, style: { color: '#43333f', width: '180px', background: '', backgroundHover: '', colorHover: '#354d99',// TODO }, init: function(){ this.val = this.val || initBehoimiObject(); }, name: 'behoimi', }, }, get: function( type, prop1, prop2 ){ var obj = (type === undefined || type === null) ? this.currentObj : this.data[type]; return nodeWalk.call( obj, prop1, prop2 ); }, style: function(type){ return this.get( type, 'style' ); }, val: function(type){ return this.get( type, 'val' ); }, needXHR: function(type){ return this.get( type, 'needXHR' ); }, 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(); clog("siteList.current: ", retVal.current); 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 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 ) { imgD.postId = siteObj.getPostId(); imgD.postUrl = window.location.href; siteObj.setImageDataDoc(imgD); }else siteObj.setImageDataThumb( imgD, img ); imgBrdDt.val( img, 'index', imgD.index); imgBrdCl.addClass( img, 'counted' ); if( imgD.state === 'ready' ) { siteObj.createDiv( imgBrdId, this.doc); imgBrdDw.init(imgBrdId, this.doc); setReadyImage( imgD, imgBrdCl, imgBrdDt, imgBrdDw ); } return imgD; } if( isPost ) { var img = siteObj.getPostImage(); if( img && !imgBrdCl.hasClass( img, 'counted') ) addNewImage( this.list, img, true ); } var thumbs = this.doc.querySelectorAll('img.preview'); if( thumbs && thumbs.length === 0 ) thumbs = this.doc.querySelectorAll('img[itemprop="thumbnailUrl"]');// donmai clog("thumbs.length: ", thumbs.length); 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 ) { /* if( !(i < 0) ) continue; */ idx = empty[i]; imgD = this.list[idx]; imgD.state = 'busy'; this.getImageData(imgD); } }, getImageData: function(imgD) { if( siteList.needXHR(imgD.type) ) { GM_xmlhttpRequest({ url: imgD.postUrl, method: 'GET', context: { 'index': imgD.index, }, onload: xhrImageData, }); }else{ console.log("TODO :D"); var siteObj = siteList.val(imgD.type); //siteObj.setImageDataFull(imgD);// TODO (yande.re, donmai) } }, }, '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) { var imgD = imageBoard.images.list[xhr.context.index]; 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 ); if( imgD.state !== 'ready' ) imgD.state = 'empty'; return; } 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, 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; clog("name: " + imgD.name); if( imageBoard && imageBoard.downloader.isActive() ) { imageBoard.downloader.activate();// TODO: activate only current image } } //----------------------------------- XRH IMAGE DATA ---------------------------------// //------------------------------------------------------------------------------------// //-------------------------------------- GELBOORU ------------------------------------// function initGelbooruObject() { var retVal = { data: { name: 'gelbooru', hostname: 'gelbooru.com', imageDir: '/images', imageUri: 'simg3.gelbooru.com//images/',// TODO //imageSubDomain: 'simg3', postDivInsertionPlace: 'h4 > a*showCommentBox', divInsertionPlace: '.contain-push > div', }, get postDivInsertionPlace(){return this.data.postDivInsertionPlace;}, get divInsertionPlace(){return this.data.divInsertionPlace;}, get name(){ return this.data.name; }, get hostname(){return this.data.hostname; }, get imageDir(){return this.data.imageDir; }, get needXHR(){return true;}, methodsMap: { // href isPost: 'booru', getPostId: 'booru', getPostUrl: 'booru', // thumb setImageDataThumb: 'booru', // post page getPostImage: 'booru', setImageOriginalResolution: 'booru', setImageDataSize: 'booru', setImageDataSourceLowres: 'booru', setImageDataSourceHighres: 'booru', setImageDataTags: 'booru', setImageDataName: 'general', setImageDataExtension: 'general', setImageDataBytes: null, setImageDataDoc: 'general', // button insertion div getPostDivInsertionPlace: 'booru', getDivInsertionPlace: 'booru', keyboardDiv: 'booru', createDiv: 'booru', }, init: function(){ var name, type; for( name in this.methodsMap ) { type = this.methodsMap[name]; if( type ) this[name] = methodsObject.method(type, name); } } }; retVal.init(); return retVal; } //-------------------------------------- GELBOORU ------------------------------------// //------------------------------------------------------------------------------------// //--------------------------------------- RULE34 -------------------------------------// function initRule34Object() { var retVal = { get needXHR(){return true;}, data: { name: 'rule34', hostname: 'rule34.xxx', imageDir: '/images', imageUrl: 'img.rule34.xxx//images/', //imageSubDomain: 'img', postDivInsertionPlace: 'h4 > a', divInsertionPlace: 'div#top', }, get name(){ return this.data.name; }, get hostname(){return this.data.hostname; }, get imageDir(){return this.data.imageDir; }, get postDivInsertionPlace(){return this.data.postDivInsertionPlace;}, get divInsertionPlace(){return this.data.divInsertionPlace;}, methodsMap: { // href isPost: 'booru', getPostId: 'booru', getPostUrl: 'booru', // method of thumbnail data grabbing (thumbSource, ) setImageDataThumb: 'booru', // post page getPostImage: 'booru', setImageOriginalResolution: 'booru', setImageDataSize: 'booru', setImageDataSourceLowres: 'booru', setImageDataSourceHighres: 'booru', setImageDataTags: 'booru', setImageDataName: 'general', setImageDataExtension: 'general', setImageDataBytes: null, setImageDataDoc: 'general', // getPostDivInsertionPlace: 'booru', getDivInsertionPlace: 'booru', keyboardDiv: 'booru', createDiv: 'booru', }, init: function(){ var name, type; for( name in this.methodsMap ) { type = this.methodsMap[name]; if( type ) this[name] = methodsObject.method(type, name); } } }; retVal.init(); return retVal; } //--------------------------------------- RULE34 -------------------------------------// //------------------------------------------------------------------------------------// //------------------------------------- YANDE.RE -------------------------------------// function initYandereObject() { var retVal = { //get needXHR(){return false;}, get needXHR(){return true;}, data: { name: 'yande.re', hostname: 'yande.re', imageDir: 'image', //imageUri: 'files.yande.re/image/', postDivInsertionPlace: 'h4 > a', //postDivInsertionPlace: 'a.js-posts-show-comments-tab', divInsertionPlace: '#post-list-posts', }, get name(){ return this.data.name; }, get hostname(){return this.data.hostname; }, get imageDir(){return this.data.imageDir; }, get postDivInsertionPlace(){return this.data.postDivInsertionPlace;}, get divInsertionPlace(){return this.data.divInsertionPlace;}, methodsMap: { // href methods isPost: 'yande.re',// if getPostId success then true else false getPostId: 'yande.re',// get post id from href getPostUrl: 'yande.re',// get post url by postId // method of thumbnail data grabbing (thumbSource, ) setImageDataThumb: 'booru', // methods of image data getting from image post page getPostImage: 'booru', setImageOriginalResolution: 'booru', setImageDataSize: 'booru', setImageDataSourceLowres: 'booru', setImageDataSourceHighres: 'booru', setImageDataTags: 'booru', setImageDataName: 'general', setImageDataExtension: 'general', setImageDataBytes: null, setImageDataDoc: 'general', // create place for buttons insertion getPostDivInsertionPlace: 'yande.re', getDivInsertionPlace: 'yande.re', keyboardDiv: 'booru', createDiv: 'booru', }, init: function(){ var name, type; for( name in this.methodsMap ) { type = this.methodsMap[name]; if( type ) this[name] = methodsObject.method(type, name); } }, }; retVal.init(); return retVal; } //------------------------------------- YANDE.RE -------------------------------------// //------------------------------------------------------------------------------------// //-------------------------------------- DONMAI --------------------------------------// function initDonmaiObject() { var retVal = { //get needXHR(){return false;}, get needXHR(){return true;}, data: { name: 'donmai', get hostname(){return this.sub_domains[this.sub_index] + '.donmai.us';}, imageDir: 'data', postDivInsertionPlace: '#post-sections > li > a', divInsertionPlace: '#posts', get sub_domains(){return this.sub.domains;}, get sub_index(){return this.sub.index;}, sub: { domains: ['safebooru', 'danbooru', 'sonohara', 'hijiribe'], index: null, init: function( sub_dom ){ var p; if( sub_dom !== undefined ) { p = this.domains.indexOf(sub_dom); if( p != -1 ) return (this.index = p); } var host = window.location.hostname; p = host.indexOf('donmai.us'); if( p == -1 ) return -1; for( var i = 0; i < this.domains.length; ++i ) { p = host.indexOf(this.domains[i]); if( p != -1 ) return (this.index = p); } return -1; }, }, }, get name(){ return this.data.name; }, get hostname(){return this.data.hostname; }, get imageDir(){return this.data.imageDir; }, get postDivInsertionPlace(){return this.data.postDivInsertionPlace;}, get divInsertionPlace(){return this.data.divInsertionPlace;}, methodsMap: { // href methods isPost: 'donmai',// if getPostId success then true else false getPostId: 'donmai',// get post id from href getPostUrl: 'donmai',// get post url by postId // method of thumbnail data grabbing (thumbSource, ) setImageDataThumb: 'booru', // methods of image data getting from image post page getPostImage: 'booru', setImageOriginalResolution: 'booru', setImageDataSize: 'booru', setImageDataSourceLowres: 'booru', setImageDataSourceHighres: 'booru', setImageDataTags: 'booru', setImageDataName: 'general', setImageDataExtension: 'general', setImageDataBytes: null, setImageDataDoc: 'general', // create place for buttons insertion getPostDivInsertionPlace: 'yande.re', getDivInsertionPlace: 'yande.re', keyboardDiv: 'booru', createDiv: 'booru', }, sub_init: function(domain){ return this.data.sub.init(domain); }, init: function(){ this.sub_init(); var name, type; for( name in this.methodsMap ) { type = this.methodsMap[name]; if( type ) this[name] = methodsObject.method(type, name); } }, }; retVal.init(); return retVal; } //-------------------------------------- DONMAI --------------------------------------// //------------------------------------------------------------------------------------// //-------------------------------------- SANKAKU -------------------------------------// function initSankakuObject() { var retVal = { get needXHR(){return true;}, data: { name: 'sankaku', get hostname(){return this.current_sub_domain + '.sankakucomplex.com';}, get imageHostname(){return this.current_sub_domain[0] + 's.sankakucomplex.com';}, imageDir: 'data', postDivInsertionPlace: '#post-content', divInsertionPlace: '#content', get sub_domains(){return this.sub.domains;}, get sub_index(){return this.sub.index;}, get current_sub_domain(){return this.sub_domains[this.sub_index] || 'c';}, sub: { domains: ['chan', 'idol'], index: null, init: function( sub_dom ){ var p; if( sub_dom !== undefined ) { p = this.domains.indexOf(sub_dom); if( p != -1 ) return (this.index = p); } var host = window.location.hostname; p = host.indexOf('sankakucomplex.com'); if( p == -1 ) return (this.index = -1); for( var i = 0; i < this.domains.length; ++i ) { p = host.indexOf(this.domains[i]); if( p != -1 ) return (this.index = p); } return (this.index = -1); }, }, }, get name(){ return this.data.name; }, get hostname(){return this.data.hostname; }, get imageDir(){return this.data.imageDir; }, get postDivInsertionPlace(){return this.data.postDivInsertionPlace;}, get divInsertionPlace(){return this.data.divInsertionPlace;}, methodsMap: { // href methods isPost: 'yande.re',// if getPostId success then true else false getPostId: 'yande.re',// get post id from href getPostUrl: 'yande.re',// get post url by postId // method of thumbnail data grabbing (thumbSource, ) setImageDataThumb: 'booru', // methods of image data getting from image post page getPostImage: 'booru', setImageOriginalResolution: 'booru', setImageDataSize: 'booru', setImageDataSourceLowres: 'booru', setImageDataSourceHighres: 'booru', setImageDataTags: 'booru', setImageDataName: 'general', setImageDataExtension: 'general', setImageDataBytes: null, setImageDataDoc: 'general', // create place for buttons insertion getPostDivInsertionPlace: 'sankaku', getDivInsertionPlace: 'sankaku', keyboardDiv: 'booru', createDiv: 'booru', }, sub_init: function(domain){ return this.data.sub.init(domain); }, init: function(){ this.sub_init(); var name, type; for( name in this.methodsMap ) { type = this.methodsMap[name]; if( type ) this[name] = methodsObject.method(type, name); } }, }; retVal.init(); return retVal; } //-------------------------------------- SANKAKU -------------------------------------// //------------------------------------------------------------------------------------// //-------------------------------------- BEHOIMI -------------------------------------// function initBehoimiObject() { var retVal = { get needXHR(){return true;}, data: { name: 'behoimi', get hostname(){ return 'behoimi.org';}, imageDir: 'data', postDivInsertionPlace: '#image', divInsertionPlace: '#content', }, get name(){ return this.data.name; }, get hostname(){return this.data.hostname; }, get imageDir(){return this.data.imageDir; }, get postDivInsertionPlace(){return this.data.postDivInsertionPlace;}, get divInsertionPlace(){return this.data.divInsertionPlace;}, methodsMap: { // href methods isPost: 'yande.re',// if getPostId success then true else false getPostId: 'yande.re',// get post id from href getPostUrl: 'yande.re',// get post url by postId // method of thumbnail data grabbing (thumbSource, ) setImageDataThumb: 'booru', // methods of image data getting from image post page getPostImage: 'booru', setImageOriginalResolution: 'booru', setImageDataSize: 'booru', setImageDataSourceLowres: 'booru', setImageDataSourceHighres: 'booru', setImageDataTags: 'booru', setImageDataName: 'general', setImageDataExtension: 'general', setImageDataBytes: null, setImageDataDoc: 'general', // create place for buttons insertion getPostDivInsertionPlace: 'sankaku', getDivInsertionPlace: 'sankaku', keyboardDiv: 'booru', createDiv: 'booru', }, sub_init: function(domain){ return this.data.sub.init(domain); }, init: function(){ var name, type; for( name in this.methodsMap ) { type = this.methodsMap[name]; if( type ) this[name] = methodsObject.method(type, name); } }, }; retVal.init(); return retVal; } //-------------------------------------- SANKAKU -------------------------------------// //------------------------------------------------------------------------------------// //----------------------------------- METHODS OBJECT ---------------------------------// function initMethodsObject() { var retVal = { data: { 'booru': { val: null, init: function(){ this.val = this.val || getBooruMethodsObject(); }, }, 'yande.re': { val: null, init: function(){ this.val = this.val || getYandereMethodsObject(); }, }, 'general': { val: null, init: function(){ this.val = this.val || getGeneralMethodsObject(); }, }, 'donmai': { val: null, init: function(){ this.val = this.val || getDonmaiMethodsObject(); }, }, 'sankaku': { val: null, init: function(){ this.val = this.val || getSankakuMethodsObject(); }, }, }, init: function(){ for( var type in this.data ) this.data[type].init(); }, method: function( type, name ){ if( this.data[type] ) { if( name ) return this.data[type].val[name]; return this.data[type].val; } return null; }, }; retVal.init(); return retVal; } //----------------------------------- METHODS OBJECT ---------------------------------// //------------------------------------------------------------------------------------// //-------------------------------- BOORU METHODS OBJECT ------------------------------// function getBooruMethodsObject() { var retVal = { getPostImage: function(doc){ return (doc || document).querySelector('#image'); }, setImageDataThumb: function( imgD, thumb ){ if( thumb && imgD ) { if( thumb.dataset && thumb.dataset.original ) imgD.thumbSource = thumb.dataset.original; else imgD.thumbSource = thumb.src; imgD.postUrl = thumb.parentNode.href; if( thumb.parentNode.id ) imgD.postId = thumb.parentNode.id.slice(1); else imgD.postId = this.getPostId( imgD.postUrl ); } }, setImageDataSourceLowres: function( imgD, doc ){ var img = this.getPostImage(doc); if( img ) imgD.lowresSource = img.src; else return 1; return 0; }, setImageOriginalResolution: function( imgD, img ){ if( !img ) return false; var width, height; width = img.getAttribute('large_width'); height = img.getAttribute('large_height'); if( !width || !height ) { width = img.getAttribute('data-original-width'); height = img.getAttribute('data-original-height'); } if( !width || !height ) { // sankaku width = img.getAttribute('orig_width'); height = img.getAttribute('orig_height'); } if( width && height ) { imgD.width = width; imgD.height = height; return true; } return false; }, setImageDataSize: function( imgD, doc ){ doc = doc || document; var img = this.getPostImage(doc), res; if( this.setImageOriginalResolution ) res = this.setImageOriginalResolution( imgD, img ); if( res ) return; var lis = doc.querySelectorAll('li'), i, li, len = lis.length; for( i = 0; i < len; ++i ) { li = lis[i]; if( li.innerHTML.indexOf('Size:') != -1 ) break; } var match = li.innerHTML.match(/(\d+)x(\d+)/); if( i < len && match ) { imgD.width = match[1]; imgD.height = match[2]; }else console.error("[setImageDataSize] can't find image size (width x height)"); }, setImageDataSourceHighres: function( imgD, doc ){ doc = doc || document; var link = doc.querySelector('li > a[href*="' + (this.imageHostname || this.hostname) + '/' + this.imageDir + '/"]'); if( !link && this.name === 'donmai' )// same origin for donmai link = doc.querySelector('li > a[href*="/' + this.imageDir + '/"]'); //var link = doc.querySelector('li > a[href*="' + this.imageUri + '"]'); if( link ) imgD.source = link.href; else if( imgD.lowresSource ) imgD.source = imgD.lowresSource; else{ console.error("[setImageDataSourceHighres] no image source found"); return 1; } // + yande.re: var jpeg = doc.querySelector('li > a[href*="' + this.hostname + '/jpeg/"]'); if( jpeg ) imgD.jpegSource = jpeg.href; return 0; }, setImageDataTags: function( imgD, doc ){ doc = doc || document; var getTagName = function( tagElm, fl) { if( fl ) return tagElm.querySelectorAll('a')[0].innerText.trim().replace(/\s+/g, '_'); return last(tagElm.querySelectorAll('a')).innerText.trim().replace(/\s+/g, '_'); }; var tagsType = ['character', 'copyright', 'artist', 'model', 'photo_set', 'circle', 'medium', 'general', 'faults'], tagsMap = ['4', '3', '1', '-1', '-1', '-1', '-1', '0', '-1'];// donmai imgD.tags = imgD.tags || []; imgD.tags.length = 0; for( var i = 0, _fl = (this.name === 'sankaku'), tags; i < tagsType.length; ++i ) { tags = doc.querySelectorAll('li.tag-type-' + tagsType[i] ); if( tags.length === 0 ) tags = doc.querySelectorAll('li.category-' + tagsMap[i]);// donmai for( var k = 0, tagEl; tags && k < tags.length; ++k ) imgD.tags.push( getTagName(tags[k], _fl) ); } }, isPost: function( url ){ url = url || window.location.href; if( this.getPostId(url) ) return true; return false; }, getPostId: function( postUrl ){ postUrl = postUrl || window.location.href; var srch = getLocation( postUrl, 'search' ), keys = getSearchObject( srch ); if( keys.s === 'view' && keys.page === 'post' ) return keys.id; else return null; }, getPostUrl: function( postId ){ return 'https://' + this.hostname + '/index.php?page=post&s=view&id=' + postId; }, getPostDivInsertionPlace: function(doc){ doc = doc || document; var insertPlace = doc.querySelector( this.postDivInsertionPlace ); if( insertPlace ) return insertPlace.parentNode; return null; }, getDivInsertionPlace: function(doc){ doc = doc || document; var insertPlace = doc.querySelector( this.divInsertionPlace ); if( insertPlace ) return insertPlace.nextSibling; 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 = this.getPostDivInsertionPlace(doc); else insertPlace = this.getDivInsertionPlace(doc); if( !insertPlace ) return null; div.setAttribute('id', id); div = insertPlace.parentNode.insertBefore( div, insertPlace); if( typeof this.keyboardDiv === 'function' ) this.keyboardDiv( id, doc ); return div; }, keyboardDiv: function( id, doc ){ /* clog("keyboard init.."); doc = doc || document; var kbId = 'keyboard-div-' + RANDOM, div = doc.querySelector('#' + kbId); if( div ) return div; var insPlace = doc.querySelector('#' + id); if( !insPlace ) { console.error("can't create keyboard div"); return; } div = document.createElement('div'); //div = document.createElement('button'); div.setAttribute('id', kbId); div.setAttribute('class', 'keyboar-inactive'); div.innerHTML = '<button class="keyboard-inactive">Keyboard</button>'; //div.innerHTML = 'Keyboard'; div = insPlace.appendChild(div); div.addEventListener('click', handleKeyboardBtnEvent, false); clog("keyboard: ", div); return div; */ }, }; return retVal; } /* function handleKeyboardBtnEvent(event) { var t = event.target; if( t.tagName !== 'BUTTON' ) return; else if( hasClass( t, 'keyboard-inactive') ) { toggleClass( t, 'keyboard-active', 'keyboard-inactive'); activateKeyboard(); } else if( hasClass( t, 'keyboard-active' ) ) { toggleClass( t, 'keyboard-inactive', 'keyboard-active'); deactivateKeyboard(); } } */ //-------------------------------- BOORU METHODS OBJECT ------------------------------// //------------------------------------------------------------------------------------// //------------------------------- GENERAL METHODS OBJECT -----------------------------// function getGeneralMethodsObject() { var retVal = { setImageDataDoc: function( imgD, doc ){ if( !imgD || imgD.state === 'ready' ) return 1; doc = doc || document; // size this.setImageDataSize( imgD, doc ); // lowres var errN = this.setImageDataSourceLowres( imgD, doc ); // highres errN += this.setImageDataSourceHighres( imgD, doc ); if( errN > 1 ) return errN; if( !imgD.lowresSource ) { imgD.lowresSource = imgD.source; //imgD.lowresSource = (imgD.jpegSource || imgD.source); } // tags this.setImageDataTags( imgD, doc ); // name this.setImageDataName( imgD ); // extension this.setImageDataExtension( imgD ); imgD.state = 'ready'; //clog("imgD[" + imgD.index + "]: ", imgD.source, imgD.state); return 0; }, setImageDataName: function( imgD ){ 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; if( userOptions.val('addImgBrdName') ) imgD.name += this.name + tagsDelim; imgD.name += imgD.postId; }, setImageDataExtension: function( imgD ){ var pathname = getLocation( imgD.source, 'pathname' ); imgD.extension = pathname.match(/\.([a-z\d]+)$/)[1]; if( imgD.jpegSource ) { pathname = getLocation( imgD.jpegSource, 'pathname' ); imgD.jpegExtension = pathname.match(/\.([a-z\d]+)$/)[1]; } }, }; return retVal; } //------------------------------- GENERAL METHODS OBJECT -----------------------------// //------------------------------------------------------------------------------------// //------------------------------- YANDERE METHODS OBJECT -----------------------------// function getYandereMethodsObject() { var retVal = { isPost: function(url){ url = url || window.location.pathname || window.location.href; return /\/post\/show\/\d+/.test(url); }, getPostId: function(url){ if( this.isPost(url) ) return getLocation(url, 'pathname').match(/\d+/)[0]; return null; }, getPostUrl: function(postId){ return window.location.protocol + '//' + this.hostname + '/post/show/' + postId; }, getPostDivInsertionPlace: function(doc){ doc = doc || document; var insertPlace = doc.querySelector(this.postDivInsertionPlace); if( insertPlace ) return insertPlace.parentNode.parentNode; return null; }, getDivInsertionPlace: function(doc){ doc = doc || document; var insertPlace = doc.querySelector(this.divInsertionPlace); if( insertPlace ) return insertPlace; return null; }, }; return retVal; } //------------------------------- YANDERE METHODS OBJECT -----------------------------// //------------------------------------------------------------------------------------// //-------------------------------- DONMAI METHODS OBJECT -----------------------------// function getDonmaiMethodsObject() { var retVal = { isPost: function(url){ url = url || window.location.href; return /\/posts\/\d+/.test(url); }, getPostId: function(url){ if( this.isPost(url) ) return getLocation(url, 'pathname').match(/(\/posts\/)?(\d+)?/)[2]; return null; }, getPostUrl: function(postId){ return window.location.protocol + '//' + this.hostname + '/posts/' + postId; }, }; return retVal; } //-------------------------------- DONMAI METHODS OBJECT -----------------------------// //------------------------------------------------------------------------------------// //------------------------------- SANKAKU METHODS OBJECT -----------------------------// function getSankakuMethodsObject() { var retVal = { getPostDivInsertionPlace: function(doc){ doc = doc || document; var insertPlace = doc.querySelector(this.postDivInsertionPlace); if( insertPlace ) return insertPlace.nextSibling; return null; }, getDivInsertionPlace: function(doc){ doc = doc || document; var insertPlace = doc.querySelector(this.divInsertionPlace); if( insertPlace ) return insertPlace.firstChild; return null; }, }; return retVal; } //------------------------------- SANKAKU METHODS OBJECT -----------------------------// //------------------------------------------------------------------------------------// //-------------------------------------- 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': { btn: 'image-board-downloader-button', on: 'image-board-downloader-on', off: 'image-board-downloader-off', active: 'image-board-downloader-active', }, init: function(id, doc){ doc = doc || document; clog("imgBrdDw.init, doc: ", doc); var div = doc.querySelector('div#' + id), html = ''; clog("div: ", div, id); if( !div ) { console.error("[initImageBoardDownloader] can't find div#" + id); return; } this.downloadSwitch = div.querySelector('#' + this.id.downloadSwitch); if( !this.downloadSwitch ) { html += '' + '<button id="' + this.id.downloadSwitch + '" class="' + this.class.off + ' ' + this.class.btn + '" ' + 'title="Press \'Shift+D\' to switch download mode on/off">Download Mode</button>'; } this.downloadAll = div.querySelector('#' + this.id.downloadAll); if( !this.downloadAll ) html += '<button id="' + this.id.downloadAll + '" class="' + this.class.btn + '">Download All (0)</button>'; div.innerHTML += html; this.downloadAll = div.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 = div.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 div; }, downloadAllHtml: function( total, loaded ){ if( !this.downloadAll ) 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'), source; if( userOptions.val('downloadJPEG') && imgD.jpegSource ) source = imgD.jpegSource; else source = imgD.source; if( hostname === window.location.hostname ) { imageBoardDownloader( imgD, source ); return; } GM_xmlhttpRequest({ url: 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], extension = getLocation(this.url, 'pathname').match(/\.([^\.]+)$/); extension = extension ? extension[1] : null; imageBoardDownloader( imgD, resource, extension ); wndURL.revokeObjectURL( resource ); } function imageBoardDownloader( imgD, resource, extension ) { var name = imgD.name + '.' + (extension || 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).toLowerCase(); if( !event.shiftKey || event.ctrlKey || event.altKey ) return; else if( str === 'd' ) { handleDownloadSwitchEvent(); } else if( str === 'i' ) { if( imageBoard ) { imageBoard.init(); imageBoard.fix(); } } /* else if( str === 'v' ) { handleViewerSwitchEvent();// dummy function } else if( 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, }, 'addImgBrdName': { val: null, def: true, setDef: _setDef, }, 'downloadJPEG': { val: null, def: false, 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-viewer-${RANDOM} { position: absolute; text-align: left; top: 2px; left: 0px; box-sizing: content-box; width: 40%; background-color: red; } #keyboard-div-${RANDOM} { position: relative; display: inline-block; } #image-board-div-${RANDOM} { text-align: right; position: relative; } #image-board-div-${RANDOM} button { margin: 3px 10px; color: ${imageBoard.siteList.style().color}; font-weight: bold; width: 180px; border: 0px; padding: 5px; background: ${imageBoard.siteList.style().background}; cursor: pointer; } #image-board-div-${RANDOM} button:hover { background: ${imageBoard.siteList.style().backgroundHover}; color: ${imageBoard.siteList.style().colorHover}; } .keyboard-inactive::after, .image-board-downloader-off::after { content: " [off]"; } .keyboard-active::after, .image-board-downloader-on::after { content: " [on]"; } img.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; } function nodeWalk() { var len = arguments.length, obj = this, i, arg; for( i = 0; i < len; ++i ) { arg = arguments[i]; if( arg === undefined ) return obj; else if( obj[arg] === undefined ) return null; obj = obj[arg]; } return obj; } })();