├── README.md └── manga-loader.user.js /README.md: -------------------------------------------------------------------------------- 1 | # [Manga Loader](https://greasyfork.org/en/scripts/692-manga-loader) 2 | 3 | ## Contributing 4 | I have decided to put this script on github in the hopes people start submitting their implementations for new sites to speed up the development. 5 | 6 | A while back I put together a video to show off the basic procedure for creating an implementation for Manga Loader, if you are interested in adding a new site yourself, but do not know how, check out [this video.](https://www.youtube.com/watch?v=zgaogJCL8xQ) 7 | 8 | I do not have much time to work on this nowadays, but at the same time I do not want the project to be left behind, so let's see how this goes! 9 | 10 | ## About 11 | 12 | Simple. Lightweight. Fast. 13 | 14 | Manga Loader allows loading of entire chapters from manga sites in a longstrip format (all images on one page). 15 | 16 | Manga Loader tries to remove the bloat many other similar scripts have. 17 | 18 | When visiting a page supporting Manga Loader, a button will appear with the text "Load Manga". 19 | Click the button or press `Ctrl+,` on the keyboard to load the manga into the current page. 20 | 21 | To automatically load the userscript whenever possible, enable autoload from the settings menu. 22 | 23 | The script loads 10 images at a time (by default) and loads when scrolling. 24 | To load all the images in the chapter at once, change the load num setting to the word "all". 25 | To load a specific number of pages, change the load num setting to the preferred number. 26 | Loading less pages at once is useful if experiencing broken images or browser slowdowns. 27 | 28 | Manually reload a misbehaving image (loading unusually slow, or stuck loading half way) by clicking the small refresh icon in the bottom right info/toolbar, then click on the image. 29 | 30 | **Bookmarklet**: To use the script as a bookmark, copy the following code and create a bookmark. Make sure the `javascript:` part of the code is still there after pasting, otherwise the bookmark function will not work. **This method also works for using the script on mobile devices such as phones and tablets**. 31 | `javascript:(function(){BM_MODE=true;document.body.appendChild(document.createElement('script')).src='https://greasyfork.org/scripts/692-manga-loader/code/Manga%20Loader.user.js';}())` 32 | 33 | ### Using the script on mobile devices 34 | **This video demonstrates using the script on an Android phone, but Manga Loader should work on any mobile device including iPhones, iPads and other tablets/phones** 35 | **Mobile Bookmarklet Demo Video** 36 | 37 | ### Default Keybindings (configurable): 38 |
 39 | Z - previous chapter
 40 | X - exit
 41 | C - next chapter
 42 | W - scroll up
 43 | S - scroll down
 44 | + - zoom in
 45 | - - zoom out
 46 | 0 - reset zoom
 47 | 
