baivong / manga comic downloader

// ==UserScript==
// @name         manga comic downloader
// @namespace    https://baivong.github.io
// @description  Tải truyện tranh từ các trang chia sẻ ở Việt Nam
// @version      1.0.0
// @icon         https://i.imgur.com/kSFp9ja.png
// @author       Zzbaivong
// @license      GPL-3.0+; http://www.gnu.org/licenses/gpl-3.0.txt
// @include      /^https?:\/\/truyentranh8\.[^\s\/\?#:\!]{2,}\/([^\/]+\/)?$/
// @include      /^https?:\/\/truyentranh8\.[^\s\/\?#:\!]{2,}\/\/?manga\/\d+\-[^\/]+\/[^\/]+\/$/
// @exclude      /^https?:\/\/truyentranh8\.[^\s\/\?#:\!]{2,}\/(vechai|truyen_tranh_tuan|danh_sach_truyen|truyen_xem_nhieu|trai|gai|quanly)\/$/
// @include      /^https?:\/\/iutruyentranh\.[^\s\/\?#:\!]{2,}\/(truyen\/\d+\-[\w\-]+\/)?$/
// @include      /^https?:\/\/truyentranh\.[^\s\/\?#:\!]{2,}\/([^\/\.]+)?$/
// @include      /^https?:\/\/manga24h\.[^\s\/\?#:\!]{2,}\/[^\/\.]+\.htm$/
// @include      /^https?:\/\/comicvn\.[^\s\/\?#:\!]{2,}\/(truyen\-tranh(\-online)\/[^\/]+\/?\d+)?$/
// @include      /^https?:\/\/hamtruyen\.[^\s\/\?#:\!]{2,}\/[^\/\.]+\.html$/
// @exclude      /^https?:\/\/hamtruyen\.[^\s\/\?#:\!]{2,}\/(dangky|quenmatkhau)\/$/
// @include      /^https?:\/\/ntruyen\.[^\s\/\?#:\!]{2,}\/truyen\/[^\/\.]+$/
// @include      /^https?:\/\/ntruyentranh\.[^\s\/\?#:\!]{2,}\/truyen\/[^\/\.]+$/
// @include      /^https?:\/\/(www\.)?a3manga\.[^\s\/\?#:\!]{2,}\/(truyen\-tranh\/[^\/]+\/)?$/
// @include      /^https?:\/\/truyenv1\.[^\s\/\?#:\!]{2,}\/truyentranh\/[^\/]+\/$/
// @include      /^https?:\/\/truyentranhtuan\.[^\s\/\?#:\!]{2,}\/([^\/](?!\-chuong\-\d+))+\/$/
// @include      /^https?:\/\/mangak\.[^\s\/\?#:\!]{2,}\/([^\/](?!\-chap\-\d+))+\/$/
// @include      /^https?:\/\/mangak\.[^\s\/\?#:\!]{2,}\/(moi\-cap\-nhat\/)?(page\/\d+\/)?$/
// @exclude      /^https?:\/\/mangak\.[^\s\/\?#:\!]{2,}\/(moi\-dang|ongoing|top\-view|hot|full|action|adult|adventure|anh\-dep|anime|bender|bishounen|comedy|comic|cooking|cosplay|demons|doujinshi|drama|ecchi|fanmade|fantasy|gender|gender\-bender|harem|historical|horror|huyen\-huyen|josei|live\-action|magic|manhua|manhwa|martial\-arts|mature|mecha|mystery|one\-shot|oneshot|psychological|romance|school\-life|sci\-fi|seinen|shoujo|shoujo\-ai|shoujoai|shounen|shounen\-ai|slice\-of\-life|smut|sports|supernatural|tragedy|trap|vampire|webtoons|yuri|zombie)\/$/
// @include      /^https?:\/\/(1\.)?truyentranhmoi\.[^\s\/\?#:\!]{2,}\/([^\/](?!\-chap\-\d+))+\/$/
// @include      /^https?:\/\/(1\.)?truyentranhmoi\.[^\s\/\?#:\!]{2,}\/(moi\-cap\-nhat\/)?(page\/\d+\/)?$/
// @exclude      /^https?:\/\/(1\.)?truyentranhmoi\.[^\s\/\?#:\!]{2,}\/(moi\-dang|ongoing|top\-view|hot|full|action|adult|adventure|anh\-dep|anime|bender|bishounen|comedy|comic|cooking|cosplay|demons|doujinshi|drama|ecchi|fanmade|fantasy|gender|gender\-bender|harem|historical|horror|huyen\-huyen|josei|live\-action|magic|manhua|manhwa|martial\-arts|mature|mecha|mystery|one\-shot|oneshot|psychological|romance|school\-life|sci\-fi|seinen|shoujo|shoujo\-ai|shoujoai|shounen|shounen\-ai|slice\-of\-life|smut|sports|supernatural|tragedy|trap|vampire|webtoons|yuri|zombie)\/$/
// @include      /^https?:\/\/dammetruyen\.[^\s\/\?#:\!]{2,}\/book\/[^\/\.]+$/
// @include      /^https?:\/\/(www\.)?goccay\.[^\s\/\?#:\!]{2,}/\d{4}/\d{2}/[^\/\.]+.html$/
// @include      /^https?:\/\/(www\.)?tuoithodudoi\.[^\s\/\?#:\!]{2,}\/(truyen\-tranh\/[^\/\.]+\.\d+\.html)?$/
// @include      /^https?:\/\/truyentranhlh\.[^\s\/\?#:\!]{2,}\/truyen\-[^\/\.]+\.html$/
// @include      /^https?:\/\/hocvientruyentranh\.[^\s\/\?#:\!]{2,}\/manga\/\d+\/[^\/\.\?]+$/
// @include      /^https?:\/\/truyenhay24h\.[^\s\/\?#:\!]{2,}\/[^\/\.]+\.html$/
// @exclude      /^https?:\/\/truyenhay24h\.[^\s\/\?#:\!]{2,}\/(lien\-he|Dang\-ky|forgetPass)\.html$/
// @include      /^https?:\/\/uptruyen\.[^\s\/\?#:\!]{2,}\/manga\/\d+/[^\/]+/[^\/\.]+.html$/
// @include      /^https?:\/\/uptruyen\.[^\s\/\?#:\!]{2,}\/manga\/\d+/([^\/]+/)?([^\/\.]+)?.html$/
// @include      /^https?:\/\/thichtruyentranh\.[^\s\/\?#:\!]{2,}/([^\/]+)/\d+(\/trang\.\d+)?.html$/
// @include      /^https?:\/\/truyen1\.[^\s\/\?#:\!]{2,}\/TruyenTranh\/[^\/]+$/
// @include      /^https?:\/\/truyentranhtop\.[^\s\/\?#:\!]{2,}\/[^\/\.]+\.html$/
// @include      /^https?:\/\/bigtruyen\.[^\s\/\?#:\!]{2,}\/([^\/]+\/)?$/
// @exclude      /^https?:\/\/bigtruyen\.[^\s\/\?#:\!]{2,}\/(danh-sach-truyen|truyen-hot|)\/$/
// @include      /^https?:\/\/(www\.)?hentailx\.[^\s\/\?#:\!]{2,}\/[^\.]+\.html$/
// @exclude      /^https?:\/\/(www\.)?hentailx\.[^\s\/\?#:\!]{2,}\/dang-ky-thanh-vien\.html$/
// @include      /^https?:\/\/hentaivn\.[^\s\/\?#:\!]{2,}\/\d+\-[^\.]+\.html$/
// @include      /^https?:\/\/catscomic\.[^\s\/\?#:\!]{2,}\/[^\/]+\/$/
// @exclude      /^https?:\/\/catscomic\.[^\s\/\?#:\!]{2,}\/(danh-sach-truyen|truyen-moi-cap-nhat|truyen-hoan-thanh|truyen-hot)\/$/
// @include      /^https?:\/\/otakusan\.[^\s\/\?#:\!]{2,}\/MangaDetail\/\d+\/[^\/\.]+$/
// @include      /^https?:\/\/ngonphongcomics\.[^\s\/\?#:\!]{2,}\/[^\/]+\/$/
// @exclude      /^https?:\/\/ngonphongcomics\.[^\s\/\?#:\!]{2,}\/(danh-sach-truyen|the-loai|nhom-dich|tac-gia)\/$/
// @include      /^https?:\/\/(www\.)?nettruyen.[^\s\/\?#:\!]{2,}\/truyen\-tranh/[^\/]+\/?$/
// @include      /^https?:\/\/(www\.)?hamtruyentranh.[^\s\/\?#:\!]{2,}\/truyen\/[\w\-]+\.html$/
// @include      /^https?:\/\/truyensieuhay\.[^\s\/\?#:\!]{2,}\/[\w\-]+\.html$/
// @include      /^https?:\/\/gocthugian\.[^\s\/\?#:\!]{2,}\/truyen\/(t|v)\d+\/$/
// @include      /^https?:\/\/ttmanga\.[^\s\/\?#:\!]{2,}\/ThongTin\/[\w\-]+_id=\d+$/
// @require      https://code.jquery.com/jquery-3.2.1.min.js
// @require      https://cdnjs.cloudflare.com/ajax/libs/jszip/3.1.4/jszip.min.js
// @require      https://cdnjs.cloudflare.com/ajax/libs/FileSaver.js/1.3.3/FileSaver.min.js
// @require      https://greasyfork.org/scripts/5392-waitforkeyelements/code/WaitForKeyElements.js?version=115012
// @require      https://greasemonkey.github.io/gm4-polyfill/gm4-polyfill.js
// @noframes
// @connect      self
// @run-at       document-idle
// @grant        GM_addStyle
// @grant        GM_xmlhttpRequest
// @grant        GM.xmlHttpRequest
// ==/UserScript==

