您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
The original fullsize images downloader for the various imageboards
当前为
// ==UserScript== // @name ImageBoard Downloader // @description The original fullsize images downloader for the various 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.1.1 // @grant GM_xmlhttpRequest // @grant GM_setValue // @grant GM_getValue // @grant GM_listValues // @grant GM_deleteValue // @grant GM_info // ==/UserScript== /* 0.1.1 + auto initialize the script [true] + 'Shift+M' - open/close user menu dialog * little changes 0.1.0 + user menu 0.0.13 * refactoring * fix button events 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); var STORAGEKey = 'user-options-storage-key-' + RANDOM; var IMAGEBOARDVersion = 'v' + GM_info.script.version; console.log('start..'); (function(){ function blank(){} var clog = console.log; clog = blank; var userOptions = initOptions(); var methodsObject = initMethodsObject(); var imageBoard = initImageBoard(); newCssClasses(); //------------------------------------------------------------------------------------// //------------------------------------ IMAGE BOARD -----------------------------------// function initImageBoard( d ) { var imgBrdCl = initImageBoardClasses(d), imgBrdDt = initImageBoardDataset(d), siteList = initSiteList(), imgBrdDw = initImageBoardDownloader(d), userMenu = initUserMenu(), 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 userMenu(){return userMenu;}, 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 ) { 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();// =) }, }, 'userMenu': { init: function(doc, type){ clog("userMenu init.."); siteList.init(type); var siteObj = siteList.val(type); siteObj.createDiv( imgBrdId, doc); userMenu.init(imgBrdId, doc); }, }, }, init: function(doc){ for( var key in this.data ) this.data[key].init(doc); }, fix: function(){ this.data.images.fix(); }, }; retVal.init(d); if( userOptions.val('autoRun') ) 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 ---------------------------------// //------------------------------------------------------------------------------------// //------------------------------------- SITE LIST ------------------------------------// function initSiteList() { var retVal = { settings: { 'gelbooru': getGelbooruSettings, 'rule34': getRule34Settings, 'yande.re': getYandereSettings, 'donmai': getDonmaiSettings, 'sankaku': getSankakuSettings, 'behoimi': getBehoimiSettings, }, data: null, 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, prefix){ if( !this.data ) { this.data = {}; for( var key in this.settings ) this.data[key] = getSiteObject( key, this.settings[key], prefix ); } if( !type ) 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 ------------------------------------// //------------------------------------------------------------------------------------// //------------------------------------- SITE OBJECT ----------------------------------// function getSiteObject( siteName, getSiteSettings, prefix ) { return { val: null, name: siteName, regexp: new RegExp( siteName ), get needXHR(){return this.val.needXHR;}, get style(){return this.val.style;}, get settings(){ var s = ( typeof getSiteSettings === 'function' ? getSiteSettings(prefix) : null); Object.defineProperty( this, 'settings', { get: function(){return s;}, enumerable: true, configurable: true, }); return s; }, init: function(){ this.val = this.val || initSiteObject( this.settings ); }, }; } function initSiteObject( siteSettings ) { var retVal = { data: null, get name(){ return this.data.name; }, get prefix(){return this.data.prefix; }, get hostname(){return this.data.hostname; }, get imageHostname(){return this.data.imageHostname;}, get imageDir(){return this.data.imageDir; }, get style(){return this.data.style;}, get postDivInsertionPlace(){return this.data.postDivInsertionPlace;}, get divInsertionPlace(){return this.data.divInsertionPlace;}, get methodsMap(){return this.data.methodsMap;}, get needXHR(){return (typeof this.data.needXHR === 'boolean' ? this.data.needXHR : true);}, init: function( settings ){ this.data = this.data || settings; if( !this.data ) { console.error("[initSiteObject] can't init siteObject, invalid data: ", this.data); return; } for( var i = 0, len = methodsObject.list.length, name, type, map = this.methodsMap || {}; i < len; ++i ) { name = methodsObject.list[i]; type = map[name] || 'booru'; if( typeof methodsObject.method(type, name) === 'function' ) this[name] = methodsObject.method(type, name); } }, }; retVal.init( siteSettings ); return retVal; } //------------------------------------------------------------------------------------// //-------------------------------------- GELBOORU ------------------------------------// function getGelbooruSettings() { return { name: 'gelbooru', hostname: 'gelbooru.com', imageDir: '/images', imageHostname: 'simg3.gelbooru.com', postDivInsertionPlace: '#image', divInsertionPlace: '.contain-push', style: { color: '#fff', width: '180px', background: '#0773fb', backgroundHover: '#fbb307', colorHover: '#fff', }, methodsMap: { isPost: 'gelbooru', getPostId: 'gelbooru', getPostUrl: 'gelbooru', }, needXHR: true, }; } //------------------------------------------------------------------------------------// //--------------------------------------- RULE34 -------------------------------------// function getRule34Settings() { return { name: 'rule34', hostname: 'rule34.xxx', imageDir: '/images', imageHostname: 'img.rule34.xxx', postDivInsertionPlace: '#image', divInsertionPlace: 'div.content', style: { color: '#fff', width: '180px', background: '#84AE83', backgroundHover: '#A4CEA3', colorHover: '#fff', }, methodsMap: { isPost: 'gelbooru', getPostId: 'gelbooru', getPostUrl: 'gelbooru', }, needXHR: true, }; } //------------------------------------------------------------------------------------// //------------------------------------- YANDE.RE -------------------------------------// function getYandereSettings() { return { name: 'yande.re', hostname: 'yande.re', imageDir: 'image', imageHostname: 'files.yande.re', postDivInsertionPlace: '#image', divInsertionPlace: 'div.content', style: { color: '#ee8887', width: '180px', background: '#222', backgroundHover: '#444', colorHover: '#ee8887', }, methodsMap: null, needXHR: true, }; } //------------------------------------------------------------------------------------// //-------------------------------------- DONMAI --------------------------------------// function getDonmaiSettings( prefix ) { var prefixList = ['safebooru.', 'danbooru.', 'sonohara.', 'hijiribe.'], hostnameSuffix = 'donmai.us'; prefix = getHostnamePrefix( hostnameSuffix, prefixList, prefix ); var hostname = prefix + hostnameSuffix; return { name: 'donmai', prefix: prefix, hostname: hostname, imageHostname: hostname, imageDir: 'data', postDivInsertionPlace: '#image', divInsertionPlace: '#posts', style: { color: '#0073ff', width: '180px', background: '#f5f5ff', backgroundHover: '#f5f5ff', colorHover: '#80b9ff', }, methodsMap: { isPost: 'donmai', getPostId: 'donmai', getPostUrl: 'donmai', }, needXHR: true, }; } //------------------------------------------------------------------------------------// //-------------------------------------- SANKAKU -------------------------------------// function getSankakuSettings(prefix) { var prefixList = ['chan.', 'idol.'], hostnameSuffix = 'sankakucomplex.com'; prefix = getHostnamePrefix( hostnameSuffix, prefixList, prefix ); var hostname = prefix + hostnameSuffix, imageHostnamePrefix = (prefix ? prefix[0] + 's.' : ''); return { name: 'sankaku', prefix: prefix, hostname: hostname, imageHostname: imageHostnamePrefix + hostnameSuffix, imageDir: 'data', postDivInsertionPlace: '#image', divInsertionPlace: '#content', style: { color: '#ff761c', width: '180px', background: '', backgroundHover: '', colorHover: '#666', }, methodsMap: null, needXHR: true, }; } //------------------------------------------------------------------------------------// //-------------------------------------- BEHOIMI -------------------------------------// function getBehoimiSettings() { return { name: 'behoimi', hostname: 'behoimi.org', imageHostname: 'behoimi.org', imageDir: 'data', postDivInsertionPlace: '#image', divInsertionPlace: 'div.content', style: { color: '#43333f', width: '180px', background: '', backgroundHover: '', colorHover: '#354d99', }, methodsMap: null, needXHR: true, }; } function getHostnamePrefix( hostnameSuffix, prefixList, prefix ) { var hostname, errorMessage = "[getHostnamePrefix](hostnameSuffix='" + hostnameSuffix + "', prefixList=[" + prefixList.join(',') + "]" + (prefix ? ", prefix='" + prefix + "'" : "") + ") "; if( prefix ) { if( prefixList.indexOf(prefix) == -1 ) { console.error(errorMessage + "\nnot supported prefix"); return ''; } }else{ hostname = window.location.hostname; if( hostname.indexOf(hostnameSuffix) == -1 ) { console.error(errorMessage + "\ninvalid hostname: " + hostname ); return ''; } for( var i = 0, len = prefixList.length; i < len; ++i ) { if( hostname.indexOf(prefixList[i]) == -1 ) continue; prefix = prefixList[i]; break; } if( !prefix ) console.error(errorMessage + "\nno valid prefix for hostname: " + hostname ); } return prefix || ''; } //------------------------------------------------------------------------------------// //----------------------------------- METHODS OBJECT ---------------------------------// function initMethodsObject() { var retVal = { get list(){return this.map.list;}, map: { list: [ 'isPost', 'getPostId',// get post id from href 'getPostUrl',// get post url by postId // method of thumbnail data grabbing 'setImageDataThumb', // methods of image data getting from image post page 'getPostImage', 'setImageOriginalResolution', 'setImageDataSize', 'setImageDataSourceLowres', 'setImageDataSourceHighres', 'setImageDataTags', 'setImageDataName', 'setImageDataExtension', 'setImageDataBytes', 'setImageDataDoc', // create place for buttons insertion 'getPostDivInsertionPlace', 'getDivInsertionPlace', 'keyboardDiv', 'createDiv', ], }, data: { 'booru': { val: null, init: function(){ this.val = this.val || getBooruMethodsObject(); }, }, 'gelbooru': { val: null, init: function(){ this.val = this.val || getGelbooruMethodsObject(); }, }, 'donmai': { val: null, init: function(){ this.val = this.val || getDonmaiMethodsObject(); }, }, }, 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 = { 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 ) { var parent = insertPlace.parentNode; if( parent.tagName === 'A' ) return parent.nextSibling || parent; return insertPlace.nextSibling || insertPlace; } return null; }, getDivInsertionPlace: function(doc){ doc = doc || document; var insertPlace = doc.querySelector(this.divInsertionPlace); if( insertPlace ) return insertPlace.firstChild || insertPlace; return null; }, 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 + '/"]'); 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) ); } }, createDiv: function(id, doc){ doc = doc || document; var div = doc.querySelector('#' + id); clog("div#" + id + ": ", div); 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; }, 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.jpegSource || imgD.source); // tags this.setImageDataTags( imgD, doc ); // name this.setImageDataName( imgD ); // extension this.setImageDataExtension( imgD ); imgD.state = 'ready'; 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; } //-------------------------------- BOORU METHODS OBJECT ------------------------------// //------------------------------------------------------------------------------------// //------------------------------- GELBOORU METHODS OBJECT ----------------------------// function getGelbooruMethodsObject() { var retVal = { 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 window.location.protocol + this.hostname + '/index.php?page=post&s=view&id=' + postId; }, }; return retVal; } //------------------------------------------------------------------------------------// //-------------------------------- 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; } //------------------------------------------------------------------------------------// //-------------------------------------- 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 = '', btn; 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 ) { btn = document.createElement('button'); btn.setAttribute('id', this.id.downloadSwitch); btn.setAttribute('class', this.class.off + ' ' + this.class.btn ); btn.setAttribute('title', 'Press \'Shift+D\' to switch download mode on/off'); btn.innerHTML = 'Donwload Mode'; this.downloadSwitch = div.appendChild( btn ); } this.downloadAll = div.querySelector('#' + this.id.downloadAll); if( !this.downloadAll ) { btn = document.createElement('button'); btn.setAttribute('id', this.id.downloadAll ); btn.setAttribute('class', this.class.btn ); btn.innerHTML = 'Donwload All (0)'; this.downloadAll = div.appendChild( btn ); } if( !this.downloadAll.classList.contains(this.class.active) ) { this.downloadAll.addEventListener('click', handleDownloadAllEvent, false); this.downloadAll.classList.add(this.class.active); } 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 === 'm' ) { handleUserMenuEvent(); } /* else if( str === 'v' ) { handleViewerSwitchEvent();// dummy function } */ } //------------------------------------ DOWNLOADER-2 ----------------------------------// //------------------------------------------------------------------------------------// //------------------------------------ IMAGE VIEWER ----------------------------------// function initImageViewer(d)// TODO { var retVal = { }; return retVal; } function handleViewerSwitchEvent(event){}// TODO //------------------------------------ IMAGE VIEWER ----------------------------------// //------------------------------------------------------------------------------------// //------------------------------------- USER MENU ------------------------------------// function initUserMenu() { var retVal = { data: { 'container-id': 'image-board-user-menu-container-' + RANDOM, 'container-class': 'image-board-user-menu-container', 'title-class': 'image-board-user-menu-title', 'content-class': 'image-board-user-menu-content', 'bottom-class': 'image-board-user-menu-bottom', 'open-class': 'image-board-user-menu-open', 'close-class': 'image-board-user-menu-close', }, get containerId(){return this.data['container-id'];}, get containerClass(){return this.data['container-class'];}, get titleClass(){return this.data['title-class'];}, get contentClass(){return this.data['content-class'];}, get bottomClass(){return this.data['bottom-class'];}, get openClass(){return this.data['open-class'];}, get closeClass(){return this.data['close-class'];}, init: function(id, doc){ doc = doc || document; var div = doc.querySelector('div#' + id), btn; clog("div: ", div, id); if( !div ) { console.error("[initUserMenu] can't find div#" + id); return; } var userMenuId = 'image-board-user-menu-id-' + RANDOM, userMenuActive = 'image-board-user-menu-button-active', userMenuBtn = div.querySelector('#' + userMenuId ); if( !userMenuBtn ) { btn = document.createElement('button'); btn.setAttribute('id', userMenuId ); for( var key in this.data ) btn.setAttribute('data-' + key, this.data[key] ); btn.innerHTML = 'User Menu'; userMenuBtn = div.insertBefore( btn, div.firstChild ); } if( !hasClass( userMenuBtn, userMenuActive ) ) { userMenuBtn.addEventListener('click', handleUserMenuEvent, false); addClass( userMenuBtn, userMenuActive ); } return div; }, }; return retVal; } function handleUserMenuEvent(event) { var dataSet = (this.dataset && this.dataset.containerId ? this.dataset : imageBoard.userMenu ), div = document.querySelector('#' + dataSet.containerId ), body = document.body, contentHtml, bottomHtml; if( !div ) { contentHtml = makeUserMenuContentHtml(); bottomHtml = makeUserMenuBottomHtml(); div = document.createElement('div'); div.setAttribute('id', dataSet.containerId); div.setAttribute('class', dataSet.containerClass); div.innerHTML = '' + '<div class="' + dataSet.titleClass + '">ImageBoard Downloader ' + IMAGEBOARDVersion + '</div>' + '<div class="' + dataSet.contentClass + '">' + contentHtml + '</div>' + '<div class="' + dataSet.bottomClass + '">' + bottomHtml + '</div>'; div = body.appendChild(div); addClass( div, dataSet.closeClass ); activateUserMenu(); } if( hasClass(div, dataSet.openClass) ) closeUserMenu.call(this); else if( hasClass(div, dataSet.closeClass) ) openUserMenu.call(this); } function makeUserMenuContentHtml() { var typeList = ['checkbox', 'number', 'text'], html = '', dt, inp, labl; for( var key in userOptions.data ) { dt = userOptions.data[key]; if( typeList.indexOf(dt.type) != -1 ) { inp = '<input id="image-board-user-menu-' + key + '-val" type="' + dt.type + '" ' + 'style="' + (dt.type!=='checkbox' ? 'text-align: center; width: 50px;': '') + '"/>'; labl = '<label id="image-board-user-menu-' + key + '-caption" ' + 'for="image-board-user-menu-' + key + '-val" style="cursor: pointer;">' + dt.getDesc() + '</label>'; html += '<section class="image-board-user-menu-section">' + (dt.type === 'checkbox' ? inp + labl : labl + inp ) + '</section>'; } } return html; } function makeUserMenuBottomHtml() { this.btnList = this.btnList || { 'close': { html: 'Close', title: '', }, 'reset': { html: 'Reset', title: 'Reset all options to default ones', }, 'remove': { html: 'Remove', title: 'Remove all saved options', }, 'save': { html: 'Save', title: 'Save options', }, }; var key, val, html = ''; for( key in this.btnList ) { val = this.btnList[key]; html += '<button id="image-board-user-menu-' + key + '-button" class="user-menu-button" ' + 'title="' + val.title + '">' + val.html + '</button>'; } return html; } function activateUserMenu() { var doc = document, active = 'image-board-user-menu-button-active', btn, key; var userMenuMethodsObj = { 'save': saveUserMenu, 'remove': removeUserMenu, 'reset': resetUserMenu, 'close': closeUserMenu, }; for( key in userMenuMethodsObj ) { btn = doc.querySelector('#image-board-user-menu-' + key + '-button'); if( btn && !btn.classList.contains(active) ) { btn.addEventListener('click', userMenuMethodsObj[key], false ); btn.classList.add(active); } } } function setUserMenu() { var doc = document, key, dt, valueElm, captionElm; for( key in userOptions.data ) { dt = userOptions.data[key]; valueElm = doc.querySelector('#image-board-user-menu-' + key + '-val'); if( !valueElm ) continue; else if( dt.type === 'checkbox' ) valueElm.checked = dt.val; else if( dt.type === 'number' || dt.type === 'text' ) valueElm.value = dt.val; captionElm = doc.querySelector('#image-board-user-menu-' + key + '-caption'); if( captionElm ) captionElm.innerHTML = dt.getDesc(); } } function saveUserMenu() { var doc = document, key, dt, valueElm; for( key in userOptions.data ) { dt = userOptions.data[key]; valueElm = doc.querySelector('#image-board-user-menu-' + key + '-val'); if( !valueElm ) continue; else if( dt.type === 'checkbox' ) userOptions.val(key, valueElm.checked ); else if( dt.type === 'number' || dt.type === 'text' ) userOptions.val( key, valueElm.value ); } userOptions.saveData(); closeUserMenu(); } function removeUserMenu() { userOptions.removeData(); } function resetUserMenu() { userOptions.setDefs(); userOptions.saveData(); setUserMenu(); } function closeUserMenu() { var dataSet = imageBoard.userMenu.data, userMenu = document.querySelector('#' + dataSet['container-id']); toggleClass( userMenu, dataSet['close-class'], dataSet['open-class'] ); } function openUserMenu() { var dataSet = imageBoard.userMenu.data, userMenu = document.querySelector('#' + dataSet['container-id']); toggleClass( userMenu, dataSet['open-class'], dataSet['close-class'] ); setUserMenu(); } //------------------------------------- USER MENU ------------------------------------// //------------------------------------------------------------------------------------// //------------------------------------ USER OPTIONS ----------------------------------// function initOptions() { function _setDef(){this.val = this.def;} var retVal = { data: { 'maxTagsInName': { val: null, def: 10, type: 'number', setDef: _setDef, getDesc: function(){return 'Maximum tags in file name';}, validator: function( v ){ return v > 3 && v < 100; }, }, 'tagsDelim': { val: null, def: '-', type: 'text', setDef: _setDef, getDesc: function(){return 'Tags delimeter';}, validator: function(v){ v = v.toString(); return v.length > 0 && v.length < 5; }, }, 'addImgBrdName': { val: null, def: true, type: 'checkbox', setDef: _setDef, getDesc: function(){return 'Add imageboard name to file name';}, }, 'downloadJPEG': { val: null, def: false, type: 'checkbox', setDef: _setDef, getDesc: function(){return 'Donwload jpeg instead of png (yande.re option)';}, }, 'autoRun': { val: null, def: true, type: 'checkbox', setDef: _setDef, getDesc: function(){return 'Initialize the script on start';}, }, /* 'addViewer': { val: null, def: true, type: 'checkbox', setDef: _setDef, getDesc: function(){return 'Add image viewer (temp. dummy option)';}, }, */ }, val: function( opt, v ) { if( this.data[opt] ) { if( v !== undefined ) { if( typeof this.data[opt].validator !== 'function' ) this.data[opt].val = v; else if( this.data[opt].validator(v) ) this.data[opt].val = v; } return this.data[opt].val; }else return null; }, saveData: function(){ var storageObj = {}; for( var key in this.data ) storageObj[key] = this.data[key].val; GM_deleteValue( this.storageKey ); GM_setValue( this.storageKey, JSON.stringify(storageObj) ); }, removeData: function(){ GM_deleteValue( this.storageKey ); }, loadData: function(){ var storageObj = JSON.parse(GM_getValue(this.storageKey, '{}')), v; for( var key in this.data ) { v = storageObj[key] if( v !== undefined ) this.val( key, v ); else this.data[key].setDef(); } this.saveData(); }, setDefs: function(){ for( var key in this.data ) this.data[key].setDef(); this.saveData(); }, init: function(){ this.storageKey = this.storageKey || STORAGEKey; this.loadData(); }, }; retVal.init(); 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-user-menu-container-${RANDOM} button, #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-user-menu-container-${RANDOM} button:hover , #image-board-div-${RANDOM} button:hover { background: ${imageBoard.siteList.style().backgroundHover}; color: ${imageBoard.siteList.style().colorHover}; } .image-board-user-menu-title, #image-board-user-menu-container-${RANDOM} { border-top-left-radius: 10px; border-top-right-radius: 10px; } .image-board-user-menu-bottom, #image-board-user-menu-container-${RANDOM} { border-bottom-left-radius: 10px; border-bottom-right-radius: 10px; } #image-board-user-menu-container-${RANDOM} { position: fixed; bottom: 10px; right: 10px; z-index: 999; background-color: #e7e7e7; } div.image-board-user-menu-title { padding-left: 8px; padding-right: 8px; font-weight: bold; line-height: 30px; color: ${imageBoard.siteList.style().color}; background-color: ${imageBoard.siteList.style().background}; } div.image-board-user-menu-content { padding: 5px 10px; background-color: #eeeeee; } /* div.image-board-user-menu-content label, div.image-board-user-menu-content input { vertical-align: middle; } */ div.image-board-user-menu-content label { font-family: verdana, sans-serif; font-weight: initial; font-size: 12px; color: #7d7d7d !important; line-height: 30px; } .image-board-user-menu-content label { padding: 0 3px; } .image-board-user-menu-bottom { text-align: right; background-color: ${imageBoard.siteList.style().background}; padding: 5px 0; } #image-board-user-menu-container-${RANDOM} button { width: initial; margin: 1px 2px; padding: 1px 4px; } .image-board-user-menu-open { display: initial; } .image-board-user-menu-close { display: none; } .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; } })();