Wenku Doc Downloader

下载文档,导出PDF或图片压缩包。支持①百度文库②豆丁网③道客巴巴④360doc个人图书馆⑤得力文库⑥MBA智库⑦爱问共享资料(新浪文档)。在文档页面左侧中间有Wenku Doc Download按钮区,说明脚本生效了。【反馈请提供网址】。暂不支持手机端,手机端请切换为电脑UA访问。

Per 23-02-2022. Zie de nieuwste versie.

Voor het installeren van scripts heb je een extensie nodig, zoals Tampermonkey, Greasemonkey of Violentmonkey.

Voor het installeren van scripts heb je een extensie nodig, zoals Tampermonkey of Violentmonkey.

Voor het installeren van scripts heb je een extensie nodig, zoals Tampermonkey of Violentmonkey.

Voor het installeren van scripts heb je een extensie nodig, zoals Tampermonkey of Userscripts.

Voor het installeren van scripts heb je een extensie nodig, zoals {tampermonkey_link:Tampermonkey}.

Voor het installeren van scripts heb je een gebruikersscriptbeheerder nodig.

(Ik heb al een user script manager, laat me het downloaden!)

Voor het installeren van gebruikersstijlen heb je een extensie nodig, zoals {stylus_link:Stylus}.

Voor het installeren van gebruikersstijlen heb je een extensie nodig, zoals {stylus_link:Stylus}.

Voor het installeren van gebruikersstijlen heb je een extensie nodig, zoals {stylus_link:Stylus}.

Voor het installeren van gebruikersstijlen heb je een gebruikersstijlbeheerder nodig.

Voor het installeren van gebruikersstijlen heb je een gebruikersstijlbeheerder nodig.

Voor het installeren van gebruikersstijlen heb je een gebruikersstijlbeheerder nodig.

(Ik heb al een beheerder - laat me doorgaan met de installatie!)

// ==UserScript==
// @name         Wenku Doc Downloader
// @namespace    http://tampermonkey.net/
// @version      1.4.13
// @description  下载文档,导出PDF或图片压缩包。支持①百度文库②豆丁网③道客巴巴④360doc个人图书馆⑤得力文库⑥MBA智库⑦爱问共享资料(新浪文档)。在文档页面左侧中间有Wenku Doc Download按钮区,说明脚本生效了。【反馈请提供网址】。暂不支持手机端,手机端请切换为电脑UA访问。
// @author       [email protected]
// @match        *://*.docin.com/p-*
// @match        *://ishare.iask.sina.com.cn/f/*
// @match        *://www.deliwenku.com/p-*
// @match        *://www.doc88.com/p-*
// @match        *://www.360doc.com/content/*
// @match        *://wenku.baidu.com/*/*
// @match        *://doc.mbalib.com/view/*
// @match        *://www.woc88.com/so-*
// @require      https://cdn.bootcdn.net/ajax/libs/FileSaver.js/2.0.5/FileSaver.min.js
// @require      https://cdn.bootcdn.net/ajax/libs/jszip/3.7.1/jszip.min.js
// @require      https://cdn.bootcdn.net/ajax/libs/jspdf/2.3.1/jspdf.umd.min.js
// @require      https://cdn.bootcdn.net/ajax/libs/html2canvas/1.4.1/html2canvas.min.js
// @icon         https://s2.loli.net/2022/01/12/wc9je8RX7HELbYQ.png
// @icon64       https://s2.loli.net/2022/01/12/tmFeSKDf8UkNMjC.png
// @grant        none
// @license      GPL-3.0-only
// @create       2021-11-22
// @note         紧急修复百度文档不能展开的bug(bug出现于1.4.12版本)
// ==/UserScript==