48 | 49 | **NEW in v1.7.x: Settings panel** 50 | - Define custom CSS in the new settings panel (accessible through the gear icon at the bottom left) 51 | - The CSS will be saved and reapplied each time the script loads 52 | - Change the background color of the page, the width of the images and anything else in the settings panel 53 | 54 | **NEW in v1.8.x: Custom page loading and keybindings** 55 | - The settings menu introduced in v1.7.x now contains all settings including number of pages to load and whether or not to autoload 56 | - Now possible to set exactly how many pages to lazyload instead of choosing between all or 10 57 | - To change the default keybindings in the settings panel, focus the corresponding textbox and press the key you would like to assign 58 | 59 | **NEW in v.1.10.x: CSS profiles** 60 | - Define multiple named css profiles you can switch between in the settings menu 61 | 62 | **NEW in v1.11.x: Zooming** 63 | - Zoom in and out of images using the +/-/0 (plus/minus/zero) keys (configurable) 64 | 65 | ### How to get to the menu options for this script 66 | 67 | Click the gear icon at the bottom right of the screen while the script is active. 68 | 69 | # Changelog 70 | 71 | See a summary of changes for each new version of the script here. 72 | 73 | # Supported Sites 74 | 75 | ### Manga 76 | 77 | * http://bato.to 78 | * http://mangafox.me 79 | * http://readms.com 80 | * http://mangareader.net 81 | * http://mangahere.co 82 | * http://mangapanda.com 83 | * http://mangapark.me 84 | * http://mangacow.co 85 | * http://mangatown.com 86 | * http://manga-joy.com 87 | * http://mangawall.com 88 | * http://manga.animea.net 89 | * http://www.thespectrum.net/manga_scans 90 | * http://kissmanga.com 91 | * http://mangadoom.co 92 | * http://www.mangago.me 93 | * http://eatmanga.com 94 | * http://mangalator.ch 95 | * http://www.mangacat.me 96 | * http://www.mangakaka.com 97 | * http://www.readmanga.today 98 | * http://raw.senmanga.com 99 | * http://manga.redhawkscans.com 100 | * http://reader.s2smanga.com 101 | * http://casanovascans.com 102 | * http://mangatraders.org 103 | * http://mangainn.me 104 | * http://reader.vortex-scans.com 105 | * http://reader.roseliascans.com 106 | * http://mangatopia.net 107 | * http://www.twistedhelscans.com 108 | * http://sensescans.com 109 | * http://reader.kireicake.com 110 | * http://substitutescans.com 111 | * http://mangaichiscans.mokkori.fr 112 | * http://reader.shoujosense.com 113 | * http://www.friendshipscans.com 114 | * http://www.mangachapter.me 115 | * http://kawaii.ca/reader 116 | * http://manga.famatg.com 117 | * http://www.demonicscans.com/FoOlSlide/directory 118 | * http://kobato.hologfx.com/reader 119 | * http://reader.cafeconirst.com 120 | * http://reader.evilflowers.com 121 | * http://manga.inpowerz.com 122 | * http://necron99scans.com/reader 123 | * http://otscans.com/foolslide/directory 124 | * http://reader.psscans.info 125 | * http://kawaii.ca/reader 126 | * http://lonemanga.com 127 | * https://manga.madokami.com 128 | * http://read.egscans.com 129 | * https://jaiminisbox.com/reader 130 | * http://imperialscans.com 131 | * http://spinybackmanga.com 132 | * http://www.senmanga.com 133 | * http://www.mangaeden.com 134 | * http://moonbunnycafe.com/comics 135 | * http://gomanga.co 136 | * http://www.otakusmash.com 137 | * http://www.mangahome.com 138 | * http://www.mangamint.com 139 | * http://reader.manga-download.org 140 | * http://titaniascans.com 141 | * http://helveticascans.com/reader 142 | * http://reader.thecatscans.com 143 | * http://yonkouprod.com/reader 144 | * http://www.sh-arab.com/manga (Arabic) 145 | * http://www.manga.ae (Arabic) 146 | * http://mangaforall.com (Arabic) 147 | * http://www.3asq.info (Arabic) 148 | * http://manga-ar.net (Arabic) 149 | * http://gmanga.me (Arabic) 150 | * http://www.japscan.com (French) 151 | * http://abandonedkittenscans.mokkori.fr/reader (French) 152 | * http://www.pecintakomik.com (Indonesian) 153 | * http://mangaindo.co (Indonesian) 154 | * http://www.komikstation.com (Indonesian) 155 | * http://centraldemangas.net (Portuguese) 156 | * http://br.mangahost.com/mangas (Portuguese) 157 | * http://hqbr.com.br (Portuguese) 158 | * http://unionmangas.net (Portuguese) 159 | * http://manhua.dmzj.com (Chinese) 160 | * http://www.dmzj.com (Chinese) 161 | * http://dm5.com (Chinese) 162 | * http://www.kukudm.com (Chinese) 163 | * http://www.chuixue.com (Chinese) 164 | * http://www.mymh8.com (Chinese) 165 | * http://www.cartoonmad.com (Chinese) 166 | * http://tw.ikanman.com (Chinese) 167 | 168 | ### Comics 169 | 170 | * http://hellocomic.com 171 | * http://readcomiconline.me 172 | * http://www.comicastle.org 173 | * http://readcomics.tv 174 | 175 | ### Pending Site Requests 176 | 177 | Sites that people have requested implementations for that I haven't gotten around to adding yet and/or sites that I intend to add in the future. 178 | 179 | ### Foolslide Site Requests 180 | -------------------------------------------------------------------------------- /manga-loader.user.js: -------------------------------------------------------------------------------- 1 | // ==UserScript== 2 | // @name Manga Loader 3 | // @namespace https://github.com/fuzetsu/manga-loader 4 | // @version 1.11.29 5 | // @description Support for over 70 sites! Loads manga chapter into one page in a long strip format, supports switching chapters, minimal script with no dependencies, easy to implement new sites, loads quickly and works on mobile devices through bookmarklet 6 | // @copyright 2016+, fuzetsu 7 | // @noframes 8 | // @grant GM_getValue 9 | // @grant GM_setValue 10 | // @grant GM_deleteValue 11 | // @match *://bato.to/reader* 12 | // @match *://mangafox.me/manga/*/*/* 13 | // @match *://mangafox.la/manga/*/*/* 14 | // @match *://fanfox.net/manga/*/*/* 15 | // @match *://readms.net/r/*/* 16 | // @match *://readms.net/read/*/* 17 | // @match *://mangastream.com/r/*/*/*/* 18 | // @match *://mangastream.com/read/*/*/*/* 19 | // @match *://www.mangareader.net/*/* 20 | // @match *://*.mangahere.co/manga/*/* 21 | // @match *://*.mangahere.cc/manga/*/* 22 | // @match *://www.mangapanda.com/*/* 23 | // @match *://mangapark.me/manga/*/*/* 24 | // @match *://mngcow.co/*/* 25 | // @match *://centraldemangas.org/online/*/* 26 | // @match *://*.com.br/leitura/online/capitulo/* 27 | // @match *://www.mangatown.com/manga/*/* 28 | // @match *://manga-joy.com/*/* 29 | // @match *://*.dm5.com/m* 30 | // @match *://*.senmanga.com/*/* 31 | // @match *://www.japscan.com/lecture-en-ligne/* 32 | // @match *://www.pecintakomik.com/manga/*/* 33 | // @match *://mangawall.com/manga/*/* 34 | // @match *://manga.animea.net/* 35 | // @match *://kissmanga.com/Manga/*/* 36 | // @match *://view.thespectrum.net/series/* 37 | // @match *://manhua.dmzj.com/*/* 38 | // @match *://hqbr.com.br/hqs/*/capitulo/*/leitor/0 39 | // @match *://www.dmzj.com/view/*/* 40 | // @match *://mangaindo.id/*/* 41 | // @match *://mangadoom.co/*/* 42 | // @match *://*.mangago.me/read-manga/*/* 43 | // @match *://mangalator.ch/show.php?gallery=* 44 | // @match *://eatmanga.com/Manga-Scan/*/* 45 | // @match *://www.mangacat.me/*/*/* 46 | // @match *://www.mangahen.com/*/* 47 | // @match *://www.readmng.com/*/* 48 | // @match *://mangatraders.biz/read-online/* 49 | // @match *://www.mangainn.net/manga/chapter/* 50 | // @match *://*.kukudm.com/comiclist/*/* 51 | // @match *://www.mangamap.com/*/* 52 | // @match *://www.mangachapter.me/*/*/*.html 53 | // @match *://kawaii.ca/reader/* 54 | // @match *://lonemanga.com/manga/*/* 55 | // @match *://read.egscans.com/om/manga/*/* 56 | // @match *://manga.madokami.al/reader/* 57 | // @match *://read.egscans.com/* 58 | // @match *://imperialscans.com/read/* 59 | // @match *://www.chuixue.com/manhua/*/* 60 | // @match *://www.sh-arab.com/manga/* 61 | // @match *://spinybackmanga.com/* 62 | // @match *://br.mangahost.com/manga/*/* 63 | // @match *://www.manga.ae/*/*/* 64 | // @match *://mangaforall.com/manga/*/*/* 65 | // @match *://hellocomic.com/*/*/* 66 | // @match *://www.3asq.info/*/* 67 | // @match *://*.readcomiconline.to/Comic/* 68 | // @match *://*.moonbunnycafe.com/* 69 | // @match *://*.mangaeden.com/* 70 | // @match *://*.comicastle.org/read-* 71 | // @match *://*.mymh8.com/chapter/* 72 | // @match *://*.unionmangas.net/leitor/* 73 | // @match *://*.otakusmash.com/*/* 74 | // @match *://*.mangahome.com/manga/*/* 75 | // @match *://*.readcomics.tv/*/chapter* 76 | // @match *://*.cartoonmad.com/comic/* 77 | // @match *://*.comicnad.com/comic/* 78 | // @match *://*.ikanman.com/comic/*/* 79 | // @match *://*.manhuagui.com/comic/*/* 80 | // @match *://*.mangasail.com/* 81 | // @match *://*.mangatail.com/* 82 | // @match *://*.titaniascans.com/reader/*/* 83 | // @match *://*.komikstation.com/*/*/* 84 | // @match *://*.gmanga.me/mangas/*/*/* 85 | // @match *://mangadex.org/chapter/* 86 | // @match *://merakiscans.com/*/* 87 | // @match *://biamamscans.com/read/* 88 | // @match *://read.lhtranslation.com/*.html 89 | // @match *://www.930mh.com/manhua/*/*.html* 90 | // @match *://www.mangabox.me/reader/*/episodes/*/ 91 | // @match *://twocomic.com/view/comic_*.html?ch=* 92 | // -- FOOLSLIDE START 93 | // @match *://manga.redhawkscans.com/reader/read/* 94 | // @match *://reader.s2smanga.com/read/* 95 | // @match *://casanovascans.com/read/* 96 | // @match *://reader.vortex-scans.com/read/* 97 | // @match *://reader.roseliascans.com/read/* 98 | // @match *://mangatopia.net/slide/read/* 99 | // @match *://www.twistedhelscans.com/read/* 100 | // @match *://sensescans.com/reader/read/* 101 | // @match *://reader.kireicake.com/read/* 102 | // @match *://substitutescans.com/reader/read/* 103 | // @match *://mangaichiscans.mokkori.fr/fs/read/* 104 | // @match *://reader.shoujosense.com/read/* 105 | // @match *://www.friendshipscans.com/slide/read/* 106 | // @match *://manga.famatg.com/read/* 107 | // @match *://www.demonicscans.com/FoOlSlide/read/* 108 | // @match *://reader.psscans.info/read/* 109 | // @match *://otscans.com/foolslide/read/* 110 | // @match *://necron99scans.com/reader/read/* 111 | // @match *://manga.inpowerz.com/read/* 112 | // @match *://reader.evilflowers.com/read/* 113 | // @match *://reader.cafeconirst.com/read/* 114 | // @match *://kobato.hologfx.com/reader/read/* 115 | // @match *://abandonedkittenscans.mokkori.fr/reader/read/* 116 | // @match *://jaiminisbox.com/reader/read/* 117 | // @match *://*.gomanga.co/reader/read/* 118 | // @match *://reader.manga-download.org/read/*/* 119 | // @match *://*.manga-ar.net/manga/*/*/* 120 | // @match *://*.helveticascans.com/r/read/* 121 | // @match *://reader.thecatscans.com/read/* 122 | // @match *://yonkouprod.com/reader/read/* 123 | // @match *://reader.championscans.com/read/* 124 | // @match *://reader.whiteoutscans.com/read/* 125 | // @match *://hatigarmscans.eu/hs/read/* 126 | // @match *://lector.kirishimafansub.com/lector/read/* 127 | // @match *://hotchocolatescans.com/fs/read/* 128 | // @match *://*.slide.world-three.org/read/* 129 | // -- FOOLSLIDE END 130 | // ==/UserScript== 131 | 132 | // short reference to unsafeWindow (or window if unsafeWindow is unavailable e.g. bookmarklet) 133 | var W = (typeof unsafeWindow === 'undefined') ? window : unsafeWindow; 134 | 135 | var scriptName = 'Manga Loader'; 136 | var pageTitle = document.title; 137 | 138 | var IMAGES = { 139 | refresh_large: 'data:image/svg+xml;charset=utf-8,' 140 | }; 141 | 142 | // reusable functions to insert in implementations 143 | var reuse = { 144 | encodeChinese: function(xhr) { 145 | xhr.overrideMimeType('text/html;charset=gbk'); 146 | }, 147 | na: function() { 148 | return 'N/A'; 149 | } 150 | }; 151 | 152 | /** 153 | Sample Implementation: 154 | { 155 | name: 'something' // name of the implementation 156 | , match: "^https?://domain.com/.*" // the url to react to for manga loading 157 | , img: '#image' // css selector to get the page's manga image 158 | , next: '#next_page' // css selector to get the link to the next page 159 | , numpages: '#page_select' // css selector to get the number of pages. elements like (select, span, etc) 160 | , curpage: '#page_select' // css selector to get the current page. usually the same as numPages if it's a select element 161 | , numchaps: '#chapters' // css selector to get the number of chapters in manga 162 | , curchap: '#chapters' // css selector to get the number of the current chapter 163 | , nextchap: '#next_chap' // css selector to get the link to the next chapter 164 | , prevchap: '#prev_chap' // same as above except for previous 165 | , 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 166 | , pages: function(next_url, current_page_number, callback, extract_function) { 167 | // gets called requesting a certain page number (current_page_number) 168 | // to continue loading execute callback with img to append as first parameter and next url as second parameter 169 | // only really needs to be used on sites that have really unusual ways of loading images or depend on javascript 170 | } 171 | 172 | Any of the CSS selectors can be functions instead that return the desired value. 173 | } 174 | */ 175 | 176 | var implementations = [{ 177 | name: 'batoto', 178 | match: "^https?://bato.to/reader.*", 179 | img: function(ctx) { 180 | var img = getEl('#comic_page', ctx); 181 | if(img) { 182 | return img.src; 183 | } else { 184 | var imgs = getEls('#content > div:nth-child(8) > img', ctx).map(function(page) { 185 | return page.src; 186 | }); 187 | if(imgs.length > 0) { 188 | this.next = function() { return imgs[0]; }; 189 | this.numpages = function() { return imgs.length; }; 190 | this.pages = function(url, num, cb, ex) { 191 | cb(imgs[num - 1], num); 192 | }; 193 | return imgs[0]; 194 | } 195 | } 196 | }, 197 | next: function() { 198 | if(!this._numpage) { 199 | this._numpage = extractInfo(this.curpage, {type: 'index'}); 200 | this._id = location.hash.split('_')[0].slice(1); 201 | } 202 | return '/areader?id=' + this._id + '&p=' + (++this._numpage); 203 | }, 204 | numpages: '#page_select', 205 | curpage: '#page_select', 206 | curchap: 'select[name=chapter_select]', 207 | numchaps: 'select[name=chapter_select]', 208 | nextchap: function(prev) { 209 | //var link = extractInfo('select[name=chapter_select]', {type: 'value', val: prev ? 1 : -1}); 210 | //return link && link.replace(/https?/, document.location.href.split(':')[0]); // fix for batotos broken https pages 211 | var menu = getEls('div.moderation_bar > ul > li', getEl('#reader')); 212 | for (var i = 0; i != menu.length; i += 1) { 213 | var img = getEl('img', menu[i]); 214 | if (img && img.title == (prev ? "Previous Chapter" : "Next Chapter")) { 215 | return img.parentNode.href.replace(/https?/, document.location.href.split(':')[0]); 216 | } 217 | } 218 | return null; 219 | }, 220 | prevchap: function() { 221 | return this.nextchap(true); 222 | }, 223 | wait: '#comic_page' 224 | }, { 225 | name: 'manga-panda', 226 | match: "^https?://www.mangapanda.com/.*/[0-9]*", 227 | img: '#img', 228 | next: '.next a', 229 | numpages: '#pageMenu', 230 | curpage: '#pageMenu', 231 | nextchap: '#mangainfofooter > #mangainfo_bas table tr:first-child a', 232 | prevchap: '#mangainfofooter > #mangainfo_bas table tr:last-child a' 233 | }, { 234 | name: 'mangafox', 235 | match: "^https?://(fan|manga)fox.(me|la|net)/manga/[^/]*/[^/]*/[^/]*", 236 | img: '.reader-main img', 237 | next: '.pager-list-left > span > a:last-child', 238 | numpages: function() { return W.imagecount; }, 239 | curpage: function () { return W.imagepage; }, 240 | nextchap: '.pager-list-left > a:last-child', 241 | prevchap: '.pager-list-left > a:first-child', 242 | imgURLs: [], 243 | pages: function(url, num, cb, ex) { 244 | var imp = this; 245 | if (this.imgURLs[num]) 246 | cb(this.imgURLs[num], num); 247 | else 248 | ajax({ 249 | url: 'chapterfun.ashx?cid=' + W.chapterid + '&page=' + num, 250 | onload: function(e) { 251 | eval(e.target.responseText); 252 | for (var i = 0; i < d.length; i++) { 253 | imp.imgURLs[num + i] = d[i]; 254 | } 255 | cb(d[0], num); 256 | } 257 | }); 258 | }, 259 | wait: function () { 260 | el = getEl('.reader-main img'); 261 | 262 | return el && el.getAttribute('src') != el.getAttribute('data-loading-img'); 263 | } 264 | }, { 265 | name: 'manga-stream', 266 | match: "^https?://(readms|mangastream).(net|com)/(r|read)/[^/]*/[^/]*", 267 | img: '#manga-page', 268 | next: '.next a', 269 | numpages: function() { 270 | var lastPage = getEl('.subnav-wrapper .controls .btn-group:last-child ul li:last-child'); 271 | return parseInt(lastPage.textContent.match(/[0-9]/g).join(''), 10); 272 | }, 273 | nextchap: function(prev) { 274 | var found; 275 | var chapters = [].slice.call(document.querySelectorAll('.controls > div:first-child > .dropdown-menu > li a')); 276 | chapters.pop(); 277 | for (var i = 0; i < chapters.length; i++) { 278 | if (window.location.href.indexOf(chapters[i].href) !== -1) { 279 | found = chapters[i + (prev ? 1 : -1)]; 280 | if (found) return found.href; 281 | } 282 | } 283 | }, 284 | prevchap: function() { 285 | return this.nextchap(true); 286 | } 287 | }, { 288 | name: 'manga-reader', 289 | match: "^https?://www.mangareader.net/.*/.*", 290 | img: '#img', 291 | next: '.next a', 292 | numpages: '#pageMenu', 293 | curpage: '#pageMenu', 294 | nextchap: '#chapterMenu', 295 | prevchap: '#chapterMenu', 296 | wait: '#chapterMenu option' 297 | }, { 298 | name: 'manga-town', 299 | match: "^https?://www.mangatown.com/manga/[^/]+/[^/]+", 300 | img: '#image', 301 | next: '#viewer a', 302 | numpages: '.page_select select', 303 | curpage: '.page_select select', 304 | nextchap: '#top_chapter_list', 305 | prevchap: '#top_chapter_list', 306 | wait: 1000 307 | }, { 308 | name: 'manga-cow, manga-doom, manga-indo, 3asq.info, moonbunnnycafe', 309 | match: "^https?://(mngcow|mangadoom|mangaindo|merakiscans|www\\.3asq|moonbunnycafe)\\.(co|id|info|com)/[^/]+/[0-9.]+", 310 | img: '.prw a > img', 311 | next: '.prw a', 312 | numpages: 'select.cbo_wpm_pag', 313 | curpage: 'select.cbo_wpm_pag', 314 | nextchap: function(prev) { 315 | var next = extractInfo('select.cbo_wpm_chp', {type: 'value', val: (prev ? 1 : -1)}); 316 | if(next) return window.location.href.replace(/\/[0-9.]+\/?([0-9]+\/?)?[^/]*$/, '/' + next); 317 | }, 318 | prevchap: function() { 319 | return this.nextchap(true); 320 | } 321 | }, { 322 | name: 'manga-here', 323 | match: "^https?://www.mangahere.c[oc]/manga/[^/]+/[^/]+", 324 | img: '#viewer img:last-child', 325 | next: '#viewer a', 326 | numpages: 'select.wid60', 327 | curpage: 'select.wid60', 328 | numchaps: '#top_chapter_list', 329 | curchap: '#top_chapter_list', 330 | nextchap: function(prev) { 331 | var chapter = W.chapter_list[W.current_chapter_index + (prev ? -1 : 1)]; 332 | return chapter && chapter[1]; 333 | }, 334 | prevchap: function() { 335 | return this.nextchap(true); 336 | }, 337 | wait: function() { 338 | return areDefined(W.current_chapter_index, W.chapter_list, getEl('#top_chapter_list')); 339 | } 340 | }, { 341 | name: 'manga-here mobile', 342 | match: "^https?://m.mangahere.c[oc]/manga/[^/]+/[^/]+", 343 | img: '#image', 344 | next: '#viewer a', 345 | numpages: '.mangaread-page', 346 | curpage: '.mangaread-page' 347 | }, { 348 | name: 'manga-park', 349 | match: "^https?://mangapark\\.me/manga/[^/]+/[^/]+/[^/]+", 350 | img: '.img-link > img', 351 | next: '.page > span:last-child > a', 352 | numpages: function() { 353 | if(W.sel_load && W.sel_load.options[W.sel_load.selectedIndex].value) { 354 | return extractInfo('#sel_page_1'); 355 | } else { 356 | var links = getEls('.img-link > img').map(function(img) { return img.src; }); 357 | this.pages = function(url, num, cb, ex) { 358 | cb(links[num - 1], num); 359 | }; 360 | return links.length; 361 | } 362 | }, 363 | curpage: '#sel_page_1', 364 | nextchap: function(prev) { 365 | var next = extractInfo('#sel_book_1', {type: 'value', val: (prev ? -1 : 1)}); 366 | if(next) return window.location.href.replace(/(\/manga\/[^\/]+).+$/, '$1' + next + '/1'); 367 | }, 368 | prevchap: function() { 369 | return this.nextchap(true); 370 | }, 371 | wait: '#sel_book_1 option' 372 | }, { 373 | name: 'central-de-mangas', 374 | match: "^https?://(centraldemangas\\.org|[^\\.]+\\.com\\.br/leitura)/online/[^/]*/[0-9]*", 375 | img: '#manga-page', 376 | next: '#manga-page', 377 | numpages: '#manga_pages', 378 | curpage: '#manga_pages', 379 | nextchap: function(prev) { 380 | var next = extractInfo('#manga_caps', {type: 'value', val: (prev ? -1 : 1)}); 381 | if(next) return window.location.href.replace(/[^\/]+$/, next); 382 | }, 383 | prevchap: function() { 384 | return this.nextchap(true); 385 | }, 386 | pages: function(url, num, cb, ex) { 387 | url = url.slice(0, url.lastIndexOf('-') + 1) + ("0" + num).slice(-2) + url.slice(url.lastIndexOf('.')); 388 | cb(url, url); 389 | } 390 | }, { 391 | name: 'manga-joy', 392 | match: "^https?://manga-joy.com/[^/]*/[0-9]*", 393 | img: '.prw img', 394 | next: '.nxt', 395 | numpages: '.wpm_nav_rdr li:nth-child(3) > select', 396 | curpage: '.wpm_nav_rdr li:nth-child(3) > select', 397 | nextchap: function(prev) { 398 | var next = extractInfo('.wpm_nav_rdr li:nth-child(2) > select', {type: 'value', val: prev ? 1 : -1}); 399 | if(next) return window.location.href.replace(/\/[0-9.]+\/?([0-9]+(\/.*)?)?$/, '/' + next); 400 | }, 401 | prevchap: function() { 402 | return this.nextchap(true); 403 | } 404 | }, { 405 | name: 'dm5', 406 | match: "^https?://[^\\.]*\\.dm5\\.com/m[0-9]*", 407 | img: function (){ 408 | return getEl('img.load-src').getAttribute('data-src'); 409 | }, 410 | next: function(){ 411 | return '#'; 412 | }, 413 | numpages: function () { 414 | return W.pages.length; 415 | }, 416 | pages: function(url, num, cb, ex) { 417 | cb(W.pages[num - 1].getAttribute('data-src'), num - 1); 418 | }, 419 | nextchap: 'a.logo_2', 420 | prevchap: 'a.logo_1', 421 | wait: function (){ 422 | W.pages = getEls('img.load-src'); 423 | return true; 424 | } 425 | }, { 426 | name: 'senmanga', 427 | match: "^https?://[^\\.]+\\.senmanga\\.com/[^/]*/.+", 428 | img: '#picture', 429 | next: '#reader > a', 430 | numpages: 'select[name=page]', 431 | curpage: 'select[name=page]', 432 | numchaps: 'select[name=chapter]', 433 | curchap: 'select[name=chapter]', 434 | nextchap: function(prev) { 435 | var next = extractInfo('select[name=chapter]', {type: 'value', val: (prev ? 1 : -1)}); 436 | if(next) { 437 | var manga = window.location.pathname.slice(1).split('/')[0]; 438 | return window.location.origin + '/' + manga + '/' + next + '/1'; 439 | } 440 | }, 441 | prevchap: function() { 442 | return this.nextchap(true); 443 | } 444 | }, { 445 | name: 'japscan', 446 | match: "^https?://www\\.japscan\\.com/lecture-en-ligne/[^/]*/[0-9]*", 447 | img: '#image', 448 | next: '#img_link', 449 | numpages: '#pages', 450 | curpage: '#pages', 451 | nextchap: '#next_chapter', 452 | prevchap: '#back_chapter' 453 | }, { 454 | name: 'pecintakomik', 455 | match: "^https?://www\\.pecintakomik\\.com/manga/[^/]*/[^/]*", 456 | img: '.picture', 457 | next: '.pager a:nth-child(3)', 458 | numpages: 'select[name=page]', 459 | curpage: 'select[name=page]', 460 | nextchap: function(prev) { 461 | var next = extractInfo('select[name=chapter]', {type: 'value', val: (prev ? 1 : -1)}); 462 | if(next) return window.location.href.replace(/\/([^\/]+)\/[0-9]+\/?$/, '/$1/' + next); 463 | }, 464 | prevchap: function() { 465 | return this.nextchap(true); 466 | } 467 | }, { 468 | name: 'manga-kaka', 469 | match: "^https?://www\\.(mangahen|mangamap)\\.com/[^/]+/[0-9]+", 470 | img: 'img.manga-page', 471 | next: '.nav_pag > li:nth-child(1) > a', 472 | numpages: 'select.cbo_wpm_pag', 473 | curpage: 'select.cbo_wpm_pag', 474 | nextchap: function(prev) { 475 | var chapter = extractInfo('select.cbo_wpm_chp', { type: 'value', val: (prev ? 1 : -1) }); 476 | if(chapter) return window.location.href.replace(/\/[0-9\.]+\/?([0-9]+\/?)?$/, '/' + chapter); 477 | }, 478 | prevchap: function() { 479 | return this.nextchap(true); 480 | } 481 | }, { 482 | name: 'manga-wall', 483 | _page: null, 484 | match: "^https?://mangawall\\.com/manga/[^/]*/[0-9]*", 485 | img: 'img.scan', 486 | next: function() { 487 | if(this._page === null) this._page = W.page; 488 | return W.series_url + '/' + W.chapter + '/' + (this._page += 1); 489 | }, 490 | numpages: '.pageselect', 491 | curpage: '.pageselect', 492 | nextchap: function(prev) { 493 | return W.series_url + '/' + (parseInt(W.chapter.slice(1)) + (prev ? -1 : 1)) + '/1'; 494 | }, 495 | prevchap: function() { 496 | return this.nextchap(true); 497 | } 498 | }, { 499 | name: 'anime-a', 500 | _page: null, 501 | match: "^https?://manga\\.animea.net/.+chapter-[0-9]+(-page-[0-9]+)?.html", 502 | img: '#scanmr', 503 | next: function() { 504 | if(this._page === null) this._page = W.page; 505 | return W.series_url + W.chapter + '-page-' + (this._page += 1) + '.html'; 506 | }, 507 | numpages: '.pageselect', 508 | curpage: '.pageselect', 509 | nextchap: function(prev) { 510 | return W.series_url + 'chapter-' + (parseInt(W.chapter.match(/[0-9]+/)[0]) + (prev ? -1 : 1)) + '-page-1.html'; 511 | }, 512 | prevchap: function() { 513 | return this.nextchap(true); 514 | } 515 | }, { 516 | name: 'kiss-manga', 517 | match: "^https?://kissmanga\\.com/Manga/[^/]+/.+", 518 | img: '#divImage img', 519 | next: '#divImage img', 520 | numpages: function() { 521 | return (W.lstOLA || W.lstImages).length; 522 | }, 523 | curpage: function() { 524 | if(getEls('#divImage img').length > 1) { 525 | return 1; 526 | } else { 527 | return W.currImage + 1; 528 | } 529 | }, 530 | nextchap: '#selectChapter, .selectChapter', 531 | prevchap: '#selectChapter, .selectChapter', 532 | pages: function(url, num, cb, ex) { 533 | cb((W.lstOLA || W.lstImages)[num - 1], num); 534 | } 535 | }, { 536 | name: 'the-spectrum-scans', 537 | match: "^https?://view\\.thespectrum\\.net/series/[^\\.]+\\.html", 538 | img: '#mainimage', 539 | next: function() { 540 | if (++this._page < this._pages.length) { 541 | return this._pages[this._page]; 542 | } 543 | }, 544 | numpages: '.selectpage', 545 | curpage: '.selectpage', 546 | nextchap: function(prev) { 547 | var ps = document.pageSelector1; 548 | var chnum = ps.ch.selectedIndex + (prev ? -1 : 1); 549 | if (chnum < ps.ch.length && chnum > -1) { 550 | return ps.action.split('?')[0] + '?ch=' + ps.ch[chnum].value + '&page=1'; 551 | } else { 552 | return false; 553 | } 554 | }, 555 | prevchap: function() { 556 | return this.nextchap(true); 557 | }, 558 | wait: function() { 559 | var ps = document.pageSelector1; 560 | this._pages = []; 561 | var base = ps.action.split('?')[0]; 562 | for (var i = 0; i < ps.page.length; i++) { 563 | this._pages.push(base + '?ch=' + ps.ch.value + '&page=' + ps.page[i].value); 564 | } 565 | this._page = ps.page[ps.page.selectedIndex].value - 1; 566 | return true; 567 | } 568 | }, { 569 | name: 'manhua-dmzj', 570 | match: "^https?://manhua.dmzj.com/[^/]*/[0-9]+(-[0-9]+)?\\.shtml", 571 | img: '#center_box > img', 572 | next: '#center_box > img', 573 | numpages: function() { 574 | return W.arr_pages.length; 575 | }, 576 | curpage: function() { 577 | var match = location.href.match(/page=([0-9]+)/); 578 | return match ? parseInt(match[1]) : 1; 579 | }, 580 | nextchap: '#next_chapter', 581 | prevchap: '#prev_chapter', 582 | pages: function(url, num, cb, ex) { 583 | cb(W.img_prefix + W.arr_pages[num - 1], num); 584 | }, 585 | wait: '#center_box > img' 586 | }, { 587 | name: 'hqbr', 588 | match: "^https?://hqbr.com.br/hqs/[^/]+/capitulo/[0-9]+/leitor/0", 589 | img: '#hq-page', 590 | next: '#hq-page', 591 | numpages: function() { 592 | return W.pages.length; 593 | }, 594 | curpage: function() { 595 | return W.paginaAtual + 1; 596 | }, 597 | nextchap: function(prev) { 598 | var chapters = getEls('#chapter-dropdown a'), 599 | current = parseInt(W.capituloIndex), 600 | chapter = chapters[current + (prev ? -1 : 1)]; 601 | return chapter && chapter.href; 602 | }, 603 | prevchap: function() { 604 | return this.nextchap(true); 605 | }, 606 | pages: function(url, num, cb, ex) { 607 | cb(W.pages[num - 1], num); 608 | } 609 | }, { 610 | name: 'dmzj', 611 | match: "^https?://www.dmzj.com/view/[^/]+/.+\\.html", 612 | img: '.comic_wraCon > img', 613 | next: '.comic_wraCon > img', 614 | numpages: function() { 615 | return parseInt(W.pic_total); 616 | }, 617 | curpage: function() { 618 | var match = location.href.match(/page=([0-9])/); 619 | return match ? parseInt(match[1]) : 1; 620 | }, 621 | nextchap: '.next > a', 622 | prevchap: '.pre > a', 623 | pages: function(url, num, cb, ex) { 624 | cb(W.img_prefix + W.picArry[num - 1], num); 625 | }, 626 | wait: '.comic_wraCon > img' 627 | }, { 628 | name: 'mangago', 629 | match: "^https?://(www.)?mangago.me/read-manga/[^/]+/[^/]+/[^/]+", 630 | img: '#page1', 631 | next: '#pic_container', 632 | numpages: '#dropdown-menu-page', 633 | curpage: function() { 634 | return parseInt(getEls('#page-mainer a.btn.dropdown-toggle')[1].textContent.match(/[0-9]+/)[0]); 635 | }, 636 | nextchap: function(prev) { 637 | var chapters = getEls('ul.dropdown-menu.chapter a'), 638 | curName = getEls('#page-mainer a.btn.dropdown-toggle')[0].textContent, 639 | curIdx; 640 | chapters.some(function(chap, idx) { 641 | if(chap.textContent.indexOf(curName) === 0) { 642 | curIdx = idx; 643 | return true; 644 | } 645 | }); 646 | var chapter = chapters[curIdx + (prev ? 1 : -1)]; 647 | return chapter && chapter.href; 648 | }, 649 | prevchap: function() { 650 | return this.nextchap(true); 651 | } 652 | }, { 653 | name: 'mangalator', 654 | match: "^https?://mangalator.ch/show.php\\?gallery=[0-9]+", 655 | img: '.image img', 656 | next: '#next', 657 | numpages: 'select[name=image]', 658 | curpage: 'select[name=image]', 659 | nextchap: function(prev) { 660 | var next = extractInfo('select[name=gallery]', {type: 'value', val: (prev ? 1 : -1)}); 661 | if(next) return location.href.replace(/\?gallery=[0-9]+/, '?gallery=' + next); 662 | }, 663 | prevchap: function() { 664 | return this.nextchap(true); 665 | } 666 | }, { 667 | name: 'eatmanga', 668 | match: "^https?://eatmanga.com/Manga-Scan/[^/]+/.+", 669 | img: '#eatmanga_image, #eatmanga_image_big', 670 | next: '#page_next', 671 | numpages: '#pages', 672 | curpage: '#pages', 673 | nextchap: '#bottom_chapter_list', 674 | prevchap: '#bottom_chapter_list', 675 | invchap: true 676 | }, { 677 | name: 'manga-cat', 678 | match: "^https?://www.mangacat.me/[^/]+/[^/]+/[^\\.]+.html", 679 | img: '.img', 680 | next: '.img-link', 681 | numpages: '#page', 682 | curpage: '#page', 683 | nextchap: '#chapter', 684 | prevchap: '#chapter', 685 | invchap: true, 686 | wait: '#chapter option' 687 | }, { 688 | name: 'readmng.com', 689 | match: "^https?://www\\.readmng\\.com/[^/]+/.+", 690 | img: '.page_chapter-2 img', 691 | next: '.list-switcher-2 > li:nth-child(3) > a, .list-switcher-2 > li:nth-child(2) > a', 692 | numpages: '.list-switcher-2 select[name=category_type]', 693 | curpage: '.list-switcher-2 select[name=category_type]', 694 | nextchap: '.jump-menu[name=chapter_list]', 695 | prevchap: '.jump-menu[name=chapter_list]', 696 | invchap: true 697 | }, { 698 | name: 'mangadex.org', 699 | match: "^https?://mangadex\\.org/chapter/[0-9]+/[0-9]+", 700 | img: '#current_page', 701 | next: function() { 702 | return this._base + ++this._page; 703 | }, 704 | numpages: '#jump_page', 705 | curpage: '#jump_page', 706 | nextchap: function() { 707 | var chapter = document.querySelector('#jump_chapter').selectedOptions[0].previousElementSibling; 708 | return (chapter === null) ? false : (this._base.replace(/[0-9]+\/$/, chapter.value)); 709 | }, 710 | prevchap: function() { 711 | var chapter = document.querySelector('#jump_chapter').selectedOptions[0].nextElementSibling; 712 | return (chapter === null) ? false : (this._base.replace(/[0-9]+\/$/, chapter.value)); 713 | }, 714 | 715 | wait: function() { 716 | var loc = document.location.toString(); 717 | var num = loc.match(/[0-9]+$/); 718 | this._base = loc.slice(0, -num.length); 719 | this._page = parseInt(num); 720 | return true; 721 | } 722 | }, { 723 | name: 'biamamscans.com', 724 | match: "^https?://biamamscans\\.com/read/.+", //nextchap and prevchap broken 725 | img: '.manga-image', 726 | next: 'span.float-right:nth-child(2) > div:nth-child(2) > a:nth-child(1)', 727 | numpages: '#page-select', 728 | curpage: '#page-select', 729 | nextchap: '#chapter-select', 730 | prevchap: '#chapter-select' 731 | }, { 732 | name: 'lhtranslation', 733 | match: "^https?://read.lhtranslation\\.com/read-.+", 734 | img: 'img.chapter-img', 735 | next: '.chapter-content > select + a.label', 736 | numpages: '.chapter-content > select', 737 | curpage: '.chapter-content > select', 738 | numchaps: '.form-control', 739 | curchap: '.form-control', 740 | nextchap: '.form-control', 741 | prevchap: '.form-control', 742 | invchap: true 743 | }, { 744 | name: 'foolslide', 745 | match: "^https?://(" + [ 746 | "manga.redhawkscans.com/reader/read/.+", 747 | "reader.s2smanga.com/read/.+", 748 | "casanovascans.com/read/.+", 749 | "reader.vortex-scans.com/read/.+", 750 | "reader.roseliascans.com/read/.+", 751 | "mangatopia.net/slide/read/.+", 752 | "www.twistedhelscans.com/read/.+", 753 | "sensescans.com/reader/read/.+", 754 | "reader.kireicake.com/read/.+", 755 | "substitutescans.com/reader/read/.+", 756 | "mangaichiscans.mokkori.fr/fs/read/.+", 757 | "reader.shoujosense.com/read/.+", 758 | "www.friendshipscans.com/slide/read/.+", 759 | "manga.famatg.com/read/.+", 760 | "www.demonicscans.com/FoOlSlide/read/.+", 761 | "necron99scans.com/reader/read/.+", 762 | "www.demonicscans.com/FoOlSlide/read/.+", 763 | "reader.psscans.info/read/.+", 764 | "otscans.com/foolslide/read/.+", 765 | "necron99scans.com/reader/read/.+", 766 | "manga.inpowerz.com/read/.+", 767 | "reader.evilflowers.com/read/.+", 768 | "reader.cafeconirst.com/read/.+", 769 | "kobato.hologfx.com/reader/read/.+", 770 | "jaiminisbox.com/reader/read/.+", 771 | "abandonedkittenscans.mokkori.fr/reader/read/.+", 772 | "gomanga.co/reader/read/.+", 773 | "reader\.manga-download\.org/read/.+", 774 | "(www\.)?manga-ar\.net/manga/.+/.+/.+", 775 | "helveticascans.com/r/read/.+", 776 | "reader.thecatscans.com/read/.+", 777 | "yonkouprod.com/reader/read/.+", 778 | "reader.championscans.com/read/.+", 779 | "reader.whiteoutscans.com/read/.+", 780 | "hatigarmscans.eu/hs/read/.+", 781 | "lector.kirishimafansub.com/lector/read/.+", 782 | "hotchocolatescans.com/fs/read/.+", 783 | "www.slide.world-three.org/read/.+", 784 | ].join('|') + ")", 785 | img: function() { 786 | return W.pages[W.current_page].url; 787 | }, 788 | next: function() { 789 | return 'N/A'; 790 | }, 791 | numpages: function() { 792 | return W.pages.length; 793 | }, 794 | curpage: function() { 795 | return W.current_page + 1; 796 | }, 797 | nextchap: function(prev) { 798 | var desired; 799 | var dropdown = getEls('ul.dropdown')[1] || getEls('ul.uk-nav')[1] || getEls('ul.dropdown-menu')[3]; 800 | if(!dropdown) return; 801 | getEls('a', dropdown).forEach(function(chap, idx, arr) { 802 | if(location.href.indexOf(chap.href) === 0) desired = arr[idx + (prev ? 1 : -1)]; 803 | }); 804 | return desired && desired.href; 805 | }, 806 | prevchap: function() { 807 | return this.nextchap(true); 808 | }, 809 | pages: function(url, num, cb, ex) { 810 | cb(W.pages[num - 1].url, num); 811 | }, 812 | wait: function() { 813 | if(W.location.href.indexOf('gomanga.co') !== -1) { 814 | var match = document.body.innerHTML.match(/(\w+)\[id\]\.url/); 815 | W.pages = match && match[1] && W[match[1]]; 816 | } 817 | return W.pages; 818 | } 819 | }, { 820 | name: 'mangatraders', 821 | match: "^https?://mangatraders\\.biz/read-online/.+", 822 | img: 'img.CurImage', 823 | next: '.image-container a', 824 | numpages: '.PageSelect', 825 | curpage: '.PageSelect', 826 | nextchap: function(prev) { 827 | var next = extractInfo('.ChapterSelect', {type:'text', val: (prev ? -1 : 1)}); 828 | if(next) { 829 | var chapter = next.match(/[0-9.]+/)[0]; 830 | return location.href.replace(/chapter-[0-9.]+/, 'chapter-' + chapter).replace(/page-[0-9]+/, 'page-1'); 831 | } 832 | }, 833 | prevchap: function() { 834 | return this.nextchap(true); 835 | } 836 | }, { 837 | name: 'mangainn', 838 | match: "^https?://www.mangainn.net/manga/chapter/.+", 839 | img: '#imgPage', 840 | next: function() { 841 | if(!this._count) this._count = extractInfo(this.curpage, {type: 'value'}); 842 | var url = location.href; 843 | if(!/page_[0-9]+/.test(url)) url += '/page_1'; 844 | return url.replace(/page_[0-9]+/, 'page_' + (++this._count)); 845 | }, 846 | numpages: '#cmbpages', 847 | curpage: '#cmbpages', 848 | nextchap: function(prev) { 849 | var next = extractInfo('#chapters', {type:'value', val: (prev ? -1 : 1)}); 850 | if(next) return location.href.replace(/\/chapter\/.+$/, '/chapter/' + next + '/page_1'); 851 | }, 852 | prevchap: function() { 853 | return this.nextchap(true); 854 | } 855 | }, { 856 | name: 'kukudm', 857 | match: "^https?://(www|comic|comic2|comic3).kukudm.com/comiclist/[0-9]+/[0-9]+/[0-9]+.htm", 858 | img: function(ctx) { 859 | var script = getEl('td > script[language=javascript]', ctx); 860 | if(script) { 861 | return 'http://n.kukudm.com/' + script.textContent.match(/\+"([^']+)/)[1]; 862 | } 863 | }, 864 | next: function(ctx) { 865 | var links = getEls('td > a', ctx); 866 | return links[links.length - 1].getAttribute('href'); 867 | }, 868 | numpages: function(cur) { 869 | return parseInt(document.body.textContent.match(/共([0-9]+)页/)[1]); 870 | }, 871 | curpage: function() { 872 | return parseInt(document.body.textContent.match(/第([0-9]+)页/)[1]); 873 | }, 874 | beforexhr: reuse.encodeChinese 875 | }, { 876 | name: 'mangachapter', 877 | match: "^https?://www\\.mangachapter\\.me/[^/]+/[^/]+/[^/]+.html", 878 | img: '#mangaImg, #viewer > table > tbody > tr > td:nth-child(1) > a:nth-child(2) > img', 879 | next: '.page-select + a.button-page', 880 | numpages: '.page-select select', 881 | curpage: '.page-select select', 882 | invchap: true, 883 | nextchap: '#top_chapter_list', 884 | prevchap: '#top_chapter_list', 885 | wait: '#top_chapter_list' 886 | }, { 887 | name: 'kawaii', 888 | match: "^https://kawaii.ca/reader/.+", 889 | img: '.picture', 890 | next: 'select[name=page] + a', 891 | numpages: 'select[name=page]', 892 | curpage: 'select[name=page]', 893 | nextchap: function(prev) { 894 | var next = extractInfo('select[name=chapter]', {type:'value', val: (prev ? -1 : 1)}); 895 | if(next) return location.href.replace(/\/reader\/([^/]+)(\/.+)?$/, '/reader/$1/' + next); 896 | }, 897 | prevchap: function() { 898 | return this.nextchap(true); 899 | } 900 | }, { 901 | name: 'lonemanga', 902 | match: "^https?://lonemanga.com/manga/[^/]+/[^/]+", 903 | img: '#imageWrapper img', 904 | next: '#imageWrapper a', 905 | numpages: '.viewerPage', 906 | curpage: '.viewerPage', 907 | nextchap: function(prev) { 908 | var next = extractInfo('.viewerChapter', {type:'value', val: (prev ? 1 : -1)}); 909 | if(next) return location.href.replace(/\/manga\/([^/]+)\/.+$/, '/manga/$1/' + next); 910 | }, 911 | prevchap: function() { 912 | return this.nextchap(true); 913 | } 914 | }, { 915 | name: 'madokami', 916 | match: "^https?://manga\\.madokami\\.al/reader/.+", 917 | img: 'img', 918 | next: 'img', 919 | curpage: function() { 920 | return parseInt(query().index) + 1; 921 | }, 922 | numpages: function() { 923 | if(!this._pages) { 924 | this._pages = JSON.parse(getEl('#reader').dataset.files); 925 | } 926 | return this._pages.length; 927 | }, 928 | pages: function(url, num, cb, ex) { 929 | url = url.replace(/file=.+$/, 'file=' + this._pages[num - 1]); 930 | cb(url, url); 931 | }, 932 | wait: '#reader' 933 | }, { 934 | name: 'egscans', 935 | match: '^https?://read.egscans.com/.+', 936 | img: '#image_frame img', 937 | next: '#image_frame img', 938 | curpage: 'select[name=page]', 939 | numpages: 'select[name=page]', 940 | nextchap: function(prev) { 941 | var data = getEl(this.curchap).getAttribute('onchange').match(/'[^']+'/g); 942 | var next = extractInfo(this.curchap, { type: 'value', val: (prev ? -1 : 1) }); 943 | if(next) return location.origin + '/' + data[0].slice(1, -1) + '/' + next; 944 | }, 945 | prevchap: function() { 946 | return this.nextchap(true); 947 | }, 948 | curchap: 'select[name=chapter]', 949 | numchaps: 'select[name=chapter]', 950 | pages: function(url, num, cb, ex) { 951 | cb('/' + W.img_url[num], num); 952 | } 953 | }, { 954 | name: 'imperialscans', 955 | match: '^https?://imperialscans.com/read/.+', 956 | img: '#page-img', 957 | next: '#page-url', 958 | curpage: function() { 959 | return extractInfo('#page-select', { type: 'index', val: -1 }); 960 | }, 961 | numpages: function() { 962 | return extractInfo('#page-select') - 1; 963 | }, 964 | curchap: function() { 965 | var options = getEls('#chapter-select option:not([disabled])'); 966 | var chapter = 1; 967 | options.some(function(value, index) { 968 | if (location.pathname === value.value) { 969 | chapter = options.length - index; 970 | return true; 971 | } 972 | }); 973 | return chapter; 974 | }, 975 | numchaps: function() { 976 | return extractInfo('#chapter-select'); 977 | }, 978 | nextchap: '#page-control > li:nth-child(5) > a', 979 | prevchap: '#page-control > li:nth-child(1) > a' 980 | }, { 981 | name: 'chuixue', 982 | match: "^https?://www.chuixue.com/manhua/[0-9]+/[0-9]+.html", 983 | img: '#qTcms_pic', 984 | next: '#qTcms_pic', 985 | curpage: '#qTcms_select_i', 986 | numpages: '#qTcms_select_i', 987 | pages: function(url, num, cb, ex) { 988 | if(!this._pages) { 989 | this._pages = W.qTcms_S_m_murl.split('$qingtiandy$'); 990 | } 991 | cb(this._pages[num - 1], num); 992 | }, 993 | nextchap: function() { 994 | return W.qTcms_Pic_nextArr; 995 | }, 996 | wait: '#qTcms_pic' 997 | }, { 998 | name: 'sh-arab', 999 | match: '^https?://www.sh-arab.com/manga/.+', 1000 | img: 'img.picture', 1001 | next: '#omv td > a', 1002 | curpage: 'select[name=page]', 1003 | numpages: 'select[name=page]', 1004 | curchap: 'select[name=chapter]', 1005 | numchaps: 'select[name=chapter]', 1006 | nextchap: function(prev) { 1007 | var next = extractInfo('select[name=chapter]', {type:'value', val: (prev ? -1 : 1)}); 1008 | if (next) return location.href.replace(/[^\/]+$/, next); 1009 | }, 1010 | prevchap: function() { 1011 | return this.nextchap(true); 1012 | } 1013 | }, { 1014 | name: 'br.mangahost.com', 1015 | match: "^http(s)?://br.mangahost.com/manga/[^/]+/.+", 1016 | img: 'img.open', 1017 | next: '.image-content > a', 1018 | curpage: '.viewerPage', 1019 | numpages: '.viewerPage' 1020 | }, { 1021 | name: 'spinybackmanga and titaniascans', 1022 | match: '^https?://(spinybackmanga.com/\\?manga=[^&]+&chapter=.+|www\.titaniascans\.com/reader/.+/.+)', 1023 | img: '#thePicLink img', 1024 | next: '#thePicLink', 1025 | curpage: function() { 1026 | return W.current; 1027 | }, 1028 | numpages: function() { 1029 | return getEl('#loadingbar tr').children.length; 1030 | }, 1031 | curchap: function() { 1032 | return parseInt(getEls('.selector')[1].firstChild.textContent.match(/[0-9]+/)[0]); 1033 | }, 1034 | numchaps: function() { 1035 | return getEls('.selector .options')[1].children.length; 1036 | }, 1037 | nextchap: function(prev) { 1038 | var nextChap = document.scripts[2].textContent.match(/location.href = "([^"]+)"/)[1]; 1039 | if(prev) { 1040 | [].some.call(getEls('.selector .options')[1].children, function(child, index, children) { 1041 | if(child.href === nextChap) { 1042 | nextChap = children[index - 2] && children[index - 2].href; 1043 | return true; 1044 | } 1045 | }); 1046 | } 1047 | return nextChap; 1048 | }, 1049 | prevchap: function() { 1050 | return this.nextchap(true); 1051 | }, 1052 | pages: function(url, num, cb, ex) { 1053 | var next = url.replace(/(?:(\/)2\/|[0-9]*)$/, '$1' + (num + 1)); 1054 | cb(W.imageArray[num - 1], next); 1055 | } 1056 | }, { 1057 | name: 'manga.ae', 1058 | match: "https?://www.manga.ae/[^/]+/[^/]+/", 1059 | img: '#showchaptercontainer img', 1060 | next: '#showchaptercontainer a', 1061 | curpage: 'a.chpage', 1062 | nextchap: '.chapter:last-child', 1063 | prevchap: '.chapter:first-child' 1064 | }, { 1065 | name: 'mangaforall', 1066 | match: "https?://mangaforall.com/manga/[^/]+/[^/]+/", 1067 | img: '#page > img', 1068 | next: '#page > img', 1069 | numpages: '#chapter > div:nth-child(1) > div > div.uk-width-large-1-3.uk-width-medium-1-3.uk-width-small-1-1.uk-text-left.uk-text-center-small > div > div > div > ul', 1070 | curpage: '#chapter > div:nth-child(1) > div > div.uk-width-large-1-3.uk-width-medium-1-3.uk-width-small-1-1.uk-text-left.uk-text-center-small > div > a.uk-button.uk-button-primary.number.uk-button-danger', 1071 | nextchap: '#chapter > div:nth-child(5) > div.uk-grid.uk-grid-collapse.uk-margin-top > div.uk-width-large-1-3.uk-width-medium-1-3.uk-width-small-1-1.uk-text-left.uk-text-center-small > a', 1072 | prevchap: '#chapter > div:nth-child(5) > div.uk-grid.uk-grid-collapse.uk-margin-top > div.uk-width-large-1-3.uk-width-medium-1-3.uk-width-small-1-1.uk-text-right.uk-text-center-small > a', 1073 | pages: function(url, num, cb, ex) { 1074 | cb(W.pages[num - 1].url, num); 1075 | } 1076 | }, { 1077 | name: 'hellocomic', 1078 | match: "https?://hellocomic.com/[^/]+/[^/]+/p[0-9]+", 1079 | img: '.coverIssue img', 1080 | next: '.coverIssue a', 1081 | numpages: '#e1', 1082 | curpage: '#e1', 1083 | nextchap: '#e2', 1084 | prevchap: '#e2', 1085 | curchap: '#e2', 1086 | numchaps: '#e2' 1087 | }, { 1088 | name: 'read-comic-online', 1089 | match: "^https?://readcomiconline\\.to/Comic/[^/]+/.+", 1090 | img: '#divImage img', 1091 | next: '#divImage img', 1092 | numpages: function() { 1093 | return W.lstImages.length; 1094 | }, 1095 | curpage: function() { 1096 | return getEls('#divImage img').length > 1 ? 1 : W.currImage + 1; 1097 | }, 1098 | nextchap: '#selectEpisode, .selectEpisode', 1099 | prevchap: '#selectEpisode, .selectEpisode', 1100 | pages: function(url, num, cb, ex) { 1101 | cb(W.lstImages[num - 1], num); 1102 | } 1103 | }, { 1104 | name: 'mangaeden', 1105 | match: "^https?://(www\\.)?mangaeden\\.com/(en|it)/(en|it)-manga/.+", 1106 | img: '#mainImg', 1107 | next: '#nextA', 1108 | numpages: '#pageSelect', 1109 | curpage: '#pageSelect', 1110 | numchaps: '#combobox', 1111 | curchap: '#combobox', 1112 | invchap: true, 1113 | nextchap: function (prev) { 1114 | var cbox = getEl('#combobox'); 1115 | var opt = cbox[prev ? cbox.selectedIndex + 1 : cbox.selectedIndex - 1]; 1116 | var span = getEl('span.hideM0 a'); 1117 | return opt && span && span.href + parseInt(opt.value) + '/1/'; 1118 | }, 1119 | prevchap: function () { 1120 | return this.nextchap(true); 1121 | } 1122 | }, { 1123 | name: 'comicastle', 1124 | match: "^https?://comicastle\\.org/read-.+", 1125 | img: '.chapter-img', 1126 | next: '.chapter-content > select + a.label', 1127 | numpages: '.chapter-content > select', 1128 | curpage: '.chapter-content > select', 1129 | numchaps: '.form-control', 1130 | curchap: '.form-control', 1131 | nextchap: '.form-control', 1132 | prevchap: '.form-control', 1133 | invchap: true 1134 | }, { 1135 | name: 'mymh8', 1136 | match: "^https?://(www\\.)?mymh8\\.com/chapter/.+", 1137 | img: '#viewimg', 1138 | next: reuse.na, 1139 | numpages: function() { 1140 | return W.maxpages; 1141 | }, 1142 | curpage: '#J_showpage > span', 1143 | nextchap: function(prev) { 1144 | var button = prev ? getEl('div.m3p > input:first-of-type') : getEl('div.m3p > input:last-of-type'); 1145 | return button && button.attributes.onclick.value.match(/\.href='([^']+)'/)[1]; 1146 | }, 1147 | prevchap: function() { 1148 | return this.nextchap(true); 1149 | }, 1150 | pages: function(url, num, cb, ex) { 1151 | cb(W.WebimgServerURL[0] + W.imageslist[num], num); 1152 | }, 1153 | wait: function() { 1154 | return W.imageslist.length > 0; 1155 | } 1156 | }, { 1157 | name: 'unionmangas', 1158 | match: "https?://(www\\.)?unionmangas\\.net/leitor/.+", 1159 | img: '.slick-active img.real', 1160 | next: reuse.na, 1161 | numpages: '.selectPage', 1162 | curpage: '.selectPage', 1163 | numchaps: '#cap_manga1', 1164 | curchap: '#cap_manga1', 1165 | nextchap: '#cap_manga1', 1166 | prevchap: '#cap_manga1', 1167 | pages: function(url, num, cb, ex) { 1168 | cb(W.pages[num - 1], num); 1169 | }, 1170 | wait: function() { 1171 | W.pages = getEls('img.real').map(function(el) { 1172 | return el.src || el.dataset.lazy; 1173 | }); 1174 | return W.pages && W.pages.length > 0; 1175 | } 1176 | }, { 1177 | name: 'otakusmash', 1178 | match: "https?://www\\.otakusmash\\.com/(read-comics|read-manga)/.+", 1179 | img: 'img.picture', 1180 | next: 'select[name=page] + a', 1181 | curpage: 'select[name=page]', 1182 | numpages: 'select[name=page]', 1183 | nextchap: function(prev) { 1184 | var nextChap = extractInfo('select[name=chapter]', {type: 'value', val: prev ? 1 : -1}); 1185 | return nextChap ? location.href.replace(/(read-(comics|manga)\/[^\/]+).*/, '$1/' + nextChap) : null; 1186 | }, 1187 | prevchap: function() { 1188 | return this.nextchap(true); 1189 | }, 1190 | numchaps: 'select[name=chapter]', 1191 | curchap: 'select[name=chapter]', 1192 | invchap: true 1193 | }, { 1194 | name: 'mangahome', 1195 | match: "https?://www\\.mangahome\\.com/manga/.+/.+", 1196 | img: '#image', 1197 | next: '#viewer > a', 1198 | curpage: '.mangaread-page select', 1199 | numpages: '.mangaread-page select', 1200 | nextchap: function(prev) { 1201 | var buttons = getEls('.mangaread-footer .left > .btn-three'); 1202 | for (var i = 0; i < buttons.length; i++) { 1203 | if (buttons[i].textContent.indexOf(prev ? 'Prev Chapter' : 'Next Chapter') > - 1) { 1204 | return buttons[i].href; 1205 | } 1206 | } 1207 | }, 1208 | prevchap: function() { 1209 | return this.nextchap(true); 1210 | }, 1211 | wait: '#image' 1212 | }, { 1213 | name: 'readcomics', 1214 | match: "https?://(www\\.)?readcomics\\.tv/.+/chapter-[0-9]+(/[0-9]+|$)", 1215 | img: '#main_img', 1216 | next: '.nav.next', 1217 | curpage: 'select[name=page_select]', 1218 | numpages: 'select[name=page_select]', 1219 | nextchap: 'select[name=chapter_select]', 1220 | prevchap: 'select[name=chapter_select]', 1221 | curchap: 'select[name=chapter_select]', 1222 | numchaps: 'select[name=chapter_select]', 1223 | wait: 'select[name=page_select]' 1224 | }, { 1225 | name: 'cartoonmad', 1226 | match: "https?://(www\\.)?(cartoonmad|comicnad)\.com/comic/[0-9]+\.html", 1227 | img: 'tr:nth-child(5) > td > table > tbody > tr:nth-child(1) > td > a > img', 1228 | next: 'a.onpage+a', 1229 | curpage: 'a.onpage', 1230 | numpages: function() { 1231 | return extractInfo('select[name=jump]') - 1; 1232 | }, 1233 | nextchap: function() { 1234 | let filter = getEls('.pages').filter(function(i) { 1235 | return i.textContent.match('下一話'); 1236 | }); 1237 | return filter.length ? filter[0].href : null; 1238 | }, 1239 | prevchap: function() { 1240 | let filter = getEls('.pages').filter(function(i) { 1241 | return i.textContent.match('上一話'); 1242 | }); 1243 | return filter.length ? filter[0].href : null; 1244 | }, 1245 | }, { 1246 | name: 'ikanman', 1247 | match: "https?://(www|tw)\.(ikanman|manhuagui)\.com/comic/[0-9]+/[0-9]+\.html", 1248 | img: '#mangaFile', 1249 | next: function() { 1250 | return W._next; 1251 | }, 1252 | curpage: '#page', 1253 | numpages: '#pageSelect', 1254 | nextchap: function(prev) { 1255 | var chap = prev ? W._prevchap : W._nextchap; 1256 | if (chap > 0) { 1257 | return location.href.replace(/(\/comic\/[0-9]+\/)[0-9]+\.html.*/, "$1" + chap + ".html"); 1258 | } else { 1259 | return false; 1260 | } 1261 | }, 1262 | prevchap: function() { 1263 | return this.nextchap(true); 1264 | }, 1265 | wait: function() { 1266 | if (getEl('#mangaFile')) { 1267 | W._nextchap = W.cInfo.nextId; 1268 | W._prevchap = W.cInfo.prevId; 1269 | var ex = extractInfo.bind(this); 1270 | W._next = location.href.replace(/(_p[0-9]+)?\.html.*/, '_p' + (ex('curpage') + 1) + '.html'); 1271 | W._base = ex('img').replace(/[^\/]+$/, ''); 1272 | return true; 1273 | } 1274 | }, 1275 | pages: function(url, num, cb, ex) { 1276 | var nexturl = url.replace(/(_p[0-9]+)?\.html.*/, '_p' + (num + 1) + '.html'); 1277 | var imgurl = W._base + W.cInfo.files[num - 1]; 1278 | cb(imgurl, nexturl); 1279 | } 1280 | }, { 1281 | name: 'mangasail and mangatail', 1282 | match: 'https?://www\.manga(sail|tail)\.com/[^/]+', 1283 | img: '#images img', 1284 | next: '#images a', 1285 | curpage: '#edit-select-page', 1286 | numpages: '#edit-select-page', 1287 | nextchap: function(prev) { 1288 | return location.origin + '/node/' + extractInfo('#edit-select-node', {type: 'value', val: prev ? -1 : 1}); 1289 | }, 1290 | prevchap: function() { 1291 | return this.nextchap(true); 1292 | }, 1293 | curchap: '#select_node', 1294 | numchaps: '#select_node' 1295 | }, { 1296 | name: 'komikstation', 1297 | match: "^https?://www\.komikstation\.com/.+/.+/.+", 1298 | img: '#mainpage', 1299 | next: function() { 1300 | return W._base + '?page=' + (W.glbCurrentpage + 1); 1301 | }, 1302 | numpages: '#index select', 1303 | curpage: '#index select', 1304 | pages: function(url, num, cb, ex) { 1305 | next = W._base + '?page=' + (num + 1); 1306 | cb(W.pages[num - 1], next); 1307 | }, 1308 | wait: function() { 1309 | W._base = location.href.replace(/[?#].+$/, ''); 1310 | return W.pages; 1311 | } 1312 | }, { 1313 | name: 'gmanga', 1314 | match: "^https?://gmanga.me/mangas/", 1315 | img: function() { 1316 | return W.pages[W.firstImg - 1]; 1317 | }, 1318 | next: function() { 1319 | return location.href + '#' + (W.firstImg + 1); 1320 | }, 1321 | numpages: function() { 1322 | return W.totalImgs; 1323 | }, 1324 | curpage: function() { 1325 | return W.firstImg; 1326 | }, 1327 | nextchap: function(prev) { 1328 | var num = parseInt(extractInfo('#chapter', {type: 'value', val: prev ? 1 : -1})); 1329 | return num && location.href.replace(/(\/mangas\/[^\/]+\/)[0-9]+(\/[^\/]+)/, '$1' + num + '$2'); 1330 | }, 1331 | prevchap: function() { 1332 | return this.nextchap(true); 1333 | }, 1334 | numchaps: '#chapter', 1335 | curchap: '#chapter', 1336 | invchap: true, 1337 | pages: function(url, num, cb, ex) { 1338 | var nexturl = location.href + '#' + (num + 1); 1339 | cb(W.pages[num - 1], nexturl); 1340 | }, 1341 | wait: function() { 1342 | W.pages = W.release_pages && W.release_pages[1]; 1343 | return W.pages; 1344 | } 1345 | }, { 1346 | name: '930mh', 1347 | match: "http://www\.930mh\.com/manhua/\\d+/\\d+.html", 1348 | img: '#images > img', 1349 | next: function() { 1350 | return location.origin + location.pathname + '?p=' + (W.SinTheme.getPage() + 1); 1351 | }, 1352 | pages: function(url, num, cb, ex) { 1353 | cb(new URL(W.pageImage).origin + '/' + W.chapterPath + W.chapterImages[num - 1], num - 1); 1354 | }, 1355 | curpage: function() { 1356 | return W.SinTheme.getPage(); 1357 | }, 1358 | numpages: function() { 1359 | return W.chapterImages.length; 1360 | }, 1361 | nextchap: function(){ 1362 | return W.nextChapterData.id && W.nextChapterData.id > 0 ? W.comicUrl + W.nextChapterData.id + '.html' : null; 1363 | }, 1364 | prevchap: function(){ 1365 | return W.prevChapterData.id && W.prevChapterData.id > 0 ? W.comicUrl + W.prevChapterData.id + '.html' : null; 1366 | }, 1367 | wait: '#images > img' 1368 | }, { 1369 | name: '漫畫王', 1370 | match: "https://www\.mangabox\.me/reader/\\d+/episodes/\\d+/", 1371 | img: 'img.jsNext', 1372 | next: function() { 1373 | return '#'; 1374 | }, 1375 | pages: function(url, num, cb, ex) { 1376 | cb(W.pages[num - 1].src, num - 1); 1377 | }, 1378 | numpages: function() { 1379 | return W.pages.length; 1380 | }, 1381 | nextchap: '.lastSlider_nextButton', 1382 | wait: function (){ 1383 | W.pages = getEls('img.jsNext'); 1384 | return true; 1385 | } 1386 | }, { 1387 | name: '2comic.com 動漫易', 1388 | match: "http://twocomic.com/view/comic_\\d+.html", 1389 | img: '#TheImg', 1390 | next: function() { 1391 | return '#'; 1392 | }, 1393 | pages: function(url, num, cb, ex) { 1394 | W.p++; 1395 | var ss = W.ss; 1396 | var c = W.c; 1397 | var ti = W.ti; 1398 | var nn = W.nn; 1399 | var p = W.p; 1400 | var mm = W.mm; 1401 | var f = W.f; 1402 | var img = 'http://img' + ss(c, 4, 2) + '.8comic.com/' + ss(c, 6, 1) + '/' + ti + '/' + ss(c, 0, 4) + '/' + nn(p) + '_' + ss(c, mm(p) + 10, 3, f) + '.jpg'; 1403 | cb(img, num - 1); 1404 | }, 1405 | numpages: function() { 1406 | return W.ps * 1; 1407 | }, 1408 | curpage: function() { 1409 | return W.p; 1410 | }, 1411 | numchaps: function() { 1412 | return W.chs; 1413 | }, 1414 | curchap: function() { 1415 | return W.ch; 1416 | }, 1417 | nextchap: function() { 1418 | return W.ch < W.chs ? W.replaceurl('ch', W.ni) : false; 1419 | }, 1420 | prevchap: function() { 1421 | return W.ch > 1 ? W.replaceurl('ch', W.pi) : false; 1422 | }, 1423 | wait:'#TheImg' 1424 | }]; 1425 | // END OF IMPL 1426 | 1427 | var log = function(msg, type) { 1428 | type = type || 'log'; 1429 | if (type === 'exit') { 1430 | log('exit: ' + msg, 'error'); 1431 | throw 'mloader error'; 1432 | } else { 1433 | try { 1434 | console[type]('%c' + scriptName + ' ' + type + ':', 'font-weight:bold;color:green;', msg); 1435 | } catch(e) { } 1436 | } 1437 | }; 1438 | 1439 | var getEl = function(q, c) { 1440 | if (!q) return; 1441 | return (c || document).querySelector(q); 1442 | }; 1443 | 1444 | var getEls = function(q, c) { 1445 | return [].slice.call((c || document).querySelectorAll(q)); 1446 | }; 1447 | 1448 | var ajax = function(obj) { 1449 | var xhr = new XMLHttpRequest(); 1450 | xhr.open(obj.method || 'get', obj.url, obj.async || true); 1451 | xhr.onload = obj.onload; 1452 | xhr.onerror = obj.onerror; 1453 | xhr.responseType = obj.responseType || 'text'; 1454 | if(obj.beforeSend) obj.beforeSend(xhr); 1455 | xhr.send(obj.data); 1456 | }; 1457 | 1458 | var storeGet = function(key) { 1459 | var res; 1460 | if (typeof GM_getValue === "undefined") { 1461 | res = localStorage.getItem(key); 1462 | } else { 1463 | res = GM_getValue(key); 1464 | } 1465 | try { 1466 | return JSON.parse(res); 1467 | } catch(e) { 1468 | return res; 1469 | } 1470 | }; 1471 | 1472 | var storeSet = function(key, value) { 1473 | value = JSON.stringify(value); 1474 | if (typeof GM_setValue === "undefined") { 1475 | return localStorage.setItem(key, value); 1476 | } 1477 | return GM_setValue(key, value); 1478 | }; 1479 | 1480 | var storeDel = function(key) { 1481 | if (typeof GM_deleteValue === "undefined") { 1482 | return localStorage.removeItem(key); 1483 | } 1484 | return GM_deleteValue(key); 1485 | }; 1486 | 1487 | var areDefined = function() { 1488 | return [].every.call(arguments, function(arg) { 1489 | return arg !== undefined && arg !== null; 1490 | }); 1491 | }; 1492 | 1493 | var updateObj = function(orig, ext) { 1494 | var key; 1495 | for (key in ext) { 1496 | if (orig.hasOwnProperty(key) && ext.hasOwnProperty(key)) { 1497 | orig[key] = ext[key]; 1498 | } 1499 | } 1500 | return orig; 1501 | }; 1502 | 1503 | var extractInfo = function(selector, mod, context) { 1504 | selector = this[selector] || selector; 1505 | if (typeof selector === 'function') { 1506 | return selector.call(this, context); 1507 | } 1508 | var elem = getEl(selector, context), 1509 | option; 1510 | mod = mod || {}; 1511 | if (elem) { 1512 | switch (elem.nodeName.toLowerCase()) { 1513 | case 'img': 1514 | return (mod.altProp && elem.getAttribute(mod.altProp)) || elem.src || elem.getAttribute('src'); 1515 | case 'a': 1516 | if(mod.type === 'index') 1517 | return parseInt(elem.textContent); 1518 | return elem.href || elem.getAttribute('href'); 1519 | case 'ul': 1520 | return elem.children.length; 1521 | case 'select': 1522 | switch (mod.type) { 1523 | case 'index': 1524 | var idx = elem.options.selectedIndex + 1 + (mod.val || 0); 1525 | if(mod.invIdx) idx = elem.options.length - idx + 1; 1526 | return idx; 1527 | case 'value': 1528 | case 'text': 1529 | option = elem.options[elem.options.selectedIndex + (mod.val || 0)] || {}; 1530 | return mod.type === 'value' ? option.value : option.textContent; 1531 | default: 1532 | return elem.options.length; 1533 | } 1534 | break; 1535 | default: 1536 | switch (mod.type) { 1537 | case 'index': 1538 | return parseInt(elem.textContent); 1539 | default: 1540 | return elem.textContent; 1541 | } 1542 | } 1543 | } 1544 | return null; 1545 | }; 1546 | 1547 | var addStyle = function(id, replace) { 1548 | if(!this.MLStyles) this.MLStyles = {}; 1549 | if(!this.MLStyles[id]) { 1550 | this.MLStyles[id] = document.createElement('style'); 1551 | this.MLStyles[id].dataset.name = 'ml-style-' + id; 1552 | document.head.appendChild(this.MLStyles[id]); 1553 | } 1554 | var style = this.MLStyles[id]; 1555 | var css = [].slice.call(arguments, 2).join('\n'); 1556 | if(replace) { 1557 | style.textContent = css; 1558 | } else { 1559 | style.textContent += css; 1560 | } 1561 | }; 1562 | 1563 | var toStyleStr = function(obj, selector) { 1564 | var stack = [], 1565 | key; 1566 | for (key in obj) { 1567 | if (obj.hasOwnProperty(key)) { 1568 | stack.push(key + ':' + obj[key]); 1569 | } 1570 | } 1571 | if (selector) { 1572 | return selector + '{' + stack.join(';') + '}'; 1573 | } else { 1574 | return stack.join(';'); 1575 | } 1576 | }; 1577 | 1578 | var throttle = function(callback, limit) { 1579 | var wait = false; 1580 | return function() { 1581 | if (!wait) { 1582 | callback(); 1583 | wait = true; 1584 | setTimeout(function() { 1585 | wait = false; 1586 | }, limit); 1587 | } 1588 | }; 1589 | }; 1590 | 1591 | var query = function() { 1592 | var map = {}; 1593 | location.search.slice(1).split('&').forEach(function(pair) { 1594 | pair = pair.split('='); 1595 | map[pair[0]] = pair[1]; 1596 | }); 1597 | return map; 1598 | }; 1599 | 1600 | var createButton = function(text, action, styleStr) { 1601 | var button = document.createElement('button'); 1602 | button.textContent = text; 1603 | button.onclick = action; 1604 | button.setAttribute('style', styleStr || ''); 1605 | return button; 1606 | }; 1607 | 1608 | var getViewer = function(prevChapter, nextChapter) { 1609 | var viewerCss = toStyleStr({ 1610 | 'background-color': 'black !important', 1611 | 'font': '0.813em monospace !important', 1612 | 'text-align': 'center', 1613 | }, 'body'), 1614 | imagesCss = toStyleStr({ 1615 | 'margin-top': '10px', 1616 | 'margin-bottom': '10px', 1617 | 'transform-origin': 'top center' 1618 | }, '.ml-images'), 1619 | imageCss = toStyleStr({ 1620 | 'max-width': '100%', 1621 | 'display': 'block', 1622 | 'margin': '3px auto' 1623 | }, '.ml-images img'), 1624 | counterCss = toStyleStr({ 1625 | 'background-color': '#222', 1626 | 'color': 'white', 1627 | 'border-radius': '10px', 1628 | 'width': '30px', 1629 | 'margin-left': 'auto', 1630 | 'margin-right': 'auto', 1631 | 'margin-top': '-12px', 1632 | 'padding-left': '5px', 1633 | 'padding-right': '5px', 1634 | 'border': '1px solid white', 1635 | 'z-index': '100', 1636 | 'position': 'relative' 1637 | }, '.ml-counter'), 1638 | navCss = toStyleStr({ 1639 | 'text-decoration': 'none', 1640 | 'color': 'white', 1641 | 'background-color': '#444', 1642 | 'padding': '3px 10px', 1643 | 'border-radius': '5px', 1644 | 'transition': '250ms' 1645 | }, '.ml-chap-nav a'), 1646 | navHoverCss = toStyleStr({ 1647 | 'background-color': '#555' 1648 | }, '.ml-chap-nav a:hover'), 1649 | boxCss = toStyleStr({ 1650 | 'position': 'fixed', 1651 | 'background-color': '#222', 1652 | 'color': 'white', 1653 | 'padding': '7px', 1654 | 'border-top-left-radius': '5px', 1655 | 'cursor': 'default' 1656 | }, '.ml-box'), 1657 | statsCss = toStyleStr({ 1658 | 'bottom': '0', 1659 | 'right': '0', 1660 | 'opacity': '0.4', 1661 | 'transition': '250ms' 1662 | }, '.ml-stats'), 1663 | statsCollapseCss = toStyleStr({ 1664 | 'color': 'orange', 1665 | 'cursor': 'pointer' 1666 | }, '.ml-stats-collapse'), 1667 | statsHoverCss = toStyleStr({ 1668 | 'opacity': '1' 1669 | }, '.ml-stats:hover'), 1670 | floatingMsgCss = toStyleStr({ 1671 | 'bottom': '30px', 1672 | 'right': '0', 1673 | 'border-bottom-left-radius': '5px', 1674 | 'text-align': 'left', 1675 | 'font': 'inherit', 1676 | 'max-width': '95%', 1677 | 'z-index': '101', 1678 | 'white-space': 'pre-wrap' 1679 | }, '.ml-floating-msg'), 1680 | floatingMsgAnchorCss = toStyleStr({ 1681 | 'color': 'orange' 1682 | }, '.ml-floating-msg a'), 1683 | buttonCss = toStyleStr({ 1684 | 'cursor': 'pointer' 1685 | }, '.ml-button'), 1686 | keySettingCss = toStyleStr({ 1687 | 'width': '35px' 1688 | }, '.ml-setting-key input'), 1689 | autoloadSettingCss = toStyleStr({ 1690 | 'vertical-align': 'middle' 1691 | }, '.ml-setting-autoload'); 1692 | // clear all styles and scripts 1693 | var title = document.title; 1694 | document.head.innerHTML = ''; 1695 | document.title = title; 1696 | document.body.className = ''; 1697 | document.body.style = ''; 1698 | // navigation 1699 | var nav = '
' + (prevChapter ? 'Prev Chapter ' : '') + 1700 | 'Exit ' + 1701 | (nextChapter ? 'Next Chapter' : '') + '
'; 1702 | // message area 1703 | var floatingMsg = '
';
1704 |   // stats
1705 |   var stats = '
>> ' + 1706 | ' ' + 1707 | ' ' + 1708 | ' ' + 1709 | '
'; 1710 | // combine ui elements 1711 | document.body.innerHTML = nav + '
' + nav + floatingMsg + stats; 1712 | // add main styles 1713 | addStyle('main', true, viewerCss, imagesCss, imageCss, counterCss, navCss, navHoverCss, statsCss, statsCollapseCss, statsHoverCss, boxCss, floatingMsgCss, buttonCss, keySettingCss, autoloadSettingCss, floatingMsgAnchorCss); 1714 | // add user styles 1715 | var userCss = storeGet('ml-setting-css-profiles'); 1716 | var curProf = storeGet('ml-setting-css-current') || 'Default'; 1717 | if(userCss && userCss.length > 0) userCss = userCss.filter(function(p) { return p.name === curProf; }); 1718 | userCss = userCss && userCss.length > 0 ? userCss[0].css : (storeGet('ml-setting-css') || ''); 1719 | addStyle('user', true, userCss); 1720 | // set up return UI object 1721 | var UI = { 1722 | images: getEl('.ml-images'), 1723 | statsContent: getEl('.ml-stats-content'), 1724 | statsPages: getEl('.ml-stats-pages'), 1725 | statsCollapse: getEl('.ml-stats-collapse'), 1726 | btnManualReload: getEl('.ml-manual-reload'), 1727 | btnInfo: getEl('.ml-info-button'), 1728 | btnMoreStats: getEl('.ml-more-stats-button'), 1729 | floatingMsg: getEl('.ml-floating-msg'), 1730 | btnNextChap: getEl('.ml-chap-next'), 1731 | btnPrevChap: getEl('.ml-chap-prev'), 1732 | btnExit: getEl('.ml-exit'), 1733 | btnSettings: getEl('.ml-settings-button'), 1734 | isTyping: false, 1735 | ignore: false, 1736 | moreStats: false, 1737 | currentProfile: storeGet('ml-setting-css-current') || '' 1738 | }; 1739 | // message func 1740 | var messageId = null; 1741 | var showFloatingMsg = function(msg, timeout, html) { 1742 | clearTimeout(messageId); 1743 | log(msg); 1744 | if(html) { 1745 | UI.floatingMsg.innerHTML = msg; 1746 | } else { 1747 | UI.floatingMsg.textContent = msg; 1748 | } 1749 | if(!msg) UI.moreStats = false; 1750 | UI.floatingMsg.style.display = msg ? '' : 'none'; 1751 | if(timeout) { 1752 | messageId = setTimeout(function() { 1753 | showFloatingMsg(''); 1754 | }, timeout); 1755 | } 1756 | }; 1757 | var isMessageFloating = function() { 1758 | return !!UI.floatingMsg.innerHTML; 1759 | }; 1760 | // configure initial state 1761 | UI.floatingMsg.style.display = 'none'; 1762 | // set up listeners 1763 | document.addEventListener('click', function(evt) { 1764 | if (evt.target.nodeName === 'A' && evt.button !== 2) { 1765 | var shouldReload = evt.target.href.indexOf('#') !== -1 && evt.target.href.split('#')[0] === document.location.href.split('#')[0] && evt.button === 0; // fix for batoto https weirdness 1766 | if(evt.target.className.indexOf('ml-chap') !== -1) { 1767 | log('next chapter will autoload'); 1768 | storeSet('autoload', 'yes'); 1769 | if(shouldReload) { 1770 | evt.preventDefault(); 1771 | location.href = evt.target.href; 1772 | location.reload(true); 1773 | } 1774 | } else if(evt.target.className.indexOf('ml-exit') !== -1) { 1775 | log('exiting chapter, stop autoload'); 1776 | storeSet('autoload', 'no'); 1777 | if(shouldReload) { 1778 | evt.preventDefault(); 1779 | location.reload(true); 1780 | } 1781 | } 1782 | } 1783 | }); 1784 | UI.btnMoreStats.addEventListener('click', function(evt) { 1785 | if(isMessageFloating() && UI.lastFloat === evt.target) { 1786 | showFloatingMsg(''); 1787 | } else { 1788 | UI.lastFloat = evt.target; 1789 | UI.moreStats = true; 1790 | showFloatingMsg([ 1791 | 'Stats:', 1792 | pageStats.loadLimit + ' pages parsed', 1793 | pageStats.numLoaded + ' images loaded', 1794 | (pageStats.loadLimit - pageStats.numLoaded) + ' images loading', 1795 | (pageStats.numPages || 'Unknown number of') + ' pages in chapter', 1796 | (pageStats.curChap !== null && pageStats.numChaps !== null ? ((pageStats.curChap - 1) + '/' + pageStats.numChaps + ' chapters read ' + (((pageStats.curChap - 1) / pageStats.numChaps * 100).toFixed(2) + '%') + ' of series') : ''), 1797 | ].join('
'), null, true); 1798 | } 1799 | }); 1800 | UI.btnManualReload.addEventListener('click', function(evt) { 1801 | var imgClick = function(e) { 1802 | var target = e.target; 1803 | UI.images.removeEventListener('click', imgClick, false); 1804 | UI.images.style.cursor = ''; 1805 | if(target.nodeName === 'IMG' && target.parentNode.className === 'ml-images') { 1806 | showFloatingMsg(''); 1807 | if(!target.title) { 1808 | showFloatingMsg('Reloading "' + target.src + '"', 3000); 1809 | if(target.complete) target.onload = null; 1810 | target.src = target.src + (target.src.indexOf('?') !== -1 ? '&' : '?') + new Date().getTime(); 1811 | } 1812 | } else { 1813 | showFloatingMsg('Cancelled manual reload...', 3000); 1814 | } 1815 | }; 1816 | showFloatingMsg('Left click the image you would like to reload.\nClick on the page margin to cancel.'); 1817 | UI.images.style.cursor = 'pointer'; 1818 | UI.images.addEventListener('click', imgClick, false); 1819 | }); 1820 | UI.statsCollapse.addEventListener('click', function(evt) { 1821 | var test = UI.statsCollapse.textContent === '>>'; 1822 | storeSet('ml-stats-collapsed', test); 1823 | UI.statsContent.style.display = test ? 'none' : ''; 1824 | UI.statsCollapse.textContent = test ? '<<' : '>>'; 1825 | }); 1826 | // restore collapse state 1827 | if(storeGet('ml-stats-collapsed')) UI.statsCollapse.click(); 1828 | UI.floatingMsg.addEventListener('focus', function(evt) { 1829 | var target = evt.target; 1830 | if(target.dataset.ignore) UI.ignore = true; 1831 | if((target.nodeName === 'INPUT' && target.type === 'text') || target.nodeName === 'TEXTAREA') UI.isTyping = true; 1832 | }, true); 1833 | UI.floatingMsg.addEventListener('blur', function(evt) { 1834 | var target = evt.target; 1835 | if(target.dataset.ignore) UI.ignore = false; 1836 | if((target.nodeName === 'INPUT' && target.type === 'text') || target.nodeName === 'TEXTAREA') UI.isTyping = false; 1837 | }, true); 1838 | UI.btnInfo.addEventListener('click', function(evt) { 1839 | if(isMessageFloating() && UI.lastFloat === evt.target) { 1840 | showFloatingMsg(''); 1841 | } else { 1842 | UI.lastFloat = evt.target; 1843 | showFloatingMsg([ 1844 | 'Information:', 1845 | 'IMPORTANT: The script has been updated to exclude NSFW sites', 1846 | 'in order to gain access to that functionality you\'ll have to install the following addon script.', 1847 | 'https://sleazyfork.org/en/scripts/12657-manga-loader-nsfw', 1848 | '', 1849 | 'New feature! You can now define custom CSS in the new settings panel (accessible through the gear icon at the bottom left).', 1850 | 'The CSS will be saved and reapplied each time the script loads. You can change the background color of the page,', 1851 | 'the width of the images and pretty much anything else.', 1852 | '', 1853 | 'CSS feature has now been enhanced to support multiple profiles you can switch between.', 1854 | '', 1855 | 'Default Keybindings:', 1856 | 'Z - previous chapter', 1857 | 'X - exit', 1858 | 'C - next chapter', 1859 | 'W - scroll up', 1860 | 'S - scroll down', 1861 | '+ - zoom in', 1862 | '- - zoom out', 1863 | '0 - reset zoom', 1864 | 'Click the info button again to close this message.' 1865 | ].join('
'), null, true); 1866 | } 1867 | }); 1868 | UI.btnSettings.addEventListener('click', function(evt) { 1869 | if(isMessageFloating() && UI.lastFloat === evt.target) { 1870 | showFloatingMsg(''); 1871 | } else { 1872 | UI.lastFloat = evt.target; 1873 | // start grid and first column 1874 | var settings = ''; 1893 | }).join(''); 1894 | settings += 'Keybindings:
'; 1875 | // Custom CSS 1876 | var cssProfiles = storeGet('ml-setting-css-profiles'); 1877 | if(!cssProfiles || cssProfiles.length === 0) { 1878 | cssProfiles = [{name: 'Default', css: storeGet('ml-setting-css') || ''}]; 1879 | storeSet('ml-setting-css-profiles', cssProfiles); 1880 | } 1881 | cssProfiles.push({ name: 'New Profile...', addNew: true }); 1882 | var prof = cssProfiles.filter(function(p) { return p.name === UI.currentProfile; })[0] || cssProfiles[0]; 1883 | settings += 'CSS (custom css for Manga Loader):
' + 1884 | '
' + 1887 | '

'; 1888 | // start new column 1889 | settings += '
'; 1890 | // Keybindings 1891 | var keyTableHtml = Object.keys(UI.keys).map(function(action) { 1892 | return '
' + action + '
' + keyTableHtml + '

'; 1895 | // Autoload 1896 | settings += 'Auto-load:

'; 1897 | // Load all or just N pages 1898 | settings += "# of pages to load:
" + 1899 | 'Type "all" to load all
default is 10
' + 1900 | '

'; 1901 | // close grid and column 1902 | settings += ''; 1903 | // Save button 1904 | settings += ' '; 1905 | showFloatingMsg(settings, null, true); 1906 | // handle keybinding detection 1907 | getEl('.ml-setting-key').onkeydown = function(e) { 1908 | var target = e.target; 1909 | if(target.nodeName.toUpperCase() === 'INPUT') { 1910 | e.preventDefault(); 1911 | e.stopPropagation(); 1912 | target.value = e.which || e.charCode || e.keyCode; 1913 | } 1914 | }; 1915 | // delete css profile 1916 | getEl('.ml-setting-delete-profile', UI.floatingMsg).onclick = function(e) { 1917 | if(['Default', 'New Profile...'].indexOf(prof.name) === -1) { 1918 | if(confirm('Are you sure you want to delete profile "' + prof.name + '"?')) { 1919 | var index = cssProfiles.indexOf(prof); 1920 | cssProfiles.splice(index, 1); 1921 | var sel = getEl('.ml-setting-css-profile'); 1922 | sel.remove(index); 1923 | sel.selectedIndex = 0; 1924 | sel.onchange({target: sel}); 1925 | } 1926 | } else { 1927 | alert('Cannot delete profile: "' + prof.name + '"'); 1928 | } 1929 | }; 1930 | // change selected css profile 1931 | getEl('.ml-setting-css-profile', UI.floatingMsg).onchange = function(e) { 1932 | var cssBox = getEl('.ml-setting-css'); 1933 | prof.css = cssBox.value; 1934 | prof = cssProfiles[e.target.selectedIndex]; 1935 | if(prof.addNew) { 1936 | // enter new name 1937 | var newName = ''; 1938 | while(!newName || cssProfiles.filter(function(p) { return p.name === newName; }).length > 0) { 1939 | newName = prompt('Enter the name for the new profile (must be unique)'); 1940 | if(!newName) { 1941 | e.target.selectedIndex = 0; 1942 | e.target.onchange({target: e.target}); 1943 | return; 1944 | } 1945 | } 1946 | // add new profile to array 1947 | var last = cssProfiles.pop(); 1948 | cssProfiles.push({name: newName, css: ''}, last); 1949 | prof = cssProfiles[cssProfiles.length - 2]; 1950 | // add new profile to select box 1951 | var option = document.createElement('option'); 1952 | option.text = newName; 1953 | e.target.add(option, e.target.options.length - 1); 1954 | e.target.selectedIndex = e.target.options.length - 2; 1955 | } 1956 | cssBox.value = prof.css; 1957 | UI.currentProfile = prof.name; 1958 | addStyle('user', true, prof.css); 1959 | }; 1960 | // handle save button 1961 | getEl('.ml-setting-save', UI.floatingMsg).onclick = function() { 1962 | // persist css 1963 | var css = getEl('.ml-setting-css', UI.floatingMsg).value.trim(); 1964 | prof.css = css; 1965 | addStyle('user', true, css); 1966 | var last = cssProfiles.pop(); 1967 | storeSet('ml-setting-css-profiles', cssProfiles); 1968 | cssProfiles.push(last); 1969 | storeSet('ml-setting-css-current', UI.currentProfile); 1970 | // keybindings 1971 | getEls('.ml-setting-key input').forEach(function(input) { 1972 | UI.keys[input.dataset.key] = parseInt(input.value); 1973 | }); 1974 | storeSet('ml-setting-key', UI.keys); 1975 | // autoload 1976 | storeSet('mAutoload', getEl('.ml-setting-autoload').checked); 1977 | // loadnum 1978 | var loadnum = getEl('.ml-setting-loadnum').value; 1979 | mLoadNum = getEl('.ml-setting-loadnum').value = loadnum.toLowerCase() === 'all' ? 'all' : (parseInt(loadnum) || 10); 1980 | storeSet('mLoadNum', mLoadNum); 1981 | // flash notify 1982 | var flash = getEl('.ml-setting-save-flash'); 1983 | flash.textContent = 'Saved!'; 1984 | setTimeout(function() { flash.textContent = ''; }, 1000); 1985 | }; 1986 | // handle close button 1987 | getEl('.ml-setting-close', UI.floatingMsg).onclick = function() { 1988 | showFloatingMsg(''); 1989 | }; 1990 | } 1991 | }); 1992 | // zoom 1993 | var lastZoom, originalZoom,newZoomPostion; 1994 | var changeZoom = function(action, elem) { 1995 | var ratioZoom = (document.documentElement.scrollTop || document.body.scrollTop)/(document.documentElement.scrollHeight || document.body.scrollHeight); 1996 | var curImage = getCurrentImage(); 1997 | if(!lastZoom) { 1998 | lastZoom = originalZoom = Math.round(curImage.clientWidth / window.innerWidth * 100); 1999 | } 2000 | var zoom = lastZoom; 2001 | if(action === '+') zoom += 5; 2002 | if(action === '-') zoom -= 5; 2003 | if(action === '=') { 2004 | lastZoom = originalZoom; 2005 | addStyle('image-width', true, ''); 2006 | showFloatingMsg('reset zoom', 500); 2007 | newZoomPostion =(document.documentElement.scrollHeight || document.body.scrollHeight)*ratioZoom; 2008 | window.scroll(0, newZoomPostion); 2009 | return; 2010 | } 2011 | zoom = Math.max(10, Math.min(zoom, 100)); 2012 | lastZoom = zoom; 2013 | addStyle('image-width', true, toStyleStr({ 2014 | width: zoom + '%' 2015 | }, '.ml-images img')); 2016 | showFloatingMsg('zoom: ' + zoom + '%', 500); 2017 | newZoomPostion =(document.documentElement.scrollHeight || document.body.scrollHeight)*ratioZoom; 2018 | window.scroll(0, newZoomPostion); 2019 | }; 2020 | var goToPage = function(toWhichPage) { 2021 | var curId = getCurrentImage().id; 2022 | var nextId = curId.split('-'); 2023 | switch (toWhichPage) { 2024 | case 'next': 2025 | nextId[2] = parseInt(nextId[2]) + 1; 2026 | break; 2027 | case 'previous': 2028 | nextId[2] = parseInt(nextId[2]) - 1; 2029 | break; 2030 | } 2031 | var nextPage = getEl('#' + nextId.join('-')); 2032 | if (nextPage == null) { 2033 | log(curId + " > " + nextId); 2034 | log("Reached the end!"); 2035 | } else { 2036 | nextPage.scrollIntoView(); 2037 | } 2038 | } 2039 | // keybindings 2040 | UI.keys = { 2041 | PREV_CHAP: 90, EXIT: 88, NEXT_CHAP: 67, 2042 | SCROLL_UP: 87, SCROLL_DOWN: 83, 2043 | ZOOM_IN: 187, ZOOM_OUT: 189, RESET_ZOOM: 48, 2044 | PREV_PAGE: 37, NEXT_PAGE: 39, 2045 | }; 2046 | // override defaults for firefox since different keycodes 2047 | if(typeof InstallTrigger !== 'undefined') { 2048 | UI.keys.ZOOM_IN = 61; 2049 | UI.keys.ZOOM_OUT = 173; 2050 | UI.keys.RESET_ZOOM = 48; 2051 | } 2052 | UI.scrollAmt = 50; 2053 | // override the defaults with the user defined ones 2054 | updateObj(UI.keys, storeGet('ml-setting-key') || {}); 2055 | UI._keys = {}; 2056 | Object.keys(UI.keys).forEach(function(action) { 2057 | UI._keys[UI.keys[action]] = action; 2058 | }); 2059 | window.addEventListener('keydown', function(evt) { 2060 | // ignore keybindings when text input is focused 2061 | if(UI.isTyping) { 2062 | if(!UI.ignore) evt.stopPropagation(); 2063 | return; 2064 | } 2065 | var code = evt.which || evt.charCode || evt.keyCode; 2066 | // stop propagation if key is registered 2067 | if(code in UI.keys) evt.stopPropagation(); 2068 | // perform action 2069 | switch(code) { 2070 | case UI.keys.PREV_CHAP: 2071 | if(UI.btnPrevChap) { 2072 | UI.btnPrevChap.click(); 2073 | } 2074 | break; 2075 | case UI.keys.EXIT: 2076 | UI.btnExit.click(); 2077 | break; 2078 | case UI.keys.NEXT_CHAP: 2079 | if(UI.btnNextChap) { 2080 | UI.btnNextChap.click(); 2081 | } 2082 | break; 2083 | case UI.keys.SCROLL_UP: 2084 | window.scrollBy(0, -UI.scrollAmt); 2085 | break; 2086 | case UI.keys.SCROLL_DOWN: 2087 | window.scrollBy(0, UI.scrollAmt); 2088 | break; 2089 | case UI.keys.ZOOM_IN: 2090 | changeZoom('+', UI.images); 2091 | break; 2092 | case UI.keys.ZOOM_OUT: 2093 | changeZoom('-', UI.images); 2094 | break; 2095 | case UI.keys.RESET_ZOOM: 2096 | changeZoom('=', UI.images); 2097 | break; 2098 | case UI.keys.NEXT_PAGE: 2099 | goToPage('next'); 2100 | break; 2101 | case UI.keys.PREV_PAGE: 2102 | goToPage('previous'); 2103 | break; 2104 | } 2105 | }, true); 2106 | return UI; 2107 | }; 2108 | 2109 | var getCurrentImage = function() { 2110 | var image; 2111 | getEls('.ml-images img').some(function(img) { 2112 | image = img; 2113 | return img.getBoundingClientRect().bottom > 200; 2114 | }); 2115 | return image; 2116 | }; 2117 | 2118 | var getCounter = function(imgNum) { 2119 | var counter = document.createElement('div'); 2120 | counter.classList.add('ml-counter'); 2121 | counter.textContent = imgNum; 2122 | return counter; 2123 | }; 2124 | 2125 | var addImage = function(src, loc, imgNum, callback) { 2126 | var image = new Image(), 2127 | counter = getCounter(imgNum); 2128 | image.onerror = function() { 2129 | log('failed to load ' + src); 2130 | image.onload = null; 2131 | image.style.backgroundColor = 'white'; 2132 | image.style.cursor = 'pointer'; 2133 | image.title = 'Reload "' + src + '"?'; 2134 | image.src = IMAGES.refresh_large; 2135 | image.onclick = function() { 2136 | image.onload = callback; 2137 | image.title = ''; 2138 | image.style.cursor = ''; 2139 | image.src = src; 2140 | }; 2141 | }; 2142 | image.id = 'ml-pageid-' + imgNum; 2143 | image.onload = callback; 2144 | image.src = src; 2145 | loc.appendChild(image); 2146 | loc.appendChild(counter); 2147 | }; 2148 | 2149 | var loadManga = function(imp) { 2150 | var ex = extractInfo.bind(imp), 2151 | imgUrl = ex('img', imp.imgmod), 2152 | nextUrl = ex('next'), 2153 | numPages = ex('numpages'), 2154 | curPage = ex('curpage', { 2155 | type: 'index' 2156 | }) || 1, 2157 | nextChapter = ex('nextchap', { 2158 | type: 'value', 2159 | val: (imp.invchap && -1) || 1 2160 | }), 2161 | prevChapter = ex('prevchap', { 2162 | type: 'value', 2163 | val: (imp.invchap && 1) || -1 2164 | }), 2165 | xhr = new XMLHttpRequest(), 2166 | d = document.implementation.createHTMLDocument(), 2167 | addAndLoad = function(img, next) { 2168 | if(!img) throw new Error('failed to retrieve img for page ' + curPage); 2169 | updateStats(); 2170 | addImage(img, UI.images, curPage, function() { 2171 | pagesLoaded += 1; 2172 | updateStats(); 2173 | }); 2174 | if(!next && curPage < numPages) throw new Error('failed to retrieve next url for page ' + curPage); 2175 | loadNextPage(next); 2176 | }, 2177 | updateStats = function() { 2178 | updateObj(pageStats, { 2179 | numLoaded: pagesLoaded, 2180 | loadLimit: curPage, 2181 | numPages: numPages 2182 | }); 2183 | if(UI.moreStats) { 2184 | for(var i=2;i--;) UI.btnMoreStats.click(); 2185 | } 2186 | UI.statsPages.textContent = ' ' + pagesLoaded + (numPages ? '/' + numPages : '') + ' loaded'; 2187 | }, 2188 | getPageInfo = function() { 2189 | var page = d.body; 2190 | d.body.innerHTML = xhr.response; 2191 | try { 2192 | // find image and link to next page 2193 | addAndLoad(ex('img', imp.imgmod, page), ex('next', null, page)); 2194 | } catch (e) { 2195 | if (xhr.status == 503 && retries > 0) { 2196 | log('xhr status ' + xhr.status + ' retrieving ' + xhr.responseURL + ', ' + retries-- + ' retries remaining'); 2197 | window.setTimeout(function() { 2198 | xhr.open('get', xhr.responseURL); 2199 | xhr.send(); 2200 | }, 500); 2201 | } else { 2202 | log(e); 2203 | log('error getting details from next page, assuming end of chapter.'); 2204 | } 2205 | } 2206 | }, 2207 | loadNextPage = function(url) { 2208 | if (mLoadNum !== 'all' && count % mLoadNum === 0) { 2209 | if (resumeUrl) { 2210 | resumeUrl = null; 2211 | } else { 2212 | resumeUrl = url; 2213 | log('waiting for user to scroll further before loading more images, loaded ' + count + ' pages so far, next url is ' + resumeUrl); 2214 | return; 2215 | } 2216 | } 2217 | if (numPages && curPage + 1 > numPages) { 2218 | log('reached "numPages" ' + numPages + ', assuming end of chapter'); 2219 | return; 2220 | } 2221 | if (lastUrl === url) { 2222 | log('last url (' + lastUrl + ') is the same as current (' + url + '), assuming end of chapter'); 2223 | return; 2224 | } 2225 | curPage += 1; 2226 | count += 1; 2227 | lastUrl = url; 2228 | retries = 5; 2229 | if (imp.pages) { 2230 | imp.pages(url, curPage, addAndLoad, ex, getPageInfo); 2231 | } else { 2232 | var colonIdx = url.indexOf(':'); 2233 | if(colonIdx > -1) { 2234 | url = location.protocol + url.slice(colonIdx + 1); 2235 | } 2236 | xhr.open('get', url); 2237 | imp.beforexhr && imp.beforexhr(xhr); 2238 | xhr.onload = getPageInfo; 2239 | xhr.onerror = function() { 2240 | log('failed to load page, aborting', 'error'); 2241 | }; 2242 | xhr.send(); 2243 | } 2244 | }, 2245 | count = 1, 2246 | pagesLoaded = curPage - 1, 2247 | lastUrl, UI, resumeUrl, retries; 2248 | if (!imgUrl || (!nextUrl && curPage < numPages)) { 2249 | log('failed to retrieve ' + (!imgUrl ? 'image url' : 'next page url'), 'exit'); 2250 | } 2251 | 2252 | // gather chapter stats 2253 | pageStats.curChap = ex('curchap', { 2254 | type: 'index', 2255 | invIdx: !!imp.invchap 2256 | }); 2257 | pageStats.numChaps = ex('numchaps'); 2258 | 2259 | // do some checks on the chapter urls 2260 | nextChapter = (nextChapter && nextChapter.trim() === location.href + '#' ? null : nextChapter); 2261 | prevChapter = (prevChapter && prevChapter.trim() === location.href + '#' ? null : prevChapter); 2262 | 2263 | UI = getViewer(prevChapter, nextChapter); 2264 | 2265 | UI.statsPages.textContent = ' 0/1 loaded, ' + numPages + ' total'; 2266 | 2267 | if (mLoadNum !== 'all') { 2268 | window.addEventListener('scroll', throttle(function(e) { 2269 | if (!resumeUrl) return; // exit early if we don't have a position to resume at 2270 | if(!UI.imageHeight) { 2271 | UI.imageHeight = getEl('.ml-images img').clientHeight; 2272 | } 2273 | var scrollBottom = document.body.scrollHeight - ((document.body.scrollTop || document.documentElement.scrollTop) + window.innerHeight); 2274 | if (scrollBottom < UI.imageHeight * 2) { 2275 | log('user scroll nearing end, loading more images starting from ' + resumeUrl); 2276 | loadNextPage(resumeUrl); 2277 | } 2278 | }, 100)); 2279 | } 2280 | 2281 | addAndLoad(imgUrl, nextUrl); 2282 | 2283 | }; 2284 | 2285 | var waitAndLoad = function(imp) { 2286 | isLoaded = true; 2287 | if(imp.wait) { 2288 | var waitType = typeof imp.wait; 2289 | if(waitType === 'number') { 2290 | setTimeout(loadManga.bind(null, imp), imp.wait || 0); 2291 | } else { 2292 | var isReady = waitType === 'function' ? imp.wait.bind(imp) : function() { 2293 | return getEl(imp.wait); 2294 | }; 2295 | var intervalId = setInterval(function() { 2296 | if(isReady()) { 2297 | log('Condition fulfilled, loading'); 2298 | clearInterval(intervalId); 2299 | loadManga(imp); 2300 | } 2301 | }, 200); 2302 | } 2303 | } else { 2304 | loadManga(imp); 2305 | } 2306 | }; 2307 | 2308 | var MLoaderLoadImps = function(imps) { 2309 | var success = imps.some(function(imp) { 2310 | if (imp.match && (new RegExp(imp.match, 'i')).test(pageUrl)) { 2311 | currentImpName = imp.name; 2312 | if (W.BM_MODE || (autoload !== 'no' && (mAutoload || autoload))) { 2313 | log('autoloading...'); 2314 | waitAndLoad(imp); 2315 | return true; 2316 | } 2317 | // setup load hotkey 2318 | var loadHotKey = function(e) { 2319 | if(e.ctrlKey && e.keyCode == 188) { // ctrl + , (comma) 2320 | e.preventDefault(); 2321 | btnLoad.click(); 2322 | window.removeEventListener('keydown', loadHotKey); 2323 | } 2324 | }; 2325 | window.addEventListener('keydown', loadHotKey); 2326 | // append button to dom that will trigger the page load 2327 | btnLoad = createButton('Load Manga', function(evt) { 2328 | waitAndLoad(imp); 2329 | this.remove(); 2330 | }, btnLoadCss); 2331 | document.body.appendChild(btnLoad); 2332 | return true; 2333 | } 2334 | }); 2335 | 2336 | if (!success) { 2337 | log('no implementation for ' + pageUrl, 'error'); 2338 | } 2339 | }; 2340 | 2341 | var pageUrl = window.location.href, 2342 | btnLoadCss = toStyleStr({ 2343 | 'position': 'fixed', 2344 | 'bottom': 0, 2345 | 'right': 0, 2346 | 'padding': '5px', 2347 | 'margin': '0 10px 10px 0', 2348 | 'z-index': '9999999999' 2349 | }), 2350 | currentImpName, btnLoad; 2351 | 2352 | // indicates whether UI loaded 2353 | var isLoaded = false; 2354 | // used when switching chapters 2355 | var autoload = storeGet('autoload'); 2356 | // manually set by user in menu 2357 | var mAutoload = storeGet('mAutoload') || false; 2358 | // should we load less pages at a time? 2359 | var mLoadNum = storeGet('mLoadNum') || 10; 2360 | // holder for statistics 2361 | var pageStats = { 2362 | numPages: null, numLoaded: null, loadLimit: null, curChap: null, numChaps: null 2363 | }; 2364 | 2365 | // clear autoload 2366 | storeDel('autoload'); 2367 | 2368 | log('starting...'); 2369 | 2370 | // extra check for settings (hack) on dumb firefox/scriptish, settings aren't udpated until document end 2371 | W.document.addEventListener('DOMContentLoaded', function(e) { 2372 | if(!isLoaded) return; 2373 | // used when switching chapters 2374 | autoload = storeGet('autoload'); 2375 | // manually set by user in menu 2376 | mAutoload = storeGet('mAutoload') || false; 2377 | // should we load less pages at a time? 2378 | mLoadNum = storeGet('mLoadNum') || 10; 2379 | if(autoload || mAutoload) { 2380 | btnLoad.click(); 2381 | } 2382 | }); 2383 | MLoaderLoadImps(implementations); 2384 | --------------------------------------------------------------------------------