Greasy Fork is available in English.

Manga Loader

Loads manga chapter into one page in a long strip format, supports switching chapters and works for a variety of sites, minimal script with no dependencies, easy to implement new sites

Від 25.12.2014. Дивіться остання версія.

  1. // ==UserScript==
  2. // @name Manga Loader
  3. // @namespace http://www.fuzetsu.com/MangaLoader
  4. // @version 1.4.13
  5. // @description Loads manga chapter into one page in a long strip format, supports switching chapters and works for a variety of sites, minimal script with no dependencies, easy to implement new sites
  6. // @copyright 2014+, fuzetsu
  7. // @noframes
  8. // @grant GM_getValue
  9. // @grant GM_setValue
  10. // @grant GM_deleteValue
  11. // @grant GM_registerMenuCommand
  12. // @match http://bato.to/read/*
  13. // @match http://mangafox.me/manga/*/*/*
  14. // @match http://readms.com/r/*/*/*/*
  15. // @match http://mangastream.com/read/*/*/*/*
  16. // @match http://g.e-hentai.org/s/*/*
  17. // @match http://exhentai.org/s/*/*
  18. // @match *://www.fakku.net/*/*/read*
  19. // @match http://www.mangareader.net/*/*
  20. // @match http://www.mangahere.co/manga/*/*
  21. // @match http://www.mangapanda.com/*/*
  22. // @match http://mangadeer.com/manga/*/*/*/*
  23. // @match http://mangacow.co/*/*
  24. // @match http://nowshelf.com/watch/*
  25. // @match http://nhentai.net/g/*/*
  26. // @match http://centraldemangas.net/online/*/*
  27. // @match http://narutomanga.com.br/leitura/online/capitulo/*
  28. // @match http://bleachmanga.com.br/leitura/online/capitulo/*
  29. // @match http://www.mangatown.com/manga/*/*/*
  30. // @match http://mangajoy.com/*/*
  31. // @match http://*.dm5.com/m*
  32. // @match http://raw.senmanga.com/*/*/*
  33. // @match http://www.japscan.com/lecture-en-ligne/*
  34. // @match http://www.pecintakomik.com/manga/*/*
  35. // @match http://dynasty-scans.com/chapters/*
  36. // @match http://www.onemanga.me/*/*
  37. // ==/UserScript==
  38. // don't bother running if in a frame
  39. if (window.top !== window.self) return;
  40.  
  41. // set to true for manga load without prompt
  42. var BM_MODE = false;
  43.  
  44. var scriptName = 'Manga Loader';
  45. var pageTitle = document.title;
  46.  
  47. /**
  48. Sample Implementation:
  49. {
  50. match: "http://domain.com/.*" // the url to react to for manga loading
  51. , img: '#image' // css selector to get the page's manga image
  52. , next: '#next_page' // css selector to get the link to the next page
  53. , numpages: '#page_select' // css selector to get the number of pages. elements like (select, span, etc)
  54. , curpage: '#page_select' // css selector to get the current page. usually the same as numPages if it's a select element
  55. , nextchap: '#next_chap' // css selector to get the link to the next chapter
  56. , prevchap: '#prev_chap' // same as above except for previous
  57. , wait: 3000 // how many ms to wait before auto loading (to wait for elements to load)
  58. , pages: function(next_url, current_page_number, callback, extract_function) {
  59. // gets called requesting a certain page number (current_page_number)
  60. // to continue loading execute callback with img to append as first parameter and next url as second parameter
  61. // only really needs to be used on sites that have really unusual ways of loading images or depend on javascript
  62. }
  63.  
  64. Any of the CSS selectors can be functions instead that return the desired value.
  65. }
  66. */
  67.  
  68. var implementations = [{ // Batoto
  69. match: "^http://bato.to/read/.*",
  70. img: '#comic_page',
  71. next: '#full_image + div > a',
  72. numpages: '#page_select',
  73. curpage: '#page_select',
  74. nextchap: 'select[name=chapter_select]',
  75. prevchap: 'select[name=chapter_select]',
  76. invchap: true
  77. }, { // MangaPanda
  78. match: "^http://www.mangapanda.com/.*/[0-9]*",
  79. img: '#img',
  80. next: '.next a',
  81. numpages: '#pageMenu',
  82. curpage: '#pageMenu',
  83. nextchap: 'td.c5 + td a',
  84. prevchap: 'table.c6 tr:last-child td:last-child a'
  85. }, { // MangaFox
  86. match: "^http://mangafox.me/manga/[^/]*/[^/]*/[^/]*",
  87. img: '#image',
  88. next: 'a.next_page',
  89. numpages: function() {
  90. return extractInfo('select.m') - 1;
  91. },
  92. curpage: 'select.m',
  93. nextchap: '#chnav p + p a',
  94. prevchap: '#chnav a'
  95. }, { // MangaStream
  96. match: "^http://(readms|mangastream).com/(r|read)/[^/]*/[^/]*/[^/]*",
  97. img: '#manga-page',
  98. next: '.next a',
  99. numpages: function() {
  100. var lastPage = getEl('.subnav-wrapper .controls .btn-group:last-child ul li:last-child');
  101. return parseInt(lastPage.textContent.match(/[0-9]/g).join(''), 10);
  102. },
  103. nextchap: function(prev) {
  104. var found;
  105. var chapters = [].slice.call(document.querySelectorAll('.controls > div:first-child > .dropdown-menu > li a'));
  106. chapters.pop();
  107. for (var i = 0; i < chapters.length; i++) {
  108. if (window.location.href.indexOf(chapters[i].href) !== -1) {
  109. found = chapters[i + (prev ? 1 : -1)];
  110. if (found) return found.href;
  111. }
  112. }
  113. },
  114. prevchap: function() {
  115. return this.nextchap(true);
  116. }
  117. }, { // MangaReader
  118. match: "^http://www.mangareader.net/.*/.*",
  119. img: '#img',
  120. next: '.next a',
  121. numpages: '#pageMenu',
  122. curpage: '#pageMenu',
  123. nextchap: 'td.c5 + td a',
  124. prevchap: 'table.c6 tr:last-child td:last-child a'
  125. }, { // MangaTown
  126. match: "^http://www.mangatown.com/manga/[^/]*/v[0-9]*/c[0-9]*",
  127. img: '#image',
  128. next: '#viewer a',
  129. numpages: '.page_select select',
  130. curpage: '.page_select select',
  131. nextchap: '#top_chapter_list',
  132. prevchap: '#top_chapter_list',
  133. wait: 1000
  134. }, { // MangaCow
  135. match: "^http://mangacow\\.co/.*/[0-9]*",
  136. img: '.prw > a > img',
  137. next: '.prw > a:last-child',
  138. numpages: 'select.cbo_wpm_pag',
  139. curpage: 'select.cbo_wpm_pag',
  140. nextchap: function(prev) {
  141. var chapSel = getEl('select.cbo_wpm_chp');
  142. var nextChap = chapSel.options[chapSel.selectedIndex + (prev ? 1 : -1)];
  143. if (nextChap) {
  144. return 'http://mangacow.co/' + window.location.pathname.slice(1, window.location.pathname.slice(1).indexOf('/') + 2) + nextChap.value;
  145. }
  146. },
  147. prevchap: function() {
  148. return this.nextchap(true);
  149. }
  150. }, { // MangaHere
  151. match: "^http://www.mangahere.co/manga/.*/.*",
  152. img: '#viewer img',
  153. next: '#viewer a',
  154. numpages: 'select.wid60',
  155. curpage: 'select.wid60',
  156. nextchap: function(prev) {
  157. var chapters = getEls('.reader_tip > p:nth-last-child(2) > a, .reader_tip > p:nth-last-child(1) > a'),
  158. tag = chapters[0] && chapters[0].previousElementSibling && chapters[0].previousElementSibling.textContent.trim();
  159. if (tag === 'Next Chapter:') {
  160. if (prev) {
  161. return chapters[1];
  162. } else {
  163. return chapters[0];
  164. }
  165. } else {
  166. if (prev) {
  167. return chapters[1];
  168. }
  169. }
  170. },
  171. prevchap: function() {
  172. return this.nextchap(true);
  173. }
  174. }, { // MangaDeer
  175. match: "^http://mangadeer\\.com/manga/.*",
  176. img: '.img-link > img',
  177. next: '.page > span:last-child > a',
  178. numpages: '#sel_page_1',
  179. curpage: '#sel_page_1',
  180. nextchap: function(prev) {
  181. var ddl = getEl('#sel_book_1');
  182. var index = ddl.selectedIndex + (prev ? -1 : 1);
  183. if (index >= ddl.options.length) return;
  184. var mangaName = window.location.href.slice(window.location.href.indexOf('manga/') + 6);
  185. mangaName = mangaName.slice(0, mangaName.indexOf('/'));
  186. return 'http://mangadeer.com/manga/' + mangaName + ddl.options[index].value + '/1';
  187. },
  188. prevchap: function() {
  189. return this.nextchap(true);
  190. }
  191. }, { // Central de Mangas
  192. match: "^http://(centraldemangas\\.net|(bleachmanga|narutomanga)\\.com\\.br/leitura)/online/[^/]*/[0-9]*",
  193. img: '#manga-page',
  194. next: '#manga-page',
  195. numpages: '#manga_pages',
  196. curpage: '#manga_pages',
  197. nextchap: function(prev) {
  198. var url = window.location.href,
  199. chapters = getEl('#manga_caps'),
  200. urlPre = url.slice(0, url.lastIndexOf('/') + 1),
  201. newChap = chapters.options[chapters.selectedIndex + (prev ? -1 : 1)];
  202. return newChap ? urlPre + newChap.textContent : null;
  203. },
  204. prevchap: function() {
  205. return this.nextchap(true);
  206. },
  207. pages: function(url, num, cb, ex) {
  208. var url = url.slice(0, url.lastIndexOf('-') + 1) + ("0" + num).slice(-2) + url.slice(url.lastIndexOf('.'));
  209. cb(url, url);
  210. }
  211. }, { // Manga Joy
  212. match: "^http://mangajoy.com/[^/]*/[0-9]*",
  213. img: '.prw img',
  214. next: '.nxt',
  215. numpages: '.wpm_pag.mng_rdr > div:nth-child(3) > ul > li:nth-child(3) > select',
  216. curpage: '.wpm_pag.mng_rdr > div:nth-child(3) > ul > li:nth-child(3) > select',
  217. nextchap: function(prev) {
  218. var chapter = extractInfo('.wpm_pag.mng_rdr > div:nth-child(3) > ul > li:nth-child(2) > select', {
  219. type: 'value',
  220. val: prev ? 1 : -1
  221. });
  222. if (chapter) {
  223. var urlParts = window.location.href.slice(7).split('/');
  224. while (urlParts.length > 2) urlParts.pop();
  225. return 'http://' + urlParts.join('/') + '/' + chapter;
  226. }
  227. },
  228. prevchap: function() {
  229. return this.nextchap(true);
  230. }
  231. }, { // GEH/EXH
  232. match: "^http://(g.e-hentai|exhentai).org/s/.*/.*",
  233. img: '.sni > a > img, #img',
  234. next: '.sni > a, #i3 a'
  235. }, { // Fakku
  236. match: "^http(s)?://www.fakku.net/.*/.*/read",
  237. img: '.current-page',
  238. next: '.current-page',
  239. numpages: '.drop',
  240. curpage: '.drop',
  241. pages: function(url, num, cb, ex) {
  242. var firstNum = url.lastIndexOf('/'),
  243. lastDot = url.lastIndexOf('.');
  244. var c = url.charAt(firstNum);
  245. while (c && !/[0-9]/.test(c)) {
  246. c = url.charAt(++firstNum);
  247. }
  248. var curPage = parseInt(url.slice(firstNum, lastDot), 10);
  249. var url = url.slice(0, firstNum) + ('00' + (curPage + 1)).slice(-3) + url.slice(lastDot);
  250. cb(url, url);
  251. }
  252. }, { // Nowshelf
  253. match: "^http://nowshelf.com/watch/[0-9]*",
  254. img: '#image',
  255. next: '#image',
  256. numpages: function() {
  257. return parseInt(getEl('#page').textContent.slice(3), 10);
  258. },
  259. curpage: function() {
  260. return parseInt(getEl('#page > input').value, 10);
  261. },
  262. pages: function(url, num, cb, ex) {
  263. var url = url.slice(0, -7) + ('00' + num).slice(-3) + url.slice(-4);
  264. cb(url, url);
  265. }
  266. }, { // nhentai
  267. match: "^http://nhentai\\.net\\/g\\/[0-9]*/[0-9]*",
  268. img: '#image-container > a > img',
  269. next: '#image-container > a > img',
  270. numpages: '.num-pages',
  271. curpage: '.current',
  272. pages: function(url, num, cb, ex) {
  273. url = url.replace(/\/[^\/]*$/, '/') + num;
  274. cb(url, url);
  275. }
  276. }, { // dm5
  277. match: "^http://[^\\.]*\\.dm5\\.com/m[0-9]*",
  278. img: '#cp_image',
  279. next: '#cp_image',
  280. numpages: '#pagelist',
  281. curpage: '#pagelist',
  282. pages: function(url, num, cb, ex) {
  283. var cid = window.location.href.match(/m[0-9]*/g)[2].slice(1),
  284. xhr = new XMLHttpRequest();
  285. xhr.open('get', 'imagefun.ashx?cid=' + cid + '&page=' + num);
  286. xhr.onload = function() {
  287. var images = eval(xhr.responseText);
  288. console.log(self.images);
  289. cb(images[0], images[0]);
  290. };
  291. xhr.send();
  292. }
  293. }, { // Senmanga
  294. match: "^http://raw\\.senmanga\\.com/[^/]*/[0-9]*/[0-9]*",
  295. img: '#picture',
  296. next: '#omv > table > tbody > tr:nth-child(2) > td > a',
  297. numpages: 'select[name=page]',
  298. curpage: 'select[name=page]',
  299. nextchap: function(prev) {
  300. var url = window.location.href,
  301. current = url.match(/\/([0-9]+)\//)[1];
  302. return window.location.href.replace(/[0-9]+\/[0-9]+\/?$/, '') + (parseInt(current) + (prev ? -1 : 1)) + '/1';
  303. },
  304. prevchap: function() {
  305. return this.nextchap(true);
  306. }
  307. }, { // japscan
  308. match: "^http://www\\.japscan\\.com/lecture-en-ligne/[^/]*/[0-9]*",
  309. img: '#imgscan',
  310. next: '#next_link',
  311. numpages: '#pages',
  312. curpage: '#pages',
  313. nextchap: '#next_chapter',
  314. prevchap: '#back_chapter'
  315. }, { // pecintakomik
  316. match: "^http://www\\.pecintakomik\\.com/manga/[^/]*/[^/]*",
  317. img: '.picture',
  318. next: '.pager a:nth-child(3)',
  319. numpages: 'select[name=page]',
  320. curpage: 'select[name=page]',
  321. nextchap: function(prev) {
  322. var chapters = getEl('select[name=chapter]'),
  323. chapter = chapters.options[chapters.selectedIndex + (prev ? 1 : -1)];
  324. if (chapter) {
  325. return window.location.href.replace(/\/[^\/]*\/[0-9]*\/?$/, '/' + chapter.value);
  326. }
  327. },
  328. prevchap: function() {
  329. return this.nextchap(true);
  330. }
  331. }, { // dynasty-scans
  332. match: "^http://dynasty-scans.com/chapters/.*",
  333. img: '#image > img',
  334. next: '#image > img',
  335. numpages: function() {
  336. return unsafeWindow.pages.length;
  337. },
  338. curpage: function() {
  339. return parseInt(getEl('#image > div.pages-list > a.page.active').textContent);
  340. },
  341. nextchap: '#next_link',
  342. prevchap: '#prev_link',
  343. pages: function(url, num, cb, ex) {
  344. url = unsafeWindow.pages[num - 1].image;
  345. cb(url, url);
  346. }
  347. }, { // OneManga
  348. match: "^http://www\\.onemanga\\.me/[^/]*/[0-9]*",
  349. img: 'img.manga-page',
  350. next: '.nav_pag > li:nth-child(1) > a',
  351. numpages: 'select.cbo_wpm_pag',
  352. curpage: 'select.cbo_wpm_pag',
  353. nextchap: function(prev) {
  354. var curChap = parseInt(extractInfo('select.cbo_wpm_chp', {type: 'value'})),
  355. targetChap = curChap + (prev ? -1 : 1);
  356. return window.location.href.replace(/\/[0-9]*(\/[0-9]*\/?)?$/, '/' + targetChap);
  357. },
  358. prevchap: function() {
  359. return this.nextchap(true);
  360. }
  361. }];
  362.  
  363. var log = function(msg, type) {
  364. type = type || 'log';
  365. if (type === 'exit') {
  366. throw scriptName + ' exit: ' + msg;
  367. } else {
  368. console[type](scriptName + ' ' + type + ': ', msg);
  369. }
  370. };
  371.  
  372. var getEl = function(q, c) {
  373. if (!q) return;
  374. return (c || document).querySelector(q);
  375. };
  376.  
  377. var getEls = function(q, c) {
  378. return [].slice.call((c || document).querySelectorAll(q));
  379. };
  380.  
  381. var storeGet = function(key) {
  382. if (typeof GM_getValue === "undefined") {
  383. var value = localStorage.getItem(key);
  384. if (value === "true" || value === "false") {
  385. return (value === "true") ? true : false;
  386. }
  387. return value;
  388. }
  389. return GM_getValue(key);
  390. };
  391.  
  392. var storeSet = function(key, value) {
  393. if (typeof GM_setValue === "undefined") {
  394. return localStorage.setItem(key, value);
  395. }
  396. return GM_setValue(key, value);
  397. };
  398.  
  399. var storeDel = function(key) {
  400. if (typeof GM_deleteValue === "undefined") {
  401. return localStorage.removeItem(key);
  402. }
  403. return GM_deleteValue(key);
  404. };
  405.  
  406. var extractInfo = function(selector, mod, context) {
  407. selector = this[selector] || selector;
  408. if (typeof selector === 'function') {
  409. return selector.call(this);
  410. }
  411. var elem = getEl(selector, context),
  412. option;
  413. mod = mod || {};
  414. if (elem) {
  415. switch (elem.nodeName.toLowerCase()) {
  416. case 'img':
  417. return (mod.altProp && elem.getAttribute(mod.altProp)) || elem.src || elem.getAttribute('src');
  418. case 'a':
  419. return elem.href || elem.getAttribute('href');
  420. case 'ul':
  421. return elem.children.length;
  422. case 'select':
  423. switch (mod.type) {
  424. case 'index':
  425. return elem.options.selectedIndex + 1;
  426. case 'value':
  427. option = elem.options[elem.options.selectedIndex + (mod.val || 0)] || {};
  428. return option.value;
  429. default:
  430. return elem.options.length;
  431. }
  432. break;
  433. default:
  434. switch (mod.type) {
  435. case 'index':
  436. return parseInt(elem.textContent);
  437. default:
  438. return elem.textContent;
  439. }
  440. }
  441. }
  442. };
  443.  
  444. var addStyle = function() {
  445. if (!this.MLStyle) {
  446. this.MLStyle = document.createElement('style');
  447. this.MLStyle.dataset.name = 'ml-style';
  448. document.head.appendChild(this.MLStyle);
  449. }
  450. this.MLStyle.textContent += [].join.call(arguments, '\n');
  451. };
  452.  
  453. var toStyleStr = function(obj, selector) {
  454. var stack = [],
  455. key;
  456. for (key in obj) {
  457. if (obj.hasOwnProperty(key)) {
  458. stack.push(key + ':' + obj[key]);
  459. }
  460. }
  461. if (selector) {
  462. return selector + '{' + stack.join(';') + '}';
  463. } else {
  464. return stack.join(';');
  465. }
  466. };
  467.  
  468. var throttle = function(callback, limit) {
  469. var wait = false;
  470. return function() {
  471. if (!wait) {
  472. callback();
  473. wait = true;
  474. setTimeout(function() {
  475. wait = false;
  476. }, limit);
  477. }
  478. };
  479. };
  480.  
  481. var createButton = function(text, action, styleStr) {
  482. var button = document.createElement('button');
  483. button.textContent = text;
  484. button.onclick = action;
  485. button.setAttribute('style', styleStr || '');
  486. return button;
  487. };
  488.  
  489. var getViewer = function(prevChapter, nextChapter) {
  490. var viewerCss = toStyleStr({
  491. 'background-color': 'black',
  492. 'font': '0.813em courier',
  493. 'text-align': 'center',
  494. }, 'body'),
  495. imagesCss = toStyleStr({
  496. 'margin-top': '10px',
  497. 'margin-bottom': '10px'
  498. }, '.ml-images'),
  499. imageCss = toStyleStr({
  500. 'max-width': '100%',
  501. 'display': 'block',
  502. 'margin': '3px auto'
  503. }, '.ml-images img'),
  504. counterCss = toStyleStr({
  505. 'background-color': '#222',
  506. 'color': 'white',
  507. 'border-radius': '10px',
  508. 'width': '30px',
  509. 'margin-left': 'auto',
  510. 'margin-right': 'auto',
  511. 'margin-top': '-12px',
  512. 'padding-left': '5px',
  513. 'padding-right': '5px',
  514. 'border': '1px solid white',
  515. 'z-index': '100',
  516. 'position': 'relative'
  517. }, '.ml-counter'),
  518. navCss = toStyleStr({
  519. 'text-decoration': 'none',
  520. 'color': 'white',
  521. 'background-color': '#444',
  522. 'padding': '3px 10px',
  523. 'border-radius': '5px',
  524. 'transition': '250ms'
  525. }, '.ml-chap-nav a'),
  526. navHoverCss = toStyleStr({
  527. 'background-color': '#555'
  528. }, '.ml-chap-nav a:hover'),
  529. statsCss = toStyleStr({
  530. 'position': 'fixed',
  531. 'bottom': '0',
  532. 'right': '0',
  533. 'background-color': '#222',
  534. 'color': 'white',
  535. 'padding': '7px',
  536. 'border-top-left-radius': '5px',
  537. 'opacity': '0.4',
  538. 'transition': '250ms',
  539. 'cursor': 'default'
  540. }, '.ml-stats'),
  541. statsCollapseCss = toStyleStr({
  542. 'color': 'orange',
  543. 'cursor': 'pointer'
  544. }, '.ml-stats-collapse'),
  545. statsHoverCss = toStyleStr({
  546. 'opacity': '1'
  547. }, '.ml-stats:hover');
  548. // clear all styles and scripts
  549. var title = document.title;
  550. document.head.innerHTML = '<meta name="viewport" content="width=device-width, initial-scale=1">';
  551. document.title = title;
  552. // navigation
  553. var nav = '<div class="ml-chap-nav">' + (prevChapter ? '<a href="' + prevChapter + '">Prev Chapter</a> ' : '') +
  554. (storeGet('mAutoload') ? '' : '<a href="" data-ignore="true">Exit</a> ') +
  555. (nextChapter ? '<a href="' + nextChapter + '">Next Chapter</a>' : '') + '</div>';
  556. // stats
  557. var stats = '<div class="ml-stats"><span title="hide stats" class="ml-stats-collapse">&gt;&gt;</span><span class="ml-stats-content"></span></div>';
  558. // combine ui elements
  559. document.body.innerHTML = nav + '<div class="ml-images"></div>' + nav + stats;
  560. // add all styles to the page
  561. addStyle(viewerCss, imagesCss, imageCss, counterCss, navCss, navHoverCss, statsCss, statsCollapseCss, statsHoverCss);
  562. // set up return UI object
  563. var UI = {
  564. images: getEl('.ml-images'),
  565. statsContent: getEl('.ml-stats-content'),
  566. statsCollapse: getEl('.ml-stats-collapse')
  567. };
  568. // set up listeners
  569. document.addEventListener('click', function(evt) {
  570. if (evt.target.nodeName === 'A' && !evt.target.dataset.ignore && evt.target.parentNode.className.indexOf('ml-chap-nav') !== -1) {
  571. log('next chapter will autoload');
  572. storeSet('autoload', 'yes');
  573. }
  574. }, false);
  575. UI.statsCollapse.addEventListener('click', function(evt) {
  576. var test = UI.statsCollapse.textContent === '>>';
  577. UI.statsContent.style.display = test ? 'none' : '';
  578. UI.statsCollapse.textContent = test ? '<<' : '>>';
  579. }, false);
  580. return UI;
  581. };
  582.  
  583. var getCounter = function(imgNum) {
  584. var counter = document.createElement('div');
  585. counter.classList.add('ml-counter');
  586. counter.textContent = imgNum;
  587. return counter;
  588. };
  589.  
  590. var addImage = function(src, loc, imgNum, callback) {
  591. var image = new Image(),
  592. counter = getCounter(imgNum);
  593. image.onerror = function() {
  594. log('failed to load ' + src);
  595. image.onload = null;
  596. image.style.backgroundColor = 'white';
  597. image.title = 'Reload?';
  598. image.src = '//rgeorgeeb-tilebuttongenerator.googlecode.com/hg-history/c35993682c2d50149976fd7a1f302f8c01a88716/asset-studio/src/res/clipart/icons/refresh.svg';
  599. image.onclick = function() {
  600. image.remove();
  601. counter.remove();
  602. addImage(src, loc, imgNum, callback);
  603. };
  604. };
  605. image.onload = callback;
  606. image.src = src;
  607. loc.appendChild(image);
  608. loc.appendChild(counter);
  609. };
  610.  
  611. var loadManga = function(imp) {
  612. var ex = extractInfo.bind(imp),
  613. imgUrl = ex('img'),
  614. nextUrl = ex('next'),
  615. numPages = ex('numpages'),
  616. curPage = ex('curpage', {
  617. type: 'index'
  618. }) || 1,
  619. nextChapter = ex('nextchap', {
  620. type: 'value',
  621. val: (imp.invchap && -1) || 1
  622. }),
  623. prevChapter = ex('prevchap', {
  624. type: 'value',
  625. val: (imp.invchap && 1) || -1
  626. }),
  627. xhr = new XMLHttpRequest(),
  628. d = document.implementation.createHTMLDocument(),
  629. addAndLoad = function(img, next) {
  630. updateStats();
  631. addImage(img, UI.images, curPage, function() {
  632. pagesLoaded += 1;
  633. updateStats();
  634. });
  635. loadNextPage(next);
  636. },
  637. updateStats = function() {
  638. UI.statsContent.textContent = ' ' + pagesLoaded + '/' + curPage + ' loaded' + (numPages ? ', ' + numPages + ' total' : '');
  639. },
  640. getPageInfo = function() {
  641. var page = d.body;
  642. d.body.innerHTML = xhr.response;
  643. try {
  644. // find image and link to next page
  645. addAndLoad(ex('img', imp.imgmod, page), ex('next', null, page));
  646. } catch (e) {
  647. log('error getting details from next page, assuming end of chapter.');
  648. }
  649. },
  650. loadNextPage = function(url) {
  651. if (mLoadLess && count % loadInterval === 0) {
  652. if (resumeUrl) {
  653. resumeUrl = null;
  654. } else {
  655. resumeUrl = url;
  656. log('waiting for user to scroll further before loading more images, loaded ' + count + ' pages so far, next url is ' + resumeUrl);
  657. return;
  658. }
  659. }
  660. if (curPage + 1 > numPages) {
  661. log('reached "numPages" ' + numPages + ', assuming end of chapter');
  662. return;
  663. }
  664. if (lastUrl === url) {
  665. log('last url (' + lastUrl + ') is the same as current (' + url + '), assuming end of chapter');
  666. return;
  667. }
  668. curPage += 1;
  669. count += 1;
  670. lastUrl = url;
  671. if (imp.pages) {
  672. imp.pages(url, curPage, addAndLoad, ex);
  673. } else {
  674. xhr.open('get', url);
  675. xhr.onload = getPageInfo;
  676. xhr.onerror = function() {
  677. log('failed to load page, aborting', 'error');
  678. };
  679. xhr.send();
  680. }
  681. },
  682. count = 1,
  683. pagesLoaded = curPage - 1,
  684. loadInterval = 10,
  685. lastUrl, UI, resumeUrl;
  686.  
  687. if (!imgUrl || !nextUrl) {
  688. log('failed to retrieve ' + (!imgUrl ? 'image url' : 'next page url'), 'exit');
  689. }
  690.  
  691. UI = getViewer(prevChapter, nextChapter);
  692.  
  693. UI.statsContent.textContent = ' 0/1 loaded, ' + numPages + ' total';
  694.  
  695. if (mLoadLess) {
  696. window.onscroll = throttle(function(e) {
  697. if (!resumeUrl) return; // exit early if we don't have a position to resume at
  698. var scrollBottom = document.body.scrollHeight - ((document.body.scrollTop || document.documentElement.scrollTop) + window.innerHeight);
  699. if (scrollBottom < 4500) {
  700. log('user scroll nearing end, loading more images starting from ' + resumeUrl);
  701. loadNextPage(resumeUrl);
  702. }
  703. }, 100);
  704. }
  705.  
  706. addAndLoad(imgUrl, nextUrl);
  707.  
  708. };
  709.  
  710. var pageUrl = window.location.href,
  711. btnLoadCss = toStyleStr({
  712. 'position': 'fixed',
  713. 'bottom': 0,
  714. 'right': 0,
  715. 'padding': '5px',
  716. 'margin': '0 10px 10px 0',
  717. 'z-index': '99999'
  718. }),
  719. btnLoad;
  720.  
  721. // used when switching chapters
  722. var autoload = storeGet('autoload');
  723. // manually set by user in menu
  724. var mAutoload = storeGet('mAutoload') || false;
  725. // should we load less pages at a time?
  726. var mLoadLess = storeGet('mLoadLess') === false ? false : true;
  727.  
  728. // clear autoload
  729. storeDel('autoload');
  730.  
  731. // register menu commands
  732. if (typeof GM_registerMenuCommand === 'function') {
  733. GM_registerMenuCommand('ML: ' + (mAutoload ? 'Disable' : 'Enable') + ' manga autoload', function() {
  734. storeSet('mAutoload', !mAutoload);
  735. window.location.reload();
  736. });
  737. GM_registerMenuCommand('ML: Load ' + (mLoadLess ? 'full chapter in one go' : '10 pages at a time'), function() {
  738. storeSet('mLoadLess', !mLoadLess);
  739. window.location.reload();
  740. });
  741. }
  742.  
  743. log('starting...');
  744.  
  745. var success = implementations.some(function(imp) {
  746. if (imp.match && (new RegExp(imp.match, 'i')).test(pageUrl)) {
  747. if (BM_MODE || mAutoload || autoload) {
  748. log('autoloading...');
  749. setTimeout(loadManga.bind(null, imp), imp.wait || 0);
  750. return true;
  751. }
  752. // append button to dom that will trigger the page load
  753. btnLoad = createButton('Load Manga', function(evt) {
  754. loadManga(imp);
  755. this.remove();
  756. }, btnLoadCss);
  757. document.body.appendChild(btnLoad);
  758. return true;
  759. }
  760. });
  761.  
  762. if (!success) {
  763. log('no implementation for ' + pageUrl, 'error');
  764. }