Greasy Fork is available in English.

刺猬猫小说下载

刺猬猫小说下载,全本下载,单章下载,暂不支持付费章节

// ==UserScript==
// @name         刺猬猫小说下载
// @namespace    http://tampermonkey.net/
// @version      1.55
// @description  刺猬猫小说下载,全本下载,单章下载,暂不支持付费章节
// @author       backrock12
// @match        *://www.ciweimao.com/chapter-list/*
// @match        *://www.ciweimao.com/chapter/*
// @match        *://www.ciweimao.com/book/*
// @require      https://cdn.bootcss.com/html2canvas/0.5.0-beta4/html2canvas.min.js
// @require      https://cdn.jsdelivr.net/npm/[email protected]/FileSaver.min.js
// @grant        GM_xmlhttpRequest

// ==/UserScript==

(function () {
    'use strict';

    var DivInited = false, IsCancel = false;
    var WCContent, WCWords, WCQuit, WCSave, WCContinue;
    var title, info;
    var downlist = [];
    var infolist = [];
    var lhref;
    var vol = 1;
    var num = 0;
    var isimg = false;
    var isvip = false;

    function initDiv() {
        console.log("initDiv");
        if (DivInited) return;
        DivInited = true;
        var content = document.createElement("div");
        document.body.appendChild(content);
        content.outerHTML = `
      <div id="CWDownContent" style='display:none' >
        <div style="width:360px;height:100px;position:fixed;left:50%;top:50%;margin-top:-50px;margin-left:-180px;z-index:100000;background-color:#ffffff;border:1px solid #afb3b6;opacity:0.95;filter:alpha(opacity=95);box-shadow:5px 5px 20px 0px#000;">
          <div id="CWDownWords" style="font-size:12px;position:absolute;width:290px;height:90px;padding: 8px;border-radius: 10px;float: left;">
          </div>
          <div style="float: right;">
            <div id="CWDownSave" style="width:43px;height:26px;cursor: pointer;background-color:#3169da;margin: 5px 5px 3px 3px;">
              <span style="line-height:25px;display:block;color:#FFF;text-align:center;font-size:14px;">保存</span>
            </div>
            <div id="CWDownQuit" style="width:43px;height:26px;cursor: pointer;background-color:#3169da;margin: 3px;">
              <span style="line-height:25px;display:block;color:#FFF;text-align:center;font-size:14px;">取消</span>
            </div>
            <div id="CWCContinue" style="width:43px;height:26px;cursor: pointer;background-color:#3169da;margin: 3px;">
            <span style="line-height:25px;display:block;color:#FFF;text-align:center;font-size:14px;">繼續</span>
          </div>
          </div>
        </div>
      </div>
      `;

        WCContent = document.querySelector("#CWDownContent");
        WCWords = document.querySelector("#CWDownWords");
        WCQuit = document.querySelector("#CWDownQuit");
        WCSave = document.querySelector("#CWDownSave");
        WCContinue = document.querySelector("#CWCContinue");

        WCQuit.onclick = function () {
            IsCancel = true;
            DivInited = false;
            WCContent.style.display = "none";
            WCWords.innerHTML = '';
            WCContent.parentNode.removeChild(WCContent);
        };
        WCContinue.onclick = function () {
            if (downlist.length == 0) {
                Analysis();
            }
            else {
                continueDown();
            }
        };
        WCSave.onclick = function () {
            SaveText();
        };
    }

    function ShowWords(value) {
        WCWords.innerHTML = (title ? title + '<br>' : '') + value;
    }


    class Book {
        constructor(name, url, text, title_mk, lock, complete, status, iframeId) {
            this.name = name;
            this.url = url;
            this.text = text;
            this.title_mk = title_mk;
            this.lock = lock;
            this.complete = complete || false;
            this.status = status || 'Null';
            this.iframeId = iframeId
            this.num = num++
        }

        setstatus(status, text) {
            this.complete = true;
            this.status = status;
            if (text)
                this.text = text;
        }
        setiframeId(iframeId) {
            this.complete = false;
            this.status = 'Down';
            this.iframeId = iframeId;
        }
        setnull() {
            this.complete = false;
            this.status = 'Null';
            this.iframeId = '';
        }
        isDown() {
            return this.status == 'Down';
        }
        isEnd() {
            // return this.title_mk == false && this.complete && (this.iframeId || this.iframeId != '');
            return this.title_mk == false && this.complete;
        }
        isCan() {
            // return this.title_mk == false && this.complete == false && (!this.iframeId || this.iframeId == '');
            return this.title_mk == false && this.complete == false;
        }

    }

    function* cratebookmap(items) {
        for (let i of items) {
            if (i.isCan()) {
                yield i;
            }
        }
    }

    async function loopDown() {
        console.log('loopDown');
        let bookobj = cratebookmap(downlist);
        let result = bookobj.next();
        while (!result.done && !IsCancel) {
            if (bookobj)
                ShowWords(`共 ${downlist.length} 章节<br>已下载完成 ${result.value.num - 1} 章节,剩余 ${downlist.length - result.value.num} 章节<br>正在下载 ${result.value.num}`);
            
            if (!isvip && result.value.lock) {
                result.value.setstatus('OK', '\r\n' + result.value.name + '\r\n' + '付费章节暂时无法下载');
                console.log(result.value.num + '   vip not download');
            } else {
                await IframeInit(result.value);
                IframeClear(result.value);
            }
            result = bookobj.next();
        }
        if (result.done) {
            SaveText();
            //getdesc(SaveText)
        }
    }

    function continueDown() {
        console.log('continueDown');
        IsCancel = true;
        downlist.forEach((item) => {
            if (item.isDown()) {
                IframeClear(item);
                item.setnull();
            }
        });
        IsCancel = false;
        loopDown();
    }

    function IframeClear(bookobj) {
        console.log('IframeClear' + bookobj.iframeId);
        var ele = document.getElementById(bookobj.iframeId);

        try {
            ele.src = 'about:blank';
            ele.onload = null;
            ele.contentWindow.document.write('');
            ele.contentWindow.document.clear();
            ele.contentWindow.close();
            ele.parentNode.removeChild(ele);
            ele = null;
        } catch (e) {
            console.log('IframeClear' + e.message);
        }
        bookobj.iframeId = '';
    }

    async function IframeInit(bookobj) {

        return new Promise((resolve, reject) => {
            var iframeId = 'iframe_' + vol + '_' + bookobj.num;
            console.log('IframeInit' + iframeId);

            var ele1 = document.createElement('iframe');
            ele1.src = bookobj.url + '#Autodown';
            ele1.name = iframeId;
            ele1.id = iframeId;
            ele1.width = "195px";
            ele1.height = "126px";
            ele1.style.display = 'none';

            ele1.onload = function () {
                var frame = this;//document.getElementById(iframeId);
                if (frame) {
                    resolve(downtext(frame.contentDocument, bookobj));
                }
            }
            document.body.appendChild(ele1);
            bookobj.setiframeId(iframeId);

        });
    }


    async function getRequest() {
        let id = /www\.ciweimao\.com\/chapter-list\/(\d{5,})/.exec(location.href);
        if (!id) {
            throw "id is null";
        }
        let url = 'https://www.ciweimao.com/book/' + id[1]
        console.log(url);
        return new Promise(function (resolve, reject) {
            GM_xmlhttpRequest({
                method: 'GET',
                url: url,
                timeout: 3000,
                onerror: function (result) {
                    console.log('onerror');
                    console.log(result);
                },
                onload: function (result) {
                    resolve(result.response);
                }
            });
        });
    }



    function getdesc(fun) {
        try {
            console.log("getdesc");

            let id = /www\.ciweimao\.com\/chapter-list\/(\d{5,})\/book_detail/.exec(lhref);
            let url = 'https://www.ciweimao.com/book/' + id[1]
            console.log(url);
            GM_xmlhttpRequest({
                method: 'GET',
                url: url,
                timeout: 3000,
                onerror: function (result) {
                    console.log('onerror');
                    console.log(result);
                },
                onload: function (result) {
                    var str = result.response;
                    var doc = document.implementation.createHTMLDocument('');
                    doc.documentElement.innerHTML = str;
                    let desc = doc.querySelector(".book-desc").innerText;
                    let grade = doc.querySelector(".book-grade").innerText;
                    let update = doc.querySelector(".update-time").innerText;
                    let author = doc.querySelector("div.book-info h3.title span").innerText;
                    let title = doc.querySelector("div.book-info h3.title").innerText;
                    title = title.replace(author, '');

                    infolist.push(title);
                    infolist.push(author);
                    infolist.push(grade);
                    infolist.push(update);
                    infolist.push(desc.trim());
                    console.log(infolist);
                    fun();
                    return true;
                }
            });
        }
        catch (e) {
            return false;
        }
    }



    async function getdescasync() {
        try {
            console.log("getdesc");

            var str = await getRequest();
            var doc = document.implementation.createHTMLDocument('');
            doc.documentElement.innerHTML = str;
            let desc = doc.querySelector(".book-desc").innerText;
            let grade = doc.querySelector(".book-grade").innerText;
            let update = doc.querySelector(".update-time").innerText;
            let author = doc.querySelector("div.book-info h3.title span").innerText;
            let title = doc.querySelector("div.book-info h3.title").innerText;
            title = title.replace(author, '');

            infolist.push(title);
            infolist.push(author);
            infolist.push(grade);
            infolist.push(update);
            infolist.push(desc.trim());
            console.log(infolist);
            return true;
        }
        catch (e) {
            return false;
        }
    }


    function Analysis() {
        console.log("Analysis");
        IsCancel = false;
        title = '';
        info = '';
        downlist = [];
        infolist = [];

        initDiv();
        if (WCContent) {
            WCContent.style.display = "block";
            ShowWords(`分析网页中`);
        }

        if (!getdescasync()) {
            ShowWords(`获取文章信息失败`);
            return;
        }

        title = document.querySelector(".hd h3").innerText;

        for (const i of document.querySelectorAll(".hd p")) {
            info += "\r\n" + i.innerText;
        }
        console.log(title);

        for (const c of document.querySelectorAll(".book-chapter .book-chapter-box")) {
            var ctitle = c.querySelector('.sub-tit').innerText;
            downlist.push(
                new Book(ctitle, '', ctitle, true, false, true, 'OK')
            );
            for (const a of c.querySelectorAll('.book-chapter-list li a')) {
                if (a.querySelector('.icon-lock,.icon-unlock'))
                    var lock = true
                else
                    lock = false;
                downlist.push(
                    new Book(a.innerText, a.href, '', false, lock)
                );
            };
        };

        console.log(downlist);

        if (downlist.length == 0) {
            ShowWords(`分析网页失败`);
            return;
        }
        loopDown();
    }


    function SaveText() {
        var texts = [];
        WCContent.style.display = "block";
        if (downlist.length > 0 && infolist.length == 0)
            getdesc();

        var ok = 0, error = 0;
        downlist.forEach(function (c) {
            if (c.isEnd())
                ok++
            else if (c.title_mk && c.complete)
                error++;
            texts.push(c.text);
        });
        ShowWords(`已下载完成<br>共 ${downlist.length} 章节<br>成功 ${ok} 章节,失败 ${error} 章节 `);


        var blob = new Blob([infolist.join("\r\n\r\n"), "\r\n\r\n", texts.join("\r\n")], { type: "text/plain;charset=utf-8" });
        saveAs(blob, `${title}.txt`);
    }

    function getElementRootText(element) {
        let ret = "";
        for (const i of element.childNodes) {
            if (i.nodeType === i.TEXT_NODE) {
                ret += i.nodeValue;
            }
        }
        return ret.replace(/^\s+|\s+$/g, "");
    }

    function image2line(img) {

        var dataURL = GM_xmlhttpRequest({
            method: 'GET',
            url: img.src,
            responseType: "blob",
            onload: function (result) {
                var reader = new window.FileReader();
                reader.readAsDataURL(result.response);
                reader.onloadend = function () {
                    return reader.result;
                }
            }
        });

        return `![${img.alt}](${dataURL} "${img.title}")`;

        return;
        img.crossOrigin = 'Anonymous'
        html2canvas(img, {
            allowTaint: true,
            logging: false,
            useCORS: true,
        }
        ).then(function (canvas) {
            var dataUrl = canvas.toDataURL();
            console.log(dataUrl);
            console.log(canvas);

            var im = document.createElement("img");
            im.src = dataUrl;

            document.body.append(canvas);

        });


        return;
        img.crossOrigin = "Anonymous";
        var canvas = document.createElement("canvas");
        canvas.width = img.naturalWidth;
        canvas.height = img.naturalHeight;
        var ctx = canvas.getContext("2d");
        ctx.drawImage(img, 0, 0, img.width, img.height);
        var ext = img.src.substring(img.src.lastIndexOf(".") + 1).toLowerCase();
        console.log('img4');
        var dataURL = canvas.toDataURL("image/" + ext);
        console.log('img5');
        console.log(dataURL);

        return `![${img.alt}](${dataURL} "${img.title}")`;
    }

    async function getimagedata(img) {
        return new Promise((resolve, reject) => {
            GM_xmlhttpRequest({
                method: 'GET',
                url: img.src,
                responseType: "blob",
                onload: function (result) {
                    var reader = new window.FileReader();
                    reader.readAsDataURL(result.response);
                    reader.onloadend = function () {
                        resolve(`![${img.alt}](${reader.result} "${img.title}")`);
                    }
                }
            });
        });
    }


    async function image2lineasync(img) {
        return new Promise((resolve, reject) => {
            resolve(image2line(img));
        });
    }

    async function imageUrl2line(url) {
        return new Promise((resolve, reject) => {
            const img = new Image();
            img.onload = () => {
                resolve(image2line(img));
            };
            img.src = url;
            console.log(url);
        });
    }

    async function downtext(str, bookobj) {

        console.log('downtext');

        if (bookobj && bookobj.lock) {
            bookobj.setstatus('OK', '\r\n' + bookobj.name + '\r\n' + '付费章节暂时无法下载');
            return;
        }


        try {

            var doc = str;
            let time, num;
            let lines = [];

            let title = doc.querySelector("#J_BookCnt h3.chapter").firstChild.textContent;

            for (const i of doc.querySelectorAll("#J_BookCnt p span")) {
                if (i.textContent.indexOf("更新时间")) {
                    time = i.textContent;
                } else if (i.textContent.indexOf("字数")) {
                    num = i.textContent;
                }
            }

            lines.push('\r\n\r\n');
            lines.push(`# ${title}`);
            lines.push(`  ${time}`);
            lines.push(`  ${num}\r\n`);

            if ((bookobj && bookobj.lock) || doc.querySelectorAll("#J_BookImage").length > 0) {
                lines.push(`付费章节暂时无法下载`);
            } else {
                // 收费章节
                if (isvip)
                    for (const i of doc.querySelectorAll("#J_BookImage")) {
                        const url = i.style["background-image"].match(/(?:url\(")?(.+)(?:"\))?/)[1];
                        const line = await imageUrl2line(url);
                        lines.push(line);
                    }

                // 免费章节
                for (const i of doc.querySelectorAll("#J_BookRead p:not(.author_say)")) {
                    let line = getElementRootText(i);
                    lines.push(line);
                    if (isimg)
                        for (const img of i.querySelectorAll("img")) {
                            const line = await getimagedata(img);
                            lines.push(line);
                        }
                }

                // 作者说
                for (const i of doc.querySelectorAll("p.author_say")) {
                    let line = getElementRootText(i);
                    lines.push(`    ${line}`);
                    if (isimg)
                        for (const img of i.querySelectorAll("img")) {
                            const line = await getimagedata(img);
                            lines.push(line);
                        }
                }

                var blob = new Blob([lines.join("\r\n")], { type: "text/plain;charset=utf-8" });

                if (bookobj) {
                    bookobj.setstatus('OK', lines.join("\r\n"));
                } else {
                    IsCancel = false;
                    saveAs(blob, title + ".txt");
                }
                return true;
            }
        }
        catch (e) {
            if (bookobj) {
                bookobj.setstatus('ERROR ', bookobj.name + " 下载错误!");
                ShowWords(bookobj.text);
            }
            console.log('downtext ERROR ' + e.message);
            return false;
        }
    }





    function Inited() {
        if (location.hash && location.hash == '#Autodown') {
            console.log('Autodown');
        } else {
            if (location.hash && location.hash == '#Autoclick') {
                var autoclick = true;
            }

            console.log('Inited');
            let reg = new RegExp('www.ciweimao.com/book/*');
            if (reg.test(location.href)) {
                var b = document.querySelector(".btn-group");
                if (b) {
                    let id = location.href.substr(location.href.lastIndexOf('/') + 1);
                    let e = document.createElement('a');
                    e.id = 'BDownBtn';
                    e.textContent = '全本下载';
                    e.className = 'btn btn-lg btn-danger'
                    e.target = '_blank'
                    e.href = `https://www.ciweimao.com/chapter-list/${id}/book_detail#Autoclick`;
                    b.append(e);
                }
            }

            let t = document.querySelector(".hd");
            if (t) {
                let e = document.createElement('button');
                e.id = 'TDownBtn';
                e.textContent = '全本下载';
                e.className = 'btn btn-md btn-default'
                e.onclick = Analysis;
                t.append(e);
                if (autoclick)
                    Analysis();
            }

            let ct = document.querySelector(".read-hd");
            if (ct) {
                let ce = document.createElement('button');
                ce.id = 'CDownBtn';
                ce.textContent = '单章下载';
                ce.className = 'btn btn-md btn-default'
                ce.onclick = function () { downtext(document); };
                ct.append(ce);
            }
        }
    }
    Inited();
    //Analysis();

})();