// ==UserScript== // @name Manga Loader // @namespace http://www.fuzetsu.com/MangaLoader // @version 1.5.4 // @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 // @copyright 2014+, fuzetsu // @noframes // @grant GM_getValue // @grant GM_setValue // @grant GM_deleteValue // @grant GM_registerMenuCommand // @match http://bato.to/read/* // @match http://mangafox.me/manga/*/*/* // @match http://readms.com/r/*/*/*/* // @match http://mangastream.com/read/*/*/*/* // @match http://g.e-hentai.org/s/*/* // @match http://exhentai.org/s/*/* // @match *://www.fakku.net/*/*/read* // @match http://www.mangareader.net/*/* // @match http://www.mangahere.co/manga/*/* // @match http://www.mangapanda.com/*/* // @match http://mangadeer.com/manga/*/*/*/* // @match http://mangacow.co/*/* // @match http://nowshelf.com/watch/* // @match http://nhentai.net/g/*/* // @match http://centraldemangas.net/online/*/* // @match http://narutomanga.com.br/leitura/online/capitulo/* // @match http://bleachmanga.com.br/leitura/online/capitulo/* // @match http://www.mangatown.com/manga/*/*/* // @match http://mangajoy.com/*/* // @match http://*.dm5.com/m* // @match http://raw.senmanga.com/*/*/* // @match http://www.japscan.com/lecture-en-ligne/* // @match http://www.pecintakomik.com/manga/*/* // @match http://dynasty-scans.com/chapters/* // @match http://www.onemanga.me/*/* // @match http://www.onemanga2.com/*/* // @match http://mangawall.com/manga/*/* // @match http://manga.animea.net/* // @match http://kissmanga.com/Manga/*/* // @match http://view.thespectrum.net/series/* // @match http://manhua.dmzj.com/*/* // @match http://www.8muses.com/picture/*/category/* // @match http://hqbr.com.br/hqs/*/capitulo/*/leitor/0 // @downloadURL none // ==/UserScript== // don't bother running if in a frame if (window.top !== window.self) return; // set to true for manga load without prompt var BM_MODE = false; // short reference to unsafeWindow (or window if unsafeWindow is unavailable e.g. bookmarklet) var W = unsafeWindow || window; var scriptName = 'Manga Loader'; var pageTitle = document.title; var IMAGES = { refresh_large: '//rgeorgeeb-tilebuttongenerator.googlecode.com/hg-history/c35993682c2d50149976fd7a1f302f8c01a88716/asset-studio/src/res/clipart/icons/refresh.svg', refresh_small: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAQAAAC1+jfqAAAABGdBTUEAALGPC/xhBQAAAAFzUkdCAK7OHOkAAAAgY0hSTQAAeiYAAICEAAD6AAAAgOgAAHUwAADqYAAAOpgAABdwnLpRPAAAAAJiS0dEAP+Hj8y/AAAACXBIWXMAAABIAAAASABGyWs+AAABSklEQVQozz3PsUvUYRzH8dfz/d2JNYRNJ0dwgxCXNUUSFFxLQVNB1NR/0FJBIYRrEA02FQ5NkQiCJkFtDQ3hUmBDUuDSJhJBJkd093ueBi8/f8D7836nOUiSrOWxGw6DQIaqBxRXvXBRsaVh119FKISQTFmyZlo2ZslH3zzSkcU+qkg6/m+ocsys987IImRsOeeen0LfedetoGPNlLzvEIp1b3Udd8sXy/ZccsRRr6pe0kDRsG3Rjk218MEJJ7W8CcVQPfquPfVbLQnPMWk6tF0zg1CjGkVn234oqnDBinkkUI9akqamJIXv9pxyWq15kBqKsyYM7IYNn014aMxAU6g0DUy6rdi0HvrmcdmiloGsNtD1Uley4FeaC9kTd/DVaxsOmXFFG8tuyg1FclfffV3dA4s/nnlgKFU9kvDOJ+PaxhU7Vs1aUEvKP5CJao7EBtfGAAAAJXRFWHRkYXRlOmNyZWF0ZQAyMDE1LTAxLTI5VDIyOjExOjI1LTA1OjAwsbFIowAAACV0RVh0ZGF0ZTptb2RpZnkAMjAxNS0wMS0yOVQyMjoxMToyNS0wNTowMMDs8B8AAAAASUVORK5CYII=' }; /** Sample Implementation: { match: "http://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 , 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) , 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. } */ var implementations = [{ // Batoto match: "^http://bato.to/read/.*", img: '#comic_page', next: '#full_image + div > a', numpages: '#page_select', curpage: '#page_select', nextchap: 'select[name=chapter_select]', prevchap: 'select[name=chapter_select]', invchap: true }, { // MangaPanda match: "^http://www.mangapanda.com/.*/[0-9]*", img: '#img', next: '.next a', numpages: '#pageMenu', curpage: '#pageMenu', nextchap: 'td.c5 + td a', prevchap: 'table.c6 tr:last-child td:last-child a' }, { // MangaFox match: "^http://mangafox.me/manga/[^/]*/[^/]*/[^/]*", img: '#image', next: 'a.next_page', numpages: function() { return extractInfo('select.m') - 1; }, curpage: 'select.m', nextchap: '#chnav p + p a', prevchap: '#chnav a' }, { // MangaStream match: "^http://(readms|mangastream).com/(r|read)/[^/]*/[^/]*/[^/]*", img: '#manga-page', next: '.next a', numpages: function() { var lastPage = getEl('.subnav-wrapper .controls .btn-group:last-child ul li:last-child'); return parseInt(lastPage.textContent.match(/[0-9]/g).join(''), 10); }, nextchap: function(prev) { var found; var chapters = [].slice.call(document.querySelectorAll('.controls > div:first-child > .dropdown-menu > li a')); chapters.pop(); for (var i = 0; i < chapters.length; i++) { if (window.location.href.indexOf(chapters[i].href) !== -1) { found = chapters[i + (prev ? 1 : -1)]; if (found) return found.href; } } }, prevchap: function() { return this.nextchap(true); } }, { // MangaReader match: "^http://www.mangareader.net/.*/.*", img: '#img', next: '.next a', numpages: '#pageMenu', curpage: '#pageMenu', nextchap: 'td.c5 + td a', prevchap: 'table.c6 tr:last-child td:last-child a' }, { // MangaTown match: "^http://www.mangatown.com/manga/[^/]*/v[0-9]*/c[0-9]*", img: '#image', next: '#viewer a', numpages: '.page_select select', curpage: '.page_select select', nextchap: '#top_chapter_list', prevchap: '#top_chapter_list', wait: 1000 }, { // MangaCow match: "^http://mangacow\\.co/.*/[0-9]*", img: '.prw > a > img', next: '.prw > a:last-child', numpages: 'select.cbo_wpm_pag', curpage: 'select.cbo_wpm_pag', nextchap: function(prev) { var chapSel = getEl('select.cbo_wpm_chp'); var nextChap = chapSel.options[chapSel.selectedIndex + (prev ? 1 : -1)]; if (nextChap) { return 'http://mangacow.co/' + window.location.pathname.slice(1, window.location.pathname.slice(1).indexOf('/') + 2) + nextChap.value; } }, prevchap: function() { return this.nextchap(true); } }, { // MangaHere match: "^http://www.mangahere.co/manga/.*/.*", img: '#viewer img', next: '#viewer a', numpages: 'select.wid60', curpage: 'select.wid60', nextchap: function(prev) { var chapter = W.chapter_list[W.current_chapter_index + (prev ? -1 : 1)]; return chapter && chapter[1]; }, prevchap: function() { return this.nextchap(true); }, wait: function() { return areDefined(W.current_chapter_index, W.chapter_list); } }, { // MangaDeer match: "^http://mangadeer\\.com/manga/.*", img: '.img-link > img', next: '.page > span:last-child > a', numpages: '#sel_page_1', curpage: '#sel_page_1', nextchap: function(prev) { var ddl = getEl('#sel_book_1'); var index = ddl.selectedIndex + (prev ? -1 : 1); if (index >= ddl.options.length) return; var mangaName = window.location.href.slice(window.location.href.indexOf('manga/') + 6); mangaName = mangaName.slice(0, mangaName.indexOf('/')); return 'http://mangadeer.com/manga/' + mangaName + ddl.options[index].value + '/1'; }, prevchap: function() { return this.nextchap(true); } }, { // Central de Mangas match: "^http://(centraldemangas\\.net|(bleachmanga|narutomanga)\\.com\\.br/leitura)/online/[^/]*/[0-9]*", img: '#manga-page', next: '#manga-page', numpages: '#manga_pages', curpage: '#manga_pages', nextchap: function(prev) { var url = window.location.href, chapters = getEl('#manga_caps'), urlPre = url.slice(0, url.lastIndexOf('/') + 1), newChap = chapters.options[chapters.selectedIndex + (prev ? -1 : 1)]; return newChap ? urlPre + newChap.textContent : null; }, prevchap: function() { return this.nextchap(true); }, pages: function(url, num, cb, ex) { var url = url.slice(0, url.lastIndexOf('-') + 1) + ("0" + num).slice(-2) + url.slice(url.lastIndexOf('.')); cb(url, url); } }, { // Manga Joy match: "^http://mangajoy.com/[^/]*/[0-9]*", img: '.prw img', next: '.nxt', numpages: '.wpm_pag.mng_rdr > div:nth-child(3) > ul > li:nth-child(3) > select', curpage: '.wpm_pag.mng_rdr > div:nth-child(3) > ul > li:nth-child(3) > select', nextchap: function(prev) { var chapter = extractInfo('.wpm_pag.mng_rdr > div:nth-child(3) > ul > li:nth-child(2) > select', { type: 'value', val: prev ? 1 : -1 }); if (chapter) { var urlParts = window.location.href.slice(7).split('/'); while (urlParts.length > 2) urlParts.pop(); return 'http://' + urlParts.join('/') + '/' + chapter; } }, prevchap: function() { return this.nextchap(true); } }, { // GEH/EXH match: "^http://(g.e-hentai|exhentai).org/s/.*/.*", img: '.sni > a > img, #img', next: '.sni > a, #i3 a', numpages: 'body > div > div:nth-child(2) > div > span:nth-child(2)', curpage: 'body > div > div:nth-child(2) > div > span:nth-child(1)' }, { // 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); var url = url.slice(0, firstNum) + ('00' + (curPage + 1)).slice(-3) + url.slice(lastDot); cb(url, url); } }, { // Nowshelf match: "^http://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) { var url = url.slice(0, -7) + ('00' + num).slice(-3) + url.slice(-4); cb(url, url); } }, { // nhentai match: "^http://nhentai\\.net\\/g\\/[0-9]*/[0-9]*", img: '#image-container > a > img', next: '#image-container > a > img', numpages: '.num-pages', curpage: '.current', pages: function(url, num, cb, ex) { url = url.replace(/\/[^\/]*$/, '/') + num; cb(url, url); } }, { // dm5 match: "^http://[^\\.]*\\.dm5\\.com/m[0-9]*", img: '#cp_image', next: '#cp_image', numpages: '#pagelist', curpage: '#pagelist', pages: function(url, num, cb, ex) { var cid = window.location.href.match(/m[0-9]*/g)[2].slice(1), xhr = new XMLHttpRequest(); xhr.open('get', 'imagefun.ashx?cid=' + cid + '&page=' + num); xhr.onload = function() { var images = eval(xhr.responseText); console.log(self.images); cb(images[0], images[0]); }; xhr.send(); } }, { // Senmanga match: "^http://raw\\.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); } }, { // japscan match: "^http://www\\.japscan\\.com/lecture-en-ligne/[^/]*/[0-9]*", img: '#imgscan', next: '#next_link', numpages: '#pages', curpage: '#pages', nextchap: '#next_chapter', prevchap: '#back_chapter' }, { // pecintakomik match: "^http://www\\.pecintakomik\\.com/manga/[^/]*/[^/]*", img: '.picture', next: '.pager a:nth-child(3)', numpages: 'select[name=page]', curpage: 'select[name=page]', nextchap: function(prev) { var chapters = getEl('select[name=chapter]'), chapter = chapters.options[chapters.selectedIndex + (prev ? 1 : -1)]; if (chapter) { return window.location.href.replace(/\/[^\/]*\/[0-9]*\/?$/, '/' + chapter.value); } }, prevchap: function() { return this.nextchap(true); } }, { // dynasty-scans match: "^http://dynasty-scans.com/chapters/.*", img: '#image > img', next: '#image > img', numpages: function() { return W.pages.length; }, curpage: function() { return parseInt(getEl('#image > div.pages-list > a.page.active').textContent); }, nextchap: '#next_link', prevchap: '#prev_link', pages: function(url, num, cb, ex) { url = W.pages[num - 1].image; cb(url, url); } }, { // OneManga match: "^http://www\\.onemanga(2)?\\.(me|com)/[^/]*/[0-9]*", img: 'img.manga-page', next: '.nav_pag > li:nth-child(1) > a', numpages: 'select.cbo_wpm_pag', curpage: 'select.cbo_wpm_pag', nextchap: function(prev) { var curChap = parseInt(extractInfo('select.cbo_wpm_chp', {type: 'value'})), targetChap = curChap + (prev ? -1 : 1); return window.location.href.replace(/\/[0-9]*(\/[0-9]*\/?)?$/, '/' + targetChap); }, prevchap: function() { return this.nextchap(true); } }, { // MangaWall _page: null, match: "^http://mangawall\\.com/manga/[^/]*/[0-9]*", img: 'img.scan', next: function() { if(this._page === null) this._page = W.page; return W.series_url + '/' + W.chapter + '/' + (this._page += 1); }, numpages: '.pageselect', curpage: '.pageselect', nextchap: function(prev) { return W.series_url + '/' + (parseInt(W.chapter.slice(1)) + (prev ? -1 : 1)) + '/1'; }, prevchap: function() { return this.nextchap(true); } }, { // Manga AnimeA _page: null, match: "^http://manga\\.animea.net/.*chapter-[0-9]*-page-[0-9]*.html", img: '#scanmr', next: function() { if(this._page === null) this._page = W.page; return W.series_url + W.chapter + '-page-' + (this._page += 1) + '.html'; }, numpages: '.pageselect', curpage: '.pageselect', nextchap: function(prev) { return W.series_url + 'chapter-' + (parseInt(W.chapter.match(/[0-9]+/)[0]) + (prev ? -1 : 1)) + '-page-1.html'; }, prevchap: function() { return this.nextchap(true); } }, { // Kissmanga match: "^http://kissmanga\\.com/Manga/[^/]*/.+", img: '#imgCurrent', next: '#imgCurrent', numpages: function() { return W.lstImages.length; }, curpage: function() { return W.currImage + 1; }, nextchap: '#selectChapter', prevchap: '#selectChapter', pages: function(url, num, cb, ex) { cb(W.lstImages[num - 1], num); } }, { // the spectrum scans match: "^http://view\\.thespectrum\\.net/series/[^\\.]+\\.html\\?ch=[^&]*&page=[0-9]+", img: '#mainimage', next: '#mainimage', numpages: '.selectpage', curpage: '.selectpage', nextchap: function(prev) { var next = extractInfo('.selectchapter', {type: 'value', val: prev ? -1 : 1}); if(next) { return window.location.href.replace(/ch=.+/, 'ch=' + next + '&page=1'); } }, prevchap: function() { return this.nextchap(true); }, pages: function(url, num, cb, ex) { url = url.replace(/[^\.\/]+\.([a-z]+)$/, ('00' + num).slice(-3) + '.$1'); cb(url, url); } }, { // manhua.dmzj.com match: "http://manhua.dmzj.com/[^/]*/[0-9]+(-[0-9]+)?.shtml", img: '.pic_link', next: '.pic_link', numpages: function() { return W.arr_pages.length; }, curpage: function() { return W.COMIC_PAGE.getPageNumber(); }, pages: function(url, num, cb, ex) { cb(W.img_prefix + W.arr_pages[num - 1], num); } }, { // 8muses match: "http://www.8muses.com/picture/[^/]+/category/.+", img: '.image', next: '.imgLiquidFill > a' }, { // hqbr.com.br match: "http://hqbr.com.br/hqs/[^/]+/capitulo/[0-9]+/leitor/0", img: '#hq-page', next: '#hq-page', numpages: function() { return W.pages.length; }, curpage: function() { return W.paginaAtual + 1; }, nextchap: function(prev) { var chapters = getEls('#chapter-dropdown a'), current = parseInt(W.capituloIndex), chapter = chapters[current + (prev ? -1 : 1)]; return chapter && chapter.href; }, prevchap: function() { return this.nextchap(true); }, pages: function(url, num, cb, ex) { cb(W.pages[num - 1], num); } }]; var log = function(msg, type) { type = type || 'log'; if (type === 'exit') { throw scriptName + ' exit: ' + msg; } else { console[type](scriptName + ' ' + type + ': ', msg); } }; var getEl = function(q, c) { if (!q) return; return (c || document).querySelector(q); }; var getEls = function(q, c) { return [].slice.call((c || document).querySelectorAll(q)); }; var storeGet = function(key) { if (typeof GM_getValue === "undefined") { var value = localStorage.getItem(key); if (value === "true" || value === "false") { return (value === "true") ? true : false; } return value; } return GM_getValue(key); }; var storeSet = function(key, value) { if (typeof GM_setValue === "undefined") { return localStorage.setItem(key, value); } return GM_setValue(key, value); }; var storeDel = function(key) { if (typeof GM_deleteValue === "undefined") { return localStorage.removeItem(key); } return GM_deleteValue(key); }; var areDefined = function() { return [].every.call(arguments, function(arg) { return arg !== undefined && arg !== null; }); }; var extractInfo = function(selector, mod, context) { selector = this[selector] || selector; if (typeof selector === 'function') { return selector.call(this); } var elem = getEl(selector, context), option; mod = mod || {}; if (elem) { switch (elem.nodeName.toLowerCase()) { case 'img': return (mod.altProp && elem.getAttribute(mod.altProp)) || elem.src || elem.getAttribute('src'); case 'a': return elem.href || elem.getAttribute('href'); case 'ul': return elem.children.length; case 'select': switch (mod.type) { case 'index': return elem.options.selectedIndex + 1; case 'value': option = elem.options[elem.options.selectedIndex + (mod.val || 0)] || {}; return option.value; default: return elem.options.length; } break; default: switch (mod.type) { case 'index': return parseInt(elem.textContent); default: return elem.textContent; } } } }; var addStyle = function() { if (!this.MLStyle) { this.MLStyle = document.createElement('style'); this.MLStyle.dataset.name = 'ml-style'; document.head.appendChild(this.MLStyle); } this.MLStyle.textContent += [].join.call(arguments, '\n'); }; var toStyleStr = function(obj, selector) { var stack = [], key; for (key in obj) { if (obj.hasOwnProperty(key)) { stack.push(key + ':' + obj[key]); } } if (selector) { return selector + '{' + stack.join(';') + '}'; } else { return stack.join(';'); } }; var throttle = function(callback, limit) { var wait = false; return function() { if (!wait) { callback(); wait = true; setTimeout(function() { wait = false; }, limit); } }; }; var createButton = function(text, action, styleStr) { var button = document.createElement('button'); button.textContent = text; button.onclick = action; button.setAttribute('style', styleStr || ''); return button; }; var getViewer = function(prevChapter, nextChapter) { var viewerCss = toStyleStr({ 'background-color': 'black', 'font': '0.813em courier', 'text-align': 'center', }, 'body'), imagesCss = toStyleStr({ 'margin-top': '10px', 'margin-bottom': '10px' }, '.ml-images'), imageCss = toStyleStr({ 'max-width': '100%', 'display': 'block', 'margin': '3px auto' }, '.ml-images img'), counterCss = toStyleStr({ 'background-color': '#222', 'color': 'white', 'border-radius': '10px', 'width': '30px', 'margin-left': 'auto', 'margin-right': 'auto', 'margin-top': '-12px', 'padding-left': '5px', 'padding-right': '5px', 'border': '1px solid white', 'z-index': '100', 'position': 'relative' }, '.ml-counter'), navCss = toStyleStr({ 'text-decoration': 'none', 'color': 'white', 'background-color': '#444', 'padding': '3px 10px', 'border-radius': '5px', 'transition': '250ms' }, '.ml-chap-nav a'), navHoverCss = toStyleStr({ 'background-color': '#555' }, '.ml-chap-nav a:hover'), boxCss = toStyleStr({ 'position': 'fixed', 'background-color': '#222', 'color': 'white', 'padding': '7px', 'border-top-left-radius': '5px', 'cursor': 'default' }, '.ml-box'), statsCss = toStyleStr({ 'bottom': '0', 'right': '0', 'opacity': '0.4', 'transition': '250ms' }, '.ml-stats'), statsCollapseCss = toStyleStr({ 'color': 'orange', 'cursor': 'pointer' }, '.ml-stats-collapse'), statsHoverCss = toStyleStr({ 'opacity': '1' }, '.ml-stats:hover'), manualReloadCss = toStyleStr({ 'cursor': 'pointer', 'vertical-align': 'middle' }, '.ml-manual-reload'), floatingMsgCss = toStyleStr({ 'bottom': '50px', 'right': '0', 'border-bottom-left-radius': '5px' }, '.ml-floating-msg'); // clear all styles and scripts var title = document.title; document.head.innerHTML = ''; document.title = title; // navigation var nav = '