Greasy Fork

Qimao

Qimao Novel Reading

当前为 2025-06-30 提交的版本,查看 最新版本

// ==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;
    }
})();