(function () {
    'use strict';

    let utils = {
        /**
         * 创建并下载文件
         * @param {string} fileName 
         * @param {string} content 
         */
        createAndDownloadFile: function(fileName, content) {
            let aTag = document.createElement('a');
            let blob = new Blob([content]);
            aTag.download = fileName;
            aTag.href = URL.createObjectURL(blob);
            aTag.click();
            URL.revokeObjectURL(blob);
        },

        /**
         * 临时禁用脚本,执行func后移除btns_section。
         * @param {Function} func
         */
        banSelf: function(func = () => { }) {
            func();
            document.querySelector(".btns_section").remove();
        },

        /**
         * 睡眠 delay 毫秒
         * @param {Number} delay 
         */
        sleep: function(delay) {
            let start = (new Date()).getTime();
            while ((new Date()).getTime() - start < delay) {
                continue;
            }
        },

        /**
         * 允许打印页面
         */
        allowPrint: function() {
            let style = document.createElement("style");
            style.innerHTML = `
            @media print {
                body{
                    display:block;
                }
            }
        `;
            document.head.appendChild(style);
        },

        /**
         * 求main_set去除cut_set后的set
         * @param {Set} main_set 
         * @param {Set} cut_set 
         * @returns 差集
         */
        difference: function(main_set, cut_set) {
            let _diff = new Set(main_set);
            for (let elem of cut_set) {
                _diff.delete(elem);
            }
            return _diff;
        },

        /**
         * 抛出set中的第一个元素
         * @param {Set} set 
         * @returns 一个元素
         */
        setPop: function(set) {
            for (let item of set) {
                set.delete(item);
                return item;
            }
        },

        /**
         * 绑定事件到指定按钮,返回按钮引用
         * @param {Function} event click事件
         * @param {Array} args 事件的参数列表 
         * @param {String} aim_btn 按钮的变量名
         * @param {String} new_text 按钮的新文本,为null则不替换
         * @returns 按钮元素的引用
         */
        setBtnEvent: function(event, args = [], aim_btn = "btn_3", new_text = null) {
            let btn = document.querySelector(`.${aim_btn.replace("_", "-")}`);
            // 如果需要,替换按钮内文本
            if (new_text) {
                btn.textContent = new_text;
            }
            // 绑定事件,添加到页面上
            btn.onclick = () => {
                this.enhanceBtnClickReaction(aim_btn);
                if (args.length) {
                    event(...args);
                } else {
                    event();
                }
            };
            return btn;
        },

        /**
         * 强制隐藏元素
         * @param {String} selector 
         */
        forceHide: function(selector) {
            document.querySelectorAll(selector).forEach((elem) => {
                elem.className += " force_hide";
            });
            let style = document.createElement("style");
            style.innerHTML = `.force_hide {
            visibility: hidden !important;
        }`;
            document.head.appendChild(style);
        },

        /**
         * 隐藏按钮,打印页面,显示按钮
         */
        hideBtnThenPrint: function() {
            // 隐藏按钮,然后打印页面
            let section = document.getElementsByClassName("btns_section")[0];
            section.style.display = "none";
            window.print();
            // 打印结束,显示按钮
            section.style.removeProperty("display");
        },

        /**
         * 返回times个倍数连接的str
         * @param {String} str 
         * @param {Number} times 
         * @returns multiplied_str
         */
        multiplyStr: function(str, times) {
            let str_list = [];
            for (let i = 0; i < times; i++) {
                str_list.push(str);
            }
            return str_list.join("");
        },

        /**
         * 增强按钮(默认为蓝色按钮:展开文档)的点击效果
         * @param {String} custom_btn 按钮变量名
         */
        enhanceBtnClickReaction: function(custom_btn = null) {
            let aim_btn;
            // 如果不使用自定义按钮元素,则默认为使用蓝色展开文档按钮
            if (!custom_btn || custom_btn === "btn_1") {
                aim_btn = document.querySelector(".btn-1");
            } else {
                aim_btn = document.querySelector(`.${custom_btn.replace("_", "-")}`);
            }

            let old_color = aim_btn.style.color; // 保存旧的颜色
            let old_text = aim_btn.textContent; // 保存旧的文字内容
            // 变黑缩小
            aim_btn.style.color = "black";
            aim_btn.style.fontWeight = "normal";
            aim_btn.textContent = `->${old_text}<-`;
            // 复原加粗
            let changeColorBack = function() {
                aim_btn.style.color = old_color;
                aim_btn.style.fontWeight = "bold";
                aim_btn.textContent = old_text;
            };
            setTimeout(changeColorBack, 1250);
        },

        /**
         * 切换按钮显示/隐藏状态
         * @param {String} aim_btn 按钮变量名
         * @returns 按钮元素的引用
         */
        toggleBtnStatus: function(aim_btn) {
            let btn = document.querySelector(`.${aim_btn.replace("_", "-")}`);
            let display = getComputedStyle(btn).display;
            // return;
            if (display === "none") {
                btn.style.display = "block";
            } else {
                btn.style.display = "none";
            }
            return btn;
        },

        /**
         * 根据canvas元素数量返回quality值
         * @param {Number} canvas_amount
         * @returns quality: Number
         */
        getQualityByCanvasAmount: function(canvas_amount) {
            let quality;
            if (canvas_amount <= 25) {
                quality = 1.0;
            } else if (25 < canvas_amount <= 50) {
                quality = 0.85;
            } else {
                quality = 0.7;
            }
            return quality;
        },

        /**
         * 用input框跳转到对应页码
         * @param {Element} cur_page 当前页码
         * @param {string} aim_page 目标页码
         * @param {string} event_type 键盘事件类型:"keyup" | "keypress" | "keydown"
         */
        jump2pageNo: function(cur_page, aim_page, event_type) {
            // 设置跳转页码为目标页码
            cur_page.value = aim_page;
            // 模拟回车事件来跳转
            let keyboard_event_enter = new KeyboardEvent(event_type, {
                bubbles: true,
                cancelable: true,
                keyCode: 13
            });
            cur_page.dispatchEvent(keyboard_event_enter);
        },

        /**
         * 滚动到页面底部
         */
        scrollToBottom: function() {
            window.scrollTo({
                top: document.body.scrollHeight,
                behavior: "smooth"
            });
        },

        /**
         * 用try移除元素
         * @param {Element} element 要移除的元素
         */
        tryToRemoveElement: function(element) {
            try {
                element.remove();
            } catch (e) {
            }
        },

        /**
         * 用try移除 [元素列表1, 元素列表2, ...] 中的元素
         * @param {Array} elem_list_box 要移除的元素列表构成的列表
         */
        tryToRemoveSameElem: function(elem_list_box) {
            for (let elem_list of elem_list_box) {
                if (!elem_list) {
                    continue;
                }
                for (let elem of elem_list) {
                    try {
                        elem.remove();
                    } catch (e) {
                        console.log();
                    }
                }
            }
        },

        /**
         * 使文档在页面上居中
         * @param {String} selector 文档容器的css选择器
         * @param {String} default_offset 文档部分向右偏移的百分比(0-59)
         * @returns 偏移值是否合法
         */
        centerDoc: function(selector, default_offset) {
            let doc_main = document.querySelector(selector);
            let offset = window.prompt("请输入偏移百分位:", default_offset);
            // 如果输入的数字不在 0-59 内,提醒用户重新设置
            if (offset.length === 1 && offset.search(/[0-9]/) !== -1) {
                doc_main.style.marginLeft = offset + "%";
                return true;
            } else if (offset.length === 2 && offset.search(/[1-5][0-9]/) !== -1) {
                doc_main.style.marginLeft = offset + "%";
                return true
            } else {
                alert("请输入一个正整数,范围在0至59之间,用来使文档居中\n(不同文档偏移量不同,所以需要手动调整)");
                return false;
            }
        },

        /**
         * 调整按钮内文本
         * @param {String} aim_btn 按钮变量名
         * @param {String} new_text 新的文本,null则保留旧文本
         * @param {Boolean} recommend_btn 是否增加"(推荐)"到按钮文本
         * @param {Boolean} use_hint 是否提示"文档已经完全展开,可以导出"
         */
        modifyBtnText: function(aim_btn = "btn_2", new_text = null, recommend_btn = false, use_hint = true) {
            // 提示文档已经展开
            if (use_hint) {
                let hint = "文档已经完全展开,可以导出";
                alert(hint);
            }
            let btn = document.querySelector(`.${aim_btn.replace("_", "-")}`);
            // 要替换的文本
            if (new_text) {
                btn.textContent = new_text;
            }
            // 推荐按钮
            if (recommend_btn) {
                btn.textContent += "(推荐)";
            }
        },

        /**
         * 将html元素转为canvas再合并到pdf中,最后下载pdf
         * @param {Array} elem_list html元素列表
         * @param {String} title 文档标题
         */
        html2PDF: async function(elem_list, title = "文档") {
            // 如果是空元素列表,返回null并终止函数
            if (elem_list.length === 0) {
                console.log("html2PDF was called, but no canvas element avaiable.");
                return null;
            }
            let tasks = []; //  存放异步任务
            let contents = []; //  存放canvas元素
            for (let elem of elem_list) {
                let task = html2canvas(elem).then((canvas) => {
                    contents.push(canvas);
                });
                tasks.push(task);
            }
            // 等待全部page转化完成
            await Promise.all(tasks);
            // 控制台检查结果
            console.log("生成的canvas元素如下:");
            console.log(contents);

            // 拿到canvas宽、高:如果第二页存在,就用第二页的宽高,如果不存在就用第一页的
            let model_page = document.querySelector("#pageNo-2") ? document.querySelector("#pageNo-2") : document.querySelector("#pageNo-1");
            let width, height;
            width = model_page.offsetWidth;
            height = model_page.offsetHeight;
            // 打包为pdf
            this.saveCanvasesToPDF(contents, title, width, height);
        },

        /**
         * 下载全部图片链接,适用性:爱问共享资料、得力文库
         * @param {string} selector 图形元素的父级元素
         */
        savePicUrls: function(selector) {
            let pages = document.querySelectorAll(selector);
            let pic_urls = [];

            for (let elem of pages) {
                let pic_obj = elem.children[0];
                let url = pic_obj.src;
                pic_urls.push(url);
            }
            let content = pic_urls.join("\n");
            // 启动下载
            this.createAndDownloadFile("urls.csv", content);
        },

        /**
         * 存储所有canvas图形为png到一个压缩包
         * @param {Array} node_list canvas元素列表
         * @param {String} title 文档标题
         */
        saveCanvasesToZip: function(node_list, title) {
            // canvas元素转为png图像
            // 所有png合并为一个zip压缩包
            let zip = new JSZip();
            let n = node_list.length;

            for (let i = 0; i < n; i++) {
                let canvas = node_list[i];
                let data_base64 = canvas.toDataURL();
                let blob = atob(data_base64.split(",")[1]);
                zip.file(`page-${i+1}.png`, blob, { binary: true });
            }

            // 导出zip
            // promise.then(onCompleted, onRejected);
            zip.generateAsync({ type: "blob" }).then(function(content) {
                // see filesaver.js
                console.log(content);
                saveAs(content, `${title}.zip`);
            });
        },

        /**
         * 将canvas转为jpeg,然后导出PDF
         * @param {Array} node_list canvas元素列表
         * @param {String} title 文档标题
         */
        saveCanvasesToPDF: function(node_list, title, width = 0, height = 0) {
            // 如果没有手动指定canvas的长宽,则自动检测
            if (!width && !height) {
                // 先获取第一个canvas用于判断竖向还是横向,以及得到页面长宽
                let first_canvas = node_list[0];
                // 如果style的长宽不存在,则直接用canvas的元素长宽
                let width_str, height_str;
                if (first_canvas.width && parseInt(first_canvas.width) && parseInt(first_canvas.height)) {
                    [width_str, height_str] = [first_canvas.width, first_canvas.height];
                } else {
                    [width_str, height_str] = [first_canvas.style.width.replace(/(px)|(rem)|(em)/, ""), first_canvas.style.height.replace(/(px)|(rem)|(em)/, "")];
                }
                // jsPDF的第三个参数为format,当自定义时,参数为数字数组。
                [width, height] = [parseFloat(width_str), parseFloat(height_str)];
            }
            console.log(`canvas数据:宽: ${width}px,高: ${height}px`);
            // 如果文档第一页的宽比长更大,则landscape,否则portrait
            let orientation = width > height ? 'l' : 'p';
            let pdf = new jspdf.jsPDF(orientation, 'px', [height, width]);

            // 根据canvas数量确定quality
            let quality = this.getQualityByCanvasAmount(node_list.length);

            // 保存每一页文档到每一页pdf
            node_list.forEach(function(canvas, index) {
                pdf.addImage(canvas.toDataURL("image/jpeg", quality), 'JPEG', 0, 0, width, height);
                // 如果当前不是文档最后一页,则需要添加下一个空白页
                if (index !== node_list.length - 1) {
                    pdf.addPage();
                }
            });

            // 导出文件
            pdf.save(`${title}.pdf`);
        },

        /**
         * 取得elem的class为class_name的父级元素
         * @param {String} class_name 
         * @param {Element} elem 
         * @param {object} JSobj 默认为window.baiduJS
         */
        getParentByClassName: function(class_name, elem, JSobj) {
            let parent = elem.parentElement;
            let now_name;
            try {
                now_name = parent.className;
            } catch (e) {
                // 没有父级元素了,返回null
                return "no parent node";
            }
            let iterator_count = JSobj.iterator_count;
            if (iterator_count > 9) {
                // 超过最大迭代次数,认为不存在,返回null
                JSobj.iterator_count = 0;
                return "over max iterator counts limit";
            } else {
                JSobj.iterator_count += 1;
            }
            // 如果类名匹配,返回该节点
            if (now_name.split(" ").includes(class_name)) {
                iterator_count = 0;
                return parent;
            }
            return this.getParentByClassName(class_name, parent, JSobj);
        },

        /**
         * 将func绑定到window.onscroll,并设置触发频率
         * @param {Function} func scroll的监听函数
         * @param {Object} JSobj 全局对象,至少要有srcoll_count
         * @param {Number} useful_range 有效的触发范围,默认是10。即0-10次时触发函数。
         * @param {Number} wait_range 等待的范围,默认是110。即useful_range-110次不触发函数。
         * @param {String} hint 触发函数后的日志内容,默认为空字符串。
         * @param {Window} inner_window 特定的window对象,主要用于 iframe 情况。JSobj中必须有scrollFunc,在调用后会重新写入scrollFunc。
         */
        scrollFunc: function(func, JSobj, useful_range = 10, wait_range = 110, hint = "", inner_window = null) {
            let new_func = (func, JSobj, useful_range, wait_range, hint) => {
                JSobj.scroll_count += 1;

                if (JSobj.scroll_count < useful_range) {
                    func();
                    console.log(hint);
                } else if (JSobj.scroll_count > wait_range) {
                    JSobj.scroll_count = 0;
                }
            };
            // 如果没有指定的window对象,则使用默认的window
            if (!inner_window) {
                window.onscroll = () => {
                    new_func(func, JSobj, useful_range, wait_range, hint);
                };
                return;
            }
            // 特定的window对象,一般用于iframe,追加scroll监听器
            let scrollFunc = () => {
                new_func(func, JSobj, useful_range, wait_range, hint);
            };
            JSobj.scrollFunc = scrollFunc;
            inner_window.addEventListener("scroll", scrollFunc, false);
        },

        /**
         * 创建5个按钮:展开文档、导出图片、导出PDF、未设定4、未设定5;默认均为隐藏
         */
        createBtns: function() {
            // 创建按钮组
            let section = document.createElement("section");
            section.className = "btns_section";
            section.innerHTML = `
            <p class="logo_tit">Wenku Doc Downloader</p>
            <button class="btn-1" title="请先滑到底部,使内容加载完,防止出现空白页">展开文档 😈</button>
            <button class="btn-2">导出图片 🖼️</button>
            <button class="btn-3">导出PDF 🌼</button>
            <button class="btn-4">未设定4</button>
            <button class="btn-5">未设定5</button>`;
            document.body.appendChild(section);

            // 设定样式
            let style = document.createElement("style");
            style.innerHTML = `
            .btns_section{
                position: fixed;
                width: 154px;                
                left: 10px;
                top: 32%;
                background: #E7F1FF;
                border: 2px solid #1676FF;                
                padding: 0px 0px 10px 0px;
                font-weight: 600;
                border-radius: 2px;
                font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'PingFang SC', 'Hiragino Sans GB',
                'Microsoft YaHei', 'Helvetica Neue', Helvetica, Arial, sans-serif, 'Apple Color Emoji',
                'Segoe UI Emoji', 'Segoe UI Symbol';
                z-index: 5000;
            }
            .logo_tit{
                width: 100%;
                background: #1676FF;
                text-align: center;
                font-size:12px ;
                color: #E7F1FF;
                line-height: 40px;
                height: 40px;
                margin: 0 0 16px 0;
            }

            .btn-1{
                display: block;
                width: 128px;
                height: 28px;
                background: linear-gradient(180deg, #00E7F7 0%, #FEB800 0.01%, #FF8700 100%);
                border-radius: 4px;
                color: #fff;
                font-size: 12px;
                border: none;
                outline: none;
                margin: 8px auto;
                font-weight: bold;
                cursor: pointer;
                opacity: .9;
            }
            .btn-2{
                display: none;
                width: 128px;
                height: 28px;
                background: #07C160;
                border-radius: 4px;
                color: #fff;
                font-size: 12px;
                border: none;
                outline: none;
                margin: 8px auto;
                font-weight: bold;
                cursor: pointer;
                opacity: .9;
            }
            .btn-3{
                display: none;
                width: 128px;
                height: 28px;
                background:#FA5151;
                border-radius: 4px;
                color: #fff;
                font-size: 12px;
                border: none;
                outline: none;
                margin: 8px auto;
                font-weight: bold;
                cursor: pointer;
                opacity: .9;
            }
            .btn-4{
                display: none;
                width: 128px;
                height: 28px;
                background: #1676FF;
                border-radius: 4px;
                color: #fff;
                font-size: 12px;
                border: none;
                outline: none;
                margin: 8px auto;
                font-weight: bold;
                cursor: pointer;
                opacity: .9;
            }
            .btn-5{
                display: none;
                width: 128px;
                height: 28px;
                background: #ff6600;
                border-radius: 4px;
                color: #fff;
                font-size: 12px;
                border: none;
                outline: none;
                margin: 8px auto;
                font-weight: bold;
                cursor: pointer;
                opacity: .9;
            }
            .btn-1:hover,.btn-2:hover,.btn-3:hover,.btn-4,.btn-5:hover{ opacity: .8;}
            .btn-1:active,.btn-2:active,.btn-3:active,.btn-4,.btn-5:active{ opacity: 1;}`;
            document.head.appendChild(style);
        }
    };

    /**
     * 清理百度文库页面的无关元素
     */
    function clearPage_Baidu() {
        let selectors = [
            "#hd, .aside, .reader-tools-bar-wrap, .sb-con, .bg-opacity",
            ".doc-tag-wrap, .doc-bottom-wrap, .ft, #ft, .crubms-wrap, .banner-ad",
            "#activity-tg, .top-ads-banner-wrap, .reader_ab_test, .tag-tips, .doc-value",
            ".owner-desc-wrap, a[title='全屏显示'], #next_doc_box"
        ];
        let elem_list = document.querySelectorAll(selectors.join(", "));
        for (let elem of elem_list) {
            utils.tryToRemoveElement(elem);
        }
        let nut_selector = ".fix-searchbar-wrap, #hd";
        utils.forceHide(nut_selector);
        // 去除页面顶部空白
        document.querySelector("#doc").style.paddingTop = "0";
    }


    /**
     * 判断是否收集完元素,如果没有,则给出提醒
     * @param {Array} msg_list 附加提示信息列表
     * @returns bool 是否完成元素收集/冻结
     */
    function isFinished(msg_list = []) {
        if (!window.baiduJS.finished) {
            let hint = [
                "仍有内容未加载完,无法使用该功能",
                "建议从头到尾慢速地再浏览一遍",
                ...msg_list
            ];
            alert(hint.join("\n"));
            return false;
        }
        return true;
    }


    /**
     * 提取文字,导出txt。适用于百度文库
     */
    function saveText_Baidu() {
        // 判断是否存在文字元素
        let elems = document.querySelectorAll(".reader-txt-layer");
        if (!elems.length) {
            alert("当前页面没有文字元素\n如果你看到了文字说明它的原文档就是图片,所以提取不到文字");
            return;
        }
        // 判断页面是否加载完成
        if (!isFinished()) {
            return;
        }
        let title = document.title.split("-")[0].trim(); // 取得文档标题
        let page_texts = [];
        for (let elem of window.baiduJS.elems_map.values()) {
            // 取得该页文档下的全部文字
            let text = elem.textContent;
            page_texts.push(text);
        }
        utils.createAndDownloadFile(`${title}.txt`, page_texts.join("\n"));
    }


    /**
     * 动态存储".ppt-image-wrap img"图形,并导出urls
     * @returns
     */
    function savePicUrls_Baidu() {
        let urls = [];
        let elems = document.querySelectorAll(".ppt-image-wrap img");
        if (!elems.length) {
            alert("当前页面没有PPT图形");
            return;
        }
        elems.forEach((elem) => {
            if (elem.hasAttribute("src")) {
                urls.push(elem.src);
            } else {
                urls.push(elem.getAttribute("data-src"));
            }
        });
        utils.createAndDownloadFile("urls.csv", urls.join("\n"));
    }


    /**
     * 动态存储".reader-pic-item"图形,并导出urls
     * @returns 
     */
    function savePicUrls_BaiduNonPPT() {
        // 判断是否存在非PPT图形元素
        let elems = document.querySelectorAll(".reader-pic-item");
        if (!elems.length) {
            alert("当前页面没有非PPT图形");
            return;
        }
        // 判断是否页面都加载完成
        if (!isFinished()) {
            return;
        }
        // 找到img元素,导出urls
        let img_urls = [];
        for (let elem of window.baiduJS.elems_map.values()) {
            // 取得img元素
            elem.querySelectorAll(".reader-pic-item").forEach((img) => {
                // 取得img链接
                let url = img.style.backgroundImage.split('"')[1];
                img_urls.push(url);
            });
        }
        utils.createAndDownloadFile("urls.csv", img_urls.join("\n"));
    }


    /**
     * 找出没有收录的id,需求的id列表形如:["pageNo-4", "pageNo-5", ...]
     * @returns 未收录的id_list: [1, 2, 3, ...]
     */
    function getUnfrozen() {
        // 获取已经冻结的文档元素
        let _frozen_ids = new Set(window.baiduJS.elems);
        let frozen_ids = new Set();
        _frozen_ids.forEach((full_id) => {
            // full_id: pageNo-1
            let id = parseInt(full_id.split("-")[1]);
            frozen_ids.add(id);
        });

        if (!frozen_ids.size) {
            return ["all pages"];
        }

        // 取得基础页码
        let a_id = utils.setPop(frozen_ids);
        let basis = parseInt(a_id / 50) * 50;
        // 获取全部文档元素
        let all_ids = new Set();
        let all_elems = document.querySelectorAll(".mod.reader-page.complex[class*=reader-page]");
        all_elems.forEach((elem) => {
            // ['mod', 'reader-page', 'complex', 'hidden-doc-banner', 'reader-page-1'] -> "1" -> 1
            let id = parseInt(elem.className.split(" ").at(-1).split("-")[2]);
            all_ids.add(basis + id);
        });

        // 求差集,取得未冻结的
        let unfrozen_set = utils.difference(all_ids, frozen_ids);
        let unfrozen_list = Array.from(unfrozen_set);
        unfrozen_list.sort((prev, next) => { return prev - next; });
        // 打印日志
        console.log([
            `all_ids: ${Array.from(all_ids)}`,
            `frozen_ids: ${Array.from(frozen_ids)}`,
            `unfrozen_list: ${unfrozen_list}`
        ].join("\n"));
        return unfrozen_list;
    }


    /**
     * 适用于【带有api=1】get参数的页面。可以打印页面。
     * @returns 
     */
    function printPage_Baidu() {
        // 判断是否页面都加载完成
        let msg_list = [
            "未加载的页面如下:",
            getUnfrozen().join(", ")
        ];
        if (!isFinished(msg_list)) {
            return;
        }
        let next50pages = document.querySelector(".pageList-btn.next-pageList");
        utils.tryToRemoveElement(next50pages);
        utils.hideBtnThenPrint();
    }


    /**
     * 适用于【*不*带有api=1】get参数的页面。可以打印页面。
     * @returns 
     */
    function printPage_BaiduNoArgs() {
        // 判断是否页面都加载完成
        let msg_list = [
            "未加载的页面如下:",
            getUnfrozen().join(", ")
        ];
        if (!isFinished(msg_list)) {
            return;
        }
        // 清理页面
        clearPage_Baidu();
        // 隐藏上下页按钮
        document.querySelectorAll("a[data-next-page], a[data-prev-page]").forEach((elem) => {
            elem.setAttribute("style", "display: none !important;");
        });
        // 打印
        utils.hideBtnThenPrint();
        // 显示上下页按钮
        document.querySelectorAll("a[data-next-page], a[data-prev-page]").forEach((elem) => {
            elem.style.display = "block";
        });
    }


    /**
     * 调整页间距为 width px
     * @param {Number} width 页间距
     */
    function adjustSpace(width) {
        // 调整页间距
        let space_selector = ".reader-container .reader-page, .reader-container .pay-page-mod";
        document.querySelectorAll(space_selector).forEach((space) => {
            space.style.margin = `0 0 ${width}px`;
        });
        console.log(`页间距已经调整为:${width}px`);
    }


    function userAdjustSpace() {
        let space_selector = ".reader-container .reader-page, .reader-container .pay-page-mod";
        let space = document.querySelector(space_selector);
        let old_width = getComputedStyle(space).marginBottom;
        let width_str = prompt(`当前页间距为:${old_width}\n请输入调整后的页间距(0-500的整数):`);
        let width = parseInt(width_str);
        width = Number.isInteger(width) && (0 <= width <= 500) ? width : parseInt(old_width);
        adjustSpace(width);
        console.log(`调整后的页间距为:${width}px`);
    }


    /**
     * 根据键中的id数字对map排序
     * @param {Map} elems_map 
     * @returns sorted_map
     */
    function sortMapByID$2(elems_map) {
        // id形式:pageNo-10
        let elems_arr = Array.from(elems_map);
        elems_arr.sort((item1, item2) => {
            // 从key中取出id
            let id1 = parseInt(item1[0].split("-")[1]);
            let id2 = parseInt(item2[0].split("-")[1]);
            // 升序排序
            return id1 - id2;
        });
        // 返回排序好的map
        return new Map(elems_arr);
    }


    /**
     * 存储html元素。适用于百度文库的文字型文档
     */
    function storeHtmlElemts_Baidu(selector = "[class*=reader-main]") {
        let elems_map = window.baiduJS.elems_map;
        document.querySelectorAll(selector).forEach(
            (elem) => {
                let origin_page_elem = utils.getParentByClassName("bd", elem, window.baiduJS);
                if (typeof(origin_page_elem) === "string") {
                    return;
                }
                // 复制元素防止丢失
                let page_elem = origin_page_elem.cloneNode(true);
                // 移除data标签,切断vue对数据的渲染控制
                page_elem.removeAttribute("data-page-no");
                let id = page_elem.id; // id的形式:pageNo-10
                if (!elems_map.has(id)) {
                    elems_map.set(id, page_elem);
                }
            });
        if (elems_map.size === window.baiduJS.max_page) {
            // 根据id排序,保证导出的图片链接不是乱序的
            window.baiduJS.elems_map = sortMapByID$2(window.baiduJS.elems_map);
            // 已经保存完全部文档页元素,移除滚动事件的绑定函数
            window.baiduJS.finished = true;
            window.onscroll = () => { };
        }
    }


    /**
     * 移除html元素上的data标签,切断vue的数据渲染控制。适用于百度文库的文字型文档
     * @param {String} selector 要移除 data-page-no属性 的元素选择器
     */
    function freezeHtmlElemts_Baidu(selector = "[class*=reader-main]") {
        let elems = window.baiduJS.elems;
        document.querySelectorAll(selector).forEach(
            (elem) => {
                let page_elem = utils.getParentByClassName("bd", elem, window.baiduJS);
                if (typeof(page_elem) === "string") {
                    return;
                }
                // 移除data标签,切断vue对数据的渲染控制
                page_elem.removeAttribute("data-page-no");
                // 存储已经冻结的元素id
                let id = page_elem.id; // id的形式:pageNo-10
                if (!elems.includes(id)) {
                    elems.push(id);
                }
            });
        if (elems.length === window.baiduJS.max_page) {
            // 已经冻结完全部文档页元素,移除滚动事件的绑定函数
            window.baiduJS.finished = true;
            window.onscroll = () => { };
        }
    }


    /**
     * 递归的展开百度文档,适用于旧版页面。
     * @param {Function} extra_task 在文档展开完成后执行的追加函数
     * @returns 
     */
    function readMoreRecursively(extra_task = () => { }) {
        let go_more = document.querySelector("#html-reader-go-more");
        if (!go_more || go_more.style.display === "none") {
            // 如果不存在继续阅读按钮,或隐藏显示,则认为完成了展开
            console.log("wk: 文档展开完成");
            extra_task();
            return;
        }
        let read_more = go_more.querySelector(".moreBtn.goBtn");
        read_more.click();
        console.log("wk: 展开文档");
        setTimeout(readMoreRecursively, 500);
    }


    /**
     * 统计文档页的数量
     * @param {String} selector 文档页元素选择器
     * @returns 文档页的数量
     */
    function countPages$1(selector = ".mod.reader-page.complex[class*=reader-page]") {
        let all_elems = document.querySelectorAll(selector);
        return all_elems.length;
    }


    /**
     * 取得当前行高
     * @returns 行高,例如 192px
     */
    function getLineHeight() {
        let line = document.querySelector("p.reader-word-layer");
        if (!line) {
            return null;
        }
        let height = getComputedStyle(line).lineHeight;
        console.log(`get line-height: ${height}`);
        return height;
    }


    /**
     * 增大行间距到1500px,用于解决文字重叠
     */
    function changeLineHeight() {
        let lines = document.querySelectorAll("p.reader-word-layer");
        let aim_height, aim_hint;

        if (getComputedStyle(lines[0]).lineHeight !== window.baiduJS.origin_line_height) {
            // 切换回旧的行高
            aim_height = window.baiduJS.origin_line_height;
            aim_hint = "解决文字重叠";
            console.log(`changed to original line height`);
        } else {
            // 切换到增大的行高
            aim_height = "1500px";
            aim_hint = "切回旧行高";
            console.log(`changed to new line height: 1500px`);
        }
        // 应用新的行高
        for (let line of lines) {
            line.style.lineHeight = aim_height;
        }
        // 应用新的按钮文本
        setTimeout(() => {
            utils.modifyBtnText("btn_5", aim_hint, false, false);
        }, 2000);
    }


    function baiduWenku_OldVer() {
        // 为导出内容提供全局变量,便于动态收集文档页元素的存取
        let real_max_page;
        try {
            real_max_page = parseInt(document.querySelector(".page-count").textContent.replace("/", ""));
        } catch (e) {
            real_max_page = countPages$1();
        }

        window.baiduJS = {
            max_page: countPages$1(), // 当前文档页面数量
            real_max_page: real_max_page, // 当前文档的总页数
            iterator_count: 0, // getParentByClassName的最大迭代次数为9
            finished: false, // 是否收集完了全部文档页元素
            scroll_count: 0, // 用于统计累计触发scroll的次数
            elems: [], // 存储已经冻结的元素id
            elems_map: new Map(), // id: element
            origin_line_height: getLineHeight() // 原始行高
        };

        if (location.href.includes("?share_api=1&width=800")) {
            // 带分享参数的页面,适用于word和excel

            // 跟随浏览,动态冻结页面元素
            utils.scrollFunc(freezeHtmlElemts_Baidu, window.baiduJS, 10, 50, "baidu元素: 冻结");

            // 隐藏按钮
            utils.toggleBtnStatus("btn_1");
            // 显示按钮
            utils.toggleBtnStatus("btn_2");
            utils.toggleBtnStatus("btn_3");
            // 绑定事件到按钮
            // 按钮2:调整页间距
            utils.setBtnEvent(userAdjustSpace, [], "btn_2", "调整页间距");
            // 按钮3:打印页面到PDF
            utils.setBtnEvent(printPage_Baidu, [], "btn_3", "打印页面到PDF");

        } else if (location.href.includes("\u002f\u0073\u0068\u0061\u0072\u0065\u002f")) {
            // old version for fetch doc elements
            // 跟随浏览,动态收集页面元素
            utils.scrollFunc(storeHtmlElemts_Baidu, window.baiduJS, 10, 50, "baidu元素: 收集");

            // 隐藏按钮
            utils.toggleBtnStatus("btn_1");
            // 显示按钮
            utils.toggleBtnStatus("btn_2");
            utils.toggleBtnStatus("btn_3");
            utils.toggleBtnStatus("btn_4");
            utils.toggleBtnStatus("btn_5");
            // 绑定事件到按钮
            utils.setBtnEvent(saveText_Baidu, [], "btn_3", "导出纯文本");
            utils.setBtnEvent(savePicUrls_Baidu, [], "btn_4", "导出图片链接(仅PPT)");
            utils.setBtnEvent(savePicUrls_BaiduNonPPT, [], "btn_5", "导出图片链接(除PPT)");
            // btn_2
            utils.setBtnEvent(() => {
                if (confirm("仅对excel和word文档有效,是否继续?")) {
                    // 在无参数的旧版页面处理超过50页的文档
                    // 跟随浏览,动态冻结页面元素
                    window.baiduJS.finished = false;
                    utils.scrollFunc(freezeHtmlElemts_Baidu, window.baiduJS, 10, 50, "baidu元素: 冻结");
                    // 重新绑定按钮监听器
                    utils.setBtnEvent(userAdjustSpace, [], "btn_4", "调整页间距");
                    utils.setBtnEvent(printPage_BaiduNoArgs, [], "btn_3", "打印页面到PDF◈");
                    alert("正在截获文档内容,请上下浏览页面后再次点击该按钮");
                    utils.setBtnEvent(changeLineHeight, [], "btn_5", "解决文字重叠");

                    // 隐藏按钮
                    utils.toggleBtnStatus("btn_2");
                }
            }, [], "btn_2", "打印页面到PDF");
        } else {
            console.log(`无法识别的页面:${location.href}`);
        }
    }


    /**
     * 百度文档下载策略
     */
    function baiduWenku() {
        // 允许打印页面
        utils.allowPrint();
        // 原文档解析到预览文档
        if (location.href.includes("\u002f\u0076\u0069\u0065\u0077\u002f")) {
            utils.createBtns();
            let jump2sharePage_Baidu = function() {
                location.href = `https://${location.host}${location.pathname.replace("\u0076\u0069\u0065\u0077", "\u0073\u0068\u0061\u0072\u0065")}`;
            };
            utils.setBtnEvent(jump2sharePage_Baidu, [], "btn_1");
        } else {
            // 在完全展开文档后启用旧版页面处理函数
            // 创建按钮组
            console.log("\n\nwk: 进入旧版页面\n\n\n");
            utils.createBtns();
            utils.setBtnEvent(readMoreRecursively, [baiduWenku_OldVer], "btn_1");
        }
    }

    /**
     * 展开道客巴巴的文档
     */
    function readAllDoc88() {
        // 获取“继续阅读”按钮
        let continue_btn = document.querySelector("#continueButton");
        // 如果存在“继续阅读”按钮
        if (continue_btn) {
            // 跳转到文末(等同于展开全文)
            let cur_page = document.querySelector("#pageNumInput");
            // 取得最大页码
            let page_max = cur_page.parentElement.textContent.replace(" / ", "");
            // 跳转到尾页
            utils.jump2pageNo(cur_page, page_max, "keypress");
            // 返回顶部
            setTimeout(utils.jump2pageNo(cur_page, "1", "keypress"), 1000);
        }
        // 文档展开后,显示按钮2、3
        else {
            // 隐藏按钮
            utils.toggleBtnStatus("btn_1");
            // 显示按钮
            utils.toggleBtnStatus("btn_2");
            utils.toggleBtnStatus("btn_3");
        }
    }

    /**
     * 道客巴巴文档下载策略
     */
    function doc88() {
        // 创建脚本启动按钮1、2
        utils.createBtns();

        // 绑定主函数
        let prepare = function() {
            // 获取canvas元素列表
            let node_list = document.querySelectorAll(".inner_page");
            // 获取文档标题
            let title;
            if (document.querySelector(".doctopic h1")) {
                title = document.querySelector(".doctopic h1").title;
            } else {
                title = "文档";
            }
            return [node_list, title];
        };

        // btn_1: 展开文档
        utils.setBtnEvent(() => {
            readAllDoc88();
        }, [], "btn_1");
        // btn_2: 导出zip
        utils.setBtnEvent(() => {
            if (confirm("确定每页内容都加载完成了吗?")) {
                utils.saveCanvasesToZip(...prepare());
            }
        }, [], "btn_2", "导出图片到zip");
        // btn_3: 导出PDF
        utils.setBtnEvent(() => {
            if (confirm("确定每页内容都加载完成了吗?")) {
                utils.saveCanvasesToPDF(...prepare());
            }
        }, [], "btn_3", "导出图片到PDF");
    }

    // 绑定主函数
    function getCanvasList() {
        // 获取全部canvas元素,用于传递canvas元素列表给 btn_2 和 btn_3
        let parent_node_list = document.querySelectorAll(".hkswf-content");
        let node_list = [];
        for (let node of parent_node_list) {
            node_list.push(node.children[0]);
        }
        return node_list;
    }


    function prepare() {
        // 获取canvas元素列表
        let node_list = getCanvasList();
        // 获取文档标题
        let title;
        if (document.querySelector("h1 [title=doc]")) {
            title = document.querySelector("h1 [title=doc]").nextElementSibling.textContent;
        } else if (document.querySelector(".doc_title")) {
            title = document.querySelector(".doc_title").textContent;
        } else {
            title = "文档";
        }
        return [node_list, title];
    }


    // 判断是否有canvas元素
    function detectCanvas() {
        let haveCanvas = getCanvasList().length === 0 ? false : true;

        // 隐藏按钮
        utils.toggleBtnStatus("btn_1");
        // 显示按钮
        utils.toggleBtnStatus("btn_2");

        // 如果没有canvas元素,则认为文档页面由外链图片构成
        if (!haveCanvas) {
            // btn_2: 导出图片链接
            utils.setBtnEvent(() => {
                if (confirm("确定每页内容都加载完成了吗?")) {
                    utils.savePicUrls("[id*=img_]");
                }
            }, [], "btn_2", "导出全部图片链接");
        } else {
            // 显示按钮3
            utils.toggleBtnStatus("btn_3");
            // btn_2: 导出zip
            utils.setBtnEvent(() => {
                if (confirm("确定每页内容都加载完成了吗?")) {
                    utils.saveCanvasesToZip(...prepare());
                }
            }, [], "btn_2", "导出图片到zip");
            // btn_3: 导出PDF
            utils.setBtnEvent(() => {
                if (confirm("确定每页内容都加载完成了吗?")) {
                    utils.saveCanvasesToPDF(...prepare());
                }
            }, [], "btn_3", "导出图片到PDF");
        }
    }


    /**
     * 豆丁文档下载策略
     */
    function docin() {
        // 创建脚本启动按钮
        utils.createBtns();

        // 隐藏底部工具栏
        document.querySelector("#j_select").click(); // 选择指针
        let tool_bar = document.querySelector(".reader_tools_bar_wrap.tools_bar_small.clear");
        tool_bar.style.display = "none";

        // btn_1: 判断文档类型
        utils.setBtnEvent(() => {
            utils.forceHide(".jz_watermark");
            detectCanvas();
        }, [], "btn_1", "判断文档类型");
    }

    /**
     * 点击“展开继续阅读”,适用性:爱尚共享资料
     */
    function readAlliShare() {
        // 获取“继续阅读”元素
        let red_btn = document.getElementsByClassName("red-color")[0];
        let red_text = red_btn.textContent;
        // 如果可以展开,则展开
        if (red_text.search("点击可继续阅读") !== -1) {
            red_btn.click();
            setTimeout(readAlliShare, 1000);
        }
        // 否则启动按钮2,准备清理页面然后打印为PDF
        else {
            // 隐藏按钮
            utils.toggleBtnStatus("btn_1");
            // 显示按钮
            utils.toggleBtnStatus("btn_2");
            utils.toggleBtnStatus("btn_3");

            // 显示svg图片的链接
            let page1 = document.querySelector('[data-num="1"] .data-detail embed');
            if (!page1) {
                // 如果不存在svg图形,终止后续代码
                console.log("当前页面不存在svg图形");
                return;
            }
            let page2 = document.querySelector('[data-num="2"] .data-detail embed');
            let [svg1_src_div, svg2_src_div] = [document.createElement("div"), document.createElement("div")];
            svg1_src_div.innerHTML = `<div id="src-1"
                                    style="font-weight: bold;font-size: 20px; height: 100px; width: 100%">
                                        访问以下链接以复制文字:<br>${page1.src}
                                    </div>`;
            svg2_src_div.innerHTML = `<div id="src-1"
                                    style="font-weight: bold;font-size: 20px; height: 100px; width: 100%">
                                    访问以下链接以复制文字:<br>${page2.src}
                                    </div>`;
            // 添加到页面上
            page1.parentElement.parentElement.parentElement.append(svg1_src_div);
            page2.parentElement.parentElement.parentElement.append(svg2_src_div);
        }
    }


    /**
     * 清理并打印爱问共享资料的文档页
     * @returns 如果输入偏移量非法,返回空值以终止函数
     */
    function printPageiShare() {
        // # 清理并打印爱问共享资料的文档页
        // ## 移除页面上无关的元素
        // ### 移除单个元素
        let topbanner = document.getElementsByClassName("detail-topbanner")[0];
        let header = document.getElementsByClassName("new-detail-header")[0];
        let fixright = document.getElementById("fix-right");
        let redpacket = document.getElementsByClassName("loginRedPacket-dialog")[0];
        let fixedrightfull = document.getElementsByClassName("fixed-right-full")[0];
        let footer = document.getElementsByClassName("website-footer")[0];
        let guess = document.getElementsByClassName("guess-you-like-warpper")[0];
        let detailtopbox = document.getElementsByClassName("detail-top-box")[0];
        let fullscreen = document.getElementsByClassName("reader-fullScreen")[0];
        let endhint = document.getElementsByClassName("endof-trial-reading")[0];
        let crumb_arrow;
        try { crumb_arrow = document.getElementsByClassName("crumb-arrow")[0].parentElement; } catch (e) { console.log(); }
        let copyright = document.getElementsByClassName("copyright-container")[0];
        let state_btn = document.getElementsByClassName("state-bottom")[0];
        let comments = document.getElementsByClassName("user-comments-wrapper")[0];
        // ### 执行移除
        let elem_list = [
            topbanner,
            header,
            fixright,
            redpacket,
            fixedrightfull,
            footer,
            guess,
            detailtopbox,
            fullscreen,
            endhint,
            crumb_arrow,
            copyright,
            state_btn,
            comments
        ];
        for (let elem of elem_list) {
            utils.tryToRemoveElement(elem);
        }
        // ### 移除全部同类元素
        let elem_list_2 = document.querySelectorAll(".tui-detail, .adv-container");
        for (let elem_2 of elem_list_2) {
            utils.tryToRemoveElement(elem_2);
        }
        // 使文档居中
        alert("建议使用:\n偏移量: 18\n缩放: 默认\n如果预览中有广告,就取消打印\n再点一次按钮,预览中应该就没有广告了");
        if (!utils.centerDoc("doc-main", "18")) {
            return; // 如果输入非法,终止函数调用
        }
        // 隐藏按钮,然后打印页面
        utils.hideBtnThenPrint();
    }


    /**
     * 爱问共享资料文档下载策略
     */
    function ishare() {
        // 创建脚本启动按钮1、2
        utils.createBtns();

        // btn_1: 展开文档
        utils.setBtnEvent(readAlliShare, [], "btn_1");
        // btn_2: 导出图片链接
        utils.setBtnEvent(() => {
            utils.savePicUrls(".data-detail");
        }, [], "btn_2", "导出图片链接(推荐)");
        // btn_3: 打印页面到PDF
        utils.setBtnEvent(printPageiShare, [], "btn_3", "打印页面到PDF");

        // 移除底部下载条
        let detailfixed = document.getElementsByClassName("detail-fixed")[0];
        utils.tryToRemoveElement(detailfixed);
    }

    /**
     * 清理并打印得力文库的文档页
     */
    function printPageDeliwenku() {
        // 移除页面上的无关元素
        let selector = ".hr-wrap, #readshop, .nav_uis, .bookdesc, #boxright, .QQ_S1, .QQ_S, #outer_page_more, .works-manage-box.shenshu, .works-intro, .mt10.related-pic-box, .mt10.works-comment, .foot_nav, .siteInner";
        let elem_list = document.querySelectorAll(selector);
        for (let elem of elem_list) {
            utils.tryToRemoveElement(elem);
        }
        // 修改页间距
        let outer_pages = document.getElementsByClassName("outer_page");
        for (let page of outer_pages) {
            page.style.marginBottom = "20px";
        }
        // 使文档居中
        alert("建议使用:\n偏移量: 3\n缩放: 112\n请上下滚动页面,确保每页内容都加载完成以避免空白页\n如果预览时有空白页或文末有绿色按钮,请取消打印重试");
        if (!utils.centerDoc("#boxleft", "3")) {
            return; // 如果输入非法,终止函数调用
        }
        // 打印文档
        utils.hideBtnThenPrint();
    }


    /**
     * 点击“继续阅读”,适用性:得力文库
     */
    function readAllDeliwenku() {
        // 点击“同意并开始预览全文”
        let start_btn = document.getElementsByClassName("pre_button")[0];
        let display = start_btn.parentElement.parentElement.style.display;
        // 如果该按钮显示着,则点击,然后滚动至页面底部,最后终止函数
        if (!display) {
            start_btn.children[0].click();
            setTimeout("scroll(0, document.body.scrollHeight)", 200);
            return;
        }
        // 增强按钮点击效果
        utils.enhanceBtnClickReaction();

        let read_all_btn = document.getElementsByClassName("fc2e")[0];
        let display2 = read_all_btn.parentElement.parentElement.style.display;
            // 继续阅读
        if (display2 !== "none") {
            // 获取input元素
            let cur_page = document.querySelector("#pageNumInput");
            let page_old = cur_page.value;
            let page_max = cur_page.parentElement.nextElementSibling.textContent.replace(" / ", "");
            // 跳转到尾页
            utils.jump2pageNo(cur_page, page_max, "keydown");
            // 跳转回来
            utils.jump2pageNo(cur_page, page_old, "keydown");

            // 切换按钮准备导出
        } else {
            // 推荐导出图片链接
            utils.modifyBtnText("btn_2", null, true);
            // 隐藏按钮
            utils.toggleBtnStatus("btn_1");
            // 显示按钮
            utils.toggleBtnStatus("btn_2");
            utils.toggleBtnStatus("btn_3");
            // btn_3 橙色按钮
            utils.setBtnEvent(printPageDeliwenku, [], "btn_3", "打印页面到PDF");
        }
    }


    /**
     * 得力文库文档下载策略
     */
    function deliwenku() {
        // 创建脚本启动按钮1、2
        utils.createBtns();

        // btn_1: 展开文档
        utils.setBtnEvent(readAllDeliwenku, [], "btn_1");
        // btn_2: 导出图片链接
        utils.setBtnEvent(() => {
            if (confirm("确定每页内容都加载完成了吗?")) {
                utils.savePicUrls('.inner_page div');
            }
        }, [], "btn_2", "导出图片链接");

        // 尝试关闭页面弹窗
        try { document.querySelector("div[title=点击关闭]").click(); } catch (e) { console.log(0); }
        // 解除打印限制
        utils.allowPrint();
    }

    function readAll360Doc() {
        // 展开文档
        document.querySelector(".articleMaxH").setAttribute("class", "");
        // 隐藏按钮
        utils.toggleBtnStatus("btn_1");
        // 显示按钮
        utils.toggleBtnStatus("btn_2");
        utils.toggleBtnStatus("btn_3");
    }


    function saveText_360Doc() {
        // 捕获图片链接
        let images = document.querySelectorAll("#artContent img");
        let content = [];

        for (let i = 0; i < images.length; i++) {
            let src = images[i].src;
            content.push(`图${i+1},链接:${src}`);
        }
        // 捕获文本
        let text = document.querySelector("#artContent").textContent;
        content.push(text);

        // 保存纯文本文档
        let title = document.querySelector("#titiletext").textContent;
        utils.createAndDownloadFile(`${title}.txt`, content.join("\n"));
    }


    function printPage360Doc() {
        // # 清理并打印360doc的文档页
        // ## 移除页面上无关的元素
        let selector = ".fontsize_bgcolor_controler, .atfixednav, .header, .a_right, .article_data, .prev_next, .str_border, .youlike, .new_plbox, .str_border, .ul-similar, #goTop2, #divtort, #divresaveunder, .bottom_controler, .floatqrcode";
        let elem_list = document.querySelectorAll(selector);
        let under_doc_1, under_doc_2;
        try {
            under_doc_1 = document.querySelector("#bgchange p.clearboth").nextElementSibling;
            under_doc_2 = document.querySelector("#bgchange").nextElementSibling.nextElementSibling;
        } catch (e) { console.log(); }
        // 执行移除
        for (let elem of elem_list) {
            utils.tryToRemoveElement(elem);
        }
        utils.tryToRemoveElement(under_doc_1);
        utils.tryToRemoveElement(under_doc_2);
        // 执行隐藏
        document.querySelector("a[title]").style.display = "none";

        // 使文档居中
        alert("建议使用:\n偏移量: 20\n缩放: 默认\n");
        if (!utils.centerDoc(".a_left", "20")) {
            return; // 如果输入非法,终止函数调用
        }
        // 隐藏按钮,然后打印页面
        utils.hideBtnThenPrint();
    }


    /**
     * 360doc个人图书馆下载策略
     */
    function doc360() {
        // 创建按钮区
        utils.createBtns();
        // btn_1: 展开文档
        utils.setBtnEvent(readAll360Doc, [], "btn_1");
        // btn_2: 导出纯文本
        utils.setBtnEvent(saveText_360Doc, [], "btn_2", "导出纯文本");
        // btn_3: 打印页面到PDF
        utils.setBtnEvent(() => {
            if (confirm("确定每页内容都加载完成了吗?")) {
                printPage360Doc();
            }
        }, [], "btn_3", "打印页面到PDF");
    }

    /**
     * 查找出所有未被捕获的页码,并返回列表
     * @returns 未捕获页码列表
     */
    function getMissedPages() {
        let all = []; // 全部页码
        for (let i = 0; i < window.mbaJS.max_page; i++) {
            all[i] = i + 1;
        }
        let missed = []; // 未捕获页码
        let possessed = Array.from(window.mbaJS.canvases_map.keys()); // 已捕获页面

        // 排除并录入未捕获页码
        for (let num of all) {
            if (!possessed.includes(`page${num}`)) {
                missed.push(num);
            }
        }
        return missed;
    }


    /**
     * 根据键中的id数字对map排序
     * @param {Map} elems_map 
     * @returns sorted_map
     */
    function sortMapByID$1(elems_map) {
        // id形式:page2
        let elems_arr = Array.from(elems_map);
        elems_arr.sort((item1, item2) => {
            // 从key中取出id
            let id1 = parseInt(item1[0].replace("page", ""));
            let id2 = parseInt(item2[0].replace("page", ""));
            // 升序排序
            return id1 - id2;
        });
        // 返回排序好的map
        return new Map(elems_arr);
    }


    /**
     * 存储动态加载的canvas元素、textContent
     */
    function storeElements_MBA() {
        let canvases_map = window.mbaJS.canvases_map;
        let texts_map = window.mbaJS.texts_map;
        let quality = window.mbaJS.quality;

        document.querySelectorAll(".page[data-loaded=true]").forEach(
            (elem) => {
                let capture = (elem) => {
                    // (1) 存储页面为canvas图形
                    let canvas, data_base64;
                    // 导出canvas数据防止丢失
                    try {
                        // 存储canvas
                        canvas = elem.querySelector("canvas[id*=page]");
                        if (window.mbaJS.only_text) {
                            data_base64 = null;
                        } else {
                            data_base64 = canvas.toDataURL("image/jpeg", quality);
                        }
                    } catch (e) {
                        // utils.sleep(500);
                        return;
                    }
                    // 增量录入map
                    let id = canvas.id; // id的形式:page2
                    if (!canvases_map.has(id)) {
                        canvases_map.set(id, data_base64);
                    }
                    // 确定canvas长宽
                    if (!window.mbaJS.only_text && !window.mbaJS.width) {
                        window.mbaJS.width = parseInt(canvas.width);
                        window.mbaJS.height = parseInt(canvas.height);
                    }

                    // (2) 存储text
                    let text = elem.textContent;
                    if (!texts_map.has(id)) {
                        texts_map.set(id, text);
                    }
                };
                setTimeout(capture, 500, elem);
            });
        if (canvases_map.size === window.mbaJS.max_page) {
            // 根据id排序
            window.mbaJS.canvases_map = sortMapByID$1(window.mbaJS.canvases_map);
            window.mbaJS.texts_map = sortMapByID$1(window.mbaJS.texts_map);
            window.mbaJS.finished = true;
            window.onscroll = null;
        }
    }


    /**
     * 将canvas转为jpeg,然后导出PDF
     * @param {Array} base64_list canvas元素列表
     * @param {String} title 文档标题
     */
    function saveCanvasesToPDF_MBA(base64_list, title) {
        let width = window.mbaJS.width;
        let height = window.mbaJS.height;

        console.log(`canvas数据:宽: ${width}px,高: ${height}px`);
        // 如果文档第一页的宽比长更大,则landscape,否则portrait
        let orientation = width > height ? 'l' : 'p';
        let pdf = new jspdf.jsPDF(orientation, 'px', [height, width]);

        // 保存每一页文档到每一页pdf
        let i = 0;
        for (let base64 of base64_list) {
            i += 1;
            pdf.addImage(base64, 'JPEG', 0, 0, width, height);
            // 如果当前不是文档最后一页,则需要添加下一个空白页
            if (i < window.mbaJS.max_page) {
                pdf.addPage();
            }
        }
        // 导出文件
        pdf.save(`${title}.pdf`);
    }

    /**
     * 判断文档页是否收集完毕,当不行时给出提示
     * @returns boolean
     */
    function ready2use() {
        removeAds(); // 顺便清理广告
        // 如果是首次点击按钮,给出提示
        if (window.mbaJS.first_hint) {
            let hint = [
                "如果浏览速度过快,比如:",
                "当前页面还没完全加载好就滚动页面去看下一页",
                "那就极有可能导致导出的PDF有空白页或文本有缺漏",
                "由防范技术的干扰,该功能目前很不好用,见谅"
            ].join("\n");
            alert(hint);
            window.mbaJS.first_hint = false;
        }
        // 如果文档页没有收集完,给出提示
        if (!window.mbaJS.finished) {
            let hint = [
                "仍有内容未加载完,无法使用该功能",
                "建议从头到尾慢速地再浏览一遍",
                "以下是没有加载完成页面的页码:",
                getMissedPages().join(",")
            ];
            alert(hint.join("\n"));
            return false;
        }
        return true;
    }


    /**
     * 用捕获好的canvas转jpg,生成PDF
     * @returns 
     */
    function canvas2PDF_mba() {
        if (!ready2use()) {
            return;
        }
        let canvases = window.mbaJS.canvases_map.values();
        // 导出PDF
        let title = document.title.split("-")[0].trim();
        saveCanvasesToPDF_MBA(canvases, title);
    }


    /**
     * 拼合捕获好的文本,保存到txt文件
     * @returns 
     */
    function saveText_mba() {
        if (!ready2use()) {
            return;
        }
        let content = Array.from(window.mbaJS.texts_map.values());
        let title = document.title.split("-")[0].trim();
        utils.createAndDownloadFile(`${title}.txt`, content.join("\n"));
    }


    /**
     * 移除广告
     */
    function removeAds() {
        document.querySelectorAll(".doc-ad").forEach((ad_elem) => {
            utils.tryToRemoveElement(ad_elem);
        });
    }


    function mbalib_() {
        // 移除广告和左侧工具栏
        removeAds();
        let tool_bar = document.querySelector(".tool-bar");
        utils.tryToRemoveElement(tool_bar);

        // 创建按钮
        utils.createBtns();
        // 隐藏按钮
        utils.toggleBtnStatus("btn_1");
        // 显示按钮
        utils.toggleBtnStatus("btn_2");
        utils.toggleBtnStatus("btn_3");
        utils.toggleBtnStatus("btn_4");

        // 取得页数
        let max_page = parseInt(document.querySelector("#numPages").textContent.replace("/ ", ""));
        let quality = utils.getQualityByCanvasAmount(max_page);

        // 为导出内容提供全局变量,便于动态收集文档页元素的存取
        window.mbaJS = {
            max_page: max_page,
            texts_map: new Map(), // id: text
            canvases_map: new Map(), // id: canvas_data_base64
            quality: quality, // canvas转jpg的质量
            width: null, // canvas宽度(px)
            height: null,
            finished: false, // 是否收集完了全部文档页元素
            first_hint: true,
            scroll_count: 0, // 用于统计累计触发scroll的次数,
            only_text: false // 是否仅捕获文本
        };
        // 跟随浏览,动态收集页面元素
        window.onscroll = () => {
            storeElements_MBA();
        };
        // 跟随浏览,动态收集页面元素
        utils.scrollFunc(storeElements_MBA, window.mbaJS, 20, 50, "mba元素: 收集");
        // 绑定事件
        utils.setBtnEvent(saveText_mba, [], "btn_2", "导出纯文本(不稳定)");
        utils.setBtnEvent(canvas2PDF_mba, [], "btn_3", "导出PDF(不稳定)");

        // 根据页数决定按钮功能:<40页,导出文本+导出pdf,>40页:导出文本
        let btn_text, aim_btn, hint;
        if (max_page > 40) {
            btn_text = "失效说明";
            aim_btn = "btn_3";
            hint = [
                "页数超过40,脚本无效",
                "只能使用导出文本功能",
                "而此脚本会使页面内容加载明显变慢,建议禁用"
            ];
            utils.setBtnEvent(utils.banSelf, [
                () => { window.onscroll = null; }
            ], "btn_4", "临时禁用脚本");
        } else {
            btn_text = "空白页说明";
            aim_btn = "btn_4";
            hint = [
                "导致空白页的原因如下",
                "加载该页的时间超过2秒 / 明显等待",
                "而此脚本会使页面内容加载明显变慢,如果影响严重请禁用"
            ];
        }

        utils.setBtnEvent(() => {
            alert(hint.join("\n"));
        }, [], aim_btn, btn_text);
    }


    function mbalib() {
        setTimeout(mbalib_, 2000);
    }

    // 拼接swf: https://pan.baidu.com/s/1PTM6watxNlqs-jvvX6XKzg?pwd=abe9 中的 JoinSWFFiles_setup.exe
    // swf转pdf: https://youfiles.herokuapp.com/swftopdf/


    /**
     * 统计文档页的数量
     * @param {String} inner_selector 【信息摘要】元素选择器
     * @returns 文档页的数量
     */
    function countPages(inner_selector = ".container div[style=' margin:20px; ']") {
        let abstract = inner_window.document.querySelector(inner_selector).textContent;
        let page_amount_pattern = /文档页数:共 ([1-9]|[1-9][0-9]|[1-9][0-9][0-9]) 页/;
        // 捕获到: ["文档页数:共 xx 页", "xx"]
        let max_page = parseInt(abstract.match(page_amount_pattern)[1]);
        return max_page;
    }


    /**
     * 判断当前帮帮文档是否为高清版页面
     * @returns 是否为高清
     */
    function isHD() {
        // 取得页面左侧【高清版】切换功能区
        let hd_bar = inner_window.document.querySelector("#gaoging");
        // 取得功能区内提示文字
        let hint = hd_bar.querySelector("#gqts").textContent;
        // 如果存在如下文字,认为当前页面是模糊版,否则是高清版
        if (hint.includes("点此阅读高清版")) {
            return false;
        }
        return true;
    }


    /**
     * 开启收集swf链接任务,隐藏按钮1,显示按钮2、3
     */
    function main$1() {
        // 已经是高清版
        // 跟随浏览,动态冻结页面元素
        utils.scrollFunc(storeSWFLinks_Woc88, window.woc88JS, 10, 30, "woc88元素: 收集", inner_window);

        // 绑定按钮的触发函数
        utils.setBtnEvent(btn2SaveSwfUrls, [], "btn_2", "导出swf链接");
        utils.setBtnEvent(() => {
            let hints = [
                "本脚本无需【flash】。",
                "你的电脑上无需安装任何flash player也可以使用。",
                "导出 media-urls.csv 后,",
                "需要使用【资源下载器】来取得swf文档资源。",
                "详细用法请访问脚本主页: ",
                "https://greatest.deepsurf.us/zh-CN/scripts/435884-wenku-doc-downloader"
            ];
            alert(hints.join("\n"));
        }, [], "btn_3", "友情提示");

        // 隐藏按钮
        utils.toggleBtnStatus("btn_1");
        // 显示按钮
        utils.toggleBtnStatus("btn_2");
        utils.toggleBtnStatus("btn_3");
    }


    function switch2HD() {
        // 取得页面左侧【高清版】切换功能区
        let hd_bar = inner_window.document.querySelector("#gaoging");
        // 取得功能区内【切换高清版】按钮
        let hd_button = hd_bar.querySelector("#gqts span[onclick]");
        hd_button.click();
        // 开启主任务
        setTimeout(main$1, 1000);
    }


    /**
     * 根据键中的id数字对map排序
     * @param {Map} elems_map 
     * @returns sorted_map
     */
    function sortMapByID(elems_map) {
        // id形式:5, 类型: int
        let elems_arr = Array.from(elems_map);
        elems_arr.sort((id1, id2) => {
            // 升序排序
            return id1 - id2;
        });
        // 返回排序好的map
        return new Map(elems_arr);
    }


    /**
     * 存储swf链接。适用于帮帮文库。
     * @param {String} inner_selector iframe#test 框架下的【swf链接所在元素】选择器
     */
    function storeSWFLinks_Woc88(inner_selector = "div[id*=imgcount] > div > object") {
        let elems_map = window.woc88JS.elems_map;
        inner_window.document.querySelectorAll(inner_selector).forEach(
            (elem) => {
                // 复制链接防止丢失
                // 拿到id
                let id_elem = elem.parentElement.parentElement.id;
                let id = parseInt(id_elem.replace("imgcount", ""));
                // 拿到link
                let link = elem.data;
                // 储存id: link
                elems_map.set(id, link);
            });
        if (elems_map.size === window.woc88JS.max_page) {
            // 根据id排序,保证导出的图片链接不是乱序的
            window.woc88JS.elems_map = sortMapByID(window.woc88JS.elems_map);
            // 已经保存完全部文档页元素,移除滚动事件的绑定函数
            window.woc88JS.finished = true;
            inner_window.removeEventListener("scroll", window.woc88JS.scrollFunc, false);
        }
    }


    /**
     * 导出swf链接到csv文件。需要配合【资源下载器】使用。
     */
    function saveSwfUrls_Woc88() {
        let urls = [];
        window.woc88JS.elems_map.forEach((url) => {
            // 遍历map就是遍历value
            urls.push(url);
        });
        utils.createAndDownloadFile("media-urls.csv", urls.join("\n"));
    }


    /**
     * 返回一个列表,包含所有未捕获的页码
     * @returns 未捕获页码列表
     */
    function getNotStored() {
        // 取得全部页码
        let max_page = window.woc88JS.max_page;
        // 取得未捕获页码
        let stored = new Set(window.woc88JS.elems_map.keys());
        let not_stored = [];
        for (let i = 1; i <= max_page; i++) {
            // 如果当前页码未被捕获,则录入not_stored
            if (!stored.has(i)) {
                not_stored.push(i);
            }
        }
        return not_stored;
    }


    function btn2SaveSwfUrls() {
        if (!window.woc88JS.finished) {
            let hints = [
                "仍有内容未加载完,无法使用该功能",
                "请再次浏览未加载出的页面,未加载的页码如下",
                getNotStored().join(",")
            ];
            alert(hints.join("\n"));
            return;
        }
        saveSwfUrls_Woc88();
    }


    /**
     * 帮帮文库下载策略
     */
    function woc88() {
        // 1 初始环境配置
        // 取得iframe的window
        window.inner_window = document.querySelector("#test").contentWindow;
        // 设置全局变量
        window.woc88JS = {
            max_page: countPages(), // 当前文档页面数量
            finished: false, // 是否收集完了全部文档页元素
            scroll_count: 0, // 用于统计累计触发scroll的次数
            elems_map: new Map(), // 存储已经捕获的元素: {id: element}
            scrollFunc: null
        };

        // 2 主任务
        // 创建按钮组
        utils.createBtns();
        // 如果不是高清版,先切换至高清版
        if (!isHD()) {
            utils.setBtnEvent(switch2HD, [], "btn_1", "高清版");
            return;
        }
        main$1();
    }

    /**
     * 主函数:识别网站,执行对应文档下载策略
     */
    function main() {
        let host = window.location.host;
        console.log(`当前host: ${host}`);
        window.user_utils = utils;
        console.log("wk: user_utils已经挂载到全局");

        if (host.includes("docin.com")) {
            docin();
        } else if (host === "ishare.iask.sina.com.cn") {
            ishare();
        } else if (host === "www.deliwenku.com") {
            deliwenku();
        } else if (host === "www.doc88.com") {
            doc88();
        } else if (host === "www.360doc.com") {
            doc360();
        } else if (host === "wenku.baidu.com") {
            baiduWenku();
        } else if (host === "doc.mbalib.com") {
            mbalib();
        } else if (host === "www.woc88.com") {
            woc88();
        } else {
            console.log("匹配到了无效网页");
        }
    }

    let options = {
        fast_mode: false,
        activation_test: false
    };
    
    if (options.cli_mode) {
        (() => {
            loadExternalScripts();
            setTimeout(main, 2000);
            return;
        })();
    }
    
    if (options.activation_test) {
        alert(`Wenku Doc Downloader 已经生效!\n当前网址:\n${window.location.host}`);
    }
    
    if (options.fast_mode) {
        main();
    } else {
        window.onload = main;
    }

})();