Manga Loader NSFW + Download

This is an unoficial fork of https://greasyfork.org/fr/scripts/12657-manga-loader-nsfw all credits goes to the original author, This script add a button to download the chapter

  1. // ==UserScript==
  2. // @name Manga Loader NSFW + Download
  3. // @version 1.0.0.1
  4. // @description This is an unoficial fork of https://greasyfork.org/fr/scripts/12657-manga-loader-nsfw all credits goes to the original author, This script add a button to download the chapter
  5. // @copyright 2023+, viatana35
  6. // @require https://cdn.jsdelivr.net/npm/jszip@3.1.5/dist/jszip.min.js
  7. // @noframes
  8. // @grant GM_getValue
  9. // @grant GM_setValue
  10. // @grant GM_deleteValue
  11. // -- NSFW START
  12. // @match *://dynasty-scans.com/chapters/*
  13. // @match *://hentaifr.net/*
  14. // @match *://prismblush.com/comic/*
  15. // @match *://www.hentairules.net/galleries*/picture.php*
  16. // @match *://pururin.us/read/*
  17. // @match *://hitomi.la/reader/*
  18. // @match *://*.doujins.com/*
  19. // @match *://www.8muses.com/comix/picture/*/*/*/*
  20. // @match *://nowshelf.com/watch/*
  21. // @match *://nhentai.net/g/*/*
  22. // @match *://e-hentai.org/s/*/*
  23. // @match *://exhentai.org/s/*/*
  24. // @match *://www.fakku.net/*/*/read*
  25. // @match *://hentaihere.com/m/*/*/*
  26. // @match *://www.hentaihere.com/m/*/*/*
  27. // @match *://*.tsumino.com/Read/View/*
  28. // @match *://www.hentaibox.net/*/*
  29. // @match *://*.hentai-free.org/*
  30. // @match *://*.mangafap.com/image/*
  31. // @match *://*.hentai4manga.com/hentai_manga/*
  32. // @match *://*.heymanga.me/manga/*
  33. // @match *://*.simply-hentai.com/*/page/*
  34. // @match *://*.gameofscanlation.moe/projects/*/*
  35. // @match *://*.luscious.net/c/*/pictures/album/*/id/*
  36. // @match *://*.hentaifox.com/g/*
  37. // @match *://*.hentai2read.com/*/*/*
  38. // @match *://*.hentai.ms/manga/*/*
  39. // -- NSFW END
  40. // -- FOOLSLIDE NSFW START
  41. // @match *://reader.yuriproject.net/read/*
  42. // @match *://ecchi.japanzai.com/read/*
  43. // @match *://h.japanzai.com/read/*
  44. // @match *://reader.japanzai.com/read/*
  45. // @match *://yomanga.co/reader/read/*
  46. // @match *://raws.yomanga.co/read/*
  47. // @match *://hentai.cafe/manga/read/*
  48. // @match *://*.yuri-ism.net/slide/read/*
  49. // -- FOOLSLIDE NSFW END
  50. // @require https://greasyfork.org/scripts/692-manga-loader/code/Manga%20Loader.user.js?29
  51. // @namespace https://greasyfork.org/users/1007048
  52. // ==/UserScript==
  53.  
  54. /**
  55. Sample Implementation:
  56. {
  57. name: 'something' // name of the implementation
  58. , match: "^https?://domain.com/.*" // the url to react to for manga loading
  59. , img: '#image' // css selector to get the page's manga image
  60. , next: '#next_page' // css selector to get the link to the next page
  61. , numpages: '#page_select' // css selector to get the number of pages. elements like (select, span, etc)
  62. , curpage: '#page_select' // css selector to get the current page. usually the same as numPages if it's a select element
  63. , numchaps: '#chapters' // css selector to get the number of chapters in manga
  64. , curchap: '#chapters' // css selector to get the number of the current chapter
  65. , nextchap: '#next_chap' // css selector to get the link to the next chapter
  66. , prevchap: '#prev_chap' // same as above except for previous
  67. , 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
  68. , pages: function(next_url, current_page_number, callback, extract_function) {
  69. // gets called requesting a certain page number (current_page_number)
  70. // to continue loading execute callback with img to append as first parameter and next url as second parameter
  71. // only really needs to be used on sites that have really unusual ways of loading images or depend on javascript
  72. }
  73.  
  74. Any of the CSS selectors can be functions instead that return the desired value.
  75. }
  76. */
  77.  
  78. //modified begin 1/2
  79. // download image from his url
  80. function downloadImage(url, filename, zip) {
  81. return new Promise((resolve, reject) => {
  82. var xhr = new XMLHttpRequest();
  83. xhr.open('GET', url, true);
  84. xhr.responseType = 'blob';
  85.  
  86. xhr.onload = function () {
  87. zip.file(filename, xhr.response);
  88. resolve();
  89. };
  90.  
  91. xhr.onerror = function () {
  92. reject(new Error('Failed to download image: ' + url));
  93. };
  94.  
  95. xhr.send();
  96. });
  97. }
  98.  
  99. // download all images of a chapter and add it to a tachiomy compatible zip
  100. async function downloadChapterImages(zipname, chaptername, images, progressCallback) {
  101. var zip = new JSZip();
  102. var folder = zip.folder(chaptername);
  103.  
  104. for (var i = 0; i < images.length; i++) {
  105. var imageUrl = images[i];
  106. var filename = 'image' + (i + 1) + '.jpg';
  107. await downloadImage(imageUrl, filename, folder);
  108.  
  109. var percentComplete = ((i + 1) / images.length) * 100;
  110. progressCallback(percentComplete);
  111. }
  112.  
  113. zip.generateAsync({ type: 'blob' })
  114. .then(function (content) {
  115. var a = document.createElement('a');
  116. a.href = URL.createObjectURL(content);
  117. a.download = zipname + '.zip';
  118. a.style.display = 'none';
  119. document.body.appendChild(a);
  120. a.click();
  121. document.body.removeChild(a);
  122. });
  123. }
  124.  
  125.  
  126. function addDownloadButton(chapterUrl) {
  127. var btnDownload = document.createElement('button');
  128. btnDownload.textContent = 'Download chapter';
  129. btnDownload.style.position = 'fixed';
  130. btnDownload.style.bottom = '50px';
  131. btnDownload.style.right = '20px';
  132. btnDownload.style.zIndex = '9999';
  133.  
  134. var progressBarContainer = document.createElement('div');
  135. progressBarContainer.style.position = 'fixed';
  136. progressBarContainer.style.top = '0';
  137. progressBarContainer.style.width = '100%';
  138. progressBarContainer.style.zIndex = '9998';
  139.  
  140. var progressBar = document.createElement('progress');
  141. progressBar.value = 0;
  142. progressBar.max = 100;
  143. progressBar.style.width = '100%';
  144.  
  145. progressBarContainer.appendChild(progressBar);
  146.  
  147. document.body.appendChild(progressBarContainer);
  148.  
  149. btnDownload.addEventListener('click', function () {
  150. var mangaName = chapterUrl.split('/').filter(Boolean).slice(-3, -1).join('_');
  151. log('Manga name :' + mangaName)
  152. var chapterNumber = chapterUrl.split('/').filter(Boolean).pop();
  153. log('Chapter number :' + chapterNumber)
  154.  
  155. var zipname = mangaName + '_' + chapterNumber;
  156. log('Zip name :' + zipname)
  157.  
  158. var images = Array.from(document.querySelectorAll('.ml-images img')).map(function(img) {
  159. return img.src;
  160. });
  161.  
  162.  
  163. if (images.length > 0) {
  164.  
  165. var progressCallback = function (percentComplete) {
  166.  
  167. progressBar.value = percentComplete;
  168. log('Progress: ' + percentComplete.toFixed(2) + '%');
  169. };
  170. downloadChapterImages(zipname, chapterNumber, images, progressCallback);
  171. } else {
  172. alert('No images found in class ml-images.');
  173. }
  174. });
  175.  
  176. document.body.appendChild(btnDownload);
  177. }
  178.  
  179.  
  180. //modified end 1/2
  181.  
  182.  
  183. // short reference to unsafeWindow (or window if unsafeWindow is unavailable e.g. bookmarklet)
  184. var W = (typeof unsafeWindow === 'undefined') ? window : unsafeWindow;
  185.  
  186. var scriptName = 'Manga Loader';
  187. var pageTitle = document.title;
  188.  
  189. var IMAGES = {
  190. refresh_large: 'data:image/svg+xml;charset=utf-8,<svg width="1792" height="1792" viewBox="0 0 1792 1792" xmlns="http://www.w3.org/2000/svg"><path d="M1639 1056q0 5-1 7-64 268-268 434.5t-478 166.5q-146 0-282.5-55t-243.5-157l-129 129q-19 19-45 19t-45-19-19-45v-448q0-26 19-45t45-19h448q26 0 45 19t19 45-19 45l-137 137q71 66 161 102t187 36q134 0 250-65t186-179q11-17 53-117 8-23 30-23h192q13 0 22.5 9.5t9.5 22.5zm25-800v448q0 26-19 45t-45 19h-448q-26 0-45-19t-19-45 19-45l138-138q-148-137-349-137-134 0-250 65t-186 179q-11 17-53 117-8 23-30 23h-199q-13 0-22.5-9.5t-9.5-22.5v-7q65-268 270-434.5t480-166.5q146 0 284 55.5t245 156.5l130-129q19-19 45-19t45 19 19 45z" /></svg>'
  191. };
  192.  
  193. // reusable functions to insert in implementations
  194. var reuse = {
  195. encodeChinese: function(xhr) {
  196. xhr.overrideMimeType('text/html;charset=gbk');
  197. },
  198. na: function() {
  199. return 'N/A';
  200. }
  201. };
  202.  
  203. /**
  204. Sample Implementation:
  205. {
  206. name: 'something' // name of the implementation
  207. , match: "^https?://domain.com/.*" // the url to react to for manga loading
  208. , img: '#image' // css selector to get the page's manga image
  209. , next: '#next_page' // css selector to get the link to the next page
  210. , numpages: '#page_select' // css selector to get the number of pages. elements like (select, span, etc)
  211. , curpage: '#page_select' // css selector to get the current page. usually the same as numPages if it's a select element
  212. , numchaps: '#chapters' // css selector to get the number of chapters in manga
  213. , curchap: '#chapters' // css selector to get the number of the current chapter
  214. , nextchap: '#next_chap' // css selector to get the link to the next chapter
  215. , prevchap: '#prev_chap' // same as above except for previous
  216. , 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
  217. , pages: function(next_url, current_page_number, callback, extract_function) {
  218. // gets called requesting a certain page number (current_page_number)
  219. // to continue loading execute callback with img to append as first parameter and next url as second parameter
  220. // only really needs to be used on sites that have really unusual ways of loading images or depend on javascript
  221. }
  222.  
  223. Any of the CSS selectors can be functions instead that return the desired value.
  224. }
  225. */
  226.  
  227. var implementations = [{
  228. name: 'batoto',
  229. match: "^https?://bato.to/reader.*",
  230. img: function(ctx) {
  231. var img = getEl('#comic_page', ctx);
  232. if(img) {
  233. return img.src;
  234. } else {
  235. var imgs = getEls('#content > div:nth-child(8) > img', ctx).map(function(page) {
  236. return page.src;
  237. });
  238. if(imgs.length > 0) {
  239. this.next = function() { return imgs[0]; };
  240. this.numpages = function() { return imgs.length; };
  241. this.pages = function(url, num, cb, ex) {
  242. cb(imgs[num - 1], num);
  243. };
  244. return imgs[0];
  245. }
  246. }
  247. },
  248. next: function() {
  249. if(!this._numpage) {
  250. this._numpage = extractInfo(this.curpage, {type: 'index'});
  251. this._id = location.hash.split('_')[0].slice(1);
  252. }
  253. return '/areader?id=' + this._id + '&p=' + (++this._numpage);
  254. },
  255. numpages: '#page_select',
  256. curpage: '#page_select',
  257. curchap: 'select[name=chapter_select]',
  258. numchaps: 'select[name=chapter_select]',
  259. nextchap: function(prev) {
  260. //var link = extractInfo('select[name=chapter_select]', {type: 'value', val: prev ? 1 : -1});
  261. //return link && link.replace(/https?/, document.location.href.split(':')[0]); // fix for batotos broken https pages
  262. var menu = getEls('div.moderation_bar > ul > li', getEl('#reader'));
  263. for (var i = 0; i != menu.length; i += 1) {
  264. var img = getEl('img', menu[i]);
  265. if (img && img.title == (prev ? "Previous Chapter" : "Next Chapter")) {
  266. return img.parentNode.href.replace(/https?/, document.location.href.split(':')[0]);
  267. }
  268. }
  269. return null;
  270. },
  271. prevchap: function() {
  272. return this.nextchap(true);
  273. },
  274. wait: '#comic_page'
  275. }, {
  276. name: 'manga-panda',
  277. match: "^https?://www.mangapanda.com/.*/[0-9]*",
  278. img: '#img',
  279. next: '.next a',
  280. numpages: '#pageMenu',
  281. curpage: '#pageMenu',
  282. nextchap: '#mangainfofooter > #mangainfo_bas table tr:first-child a',
  283. prevchap: '#mangainfofooter > #mangainfo_bas table tr:last-child a'
  284. }, {
  285. name: 'mangafox',
  286. match: "^https?://(fan|manga)fox.(me|la|net)/manga/[^/]*/[^/]*/[^/]*",
  287. img: '.reader-main img',
  288. next: '.pager-list-left > span > a:last-child',
  289. numpages: function() { return W.imagecount; },
  290. curpage: function () { return W.imagepage; },
  291. nextchap: '.pager-list-left > a:last-child',
  292. prevchap: '.pager-list-left > a:first-child',
  293. imgURLs: [],
  294. pages: function(url, num, cb, ex) {
  295. var imp = this;
  296. if (this.imgURLs[num])
  297. cb(this.imgURLs[num], num);
  298. else
  299. ajax({
  300. url: 'chapterfun.ashx?cid=' + W.chapterid + '&page=' + num,
  301. onload: function(e) {
  302. eval(e.target.responseText);
  303. for (var i = 0; i < d.length; i++) {
  304. imp.imgURLs[num + i] = d[i];
  305. }
  306. cb(d[0], num);
  307. }
  308. });
  309. },
  310. wait: function () {
  311. el = getEl('.reader-main img');
  312.  
  313. return el && el.getAttribute('src') != el.getAttribute('data-loading-img');
  314. }
  315. }, {
  316. name: 'manga-stream',
  317. match: "^https?://(readms|mangastream).(net|com)/(r|read)/[^/]*/[^/]*",
  318. img: '#manga-page',
  319. next: '.next a',
  320. numpages: function() {
  321. var lastPage = getEl('.subnav-wrapper .controls .btn-group:last-child ul li:last-child');
  322. return parseInt(lastPage.textContent.match(/[0-9]/g).join(''), 10);
  323. },
  324. nextchap: function(prev) {
  325. var found;
  326. var chapters = [].slice.call(document.querySelectorAll('.controls > div:first-child > .dropdown-menu > li a'));
  327. chapters.pop();
  328. for (var i = 0; i < chapters.length; i++) {
  329. if (window.location.href.indexOf(chapters[i].href) !== -1) {
  330. found = chapters[i + (prev ? 1 : -1)];
  331. if (found) return found.href;
  332. }
  333. }
  334. },
  335. prevchap: function() {
  336. return this.nextchap(true);
  337. }
  338. }, {
  339. name: 'manga-reader',
  340. match: "^https?://www.mangareader.net/.*/.*",
  341. img: '#img',
  342. next: '.next a',
  343. numpages: '#pageMenu',
  344. curpage: '#pageMenu',
  345. nextchap: '#chapterMenu',
  346. prevchap: '#chapterMenu',
  347. wait: '#chapterMenu option'
  348. }, {
  349. name: 'manga-town',
  350. match: "^https?://www.mangatown.com/manga/[^/]+/[^/]+",
  351. img: '#image',
  352. next: '#viewer a',
  353. numpages: '.page_select select',
  354. curpage: '.page_select select',
  355. nextchap: '#top_chapter_list',
  356. prevchap: '#top_chapter_list',
  357. wait: 1000
  358. }, {
  359. name: 'manga-cow, manga-doom, manga-indo, 3asq.info, moonbunnnycafe',
  360. match: "^https?://(mngcow|mangadoom|mangaindo|merakiscans|www\\.3asq|moonbunnycafe)\\.(co|id|info|com)/[^/]+/[0-9.]+",
  361. img: '.prw a > img',
  362. next: '.prw a',
  363. numpages: 'select.cbo_wpm_pag',
  364. curpage: 'select.cbo_wpm_pag',
  365. nextchap: function(prev) {
  366. var next = extractInfo('select.cbo_wpm_chp', {type: 'value', val: (prev ? 1 : -1)});
  367. if(next) return window.location.href.replace(/\/[0-9.]+\/?([0-9]+\/?)?[^/]*$/, '/' + next);
  368. },
  369. prevchap: function() {
  370. return this.nextchap(true);
  371. }
  372. }, {
  373. name: 'manga-here',
  374. match: "^https?://www.mangahere.c[oc]/manga/[^/]+/[^/]+",
  375. img: '#viewer img:last-child',
  376. next: '#viewer a',
  377. numpages: 'select.wid60',
  378. curpage: 'select.wid60',
  379. numchaps: '#top_chapter_list',
  380. curchap: '#top_chapter_list',
  381. nextchap: function(prev) {
  382. var chapter = W.chapter_list[W.current_chapter_index + (prev ? -1 : 1)];
  383. return chapter && chapter[1];
  384. },
  385. prevchap: function() {
  386. return this.nextchap(true);
  387. },
  388. wait: function() {
  389. return areDefined(W.current_chapter_index, W.chapter_list, getEl('#top_chapter_list'));
  390. }
  391. }, {
  392. name: 'manga-here mobile',
  393. match: "^https?://m.mangahere.c[oc]/manga/[^/]+/[^/]+",
  394. img: '#image',
  395. next: '#viewer a',
  396. numpages: '.mangaread-page',
  397. curpage: '.mangaread-page'
  398. }, {
  399. name: 'manga-park',
  400. match: "^https?://mangapark\\.me/manga/[^/]+/[^/]+/[^/]+",
  401. img: '.img-link > img',
  402. next: '.page > span:last-child > a',
  403. numpages: function() {
  404. if(W.sel_load && W.sel_load.options[W.sel_load.selectedIndex].value) {
  405. return extractInfo('#sel_page_1');
  406. } else {
  407. var links = getEls('.img-link > img').map(function(img) { return img.src; });
  408. this.pages = function(url, num, cb, ex) {
  409. cb(links[num - 1], num);
  410. };
  411. return links.length;
  412. }
  413. },
  414. curpage: '#sel_page_1',
  415. nextchap: function(prev) {
  416. var next = extractInfo('#sel_book_1', {type: 'value', val: (prev ? -1 : 1)});
  417. if(next) return window.location.href.replace(/(\/manga\/[^\/]+).+$/, '$1' + next + '/1');
  418. },
  419. prevchap: function() {
  420. return this.nextchap(true);
  421. },
  422. wait: '#sel_book_1 option'
  423. }, {
  424. name: 'central-de-mangas',
  425. match: "^https?://(centraldemangas\\.org|[^\\.]+\\.com\\.br/leitura)/online/[^/]*/[0-9]*",
  426. img: '#manga-page',
  427. next: '#manga-page',
  428. numpages: '#manga_pages',
  429. curpage: '#manga_pages',
  430. nextchap: function(prev) {
  431. var next = extractInfo('#manga_caps', {type: 'value', val: (prev ? -1 : 1)});
  432. if(next) return window.location.href.replace(/[^\/]+$/, next);
  433. },
  434. prevchap: function() {
  435. return this.nextchap(true);
  436. },
  437. pages: function(url, num, cb, ex) {
  438. url = url.slice(0, url.lastIndexOf('-') + 1) + ("0" + num).slice(-2) + url.slice(url.lastIndexOf('.'));
  439. cb(url, url);
  440. }
  441. }, {
  442. name: 'manga-joy',
  443. match: "^https?://manga-joy.com/[^/]*/[0-9]*",
  444. img: '.prw img',
  445. next: '.nxt',
  446. numpages: '.wpm_nav_rdr li:nth-child(3) > select',
  447. curpage: '.wpm_nav_rdr li:nth-child(3) > select',
  448. nextchap: function(prev) {
  449. var next = extractInfo('.wpm_nav_rdr li:nth-child(2) > select', {type: 'value', val: prev ? 1 : -1});
  450. if(next) return window.location.href.replace(/\/[0-9.]+\/?([0-9]+(\/.*)?)?$/, '/' + next);
  451. },
  452. prevchap: function() {
  453. return this.nextchap(true);
  454. }
  455. }, {
  456. name: 'dm5',
  457. match: "^https?://[^\\.]*\\.dm5\\.com/m[0-9]*",
  458. img: function (){
  459. return getEl('img.load-src').getAttribute('data-src');
  460. },
  461. next: function(){
  462. return '#';
  463. },
  464. numpages: function () {
  465. return W.pages.length;
  466. },
  467. pages: function(url, num, cb, ex) {
  468. cb(W.pages[num - 1].getAttribute('data-src'), num - 1);
  469. },
  470. nextchap: 'a.logo_2',
  471. prevchap: 'a.logo_1',
  472. wait: function (){
  473. W.pages = getEls('img.load-src');
  474. return true;
  475. }
  476. }, {
  477. name: 'senmanga',
  478. match: "^https?://[^\\.]+\\.senmanga\\.com/[^/]*/.+",
  479. img: '#picture',
  480. next: '#reader > a',
  481. numpages: 'select[name=page]',
  482. curpage: 'select[name=page]',
  483. numchaps: 'select[name=chapter]',
  484. curchap: 'select[name=chapter]',
  485. nextchap: function(prev) {
  486. var next = extractInfo('select[name=chapter]', {type: 'value', val: (prev ? 1 : -1)});
  487. if(next) {
  488. var manga = window.location.pathname.slice(1).split('/')[0];
  489. return window.location.origin + '/' + manga + '/' + next + '/1';
  490. }
  491. },
  492. prevchap: function() {
  493. return this.nextchap(true);
  494. }
  495. }, {
  496. name: 'japscan',
  497. match: "^https?://www\\.japscan\\.com/lecture-en-ligne/[^/]*/[0-9]*",
  498. img: '#image',
  499. next: '#img_link',
  500. numpages: '#pages',
  501. curpage: '#pages',
  502. nextchap: '#next_chapter',
  503. prevchap: '#back_chapter'
  504. }, {
  505. name: 'pecintakomik',
  506. match: "^https?://www\\.pecintakomik\\.com/manga/[^/]*/[^/]*",
  507. img: '.picture',
  508. next: '.pager a:nth-child(3)',
  509. numpages: 'select[name=page]',
  510. curpage: 'select[name=page]',
  511. nextchap: function(prev) {
  512. var next = extractInfo('select[name=chapter]', {type: 'value', val: (prev ? 1 : -1)});
  513. if(next) return window.location.href.replace(/\/([^\/]+)\/[0-9]+\/?$/, '/$1/' + next);
  514. },
  515. prevchap: function() {
  516. return this.nextchap(true);
  517. }
  518. }, {
  519. name: 'manga-kaka',
  520. match: "^https?://www\\.(mangahen|mangamap)\\.com/[^/]+/[0-9]+",
  521. img: 'img.manga-page',
  522. next: '.nav_pag > li:nth-child(1) > a',
  523. numpages: 'select.cbo_wpm_pag',
  524. curpage: 'select.cbo_wpm_pag',
  525. nextchap: function(prev) {
  526. var chapter = extractInfo('select.cbo_wpm_chp', { type: 'value', val: (prev ? 1 : -1) });
  527. if(chapter) return window.location.href.replace(/\/[0-9\.]+\/?([0-9]+\/?)?$/, '/' + chapter);
  528. },
  529. prevchap: function() {
  530. return this.nextchap(true);
  531. }
  532. }, {
  533. name: 'manga-wall',
  534. _page: null,
  535. match: "^https?://mangawall\\.com/manga/[^/]*/[0-9]*",
  536. img: 'img.scan',
  537. next: function() {
  538. if(this._page === null) this._page = W.page;
  539. return W.series_url + '/' + W.chapter + '/' + (this._page += 1);
  540. },
  541. numpages: '.pageselect',
  542. curpage: '.pageselect',
  543. nextchap: function(prev) {
  544. return W.series_url + '/' + (parseInt(W.chapter.slice(1)) + (prev ? -1 : 1)) + '/1';
  545. },
  546. prevchap: function() {
  547. return this.nextchap(true);
  548. }
  549. }, {
  550. name: 'anime-a',
  551. _page: null,
  552. match: "^https?://manga\\.animea.net/.+chapter-[0-9]+(-page-[0-9]+)?.html",
  553. img: '#scanmr',
  554. next: function() {
  555. if(this._page === null) this._page = W.page;
  556. return W.series_url + W.chapter + '-page-' + (this._page += 1) + '.html';
  557. },
  558. numpages: '.pageselect',
  559. curpage: '.pageselect',
  560. nextchap: function(prev) {
  561. return W.series_url + 'chapter-' + (parseInt(W.chapter.match(/[0-9]+/)[0]) + (prev ? -1 : 1)) + '-page-1.html';
  562. },
  563. prevchap: function() {
  564. return this.nextchap(true);
  565. }
  566. }, {
  567. name: 'kiss-manga',
  568. match: "^https?://kissmanga\\.com/Manga/[^/]+/.+",
  569. img: '#divImage img',
  570. next: '#divImage img',
  571. numpages: function() {
  572. return (W.lstOLA || W.lstImages).length;
  573. },
  574. curpage: function() {
  575. if(getEls('#divImage img').length > 1) {
  576. return 1;
  577. } else {
  578. return W.currImage + 1;
  579. }
  580. },
  581. nextchap: '#selectChapter, .selectChapter',
  582. prevchap: '#selectChapter, .selectChapter',
  583. pages: function(url, num, cb, ex) {
  584. cb((W.lstOLA || W.lstImages)[num - 1], num);
  585. }
  586. }, {
  587. name: 'the-spectrum-scans',
  588. match: "^https?://view\\.thespectrum\\.net/series/[^\\.]+\\.html",
  589. img: '#mainimage',
  590. next: function() {
  591. if (++this._page < this._pages.length) {
  592. return this._pages[this._page];
  593. }
  594. },
  595. numpages: '.selectpage',
  596. curpage: '.selectpage',
  597. nextchap: function(prev) {
  598. var ps = document.pageSelector1;
  599. var chnum = ps.ch.selectedIndex + (prev ? -1 : 1);
  600. if (chnum < ps.ch.length && chnum > -1) {
  601. return ps.action.split('?')[0] + '?ch=' + ps.ch[chnum].value + '&page=1';
  602. } else {
  603. return false;
  604. }
  605. },
  606. prevchap: function() {
  607. return this.nextchap(true);
  608. },
  609. wait: function() {
  610. var ps = document.pageSelector1;
  611. this._pages = [];
  612. var base = ps.action.split('?')[0];
  613. for (var i = 0; i < ps.page.length; i++) {
  614. this._pages.push(base + '?ch=' + ps.ch.value + '&page=' + ps.page[i].value);
  615. }
  616. this._page = ps.page[ps.page.selectedIndex].value - 1;
  617. return true;
  618. }
  619. }, {
  620. name: 'manhua-dmzj',
  621. match: "^https?://manhua.dmzj.com/[^/]*/[0-9]+(-[0-9]+)?\\.shtml",
  622. img: '#center_box > img',
  623. next: '#center_box > img',
  624. numpages: function() {
  625. return W.arr_pages.length;
  626. },
  627. curpage: function() {
  628. var match = location.href.match(/page=([0-9]+)/);
  629. return match ? parseInt(match[1]) : 1;
  630. },
  631. nextchap: '#next_chapter',
  632. prevchap: '#prev_chapter',
  633. pages: function(url, num, cb, ex) {
  634. cb(W.img_prefix + W.arr_pages[num - 1], num);
  635. },
  636. wait: '#center_box > img'
  637. }, {
  638. name: 'hqbr',
  639. match: "^https?://hqbr.com.br/hqs/[^/]+/capitulo/[0-9]+/leitor/0",
  640. img: '#hq-page',
  641. next: '#hq-page',
  642. numpages: function() {
  643. return W.pages.length;
  644. },
  645. curpage: function() {
  646. return W.paginaAtual + 1;
  647. },
  648. nextchap: function(prev) {
  649. var chapters = getEls('#chapter-dropdown a'),
  650. current = parseInt(W.capituloIndex),
  651. chapter = chapters[current + (prev ? -1 : 1)];
  652. return chapter && chapter.href;
  653. },
  654. prevchap: function() {
  655. return this.nextchap(true);
  656. },
  657. pages: function(url, num, cb, ex) {
  658. cb(W.pages[num - 1], num);
  659. }
  660. }, {
  661. name: 'dmzj',
  662. match: "^https?://www.dmzj.com/view/[^/]+/.+\\.html",
  663. img: '.comic_wraCon > img',
  664. next: '.comic_wraCon > img',
  665. numpages: function() {
  666. return parseInt(W.pic_total);
  667. },
  668. curpage: function() {
  669. var match = location.href.match(/page=([0-9])/);
  670. return match ? parseInt(match[1]) : 1;
  671. },
  672. nextchap: '.next > a',
  673. prevchap: '.pre > a',
  674. pages: function(url, num, cb, ex) {
  675. cb(W.img_prefix + W.picArry[num - 1], num);
  676. },
  677. wait: '.comic_wraCon > img'
  678. }, {
  679. name: 'mangago',
  680. match: "^https?://(www.)?mangago.me/read-manga/[^/]+/[^/]+/[^/]+",
  681. img: '#page1',
  682. next: '#pic_container',
  683. numpages: '#dropdown-menu-page',
  684. curpage: function() {
  685. return parseInt(getEls('#page-mainer a.btn.dropdown-toggle')[1].textContent.match(/[0-9]+/)[0]);
  686. },
  687. nextchap: function(prev) {
  688. var chapters = getEls('ul.dropdown-menu.chapter a'),
  689. curName = getEls('#page-mainer a.btn.dropdown-toggle')[0].textContent,
  690. curIdx;
  691. chapters.some(function(chap, idx) {
  692. if(chap.textContent.indexOf(curName) === 0) {
  693. curIdx = idx;
  694. return true;
  695. }
  696. });
  697. var chapter = chapters[curIdx + (prev ? 1 : -1)];
  698. return chapter && chapter.href;
  699. },
  700. prevchap: function() {
  701. return this.nextchap(true);
  702. }
  703. }, {
  704. name: 'mangalator',
  705. match: "^https?://mangalator.ch/show.php\\?gallery=[0-9]+",
  706. img: '.image img',
  707. next: '#next',
  708. numpages: 'select[name=image]',
  709. curpage: 'select[name=image]',
  710. nextchap: function(prev) {
  711. var next = extractInfo('select[name=gallery]', {type: 'value', val: (prev ? 1 : -1)});
  712. if(next) return location.href.replace(/\?gallery=[0-9]+/, '?gallery=' + next);
  713. },
  714. prevchap: function() {
  715. return this.nextchap(true);
  716. }
  717. }, {
  718. name: 'eatmanga',
  719. match: "^https?://eatmanga.com/Manga-Scan/[^/]+/.+",
  720. img: '#eatmanga_image, #eatmanga_image_big',
  721. next: '#page_next',
  722. numpages: '#pages',
  723. curpage: '#pages',
  724. nextchap: '#bottom_chapter_list',
  725. prevchap: '#bottom_chapter_list',
  726. invchap: true
  727. }, {
  728. name: 'manga-cat',
  729. match: "^https?://www.mangacat.me/[^/]+/[^/]+/[^\\.]+.html",
  730. img: '.img',
  731. next: '.img-link',
  732. numpages: '#page',
  733. curpage: '#page',
  734. nextchap: '#chapter',
  735. prevchap: '#chapter',
  736. invchap: true,
  737. wait: '#chapter option'
  738. }, {
  739. name: 'readmng.com',
  740. match: "^https?://www\\.readmng\\.com/[^/]+/.+",
  741. img: '.page_chapter-2 img',
  742. next: '.list-switcher-2 > li:nth-child(3) > a, .list-switcher-2 > li:nth-child(2) > a',
  743. numpages: '.list-switcher-2 select[name=category_type]',
  744. curpage: '.list-switcher-2 select[name=category_type]',
  745. nextchap: '.jump-menu[name=chapter_list]',
  746. prevchap: '.jump-menu[name=chapter_list]',
  747. invchap: true
  748. }, {
  749. name: 'mangadex.org',
  750. match: "^https?://mangadex\\.org/chapter/[0-9]+/[0-9]+",
  751. img: '#current_page',
  752. next: function() {
  753. return this._base + ++this._page;
  754. },
  755. numpages: '#jump_page',
  756. curpage: '#jump_page',
  757. nextchap: function() {
  758. var chapter = document.querySelector('#jump_chapter').selectedOptions[0].previousElementSibling;
  759. return (chapter === null) ? false : (this._base.replace(/[0-9]+\/$/, chapter.value));
  760. },
  761. prevchap: function() {
  762. var chapter = document.querySelector('#jump_chapter').selectedOptions[0].nextElementSibling;
  763. return (chapter === null) ? false : (this._base.replace(/[0-9]+\/$/, chapter.value));
  764. },
  765.  
  766. wait: function() {
  767. var loc = document.location.toString();
  768. var num = loc.match(/[0-9]+$/);
  769. this._base = loc.slice(0, -num.length);
  770. this._page = parseInt(num);
  771. return true;
  772. }
  773. }, {
  774. name: 'biamamscans.com',
  775. match: "^https?://biamamscans\\.com/read/.+", //nextchap and prevchap broken
  776. img: '.manga-image',
  777. next: 'span.float-right:nth-child(2) > div:nth-child(2) > a:nth-child(1)',
  778. numpages: '#page-select',
  779. curpage: '#page-select',
  780. nextchap: '#chapter-select',
  781. prevchap: '#chapter-select'
  782. }, {
  783. name: 'lhtranslation',
  784. match: "^https?://read.lhtranslation\\.com/read-.+",
  785. img: 'img.chapter-img',
  786. next: '.chapter-content > select + a.label',
  787. numpages: '.chapter-content > select',
  788. curpage: '.chapter-content > select',
  789. numchaps: '.form-control',
  790. curchap: '.form-control',
  791. nextchap: '.form-control',
  792. prevchap: '.form-control',
  793. invchap: true
  794. }, {
  795. name: 'foolslide',
  796. match: "^https?://(" + [
  797. "manga.redhawkscans.com/reader/read/.+",
  798. "reader.s2smanga.com/read/.+",
  799. "casanovascans.com/read/.+",
  800. "reader.vortex-scans.com/read/.+",
  801. "reader.roseliascans.com/read/.+",
  802. "mangatopia.net/slide/read/.+",
  803. "www.twistedhelscans.com/read/.+",
  804. "sensescans.com/reader/read/.+",
  805. "reader.kireicake.com/read/.+",
  806. "substitutescans.com/reader/read/.+",
  807. "mangaichiscans.mokkori.fr/fs/read/.+",
  808. "reader.shoujosense.com/read/.+",
  809. "www.friendshipscans.com/slide/read/.+",
  810. "manga.famatg.com/read/.+",
  811. "www.demonicscans.com/FoOlSlide/read/.+",
  812. "necron99scans.com/reader/read/.+",
  813. "www.demonicscans.com/FoOlSlide/read/.+",
  814. "reader.psscans.info/read/.+",
  815. "otscans.com/foolslide/read/.+",
  816. "necron99scans.com/reader/read/.+",
  817. "manga.inpowerz.com/read/.+",
  818. "reader.evilflowers.com/read/.+",
  819. "reader.cafeconirst.com/read/.+",
  820. "kobato.hologfx.com/reader/read/.+",
  821. "jaiminisbox.com/reader/read/.+",
  822. "abandonedkittenscans.mokkori.fr/reader/read/.+",
  823. "gomanga.co/reader/read/.+",
  824. "reader\.manga-download\.org/read/.+",
  825. "(www\.)?manga-ar\.net/manga/.+/.+/.+",
  826. "helveticascans.com/r/read/.+",
  827. "reader.thecatscans.com/read/.+",
  828. "yonkouprod.com/reader/read/.+",
  829. "reader.championscans.com/read/.+",
  830. "reader.whiteoutscans.com/read/.+",
  831. "hatigarmscans.eu/hs/read/.+",
  832. "lector.kirishimafansub.com/lector/read/.+",
  833. "hotchocolatescans.com/fs/read/.+",
  834. "www.slide.world-three.org/read/.+",
  835. ].join('|') + ")",
  836. img: function() {
  837. return W.pages[W.current_page].url;
  838. },
  839. next: function() {
  840. return 'N/A';
  841. },
  842. numpages: function() {
  843. return W.pages.length;
  844. },
  845. curpage: function() {
  846. return W.current_page + 1;
  847. },
  848. nextchap: function(prev) {
  849. var desired;
  850. var dropdown = getEls('ul.dropdown')[1] || getEls('ul.uk-nav')[1] || getEls('ul.dropdown-menu')[3];
  851. if(!dropdown) return;
  852. getEls('a', dropdown).forEach(function(chap, idx, arr) {
  853. if(location.href.indexOf(chap.href) === 0) desired = arr[idx + (prev ? 1 : -1)];
  854. });
  855. return desired && desired.href;
  856. },
  857. prevchap: function() {
  858. return this.nextchap(true);
  859. },
  860. pages: function(url, num, cb, ex) {
  861. cb(W.pages[num - 1].url, num);
  862. },
  863. wait: function() {
  864. if(W.location.href.indexOf('gomanga.co') !== -1) {
  865. var match = document.body.innerHTML.match(/(\w+)\[id\]\.url/);
  866. W.pages = match && match[1] && W[match[1]];
  867. }
  868. return W.pages;
  869. }
  870. }, {
  871. name: 'mangatraders',
  872. match: "^https?://mangatraders\\.biz/read-online/.+",
  873. img: 'img.CurImage',
  874. next: '.image-container a',
  875. numpages: '.PageSelect',
  876. curpage: '.PageSelect',
  877. nextchap: function(prev) {
  878. var next = extractInfo('.ChapterSelect', {type:'text', val: (prev ? -1 : 1)});
  879. if(next) {
  880. var chapter = next.match(/[0-9.]+/)[0];
  881. return location.href.replace(/chapter-[0-9.]+/, 'chapter-' + chapter).replace(/page-[0-9]+/, 'page-1');
  882. }
  883. },
  884. prevchap: function() {
  885. return this.nextchap(true);
  886. }
  887. }, {
  888. name: 'mangainn',
  889. match: "^https?://www.mangainn.net/manga/chapter/.+",
  890. img: '#imgPage',
  891. next: function() {
  892. if(!this._count) this._count = extractInfo(this.curpage, {type: 'value'});
  893. var url = location.href;
  894. if(!/page_[0-9]+/.test(url)) url += '/page_1';
  895. return url.replace(/page_[0-9]+/, 'page_' + (++this._count));
  896. },
  897. numpages: '#cmbpages',
  898. curpage: '#cmbpages',
  899. nextchap: function(prev) {
  900. var next = extractInfo('#chapters', {type:'value', val: (prev ? -1 : 1)});
  901. if(next) return location.href.replace(/\/chapter\/.+$/, '/chapter/' + next + '/page_1');
  902. },
  903. prevchap: function() {
  904. return this.nextchap(true);
  905. }
  906. }, {
  907. name: 'kukudm',
  908. match: "^https?://(www|comic|comic2|comic3).kukudm.com/comiclist/[0-9]+/[0-9]+/[0-9]+.htm",
  909. img: function(ctx) {
  910. var script = getEl('td > script[language=javascript]', ctx);
  911. if(script) {
  912. return 'http://n.kukudm.com/' + script.textContent.match(/\+"([^']+)/)[1];
  913. }
  914. },
  915. next: function(ctx) {
  916. var links = getEls('td > a', ctx);
  917. return links[links.length - 1].getAttribute('href');
  918. },
  919. numpages: function(cur) {
  920. return parseInt(document.body.textContent.match(/共([0-9]+)页/)[1]);
  921. },
  922. curpage: function() {
  923. return parseInt(document.body.textContent.match(/第([0-9]+)页/)[1]);
  924. },
  925. beforexhr: reuse.encodeChinese
  926. }, {
  927. name: 'mangachapter',
  928. match: "^https?://www\\.mangachapter\\.me/[^/]+/[^/]+/[^/]+.html",
  929. img: '#mangaImg, #viewer > table > tbody > tr > td:nth-child(1) > a:nth-child(2) > img',
  930. next: '.page-select + a.button-page',
  931. numpages: '.page-select select',
  932. curpage: '.page-select select',
  933. invchap: true,
  934. nextchap: '#top_chapter_list',
  935. prevchap: '#top_chapter_list',
  936. wait: '#top_chapter_list'
  937. }, {
  938. name: 'kawaii',
  939. match: "^https://kawaii.ca/reader/.+",
  940. img: '.picture',
  941. next: 'select[name=page] + a',
  942. numpages: 'select[name=page]',
  943. curpage: 'select[name=page]',
  944. nextchap: function(prev) {
  945. var next = extractInfo('select[name=chapter]', {type:'value', val: (prev ? -1 : 1)});
  946. if(next) return location.href.replace(/\/reader\/([^/]+)(\/.+)?$/, '/reader/$1/' + next);
  947. },
  948. prevchap: function() {
  949. return this.nextchap(true);
  950. }
  951. }, {
  952. name: 'lonemanga',
  953. match: "^https?://lonemanga.com/manga/[^/]+/[^/]+",
  954. img: '#imageWrapper img',
  955. next: '#imageWrapper a',
  956. numpages: '.viewerPage',
  957. curpage: '.viewerPage',
  958. nextchap: function(prev) {
  959. var next = extractInfo('.viewerChapter', {type:'value', val: (prev ? 1 : -1)});
  960. if(next) return location.href.replace(/\/manga\/([^/]+)\/.+$/, '/manga/$1/' + next);
  961. },
  962. prevchap: function() {
  963. return this.nextchap(true);
  964. }
  965. }, {
  966. name: 'madokami',
  967. match: "^https?://manga\\.madokami\\.al/reader/.+",
  968. img: 'img',
  969. next: 'img',
  970. curpage: function() {
  971. return parseInt(query().index) + 1;
  972. },
  973. numpages: function() {
  974. if(!this._pages) {
  975. this._pages = JSON.parse(getEl('#reader').dataset.files);
  976. }
  977. return this._pages.length;
  978. },
  979. pages: function(url, num, cb, ex) {
  980. url = url.replace(/file=.+$/, 'file=' + this._pages[num - 1]);
  981. cb(url, url);
  982. },
  983. wait: '#reader'
  984. }, {
  985. name: 'egscans',
  986. match: '^https?://read.egscans.com/.+',
  987. img: '#image_frame img',
  988. next: '#image_frame img',
  989. curpage: 'select[name=page]',
  990. numpages: 'select[name=page]',
  991. nextchap: function(prev) {
  992. var data = getEl(this.curchap).getAttribute('onchange').match(/'[^']+'/g);
  993. var next = extractInfo(this.curchap, { type: 'value', val: (prev ? -1 : 1) });
  994. if(next) return location.origin + '/' + data[0].slice(1, -1) + '/' + next;
  995. },
  996. prevchap: function() {
  997. return this.nextchap(true);
  998. },
  999. curchap: 'select[name=chapter]',
  1000. numchaps: 'select[name=chapter]',
  1001. pages: function(url, num, cb, ex) {
  1002. cb('/' + W.img_url[num], num);
  1003. }
  1004. }, {
  1005. name: 'imperialscans',
  1006. match: '^https?://imperialscans.com/read/.+',
  1007. img: '#page-img',
  1008. next: '#page-url',
  1009. curpage: function() {
  1010. return extractInfo('#page-select', { type: 'index', val: -1 });
  1011. },
  1012. numpages: function() {
  1013. return extractInfo('#page-select') - 1;
  1014. },
  1015. curchap: function() {
  1016. var options = getEls('#chapter-select option:not([disabled])');
  1017. var chapter = 1;
  1018. options.some(function(value, index) {
  1019. if (location.pathname === value.value) {
  1020. chapter = options.length - index;
  1021. return true;
  1022. }
  1023. });
  1024. return chapter;
  1025. },
  1026. numchaps: function() {
  1027. return extractInfo('#chapter-select');
  1028. },
  1029. nextchap: '#page-control > li:nth-child(5) > a',
  1030. prevchap: '#page-control > li:nth-child(1) > a'
  1031. }, {
  1032. name: 'chuixue',
  1033. match: "^https?://www.chuixue.com/manhua/[0-9]+/[0-9]+.html",
  1034. img: '#qTcms_pic',
  1035. next: '#qTcms_pic',
  1036. curpage: '#qTcms_select_i',
  1037. numpages: '#qTcms_select_i',
  1038. pages: function(url, num, cb, ex) {
  1039. if(!this._pages) {
  1040. this._pages = W.qTcms_S_m_murl.split('$qingtiandy$');
  1041. }
  1042. cb(this._pages[num - 1], num);
  1043. },
  1044. nextchap: function() {
  1045. return W.qTcms_Pic_nextArr;
  1046. },
  1047. wait: '#qTcms_pic'
  1048. }, {
  1049. name: 'sh-arab',
  1050. match: '^https?://www.sh-arab.com/manga/.+',
  1051. img: 'img.picture',
  1052. next: '#omv td > a',
  1053. curpage: 'select[name=page]',
  1054. numpages: 'select[name=page]',
  1055. curchap: 'select[name=chapter]',
  1056. numchaps: 'select[name=chapter]',
  1057. nextchap: function(prev) {
  1058. var next = extractInfo('select[name=chapter]', {type:'value', val: (prev ? -1 : 1)});
  1059. if (next) return location.href.replace(/[^\/]+$/, next);
  1060. },
  1061. prevchap: function() {
  1062. return this.nextchap(true);
  1063. }
  1064. }, {
  1065. name: 'br.mangahost.com',
  1066. match: "^http(s)?://br.mangahost.com/manga/[^/]+/.+",
  1067. img: 'img.open',
  1068. next: '.image-content > a',
  1069. curpage: '.viewerPage',
  1070. numpages: '.viewerPage'
  1071. }, {
  1072. name: 'spinybackmanga and titaniascans',
  1073. match: '^https?://(spinybackmanga.com/\\?manga=[^&]+&chapter=.+|www\.titaniascans\.com/reader/.+/.+)',
  1074. img: '#thePicLink img',
  1075. next: '#thePicLink',
  1076. curpage: function() {
  1077. return W.current;
  1078. },
  1079. numpages: function() {
  1080. return getEl('#loadingbar tr').children.length;
  1081. },
  1082. curchap: function() {
  1083. return parseInt(getEls('.selector')[1].firstChild.textContent.match(/[0-9]+/)[0]);
  1084. },
  1085. numchaps: function() {
  1086. return getEls('.selector .options')[1].children.length;
  1087. },
  1088. nextchap: function(prev) {
  1089. var nextChap = document.scripts[2].textContent.match(/location.href = "([^"]+)"/)[1];
  1090. if(prev) {
  1091. [].some.call(getEls('.selector .options')[1].children, function(child, index, children) {
  1092. if(child.href === nextChap) {
  1093. nextChap = children[index - 2] && children[index - 2].href;
  1094. return true;
  1095. }
  1096. });
  1097. }
  1098. return nextChap;
  1099. },
  1100. prevchap: function() {
  1101. return this.nextchap(true);
  1102. },
  1103. pages: function(url, num, cb, ex) {
  1104. var next = url.replace(/(?:(\/)2\/|[0-9]*)$/, '$1' + (num + 1));
  1105. cb(W.imageArray[num - 1], next);
  1106. }
  1107. }, {
  1108. name: 'manga.ae',
  1109. match: "https?://www.manga.ae/[^/]+/[^/]+/",
  1110. img: '#showchaptercontainer img',
  1111. next: '#showchaptercontainer a',
  1112. curpage: 'a.chpage',
  1113. nextchap: '.chapter:last-child',
  1114. prevchap: '.chapter:first-child'
  1115. }, {
  1116. name: 'mangaforall',
  1117. match: "https?://mangaforall.com/manga/[^/]+/[^/]+/",
  1118. img: '#page > img',
  1119. next: '#page > img',
  1120. 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',
  1121. 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',
  1122. 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',
  1123. 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',
  1124. pages: function(url, num, cb, ex) {
  1125. cb(W.pages[num - 1].url, num);
  1126. }
  1127. }, {
  1128. name: 'hellocomic',
  1129. match: "https?://hellocomic.com/[^/]+/[^/]+/p[0-9]+",
  1130. img: '.coverIssue img',
  1131. next: '.coverIssue a',
  1132. numpages: '#e1',
  1133. curpage: '#e1',
  1134. nextchap: '#e2',
  1135. prevchap: '#e2',
  1136. curchap: '#e2',
  1137. numchaps: '#e2'
  1138. }, {
  1139. name: 'read-comic-online',
  1140. match: "^https?://readcomiconline\\.to/Comic/[^/]+/.+",
  1141. img: '#divImage img',
  1142. next: '#divImage img',
  1143. numpages: function() {
  1144. return W.lstImages.length;
  1145. },
  1146. curpage: function() {
  1147. return getEls('#divImage img').length > 1 ? 1 : W.currImage + 1;
  1148. },
  1149. nextchap: '#selectEpisode, .selectEpisode',
  1150. prevchap: '#selectEpisode, .selectEpisode',
  1151. pages: function(url, num, cb, ex) {
  1152. cb(W.lstImages[num - 1], num);
  1153. }
  1154. }, {
  1155. name: 'mangaeden',
  1156. match: "^https?://(www\\.)?mangaeden\\.com/(en|it)/(en|it)-manga/.+",
  1157. img: '#mainImg',
  1158. next: '#nextA',
  1159. numpages: '#pageSelect',
  1160. curpage: '#pageSelect',
  1161. numchaps: '#combobox',
  1162. curchap: '#combobox',
  1163. invchap: true,
  1164. nextchap: function (prev) {
  1165. var cbox = getEl('#combobox');
  1166. var opt = cbox[prev ? cbox.selectedIndex + 1 : cbox.selectedIndex - 1];
  1167. var span = getEl('span.hideM0 a');
  1168. return opt && span && span.href + parseInt(opt.value) + '/1/';
  1169. },
  1170. prevchap: function () {
  1171. return this.nextchap(true);
  1172. }
  1173. }, {
  1174. name: 'comicastle',
  1175. match: "^https?://comicastle\\.org/read-.+",
  1176. img: '.chapter-img',
  1177. next: '.chapter-content > select + a.label',
  1178. numpages: '.chapter-content > select',
  1179. curpage: '.chapter-content > select',
  1180. numchaps: '.form-control',
  1181. curchap: '.form-control',
  1182. nextchap: '.form-control',
  1183. prevchap: '.form-control',
  1184. invchap: true
  1185. }, {
  1186. name: 'mymh8',
  1187. match: "^https?://(www\\.)?mymh8\\.com/chapter/.+",
  1188. img: '#viewimg',
  1189. next: reuse.na,
  1190. numpages: function() {
  1191. return W.maxpages;
  1192. },
  1193. curpage: '#J_showpage > span',
  1194. nextchap: function(prev) {
  1195. var button = prev ? getEl('div.m3p > input:first-of-type') : getEl('div.m3p > input:last-of-type');
  1196. return button && button.attributes.onclick.value.match(/\.href='([^']+)'/)[1];
  1197. },
  1198. prevchap: function() {
  1199. return this.nextchap(true);
  1200. },
  1201. pages: function(url, num, cb, ex) {
  1202. cb(W.WebimgServerURL[0] + W.imageslist[num], num);
  1203. },
  1204. wait: function() {
  1205. return W.imageslist.length > 0;
  1206. }
  1207. }, {
  1208. name: 'unionmangas',
  1209. match: "https?://(www\\.)?unionmangas\\.net/leitor/.+",
  1210. img: '.slick-active img.real',
  1211. next: reuse.na,
  1212. numpages: '.selectPage',
  1213. curpage: '.selectPage',
  1214. numchaps: '#cap_manga1',
  1215. curchap: '#cap_manga1',
  1216. nextchap: '#cap_manga1',
  1217. prevchap: '#cap_manga1',
  1218. pages: function(url, num, cb, ex) {
  1219. cb(W.pages[num - 1], num);
  1220. },
  1221. wait: function() {
  1222. W.pages = getEls('img.real').map(function(el) {
  1223. return el.src || el.dataset.lazy;
  1224. });
  1225. return W.pages && W.pages.length > 0;
  1226. }
  1227. }, {
  1228. name: 'otakusmash',
  1229. match: "https?://www\\.otakusmash\\.com/(read-comics|read-manga)/.+",
  1230. img: 'img.picture',
  1231. next: 'select[name=page] + a',
  1232. curpage: 'select[name=page]',
  1233. numpages: 'select[name=page]',
  1234. nextchap: function(prev) {
  1235. var nextChap = extractInfo('select[name=chapter]', {type: 'value', val: prev ? 1 : -1});
  1236. return nextChap ? location.href.replace(/(read-(comics|manga)\/[^\/]+).*/, '$1/' + nextChap) : null;
  1237. },
  1238. prevchap: function() {
  1239. return this.nextchap(true);
  1240. },
  1241. numchaps: 'select[name=chapter]',
  1242. curchap: 'select[name=chapter]',
  1243. invchap: true
  1244. }, {
  1245. name: 'mangahome',
  1246. match: "https?://www\\.mangahome\\.com/manga/.+/.+",
  1247. img: '#image',
  1248. next: '#viewer > a',
  1249. curpage: '.mangaread-page select',
  1250. numpages: '.mangaread-page select',
  1251. nextchap: function(prev) {
  1252. var buttons = getEls('.mangaread-footer .left > .btn-three');
  1253. for (var i = 0; i < buttons.length; i++) {
  1254. if (buttons[i].textContent.indexOf(prev ? 'Prev Chapter' : 'Next Chapter') > - 1) {
  1255. return buttons[i].href;
  1256. }
  1257. }
  1258. },
  1259. prevchap: function() {
  1260. return this.nextchap(true);
  1261. },
  1262. wait: '#image'
  1263. }, {
  1264. name: 'readcomics',
  1265. match: "https?://(www\\.)?readcomics\\.tv/.+/chapter-[0-9]+(/[0-9]+|$)",
  1266. img: '#main_img',
  1267. next: '.nav.next',
  1268. curpage: 'select[name=page_select]',
  1269. numpages: 'select[name=page_select]',
  1270. nextchap: 'select[name=chapter_select]',
  1271. prevchap: 'select[name=chapter_select]',
  1272. curchap: 'select[name=chapter_select]',
  1273. numchaps: 'select[name=chapter_select]',
  1274. wait: 'select[name=page_select]'
  1275. }, {
  1276. name: 'cartoonmad',
  1277. match: "https?://(www\\.)?(cartoonmad|comicnad)\.com/comic/[0-9]+\.html",
  1278. img: 'tr:nth-child(5) > td > table > tbody > tr:nth-child(1) > td > a > img',
  1279. next: 'a.onpage+a',
  1280. curpage: 'a.onpage',
  1281. numpages: function() {
  1282. return extractInfo('select[name=jump]') - 1;
  1283. },
  1284. nextchap: function() {
  1285. let filter = getEls('.pages').filter(function(i) {
  1286. return i.textContent.match('下一話');
  1287. });
  1288. return filter.length ? filter[0].href : null;
  1289. },
  1290. prevchap: function() {
  1291. let filter = getEls('.pages').filter(function(i) {
  1292. return i.textContent.match('上一話');
  1293. });
  1294. return filter.length ? filter[0].href : null;
  1295. },
  1296. }, {
  1297. name: 'ikanman',
  1298. match: "https?://(www|tw)\.(ikanman|manhuagui)\.com/comic/[0-9]+/[0-9]+\.html",
  1299. img: '#mangaFile',
  1300. next: function() {
  1301. return W._next;
  1302. },
  1303. curpage: '#page',
  1304. numpages: '#pageSelect',
  1305. nextchap: function(prev) {
  1306. var chap = prev ? W._prevchap : W._nextchap;
  1307. if (chap > 0) {
  1308. return location.href.replace(/(\/comic\/[0-9]+\/)[0-9]+\.html.*/, "$1" + chap + ".html");
  1309. } else {
  1310. return false;
  1311. }
  1312. },
  1313. prevchap: function() {
  1314. return this.nextchap(true);
  1315. },
  1316. wait: function() {
  1317. if (getEl('#mangaFile')) {
  1318. W._nextchap = W.cInfo.nextId;
  1319. W._prevchap = W.cInfo.prevId;
  1320. var ex = extractInfo.bind(this);
  1321. W._next = location.href.replace(/(_p[0-9]+)?\.html.*/, '_p' + (ex('curpage') + 1) + '.html');
  1322. W._base = ex('img').replace(/[^\/]+$/, '');
  1323. return true;
  1324. }
  1325. },
  1326. pages: function(url, num, cb, ex) {
  1327. var nexturl = url.replace(/(_p[0-9]+)?\.html.*/, '_p' + (num + 1) + '.html');
  1328. var imgurl = W._base + W.cInfo.files[num - 1];
  1329. cb(imgurl, nexturl);
  1330. }
  1331. }, {
  1332. name: 'mangasail and mangatail',
  1333. match: 'https?://www\.manga(sail|tail)\.com/[^/]+',
  1334. img: '#images img',
  1335. next: '#images a',
  1336. curpage: '#edit-select-page',
  1337. numpages: '#edit-select-page',
  1338. nextchap: function(prev) {
  1339. return location.origin + '/node/' + extractInfo('#edit-select-node', {type: 'value', val: prev ? -1 : 1});
  1340. },
  1341. prevchap: function() {
  1342. return this.nextchap(true);
  1343. },
  1344. curchap: '#select_node',
  1345. numchaps: '#select_node'
  1346. }, {
  1347. name: 'komikstation',
  1348. match: "^https?://www\.komikstation\.com/.+/.+/.+",
  1349. img: '#mainpage',
  1350. next: function() {
  1351. return W._base + '?page=' + (W.glbCurrentpage + 1);
  1352. },
  1353. numpages: '#index select',
  1354. curpage: '#index select',
  1355. pages: function(url, num, cb, ex) {
  1356. next = W._base + '?page=' + (num + 1);
  1357. cb(W.pages[num - 1], next);
  1358. },
  1359. wait: function() {
  1360. W._base = location.href.replace(/[?#].+$/, '');
  1361. return W.pages;
  1362. }
  1363. }, {
  1364. name: 'gmanga',
  1365. match: "^https?://gmanga.me/mangas/",
  1366. img: function() {
  1367. return W.pages[W.firstImg - 1];
  1368. },
  1369. next: function() {
  1370. return location.href + '#' + (W.firstImg + 1);
  1371. },
  1372. numpages: function() {
  1373. return W.totalImgs;
  1374. },
  1375. curpage: function() {
  1376. return W.firstImg;
  1377. },
  1378. nextchap: function(prev) {
  1379. var num = parseInt(extractInfo('#chapter', {type: 'value', val: prev ? 1 : -1}));
  1380. return num && location.href.replace(/(\/mangas\/[^\/]+\/)[0-9]+(\/[^\/]+)/, '$1' + num + '$2');
  1381. },
  1382. prevchap: function() {
  1383. return this.nextchap(true);
  1384. },
  1385. numchaps: '#chapter',
  1386. curchap: '#chapter',
  1387. invchap: true,
  1388. pages: function(url, num, cb, ex) {
  1389. var nexturl = location.href + '#' + (num + 1);
  1390. cb(W.pages[num - 1], nexturl);
  1391. },
  1392. wait: function() {
  1393. W.pages = W.release_pages && W.release_pages[1];
  1394. return W.pages;
  1395. }
  1396. }, {
  1397. name: '930mh',
  1398. match: "http://www\.930mh\.com/manhua/\\d+/\\d+.html",
  1399. img: '#images > img',
  1400. next: function() {
  1401. return location.origin + location.pathname + '?p=' + (W.SinTheme.getPage() + 1);
  1402. },
  1403. pages: function(url, num, cb, ex) {
  1404. cb(new URL(W.pageImage).origin + '/' + W.chapterPath + W.chapterImages[num - 1], num - 1);
  1405. },
  1406. curpage: function() {
  1407. return W.SinTheme.getPage();
  1408. },
  1409. numpages: function() {
  1410. return W.chapterImages.length;
  1411. },
  1412. nextchap: function(){
  1413. return W.nextChapterData.id && W.nextChapterData.id > 0 ? W.comicUrl + W.nextChapterData.id + '.html' : null;
  1414. },
  1415. prevchap: function(){
  1416. return W.prevChapterData.id && W.prevChapterData.id > 0 ? W.comicUrl + W.prevChapterData.id + '.html' : null;
  1417. },
  1418. wait: '#images > img'
  1419. }, {
  1420. name: '漫畫王',
  1421. match: "https://www\.mangabox\.me/reader/\\d+/episodes/\\d+/",
  1422. img: 'img.jsNext',
  1423. next: function() {
  1424. return '#';
  1425. },
  1426. pages: function(url, num, cb, ex) {
  1427. cb(W.pages[num - 1].src, num - 1);
  1428. },
  1429. numpages: function() {
  1430. return W.pages.length;
  1431. },
  1432. nextchap: '.lastSlider_nextButton',
  1433. wait: function (){
  1434. W.pages = getEls('img.jsNext');
  1435. return true;
  1436. }
  1437. }, {
  1438. name: '2comic.com 動漫易',
  1439. match: "http://twocomic.com/view/comic_\\d+.html",
  1440. img: '#TheImg',
  1441. next: function() {
  1442. return '#';
  1443. },
  1444. pages: function(url, num, cb, ex) {
  1445. W.p++;
  1446. var ss = W.ss;
  1447. var c = W.c;
  1448. var ti = W.ti;
  1449. var nn = W.nn;
  1450. var p = W.p;
  1451. var mm = W.mm;
  1452. var f = W.f;
  1453. 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';
  1454. cb(img, num - 1);
  1455. },
  1456. numpages: function() {
  1457. return W.ps * 1;
  1458. },
  1459. curpage: function() {
  1460. return W.p;
  1461. },
  1462. numchaps: function() {
  1463. return W.chs;
  1464. },
  1465. curchap: function() {
  1466. return W.ch;
  1467. },
  1468. nextchap: function() {
  1469. return W.ch < W.chs ? W.replaceurl('ch', W.ni) : false;
  1470. },
  1471. prevchap: function() {
  1472. return W.ch > 1 ? W.replaceurl('ch', W.pi) : false;
  1473. },
  1474. wait:'#TheImg'
  1475. }];
  1476. // END OF IMPL
  1477.  
  1478. var log = function(msg, type) {
  1479. type = type || 'log';
  1480. if (type === 'exit') {
  1481. log('exit: ' + msg, 'error');
  1482. throw 'mloader error';
  1483. } else {
  1484. try {
  1485. console[type]('%c' + scriptName + ' ' + type + ':', 'font-weight:bold;color:green;', msg);
  1486. } catch(e) { }
  1487. }
  1488. };
  1489.  
  1490. var getEl = function(q, c) {
  1491. if (!q) return;
  1492. return (c || document).querySelector(q);
  1493. };
  1494.  
  1495. var getEls = function(q, c) {
  1496. return [].slice.call((c || document).querySelectorAll(q));
  1497. };
  1498.  
  1499. var ajax = function(obj) {
  1500. var xhr = new XMLHttpRequest();
  1501. xhr.open(obj.method || 'get', obj.url, obj.async || true);
  1502. xhr.onload = obj.onload;
  1503. xhr.onerror = obj.onerror;
  1504. xhr.responseType = obj.responseType || 'text';
  1505. if(obj.beforeSend) obj.beforeSend(xhr);
  1506. xhr.send(obj.data);
  1507. };
  1508.  
  1509. var storeGet = function(key) {
  1510. var res;
  1511. if (typeof GM_getValue === "undefined") {
  1512. res = localStorage.getItem(key);
  1513. } else {
  1514. res = GM_getValue(key);
  1515. }
  1516. try {
  1517. return JSON.parse(res);
  1518. } catch(e) {
  1519. return res;
  1520. }
  1521. };
  1522.  
  1523. var storeSet = function(key, value) {
  1524. value = JSON.stringify(value);
  1525. if (typeof GM_setValue === "undefined") {
  1526. return localStorage.setItem(key, value);
  1527. }
  1528. return GM_setValue(key, value);
  1529. };
  1530.  
  1531. var storeDel = function(key) {
  1532. if (typeof GM_deleteValue === "undefined") {
  1533. return localStorage.removeItem(key);
  1534. }
  1535. return GM_deleteValue(key);
  1536. };
  1537.  
  1538. var areDefined = function() {
  1539. return [].every.call(arguments, function(arg) {
  1540. return arg !== undefined && arg !== null;
  1541. });
  1542. };
  1543.  
  1544. var updateObj = function(orig, ext) {
  1545. var key;
  1546. for (key in ext) {
  1547. if (orig.hasOwnProperty(key) && ext.hasOwnProperty(key)) {
  1548. orig[key] = ext[key];
  1549. }
  1550. }
  1551. return orig;
  1552. };
  1553.  
  1554. var extractInfo = function(selector, mod, context) {
  1555. selector = this[selector] || selector;
  1556. if (typeof selector === 'function') {
  1557. return selector.call(this, context);
  1558. }
  1559. var elem = getEl(selector, context),
  1560. option;
  1561. mod = mod || {};
  1562. if (elem) {
  1563. switch (elem.nodeName.toLowerCase()) {
  1564. case 'img':
  1565. return (mod.altProp && elem.getAttribute(mod.altProp)) || elem.src || elem.getAttribute('src');
  1566. case 'a':
  1567. if(mod.type === 'index')
  1568. return parseInt(elem.textContent);
  1569. return elem.href || elem.getAttribute('href');
  1570. case 'ul':
  1571. return elem.children.length;
  1572. case 'select':
  1573. switch (mod.type) {
  1574. case 'index':
  1575. var idx = elem.options.selectedIndex + 1 + (mod.val || 0);
  1576. if(mod.invIdx) idx = elem.options.length - idx + 1;
  1577. return idx;
  1578. case 'value':
  1579. case 'text':
  1580. option = elem.options[elem.options.selectedIndex + (mod.val || 0)] || {};
  1581. return mod.type === 'value' ? option.value : option.textContent;
  1582. default:
  1583. return elem.options.length;
  1584. }
  1585. break;
  1586. default:
  1587. switch (mod.type) {
  1588. case 'index':
  1589. return parseInt(elem.textContent);
  1590. default:
  1591. return elem.textContent;
  1592. }
  1593. }
  1594. }
  1595. return null;
  1596. };
  1597.  
  1598. var addStyle = function(id, replace) {
  1599. if(!this.MLStyles) this.MLStyles = {};
  1600. if(!this.MLStyles[id]) {
  1601. this.MLStyles[id] = document.createElement('style');
  1602. this.MLStyles[id].dataset.name = 'ml-style-' + id;
  1603. document.head.appendChild(this.MLStyles[id]);
  1604. }
  1605. var style = this.MLStyles[id];
  1606. var css = [].slice.call(arguments, 2).join('\n');
  1607. if(replace) {
  1608. style.textContent = css;
  1609. } else {
  1610. style.textContent += css;
  1611. }
  1612. };
  1613.  
  1614. var toStyleStr = function(obj, selector) {
  1615. var stack = [],
  1616. key;
  1617. for (key in obj) {
  1618. if (obj.hasOwnProperty(key)) {
  1619. stack.push(key + ':' + obj[key]);
  1620. }
  1621. }
  1622. if (selector) {
  1623. return selector + '{' + stack.join(';') + '}';
  1624. } else {
  1625. return stack.join(';');
  1626. }
  1627. };
  1628.  
  1629. var throttle = function(callback, limit) {
  1630. var wait = false;
  1631. return function() {
  1632. if (!wait) {
  1633. callback();
  1634. wait = true;
  1635. setTimeout(function() {
  1636. wait = false;
  1637. }, limit);
  1638. }
  1639. };
  1640. };
  1641.  
  1642. var query = function() {
  1643. var map = {};
  1644. location.search.slice(1).split('&').forEach(function(pair) {
  1645. pair = pair.split('=');
  1646. map[pair[0]] = pair[1];
  1647. });
  1648. return map;
  1649. };
  1650.  
  1651. var createButton = function(text, action, styleStr) {
  1652. var button = document.createElement('button');
  1653. button.textContent = text;
  1654. button.onclick = action;
  1655. button.setAttribute('style', styleStr || '');
  1656. return button;
  1657. };
  1658.  
  1659. var getViewer = function(prevChapter, nextChapter) {
  1660. var viewerCss = toStyleStr({
  1661. 'background-color': 'black !important',
  1662. 'font': '0.813em monospace !important',
  1663. 'text-align': 'center',
  1664. }, 'body'),
  1665. imagesCss = toStyleStr({
  1666. 'margin-top': '10px',
  1667. 'margin-bottom': '10px',
  1668. 'transform-origin': 'top center'
  1669. }, '.ml-images'),
  1670. imageCss = toStyleStr({
  1671. 'max-width': '100%',
  1672. 'display': 'block',
  1673. 'margin': '3px auto'
  1674. }, '.ml-images img'),
  1675. counterCss = toStyleStr({
  1676. 'background-color': '#222',
  1677. 'color': 'white',
  1678. 'border-radius': '10px',
  1679. 'width': '30px',
  1680. 'margin-left': 'auto',
  1681. 'margin-right': 'auto',
  1682. 'margin-top': '-12px',
  1683. 'padding-left': '5px',
  1684. 'padding-right': '5px',
  1685. 'border': '1px solid white',
  1686. 'z-index': '100',
  1687. 'position': 'relative'
  1688. }, '.ml-counter'),
  1689. navCss = toStyleStr({
  1690. 'text-decoration': 'none',
  1691. 'color': 'white',
  1692. 'background-color': '#444',
  1693. 'padding': '3px 10px',
  1694. 'border-radius': '5px',
  1695. 'transition': '250ms'
  1696. }, '.ml-chap-nav a'),
  1697. navHoverCss = toStyleStr({
  1698. 'background-color': '#555'
  1699. }, '.ml-chap-nav a:hover'),
  1700. boxCss = toStyleStr({
  1701. 'position': 'fixed',
  1702. 'background-color': '#222',
  1703. 'color': 'white',
  1704. 'padding': '7px',
  1705. 'border-top-left-radius': '5px',
  1706. 'cursor': 'default'
  1707. }, '.ml-box'),
  1708. statsCss = toStyleStr({
  1709. 'bottom': '0',
  1710. 'right': '0',
  1711. 'opacity': '0.4',
  1712. 'transition': '250ms'
  1713. }, '.ml-stats'),
  1714. statsCollapseCss = toStyleStr({
  1715. 'color': 'orange',
  1716. 'cursor': 'pointer'
  1717. }, '.ml-stats-collapse'),
  1718. statsHoverCss = toStyleStr({
  1719. 'opacity': '1'
  1720. }, '.ml-stats:hover'),
  1721. floatingMsgCss = toStyleStr({
  1722. 'bottom': '30px',
  1723. 'right': '0',
  1724. 'border-bottom-left-radius': '5px',
  1725. 'text-align': 'left',
  1726. 'font': 'inherit',
  1727. 'max-width': '95%',
  1728. 'z-index': '101',
  1729. 'white-space': 'pre-wrap'
  1730. }, '.ml-floating-msg'),
  1731. floatingMsgAnchorCss = toStyleStr({
  1732. 'color': 'orange'
  1733. }, '.ml-floating-msg a'),
  1734. buttonCss = toStyleStr({
  1735. 'cursor': 'pointer'
  1736. }, '.ml-button'),
  1737. keySettingCss = toStyleStr({
  1738. 'width': '35px'
  1739. }, '.ml-setting-key input'),
  1740. autoloadSettingCss = toStyleStr({
  1741. 'vertical-align': 'middle'
  1742. }, '.ml-setting-autoload');
  1743. // clear all styles and scripts
  1744. var title = document.title;
  1745. document.head.innerHTML = '<meta name="viewport" content="width=device-width, initial-scale=1"><link rel="stylesheet" href="//maxcdn.bootstrapcdn.com/font-awesome/4.3.0/css/font-awesome.min.css">';
  1746. document.title = title;
  1747. document.body.className = '';
  1748. document.body.style = '';
  1749. // navigation
  1750. var nav = '<div class="ml-chap-nav">' + (prevChapter ? '<a class="ml-chap-prev" href="' + prevChapter + '">Prev Chapter</a> ' : '') +
  1751. '<a class="ml-exit" href="' + location.href + '" data-exit="true">Exit</a> ' +
  1752. (nextChapter ? '<a class="ml-chap-next" href="' + nextChapter + '">Next Chapter</a>' : '') + '</div>';
  1753. // message area
  1754. var floatingMsg = '<pre class="ml-box ml-floating-msg"></pre>';
  1755. // stats
  1756. var stats = '<div class="ml-box ml-stats"><span title="hide stats" class="ml-stats-collapse">&gt;&gt;</span><span class="ml-stats-content"><span class="ml-stats-pages"></span> ' +
  1757. '<i class="fa fa-info ml-button ml-info-button" title="See userscript information and help"></i> ' +
  1758. '<i class="fa fa-bar-chart ml-button ml-more-stats-button" title="See page stats"></i> ' +
  1759. '<i class="fa fa-cog ml-button ml-settings-button" title="Adjust userscript settings"></i> ' +
  1760. '<i class="fa fa-refresh ml-button ml-manual-reload" title="Manually refresh next clicked image."></i></span></div>';
  1761. // combine ui elements
  1762. document.body.innerHTML = nav + '<div class="ml-images"></div>' + nav + floatingMsg + stats;
  1763. // add main styles
  1764. addStyle('main', true, viewerCss, imagesCss, imageCss, counterCss, navCss, navHoverCss, statsCss, statsCollapseCss, statsHoverCss, boxCss, floatingMsgCss, buttonCss, keySettingCss, autoloadSettingCss, floatingMsgAnchorCss);
  1765. // add user styles
  1766. var userCss = storeGet('ml-setting-css-profiles');
  1767. var curProf = storeGet('ml-setting-css-current') || 'Default';
  1768. if(userCss && userCss.length > 0) userCss = userCss.filter(function(p) { return p.name === curProf; });
  1769. userCss = userCss && userCss.length > 0 ? userCss[0].css : (storeGet('ml-setting-css') || '');
  1770. addStyle('user', true, userCss);
  1771. // set up return UI object
  1772. var UI = {
  1773. images: getEl('.ml-images'),
  1774. statsContent: getEl('.ml-stats-content'),
  1775. statsPages: getEl('.ml-stats-pages'),
  1776. statsCollapse: getEl('.ml-stats-collapse'),
  1777. btnManualReload: getEl('.ml-manual-reload'),
  1778. btnInfo: getEl('.ml-info-button'),
  1779. btnMoreStats: getEl('.ml-more-stats-button'),
  1780. floatingMsg: getEl('.ml-floating-msg'),
  1781. btnNextChap: getEl('.ml-chap-next'),
  1782. btnPrevChap: getEl('.ml-chap-prev'),
  1783. btnExit: getEl('.ml-exit'),
  1784. btnSettings: getEl('.ml-settings-button'),
  1785. isTyping: false,
  1786. ignore: false,
  1787. moreStats: false,
  1788. currentProfile: storeGet('ml-setting-css-current') || ''
  1789. };
  1790. // message func
  1791. var messageId = null;
  1792. var showFloatingMsg = function(msg, timeout, html) {
  1793. clearTimeout(messageId);
  1794. log(msg);
  1795. if(html) {
  1796. UI.floatingMsg.innerHTML = msg;
  1797. } else {
  1798. UI.floatingMsg.textContent = msg;
  1799. }
  1800. if(!msg) UI.moreStats = false;
  1801. UI.floatingMsg.style.display = msg ? '' : 'none';
  1802. if(timeout) {
  1803. messageId = setTimeout(function() {
  1804. showFloatingMsg('');
  1805. }, timeout);
  1806. }
  1807. };
  1808. var isMessageFloating = function() {
  1809. return !!UI.floatingMsg.innerHTML;
  1810. };
  1811. // configure initial state
  1812. UI.floatingMsg.style.display = 'none';
  1813. // set up listeners
  1814. document.addEventListener('click', function(evt) {
  1815. if (evt.target.nodeName === 'A' && evt.button !== 2) {
  1816. 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
  1817. if(evt.target.className.indexOf('ml-chap') !== -1) {
  1818. log('next chapter will autoload');
  1819. storeSet('autoload', 'yes');
  1820. if(shouldReload) {
  1821. evt.preventDefault();
  1822. location.href = evt.target.href;
  1823. location.reload(true);
  1824. }
  1825. } else if(evt.target.className.indexOf('ml-exit') !== -1) {
  1826. log('exiting chapter, stop autoload');
  1827. storeSet('autoload', 'no');
  1828. if(shouldReload) {
  1829. evt.preventDefault();
  1830. location.reload(true);
  1831. }
  1832. }
  1833. }
  1834. });
  1835. UI.btnMoreStats.addEventListener('click', function(evt) {
  1836. if(isMessageFloating() && UI.lastFloat === evt.target) {
  1837. showFloatingMsg('');
  1838. } else {
  1839. UI.lastFloat = evt.target;
  1840. UI.moreStats = true;
  1841. showFloatingMsg([
  1842. '<strong>Stats:</strong>',
  1843. pageStats.loadLimit + ' pages parsed',
  1844. pageStats.numLoaded + ' images loaded',
  1845. (pageStats.loadLimit - pageStats.numLoaded) + ' images loading',
  1846. (pageStats.numPages || 'Unknown number of') + ' pages in chapter',
  1847. (pageStats.curChap !== null && pageStats.numChaps !== null ? ((pageStats.curChap - 1) + '/' + pageStats.numChaps + ' chapters read ' + (((pageStats.curChap - 1) / pageStats.numChaps * 100).toFixed(2) + '%') + ' of series') : ''),
  1848. ].join('<br>'), null, true);
  1849. }
  1850. });
  1851. UI.btnManualReload.addEventListener('click', function(evt) {
  1852. var imgClick = function(e) {
  1853. var target = e.target;
  1854. UI.images.removeEventListener('click', imgClick, false);
  1855. UI.images.style.cursor = '';
  1856. if(target.nodeName === 'IMG' && target.parentNode.className === 'ml-images') {
  1857. showFloatingMsg('');
  1858. if(!target.title) {
  1859. showFloatingMsg('Reloading "' + target.src + '"', 3000);
  1860. if(target.complete) target.onload = null;
  1861. target.src = target.src + (target.src.indexOf('?') !== -1 ? '&' : '?') + new Date().getTime();
  1862. }
  1863. } else {
  1864. showFloatingMsg('Cancelled manual reload...', 3000);
  1865. }
  1866. };
  1867. showFloatingMsg('Left click the image you would like to reload.\nClick on the page margin to cancel.');
  1868. UI.images.style.cursor = 'pointer';
  1869. UI.images.addEventListener('click', imgClick, false);
  1870. });
  1871. UI.statsCollapse.addEventListener('click', function(evt) {
  1872. var test = UI.statsCollapse.textContent === '>>';
  1873. storeSet('ml-stats-collapsed', test);
  1874. UI.statsContent.style.display = test ? 'none' : '';
  1875. UI.statsCollapse.textContent = test ? '<<' : '>>';
  1876. });
  1877. // restore collapse state
  1878. if(storeGet('ml-stats-collapsed')) UI.statsCollapse.click();
  1879. UI.floatingMsg.addEventListener('focus', function(evt) {
  1880. var target = evt.target;
  1881. if(target.dataset.ignore) UI.ignore = true;
  1882. if((target.nodeName === 'INPUT' && target.type === 'text') || target.nodeName === 'TEXTAREA') UI.isTyping = true;
  1883. }, true);
  1884. UI.floatingMsg.addEventListener('blur', function(evt) {
  1885. var target = evt.target;
  1886. if(target.dataset.ignore) UI.ignore = false;
  1887. if((target.nodeName === 'INPUT' && target.type === 'text') || target.nodeName === 'TEXTAREA') UI.isTyping = false;
  1888. }, true);
  1889. UI.btnInfo.addEventListener('click', function(evt) {
  1890. if(isMessageFloating() && UI.lastFloat === evt.target) {
  1891. showFloatingMsg('');
  1892. } else {
  1893. UI.lastFloat = evt.target;
  1894. showFloatingMsg([
  1895. '<strong>Information:</strong>',
  1896. '<strong>IMPORTANT:</strong> The script has been updated to exclude NSFW sites',
  1897. 'in order to gain access to that functionality you\'ll have to install the following addon script.',
  1898. '<a href="https://sleazyfork.org/en/scripts/12657-manga-loader-nsfw" target="_blank">https://sleazyfork.org/en/scripts/12657-manga-loader-nsfw</a>',
  1899. '',
  1900. 'New feature! You can now define custom CSS in the new settings panel (accessible through the gear icon at the bottom left).',
  1901. 'The CSS will be saved and reapplied each time the script loads. You can change the background color of the page,',
  1902. 'the width of the images and pretty much anything else.',
  1903. '',
  1904. 'CSS feature has now been enhanced to support multiple profiles you can switch between.',
  1905. '',
  1906. '<strong>Default Keybindings:</strong>',
  1907. 'Z - previous chapter',
  1908. 'X - exit',
  1909. 'C - next chapter',
  1910. 'W - scroll up',
  1911. 'S - scroll down',
  1912. '+ - zoom in',
  1913. '- - zoom out',
  1914. '0 - reset zoom',
  1915. 'Click the info button again to close this message.'
  1916. ].join('<br>'), null, true);
  1917. }
  1918. });
  1919. UI.btnSettings.addEventListener('click', function(evt) {
  1920. if(isMessageFloating() && UI.lastFloat === evt.target) {
  1921. showFloatingMsg('');
  1922. } else {
  1923. UI.lastFloat = evt.target;
  1924. // start grid and first column
  1925. var settings = '<table><tr><td>';
  1926. // Custom CSS
  1927. var cssProfiles = storeGet('ml-setting-css-profiles');
  1928. if(!cssProfiles || cssProfiles.length === 0) {
  1929. cssProfiles = [{name: 'Default', css: storeGet('ml-setting-css') || ''}];
  1930. storeSet('ml-setting-css-profiles', cssProfiles);
  1931. }
  1932. cssProfiles.push({ name: 'New Profile...', addNew: true });
  1933. var prof = cssProfiles.filter(function(p) { return p.name === UI.currentProfile; })[0] || cssProfiles[0];
  1934. settings += 'CSS (custom css for Manga Loader):<br>' +
  1935. '<select class="ml-setting-css-profile">' +
  1936. cssProfiles.map(function(profile) { return '<option ' + (profile.name === prof.name ? 'selected' : '') + '>' + profile.name + '</option>'; }).join('') +
  1937. '</select><button class="ml-setting-delete-profile">x</button><br>' +
  1938. '<textarea style="width: 300px; height: 300px;" type="text" class="ml-setting-css">' + prof.css + '</textarea><br><br>';
  1939. // start new column
  1940. settings += '</td><td>';
  1941. // Keybindings
  1942. var keyTableHtml = Object.keys(UI.keys).map(function(action) {
  1943. return '<tr><td>' + action + '</td><td><input data-ignore="true" data-key="' + action + '" type="text" value="' + UI.keys[action] + '"></td></tr>';
  1944. }).join('');
  1945. settings += 'Keybindings:<br><table class="ml-setting-key">' + keyTableHtml + '</table><br>';
  1946. // Autoload
  1947. settings += 'Auto-load: <input class="ml-setting-autoload" type="checkbox" ' + (storeGet('mAutoload') && 'checked' || '') + '><br><br>';
  1948. // Load all or just N pages
  1949. settings += "# of pages to load:<br>" +
  1950. 'Type "all" to load all<br>default is 10<br>' +
  1951. '<input class="ml-setting-loadnum" size="3" type="text" value="' + (storeGet('mLoadNum') || 10) + '" /><br><br>';
  1952. // close grid and column
  1953. settings += '</td></tr></table>';
  1954. // Save button
  1955. settings += '<button class="ml-setting-save">Save</button> <button class="ml-setting-close">Close</button> <span class="ml-setting-save-flash"></span>';
  1956. showFloatingMsg(settings, null, true);
  1957. // handle keybinding detection
  1958. getEl('.ml-setting-key').onkeydown = function(e) {
  1959. var target = e.target;
  1960. if(target.nodeName.toUpperCase() === 'INPUT') {
  1961. e.preventDefault();
  1962. e.stopPropagation();
  1963. target.value = e.which || e.charCode || e.keyCode;
  1964. }
  1965. };
  1966. // delete css profile
  1967. getEl('.ml-setting-delete-profile', UI.floatingMsg).onclick = function(e) {
  1968. if(['Default', 'New Profile...'].indexOf(prof.name) === -1) {
  1969. if(confirm('Are you sure you want to delete profile "' + prof.name + '"?')) {
  1970. var index = cssProfiles.indexOf(prof);
  1971. cssProfiles.splice(index, 1);
  1972. var sel = getEl('.ml-setting-css-profile');
  1973. sel.remove(index);
  1974. sel.selectedIndex = 0;
  1975. sel.onchange({target: sel});
  1976. }
  1977. } else {
  1978. alert('Cannot delete profile: "' + prof.name + '"');
  1979. }
  1980. };
  1981. // change selected css profile
  1982. getEl('.ml-setting-css-profile', UI.floatingMsg).onchange = function(e) {
  1983. var cssBox = getEl('.ml-setting-css');
  1984. prof.css = cssBox.value;
  1985. prof = cssProfiles[e.target.selectedIndex];
  1986. if(prof.addNew) {
  1987. // enter new name
  1988. var newName = '';
  1989. while(!newName || cssProfiles.filter(function(p) { return p.name === newName; }).length > 0) {
  1990. newName = prompt('Enter the name for the new profile (must be unique)');
  1991. if(!newName) {
  1992. e.target.selectedIndex = 0;
  1993. e.target.onchange({target: e.target});
  1994. return;
  1995. }
  1996. }
  1997. // add new profile to array
  1998. var last = cssProfiles.pop();
  1999. cssProfiles.push({name: newName, css: ''}, last);
  2000. prof = cssProfiles[cssProfiles.length - 2];
  2001. // add new profile to select box
  2002. var option = document.createElement('option');
  2003. option.text = newName;
  2004. e.target.add(option, e.target.options.length - 1);
  2005. e.target.selectedIndex = e.target.options.length - 2;
  2006. }
  2007. cssBox.value = prof.css;
  2008. UI.currentProfile = prof.name;
  2009. addStyle('user', true, prof.css);
  2010. };
  2011. // handle save button
  2012. getEl('.ml-setting-save', UI.floatingMsg).onclick = function() {
  2013. // persist css
  2014. var css = getEl('.ml-setting-css', UI.floatingMsg).value.trim();
  2015. prof.css = css;
  2016. addStyle('user', true, css);
  2017. var last = cssProfiles.pop();
  2018. storeSet('ml-setting-css-profiles', cssProfiles);
  2019. cssProfiles.push(last);
  2020. storeSet('ml-setting-css-current', UI.currentProfile);
  2021. // keybindings
  2022. getEls('.ml-setting-key input').forEach(function(input) {
  2023. UI.keys[input.dataset.key] = parseInt(input.value);
  2024. });
  2025. storeSet('ml-setting-key', UI.keys);
  2026. // autoload
  2027. storeSet('mAutoload', getEl('.ml-setting-autoload').checked);
  2028. // loadnum
  2029. var loadnum = getEl('.ml-setting-loadnum').value;
  2030. mLoadNum = getEl('.ml-setting-loadnum').value = loadnum.toLowerCase() === 'all' ? 'all' : (parseInt(loadnum) || 10);
  2031. storeSet('mLoadNum', mLoadNum);
  2032. // flash notify
  2033. var flash = getEl('.ml-setting-save-flash');
  2034. flash.textContent = 'Saved!';
  2035. setTimeout(function() { flash.textContent = ''; }, 1000);
  2036. };
  2037. // handle close button
  2038. getEl('.ml-setting-close', UI.floatingMsg).onclick = function() {
  2039. showFloatingMsg('');
  2040. };
  2041. }
  2042. });
  2043. // zoom
  2044. var lastZoom, originalZoom,newZoomPostion;
  2045. var changeZoom = function(action, elem) {
  2046. var ratioZoom = (document.documentElement.scrollTop || document.body.scrollTop)/(document.documentElement.scrollHeight || document.body.scrollHeight);
  2047. var curImage = getCurrentImage();
  2048. if(!lastZoom) {
  2049. lastZoom = originalZoom = Math.round(curImage.clientWidth / window.innerWidth * 100);
  2050. }
  2051. var zoom = lastZoom;
  2052. if(action === '+') zoom += 5;
  2053. if(action === '-') zoom -= 5;
  2054. if(action === '=') {
  2055. lastZoom = originalZoom;
  2056. addStyle('image-width', true, '');
  2057. showFloatingMsg('reset zoom', 500);
  2058. newZoomPostion =(document.documentElement.scrollHeight || document.body.scrollHeight)*ratioZoom;
  2059. window.scroll(0, newZoomPostion);
  2060. return;
  2061. }
  2062. zoom = Math.max(10, Math.min(zoom, 100));
  2063. lastZoom = zoom;
  2064. addStyle('image-width', true, toStyleStr({
  2065. width: zoom + '%'
  2066. }, '.ml-images img'));
  2067. showFloatingMsg('zoom: ' + zoom + '%', 500);
  2068. newZoomPostion =(document.documentElement.scrollHeight || document.body.scrollHeight)*ratioZoom;
  2069. window.scroll(0, newZoomPostion);
  2070. };
  2071. var goToPage = function(toWhichPage) {
  2072. var curId = getCurrentImage().id;
  2073. var nextId = curId.split('-');
  2074. switch (toWhichPage) {
  2075. case 'next':
  2076. nextId[2] = parseInt(nextId[2]) + 1;
  2077. break;
  2078. case 'previous':
  2079. nextId[2] = parseInt(nextId[2]) - 1;
  2080. break;
  2081. }
  2082. var nextPage = getEl('#' + nextId.join('-'));
  2083. if (nextPage == null) {
  2084. log(curId + " > " + nextId);
  2085. log("Reached the end!");
  2086. } else {
  2087. nextPage.scrollIntoView();
  2088. }
  2089. }
  2090. // keybindings
  2091. UI.keys = {
  2092. PREV_CHAP: 90, EXIT: 88, NEXT_CHAP: 67,
  2093. SCROLL_UP: 87, SCROLL_DOWN: 83,
  2094. ZOOM_IN: 187, ZOOM_OUT: 189, RESET_ZOOM: 48,
  2095. PREV_PAGE: 37, NEXT_PAGE: 39,
  2096. };
  2097. // override defaults for firefox since different keycodes
  2098. if(typeof InstallTrigger !== 'undefined') {
  2099. UI.keys.ZOOM_IN = 61;
  2100. UI.keys.ZOOM_OUT = 173;
  2101. UI.keys.RESET_ZOOM = 48;
  2102. }
  2103. UI.scrollAmt = 50;
  2104. // override the defaults with the user defined ones
  2105. updateObj(UI.keys, storeGet('ml-setting-key') || {});
  2106. UI._keys = {};
  2107. Object.keys(UI.keys).forEach(function(action) {
  2108. UI._keys[UI.keys[action]] = action;
  2109. });
  2110. window.addEventListener('keydown', function(evt) {
  2111. // ignore keybindings when text input is focused
  2112. if(UI.isTyping) {
  2113. if(!UI.ignore) evt.stopPropagation();
  2114. return;
  2115. }
  2116. var code = evt.which || evt.charCode || evt.keyCode;
  2117. // stop propagation if key is registered
  2118. if(code in UI.keys) evt.stopPropagation();
  2119. // perform action
  2120. switch(code) {
  2121. case UI.keys.PREV_CHAP:
  2122. if(UI.btnPrevChap) {
  2123. UI.btnPrevChap.click();
  2124. }
  2125. break;
  2126. case UI.keys.EXIT:
  2127. UI.btnExit.click();
  2128. break;
  2129. case UI.keys.NEXT_CHAP:
  2130. if(UI.btnNextChap) {
  2131. UI.btnNextChap.click();
  2132. }
  2133. break;
  2134. case UI.keys.SCROLL_UP:
  2135. window.scrollBy(0, -UI.scrollAmt);
  2136. break;
  2137. case UI.keys.SCROLL_DOWN:
  2138. window.scrollBy(0, UI.scrollAmt);
  2139. break;
  2140. case UI.keys.ZOOM_IN:
  2141. changeZoom('+', UI.images);
  2142. break;
  2143. case UI.keys.ZOOM_OUT:
  2144. changeZoom('-', UI.images);
  2145. break;
  2146. case UI.keys.RESET_ZOOM:
  2147. changeZoom('=', UI.images);
  2148. break;
  2149. case UI.keys.NEXT_PAGE:
  2150. goToPage('next');
  2151. break;
  2152. case UI.keys.PREV_PAGE:
  2153. goToPage('previous');
  2154. break;
  2155. }
  2156. }, true);
  2157. return UI;
  2158. };
  2159.  
  2160. var getCurrentImage = function() {
  2161. var image;
  2162. getEls('.ml-images img').some(function(img) {
  2163. image = img;
  2164. return img.getBoundingClientRect().bottom > 200;
  2165. });
  2166. return image;
  2167. };
  2168.  
  2169. var getCounter = function(imgNum) {
  2170. var counter = document.createElement('div');
  2171. counter.classList.add('ml-counter');
  2172. counter.textContent = imgNum;
  2173. return counter;
  2174. };
  2175.  
  2176. var addImage = function(src, loc, imgNum, callback) {
  2177. var image = new Image(),
  2178. counter = getCounter(imgNum);
  2179. image.onerror = function() {
  2180. log('failed to load ' + src);
  2181. image.onload = null;
  2182. image.style.backgroundColor = 'white';
  2183. image.style.cursor = 'pointer';
  2184. image.title = 'Reload "' + src + '"?';
  2185. image.src = IMAGES.refresh_large;
  2186. image.onclick = function() {
  2187. image.onload = callback;
  2188. image.title = '';
  2189. image.style.cursor = '';
  2190. image.src = src;
  2191. };
  2192. };
  2193. image.id = 'ml-pageid-' + imgNum;
  2194. image.onload = callback;
  2195. image.src = src;
  2196. loc.appendChild(image);
  2197. loc.appendChild(counter);
  2198. };
  2199.  
  2200. var loadManga = function(imp) {
  2201. var ex = extractInfo.bind(imp),
  2202. imgUrl = ex('img', imp.imgmod),
  2203. nextUrl = ex('next'),
  2204. numPages = ex('numpages'),
  2205. curPage = ex('curpage', {
  2206. type: 'index'
  2207. }) || 1,
  2208. nextChapter = ex('nextchap', {
  2209. type: 'value',
  2210. val: (imp.invchap && -1) || 1
  2211. }),
  2212. prevChapter = ex('prevchap', {
  2213. type: 'value',
  2214. val: (imp.invchap && 1) || -1
  2215. }),
  2216. xhr = new XMLHttpRequest(),
  2217. d = document.implementation.createHTMLDocument(),
  2218. addAndLoad = function(img, next) {
  2219. if(!img) throw new Error('failed to retrieve img for page ' + curPage);
  2220. updateStats();
  2221. addImage(img, UI.images, curPage, function() {
  2222. pagesLoaded += 1;
  2223. updateStats();
  2224. });
  2225. if(!next && curPage < numPages) throw new Error('failed to retrieve next url for page ' + curPage);
  2226. loadNextPage(next);
  2227. },
  2228. updateStats = function() {
  2229. updateObj(pageStats, {
  2230. numLoaded: pagesLoaded,
  2231. loadLimit: curPage,
  2232. numPages: numPages
  2233. });
  2234. if(UI.moreStats) {
  2235. for(var i=2;i--;) UI.btnMoreStats.click();
  2236. }
  2237. UI.statsPages.textContent = ' ' + pagesLoaded + (numPages ? '/' + numPages : '') + ' loaded';
  2238. },
  2239. getPageInfo = function() {
  2240. var page = d.body;
  2241. d.body.innerHTML = xhr.response;
  2242. try {
  2243. // find image and link to next page
  2244. addAndLoad(ex('img', imp.imgmod, page), ex('next', null, page));
  2245. } catch (e) {
  2246. if (xhr.status == 503 && retries > 0) {
  2247. log('xhr status ' + xhr.status + ' retrieving ' + xhr.responseURL + ', ' + retries-- + ' retries remaining');
  2248. window.setTimeout(function() {
  2249. xhr.open('get', xhr.responseURL);
  2250. xhr.send();
  2251. }, 500);
  2252. } else {
  2253. log(e);
  2254. log('error getting details from next page, assuming end of chapter.');
  2255. }
  2256. }
  2257. },
  2258. loadNextPage = function(url) {
  2259. if (mLoadNum !== 'all' && count % mLoadNum === 0) {
  2260. if (resumeUrl) {
  2261. resumeUrl = null;
  2262. } else {
  2263. resumeUrl = url;
  2264. log('waiting for user to scroll further before loading more images, loaded ' + count + ' pages so far, next url is ' + resumeUrl);
  2265. return;
  2266. }
  2267. }
  2268. if (numPages && curPage + 1 > numPages) {
  2269. log('reached "numPages" ' + numPages + ', assuming end of chapter');
  2270. //modified begin 2/2
  2271. var currentUrl = window.location.href;
  2272. addDownloadButton(currentUrl);
  2273. //modified end 2/2
  2274. return;
  2275. }
  2276. if (lastUrl === url) {
  2277. log('last url (' + lastUrl + ') is the same as current (' + url + '), assuming end of chapter');
  2278. return;
  2279. }
  2280. curPage += 1;
  2281. count += 1;
  2282. lastUrl = url;
  2283. retries = 5;
  2284. if (imp.pages) {
  2285. imp.pages(url, curPage, addAndLoad, ex, getPageInfo);
  2286. } else {
  2287. var colonIdx = url.indexOf(':');
  2288. if(colonIdx > -1) {
  2289. url = location.protocol + url.slice(colonIdx + 1);
  2290. }
  2291. xhr.open('get', url);
  2292. imp.beforexhr && imp.beforexhr(xhr);
  2293. xhr.onload = getPageInfo;
  2294. xhr.onerror = function() {
  2295. log('failed to load page, aborting', 'error');
  2296. };
  2297. xhr.send();
  2298. }
  2299. },
  2300. count = 1,
  2301. pagesLoaded = curPage - 1,
  2302. lastUrl, UI, resumeUrl, retries;
  2303. if (!imgUrl || (!nextUrl && curPage < numPages)) {
  2304. log('failed to retrieve ' + (!imgUrl ? 'image url' : 'next page url'), 'exit');
  2305. }
  2306.  
  2307. // gather chapter stats
  2308. pageStats.curChap = ex('curchap', {
  2309. type: 'index',
  2310. invIdx: !!imp.invchap
  2311. });
  2312. pageStats.numChaps = ex('numchaps');
  2313.  
  2314. // do some checks on the chapter urls
  2315. nextChapter = (nextChapter && nextChapter.trim() === location.href + '#' ? null : nextChapter);
  2316. prevChapter = (prevChapter && prevChapter.trim() === location.href + '#' ? null : prevChapter);
  2317.  
  2318. UI = getViewer(prevChapter, nextChapter);
  2319.  
  2320. UI.statsPages.textContent = ' 0/1 loaded, ' + numPages + ' total';
  2321.  
  2322. if (mLoadNum !== 'all') {
  2323. window.addEventListener('scroll', throttle(function(e) {
  2324. if (!resumeUrl) return; // exit early if we don't have a position to resume at
  2325. if(!UI.imageHeight) {
  2326. UI.imageHeight = getEl('.ml-images img').clientHeight;
  2327. }
  2328. var scrollBottom = document.body.scrollHeight - ((document.body.scrollTop || document.documentElement.scrollTop) + window.innerHeight);
  2329. if (scrollBottom < UI.imageHeight * 2) {
  2330. log('user scroll nearing end, loading more images starting from ' + resumeUrl);
  2331. loadNextPage(resumeUrl);
  2332. }
  2333. }, 100));
  2334. }
  2335.  
  2336. addAndLoad(imgUrl, nextUrl);
  2337.  
  2338. };
  2339.  
  2340. var waitAndLoad = function(imp) {
  2341. isLoaded = true;
  2342. if(imp.wait) {
  2343. var waitType = typeof imp.wait;
  2344. if(waitType === 'number') {
  2345. setTimeout(loadManga.bind(null, imp), imp.wait || 0);
  2346. } else {
  2347. var isReady = waitType === 'function' ? imp.wait.bind(imp) : function() {
  2348. return getEl(imp.wait);
  2349. };
  2350. var intervalId = setInterval(function() {
  2351. if(isReady()) {
  2352. log('Condition fulfilled, loading');
  2353. clearInterval(intervalId);
  2354. loadManga(imp);
  2355. }
  2356. }, 200);
  2357. }
  2358. } else {
  2359. loadManga(imp);
  2360. }
  2361. };
  2362.  
  2363. var MLoaderLoadImps = function(imps) {
  2364. var success = imps.some(function(imp) {
  2365. if (imp.match && (new RegExp(imp.match, 'i')).test(pageUrl)) {
  2366. currentImpName = imp.name;
  2367. if (W.BM_MODE || (autoload !== 'no' && (mAutoload || autoload))) {
  2368. log('autoloading...');
  2369. waitAndLoad(imp);
  2370. return true;
  2371. }
  2372. // setup load hotkey
  2373. var loadHotKey = function(e) {
  2374. if(e.ctrlKey && e.keyCode == 188) { // ctrl + , (comma)
  2375. e.preventDefault();
  2376. btnLoad.click();
  2377. window.removeEventListener('keydown', loadHotKey);
  2378. }
  2379. };
  2380. window.addEventListener('keydown', loadHotKey);
  2381. // append button to dom that will trigger the page load
  2382. btnLoad = createButton('Load Manga', function(evt) {
  2383. waitAndLoad(imp);
  2384. this.remove();
  2385. }, btnLoadCss);
  2386. document.body.appendChild(btnLoad);
  2387. return true;
  2388. }
  2389. });
  2390.  
  2391. if (!success) {
  2392. log('no implementation for ' + pageUrl, 'error');
  2393. }
  2394. };
  2395.  
  2396. var pageUrl = window.location.href,
  2397. btnLoadCss = toStyleStr({
  2398. 'position': 'fixed',
  2399. 'bottom': 0,
  2400. 'right': 0,
  2401. 'padding': '5px',
  2402. 'margin': '0 10px 10px 0',
  2403. 'z-index': '9999999999'
  2404. }),
  2405. currentImpName, btnLoad;
  2406.  
  2407. // indicates whether UI loaded
  2408. var isLoaded = false;
  2409. // used when switching chapters
  2410. var autoload = storeGet('autoload');
  2411. // manually set by user in menu
  2412. var mAutoload = storeGet('mAutoload') || false;
  2413. // should we load less pages at a time?
  2414. var mLoadNum = storeGet('mLoadNum') || 10;
  2415. // holder for statistics
  2416. var pageStats = {
  2417. numPages: null, numLoaded: null, loadLimit: null, curChap: null, numChaps: null
  2418. };
  2419.  
  2420. // clear autoload
  2421. storeDel('autoload');
  2422. log('starting...');
  2423.  
  2424. // extra check for settings (hack) on dumb firefox/scriptish, settings aren't udpated until document end
  2425. W.document.addEventListener('DOMContentLoaded', function(e) {
  2426. if(!isLoaded) return;
  2427. // used when switching chapters
  2428. autoload = storeGet('autoload');
  2429. // manually set by user in menu
  2430. mAutoload = storeGet('mAutoload') || false;
  2431. // should we load less pages at a time?
  2432. mLoadNum = storeGet('mLoadNum') || 10;
  2433. if(autoload || mAutoload) {
  2434. btnLoad.click();
  2435. }
  2436. });
  2437. MLoaderLoadImps(implementations);
  2438.  
  2439.  
  2440. // ------------------ All loaders for websites ------------------ //
  2441.  
  2442. // reusable functions to insert in implementations
  2443. var reuse = {
  2444. encodeChinese: function(xhr) {
  2445. xhr.overrideMimeType('text/html;charset=gbk');
  2446. },
  2447. na: function() {
  2448. return 'N/A';
  2449. }
  2450. };
  2451.  
  2452. var exUtil = {
  2453. clearAllTimeouts: function() {
  2454. var id = window.setTimeout(function() {}, 0);
  2455. while (id--) {
  2456. window.clearTimeout(id);
  2457. }
  2458. }
  2459. };
  2460.  
  2461. var nsfwimp = [{
  2462. name: 'geh-and-exh',
  2463. match: "^https?://(e-hentai|exhentai).org/s/.*/.*",
  2464. img: '.sni > a > img, #img',
  2465. next: '.sni > a, #i3 a',
  2466. numpages: 'div.sn > div > span:nth-child(2)',
  2467. curpage: 'div.sn > div > span:nth-child(1)'
  2468. }, {
  2469. name: 'fakku',
  2470. match: "^http(s)?://www.fakku.net/.*/.*/read",
  2471. img: '.current-page',
  2472. next: '.current-page',
  2473. numpages: '.drop',
  2474. curpage: '.drop',
  2475. pages: function(url, num, cb, ex) {
  2476. var firstNum = url.lastIndexOf('/'),
  2477. lastDot = url.lastIndexOf('.');
  2478. var c = url.charAt(firstNum);
  2479. while (c && !/[0-9]/.test(c)) {
  2480. c = url.charAt(++firstNum);
  2481. }
  2482. var curPage = parseInt(url.slice(firstNum, lastDot), 10);
  2483. url = url.slice(0, firstNum) + ('00' + (curPage + 1)).slice(-3) + url.slice(lastDot);
  2484. cb(url, url);
  2485. }
  2486. }, {
  2487. name: 'nowshelf',
  2488. match: "^https?://nowshelf.com/watch/[0-9]*",
  2489. img: '#image',
  2490. next: '#image',
  2491. numpages: function() {
  2492. return parseInt(getEl('#page').textContent.slice(3), 10);
  2493. },
  2494. curpage: function() {
  2495. return parseInt(getEl('#page > input').value, 10);
  2496. },
  2497. pages: function(url, num, cb, ex) {
  2498. cb(page[num], num);
  2499. }
  2500. }, {
  2501. name: 'nhentai',
  2502. match: "^https?://nhentai\\.net\\/g\\/[0-9]+/[0-9]+",
  2503. img: '#image-container > a img',
  2504. next: '#image-container > a',
  2505. numpages: '.num-pages',
  2506. curpage: '.current',
  2507. imgmod: {
  2508. altProp: 'data-cfsrc'
  2509. },
  2510. }, {
  2511. name: '8muses',
  2512. match: "^http(s)?://www.8muses.com/comix/picture/[^/]+/[^/]+/[^/]+/.+",
  2513. img: function(ctx) {
  2514. var img = getEl('.photo img.image', ctx);
  2515. return img ? img.src : getEl('#imageDir', ctx).value + getEl('#imageName', ctx).value;
  2516. },
  2517. next: '.photo > a',
  2518. curpage: '#page-select-s',
  2519. numpages: '#page-select-s'
  2520. }, {
  2521. name: 'hitomi',
  2522. match: "^http(s)?://hitomi.la/reader/[0-9]+.html",
  2523. img: '#comicImages > img',
  2524. next: '#comicImages > img',
  2525. numpages: function() {
  2526. return W.images.length;
  2527. },
  2528. curpage: function() {
  2529. return parseInt(W.curPanel);
  2530. },
  2531. pages: function(url, num, cb, ex) {
  2532. cb(W.images[num - 1].path, num);
  2533. },
  2534. wait: '#comicImages > img'
  2535. }, {
  2536. name: 'doujin-moe',
  2537. _pages: null,
  2538. match: "^https?://doujins\.com/.+",
  2539. img: 'img.picture',
  2540. next: reuse.na,
  2541. numpages: function() {
  2542. if (!this._pages) {
  2543. this._pages = getEls('#gallery djm').map(function(file) {
  2544. return file.getAttribute('file').replace('static2.', 'static.');
  2545. });
  2546. }
  2547. return this._pages.length;
  2548. },
  2549. curpage: function() {
  2550. return parseInt(getEl('.counter').textContent.match(/^[0-9]+/)[0]);
  2551. },
  2552. pages: function(url, num, cb, ex) {
  2553. cb(this._pages[num - 1], num);
  2554. }
  2555. }, {
  2556. name: 'pururin',
  2557. match: "https?://pururin\\.us/read/.+",
  2558. img: 'img.image-next',
  2559. next: 'a.image-next',
  2560. numpages: function() {
  2561. return Object.keys(chapters).length;
  2562. },
  2563. curpage: 'option:checked',
  2564. pages: function(url, num, cb, ex) {
  2565. cb(chapters[num].image, num);
  2566. }
  2567. }, {
  2568. name: 'hentai-rules',
  2569. match: "^https?://www\\.hentairules\\.net/galleries[0-9]*/picture\\.php.+",
  2570. img: '#theMainImage',
  2571. next: '#linkNext',
  2572. imgmod: {
  2573. altProp: 'data-src'
  2574. },
  2575. numpages: function(cur) {
  2576. return parseInt(getEl('.imageNumber').textContent.replace(/([0-9]+)\/([0-9]+)/, cur ? '$1' : '$2'));
  2577. },
  2578. curpage: function() {
  2579. return this.numpages(true);
  2580. }
  2581. }, {
  2582. name: 'ero-senmanga',
  2583. match: "^https?://ero\\.senmanga\\.com/[^/]*/[^/]*/[0-9]*",
  2584. img: '#picture',
  2585. next: '#omv > table > tbody > tr:nth-child(2) > td > a',
  2586. numpages: 'select[name=page]',
  2587. curpage: 'select[name=page]',
  2588. nextchap: function(prev) {
  2589. var next = extractInfo('select[name=chapter]', {
  2590. type: 'value',
  2591. val: (prev ? -1 : 1)
  2592. });
  2593. if (next) return window.location.href.replace(/\/[^\/]*\/[0-9]+\/?$/, '') + '/' + next + '/1';
  2594. },
  2595. prevchap: function() {
  2596. return this.nextchap(true);
  2597. }
  2598. }, {
  2599. name: 'hentaifr',
  2600. match: "^https?://hentaifr\\.net/.+\\.php\\?id=[0-9]+",
  2601. img: function(ctx, next) {
  2602. var img = getEls('img[width]', ctx).filter(function(img) {
  2603. return img.getAttribute('src').indexOf('contenu/doujinshis') !== -1;
  2604. })[0];
  2605. return next ? img : (img ? img.getAttribute('src') : null);
  2606. },
  2607. next: function(ctx) {
  2608. var img = this.img(ctx, true);
  2609. return img ? img.parentNode.getAttribute('href') : null;
  2610. },
  2611. wait: function() {
  2612. return this.img() && this.next();
  2613. }
  2614. }, {
  2615. name: 'prism-blush',
  2616. match: "^https?://prismblush.com/comic/.+",
  2617. img: '#comic img',
  2618. next: '#comic a'
  2619. }, {
  2620. name: 'hentai-here',
  2621. match: "^https?://(www\\.)?hentaihere.com/m/[^/]+/[0-9]+/[0-9]+",
  2622. img: '#arf-reader-img',
  2623. next: reuse.na,
  2624. curpage: function() {
  2625. return parseInt(W.rff_thisIndex);
  2626. },
  2627. numpages: function() {
  2628. return W.rff_imageList.length;
  2629. },
  2630. pages: function(url, num, cb, ex) {
  2631. cb(W.imageCDN + W.rff_imageList[num - 1], num);
  2632. },
  2633. nextchap: function() {
  2634. return W.rff_nextChapter;
  2635. },
  2636. prevchap: function() {
  2637. return W.rff_previousChapter;
  2638. },
  2639. curchap: function() {
  2640. var curchap;
  2641. getEls('ul.dropdown-menu.text-left li').some(function(li, index) {
  2642. if(getEl('a.bg-info', li)) {
  2643. curchap = index + 1;
  2644. }
  2645. });
  2646. return curchap;
  2647. },
  2648. numchaps: 'ul.dropdown-menu.text-left',
  2649. wait: 'ul.dropdown-menu.text-left'
  2650. }, {
  2651. name: 'foolslide',
  2652. match: "^https?://(" + [
  2653. 'reader.yuriproject.net/read/.+',
  2654. 'ecchi.japanzai.com/read/.+',
  2655. 'h.japanzai.com/read/.+',
  2656. 'reader.japanzai.com/read/.+',
  2657. '(raws\\.)?yomanga.co(/reader)?/read/.+',
  2658. 'hentai.cafe/manga/read/.+',
  2659. '(www\\.)?yuri-ism\\.net/slide/read/.'
  2660. ].join('|') + ")",
  2661. img: function() {
  2662. return W.pages[W.current_page].url;
  2663. },
  2664. next: reuse.na,
  2665. numpages: function() {
  2666. return W.pages.length;
  2667. },
  2668. curpage: function() {
  2669. return W.current_page + 1;
  2670. },
  2671. nextchap: function(prev) {
  2672. var desired;
  2673. var dropdown = getEls('ul.dropdown')[1] || getEls('ul.uk-nav')[1];
  2674. if(!dropdown) return;
  2675. getEls('a', dropdown).forEach(function(chap, idx, arr) {
  2676. if(location.href.indexOf(chap.href) === 0) desired = arr[idx + (prev ? 1 : -1)];
  2677. });
  2678. return desired && desired.href;
  2679. },
  2680. prevchap: function() {
  2681. return this.nextchap(true);
  2682. },
  2683. pages: function(url, num, cb, ex) {
  2684. cb(W.pages[num - 1].url, num);
  2685. },
  2686. wait: function() {
  2687. if(W.location.href.indexOf('yomanga') !== -1) {
  2688. // select all possible variable names for data structure
  2689. var matches = [].slice.call(document.body.innerHTML.match(/\w+\s*=\s*\[\{"id"/ig));
  2690. // extract actual variable name from match
  2691. var tokens = matches.map(function(m) {
  2692. var tok = m.match(/^(\w+)\s*=/i);
  2693. return tok && tok[1];
  2694. });
  2695. // determine the variable that holds the largest data structure
  2696. var largest;
  2697. var max = 0;
  2698. tokens.forEach(function(token) {
  2699. var cur = W[token];
  2700. if(cur && cur.length > max && [].every.call(cur, function(pg) { return pg.url.endsWith(pg.filename); })) {
  2701. max = cur.length;
  2702. largest = cur;
  2703. }
  2704. });
  2705. W.pages = largest || 'fail';
  2706. }
  2707. return W.pages;
  2708. }
  2709. }, {
  2710. name: 'tsumino',
  2711. match: '^https?://(www\\.)?tsumino.com/Read/View/.+',
  2712. img: '.reader-img',
  2713. next: reuse.na,
  2714. numpages: function(curpage) {
  2715. return W.reader_max_page;
  2716. },
  2717. curpage: function() {
  2718. return W.reader_current_pg;
  2719. },
  2720. pages: function(url, num, cb) {
  2721. var self = this;
  2722. if(!self._pages) {
  2723. ajax({
  2724. method: 'POST',
  2725. url: '/Read/Load',
  2726. data: 'q=' + W.location.href.match(/View\/(\d+)/)[1],
  2727. responseType: 'json',
  2728. beforeSend: function(xhr) {
  2729. xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
  2730. },
  2731. onload: function(e) {
  2732. var res = e.target.response;
  2733. if(!res) return log('failed to load tsumino pages, site has probably been updated, report on forums', 'error');
  2734. self._pages = res.reader_page_urls.map(function(page) {
  2735. return W.reader_baseobj_url + '?name=' + encodeURIComponent(page);
  2736. });
  2737. cb(self._pages[num - 1], num);
  2738. }
  2739. });
  2740. } else {
  2741. cb(self._pages[num - 1], num);
  2742. }
  2743. },
  2744. wait: '.reader-img'
  2745. }, {
  2746. name: 'dynasty-scans',
  2747. match: "^https?://dynasty-scans.com/chapters/.*",
  2748. img: '#image > img',
  2749. next: reuse.na,
  2750. numpages: function() {
  2751. return W.pages.length;
  2752. },
  2753. curpage: function() {
  2754. return parseInt(getEl('#image > div.pages-list > a.page.active').getAttribute('href').slice(1));
  2755. },
  2756. nextchap: '#next_link',
  2757. prevchap: '#prev_link',
  2758. pages: function(url, num, cb, ex) {
  2759. url = W.pages[num - 1].image;
  2760. cb(url, url);
  2761. }
  2762. }, {
  2763. name: 'hentaibox',
  2764. match: 'https?://www\\.hentaibox\\.net/hentai-manga/[^/]+/[0-9]+',
  2765. img: 'td > center > a > img',
  2766. next: 'td > center > a',
  2767. numpages: function(cur) {
  2768. var sel = getEl('select[name=np2]');
  2769. if(sel) {
  2770. var info = sel.options[0].textContent.match(/Page ([0-9]+) out of ([0-9]+)/i);
  2771. if(info && info.length >= 3) return parseInt(cur ? info[1] : info[2]);
  2772. }
  2773. },
  2774. curpage: function() {
  2775. return this.numpages(true);
  2776. }
  2777. }, {
  2778. name: 'hentai-free', // trying to only show button on manga pages, but URL scheme makes it tough.
  2779. match: "^http://hentai-free.org/(?!(?:tag|manga-doujinshi|hentai-video|hentai-wall|tags-map)/|.*\\?).+/$",
  2780. img: function() {
  2781. return W.pages[0];
  2782. },
  2783. next: reuse.na,
  2784. numpages: function() {
  2785. return W.pages.length;
  2786. },
  2787. curpage: function() {
  2788. return 1;
  2789. },
  2790. wait: function() {
  2791. var links = getEls('.gallery-item a, a.bwg_lightbox_0');
  2792. if (links.length > 0) {
  2793. W.pages = links.map(function(el) {
  2794. return el.href.replace(/\?.*$/, '');
  2795. });
  2796. return true;
  2797. }
  2798. return false;
  2799. },
  2800. pages: function(url, num, cb, ex) {
  2801. cb(W.pages[num - 1], num);
  2802. }
  2803. }, {
  2804. name: 'mangafap',
  2805. match: "^https?://mangafap\\.com/image/.+",
  2806. img: '#p',
  2807. next: '#lbanner + div > a',
  2808. numpages: 'select.span2',
  2809. curpage: '.pagination li.active a'
  2810. }, {
  2811. name: 'hentai4manga',
  2812. match: "^https?://hentai4manga\\.com/hentai_manga/.+/\\d+/$",
  2813. img: '#textboxContent img',
  2814. next: '#textboxContent a',
  2815. numpages: '#sl',
  2816. curpage: '#sl'
  2817. }, {
  2818. name: 'heymanga',
  2819. match: "https?://(www\\.)?heymanga\\.me/manga/[^/]+/[0-9.]+/[0-9]+",
  2820. img: '#img-content',
  2821. next: function(context) {
  2822. var num = this.curpage(context) + 1;
  2823. return document.location.href.replace(/([0-9]+)$/, num);
  2824. },
  2825. numpages: function() {
  2826. exUtil.clearAllTimeouts(); // site things mloader is an adblocker because it removes elems and blocks page, this removes the interval that checks for it
  2827. return getEl('#page_list').length - 1;
  2828. },
  2829. curpage: function(context) {
  2830. var match = getEls('#page_list > option[selected]', context);
  2831. return parseInt(match[match.length - 1].value); // dumb site always marks page 1 as "selected" in addition to actual selected page
  2832. },
  2833. nextchap: 'section > .row.text-center > p + a, section > .row.text-center .ti-line-dotted + a',
  2834. prevchap: 'section > .row.text-center .ti-hand-point-left + a',
  2835. wait: '#page_list > option[selected]'
  2836. }, {
  2837. name: 'simply-hentai',
  2838. match: "https?://(.+\\.)?simply-hentai\\.com/.+/page/[0-9]+",
  2839. img: function(ctx) {
  2840. return getEl('#image span:nth-of-type(2)', ctx).dataset.src;
  2841. },
  2842. next: '#nextLink',
  2843. numpages: function() {
  2844. return parseInt(getEl('.inner-content .row .m10b.bold').textContent.match(/Page\s*\d+\s*of\s*(\d+)/)[1]);
  2845. },
  2846. curpage: function() {
  2847. return parseInt(getEl('.inner-content .row .m10b.bold').textContent.match(/Page\s*(\d+)/)[1]);
  2848. },
  2849. wait: '#image img'
  2850. }, {
  2851. name: 'gameofscanlation',
  2852. match: "https?://gameofscanlation\.moe/projects/.+/.+",
  2853. img: '.chapterPages img:first-of-type',
  2854. next: function() {
  2855. return location.href;
  2856. },
  2857. numpages: function() {
  2858. return getEls('.chapterPages img').length;
  2859. },
  2860. nextchap: '.comicNextPageUrl',
  2861. prevchap: '.comicPreviousPageUrl',
  2862. curchap: 'select[name=chapter_list]',
  2863. numchaps: 'select[name=chapter_list]',
  2864. pages: function(url, num, cb, ex) {
  2865. cb(W.pages[num - 1], location.href + '#' + num);
  2866. },
  2867. wait: function() {
  2868. W.pages = getEls('.chapterPages img').map(function (el) {
  2869. return el.src;
  2870. });
  2871. return W.pages && W.pages.length > 0;
  2872. }
  2873. }, {
  2874. name: 'luscious',
  2875. match: "https?://luscious\\.net/c/.+?/pictures/album/.+?/id/.+",
  2876. img: '.icon-download',
  2877. next: '#next',
  2878. curpage: function() {
  2879. return parseInt(getEl('#pj_page_no').value);
  2880. },
  2881. numpages: '#pj_no_pictures'
  2882. }, {
  2883. name: 'hentaifox',
  2884. match: "https?://hentaifox\\.com/g/.+",
  2885. img: '.gallery_content img.lazy',
  2886. next: '.gallery_content a.next_nav',
  2887. curpage: function() {
  2888. return parseInt(extractInfo('.pag_info', {type: 'text'}));
  2889. },
  2890. numpages: function() {
  2891. return extractInfo('.pag_info') - 2;
  2892. }
  2893. }, {
  2894. name: 'hentai2read',
  2895. match: "https?://hentai2read\\.com/.+",
  2896. img: '#arf-reader',
  2897. next: '#arf-reader',
  2898. curpage: function() {
  2899. return parseInt(ARFfwk.doReader.data.index);
  2900. },
  2901. numpages: function() {
  2902. return ARFfwk.doReader.data['images'].length;
  2903. },
  2904. nextchap: function(){
  2905. return ARFfwk.doReader.data.nextURL;
  2906. },
  2907. prevchap: function(){
  2908. return ARFfwk.doReader.data.previousURL;
  2909. },
  2910. pages: function(url, num, cb, ex) {
  2911. cb(ARFfwk.doReader.getImageUrl(ARFfwk.doReader.data['images'][num - 1]), num);
  2912. }
  2913. }, {
  2914. name: 'hentai.ms',
  2915. match: "http://www.hentai.ms/manga/[^/]+/.+",
  2916. img: 'center > a > img',
  2917. next: 'center > a:last-child',
  2918. curpage: function() {
  2919. return parseInt(getEl('center > a').parentNode.textContent.match(/([0-9]+)\//)[1]);
  2920. },
  2921. numpages: function() {
  2922. return parseInt(getEl('center > a').parentNode.textContent.match(/\/([0-9]+)/)[1]);
  2923. }
  2924. }];
  2925.  
  2926. log('loading nsfw implementations...');
  2927. MLoaderLoadImps(nsfwimp);