// ==UserScript==
// @name MeTruyenChu downloader
// @namespace https://nntoan.com/
// @description Tải truyện từ metruyenchu.com định dạng epub.
// @version 1.0.1
// @icon https://i.imgur.com/ZOmmIGK.png
// @author Toan Nguyen
// @oujs:author nntoan
// @license MIT; https://nntoan.mit-license.org/
// @supportURL https://github.com/nntoan/UserScripts/issues
// @match http://metruyenchu.com/truyen/*
// @match https://metruyenchu.com/truyen/*
// @match http://vtruyen.com/truyen/*
// @match https://vtruyen.com/truyen/*
// @require https://code.jquery.com/ui/1.12.0/jquery-ui.min.js
// @require https://unpkg.com/jszip@3.2.1/dist/jszip.min.js
// @require https://unpkg.com/ejs@2.6.1/ejs.min.js
// @require https://unpkg.com/jepub@2.1.0/dist/jepub.min.js
// @require https://unpkg.com/file-saver@2.0.2/dist/FileSaver.min.js
// @require https://cdn.jsdelivr.net/npm/mbdownloader@0.2.4/src/mbDownloader.js
// @connect self
// @run-at document-idle
// @noframes
// ==/UserScript==
/*global console, location*/
(function ($, window, document) { // eslint-disable-line
'use strict';
$(document).ready(function() {
$.widget('nntoan.mbDownloader', $.nntoan.mbDownloader, {
_create: function () {
var self = this;
this._super();
// Capture network requests
var proxied = window.XMLHttpRequest.prototype.open;
window.XMLHttpRequest.prototype.open = function () {
if (arguments[1].includes('v2/chapters') === true) {
self.options.xhr.chapter.url = arguments[1];
}
return proxied.apply(this, [].slice.call(arguments));
};
// Extending options
this.options.processing.ebookFileName = this.options.general.pathname.slice(8, -5);
this.options.xhr.content.url = this.options.general.pathname + this.options.chapters.chapId + '/';
// Styling download button for current site
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');
this.elements.$downloadBtn.html('<i class="nh-icon icon-book mr-2"></i>Tải xuống');
this.elements.$downloadWrapper = $('<li></li>', {
class: 'mr-3 w-150'
});
this.elements.$downloadWrapper.html(this.elements.$downloadBtn);
this.elements.$downloadWrapper.appendTo(this.options.classNames.downloadAppendTo);
document.getElementById(this.options.classNames.getChapterTrigger).click();
console.time('downloadAndGenerateEpub');
},
/**
* Get list of chapters request.
*
* @param {Object} that Curent widget object
* @param {Event} event jQuery event
* @param {Element} $widget Current node element
* @returns void
*/
getListOfChapters: function (that, event, $widget) {
var options = that.options, $ajax = null;
if (options.isGlocCallbackRequired) {
$ajax = that._trigger('getListOfChaptersPreprocess', event, that);
} else {
$ajax = $.ajax(options.xhr.chapter);
}
$ajax.done(function (response) {
if (options.isGlocCallbackRequired) {
$.ajax(options.xhr.chapter).done(function (data) {
that.processListOfChapters(data, that, $widget);
}).fail(function (error) {
$widget.text('Lỗi trước khi bị lỗi danh mục :)');
that.downloadStatus('error');
console.error(error); //eslint-disable-line
});
} else {
that.processListOfChapters(response, that, $widget);
}
}).fail(function (error) {
$widget.text('Lỗi danh mục');
that.downloadStatus('error');
console.error(error); //eslint-disable-line
});
},
/**
* Process with the XHR response of chapters list.
*
* @param {jqXHR} response XHR response
* @param {Object} that Curent widget object
* @param {Element} $widget Current node element
* @returns void
*/
processListOfChapters: function (response, that, $widget) {
var options = that.options;
options.chapters.chapList = response._data.chapters;
options.chapters.chapList = options.chapters.chapList.map(function (val) {
return that.chapListValueFilter(options, val);
}).filter(function (chapter) {
return chapter !== '';
});
that._trigger('chapListFiltered', null, options.chapters.chapList);
that.processing.chapListSize = options.chapters.chapList.length;
if (that.processing.chapListSize > 0) {
that.elements.$window.on('beforeunload', function () {
return 'Truyện đang được tải xuống...';
});
$widget.one('click', function (e) {
e.preventDefault();
that.saveEbook($widget);
});
that.getContent($widget);
}
},
/**
* Callback function to handle chap list values.
*
* @param {Object} options
* @param {String} val
* @returns {String}
*/
chapListValueFilter: function (options, val) {
val = val.slug.replace(options.general.referrer, '');
val = '/' + val;
return val.trim();
},
/**
* Update CSS of download button.
*
* @param {String} status Download status
* @returns void
*/
downloadStatus: function (status) {
var self = this,
options = this.options;
self.elements.$downloadBtn.removeClass(options.classNames.downloadBtnStatus);
if (status === 'error') {
self.elements.$downloadBtn.addClass('btn-danger');
}
if (status === 'warning') {
self.elements.$downloadBtn.addClass('btn-warning');
}
if (status === 'success') {
self.elements.$downloadBtn.addClass('btn-success');
}
},
/**
* Cleanup redundant charactes in chapter content.
*
* @param {String} html Chapter content as HTML
* @returns {String}
*/
cleanupHtml: function (html) {
var options = this.options;
html = html.replace(options.regularExp.chapter, '');
html = html.replace(options.regularExp.novel, '');
html = html.replace(options.regularExp.chineseSpecialChars, '');
html = html.replace(options.regularExp.alphanumeric, function (key, attr) {
if (attr) return ' ';
if (!isNaN(key)) return key;
if (key.split(options.regularExp.alphabet).length > 2) return ' ';
if (key.split(options.regularExp.number).length > 1) return ' ';
return key;
});
html = html.replace(options.regularExp.buttons, '');
html = html.split(this.createRegExp(options.regularExp.eoctext))[0];
html = html.replace('<br> <br>', '<br />');
return '<div>' + html + '</div>';
},
});
$(this).mbDownloader({
readyToInit: true,
createDownloadWrapper: true,
processing: {
ebookFileExt: '.epub'
},
jwt: {
crypt: 'c&fjFR!WXPDPPmTj*!np2E98TPw5GMN93S43WVZDnR9fcmf@g*RA*Z',
},
classNames: {
novelId: '#report',
infoBlock: '#app .container',
chapterContent: '#js-read__content',
chapterNotContent: 'iframe, script, style, a, div, p:has(a[href*="metruyenchu.com"])',
chapterVip: '#btnChapterVip',
chapterTitle: '.nh-read__title',
ebookTitle: 'h1',
ebookAuthor: '.row.no-gutters .list-unstyled.mb-4 .border-secondary',
ebookCover: '.row.no-gutters .nh-thumb.shadow',
ebookDesc: '#nav-intro .content',
ebookType: '.row.no-gutters .list-unstyled.mb-4 li .border-primary',
getChapterTrigger: 'nav-tab-chap',
downloadBtnStatus: 'btn-primary btn-success btn-info btn-warning btn-danger blue success warning info danger error',
downloadWrapper: 'mr-3 w-150',
downloadAppendTo: '.row.no-gutters .list-unstyled.d-flex.align-items-center',
},
ebook: {
fallbackCover: 'https://static.cdnno.com/background/metruyenchu.jpg'
},
chapters: {
chapListSlice: [6, -1],
},
xhr: {
chapter: {
type: 'GET',
url: '',
},
content: {
type: 'GET',
xhrFields: {
withCredentials: true
}
}
},
bookInfoUpdated: function (event, data) {
var that = data.that,
options = that.options,
$infoBlock = that.elements.$infoBlock;
options.ebook = $.extend(options.ebook, {
title: $infoBlock.find(options.classNames.ebookTitle).text().trim(),
author: $infoBlock.find(options.classNames.ebookAuthor).text().trim(),
cover: $infoBlock.find(options.classNames.ebookCover).find('img').attr('src'),
description: $infoBlock.find(options.classNames.ebookDesc).html(),
});
data.epubInfo = $.extend(data.epubInfo, options.ebook);
console.log('Book information updated...', data.epubInfo);
},
chapTitleUpdated: function(event, data) {
var options = data.this.options;
options.xhr.content.url = location.protocol + '//' + options.general.host + options.chapters.chapId;
},
beforeCreateEpub: function(event, that) {
console.log('Prepare generate epub...');
},
complete: function(event, that) {
console.log('Epub downloaded successfully. Please check your Downloads folder.');
console.timeEnd('downloadAndGenerateEpub');
}
});
});
})(jQuery, window, document); // eslint-disable-line