// ==UserScript==
// @name Manga Loader NSFW
// @namespace http://www.fuzetsu.com/MangaLoaderNSFW
// @version 1.0.57
// @description Loads manga chapter into one page in a long strip format, supports switching chapters and works for a variety of sites, minimal script with no dependencies, easy to implement new sites, loads quickly and works on mobile devices through bookmarklet
// @copyright 2016+, fuzetsu
// @noframes
// @grant GM_getValue
// @grant GM_setValue
// @grant GM_deleteValue
// -- NSFW START
// @match *://dynasty-scans.com/chapters/*
// @match *://hentaifr.net/*
// @match *://prismblush.com/comic/*
// @match *://www.hentairules.net/galleries*/picture.php*
// @match *://pururin.us/read/*
// @match *://hitomi.la/reader/*
// @match *://*.doujins.com/*
// @match *://www.8muses.com/comix/picture/*/*/*/*
// @match *://nowshelf.com/watch/*
// @match *://nhentai.net/g/*/*
// @match *://e-hentai.org/s/*/*
// @match *://exhentai.org/s/*/*
// @match *://www.fakku.net/*/*/read*
// @match *://hentaihere.com/m/*/*/*
// @match *://www.hentaihere.com/m/*/*/*
// @match *://*.tsumino.com/Read/View/*
// @match *://www.hentaibox.net/*/*
// @match *://*.hentai-free.org/*
// @match *://*.mangafap.com/image/*
// @match *://*.hentai4manga.com/hentai_manga/*
// @match *://*.heymanga.me/manga/*
// @match *://*.simply-hentai.com/*/page/*
// @match *://*.gameofscanlation.moe/projects/*/*
// @match *://*.luscious.net/c/*/pictures/album/*/id/*
// @match *://*.hentaifox.com/g/*
// @match *://*.hentai2read.com/*/*/*
// @match *://*.hentai.ms/manga/*/*
// -- NSFW END
// -- FOOLSLIDE NSFW START
// @match *://reader.yuriproject.net/read/*
// @match *://ecchi.japanzai.com/read/*
// @match *://h.japanzai.com/read/*
// @match *://reader.japanzai.com/read/*
// @match *://yomanga.co/reader/read/*
// @match *://raws.yomanga.co/read/*
// @match *://hentai.cafe/manga/read/*
// @match *://*.yuri-ism.net/slide/read/*
// -- FOOLSLIDE NSFW END
// @require https://greasyfork.org/scripts/692-manga-loader/code/Manga%20Loader.user.js?29
// ==/UserScript==
/**
Sample Implementation:
{
name: 'something' // name of the implementation
, match: "^https?://domain.com/.*" // the url to react to for manga loading
, img: '#image' // css selector to get the page's manga image
, next: '#next_page' // css selector to get the link to the next page
, numpages: '#page_select' // css selector to get the number of pages. elements like (select, span, etc)
, curpage: '#page_select' // css selector to get the current page. usually the same as numPages if it's a select element
, numchaps: '#chapters' // css selector to get the number of chapters in manga
, curchap: '#chapters' // css selector to get the number of the current chapter
, nextchap: '#next_chap' // css selector to get the link to the next chapter
, prevchap: '#prev_chap' // same as above except for previous
, wait: 3000 // how many ms to wait before auto loading (to wait for elements to load), or a css selector to keep trying until it returns an elem
, pages: function(next_url, current_page_number, callback, extract_function) {
// gets called requesting a certain page number (current_page_number)
// to continue loading execute callback with img to append as first parameter and next url as second parameter
// only really needs to be used on sites that have really unusual ways of loading images or depend on javascript
}
Any of the CSS selectors can be functions instead that return the desired value.
}
*/
// reusable functions to insert in implementations
var reuse = {
encodeChinese: function(xhr) {
xhr.overrideMimeType('text/html;charset=gbk');
},
na: function() {
return 'N/A';
}
};
var exUtil = {
clearAllTimeouts: function() {
var id = window.setTimeout(function() {}, 0);
while (id--) {
window.clearTimeout(id);
}
}
};
var nsfwimp = [{
name: 'geh-and-exh',
match: "^https?://(e-hentai|exhentai).org/s/.*/.*",
img: '.sni > a > img, #img',
next: '.sni > a, #i3 a',
numpages: 'div.sn > div > span:nth-child(2)',
curpage: 'div.sn > div > span:nth-child(1)'
}, {
name: 'fakku',
match: "^http(s)?://www.fakku.net/.*/.*/read",
img: '.current-page',
next: '.current-page',
numpages: '.drop',
curpage: '.drop',
pages: function(url, num, cb, ex) {
var firstNum = url.lastIndexOf('/'),
lastDot = url.lastIndexOf('.');
var c = url.charAt(firstNum);
while (c && !/[0-9]/.test(c)) {
c = url.charAt(++firstNum);
}
var curPage = parseInt(url.slice(firstNum, lastDot), 10);
url = url.slice(0, firstNum) + ('00' + (curPage + 1)).slice(-3) + url.slice(lastDot);
cb(url, url);
}
}, {
name: 'nowshelf',
match: "^https?://nowshelf.com/watch/[0-9]*",
img: '#image',
next: '#image',
numpages: function() {
return parseInt(getEl('#page').textContent.slice(3), 10);
},
curpage: function() {
return parseInt(getEl('#page > input').value, 10);
},
pages: function(url, num, cb, ex) {
cb(page[num], num);
}
}, {
name: 'nhentai',
match: "^https?://nhentai\\.net\\/g\\/[0-9]+/[0-9]+",
img: '#image-container > a img',
next: '#image-container > a',
numpages: '.num-pages',
curpage: '.current',
imgmod: {
altProp: 'data-cfsrc'
},
}, {
name: '8muses',
match: "^http(s)?://www.8muses.com/comix/picture/[^/]+/[^/]+/[^/]+/.+",
img: function(ctx) {
var img = getEl('.photo img.image', ctx);
return img ? img.src : getEl('#imageDir', ctx).value + getEl('#imageName', ctx).value;
},
next: '.photo > a',
curpage: '#page-select-s',
numpages: '#page-select-s'
}, {
name: 'hitomi',
match: "^http(s)?://hitomi.la/reader/[0-9]+.html",
img: '#comicImages > img',
next: '#comicImages > img',
numpages: function() {
return W.images.length;
},
curpage: function() {
return parseInt(W.curPanel);
},
pages: function(url, num, cb, ex) {
cb(W.images[num - 1].path, num);
},
wait: '#comicImages > img'
}, {
name: 'doujin-moe',
_pages: null,
match: "^https?://doujins\.com/.+",
img: 'img.picture',
next: reuse.na,
numpages: function() {
if (!this._pages) {
this._pages = getEls('#gallery djm').map(function(file) {
return file.getAttribute('file').replace('static2.', 'static.');
});
}
return this._pages.length;
},
curpage: function() {
return parseInt(getEl('.counter').textContent.match(/^[0-9]+/)[0]);
},
pages: function(url, num, cb, ex) {
cb(this._pages[num - 1], num);
}
}, {
name: 'pururin',
match: "https?://pururin\\.us/read/.+",
img: 'img.image-next',
next: 'a.image-next',
numpages: function() {
return Object.keys(chapters).length;
},
curpage: 'option:checked',
pages: function(url, num, cb, ex) {
cb(chapters[num].image, num);
}
}, {
name: 'hentai-rules',
match: "^https?://www\\.hentairules\\.net/galleries[0-9]*/picture\\.php.+",
img: '#theMainImage',
next: '#linkNext',
imgmod: {
altProp: 'data-src'
},
numpages: function(cur) {
return parseInt(getEl('.imageNumber').textContent.replace(/([0-9]+)\/([0-9]+)/, cur ? '$1' : '$2'));
},
curpage: function() {
return this.numpages(true);
}
}, {
name: 'ero-senmanga',
match: "^https?://ero\\.senmanga\\.com/[^/]*/[^/]*/[0-9]*",
img: '#picture',
next: '#omv > table > tbody > tr:nth-child(2) > td > a',
numpages: 'select[name=page]',
curpage: 'select[name=page]',
nextchap: function(prev) {
var next = extractInfo('select[name=chapter]', {
type: 'value',
val: (prev ? -1 : 1)
});
if (next) return window.location.href.replace(/\/[^\/]*\/[0-9]+\/?$/, '') + '/' + next + '/1';
},
prevchap: function() {
return this.nextchap(true);
}
}, {
name: 'hentaifr',
match: "^https?://hentaifr\\.net/.+\\.php\\?id=[0-9]+",
img: function(ctx, next) {
var img = getEls('img[width]', ctx).filter(function(img) {
return img.getAttribute('src').indexOf('contenu/doujinshis') !== -1;
})[0];
return next ? img : (img ? img.getAttribute('src') : null);
},
next: function(ctx) {
var img = this.img(ctx, true);
return img ? img.parentNode.getAttribute('href') : null;
},
wait: function() {
return this.img() && this.next();
}
}, {
name: 'prism-blush',
match: "^https?://prismblush.com/comic/.+",
img: '#comic img',
next: '#comic a'
}, {
name: 'hentai-here',
match: "^https?://(www\\.)?hentaihere.com/m/[^/]+/[0-9]+/[0-9]+",
img: '#arf-reader-img',
next: reuse.na,
curpage: function() {
return parseInt(W.rff_thisIndex);
},
numpages: function() {
return W.rff_imageList.length;
},
pages: function(url, num, cb, ex) {
cb(W.imageCDN + W.rff_imageList[num - 1], num);
},
nextchap: function() {
return W.rff_nextChapter;
},
prevchap: function() {
return W.rff_previousChapter;
},
curchap: function() {
var curchap;
getEls('ul.dropdown-menu.text-left li').some(function(li, index) {
if(getEl('a.bg-info', li)) {
curchap = index + 1;
}
});
return curchap;
},
numchaps: 'ul.dropdown-menu.text-left',
wait: 'ul.dropdown-menu.text-left'
}, {
name: 'foolslide',
match: "^https?://(" + [
'reader.yuriproject.net/read/.+',
'ecchi.japanzai.com/read/.+',
'h.japanzai.com/read/.+',
'reader.japanzai.com/read/.+',
'(raws\\.)?yomanga.co(/reader)?/read/.+',
'hentai.cafe/manga/read/.+',
'(www\\.)?yuri-ism\\.net/slide/read/.'
].join('|') + ")",
img: function() {
return W.pages[W.current_page].url;
},
next: reuse.na,
numpages: function() {
return W.pages.length;
},
curpage: function() {
return W.current_page + 1;
},
nextchap: function(prev) {
var desired;
var dropdown = getEls('ul.dropdown')[1] || getEls('ul.uk-nav')[1];
if(!dropdown) return;
getEls('a', dropdown).forEach(function(chap, idx, arr) {
if(location.href.indexOf(chap.href) === 0) desired = arr[idx + (prev ? 1 : -1)];
});
return desired && desired.href;
},
prevchap: function() {
return this.nextchap(true);
},
pages: function(url, num, cb, ex) {
cb(W.pages[num - 1].url, num);
},
wait: function() {
if(W.location.href.indexOf('yomanga') !== -1) {
// select all possible variable names for data structure
var matches = [].slice.call(document.body.innerHTML.match(/\w+\s*=\s*\[\{"id"/ig));
// extract actual variable name from match
var tokens = matches.map(function(m) {
var tok = m.match(/^(\w+)\s*=/i);
return tok && tok[1];
});
// determine the variable that holds the largest data structure
var largest;
var max = 0;
tokens.forEach(function(token) {
var cur = W[token];
if(cur && cur.length > max && [].every.call(cur, function(pg) { return pg.url.endsWith(pg.filename); })) {
max = cur.length;
largest = cur;
}
});
W.pages = largest || 'fail';
}
return W.pages;
}
}, {
name: 'tsumino',
match: '^https?://(www\\.)?tsumino.com/Read/View/.+',
img: '.reader-img',
next: reuse.na,
numpages: function(curpage) {
return W.reader_max_page;
},
curpage: function() {
return W.reader_current_pg;
},
pages: function(url, num, cb) {
var self = this;
if(!self._pages) {
ajax({
method: 'POST',
url: '/Read/Load',
data: 'q=' + W.location.href.match(/View\/(\d+)/)[1],
responseType: 'json',
beforeSend: function(xhr) {
xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
},
onload: function(e) {
var res = e.target.response;
if(!res) return log('failed to load tsumino pages, site has probably been updated, report on forums', 'error');
self._pages = res.reader_page_urls.map(function(page) {
return W.reader_baseobj_url + '?name=' + encodeURIComponent(page);
});
cb(self._pages[num - 1], num);
}
});
} else {
cb(self._pages[num - 1], num);
}
},
wait: '.reader-img'
}, {
name: 'dynasty-scans',
match: "^https?://dynasty-scans.com/chapters/.*",
img: '#image > img',
next: reuse.na,
numpages: function() {
return W.pages.length;
},
curpage: function() {
return parseInt(getEl('#image > div.pages-list > a.page.active').getAttribute('href').slice(1));
},
nextchap: '#next_link',
prevchap: '#prev_link',
pages: function(url, num, cb, ex) {
url = W.pages[num - 1].image;
cb(url, url);
}
}, {
name: 'hentaibox',
match: 'https?://www\\.hentaibox\\.net/hentai-manga/[^/]+/[0-9]+',
img: 'td > center > a > img',
next: 'td > center > a',
numpages: function(cur) {
var sel = getEl('select[name=np2]');
if(sel) {
var info = sel.options[0].textContent.match(/Page ([0-9]+) out of ([0-9]+)/i);
if(info && info.length >= 3) return parseInt(cur ? info[1] : info[2]);
}
},
curpage: function() {
return this.numpages(true);
}
}, {
name: 'hentai-free', // trying to only show button on manga pages, but URL scheme makes it tough.
match: "^http://hentai-free.org/(?!(?:tag|manga-doujinshi|hentai-video|hentai-wall|tags-map)/|.*\\?).+/$",
img: function() {
return W.pages[0];
},
next: reuse.na,
numpages: function() {
return W.pages.length;
},
curpage: function() {
return 1;
},
wait: function() {
var links = getEls('.gallery-item a, a.bwg_lightbox_0');
if (links.length > 0) {
W.pages = links.map(function(el) {
return el.href.replace(/\?.*$/, '');
});
return true;
}
return false;
},
pages: function(url, num, cb, ex) {
cb(W.pages[num - 1], num);
}
}, {
name: 'mangafap',
match: "^https?://mangafap\\.com/image/.+",
img: '#p',
next: '#lbanner + div > a',
numpages: 'select.span2',
curpage: '.pagination li.active a'
}, {
name: 'hentai4manga',
match: "^https?://hentai4manga\\.com/hentai_manga/.+/\\d+/$",
img: '#textboxContent img',
next: '#textboxContent a',
numpages: '#sl',
curpage: '#sl'
}, {
name: 'heymanga',
match: "https?://(www\\.)?heymanga\\.me/manga/[^/]+/[0-9.]+/[0-9]+",
img: '#img-content',
next: function(context) {
var num = this.curpage(context) + 1;
return document.location.href.replace(/([0-9]+)$/, num);
},
numpages: function() {
exUtil.clearAllTimeouts(); // site things mloader is an adblocker because it removes elems and blocks page, this removes the interval that checks for it
return getEl('#page_list').length - 1;
},
curpage: function(context) {
var match = getEls('#page_list > option[selected]', context);
return parseInt(match[match.length - 1].value); // dumb site always marks page 1 as "selected" in addition to actual selected page
},
nextchap: 'section > .row.text-center > p + a, section > .row.text-center .ti-line-dotted + a',
prevchap: 'section > .row.text-center .ti-hand-point-left + a',
wait: '#page_list > option[selected]'
}, {
name: 'simply-hentai',
match: "https?://(.+\\.)?simply-hentai\\.com/.+/page/[0-9]+",
img: function(ctx) {
return getEl('#image span:nth-of-type(2)', ctx).dataset.src;
},
next: '#nextLink',
numpages: function() {
return parseInt(getEl('.inner-content .row .m10b.bold').textContent.match(/Page\s*\d+\s*of\s*(\d+)/)[1]);
},
curpage: function() {
return parseInt(getEl('.inner-content .row .m10b.bold').textContent.match(/Page\s*(\d+)/)[1]);
},
wait: '#image img'
}, {
name: 'gameofscanlation',
match: "https?://gameofscanlation\.moe/projects/.+/.+",
img: '.chapterPages img:first-of-type',
next: function() {
return location.href;
},
numpages: function() {
return getEls('.chapterPages img').length;
},
nextchap: '.comicNextPageUrl',
prevchap: '.comicPreviousPageUrl',
curchap: 'select[name=chapter_list]',
numchaps: 'select[name=chapter_list]',
pages: function(url, num, cb, ex) {
cb(W.pages[num - 1], location.href + '#' + num);
},
wait: function() {
W.pages = getEls('.chapterPages img').map(function (el) {
return el.src;
});
return W.pages && W.pages.length > 0;
}
}, {
name: 'luscious',
match: "https?://luscious\\.net/c/.+?/pictures/album/.+?/id/.+",
img: '.icon-download',
next: '#next',
curpage: function() {
return parseInt(getEl('#pj_page_no').value);
},
numpages: '#pj_no_pictures'
}, {
name: 'hentaifox',
match: "https?://hentaifox\\.com/g/.+",
img: '.gallery_content img.lazy',
next: '.gallery_content a.next_nav',
curpage: function() {
return parseInt(extractInfo('.pag_info', {type: 'text'}));
},
numpages: function() {
return extractInfo('.pag_info') - 2;
}
}, {
name: 'hentai2read',
match: "https?://hentai2read\\.com/.+",
img: '#arf-reader',
next: '#arf-reader',
curpage: function() {
return parseInt(ARFfwk.doReader.data.index);
},
numpages: function() {
return ARFfwk.doReader.data['images'].length;
},
nextchap: function(){
return ARFfwk.doReader.data.nextURL;
},
prevchap: function(){
return ARFfwk.doReader.data.previousURL;
},
pages: function(url, num, cb, ex) {
cb(ARFfwk.doReader.getImageUrl(ARFfwk.doReader.data['images'][num - 1]), num);
}
}, {
name: 'hentai.ms',
match: "http://www.hentai.ms/manga/[^/]+/.+",
img: 'center > a > img',
next: 'center > a:last-child',
curpage: function() {
return parseInt(getEl('center > a').parentNode.textContent.match(/([0-9]+)\//)[1]);
},
numpages: function() {
return parseInt(getEl('center > a').parentNode.textContent.match(/\/([0-9]+)/)[1]);
}
}];
log('loading nsfw implementations...');
MLoaderLoadImps(nsfwimp);