Manga Loader

Loads entire chapter into the page in a long strip format

Устаревшая версия за 05.06.2014. Перейдите к последней версии.

  1. // ==UserScript==
  2. // @name Manga Loader
  3. // @namespace http://www.fuzetsu.com/MangaLoader
  4. // @version 1.0.5
  5. // @description Loads entire chapter into the page in a long strip format
  6. // @copyright 2014+, fuzetsu
  7. // @match http://www.batoto.net/read/*
  8. // @match http://mangafox.me/manga/*/*/*
  9. // @match http://readms.com/r/*/*/*/*
  10. // @match http://g.e-hentai.org/s/*/*
  11. // @match http://exhentai.org/s/*/*
  12. // @match http://www.fakku.net/*/*/read*
  13. // @match http://www.mangareader.net/*/*
  14. // @match http://www.mangahere.co/manga/*/*
  15. // @match http://www.mangapanda.com/*/*
  16. // @match http://mangadeer.com/manga/*/*/*/*
  17. // @match http://mngacow.com/*/*
  18. // ==/UserScript==
  19.  
  20. // set to true for manga load without prompt
  21. var BM_MODE = false;
  22.  
  23. var scriptName = 'Manga Loader';
  24.  
  25. /**
  26. Sample Implementation:
  27. {
  28. match: "http://domain.com/.*" // the url to react to for manga loading
  29. , img: '#image' // css selector to get the page's manga image
  30. , next: '#next_page' // css selector to get the link to the next page
  31. , numpages: '#page_select' // css selector to get the number of pages. elements like (select, span, etc)
  32. , curpage: '#page_select' // css selector to get the current page. usually the same as numPages if it's a select element
  33. , nextchap: '#next_chap' // css selector to get the link to the next chapter
  34. , prevchap: '#prev_chap' // same as above except for previous
  35. , wait: 3000 // how many ms to wait before auto loading (to wait for elements to load)
  36.  
  37. Any of the CSS selectors can be functions instead that return the desired value.
  38. }
  39. */
  40.  
  41. var implementations = [
  42. { // Batoto
  43. match: "http://www.batoto.net/read/.*"
  44. , img: '#comic_page'
  45. , next: '#full_image + div > a'
  46. , numpages: '#page_select'
  47. , curpage: '#page_select'
  48. , nextchap: 'select[name=chapter_select]'
  49. , prevchap: 'select[name=chapter_select]'
  50. , invchap: true
  51. }
  52. , { // MangaPanda
  53. match: "http://www.mangapanda.com/.*/[0-9]*"
  54. , img: '#img'
  55. , next: '.next a'
  56. , numpages: '#pageMenu'
  57. , curpage: '#pageMenu'
  58. , nextchap: 'td.c5 + td a'
  59. , prevchap: 'table.c6 tr:last-child td:last-child a'
  60. }
  61. , { // MangaFox
  62. match: "http://mangafox.me/manga/.*/.*/.*"
  63. , img: '#image'
  64. , next: '.next_page'
  65. , numpages: 'select.m'
  66. , curpage: 'select.m'
  67. , nextchap: '#chnav p + p a'
  68. , prevchap: '#chnav a'
  69. }
  70. , { // MangaStream
  71. match: "http://readms.com/r/.*/.*/.*/.*"
  72. , img: '#manga-page'
  73. , next: '.next a'
  74. , numpages: function() {
  75. var lastPage = getEl('.subnav-wrapper .controls .btn-group:last-child ul li:last-child');
  76. return parseInt(lastPage.textContent.match(/[0-9]/g).join(''), 10);
  77. }
  78. }
  79. , { // MangaReader
  80. match: "http://www.mangareader.net/.*/.*"
  81. , img:'#img'
  82. , next: '.next a'
  83. , numpages: '#pageMenu'
  84. , curpage: '#pageMenu'
  85. , nextchap: 'td.c5 + td a'
  86. , prevchap: 'table.c6 tr:last-child td:last-child a'
  87. }
  88. , { // MangaCow
  89. match: "^http://mngacow\.com/.*/[0-9]*"
  90. , img: '.prw > a > img'
  91. , next: '.prw > a'
  92. , numpages: 'select.cbo_wpm_pag'
  93. , curpage: 'select.cbo_wpm_pag'
  94. , nextchap: function(prev) {
  95. var chapSel = getEl('select.cbo_wpm_chp');
  96. var nextChap = chapSel.options[chapSel.selectedIndex + (prev ? 1 : -1)];
  97. if(nextChap) {
  98. return 'http://mngacow.com/' + window.location.pathname.slice(1, window.location.pathname.slice(1).indexOf('/') + 2) + nextChap.value;
  99. }
  100. }
  101. , prevchap: function() {
  102. return this.nextchap(true);
  103. }
  104. }
  105. , { // MangaHere
  106. match: "^http://www.mangahere.co/manga/.*/.*"
  107. , img: '#viewer img'
  108. , next: '.next_page'
  109. , numpages: 'select.wid60'
  110. , curpage: 'select.wid60'
  111. , nextchap: '#top_chapter_list'
  112. , prevchap: '#top_chapter_list'
  113. , wait: 2000
  114. }
  115. , { // MangaDeer
  116. match: "^http://mangadeer\.com/manga/.*"
  117. , img: '.img-link > img'
  118. , next: '.page > span:last-child > a'
  119. , numpages: '#sel_page_1'
  120. , curpage: '#sel_page_1'
  121. , nextchap: function(prev) {
  122. var ddl = getEl('#sel_book_1');
  123. var index = ddl.selectedIndex + (prev ? -1 : 1);
  124. if(index >= ddl.options.length) return;
  125. var mangaName = window.location.href.slice(window.location.href.indexOf('manga/') + 6);
  126. mangaName = mangaName.slice(0, mangaName.indexOf('/'));
  127. return 'http://mangadeer.com/manga/' + mangaName + ddl.options[index].value + '/1';
  128. }
  129. , prevchap: function() {
  130. return this.nextchap(true);
  131. }
  132. }
  133. , { // GEH/EXH
  134. match: "http://(g.e-hentai|exhentai).org/s/.*/.*"
  135. , img: '.sni > a > img, #img'
  136. , next: '.sni > a, #i3 a'
  137. }
  138. , { // Fakku (doesn' work...)
  139. match: "^http://www.fakku.net/.*/.*/read#page=[0-9]*"
  140. , img: '.current-page'
  141. , next: '#image a'
  142. , numpages: '.drop'
  143. , curpage: '.drop'
  144. }
  145. ];
  146.  
  147. var log = function(msg, type) {
  148. type = type || 'log';
  149. if(type === 'exit') {
  150. throw scriptName + ' exit: ' + msg;
  151. } else {
  152. console[type](scriptName + ' ' + type + ': ', msg);
  153. }
  154. };
  155.  
  156. var getEl = function(q, c) {
  157. if(!q) return;
  158. return (c || document).querySelector(q);
  159. };
  160.  
  161. var storeGet = function(key) {
  162. if(typeof GM_getValue === "undefined") {
  163. return localStorage.getItem(key);
  164. }
  165. return GM_getValue(key);
  166. };
  167.  
  168. var storeSet = function(key, value) {
  169. if(typeof GM_setValue === "undefined") {
  170. return localStorage.setItem(key, value);
  171. }
  172. return GM_setValue(key, value);
  173. };
  174.  
  175. var storeDel = function(key) {
  176. if(typeof GM_deleteValue === "undefined") {
  177. return localStorage.removeItem(key);
  178. }
  179. return GM_deleteValue(key);
  180. };
  181.  
  182. var extractInfo = function(selector, mod, context) {
  183. selector = this[selector];
  184. if(typeof selector === 'function') {
  185. return selector.call(this);
  186. }
  187. var elem = getEl(selector, context)
  188. , option;
  189. mod = mod || {};
  190. if(elem) {
  191. switch (elem.nodeName.toLowerCase()) {
  192. case 'img':
  193. return elem.getAttribute('src');
  194. case 'a':
  195. return elem.getAttribute('href');
  196. case 'ul':
  197. return elem.children.length;
  198. case 'select':
  199. switch(mod.type) {
  200. case 'index':
  201. return elem.options.selectedIndex;
  202. case 'value':
  203. option = elem.options[elem.options.selectedIndex + (mod.val || 0)] || {};
  204. return option.value;
  205. default:
  206. return elem.options.length;
  207. }
  208. break;
  209. default:
  210. return elem.textContent;
  211. }
  212. }
  213. };
  214.  
  215. var toStyleStr = function(obj) {
  216. var stack = []
  217. , key;
  218. for(key in obj) {
  219. if(obj.hasOwnProperty(key)) {
  220. stack.push(key + ':' + obj[key]);
  221. }
  222. }
  223. return stack.join(';');
  224. };
  225.  
  226. var createButton = function(text, action, styleStr) {
  227. var button = document.createElement('button');
  228. button.textContent = text;
  229. button.onclick = action;
  230. button.setAttribute('style', styleStr || '');
  231. return button;
  232. };
  233.  
  234. var getViewer = function(prevChapter, nextChapter) {
  235. var viewerCss = toStyleStr({
  236. 'background-color': 'black'
  237. , 'text-align': 'center'
  238. , 'font': '.9em sans-serif'
  239. })
  240. , imagesCss = toStyleStr({
  241. 'margin': '5px 0'
  242. })
  243. , navCss = toStyleStr({
  244. 'text-decoration': 'none'
  245. , 'color': 'black'
  246. , 'background': 'linear-gradient(white, #ccc)'
  247. , 'padding': '3px 10px'
  248. , 'border': '1px solid #ccc'
  249. , 'border-radius': '5px'
  250. })
  251. ;
  252. // clear all styles and scripts
  253. document.head.innerHTML = '';
  254. // and navigation
  255. var nav = (prevChapter ? '<a href="' + prevChapter + '" style="' + navCss + '" class="ml-chap-nav">Prev Chapter</a> ' : '') +
  256. (storeGet('mAutoload') ? '' : '<a href="" style="' + navCss + '">Exit</a> ') +
  257. (nextChapter ? '<a href="' + nextChapter + '" style="' + navCss + '" class="ml-chap-nav">Next Chapter</a>' : '');
  258. document.body.innerHTML = nav + '<div id="images" style="' + imagesCss + '"></div>' + nav;
  259. // set the viewer css
  260. document.body.setAttribute('style', viewerCss);
  261. // set up listeners for chapter navigation
  262. document.addEventListener('click', function(evt) {
  263. if(evt.target.className.indexOf('ml-chap-nav') !== -1) {
  264. log('next chapter will autoload');
  265. storeSet('autoload', 'yes');
  266. }
  267. }, false);
  268. return getEl('#images');
  269. };
  270.  
  271. var imageCss = toStyleStr({
  272. 'max-width': '100%'
  273. , 'display': 'block'
  274. , 'margin': '3px auto'
  275. });
  276.  
  277. var addImage = function(src, loc, callback) {
  278. var image = new Image();
  279. image.onerror = function() {
  280. log('failed to load ' + src);
  281. image.remove();
  282. };
  283. image.onload = callback;
  284. image.src = src;
  285. image.setAttribute('style', imageCss);
  286. loc.appendChild(image);
  287. };
  288.  
  289. var loadManga = function(imp) {
  290. var ex = extractInfo.bind(imp)
  291. , url = ex('img')
  292. , nextUrl = ex('next')
  293. , numPages = ex('numpages')
  294. , curPage = ex('curpage', {type:'index'}) || 1
  295. , nextChapter = ex('nextchap', {type:'value', val: (imp.invchap && -1) || 1})
  296. , prevChapter = ex('prevchap', {type:'value', val: (imp.invchap && 1) || -1})
  297. , xhr = new XMLHttpRequest()
  298. , d = document.implementation.createHTMLDocument()
  299. , getPageInfo = function() {
  300. var page = d.body;
  301. d.body.innerHTML = xhr.response;
  302. try {
  303. // find image and append
  304. addImage(ex('img', null, page), loc);
  305. // find next link and load it
  306. loadNextPage(ex('next', null, page));
  307. } catch(e) {
  308. log('error getting details from next page, assuming end of chapter.');
  309. }
  310. }
  311. , loadNextPage = function(url) {
  312. if(++curPage > numPages) {
  313. log('reached "numPages" ' + numPages + ', assuming end of chapter');
  314. return;
  315. }
  316. if(lastUrl === url) {
  317. log('last url is the same as current, assuming end of chapter');
  318. return;
  319. }
  320. lastUrl = url;
  321. xhr.open('get', url);
  322. xhr.onload = getPageInfo;
  323. xhr.onerror = function() {
  324. log('failed to load page, aborting', 'error');
  325. };
  326. xhr.send();
  327. }
  328. , lastUrl, loc
  329. ;
  330.  
  331. if(!url || !nextUrl) {
  332. log('failed to retrieve ' + (!url ? 'image url' : 'next page url'), 'exit');
  333. }
  334.  
  335. loc = getViewer(prevChapter, nextChapter);
  336.  
  337. addImage(url, loc);
  338. loadNextPage(nextUrl);
  339.  
  340. };
  341.  
  342. var pageUrl = window.location.href
  343. , btnLoadCss = toStyleStr({
  344. 'position': 'fixed'
  345. , 'bottom': 0
  346. , 'right': 0
  347. , 'padding': '5px'
  348. , 'margin': '0 10px 10px 0'
  349. , 'z-index': '1000'
  350. })
  351. , btnLoad
  352. ;
  353.  
  354. // used when switching chapters
  355. var autoload = storeGet('autoload');
  356. // manually set by user in menu
  357. var mAutoload = storeGet('mAutoload') || false;
  358.  
  359. // register menu command
  360. typeof GM_registerMenuCommand === 'function' && GM_registerMenuCommand((mAutoload ? 'Disable' : 'Enable') + ' manga autoload', function() {
  361. storeSet('mAutoload', !mAutoload);
  362. window.location.reload();
  363. });
  364. // clear autoload
  365. storeDel('autoload');
  366.  
  367. log('starting...');
  368.  
  369. var success = implementations.some(function(imp) {
  370. if(imp.match && (new RegExp(imp.match, 'i')).test(pageUrl)) {
  371. if(BM_MODE || mAutoload || autoload) {
  372. setTimeout(loadManga.bind(null, imp),imp.wait || 0);
  373. return true;
  374. }
  375. // append button to dom that will trigger the page load
  376. btnLoad = createButton('Load Manga', function(evt) {
  377. loadManga(imp);
  378. this.remove();
  379. }, btnLoadCss);
  380. document.body.appendChild(btnLoad);
  381. return true;
  382. }
  383. });
  384.  
  385. if(!success) {
  386. log('no implementation for ' + pageUrl, 'error');
  387. }