// ==UserScript==
// @name ImageBoard Downloader
// @description The original fullsize images downloader for gelbooru, rule34, yande.re, donmai, and 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*
// @version 0.0.8
// @grant GM_xmlhttpRequest
// ==/UserScript==
/*
0.0.8
+ sankaku downloader
- chan.sankakucomplex.com
- idol.sankakucomplex.com
0.0.7
+ 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',
},
},
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 )
{
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 -------------------------------------//
//------------------------------------------------------------------------------------//
//----------------------------------- 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', 'circle', 'medium', 'general', 'faults'],
tagsMap = ['4', '3', '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 'https://' + 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 'https://' + 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;
},
// TODO
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': {
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);
clog("div: ", div, id);
if( !div )
{
console.error("[initImageBoardDownloader] can't init div");
return;
}
this.val = div.querySelector('#' + this.id.downloader );
if( !this.val )
{
this.val = document.createElement('div');
this.val.setAttribute('id', this.id.downloader);
var html = '' +
'<button id="' + this.id.downloadSwitch + '" class="' + this.class.off + '" ' +
'title="Press \'Shift+D\' to switch download mode on/off">Download Mode</button>' +
'<button id="' + this.id.downloadAll + '" class="">Download All (0)</button>' +
'';
this.val.innerHTML = html;
this.val = div.appendChild(this.val);
}
this.downloadAll = this.val.querySelector('#' + this.id.downloadAll);
if( !this.downloadAll.classList.contains(this.class.active) )
{
this.downloadAll.addEventListener('click', handleDownloadAllEvent, false);
this.downloadAll.classList.add(this.class.active);
}
this.downloadSwitch = this.val.querySelector('#' + this.id.downloadSwitch);
if( !this.downloadSwitch.classList.contains(this.class.active) )
{
activateKeyboard();
this.downloadSwitch.addEventListener('click', handleDownloadSwitchEvent, false);
this.downloadSwitch.classList.add(this.class.active);
}
return this.val;
},
downloadAllHtml: function( total, loaded ){
if( !this.val )
return;
this.downloadAll.innerHTML = '' +
'Download All (' + (loaded ? loaded + ' / ': '') + (total ? total : 0) + ')';
},
downloadOn: function(){
toggleClass( this.downloadSwitch, this.class.on, this.class.off );
},
downloadOff: function(){
toggleClass( this.downloadSwitch, this.class.off, this.class.on );
},
isActive: function(){
if( this.downloadSwitch )
return this.downloadSwitch.classList.contains(this.class.on);
return false;
},
};
return retVal;
}
//------------------------------------- DOWNLOADER -----------------------------------//
//------------------------------------------------------------------------------------//
//------------------------------------ DOWNLOADER-2 ----------------------------------//
function handleDownloadEvent(event)
{
if( !imageBoard.imgBrdCl.hasClass( this, 'downloadActive' ) )
return;
event.preventDefault();
var thumb = event.target,
index = imageBoard.imgBrdDt.val( thumb, 'index' ),
imgD = imageBoard.images.list[index];
downloadFile( imgD );
}
function handleDownloadAllEvent(event)
{
var list = imageBoard.images.list;
for( var i = 0, len = list.length, imgD; i < len; ++i )
{
imgD = list[i];
downloadFile( imgD );
}
}
function fileDownloader( name, resource )
{
var a = document.createElement('a'),
body = document.body || document.getElementsByTagName('body')[0];
a.setAttribute('download', name);
a.href = resource;
body.appendChild(a);
a.click();
body.removeChild(a);
}
function downloadFile( imgD )
{
if( imgD.state !== 'ready' || imgD.downloadState === 'downloaded' || imgD.downloadState === 'inProgress' )
return;
imgD.downloadState = 'inProgress';
var hostname = getLocation(imgD.source, 'hostname'), 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];
imageBoardDownloader( imgD, resource );
wndURL.revokeObjectURL( resource );
}
function imageBoardDownloader( imgD, resource )
{
var name = imgD.name + '.' + imgD.extension;
fileDownloader( name, resource );
var thumb = imageBoard.imgBrdDt.query('index', imgD.index + '');
imageBoard.imgBrdCl.addClass( thumb, 'downloaded' );
imgD.downloadState = 'downloaded';
imageBoard.imgBrdDw.done += 1;
}
function handleDownloadSwitchEvent(event)
{
if( imageBoard.imgBrdDw.isActive() )
imageBoard.downloader.deactivate();
else
imageBoard.downloader.activate();
}
function activateKeyboard()
{
window.addEventListener('keydown', handleKeyboardEvent, false);
}
function deactivateKeyboard()
{
window.removeEventListener('keydown', handleKeyboardEvent, false);
}
function handleKeyboardEvent(event)
{
var charCode = event.keyCode || event.which,
str = String.fromCharCode(charCode).toLowerCase();
if( !event.shiftKey || event.ctrlKey || event.altKey )
return;
else if( str === 'd' )
{
handleDownloadSwitchEvent();
}
/*
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-downloader-${RANDOM} {
position: relative;
//float: right;
text-align: right;
top: 2px;
right: 10px;
bottom: 2px;
//display: inline-block;
}
#keyboard-div-${RANDOM} {
position: relative;
display: inline-block;
}
#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};
}
#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;
}
})();