manga comic downloader

Tải truyện tranh từ các trang chia sẻ ở Việt Nam. Nhấn Alt+Y để tải toàn bộ.

  1. // ==UserScript==
  2. // @name manga comic downloader
  3. // @namespace https://baivong.github.io
  4. // @description Tải truyện tranh từ các trang chia sẻ ở Việt Nam. Nhấn Alt+Y để tải toàn bộ.
  5. // @version 3.4.7
  6. // @icon https://raw.githubusercontent.com/lelinhtinh/Userscript/master/manga_comic_downloader/icon.png
  7. // @author Zzbaivong
  8. // @license MIT; https://baivong.mit-license.org/license.txt
  9. // @match http://*.truyentranh8.com/*
  10. // @match https://*.truyentranh8.com/*
  11. // @match http://*.truyentranh8.net/*
  12. // @match https://*.truyentranh8.net/*
  13. // @match http://*.truyentranh8.org/*
  14. // @match https://*.truyentranh8.org/*
  15. // @match http://*.truyentranh86.com/*
  16. // @match https://*.truyentranh86.com/*
  17. // @match http://*.truyentranh869.com/*
  18. // @match https://*.truyentranh869.com/*
  19. // @match https://mangaxy.com/*
  20. // @match https://*.truyentranh.net/*
  21. // @match https://*.hamtruyen.com/*
  22. // @match https://*.hamtruyenmoi.com/*
  23. // @match https://*.a3manga.com/*
  24. // @match https://*.a3mnga.com/*
  25. // @match http://truyentranhtuan.com/*
  26. // @match https://truyentranhlh.net/*
  27. // @match https://truyenhay24h.com/*
  28. // @match https://thichtruyentranh.com/*
  29. // @match https://lxhentai.com/*
  30. // @match https://hentaivn.net/*
  31. // @match https://hentaivn.moe/*
  32. // @match https://otakusan.net/*
  33. // @match https://*.ngonphong.com/*
  34. // @match https://*.nettruyen.com/*
  35. // @match http://*.nettruyen.com/*
  36. // @match https://*.nettruyentop.com/*
  37. // @match http://*.nettruyentop.com/*
  38. // @match http://*.nettruyenonline.com/*
  39. // @match https://*.nettruyenonline.com/*
  40. // @match http://*.nettruyenapp.com/*
  41. // @match https://*.nettruyenapp.com/*
  42. // @match http://*.nettruyenpro.com/*
  43. // @match https://*.nettruyenpro.com/*
  44. // @match http://*.nettruyengo.com/*
  45. // @match https://*.nettruyengo.com/*
  46. // @match https://*.nettruyenmax.com/*
  47. // @match http://*.nettruyenmoi.com/*
  48. // @match https://*.nettruyenbb.com/*
  49. // @match https://*.kingtruyen.com/*
  50. // @match http://nhattruyen.com/*
  51. // @match http://nhattruyengo.com/*
  52. // @match http://*.hamtruyentranh.net/*
  53. // @match https://ttmanga.com/*
  54. // @match http://truyen.vnsharing.site/*
  55. // @match http://*.blogtruyen.com/*
  56. // @match https://*.blogtruyen.com/*
  57. // @match https://*.blogtruyenmoi.com/*
  58. // @match http://*.blogtruyen.vn/*
  59. // @match https://*.blogtruyen.vn/*
  60. // @match http://*.blogtruyen.top/*
  61. // @match https://*.blogtruyen.top/*
  62. // @match https://truyensieuhay.com/*
  63. // @match http://truyenqqvn.com/*
  64. // @match http://truyenqqvn.net/*
  65. // @match http://truyenqqtop.com/*
  66. // @match http://truyenqqpro.com/*
  67. // @match https://hentaicube.net/*
  68. // @match https://hentaicb.top/*
  69. // @match http://*.tuthienbao.com/*
  70. // @match https://vietcomic.net/*
  71. // @match https://hamtruyentranh.com/*
  72. // @match https://khotruyentranhz.com/*
  73. // @match https://truyenvn.com/*
  74. // @match https://truyenvn.vip/*
  75. // @match https://truyenvnpro.com/*
  76. // @match https://*.saytruyen.net/*
  77. // @match https://*.saytruyen.com/*
  78. // @match https://*.sayhentai.net/*
  79. // @match https://cocomic.net/truyen-tranh/*
  80. // @require https://code.jquery.com/jquery-3.6.0.min.js
  81. // @require https://unpkg.com/@zip.js/zip.js@2.7.41/dist/zip.min.js
  82. // @require https://unpkg.com/file-saver@2.0.5/dist/FileSaver.min.js
  83. // @require https://cdn.jsdelivr.net/npm/web-streams-polyfill@2.0.2/dist/ponyfill.min.js
  84. // @require https://cdn.jsdelivr.net/npm/streamsaver@2.0.3/StreamSaver.min.js
  85. // @require https://greasemonkey.github.io/gm4-polyfill/gm4-polyfill.js?v=a834d46
  86. // @require https://cdnjs.cloudflare.com/ajax/libs/crypto-js/4.1.1/crypto-js.min.js
  87. // @require https://cdn.jsdelivr.net/gh/Joe12387/detectIncognito@53bb1cde47a849dd166443a24b92f15432d7c40a/dist/es5/detectIncognito.min.js
  88. // @resource success https://unpkg.com/facebook-sound-kit@2.0.0/Low_Volume_-20dB/Complete_and_Success/Success_2.m4a
  89. // @resource error https://unpkg.com/facebook-sound-kit@2.0.0/Low_Volume_-20dB/Errors_and_Cancel/Error_2.m4a
  90. // @noframes
  91. // @connect *
  92. // @supportURL https://github.com/lelinhtinh/Userscript/issues
  93. // @run-at document-start
  94. // @grant GM_addStyle
  95. // @grant GM_xmlhttpRequest
  96. // @grant GM.xmlHttpRequest
  97. // @grant GM.getResourceUrl
  98. // @grant GM_getResourceURL
  99. // @grant GM_registerMenuCommand
  100. // ==/UserScript==
  101.  
  102. /* global zip, streamSaver, CryptoJS, detectIncognito */
  103. window._URL = window.URL || window.webkitURL;
  104.  
  105. jQuery(function ($) {
  106. /**
  107. * Output extension
  108. * @type {String} zip
  109. * cbz
  110. *
  111. * Tips: Convert .zip to .cbz
  112. * Windows
  113. * $ ren *.zip *.cbz
  114. * Linux
  115. * $ rename 's/\.zip$/\.cbz/' *.zip
  116. */
  117. var outputExt = 'cbz'; // or 'zip'
  118.  
  119. /**
  120. * Multithreading
  121. * @type {Number} [1 -> 32]
  122. */
  123. var threading = 4;
  124.  
  125. /**
  126. * The number of times the download may be attempted.
  127. * @type {Number}
  128. */
  129. var tries = 5;
  130.  
  131. /**
  132. * Enable audio cues.
  133. * @type {Boolean}
  134. */
  135. var audioCues = true;
  136.  
  137. /**
  138. * Image list will be ignored
  139. * @type {Array} url
  140. */
  141. var ignoreList = [
  142. 'http://truyentranhtam.net/templates/main/images/gioithieubanbe3.png',
  143. 'http://1.bp.blogspot.com/-U1SdU4_52Xs/WvLvn1OjvHI/AAAAAAAEugM/dLBgVGSeUN0bVy-FoFfIZvrCJ07YQew7wCHMYCw/s0/haybaoloi.png',
  144. '/public/images/loading.gif',
  145. 'http://truyentranhlh.net/wp-content/uploads/2015/10/lhmanga.png',
  146. '/Content/Img/1eeef5d2-b936-496d-ba41-df1b21d0166a.jpg',
  147. '/Content/Img/d79886b3-3699-47b2-bbf4-af6149c2e8fb.jpg',
  148. 'http://st.beeng.net/files/uploads/images/21/c8/21c8d2c3599c485e31f270675bc57e4c.jpeg',
  149. '00k9jbV.gif',
  150. ];
  151.  
  152. /**
  153. * Keep the original url
  154. * @type {Array} key
  155. */
  156. var keepOriginal = [
  157. 'proxy.truyen.cloud',
  158. '.ttmanga.com',
  159. '.fbcdn.net',
  160. 'mangaqq.net',
  161. 'mangaqq.com',
  162. 'truyenqqvn.net',
  163. 'truyenqqvn.com',
  164. 'truyenqqtop.com',
  165. 'truyenqqpro.com',
  166. '.upanhmoi.net',
  167. 'qqtaku.com',
  168. 'qqtaku.net',
  169. 'trangshop.net',
  170. '.beeng.net',
  171. '.beeng.org',
  172. 'forumnt.com',
  173. 'hoitruyentranh.com',
  174. 'hoihentai.com',
  175. 'i02.hentaivn.net',
  176. 'truyentop1.com',
  177. 'anhnhanh.org',
  178. 'truyenvua.xyz',
  179. 'hamtruyen.vn',
  180. '.xem-truyen.com',
  181. ];
  182.  
  183. /**
  184. * HTTP referer
  185. * @param {Object} hostname
  186. */
  187. var referer = {
  188. 'i8.xem-truyen.com': 'https://blogtruyenmoi.com',
  189. 'i.blogtruyen.com': 'https://blogtruyen.com',
  190. 'truyen.cloud': 'http://www.nettruyen.com',
  191. 'proxy.truyen.cloud': 'http://www.nettruyen.com',
  192. 'i.netsnippet.com': 'http://www.nettruyen.com/',
  193. 'forumnt.com': 'http://www.nettruyen.com/',
  194. 'upload.forumnt.com': 'http://www.nettruyen.com/',
  195. 'upload2.forumnt.com': 'http://www.nettruyen.com/',
  196. 'upload.upanhmoi.net': 'https://upanhmoi.net',
  197. 'upload2.upanhmoi.net': 'https://upanhmoi.net',
  198. 'upload3.upanhmoi.net': 'https://upanhmoi.net',
  199. 'upload4.upanhmoi.com': 'https://upanhmoi.com',
  200. 'upload5.upanhmoi.com': 'https://upanhmoi.com',
  201. 'upload6.upanhmoi.com': 'https://upanhmoi.com',
  202. 'upload7.upanhmoi.com': 'https://upanhmoi.com',
  203. 'upload8.upanhmoi.com': 'https://upanhmoi.com',
  204. 'upload9.upanhmoi.com': 'https://upanhmoi.com',
  205. 'img1.upanhmoi.net': 'https://upanhmoi.net',
  206. 'img2.upanhmoi.net': 'https://upanhmoi.net',
  207. 'proxy1.ttmanga.com': 'https://ttmanga.com',
  208. 'proxy2.ttmanga.com': 'https://ttmanga.com',
  209. 'proxy3.ttmanga.com': 'https://ttmanga.com',
  210. 'cdn.lhmanga.com': 'https://truyentranhlh.net',
  211. 'cdn1.lhmanga.com': 'https://truyentranhlh.net',
  212. 'storage.fshare.vn': 'https://truyentranh.net',
  213. 'ocumeo.com': 'https://www.a3manga.com/',
  214. 'www.ocumeo.com': 'https://www.a3manga.com/',
  215. 'mangaqq.net': 'http://truyenqqpro.com/',
  216. 'mangaqq.com': 'http://truyenqqpro.com/',
  217. 'truyenqq.net': 'http://truyenqqpro.com/',
  218. 'truyenvua.xyz': 'http://truyenqqpro.com/',
  219. 'i02.hentaivn.net': 'https://hentaivn.net/',
  220. 'i1.hentaivn.net': 'https://hentaivn.net/',
  221. 'i.imgur.com': 'https://imgur.com/',
  222. };
  223.  
  224. /* === DO NOT CHANGE === */
  225.  
  226. window.URL = window._URL;
  227.  
  228. var successSound, errorSound;
  229. if (audioCues) {
  230. GM.getResourceUrl('success').then(function (url) {
  231. successSound = new Audio(url);
  232. });
  233. GM.getResourceUrl('error').then(function (url) {
  234. errorSound = new Audio(url);
  235. });
  236. }
  237.  
  238. function getImageType(arrayBuffer) {
  239. if (!arrayBuffer.byteLength)
  240. return {
  241. mime: null,
  242. ext: null,
  243. };
  244.  
  245. var ext = '',
  246. mime = '',
  247. dv = new DataView(arrayBuffer, 0, 5),
  248. numE1 = dv.getUint8(0, true),
  249. numE2 = dv.getUint8(1, true),
  250. hex = numE1.toString(16) + numE2.toString(16);
  251.  
  252. switch (hex) {
  253. case '8950':
  254. ext = 'png';
  255. mime = 'image/png';
  256. break;
  257. case '4749':
  258. ext = 'gif';
  259. mime = 'image/gif';
  260. break;
  261. case 'ffd8':
  262. ext = 'jpg';
  263. mime = 'image/jpeg';
  264. break;
  265. case '424d':
  266. ext = 'bmp';
  267. mime = 'image/bmp';
  268. break;
  269. case '5249':
  270. ext = 'webp';
  271. mime = 'image/webp';
  272. break;
  273. default:
  274. ext = null;
  275. mime = null;
  276. break;
  277. }
  278.  
  279. return {
  280. mime: mime,
  281. ext: ext,
  282. };
  283. }
  284.  
  285. var notyTimeout;
  286. function noty(txt, status) {
  287. function destroy() {
  288. var $noty = $doc.find('#mcd_noty_wrap');
  289. if (!$noty.length) return;
  290. $noty.fadeOut(300, function () {
  291. $noty.remove();
  292. $noty = [];
  293. });
  294. clearTimeout(notyTimeout);
  295. }
  296.  
  297. function autoHide() {
  298. notyTimeout = setTimeout(function () {
  299. destroy();
  300. }, 2000);
  301. }
  302.  
  303. var $noty = $doc.find('#mcd_noty_wrap');
  304. if (!$noty.length) {
  305. var $wrap = $('<div>', {
  306. id: 'mcd_noty_wrap',
  307. }),
  308. $content = $('<div>', {
  309. id: 'mcd_noty_content',
  310. class: 'mcd_' + status,
  311. html: txt,
  312. }),
  313. $close = $('<div>', {
  314. id: 'mcd_noty_close',
  315. html: '&times;',
  316. });
  317.  
  318. $noty = $wrap.append($content).append($close);
  319. $noty.appendTo('body');
  320. }
  321.  
  322. $noty
  323. .find('#mcd_noty_content')
  324. .attr('class', 'mcd_' + status)
  325. .html(txt);
  326.  
  327. $noty.fadeIn(300);
  328. clearTimeout(notyTimeout);
  329.  
  330. $doc
  331. .on('click', '#mcd_noty_wrap', function () {
  332. destroy();
  333. })
  334. .on('mouseenter', '#mcd_noty_wrap', function () {
  335. clearTimeout(notyTimeout);
  336. })
  337. .on('mouseleave', '#mcd_noty_wrap', autoHide);
  338.  
  339. if (status !== 'warning' && status !== 'success') autoHide();
  340. }
  341.  
  342. function targetLink(selector) {
  343. return configs.link
  344. .split(/\s*,\s*/)
  345. .map(function (i) {
  346. return i + selector;
  347. })
  348. .join(',');
  349. }
  350.  
  351. function linkError() {
  352. $(targetLink('[href="' + configs.href + '"]')).css({
  353. color: 'red',
  354. textShadow: '0 0 1px red, 0 0 1px red, 0 0 1px red',
  355. });
  356. hasDownloadError = true;
  357. }
  358.  
  359. function linkSuccess() {
  360. var $currLink = $(targetLink('[href="' + configs.href + '"]'));
  361. if (!hasDownloadError)
  362. $currLink.css({
  363. color: 'green',
  364. textShadow: '0 0 1px green, 0 0 1px green, 0 0 1px green',
  365. });
  366. }
  367.  
  368. function beforeleaving(e) {
  369. e.preventDefault();
  370. e.returnValue = '';
  371. }
  372.  
  373. function cancelProgress() {
  374. linkError();
  375. inProgress = false;
  376. window.removeEventListener('beforeunload', beforeleaving);
  377. setTimeout(() => {
  378. if (dlAll.length || inProgress || !errorSound) return;
  379. errorSound.play();
  380. }, 0);
  381. }
  382.  
  383. function notyError() {
  384. noty('Lỗi! Không tải được <strong>' + chapName + '</strong>', 'error');
  385. cancelProgress();
  386. }
  387.  
  388. function notyImages() {
  389. noty('Lỗi! <strong>' + chapName + '</strong> không có dữ liệu', 'error');
  390. cancelProgress();
  391. }
  392.  
  393. function notySuccess(source) {
  394. if (threading < 1) threading = 1;
  395. if (threading > 32) threading = 32;
  396.  
  397. dlImages = source.map(function (url) {
  398. return {
  399. url: url,
  400. attempt: tries,
  401. };
  402. });
  403. dlTotal = dlImages.length;
  404. addZip();
  405.  
  406. noty('Bắt đầu tải <strong>' + chapName + '</strong>', 'warning');
  407. window.addEventListener('beforeunload', beforeleaving);
  408. }
  409.  
  410. function notyWait() {
  411. document.title = '[…] ' + tit;
  412.  
  413. noty('<strong>' + chapName + '</strong> đang lấy dữ liệu...', 'warning');
  414.  
  415. dlAll = dlAll.filter(function (l) {
  416. return configs.href.indexOf(l) === -1;
  417. });
  418.  
  419. $(targetLink('[href="' + configs.href + '"]')).css({
  420. color: 'orange',
  421. fontWeight: 'bold',
  422. fontStyle: 'italic',
  423. textDecoration: 'underline',
  424. textShadow: '0 0 1px orange, 0 0 1px orange, 0 0 1px orange',
  425. });
  426. }
  427.  
  428. function dlAllGen() {
  429. dlAll = [];
  430. $(configs.link).each(function (i, el) {
  431. dlAll[i] = $(el).attr('href');
  432. });
  433. if (configs.reverse) dlAll.reverse();
  434. }
  435.  
  436. function notyReady() {
  437. noty('Script đã <strong>sẵn sàng</strong> làm việc', 'info');
  438.  
  439. dlAllGen();
  440.  
  441. $doc
  442. .on('click', configs.link, function (e) {
  443. if (!e.ctrlKey && !e.shiftKey) return;
  444.  
  445. e.preventDefault();
  446. var _link = $(this).attr('href');
  447.  
  448. if (e.ctrlKey && e.shiftKey) {
  449. dlAll = dlAll.filter(function (l) {
  450. return _link.indexOf(l) === -1;
  451. });
  452.  
  453. $(targetLink('[href="' + _link + '"]')).css({
  454. color: 'gray',
  455. fontWeight: 'bold',
  456. fontStyle: 'italic',
  457. textDecoration: 'line-through',
  458. textShadow: '0 0 1px gray, 0 0 1px gray, 0 0 1px gray',
  459. });
  460. } else {
  461. if (!inCustom) {
  462. dlAll = [];
  463. inCustom = true;
  464. }
  465.  
  466. dlAll.push(_link);
  467.  
  468. var color = e.shiftKey ? 'violet' : 'purple';
  469. $(targetLink('[href="' + _link + '"]')).css({
  470. color: color,
  471. textDecoration: 'overline',
  472. textShadow: '0 0 1px ' + color + ', 0 0 1px ' + color + ', 0 0 1px ' + color,
  473. });
  474. }
  475. })
  476. .on('keyup', function (e) {
  477. if (e.key === 'Control' || e.key === 'Shift') {
  478. e.preventDefault();
  479.  
  480. if (dlAll.length && inCustom) {
  481. if (e.key === 'Shift') inMerge = true;
  482. downloadAll();
  483. }
  484. }
  485. });
  486. }
  487.  
  488. function downloadAll() {
  489. if (inProgress || inAuto) return;
  490. if (!inCustom && !dlAll.length) dlAllGen();
  491. if (!dlAll.length) return;
  492. inAuto = true;
  493. $(targetLink('[href$="' + dlAll[0] + '"]')).trigger('contextmenu');
  494. }
  495.  
  496. function downloadAllOne() {
  497. inMerge = true;
  498. downloadAll();
  499. }
  500.  
  501. async function saveFile(blob, zipName) {
  502. noty('Tải hoàn tất <strong>' + zipName + '</strong>', 'info');
  503.  
  504. try {
  505. const { isPrivate } = await detectIncognito();
  506. if (isPrivate) throw 'isPrivate';
  507.  
  508. const fileStream = streamSaver.createWriteStream(zipName, {
  509. size: blob.size,
  510. });
  511. const readableStream = blob.stream();
  512.  
  513. if (window.WritableStream && readableStream.pipeTo) {
  514. return readableStream.pipeTo(fileStream);
  515. }
  516. window.writer = fileStream.getWriter();
  517.  
  518. const reader = readableStream.getReader();
  519. const pump = () =>
  520. reader.read().then((res) => (res.done ? window.writer.close() : window.writer.write(res.value).then(pump)));
  521. pump();
  522.  
  523. return;
  524. } catch (_err) {
  525. console.warn(_err);
  526. saveAs(blob, zipName);
  527. }
  528. }
  529.  
  530. function genFileName() {
  531. return chapName
  532. .replace(/\s+/g, '_')
  533. .replace(/・/g, '·')
  534. .replace(/(^_+|_+$)/, '');
  535. }
  536.  
  537. function endZip() {
  538. if (!inMerge) {
  539. zipFileStream = new TransformStream();
  540. zipFileBlobPromise = new Response(zipFileStream.readable).blob();
  541. zipWriter = new zip.ZipWriter(zipFileStream.writable, { zip64: true });
  542. downloadController = new AbortController();
  543. downloadSignal = downloadController.signal;
  544. zipData = [];
  545. }
  546.  
  547. dlCurrent = 0;
  548. dlFinal = 0;
  549. dlTotal = 0;
  550. dlImages = [];
  551.  
  552. hasDownloadError = false;
  553. inProgress = false;
  554.  
  555. if (inAuto) {
  556. if (dlAll.length) {
  557. $(targetLink('[href$="' + dlAll[0] + '"]')).trigger('contextmenu');
  558. } else {
  559. inAuto = false;
  560. inCustom = false;
  561. }
  562. }
  563.  
  564. setTimeout(() => {
  565. if (dlAll.length || inProgress || !successSound) return;
  566. successSound.play();
  567. }, 0);
  568. }
  569.  
  570. async function genZip() {
  571. noty('Tạo file nén của <strong>' + chapName + '</strong>', 'warning');
  572.  
  573. try {
  574. await Promise.all(zipData);
  575. await zipWriter.close();
  576. const zipFileBlob = await zipFileBlobPromise;
  577.  
  578. saveFile(zipFileBlob, genFileName() + '.' + outputExt);
  579. linkSuccess();
  580. window.removeEventListener('beforeunload', beforeleaving);
  581.  
  582. document.title = '[⇓] ' + tit;
  583. endZip();
  584. } catch (err) {
  585. console.error(err);
  586. if (downloadSignal.reason == err || (downloadSignal.reason && downloadSignal.reason.code == err.code)) {
  587. zip.terminateWorkers();
  588. }
  589. noty('Lỗi nén dữ liệu của <strong>' + chapName + '</strong>', 'error');
  590. cancelProgress();
  591.  
  592. document.title = '[x] ' + tit;
  593. endZip();
  594. }
  595. }
  596.  
  597. function dlImgError(current, success, error, err, filename) {
  598. if (dlImages[current].attempt <= 0) {
  599. dlFinal++;
  600. error(err, filename);
  601. return;
  602. }
  603.  
  604. setTimeout(function () {
  605. dlImg(current, success, error);
  606. dlImages[current].attempt--;
  607. }, 2000);
  608. }
  609.  
  610. function dlImg(current, success, error) {
  611. var url = dlImages[current].url,
  612. filename = ('0000' + dlCurrent).slice(-4),
  613. urlObj,
  614. urlHost,
  615. headers = {};
  616.  
  617. if (url.indexOf('//') === 0) url = location.protocol + url;
  618. urlObj = new URL(url);
  619. urlHost = urlObj.hostname;
  620.  
  621. if (referer[urlHost]) {
  622. headers.referer = referer[urlHost];
  623. headers.origin = referer[urlHost];
  624. } else {
  625. headers.referer = location.origin;
  626. headers.origin = location.origin;
  627. }
  628.  
  629. GM.xmlHttpRequest({
  630. method: 'GET',
  631. url: url,
  632. responseType: 'arraybuffer',
  633. headers: headers,
  634. onload: function (response) {
  635. var imgExt = getImageType(response.response).ext;
  636. if (imgExt === 'gif') return next();
  637.  
  638. if (
  639. !imgExt ||
  640. response.response.byteLength < 100 ||
  641. (response.statusText !== 'OK' && response.statusText !== '')
  642. ) {
  643. dlImgError(current, success, error, response, filename);
  644. } else {
  645. filename = filename + '.' + imgExt;
  646. success(response.response, filename);
  647. }
  648. },
  649. onerror: function (err) {
  650. dlImgError(current, success, error, err, filename);
  651. },
  652. });
  653. }
  654.  
  655. function next() {
  656. noty('Đang tải xuống <strong>' + dlFinal++ + '/' + dlTotal + '</strong>', 'warning');
  657. if (dlFinal < dlCurrent) return;
  658.  
  659. if (dlFinal < dlTotal) {
  660. addZip();
  661. } else {
  662. if (inMerge) {
  663. if (dlAll.length) {
  664. linkSuccess();
  665. endZip();
  666. } else {
  667. inMerge = false;
  668. genZip();
  669. }
  670. } else {
  671. genZip();
  672. }
  673. }
  674. }
  675.  
  676. function addZip() {
  677. var max = dlCurrent + threading,
  678. path = '';
  679.  
  680. if (max > dlTotal) max = dlTotal;
  681. if (inMerge) path = genFileName() + '/';
  682.  
  683. for (dlCurrent; dlCurrent < max; dlCurrent++) {
  684. dlImg(
  685. dlCurrent,
  686. function (buff, filename) {
  687. var fileData = zipWriter.add(path + filename, new Response(buff).body, {
  688. signal: downloadSignal,
  689. onend: next,
  690. });
  691. zipData.push(fileData);
  692. },
  693. function (err, filename) {
  694. zipData.push(
  695. zipWriter.add(path + filename + '_error.txt', new Blob([err.statusText + '\r\n' + err.finalUrl]).stream(), {
  696. signal: downloadSignal,
  697. onend: next,
  698. }),
  699. );
  700. noty(err.statusText, 'error');
  701. linkError();
  702. },
  703. );
  704. }
  705. }
  706.  
  707. function imageIgnore(url) {
  708. return ignoreList.some(function (v) {
  709. return url.indexOf(v) !== -1;
  710. });
  711. }
  712.  
  713. function protocolUrl(url) {
  714. if (url.indexOf('//') === 0) url = location.protocol + url;
  715. if (url.search(/https?:\/\//) !== 0) url = 'http://' + url;
  716. return url;
  717. }
  718.  
  719. function redirectSSL(url) {
  720. if (
  721. url.search(/(i\.imgur\.com|\.blogspot\.com|\.fbcdn\.net|storage\.fshare\.vn)/i) !== -1 &&
  722. url.indexOf('http://') === 0
  723. )
  724. url = url.replace(/^http:\/\//, 'https://');
  725.  
  726. return url;
  727. }
  728.  
  729. function decodeUrl(url) {
  730. var parser = new DOMParser(),
  731. dom = parser.parseFromString('<!doctype html><body>' + url, 'text/html');
  732.  
  733. return decodeURIComponent(dom.body.textContent);
  734. }
  735.  
  736. function imageFilter(url) {
  737. url = decodeUrl(url);
  738. url = url.trim();
  739. url = url.replace(/^.+(&|\?)url=/, '');
  740. url = url.replace(/(https?:\/\/)lh(\d)(\.bp\.blogspot\.com)/, '$1$2$3');
  741. url = url.replace(/(https?:\/\/)lh\d\.(googleusercontent|ggpht)\.com/, '$14.bp.blogspot.com');
  742. url = url.replace(/\?.+$/, '');
  743. if (url.indexOf('imgur.com') !== -1) {
  744. url = url.replace(/(\/)(\w{5}|\w{7})(s|b|t|m|l|h)(\.(jpe?g|png|webp))$/, '$1$2$4');
  745. } else if (url.indexOf('blogspot.com') !== -1) {
  746. url = url.replace(/\/([^/]+-)?(Ic42)(-[^/]+)?\//, '/$2/');
  747. url = url.replace(/\/(((s|w|h)\d+|(w|h)\d+-(w|h)\d+))?-?(c|d|g)?\/(?=[^/]+$)/, '/');
  748. url += '?imgmax=16383';
  749. } else {
  750. url = url.replace(/(\?|&).+/, '');
  751. }
  752. url = encodeURI(url);
  753. url = protocolUrl(url);
  754. url = redirectSSL(url);
  755.  
  756. return url;
  757. }
  758.  
  759. function checkImages(images) {
  760. var source = [];
  761.  
  762. if (!images.length) {
  763. notyImages();
  764. } else {
  765. $.each(images, function (i, v) {
  766. v = v.replace(/^[\s\n]+|[\s\n]+$/g, '');
  767.  
  768. var keep = keepOriginal.some(function (key) {
  769. return v.indexOf(key) !== -1;
  770. });
  771. if (keep) {
  772. source.push(v);
  773. return;
  774. }
  775.  
  776. if (imageIgnore(v) || typeof v === 'undefined') return;
  777. if (/[><"']/.test(v)) return;
  778.  
  779. if (
  780. (v.indexOf(location.origin) === 0 || (v.indexOf('/') === 0 && v.indexOf('//') !== 0)) &&
  781. !/^(\.(jpg|png)|webp|jpeg)$/.test(v.slice(-4))
  782. ) {
  783. return;
  784. } else if (v.indexOf('http') !== 0 && v.indexOf('//') !== 0) {
  785. v = location.origin + (v.indexOf('/') === 0 ? '' : '/') + v;
  786. } else if (v.indexOf('http') === 0 || v.indexOf('//') === 0) {
  787. v = imageFilter(v);
  788. } else {
  789. return;
  790. }
  791.  
  792. source.push(v);
  793. });
  794.  
  795. notySuccess(source);
  796. }
  797. }
  798.  
  799. function getImages($contents) {
  800. var images = [];
  801. $contents.each(function (i, v) {
  802. var $img = $(v);
  803. images[i] =
  804. (!configs.imgSrc ? $img.data('src') || $img.data('original') : $img.attr(configs.imgSrc)) || $img.attr('src');
  805. });
  806.  
  807. checkImages(images);
  808. }
  809.  
  810. function getContents($source) {
  811. var method = 'find';
  812. if (configs.filter) method = 'filter';
  813.  
  814. var $entry = $source[method](configs.contents).find('img');
  815. if (!$entry.length) {
  816. notyImages();
  817. } else {
  818. getImages($entry);
  819. }
  820. }
  821.  
  822. function cleanSource(response) {
  823. var responseText = response.responseText;
  824. if (!configs.imgSrc) {
  825. responseText = responseText.replace(/[\s\n]+src[\s\n]*=[\s\n]*/gi, ' data-src=');
  826. }
  827. responseText = responseText.replace(/^[^<]*/, '');
  828.  
  829. if (configs.imgSrc) return $(responseText);
  830. return $(responseText);
  831. }
  832.  
  833. function rightClickEvent(_this, callback) {
  834. var $this = $(_this),
  835. name = configs.name;
  836.  
  837. configs.href = $this.attr('href');
  838. chapName = $this.text().trim();
  839.  
  840. if (typeof name === 'function') {
  841. chapName = name(_this, chapName);
  842. } else if (typeof name === 'string') {
  843. chapName = $(name).text().trim() + ' ' + chapName;
  844. }
  845.  
  846. notyWait();
  847.  
  848. GM.xmlHttpRequest({
  849. method: 'GET',
  850. url: configs.href,
  851. onload: function (response) {
  852. var $data = cleanSource(response);
  853. if (typeof callback === 'function') {
  854. callback($data);
  855. } else {
  856. getContents($data);
  857. }
  858. },
  859. onerror: function () {
  860. notyError();
  861. },
  862. });
  863. }
  864.  
  865. function oneProgress() {
  866. if (inProgress) {
  867. noty('Chỉ được phép <strong>tải một truyện</strong> mỗi lần', 'error');
  868. return false;
  869. }
  870. inProgress = true;
  871. return true;
  872. }
  873.  
  874. function getSource(callback) {
  875. var $link = $(configs.link);
  876. if (!$link.length) return;
  877.  
  878. $doc.on('contextmenu', configs.link, function (e) {
  879. e.preventDefault();
  880. hasDownloadError = false;
  881. if (!oneProgress()) return;
  882.  
  883. rightClickEvent(e.currentTarget, callback);
  884. });
  885.  
  886. notyReady();
  887. }
  888.  
  889. function getA3Manga() {
  890. getSource(function ($data) {
  891. var chapterCode = $data.find('script:contains("htmlContent")');
  892. var chapterCrypto = $data.filter('script:contains("htmlContent")');
  893. if (!chapterCode.length || !chapterCrypto.length) {
  894. notyImages();
  895. } else {
  896. chapterCrypto = chapterCrypto.text().match(/(CryptoJSAesDecrypt\(.+?(?:(;}|htmlContent\);)))/g);
  897.  
  898. var chapterDecrypt = new Function(
  899. chapterCode.text() + 'function ' + chapterCrypto[0] + 'return ' + chapterCrypto[1],
  900. );
  901. this.CryptoJS = CryptoJS;
  902. chapterDecrypt.apply(this);
  903.  
  904. var chapterHTML = chapterDecrypt();
  905. var images = chapterHTML.match(/(?<=(data-(lqz53ud|3dn5rc9)="))(.+?)(?=")/g);
  906. if (!images) {
  907. notyImages();
  908. return;
  909. }
  910.  
  911. images = images.map((imgLink) => {
  912. imgLink = imgLink.replace(/LqZ53ud|3Dn5rc9/g, '.');
  913. imgLink = imgLink.replace(/pPdp7FG|gNa8fuX/g, ':');
  914. imgLink = imgLink.replace(/9pyrBcb|hT3k3S6/g, '/');
  915. return imgLink;
  916. });
  917. checkImages(images);
  918. }
  919. });
  920. }
  921.  
  922. function getTruyenTranhTuan() {
  923. getSource(function ($data) {
  924. var $entry = $data.find('#read-title').next('script');
  925. if (!$entry.length) {
  926. notyImages();
  927. } else {
  928. $data = $entry.text().match(/var\s+slides_page_path\s*=\s*(.+?);/)[1];
  929. $data = JSON.parse($data);
  930.  
  931. var type_server = 1;
  932. var slides_page;
  933. var slides_page_path = [];
  934. var use_server_gg = true;
  935. var slides_page_url_path = $data;
  936.  
  937. if (slides_page_url_path.length > 0) {
  938. type_server = 2;
  939. } else {
  940. type_server = 1;
  941. }
  942.  
  943. if (!use_server_gg) {
  944. type_server = 1;
  945. }
  946. if (type_server != 1) {
  947. slides_page = slides_page_url_path;
  948. } else {
  949. slides_page = slides_page_path;
  950. var i,
  951. j,
  952. length_chapter = slides_page.length - 1;
  953. for (i = 0; i < length_chapter; i++)
  954. for (j = i + 1; j < slides_page.length; j++)
  955. if (slides_page[j] < slides_page[i]) {
  956. var temp = slides_page[j];
  957. slides_page[j] = slides_page[i];
  958. slides_page[i] = temp;
  959. }
  960. }
  961.  
  962. checkImages(slides_page);
  963. }
  964. });
  965. }
  966.  
  967. function getTruyenHay24h() {
  968. getSource(function ($data) {
  969. $data = $data.find('#dvContentChap').siblings('script').text();
  970. $data = $data.match(/GI2017\(([^;]+);/)[1];
  971. $data = $data.split(/[,']+/);
  972.  
  973. $.post('/TH24Service.asmx/GetChapterImages', {
  974. PID: $data[0],
  975. ChapNumber: $data[1],
  976. cc18: $data[2],
  977. name: '',
  978. s: 0,
  979. })
  980. .done(function (response) {
  981. var images = [];
  982. $(response)
  983. .find('string')
  984. .each(function (i, v) {
  985. images[i] = v.textContent.replace(/\.(jpe?g|png)\w*$/, '.$1');
  986. });
  987.  
  988. checkImages(images);
  989. })
  990. .fail(function () {
  991. notyError();
  992. });
  993. });
  994. }
  995.  
  996. function getThichTruyenTranh() {
  997. getSource(function ($data) {
  998. $data = $data.find('#content_read').next('script').text();
  999. $data = $data.match(/https?:\/\/[^"]+/g);
  1000. if (!$data.length) {
  1001. notyImages();
  1002. } else {
  1003. checkImages($data);
  1004. }
  1005. });
  1006. }
  1007.  
  1008. function getHentaiVN() {
  1009. var chapterList = document.querySelector('#inner-listshowchapter');
  1010. if (chapterList === null) return;
  1011.  
  1012. noty('wait a moment...', 'warning');
  1013. setTimeout(() => {
  1014. chapterList.scrollIntoView();
  1015. }, 0);
  1016.  
  1017. var observer = new MutationObserver(function (mutations_list) {
  1018. mutations_list.forEach(function (mutation) {
  1019. if (mutation.type !== 'childList') return;
  1020. if (!mutation.target.querySelector(configs.link)) return;
  1021.  
  1022. getSource();
  1023. observer.disconnect();
  1024. });
  1025. });
  1026.  
  1027. observer.observe(chapterList, {
  1028. attributes: false,
  1029. childList: true,
  1030. subtree: false,
  1031. });
  1032. }
  1033.  
  1034. function getOtakuSan() {
  1035. var $link = $(configs.link);
  1036. if (!$link.length) return;
  1037.  
  1038. $link.on('contextmenu', function (e) {
  1039. e.preventDefault();
  1040. hasDownloadError = false;
  1041. if (!oneProgress()) return;
  1042.  
  1043. var $this = $(this);
  1044.  
  1045. configs.href = $this.attr('href');
  1046. chapName = $('h1.title').first().attr('title') + ' ' + $this.text().trim();
  1047. notyWait();
  1048.  
  1049. GM.xmlHttpRequest({
  1050. method: 'GET',
  1051. url: configs.href,
  1052. withCredentials: true,
  1053. headers: {
  1054. host: 'otakusan.net',
  1055. },
  1056. onload: function () {
  1057. GM.xmlHttpRequest({
  1058. method: 'POST',
  1059. url: '/Manga/UpdateView',
  1060. responseType: 'json',
  1061. withCredentials: true,
  1062. headers: {
  1063. host: 'otakusan.net',
  1064. 'content-length': 14,
  1065. 'content-type': 'application/x-www-form-urlencoded; charset=UTF-8',
  1066. },
  1067. data: 'chapId=' + configs.href.match(/\/(\d+)\/.+$/)[1],
  1068. onload: function (response) {
  1069. var res = response.response;
  1070. if (!res.chapid) {
  1071. notyImages();
  1072. } else {
  1073. notySuccess(JSON.parse(res.view));
  1074. }
  1075. },
  1076. });
  1077. },
  1078. });
  1079. });
  1080.  
  1081. notyReady();
  1082. }
  1083.  
  1084. function getTtManga() {
  1085. getSource(function ($data) {
  1086. var data = $data.find('#divImage').siblings('script').first().text();
  1087. if (!/lstImages\.push\("([^"]+)"\)/.test(data)) {
  1088. notyImages();
  1089. } else {
  1090. var regex = /lstImages\.push\("([^"]+)"\)/gi,
  1091. matches,
  1092. output = [];
  1093.  
  1094. // eslint-disable-next-line no-cond-assign
  1095. while ((matches = regex.exec(data))) {
  1096. output.push(decodeURIComponent(matches[1]));
  1097. }
  1098. checkImages(output);
  1099. }
  1100. });
  1101. }
  1102.  
  1103. function getTruyenSieuHay() {
  1104. function decrypt(des, id) {
  1105. id = id.substring(2, id.length - 3);
  1106. var passphrase = CryptoJS.enc.Utf8.parse(id.toLowerCase());
  1107. var iv = CryptoJS.enc.Utf8.parse('gqLOHUioQ0QjhuvI');
  1108. var decrypted = CryptoJS.AES.decrypt(des, passphrase, {
  1109. iv: iv,
  1110. mode: CryptoJS.mode.CBC,
  1111. });
  1112. var result = decrypted.toString(CryptoJS.enc.Utf8);
  1113. return result;
  1114. }
  1115.  
  1116. getSource(function ($data) {
  1117. if ($data.find('#wrap_alertvip').length) {
  1118. notyImages();
  1119. return;
  1120. }
  1121.  
  1122. var sID = $data.find('.content-chap-image').find('script:not([type]):first').text();
  1123. sID = /\bgetContentchap\('(.+?)','(.+?)'\)/.exec(sID);
  1124. if (!sID) {
  1125. notyImages();
  1126. return;
  1127. }
  1128.  
  1129. $.ajax({
  1130. type: 'POST',
  1131. url: '/Service.asmx/getContentChap',
  1132. data: '{ sID: "' + sID[1] + '",chuc:"' + sID[2] + '" }',
  1133. contentType: 'application/json; charset=utf-8',
  1134. dataType: 'json',
  1135. success: function (data) {
  1136. if (data.Message) {
  1137. notyImages();
  1138. return;
  1139. }
  1140.  
  1141. data = JSON.parse(data.d);
  1142. var $contents = decrypt(data.des, data.id);
  1143. $contents = $($contents).filter('img');
  1144. getImages($contents);
  1145. },
  1146. error: function () {
  1147. notyImages();
  1148. },
  1149. });
  1150. });
  1151. }
  1152.  
  1153. function getVietComic() {
  1154. getSource(function ($data) {
  1155. var data = $data.filter('script:not([src]):contains("Loadimage(i)")');
  1156. if (!data.length) {
  1157. notyImages();
  1158. return;
  1159. }
  1160.  
  1161. data = data.text().match(/data\s=\s'(.+?)';/);
  1162. if (!data) {
  1163. notyImages();
  1164. return;
  1165. }
  1166.  
  1167. data = data[1];
  1168. data = data.split('|');
  1169. checkImages(data);
  1170. });
  1171. }
  1172.  
  1173. var configsDefault = {
  1174. reverse: true,
  1175. link: '',
  1176. name: '',
  1177. contents: '',
  1178. imgSrc: '',
  1179. filter: false,
  1180. init: getSource,
  1181. },
  1182. configs,
  1183. chapName,
  1184. domainName = location.host,
  1185. tit = document.title,
  1186. $doc = $(document),
  1187. // zipMime = outputExt === 'cbz' ? 'application/vnd.comicbook+zip' : 'application/zip',
  1188. zipFileStream = new TransformStream(),
  1189. zipFileBlobPromise = new Response(zipFileStream.readable).blob(),
  1190. zipWriter = new zip.ZipWriter(zipFileStream.writable, { zip64: true }),
  1191. downloadController = new AbortController(),
  1192. downloadSignal = downloadController.signal,
  1193. zipData = [],
  1194. dlCurrent = 0,
  1195. dlFinal = 0,
  1196. dlTotal = 0,
  1197. dlImages = [],
  1198. dlAll = [],
  1199. hasDownloadError = false,
  1200. inProgress = false,
  1201. inAuto = false,
  1202. inCustom = false,
  1203. inMerge = false;
  1204.  
  1205. streamSaver.mitm = 'https://lelinhtinh.github.io/stream/mitm.html';
  1206.  
  1207. GM_registerMenuCommand('Download All Chapters', downloadAll);
  1208. GM_registerMenuCommand('Download All To One File', downloadAllOne);
  1209.  
  1210. $doc.on('keydown', function (e) {
  1211. if (e.code === 'KeyY' && e.altKey) {
  1212. // Alt+Y
  1213. e.preventDefault();
  1214. e.shiftKey ? downloadAllOne() : downloadAll();
  1215. }
  1216. });
  1217.  
  1218. GM_addStyle(
  1219. '#mcd_noty_wrap{background:#fff;position:fixed;z-index:2147483647;right:20px;top:20px;min-width:150px;max-width:100%;padding:15px 25px;border:1px solid #ddd;border-radius:2px;box-shadow:0 0 0 1px rgba(0,0,0,.1),0 1px 10px rgba(0,0,0,.35);cursor:pointer}#mcd_noty_content{color:#444}#mcd_noty_content strong{font-weight:700}#mcd_noty_content.mcd_info strong{color:#2196f3}#mcd_noty_content.mcd_success strong{color:#4caf50}#mcd_noty_content.mcd_warning strong{color:#ffc107}#mcd_noty_content.mcd_error strong{color:#f44336}#mcd_noty_content strong.centered{display:block;text-align:center}#mcd_noty_close{position:absolute;right:0;top:0;font-size:18px;color:#ddd;height:20px;width:20px;line-height:20px;text-align:center}#mcd_noty_wrap:hover #mcd_noty_close{color:#333}',
  1220. );
  1221.  
  1222. switch (domainName) {
  1223. case 'truyentranh8.com':
  1224. case 'truyentranh8.net':
  1225. case 'truyentranh8.org':
  1226. case 'truyentranh869.com':
  1227. case 'truyentranh86.com':
  1228. configs = {
  1229. link: '#ChapList a',
  1230. name: function (_this) {
  1231. return $('.breadcrumb li:last').text().trim() + ' ' + $(_this).find('span, strong, h2').text().trim();
  1232. },
  1233. contents: '#reading-detail',
  1234. };
  1235. break;
  1236. case 'mangaxy.com':
  1237. configs = {
  1238. link: '#ChapList a',
  1239. name: function (_this) {
  1240. var title = $('h1.comics-title').text();
  1241. var chapTitle = $(_this).find('div.episode-title').text();
  1242. return `${title} ${chapTitle}`;
  1243. },
  1244. contents: '#reading-detail',
  1245. };
  1246. break;
  1247. case 'truyentranh.net':
  1248. case 'www.truyentranh.net':
  1249. configs = {
  1250. reverse: false,
  1251. link: '.chapter-list-item-box a',
  1252. name: function (_this) {
  1253. return $('h1').text().trim() + ' ' + $(_this).text().trim();
  1254. },
  1255. contents: '.manga-reading-box',
  1256. };
  1257. break;
  1258. case 'hamtruyen.com':
  1259. case 'www.hamtruyen.com':
  1260. case 'hamtruyenmoi.com':
  1261. configs = {
  1262. link: '.list-chaps a',
  1263. name: 'h3.story-name',
  1264. contents: '.list-images',
  1265. };
  1266. break;
  1267. case 'm.hamtruyen.com':
  1268. configs = {
  1269. link: '.list-chap a',
  1270. name: '.tentruyen',
  1271. contents: '#content_chap',
  1272. };
  1273. break;
  1274. case 'a3manga.com':
  1275. case 'www.a3manga.com':
  1276. case 'a3mnga.com':
  1277. case 'www.a3mnga.com':
  1278. case 'www.ngonphong.com':
  1279. configs = {
  1280. link: '.chapter-table a.text-capitalize',
  1281. init: getA3Manga,
  1282. };
  1283. break;
  1284. case 'truyentranhtuan.com':
  1285. configs = {
  1286. link: '.chapter-name a',
  1287. init: getTruyenTranhTuan,
  1288. };
  1289. break;
  1290. case 'truyentranhlh.net':
  1291. configs = {
  1292. link: '.list-chapters a',
  1293. contents: '#chapter-content',
  1294. name: function (_this) {
  1295. return $('.series-name').text().trim() + ' ' + $(_this).find('.chapter-name').text().trim();
  1296. },
  1297. };
  1298. break;
  1299. case 'truyenhay24h.com':
  1300. configs = {
  1301. link: '.nano .chapname a',
  1302. name: '.name_sp',
  1303. init: getTruyenHay24h,
  1304. };
  1305. break;
  1306. case 'thichtruyentranh.com':
  1307. configs = {
  1308. reverse: false,
  1309. link: '#listChapterBlock .ul_listchap a',
  1310. init: getThichTruyenTranh,
  1311. };
  1312. break;
  1313. case 'lxhentai.com':
  1314. configs = {
  1315. link: '[style="max-height: 500px"] a',
  1316. contents: '[class="text-center"]',
  1317. name: function (_this) {
  1318. return (
  1319. $('.grow.text-lg.ml-1.text-ellipsis.font-semibold:first').text().trim() +
  1320. ' ' +
  1321. $(_this).find('span.text-ellipsis').text().trim()
  1322. );
  1323. },
  1324. };
  1325. break;
  1326. case 'hentaivn.net':
  1327. case 'hentaivn.moe':
  1328. configs = [
  1329. {
  1330. link: '.listing a',
  1331. name: function (_this) {
  1332. var title = $('h1[itemprop="name"]').find('a').text();
  1333. title = title.split('-')[0].trim();
  1334. var chapTitle = $(_this).find('.chuong_t').attr('title');
  1335. chapTitle = chapTitle.split('-')[1].trim();
  1336. return `${title} ${chapTitle}`;
  1337. },
  1338. contents: '#image',
  1339. init: getHentaiVN,
  1340. },
  1341. {
  1342. link: '.episodes a',
  1343. name: '[itemprop="name"] b',
  1344. contents: '#image',
  1345. },
  1346. ];
  1347. break;
  1348. case 'otakusan.net':
  1349. configs = {
  1350. link: '.read-chapter a',
  1351. name: 'h1.header',
  1352. init: getOtakuSan,
  1353. };
  1354. break;
  1355. case 'www.nettruyen.com':
  1356. case 'nhattruyen.com':
  1357. case 'nhattruyengo.com':
  1358. case 'www.nettruyenapp.com':
  1359. case 'www.nettruyenpro.com':
  1360. case 'www.nettruyengo.com':
  1361. case 'www.nettruyenmoi.com':
  1362. case 'www.nettruyenmax.com':
  1363. configs = {
  1364. link: '#nt_listchapter .chapter a',
  1365. name: '.title-detail',
  1366. contents: '.reading-detail.box_doc',
  1367. imgSrc: 'data-original',
  1368. };
  1369. break;
  1370. case 'www.nettruyentop.com':
  1371. case 'www.nettruyenonline.com':
  1372. case 'www.nettruyenbb.com':
  1373. case 'kingtruyen.com':
  1374. configs = {
  1375. link: '#nt_listchapter .chapter a',
  1376. name: '.title-detail',
  1377. contents: '.readimg,.reading-detail.box_doc',
  1378. };
  1379. break;
  1380.  
  1381. case 'www.hamtruyentranh.net':
  1382. configs = {
  1383. link: '#examples a',
  1384. name: function (_this) {
  1385. var $clone = $(_this).clone();
  1386. $clone.find('span').remove();
  1387. return $('.title-manga').text().trim() + ' ' + $clone.text().trim();
  1388. },
  1389. contents: '.each-page',
  1390. };
  1391. break;
  1392. case 'ttmanga.com':
  1393. configs = {
  1394. link: '#list-chapter a',
  1395. init: getTtManga,
  1396. };
  1397. break;
  1398. case 'truyen.vnsharing.site':
  1399. configs = {
  1400. link: '#manga-chaplist a',
  1401. contents: '.read_content',
  1402. };
  1403. break;
  1404. case 'blogtruyen.com':
  1405. case 'blogtruyen.vn':
  1406. case 'blogtruyen.top':
  1407. case 'blogtruyenmoi.com':
  1408. case 'www.blogtruyen.com':
  1409. case 'www.blogtruyen.vn':
  1410. case 'www.blogtruyen.top':
  1411. configs = {
  1412. link: '#list-chapters .title a',
  1413. contents: '#content',
  1414. };
  1415. break;
  1416. case 'm.blogtruyen.com':
  1417. case 'm.blogtruyen.vn':
  1418. case 'm.blogtruyen.top':
  1419. case 'm.blogtruyenmoi.com':
  1420. configs = {
  1421. link: '.list-chapter a',
  1422. name: function (_this) {
  1423. return $('h1.title').text().trim() + ' ' + $(_this).find('span').text().trim();
  1424. },
  1425. contents: '.content',
  1426. };
  1427. break;
  1428. case 'truyensieuhay.com':
  1429. configs = {
  1430. link: '#chapter-list-flag a',
  1431. name: 'h1',
  1432. init: getTruyenSieuHay,
  1433. };
  1434. break;
  1435. case 'truyenqq.com':
  1436. case 'truyenqq.net':
  1437. case 'truyenqqtop.com':
  1438. case 'truyenqqpro.com':
  1439. configs = {
  1440. link: '.works-chapter-list a',
  1441. name: 'h1',
  1442. contents: '.chapter_content',
  1443. };
  1444. break;
  1445. case 'hentaicube.net':
  1446. case 'hentaicb.top':
  1447. configs = {
  1448. link: '.wp-manga-chapter a',
  1449. name: '.post-title',
  1450. contents: '.reading-content',
  1451. imgSrc: 'data-src',
  1452. };
  1453. break;
  1454. case 'tuthienbao.com':
  1455. case 'www.tuthienbao.com':
  1456. configs = {
  1457. link: 'a[id^="thread_title_"]',
  1458. contents: '.quotecontent',
  1459. };
  1460. break;
  1461. case 'vietcomic.net':
  1462. configs = {
  1463. link: '.chapter-list a:not([rel="nofollow"])',
  1464. name: function (_this, chapName) {
  1465. return $('.manga-info-text h1').text().trim() + ' ' + chapName;
  1466. },
  1467. init: getVietComic,
  1468. };
  1469. break;
  1470. case 'hamtruyentranh.com':
  1471. configs = {
  1472. reverse: false,
  1473. link: '#chaps .chapter-title a:not([target])',
  1474. name: 'h1',
  1475. contents: '.table-responsive',
  1476. imgSrc: 'id-source',
  1477. };
  1478. break;
  1479. case 'khotruyentranhz.com':
  1480. configs = {
  1481. link: '#chapters-list-content .chapter-list a',
  1482. name: 'h1',
  1483. contents: '.box-chapter-content',
  1484. };
  1485. break;
  1486. case 'truyenvn.com':
  1487. case 'truyenvn.vip':
  1488. case 'truyenvnpro.com':
  1489. configs = {
  1490. link: '#chapterList a',
  1491. name: function (_this) {
  1492. return $('h1.name').text().trim() + ' ' + $(_this).find('span:first').text().trim();
  1493. },
  1494. contents: '.content-text',
  1495. };
  1496. break;
  1497. case 'saytruyen.net':
  1498. case 'www.saytruyen.net':
  1499. case 'saytruyen.com':
  1500. case 'www.saytruyen.com':
  1501. case 'sayhentai.net':
  1502. case 'www.sayhentai.net':
  1503. case 'sayhentai.tv':
  1504. configs = {
  1505. link: '.main-col .wp-manga-chapter a',
  1506. name: '.post-title h1',
  1507. contents: '#chapter_content',
  1508. };
  1509. break;
  1510. case 'cocomic.net':
  1511. configs = {
  1512. link: '.chapter-list .citem a',
  1513. name: 'h1',
  1514. contents: '.chapter-content',
  1515. };
  1516. break;
  1517. default:
  1518. configs = {};
  1519. break;
  1520. }
  1521.  
  1522. if (Array.isArray(configs)) {
  1523. var isMobile = /mobi|android|touch|mini/i.test(navigator.userAgent.toLowerCase());
  1524. configs = configs[isMobile ? 1 : 0];
  1525. }
  1526. if (!configs) return;
  1527.  
  1528. configs = $.extend(configsDefault, configs);
  1529. configs.init();
  1530. });