Qimao

Qimao Novel Reading

You will need to install an extension such as Tampermonkey, Greasemonkey or Violentmonkey to install this script.

You will need to install an extension such as Tampermonkey to install this script.

You will need to install an extension such as Tampermonkey or Violentmonkey to install this script.

You will need to install an extension such as Tampermonkey or Userscripts to install this script.

You will need to install an extension such as Tampermonkey to install this script.

You will need to install a user script manager extension to install this script.

(У мене вже є менеджер скриптів, дайте мені встановити його!)

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

(I already have a user style manager, let me install it!)

// ==UserScript==
// @name              Qimao
// @version           2.0.+n.3
// @description       Qimao Novel Reading
// @author            Me
// @match             https://api-bc.wtzw.com/*
// @match             https://www.qimao.com/*
// @require           https://greasyfork.org/scripts/479459-cryptojs/code/CryptoJS-.js?version=1277994
// @require           https://cdnjs.cloudflare.com/ajax/libs/crypto-js/3.1.9-1/crypto-js.js
// @require           https://cdnjs.cloudflare.com/ajax/libs/FileSaver.js/2.0.5/FileSaver.min.js
// @license           MIT
// @grant             GM_xmlhttpRequest
// @grant             GM_addStyle
// @namespace https://greasyfork.org/users/682326
// ==/UserScript==