/* global waitForKeyElements */
(function ($, window, document) {

    /**
     * Output extension
     * @type {String} zip
     *                cbz
     *
     * Tips: Convert .zip to .cbz
     * Windows
     * $ ren *.zip *.cbz
     * Linux
     * $ rename 's/\.zip$/\.cbz/' *.zip
     */
    var outputExt = 'cbz'; // or 'zip'

    /**
     * Multithreading
     * @type {Number} [1 -> 32]
     */
    var threading = 8;


    /* === DO NOT CHANGE === */

    function isEmpty(el) {
        return !$.trim(el.html());
    }

    function decodeUrl(url) {
        var parser = new DOMParser,
            dom = parser.parseFromString(
                '<!doctype html><body>' + url,
                'text/html');

        return decodeURIComponent(dom.body.textContent);
    }

    function noty(txt, status) {
        function destroy(time) {
            $noty.fadeOut((time || 0), function () {
                $noty.remove();
                $noty = [];
            });
            clearTimeout(notyTimeout);
        }

        function autoHide() {
            notyTimeout = setTimeout(function () {
                destroy(300);
            }, 2000);
        }

        if (!$noty.length) {
            var $wrap = $('<div>', {
                    id: 'baivong_noty_wrap'
                }),
                $content = $('<div>', {
                    id: 'baivong_noty_content',
                    'class': 'baivong_' + status,
                    html: txt
                }),
                $close = $('<div>', {
                    id: 'baivong_noty_close',
                    html: '&times;'
                });

            $noty = $wrap.append($content).append($close);
            $noty.fadeIn(300).appendTo('body');
        } else {
            $noty.find('#baivong_noty_content').attr('class', 'baivong_' + status).html(txt);

            $noty.show();
            clearTimeout(notyTimeout);
        }

        $noty.click(function () {
            destroy();
        }).hover(function () {
            clearTimeout(notyTimeout);
        }, function () {
            autoHide();
        });
        if (status !== 'warning') autoHide();
    }

    function notyError() {
        noty('Lỗi! Không tải được <strong>' + chapName + '</strong>', 'error');
    }

    function notyImages() {
        noty('Lỗi! Không thể copy <strong>' + chapName + '</strong>', 'error');
    }

    function notySuccess(source) {
        if (threading < 1) threading = 1;
        if (threading > 32) threading = 32;

        dlImages = source;

        dlTotal = dlImages.length;
        addZip();

        noty('Bắt đầu tải <strong>' + chapName + '</strong>', 'success');
    }

    function notyWait() {
        noty('<strong>' + chapName + '</strong> đang lấy dữ liệu...', 'warning');
    }

    function notyReady() {
        noty('Script đã <strong>sẵn sàng</strong> làm việc', 'info');
    }

    function endZip() {
        dlZip = new JSZip();
        dlPrevZip = false;
        dlCurrent = 0;
        dlFinal = 0;
        dlTotal = 0;
        dlImages = [];
        
        inProgress = false;
    }

    function genZip() {
        dlZip.generateAsync({
            type: 'blob'
        }).then(function (blob) {
            var zipName = chapName.replace(/\s/g, '_') + '.' + outputExt;

            if (dlPrevZip) URL.revokeObjectURL(dlPrevZip);
            dlPrevZip = blob;

            noty('<a href="' + URL.createObjectURL(dlPrevZip) + '" download="' + zipName + '">Click vào đây</strong> nếu trình duyệt không tự tải xuống', 'success');

            saveAs(blob, zipName);

            document.title = '[⇓] ' + tit;
            noty('Đã tải <strong>' + chapName + '</strong> thành công', 'success');
            endZip();
        }, function (e) {
            noty(e.message, 'error');

            document.title = '[x] ' + tit;
            noty('Lỗi tạo file nén của <strong>' + chapName + '</strong>', 'error');
            endZip();
        });
    }

    function dlImg(url, success, error) {
        var filename = url.replace(/.*\//g, '');
        filename = filename.replace(/(\?|#).*/, '');
        filename = filename.split('.');
        filename = ('0000' + dlCurrent).slice(-4) + '.' + filename[1];

        GM.xmlHttpRequest({
            method: 'GET',
            url: url,
            responseType: 'arraybuffer',
            onload: function (response) {
                dlFinal++;
                success(response, filename);
            },
            onerror: function (err) {
                dlFinal++;
                error(err, filename);
            }
        });
    }

    function next() {
        noty('<strong class="centered">' + dlFinal + '/' + dlTotal + '</strong>', 'warning');

        if (dlFinal < dlCurrent) return;
        dlFinal < dlTotal ? addZip() : genZip();
    }

    function addZip() {
        var max = dlCurrent + threading;
        if (max > dlTotal) max = dlTotal;

        for (dlCurrent; dlCurrent < max; dlCurrent++) {
            var url = dlImages[dlCurrent];

            dlImg(url, function (response, filename) {
                dlZip.file(filename, response.response);

                next();
            }, function (err, filename) {
                dlZip.file(filename + '_error.gif', 'R0lGODdhBQAFAIACAAAAAP/eACwAAAAABQAFAAACCIwPkWerClIBADs=', {
                    base64: true
                });
                noty(url, 'error');

                next();
            });
        }
    }
    
    function imageIgnore(url) {
        var ignoreList = [
            'http://truyentranh8.net/templates/main/images/gioithieubanbe3.png',
            '/public/images/loading.gif',
            'http://truyentranhlh.net/wp-content/uploads/2015/10/lhmanga.png',
            '/Content/Img/1eeef5d2-b936-496d-ba41-df1b21d0166a.jpg',
            '/Content/Img/d79886b3-3699-47b2-bbf4-af6149c2e8fb.jpg'
        ];

        return (ignoreList.indexOf(url) !== -1);
    }

    function imageFilter(url) {
        url = decodeUrl(url);
        url = url.trim();
        url = url.replace(/^.+(&|\?)url=/, '');
        url = url.replace(/(https?:\/\/)lh(\d)(\.bp\.blogspot\.com)/, '$1$2$3');
        url = url.replace(/(https?:\/\/)lh\d\.(googleusercontent|ggpht)\.com/, '$14.bp.blogspot.com');
        url = url.replace(/\?.+$/, '');
        if (url.indexOf('blogspot.com') !== -1) {
            url = url.replace(/\/([^/]+-)?(Ic42)(-[^/]+)?\//, '/$2/');
            url = url.replace(/\/(((s|w|h)\d+|(w|h)\d+-(w|h)\d+))?-?(c|d|g)?\/(?=[^/]+$)/, '/');
            url += '?imgmax=16383';
        }
        if (url.indexOf('i.imgur.com') !== -1) url = url.replace(/(\/)(\w{5}|\w{7})(s|b|t|m|l|h)(\.(jpe?g|png|gif))$/, '$1$2$4');
        url = url.replace(/^https:\/\//, 'http://');
        url = encodeURI(url);

        return url;
    }

    function saveToClipboard(images) {
        var source = [];

        if (!images.length) {
            notyImages();
        } else {
            $.each(images, function (i, v) {
                if (imageIgnore(v) || typeof v === 'undefined') return;

                if ((v.indexOf(location.origin) === 0 || v.indexOf('/') === 0) && !/^(\.(jpg|png|gif)|webp|jpeg)$/.test(v.slice(-4))) {
                    return;
                } else if (v.indexOf('http') === -1) {
                    v = location.origin + '/' + v;
                } else if (v.indexOf('/') === 0) {
                    v = location.origin + v;
                } else if (v.indexOf('http') === 0) {
                    v = imageFilter(v);
                } else {
                    return;
                }

                source.push(v);
            });

            notySuccess(source);
        }
    }

    function getImages($contents) {
        var images = [];
        $contents.each(function (i, v) {
            var $img = $(v);
            images[i] = $img.data('src') || $img.data('original');
        });

        saveToClipboard(images);
    }

    function getContents($source) {
        var method = 'find';
        if (configs.filter) method = 'filter';

        var $entry = $source[method](configs.contents).find('img');
        if (!$entry.length) {
            notyImages();
        } else {
            getImages($entry);
        }
    }

    function rightClickEvent(_this, callback) {
        var $this = $(_this),
            name = configs.name;

        configs.href = _this.href;

        if (!name) {
            chapName = $this.text();
        } else if (typeof name === 'function') {
            chapName = name(_this);
        } else {
            chapName = $this.find(name).text();
        }
        chapName = $.trim(chapName);
        notyWait();

        $.get(configs.href).done(function (responseText) {
            responseText = responseText.replace(/<img [^>]*src\s?=['"]([^'"]+)[^>]*>/gi, function (match, capture) {
                return '<img data-src="' + capture + '" />';
            });
            responseText = responseText.replace(/^[^<]*/, '');

            var $data = $(responseText);
            if (typeof callback === 'function') {
                callback($data);
            } else {
                getContents($data);
            }
        }).fail(function () {
            notyError();
        });
    }

    function oneProgress() {
        if (inProgress) {
            noty('Chỉ được phép <strong>tải một truyện</strong> mỗi lần', 'error');
            return false;
        }
        inProgress = true;
        return true;
    }

    function getSource(callback) {
        var $link = $(configs.link);

        if (!$link.length) return;
        notyReady();

        $link.on('contextmenu', function (e) {
            e.preventDefault();
            if (!oneProgress()) return;

            rightClickEvent(this, callback);
        });
    }


    function getTruyenTranh8() {
        getSource(function ($data) {
            var packer = $data.find('#logoTT8').siblings('script').text().trim().split('eval')[1],
                lstImages = [];

            eval(eval(packer));
            saveToClipboard(lstImages);
        });
    }

    function getIuTruyenTranh() {
        getSource(function ($data) {
            var packer = $data.filter('div.wrapper').find('script:first').text().trim().split('eval')[1],
                lstImages = [];

            eval(eval(packer));
            saveToClipboard(lstImages);
        });
    }

    function getComicVn() {
        getSource(function ($data) {
            var $iframe = $data.filter('iframe'),
                getSourceComicVn = function ($data) {
                    var $entry = $data.find('#txtarea');

                    if (!$entry.length) {
                        notyImages();
                    } else {
                        var input = $entry.val(),
                            regex = /src="([^"']+)"/gi,
                            matches, output = [];

                        // eslint-disable-next-line no-cond-assign
                        while (matches = regex.exec(input)) {
                            output.push(decodeURIComponent(matches[1]));
                        }
                        saveToClipboard(output);
                    }
                };

            if ($iframe.length) {
                GM_xmlhttpRequest({
                    method: 'GET',
                    url: $iframe.attr('src'),
                    onload: function (response) {
                        response = response.responseText.replace(/<img [^>]*src=['"]([^'"]+)[^>]*>/gi, function (match, capture) {
                            return '<img data-src="' + capture + '" />';
                        });

                        getSourceComicVn($(response));
                    },
                    onerror: function () {
                        notyError();
                    }
                });
            } else {
                getSourceComicVn($data);
            }
        });
    }

    function getNtruyen() {
        getSource(function ($data) {
            var $entry = $data.find('#containerListPage');
            if (!$entry.length) {
                notyImages();
            } else {
                if (isEmpty($entry)) {
                    var id = configs.href.match(/\/(\d+)\/[\w\d-]+$/i)[1];
                    $.ajax({
                        type: 'post',
                        url: '/MainHandler.ashx',
                        data: JSON.stringify({
                            id: 3,
                            method: 'getChapter',
                            params: [id]
                        }),
                        contentType: 'application/json',
                        dataType: 'json'
                    }).done(function (data) {
                        var input = data.result.data.listPages,
                            regex = /src="([^"]+)"/gi,
                            matches, output = [];

                        // eslint-disable-next-line no-cond-assign
                        while (matches = regex.exec(input)) {
                            output.push(decodeURIComponent(matches[1]));
                        }
                        saveToClipboard(output);
                    });
                } else {
                    configs.contents = '#containerListPage';
                    getContents($data);
                }
            }
        });
    }

    function getTruyenTranhTuan() {
        getSource(function ($data) {
            var $entry = $data.find('#read-title').next('script');
            if (!$entry.length) {
                notyImages();
            } else {
                $data = $entry.text().match(/slides_page_url_path\s=\s([^;]+);/)[1];
                $data = JSON.parse($data);
                saveToClipboard($data);
            }
        });
    }

    function getDamMeTruyen() {
        $(window).load(function () {
            notyReady();

            $(configs.link).on('contextmenu', function (e) {
                e.preventDefault();
                if (!oneProgress()) return;

                var $this = $(this);
                chapName = $this.text();
                notyWait();

                $.get('/truyen/gen_html_chapter/' + $('[name="book_id"]').val() + '/' + this.href.match(/\/chap-(.+)\.html$/)[1]).done(function (data) {
                    saveToClipboard(data.match(/https?:\/\/[^"']+/gi));
                }).fail(function () {
                    notyError();
                });
            });
        });
    }

    function getGocCay() {
        notyReady();

        $(configs.link).on('contextmenu', function (e) {
            e.preventDefault();
            if (!oneProgress()) return;

            var $this = $(this);
            chapName = $this.text();
            notyWait();

            GM_xmlhttpRequest({
                method: 'GET',
                url: $this.attr('href'),
                onload: function (response) {
                    response = response.responseText.replace(/<img [^>]*src=['"]([^'"]+)[^>]*>/gi, function (match, capture) {
                        return '<img data-src="' + capture + '" />';
                    });

                    var $entry = $(response).find('.entry-content img');
                    if (!$entry.length) {
                        notyImages();
                    } else {
                        getImages($(response).find('.entry-content img'));
                    }
                },
                onerror: function () {
                    notyError();
                }
            });
        });
    }

    function getTuoiThoDuDoi() {
        notyReady();

        configs.contents = '.detail-list-chapter';
        $(configs.link).not('.zzbaivong').on('contextmenu', function (e) {
            e.preventDefault();
            if (!oneProgress()) return;

            rightClickEvent(this);
        }).addClass('zzbaivong');
    }

    function getTruyenHay24h() {
        getSource(function ($data) {
            $data = $data.find('#dvContentChap').siblings('script').text();
            $data = $data.match(/GI2\(([^)]+)\);/)[1];
            $data = $data.split(/[,']+/);

            $.post('http://truyenhay24h.com/TH24Service.asmx/GetChapterImages', {
                PID: $data[0],
                ChapNumber: $data[1],
                cc: $data[2]
            }).done(function (response) {
                var images = [];
                $(response).find('string').each(function (i, v) {
                    images[i] = v.textContent;
                });

                saveToClipboard(images);
            }).fail(function () {
                notyError();
            });
        });
    }

    function getThichTruyenTranh() {
        getSource(function ($data) {
            $data = $data.find('#content_read').next('script').text();
            $data = $data.match(/https?:\/\/[^"]+/g);
            if (!$data.length) {
                notyImages();
            } else {
                saveToClipboard($data);
            }
        });
    }

    function getTruyen1() {
        notyReady();

        $(configs.link).on('contextmenu', function (e) {
            e.preventDefault();
            if (!oneProgress()) return;

            var $this = $(this);
            chapName = $('h1.title').text().trim() + ' ' + $this.text().trim();
            notyWait();

            var chapKey = $this.attr('href').match(/\/(\d+)\/[^/]+$/);
            if (!chapKey) {
                notyError();
                return;
            }
            chapKey = chapKey[1];

            $.ajax({
                url: '/MainHandler.ashx',
                type: 'POST',
                dataType: 'json',
                data: JSON.stringify({
                    'id': 2,
                    'method': 'getChapter',
                    'params': [chapKey]
                }),
                contentType: 'application/json'
            }).done(function (response) {
                if (!response.result) {
                    notyError();
                    return;
                }
                if (response.result.hasErrors) {
                    notyImages();
                    return;
                }
                var data = response.result.data.listPages.match(/https?:\/\/[^"]+/g);
                if (!data.length) {
                    notyImages();
                } else {
                    saveToClipboard(data);
                }
            }).fail(function () {
                notyError();
            });
        });
    }

    function getTruyenTranhMoi() {
        notyReady();

        $(configs.link).on('contextmenu', function (e) {
            e.preventDefault();
            if (!oneProgress()) return;

            var $this = $(this),
                name = configs.name;

            if (!name) {
                chapName = $this.text();
            } else if (typeof name === 'function') {
                chapName = name(this);
            }
            chapName = $.trim(chapName);

            notyWait();

            GM_xmlhttpRequest({
                method: 'GET',
                url: $this.attr('href'),
                onload: function (response) {
                    response = response.responseText.replace(/<img [^>]*src=['"]([^'"]+)[^>]*>/gi, function (match, capture) {
                        return '<img data-src="' + capture + '" />';
                    });

                    var $entry = $(response).find('.image-chap img');
                    if (!$entry.length) {
                        notyImages();
                    } else {
                        getImages($entry);
                    }
                },
                onerror: function () {
                    notyError();
                }
            });
        });
    }

    function getTtManga() {
        getSource(function ($data) {
            var data = $data.find('#divImage').siblings('script').first().text();
            if (!/lstImages\.push\("([^"]+)"\)/.test(data)) {
                notyImages();
            } else {
                var regex = /lstImages\.push\("([^"]+)"\)/gi,
                    matches, output = [];

                // eslint-disable-next-line no-cond-assign
                while (matches = regex.exec(data)) {
                    output.push(decodeURIComponent(matches[1]));
                }
                saveToClipboard(output);
            }
        });
    }

    var configs = {
            link: '',
            name: '',
            contents: ''
        },
        chapName,
        $noty = [],
        notyTimeout,
        indexPage = true,
        domainName = location.host,
        tit = document.title,

        dlZip = new JSZip(),
        dlPrevZip = false,
        dlCurrent = 0,
        dlFinal = 0,
        dlTotal = 0,
        dlImages = [],
        
        inProgress = false;

    GM_addStyle('#baivong_noty_wrap{display:none;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}#baivong_noty_content{color:#444}#baivong_noty_content strong{font-weight:700}#baivong_noty_content.baivong_info strong{color:#2196f3}#baivong_noty_content.baivong_success strong{color:#4caf50}#baivong_noty_content.baivong_warning strong{color:#ffc107}#baivong_noty_content.baivong_error strong{color:#f44336}#baivong_noty_content strong.centered{display:block;text-align:center}#baivong_noty_close{position:absolute;right:0;top:0;font-size:18px;color:#ddd;height:20px;width:20px;line-height:20px;text-align:center}#baivong_noty_wrap:hover #baivong_noty_close{color:#333}');

    if (location.pathname !== '/') indexPage = false;

    domainName = domainName.split('.');
    domainName = domainName.length === 2 ? domainName[0] : (domainName[1] === 'com' || domainName[1] === 'net' || domainName[1] === 'co' ? domainName[0] : domainName[1]);

    switch (domainName) {
    case 'truyentranh8':
        configs = {
            link: '#ChapList a',
            name: 'span, strong, h2',
            contents: ''
        };
        if (indexPage) {
            configs.link = '.truyen ul a';
            configs.name = '';
        }
        getTruyenTranh8();
        break;
    case 'iutruyentranh':
        configs = {
            link: '#chaplist a',
            contents: ''
        };
        if (indexPage) {
            configs.link = 'ul.chaplist a';
        }
        getIuTruyenTranh();
        break;
    case 'truyentranh':
        configs = {
            link: '.content a, .chapter-list a',
            name: function (_this) {
                return _this.title;
            },
            contents: '.paddfixboth-mobile'
        };
        if (indexPage) {
            configs.link = '.hotup-list a';
            configs.name = function (_this) {
                return _this.title;
            };
        }
        break;
    case 'manga24h':
        configs = {
            link: '.chap_name a',
            name: function (_this) {
                return $('h1').text() + ' ' + $(_this).text();
            },
            contents: '#chapcontent'
        };
        break;
    case 'comicvn':
        configs = {
            link: '.manga-chapter a',
            name: function (_this) {
                return $('#site-title').text() + ' ' + $(_this).text();
            },
            contents: ''
        };
        if (indexPage) {
            configs.link = '.chapter a';
            configs.name = function (_this) {
                var $this = $(_this);
                return $this.closest('.item').find('.tit a').text() + ' ' + $this.text();
            };
        }
        getComicVn();
        break;
    case 'hamtruyen':
        configs = {
            link: '.tenChapter a',
            name: function (_this) {
                return $('.tentruyen').text() + ' ' + $(_this).text();
            },
            contents: '#content_chap'
        };
        break;
    case 'ntruyentranh':
    case 'ntruyen':
        configs = {
            link: '.cellChapter a',
            name: function (_this) {
                return $('h1').text() + ' ' + $(_this).text();
            },
            contents: ''
        };
        getNtruyen();
        break;
    case 'a3manga':
        configs = {
            link: '.table-striped a',
            contents: '.view-chapter',
            filter: true
        };
        if (indexPage) {
            configs.link = '.comic-title-link a:not([title])';
            configs.name = function (_this) {
                var $this = $(_this);
                return $this.prev().attr('title') + ' ' + $this.text();
            };
        }
        break;
    case 'truyenv1':
        configs = {
            link: '.post-category1 a',
            contents: '.entry-content'
        };
        break;
    case 'truyentranhtuan':
        configs = {
            link: '.chapter-name a',
            contents: ''
        };
        getTruyenTranhTuan();
        break;
    case 'mangak':
        configs = {
            link: '.chapter-list a',
            contents: '.vung_doc'
        };
        if (indexPage || !location.pathname.indexOf('/moi-cap-nhat/')) {
            configs.link = '.update_item > a';
            configs.name = function (_this) {
                return _this.title;
            };
        }
        break;
    case 'truyentranhmoi':
    case '1.truyentranhmoi':
        configs = {
            link: '.chap-list a',
            contents: ''
        };
        if (indexPage) {
            configs.link = '.update-item .chapter';
            configs.name = function (_this) {
                return _this.title;
            };
        }
        getTruyenTranhMoi();
        break;
    case 'dammetruyen':
        configs = {
            link: '.val a',
            contents: ''
        };
        getDamMeTruyen();
        break;
    case 'goccay':
        configs = {
            link: 'table a',
            contents: ''
        };
        getGocCay();
        break;
    case 'tuoithodudoi':
        configs = {
            link: '.chap-name-link',
            name: function (_this) {
                return $('.comic-name h2').text() + ' ' + $(_this).text();
            },
            contents: ''
        };
        if (indexPage) {
            configs.link = '.update a';
            configs.contents = '.detail-list-chapter';
            configs.name = function (_this) {
                var $this = $(_this);
                return $this.closest('.content-comic').find('.info-comic h4').text() + ' ' + $this.text();
            };
        } else {
            waitForKeyElements('.chap-name-link', getTuoiThoDuDoi);
        }
        break;
    case 'truyentranhlh':
        configs = {
            link: '#tab-chapper a',
            contents: '.chapter-content',
            filter: true
        };
        break;
    case 'hocvientruyentranh':
        configs = {
            link: '.table-scroll a',
            name: function (_this) {
                return $('.__name').text() + ' ' + $(_this).text();
            },
            contents: '.manga-container'
        };
        break;
    case 'truyenhay24h':
        configs = {
            link: '.chapname a',
            name: function (_this) {
                return $.trim($('.name_sp').text()) + ' ' + $(_this).text();
            },
            contents: ''
        };
        getTruyenHay24h();
        break;
    case 'uptruyen':
        configs = {
            link: '#chapter_table a',
            contents: '#reader-box'
        };
        break;
    case 'thichtruyentranh':
        configs = {
            link: '.ul_listchap a',
            contents: ''
        };
        getThichTruyenTranh();
        break;
    case 'truyen1':
        configs = {
            link: '#MainContent_CenterContent_detailStoryControl_listChapter a',
            contents: ''
        };
        getTruyen1();
        break;
    case 'truyentranhtop':
        configs = {
            link: '.detailStory-table .table a',
            contents: '#content p'
        };
        break;
    case 'bigtruyen':
        configs = {
            link: '.chapter-list a',
            contents: '#chapter-content'
        };
        if (indexPage) configs.link = '#contentstory .chapter';
        break;
    case 'hentailx':
        configs = {
            link: '.item_chap .chap-link',
            contents: '#content_chap',
            filter: true
        };
        break;
    case 'hentaivn':
        configs = {
            link: '.chuong_td > a',
            contents: '#image'
        };
        break;
    case 'catscomic':
        configs = {
            link: '.chapter-list a',
            contents: '#chapter-content'
        };
        break;
    case 'otakusan':
        configs = {
            link: '.read-chapter a',
            contents: '#chapter-container'
        };
        break;
    case 'ngonphongcomics':
        configs = {
            link: '.comic-intro .table-striped a',
            contents: '.view-chapter',
            filter: true
        };
        break;
    case 'nettruyen':
        configs = {
            link: '#nt_listchapter a',
            contents: '.reading-detail.box_doc'
        };
        break;
    case 'hamtruyentranh':
        configs = {
            link: '#examples a',
            name: function (_this) {
                var $this = $(_this);
                $this.find('span').remove();
                return $(_this).text().trim();
            },
            contents: '.each-page'
        };
        break;
    case 'truyensieuhay':
        configs = {
            link: '#chapter-list-flag a',
            contents: '#content_chap'
        };
        break;
    case 'gocthugian':
        configs = {
            link: '.ChI a',
            contents: '#ctl14_PC'
        };
        break;
    case 'ttmanga':
        configs = {
            link: '#list-chapter a',
            contents: ''
        };
        getTtManga();
        break;
    default:
        configs = {
            contents: ''
        };
        break;
    }

    if (configs.contents !== '') getSource();

})(jQuery, window, document);