MeTruyenChu downloader

Tải truyện từ metruyenchu.com định dạng epub.

  1. // ==UserScript==
  2. // @name MeTruyenChu downloader
  3. // @namespace https://nntoan.com/
  4. // @description Tải truyện từ metruyenchu.com định dạng epub.
  5. // @version 1.0.1
  6. // @icon https://i.imgur.com/ZOmmIGK.png
  7. // @author Toan Nguyen
  8. // @oujs:author nntoan
  9. // @license MIT; https://nntoan.mit-license.org/
  10. // @supportURL https://github.com/nntoan/UserScripts/issues
  11. // @match http://metruyenchu.com/truyen/*
  12. // @match https://metruyenchu.com/truyen/*
  13. // @match http://vtruyen.com/truyen/*
  14. // @match https://vtruyen.com/truyen/*
  15. // @require https://code.jquery.com/ui/1.12.0/jquery-ui.min.js
  16. // @require https://unpkg.com/jszip@3.2.1/dist/jszip.min.js
  17. // @require https://unpkg.com/ejs@2.6.1/ejs.min.js
  18. // @require https://unpkg.com/jepub@2.1.0/dist/jepub.min.js
  19. // @require https://unpkg.com/file-saver@2.0.2/dist/FileSaver.min.js
  20. // @require https://cdn.jsdelivr.net/npm/mbdownloader@0.2.4/src/mbDownloader.js
  21. // @connect self
  22. // @run-at document-idle
  23. // @noframes
  24. // ==/UserScript==
  25. /*global console, location*/
  26. (function ($, window, document) { // eslint-disable-line
  27. 'use strict';
  28. $(document).ready(function() {
  29. $.widget('nntoan.mbDownloader', $.nntoan.mbDownloader, {
  30. _create: function () {
  31. var self = this;
  32. this._super();
  33. // Capture network requests
  34. var proxied = window.XMLHttpRequest.prototype.open;
  35. window.XMLHttpRequest.prototype.open = function () {
  36. if (arguments[1].includes('v2/chapters') === true) {
  37. self.options.xhr.chapter.url = arguments[1];
  38. }
  39. return proxied.apply(this, [].slice.call(arguments));
  40. };
  41. // Extending options
  42. this.options.processing.ebookFileName = this.options.general.pathname.slice(8, -5);
  43. this.options.xhr.content.url = this.options.general.pathname + this.options.chapters.chapId + '/';
  44. // Styling download button for current site
  45. this.elements.$downloadBtn.attr('class', 'btn btn-warning btn-md btn-block text-white font-weight-semibold d-flex align-items-center justify-content-center');
  46. this.elements.$downloadBtn.html('<i class="nh-icon icon-book mr-2"></i>Tải xuống');
  47. this.elements.$downloadWrapper = $('<li></li>', {
  48. class: 'mr-3 w-150'
  49. });
  50. this.elements.$downloadWrapper.html(this.elements.$downloadBtn);
  51. this.elements.$downloadWrapper.appendTo(this.options.classNames.downloadAppendTo);
  52. document.getElementById(this.options.classNames.getChapterTrigger).click();
  53. console.time('downloadAndGenerateEpub');
  54. },
  55. /**
  56. * Get list of chapters request.
  57. *
  58. * @param {Object} that Curent widget object
  59. * @param {Event} event jQuery event
  60. * @param {Element} $widget Current node element
  61. * @returns void
  62. */
  63. getListOfChapters: function (that, event, $widget) {
  64. var options = that.options, $ajax = null;
  65. if (options.isGlocCallbackRequired) {
  66. $ajax = that._trigger('getListOfChaptersPreprocess', event, that);
  67. } else {
  68. $ajax = $.ajax(options.xhr.chapter);
  69. }
  70. $ajax.done(function (response) {
  71. if (options.isGlocCallbackRequired) {
  72. $.ajax(options.xhr.chapter).done(function (data) {
  73. that.processListOfChapters(data, that, $widget);
  74. }).fail(function (error) {
  75. $widget.text('Lỗi trước khi bị lỗi danh mục :)');
  76. that.downloadStatus('error');
  77. console.error(error); //eslint-disable-line
  78. });
  79. } else {
  80. that.processListOfChapters(response, that, $widget);
  81. }
  82. }).fail(function (error) {
  83. $widget.text('Lỗi danh mục');
  84. that.downloadStatus('error');
  85. console.error(error); //eslint-disable-line
  86. });
  87. },
  88. /**
  89. * Process with the XHR response of chapters list.
  90. *
  91. * @param {jqXHR} response XHR response
  92. * @param {Object} that Curent widget object
  93. * @param {Element} $widget Current node element
  94. * @returns void
  95. */
  96. processListOfChapters: function (response, that, $widget) {
  97. var options = that.options;
  98. options.chapters.chapList = response._data.chapters;
  99. options.chapters.chapList = options.chapters.chapList.map(function (val) {
  100. return that.chapListValueFilter(options, val);
  101. }).filter(function (chapter) {
  102. return chapter !== '';
  103. });
  104. that._trigger('chapListFiltered', null, options.chapters.chapList);
  105. that.processing.chapListSize = options.chapters.chapList.length;
  106. if (that.processing.chapListSize > 0) {
  107. that.elements.$window.on('beforeunload', function () {
  108. return 'Truyện đang được tải xuống...';
  109. });
  110. $widget.one('click', function (e) {
  111. e.preventDefault();
  112. that.saveEbook($widget);
  113. });
  114. that.getContent($widget);
  115. }
  116. },
  117. /**
  118. * Callback function to handle chap list values.
  119. *
  120. * @param {Object} options
  121. * @param {String} val
  122. * @returns {String}
  123. */
  124. chapListValueFilter: function (options, val) {
  125. val = val.slug.replace(options.general.referrer, '');
  126. val = '/' + val;
  127. return val.trim();
  128. },
  129. /**
  130. * Update CSS of download button.
  131. *
  132. * @param {String} status Download status
  133. * @returns void
  134. */
  135. downloadStatus: function (status) {
  136. var self = this,
  137. options = this.options;
  138. self.elements.$downloadBtn.removeClass(options.classNames.downloadBtnStatus);
  139. if (status === 'error') {
  140. self.elements.$downloadBtn.addClass('btn-danger');
  141. }
  142. if (status === 'warning') {
  143. self.elements.$downloadBtn.addClass('btn-warning');
  144. }
  145. if (status === 'success') {
  146. self.elements.$downloadBtn.addClass('btn-success');
  147. }
  148. },
  149. /**
  150. * Cleanup redundant charactes in chapter content.
  151. *
  152. * @param {String} html Chapter content as HTML
  153. * @returns {String}
  154. */
  155. cleanupHtml: function (html) {
  156. var options = this.options;
  157. html = html.replace(options.regularExp.chapter, '');
  158. html = html.replace(options.regularExp.novel, '');
  159. html = html.replace(options.regularExp.chineseSpecialChars, '');
  160. html = html.replace(options.regularExp.alphanumeric, function (key, attr) {
  161. if (attr) return ' ';
  162. if (!isNaN(key)) return key;
  163. if (key.split(options.regularExp.alphabet).length > 2) return ' ';
  164. if (key.split(options.regularExp.number).length > 1) return ' ';
  165. return key;
  166. });
  167. html = html.replace(options.regularExp.buttons, '');
  168. html = html.split(this.createRegExp(options.regularExp.eoctext))[0];
  169. html = html.replace('<br> <br>', '<br />');
  170. return '<div>' + html + '</div>';
  171. },
  172. });
  173. $(this).mbDownloader({
  174. readyToInit: true,
  175. createDownloadWrapper: true,
  176. processing: {
  177. ebookFileExt: '.epub'
  178. },
  179. jwt: {
  180. crypt: 'c&fjFR!WXPDPPmTj*!np2E98TPw5GMN93S43WVZDnR9fcmf@g*RA*Z',
  181. },
  182. classNames: {
  183. novelId: '#report',
  184. infoBlock: '#app .container',
  185. chapterContent: '#js-read__content',
  186. chapterNotContent: 'iframe, script, style, a, div, p:has(a[href*="metruyenchu.com"])',
  187. chapterVip: '#btnChapterVip',
  188. chapterTitle: '.nh-read__title',
  189. ebookTitle: 'h1',
  190. ebookAuthor: '.row.no-gutters .list-unstyled.mb-4 .border-secondary',
  191. ebookCover: '.row.no-gutters .nh-thumb.shadow',
  192. ebookDesc: '#nav-intro .content',
  193. ebookType: '.row.no-gutters .list-unstyled.mb-4 li .border-primary',
  194. getChapterTrigger: 'nav-tab-chap',
  195. downloadBtnStatus: 'btn-primary btn-success btn-info btn-warning btn-danger blue success warning info danger error',
  196. downloadWrapper: 'mr-3 w-150',
  197. downloadAppendTo: '.row.no-gutters .list-unstyled.d-flex.align-items-center',
  198. },
  199. ebook: {
  200. fallbackCover: 'https://static.cdnno.com/background/metruyenchu.jpg'
  201. },
  202. chapters: {
  203. chapListSlice: [6, -1],
  204. },
  205. xhr: {
  206. chapter: {
  207. type: 'GET',
  208. url: '',
  209. },
  210. content: {
  211. type: 'GET',
  212. xhrFields: {
  213. withCredentials: true
  214. }
  215. }
  216. },
  217. bookInfoUpdated: function (event, data) {
  218. var that = data.that,
  219. options = that.options,
  220. $infoBlock = that.elements.$infoBlock;
  221. options.ebook = $.extend(options.ebook, {
  222. title: $infoBlock.find(options.classNames.ebookTitle).text().trim(),
  223. author: $infoBlock.find(options.classNames.ebookAuthor).text().trim(),
  224. cover: $infoBlock.find(options.classNames.ebookCover).find('img').attr('src'),
  225. description: $infoBlock.find(options.classNames.ebookDesc).html(),
  226. });
  227. data.epubInfo = $.extend(data.epubInfo, options.ebook);
  228. console.log('Book information updated...', data.epubInfo);
  229. },
  230. chapTitleUpdated: function(event, data) {
  231. var options = data.this.options;
  232. options.xhr.content.url = location.protocol + '//' + options.general.host + options.chapters.chapId;
  233. },
  234. beforeCreateEpub: function(event, that) {
  235. console.log('Prepare generate epub...');
  236. },
  237. complete: function(event, that) {
  238. console.log('Epub downloaded successfully. Please check your Downloads folder.');
  239. console.timeEnd('downloadAndGenerateEpub');
  240. }
  241. });
  242. });
  243. })(jQuery, window, document); // eslint-disable-line