(function() {
    // Helper functions for MD5 hash
    var rotateLeft = function(lValue, iShiftBits) {
        return (lValue << iShiftBits) | (lValue >>> (32 - iShiftBits));
    };

    var addUnsigned = function(lX, lY) {
        var lX4, lY4, lX8, lY8, lResult;
        lX8 = (lX & 0x80000000);
        lY8 = (lY & 0x80000000);
        lX4 = (lX & 0x40000000);
        lY4 = (lY & 0x40000000);
        lResult = (lX & 0x3FFFFFFF) + (lY & 0x3FFFFFFF);
        if (lX4 & lY4) return (lResult ^ 0x80000000 ^ lX8 ^ lY8);
        if (lX4 | lY4) {
            if (lResult & 0x40000000) return (lResult ^ 0xC0000000 ^ lX8 ^ lY8);
            else return (lResult ^ 0x40000000 ^ lX8 ^ lY8);
        } else {
            return lResult ^ lX8 ^ lY8;
        }
    };

    var F = function(x, y, z) { return (x & y) | ((~x) & z); };
    var G = function(x, y, z) { return (x & z) | (y & (~z)); };
    var H = function(x, y, z) { return (x ^ y ^ z); };
    var I = function(x, y, z) { return (y ^ (x | (~z))); };

    var FF = function(a, b, c, d, x, s, ac) {
        a = addUnsigned(a, addUnsigned(addUnsigned(F(b, c, d), x), ac));
        return addUnsigned(rotateLeft(a, s), b);
    };

    var GG = function(a, b, c, d, x, s, ac) {
        a = addUnsigned(a, addUnsigned(addUnsigned(G(b, c, d), x), ac));
        return addUnsigned(rotateLeft(a, s), b);
    };

    var HH = function(a, b, c, d, x, s, ac) {
        a = addUnsigned(a, addUnsigned(addUnsigned(H(b, c, d), x), ac));
        return addUnsigned(rotateLeft(a, s), b);
    };

    var II = function(a, b, c, d, x, s, ac) {
        a = addUnsigned(a, addUnsigned(addUnsigned(I(b, c, d), x), ac));
        return addUnsigned(rotateLeft(a, s), b);
    };

    var convertToWordArray = function(string) {
        var lWordCount;
        var lMessageLength = string.length;
        var lNumberOfWordsTempOne = lMessageLength + 8;
        var lNumberOfWordsTempTwo = (lNumberOfWordsTempOne - (lNumberOfWordsTempOne % 64)) / 64;
        var lNumberOfWords = (lNumberOfWordsTempTwo + 1) * 16;
        var lWordArray = Array(lNumberOfWords - 1);
        var lBytePosition = 0;
        var lByteCount = 0;
        while (lByteCount < lMessageLength) {
            lWordCount = (lByteCount - (lByteCount % 4)) / 4;
            lBytePosition = (lByteCount % 4) * 8;
            lWordArray[lWordCount] = (lWordArray[lWordCount] | (string.charCodeAt(lByteCount) << lBytePosition));
            lByteCount++;
        }
        lWordCount = (lByteCount - (lByteCount % 4)) / 4;
        lBytePosition = (lByteCount % 4) * 8;
        lWordArray[lWordCount] = lWordArray[lWordCount] | (0x80 << lBytePosition);
        lWordArray[lNumberOfWords - 2] = lMessageLength << 3;
        lWordArray[lNumberOfWords - 1] = lMessageLength >>> 29;
        return lWordArray;
    };

    var wordToHex = function(lValue) {
        var WordToHexValue = "",
            WordToHexValueTemp = "",
            lByte, lCount;
        for (lCount = 0; lCount <= 3; lCount++) {
            lByte = (lValue >>> (lCount * 8)) & 255;
            WordToHexValueTemp = "0" + lByte.toString(16);
            WordToHexValue = WordToHexValue + WordToHexValueTemp.substr(WordToHexValueTemp.length - 2, 2);
        }
        return WordToHexValue;
    };

    var uTF8Encode = function(string) {
        string = string.replace(/\x0d\x0a/g, "\x0a");
        var output = "";
        for (var n = 0; n < string.length; n++) {
            var c = string.charCodeAt(n);
            if (c < 128) {
                output += String.fromCharCode(c);
            } else if ((c > 127) && (c < 2048)) {
                output += String.fromCharCode((c >> 6) | 192);
                output += String.fromCharCode((c & 63) | 128);
            } else {
                output += String.fromCharCode((c >> 12) | 224);
                output += String.fromCharCode(((c >> 6) & 63) | 128);
                output += String.fromCharCode((c & 63) | 128);
            }
        }
        return output;
    };

    function md5(string) {
        var x = Array();
        var k, AA, BB, CC, DD, a, b, c, d;
        var S11 = 7, S12 = 12, S13 = 17, S14 = 22;
        var S21 = 5, S22 = 9, S23 = 14, S24 = 20;
        var S31 = 4, S32 = 11, S33 = 16, S34 = 23;
        var S41 = 6, S42 = 10, S43 = 15, S44 = 21;
        string = uTF8Encode(string);
        x = convertToWordArray(string);
        a = 0x67452301;
        b = 0xEFCDAB89;
        c = 0x98BADCFE;
        d = 0x10325476;
        for (k = 0; k < x.length; k += 16) {
            AA = a;
            BB = b;
            CC = c;
            DD = d;
            a = FF(a, b, c, d, x[k + 0], S11, 0xD76AA478);
            d = FF(d, a, b, c, x[k + 1], S12, 0xE8C7B756);
            c = FF(c, d, a, b, x[k + 2], S13, 0x242070DB);
            b = FF(b, c, d, a, x[k + 3], S14, 0xC1BDCEEE);
            a = FF(a, b, c, d, x[k + 4], S11, 0xF57C0FAF);
            d = FF(d, a, b, c, x[k + 5], S12, 0x4787C62A);
            c = FF(c, d, a, b, x[k + 6], S13, 0xA8304613);
            b = FF(b, c, d, a, x[k + 7], S14, 0xFD469501);
            a = FF(a, b, c, d, x[k + 8], S11, 0x698098D8);
            d = FF(d, a, b, c, x[k + 9], S12, 0x8B44F7AF);
            c = FF(c, d, a, b, x[k + 10], S13, 0xFFFF5BB1);
            b = FF(b, c, d, a, x[k + 11], S14, 0x895CD7BE);
            a = FF(a, b, c, d, x[k + 12], S11, 0x6B901122);
            d = FF(d, a, b, c, x[k + 13], S12, 0xFD987193);
            c = FF(c, d, a, b, x[k + 14], S13, 0xA679438E);
            b = FF(b, c, d, a, x[k + 15], S14, 0x49B40821);
            a = GG(a, b, c, d, x[k + 1], S21, 0xF61E2562);
            d = GG(d, a, b, c, x[k + 6], S22, 0xC040B340);
            c = GG(c, d, a, b, x[k + 11], S23, 0x265E5A51);
            b = GG(b, c, d, a, x[k + 0], S24, 0xE9B6C7AA);
            a = GG(a, b, c, d, x[k + 5], S21, 0xD62F105D);
            d = GG(d, a, b, c, x[k + 10], S22, 0x2441453);
            c = GG(c, d, a, b, x[k + 15], S23, 0xD8A1E681);
            b = GG(b, c, d, a, x[k + 4], S24, 0xE7D3FBC8);
            a = GG(a, b, c, d, x[k + 9], S21, 0x21E1CDE6);
            d = GG(d, a, b, c, x[k + 14], S22, 0xC33707D6);
            c = GG(c, d, a, b, x[k + 3], S23, 0xF4D50D87);
            b = GG(b, c, d, a, x[k + 8], S24, 0x455A14ED);
            a = GG(a, b, c, d, x[k + 13], S21, 0xA9E3E905);
            d = GG(d, a, b, c, x[k + 2], S22, 0xFCEFA3F8);
            c = GG(c, d, a, b, x[k + 7], S23, 0x676F02D9);
            b = GG(b, c, d, a, x[k + 12], S24, 0x8D2A4C8A);
            a = HH(a, b, c, d, x[k + 5], S31, 0xFFFA3942);
            d = HH(d, a, b, c, x[k + 8], S32, 0x8771F681);
            c = HH(c, d, a, b, x[k + 11], S33, 0x6D9D6122);
            b = HH(b, c, d, a, x[k + 14], S34, 0xFDE5380C);
            a = HH(a, b, c, d, x[k + 1], S31, 0xA4BEEA44);
            d = HH(d, a, b, c, x[k + 4], S32, 0x4BDECFA9);
            c = HH(c, d, a, b, x[k + 7], S33, 0xF6BB4B60);
            b = HH(b, c, d, a, x[k + 10], S34, 0xBEBFBC70);
            a = HH(a, b, c, d, x[k + 13], S31, 0x289B7EC6);
            d = HH(d, a, b, c, x[k + 0], S32, 0xEAA127FA);
            c = HH(c, d, a, b, x[k + 3], S33, 0xD4EF3085);
            b = HH(b, c, d, a, x[k + 6], S34, 0x4881D05);
            a = HH(a, b, c, d, x[k + 9], S31, 0xD9D4D039);
            d = HH(d, a, b, c, x[k + 12], S32, 0xE6DB99E5);
            c = HH(c, d, a, b, x[k + 15], S33, 0x1FA27CF8);
            b = HH(b, c, d, a, x[k + 2], S34, 0xC4AC5665);
            a = II(a, b, c, d, x[k + 0], S41, 0xF4292244);
            d = II(d, a, b, c, x[k + 7], S42, 0x432AFF97);
            c = II(c, d, a, b, x[k + 14], S43, 0xAB9423A7);
            b = II(b, c, d, a, x[k + 5], S44, 0xFC93A039);
            a = II(a, b, c, d, x[k + 12], S41, 0x655B59C3);
            d = II(d, a, b, c, x[k + 3], S42, 0x8F0CCC92);
            c = II(c, d, a, b, x[k + 10], S43, 0xFFEFF47D);
            b = II(b, c, d, a, x[k + 1], S44, 0x85845DD1);
            a = II(a, b, c, d, x[k + 8], S41, 0x6FA87E4F);
            d = II(d, a, b, c, x[k + 15], S42, 0xFE2CE6E0);
            c = II(c, d, a, b, x[k + 6], S43, 0xA3014314);
            b = II(b, c, d, a, x[k + 13], S44, 0x4E0811A1);
            a = II(a, b, c, d, x[k + 4], S41, 0xF7537E82);
            d = II(d, a, b, c, x[k + 11], S42, 0xBD3AF235);
            c = II(c, d, a, b, x[k + 2], S43, 0x2AD7D2BB);
            b = II(b, c, d, a, x[k + 9], S44, 0xEB86D391);
            a = addUnsigned(a, AA);
            b = addUnsigned(b, BB);
            c = addUnsigned(c, CC);
            d = addUnsigned(d, DD);
        }
        var tempValue = wordToHex(a) + wordToHex(b) + wordToHex(c) + wordToHex(d);
        return tempValue.toLowerCase();
    }

    async function decode(response) {
        let $ = response;
        let txt = CryptoJS.enc.Base64.parse($.data.content).toString();
        let iv = txt.slice(0, 32);
        let _content = await decrypt(txt.slice(32), iv).trim().replace(/\n/g, '<br>');
        return await _content;
    }

    const decrypt = function(data, iv) {
        let key = CryptoJS.enc.Hex.parse('32343263636238323330643730396531');
        iv = CryptoJS.enc.Hex.parse(iv);
        let HexStr = CryptoJS.enc.Hex.parse(data);
        let Base64Str = CryptoJS.enc.Base64.stringify(HexStr);
        let decrypted = CryptoJS.AES.decrypt(Base64Str, key, {
            iv: iv,
            mode: CryptoJS.mode.CBC,
            padding: CryptoJS.pad.Pkcs7
        });
        return decrypted.toString(CryptoJS.enc.Utf8);
    };

    async function novel(murl, func) {
        const matchb = murl.match(/https:\/\/www\.qimao\.com\/shuku\/([0-9_]+)-([0-9]+)\//);
        const toparams = obj => Object.keys(obj).map(key => `${encodeURIComponent(key)}=${encodeURIComponent(obj[key])}`).join('&');
        const sign_key = 'd3dGiJc651gSQ8w1';

        const params = {
            id: matchb[1],
            chapterId: matchb[2]
        };
        const paramSign = md5(Object.keys(params).sort().reduce((pre, n) => pre + n + '=' + params[n], '') + sign_key);
        params['sign'] = paramSign;

        const headers = {
            "app-version": "51110",
            "platform": "android",
            "reg": "0",
            "AUTHORIZATION": "",
            "application-id": "com.****.reader",
            "net-env": "1",
            "channel": "unknown",
            "qm-params": ""
        };
        const headersSign = md5(Object.keys(headers).sort().reduce((pre, n) => pre + n + '=' + headers[n], '') + sign_key);
        headers['sign'] = headersSign;

        const url = "https://api-ks.wtzw.com/api/v1/chapter/content?" + toparams(params);

        console.log(url, headers);
        return new Promise((resolve, reject) => {
            GM_xmlhttpRequest({
                method: "GET",
                url: url,
                headers: headers,
                onload: async function(response) {
                    const data = JSON.parse(response.responseText);
                    const get = (await decode(data)).replace(/<br>/g, '\n');
                    await func(get);
                    resolve();
                },
                ontimeout: async function() {
                    return await func(murl, func = func);
                },
                onerror: async function(response) {
                    func('err');
                    throw new Error(JSON.stringify(response));
                    reject();
                }
            });
        });
    }

    const murl = window.location.href;

    const mode =
          /https:\/\/www\.qimao\.com\/shuku\/([0-9_]+)-([0-9]+)\//.test(murl) ? 'reader' :
    /https:\/\/www\.qimao\.com\/shuku\/([0-9_]+)\//.test(murl) ? 'page' : null;

    switch (mode) {
        case 'reader':
            GM_addStyle(`
                .reader-fixed-left li.go-back[data-v-b5fc2672] {
                    border-radius: 5px !important;
                    background-color: #f1e5af36 !important;
                }
                .reader-layout-theme[data-theme=default] .reader-fixed-left li.reader-guide[data-v-b5fc2672] {
                    margin-top: 0px !important;
                    background-color: #f1e5af36 !important;
                }
                .reader-header-con {
                    display: none !important;
                }
                #__layout div div.wrapper.reader.reader-layout-theme div.reader-header div.reader-header-con {
                    display: none !important;
                }
                .qm-fixed-right-item click reader-phone {
                    display: none !important;
                }
                .s-tit {
                    display: none !important;
                }
                .chapter-tips > * {
                    color: #66666647 !important;
                    text-align: left !important;
                }
                .chapter-title {
                    text-align: left !important;
                }
                .chapter-detail-wrap-info {
                    border-bottom: 1px solid #ddd !important;
                }
                .qm-fixed-right-link {
                    width: fit-content !important;
                }
                .qm-fixed-right.type-3 .qm-fixed-right-link[data-v-754474f8]:before {
                    width: 8px !important;
                }
                .qm-fixed-right[data-v-754474f8] {
                    left: 51% !important;
                }
                .i-arrow {
                    border-radius: 5px;
                }
                .reader-setting, .book-catalog {
                    border-radius: 10px !important;
                    outline: 2px solid #0000000a;
                }
                .reader-login-code {
                    display: none !important;
                }
                .show-part[data-v-10da8d56]:after {
                    display: none !important;
                }
                .chapter-detail-article p {
                    font-size: 18px;
                    text-indent: 2em;
                    line-height: 1.8;
                    padding: 13px 0;
                    user-select: text;
                }
                .article {
                    user-select: text;
                }
                body {
                    user-select: text;
                }
            `);
            novel(murl, func = (get) => {
                if (get === 'err') return;
                document.getElementsByClassName('chapter-detail-article')[0].innerHTML = '<p>' + get.replace(/\n/g, "</p><p>") + '</p>';
            });
            break;

        case 'page':
            async function startdownload() {
                GM_addStyle(`
        .tab-inner {
            display: none !important;
        }
    `);
                document.querySelector("#__layout > div > div.wrapper > div > div > div > div.book-detail-body > div > div.qm-tab.type-2.normal > ul > li:nth-child(1) > div").click();
                await new Promise(resolve => setTimeout(resolve, 120));

                var content = '使用七猫全文在线免费读下载\n' +
                    ['title', 'tags-wrap', 'sub-title', 'statistics-wrap', 'update-info', 'book-introduction-item']
                .map(e => document.getElementsByClassName(e)[0].textContent + '\n')
                .join()
                .replace(/\n\s*(?!0\b)/g, '')
                .replace(/,/g, '\n');

                for (const e of ['title', 'tags-wrap', 'sub-title', 'statistics-wrap', 'update-info', 'book-introduction-item']) {
                    document.getElementsByClassName(e)[0].classList.add('succeed');
                    await new Promise(resolve => setTimeout(resolve, 120));
                }

                document.querySelector("#__layout > div > div.wrapper > div > div > div > div.book-detail-body > div > div.qm-tab.type-2.normal > ul > li:nth-child(2) > div").click();
                await new Promise(resolve => setTimeout(resolve, 120));

                const elements = document.getElementsByClassName('qm-book-catalog-list-content')[0].children;

                let a, span;
                let startChapter = prompt("Nhập tên chương bạn muốn bắt đầu tải từ:", "1");
                let start = false;

                for (var i = 0; i < elements.length; i++) {
                    a = elements[i].querySelector('a');
                    span = a.querySelector('.txt');

                    // Kiểm tra nếu đã đến chương bắt đầu
                    if (span.innerText.includes(startChapter)) {
                        start = true;
                    }

                    // Nếu chưa đến chương bắt đầu, bỏ qua
                    if (!start) {
                        continue;
                    }

                    span.classList.add('processing');
                    content += '\n\n' + span.innerText + '\n';

                    await novel(a.href, func = (c) => {
                        span.classList.remove('processing');
                        if (c === 'err') {
                            span.classList.add('err');
                            content += '\nChương tiết sai lầm\n';
                        }
                        span.classList.add('succeed');
                        content += c;
                    });
                }

                const blob = new Blob([content], { type: "text/plain;charset=utf-8" });
                saveAs(blob, document.getElementsByClassName('title')[0].querySelector('span').innerText + ".txt");
            }


            GM_addStyle(`
                #__layout > div > div.wrapper > div > div > div > div.book-detail-header > div > div.book-information.clearfix.left > div.wrap-txt > div.btns-wrap.clearfix > div > div.qm-popper-title > span {
                    display: none !important;
                }
                .err {
                    background-color: pink;
                }
                .succeed {
                    background-color: #D2F9D1;
                    border-radius: 5px;
                }
                .processing {
                    background-color: navajowhite;
                    border-radius: 5px;
                }
                .qm-book-catalog-list-content li {
                    line-height: normal !important;
                    padding: 18px;
                }
                .qm-book-catalog-list-content li .txt {
                    width: auto !important;
                }
                .qm-book-catalog-list-content li .vip-icon {
                    float: right !important;
                    margin-top: 0px !important;
                }
            `);

            const menu = document.querySelector("#__layout > div > div.wrapper > div > div > div > div.book-detail-header > div > div.book-information.clearfix.left > div.wrap-txt > div.btns-wrap.clearfix");
            const copy = document.querySelector("#__layout > div > div.wrapper > div > div > div > div.book-detail-header > div > div.book-information.clearfix.left > div.wrap-txt > div.btns-wrap.clearfix > a.qm-btn.item.inline-block.default.large.radius");
            const btn = copy.cloneNode(true);
            menu.insertBefore(btn, copy.nextSibling);
            btn.innerText = 'Tải Text';
            btn.addEventListener('click', startdownload);
            break;
    }
})();