Greasy Fork is available in English.

学习强国梨酱小帮手

页面内登录/搜索+视频/音频/电子书一键批量下载+拦截Log请求+电子书去水印

// ==UserScript==
// @name         学习强国梨酱小帮手
// @namespace    https://qinlili.bid/
// @version      1.1.8
// @description  页面内登录/搜索+视频/音频/电子书一键批量下载+拦截Log请求+电子书去水印
// @author       琴梨梨
// @match        *://www.xuexi.cn/*
// @match        *://boot-source.xuexi.cn/newmoocdown?*
// @match        *://boot-source.xuexi.cn/audiodown?*
// @match        *://preview-pdf.xuexi.cn/*
// @match        *://article.xuexi.cn/*
// @match        https://login.xuexi.cn/login/xuexiWeb?*
// @match        https://static.xuexi.cn/search/*
// @icon         https://www.xuexi.cn/favicon.ico
// @homepage     https://github.com/qinlili23333/XXQG-DL
// @supportURL   https://github.com/qinlili23333/XXQG-DL
// @grant        none
// @run-at       document-end
// @require      https://lib.baomitu.com/jspdf/2.5.1/jspdf.umd.min.js
// @license      Anti996License
// ==/UserScript==


(async function () {
    'use strict';
    //既然连喜欢的人都没能力留住,那还是把更多时间投入到写代码吧--记于与悦悦子分手的7天之后(2022.1.11)
    //这些内容给本项目开发提供了帮助,感谢
    //https://stackoverflow.com/a/60644673
    //https://stackoverflow.com/a/55165133
    //也感谢每一位相信琴梨梨的用户

    //真的有人会看琴梨梨写的注释吗?在看的话MUA你一下~

    //是否开启跨源服务器
    //开启跨源服务器可以下载部分本来会出错的电子书
    //请访问此地址获取跨源服务器https://github.com/Rob--W/cors-anywhere
    //Clone到本地后运行npm install,然后运行node server.js,即可运行在默认地址和端口上
    var corsServer = "http://localhost:8080/";
    if (document.location.host == "preview-pdf.xuexi.cn" && (document.location.search.indexOf("boot-video.xuexi.cn") > 1) && (window.self === window.top) && confirm("该地址可能需要跨源服务器下载。启用跨源服务器吗?请在确认跨源服务器已启动之后点击确定。\n不知道跨源服务器是什么的话打开脚本源码看注释")) {
        var valueProp = Object.getOwnPropertyDescriptor(Image.prototype, 'src');
        Object.defineProperty(Image.prototype, 'src', {
            set: function(newimgValue){
                if (!newimgValue.startsWith("data:")) {
                    newimgValue = corsServer + newimgValue;
                }
                this.crossOrigin = "anonymous"
                valueProp.set.call(this, newimgValue);
            }
        });
    }
    //iframe页面处理
    if (!(window.self === window.top)) {
        var transparentStyle = "background:none transparent !important;";
        document.documentElement.style = transparentStyle;
        document.body.style = transparentStyle;
        if (document.location.href.indexOf("login.xuexi.cn/login/xuexiWeb?") > 1) {
            document.getElementsByClassName("login_content")[0].style.background = "none"
        };
        if (document.location.href.indexOf("static.xuexi.cn/search/online/index.html") > 1) {
            document.getElementById("root").style.background = transparentStyle;
            if ((window.self.innerWidth > window.self.innerHeight) && window.self.innerWidth > 1000) {
                document.getElementsByClassName("search-content")[0].style = "padding-left:20px;padding-right:20px;"
            }
        };
    }
    //干掉日志
    (open=> {
        XMLHttpRequest.prototype.open = function (method, url, async, user, pass) {
            if (!(async === false)) {
                async = true;
            };
            if (url.startsWith("https://iflow-api.xuexi.cn/logflow/api/v1/pclog") || url.startsWith("https://arms-retcode.aliyuncs.com/r.png")) {
                console.log("Rejected Log XHR! " + url + " -Qinlili")
                url = "data:text,null"
            }
            open.call(this, method, url, async, user, pass);
        };
    })(XMLHttpRequest.prototype.open);
    var originFetch = fetch;
    window.fetch = (url, options) => {
        if (url.startsWith("https://iflow-api.xuexi.cn/logflow/api/v1/pclog") || url.startsWith("https://arms-retcode.aliyuncs.com/r.png")) {
            console.log("Rejected Log Fetch! " + url + " -Qinlili")
            url = "data:text,null"
        }
        return originFetch(url, options);
    }
    //干掉PDF水印
    if (document.location.host == "preview-pdf.xuexi.cn") {
        CanvasRenderingContext2D.prototype.fillText = () => { }
    }
    //共享库
    const SakiProgress = {
        isLoaded: false,
        progres: false,
        pgDiv: false,
        textSpan: false,
        first: false,
        alertMode: false,
        init: function (color) {
            if (!this.isLoaded) {
                this.isLoaded = true;
                console.info("SakiProgress Initializing!\nVersion:1.0.3\nQinlili Tech:Github@qinlili23333");
                this.pgDiv = document.createElement("div");
                this.pgDiv.id = "pgdiv";
                this.pgDiv.style = "z-index:9999;position:fixed;background-color:white;min-height:32px;width:auto;height:32px;left:0px;right:0px;top:0px;box-shadow:0px 2px 2px 1px rgba(0, 0, 0, 0.5);transition:opacity 0.5s;display:none;";
                this.pgDiv.style.opacity = 0;
                this.first = document.body.firstElementChild;
                document.body.insertBefore(this.pgDiv, this.first);
                this.first.style.transition = "margin-top 0.5s"
                this.progress = document.createElement("div");
                this.progress.id = "dlprogress"
                this.progress.style = "position: absolute;top: 0;bottom: 0;left: 0;background-color: #F17C67;z-index: -1;width:0%;transition: width 0.25s ease-in-out,opacity 0.25s,background-color 1s;"
                if (color) {
                    this.setColor(color);
                }
                this.pgDiv.appendChild(this.progress);
                this.textSpan = document.createElement("span");
                this.textSpan.style = "padding-left:4px;font-size:24px;";
                this.textSpan.style.display = "inline-block"
                this.pgDiv.appendChild(this.textSpan);
                var css = ".barBtn:hover{ background-color: #cccccc }.barBtn:active{ background-color: #999999 }";
                var style = document.createElement('style');
                if (style.styleSheet) {
                    style.styleSheet.cssText = css;
                } else {
                    style.appendChild(document.createTextNode(css));
                }
                document.getElementsByTagName('head')[0].appendChild(style);
                console.info("SakiProgress Initialized!");
            } else {
                console.error("Multi Instance Error-SakiProgress Already Loaded!");
            }
        },
        destroy: function () {
            if (this.pgDiv) {
                document.body.removeChild(this.pgDiv);
                this.isLoaded = false;
                this.progres = false;
                this.pgDiv = false;
                this.textSpan = false;
                this.first = false;
                console.info("SakiProgress Destroyed!You Can Reload Later!");
            }
        },
        setPercent: function (percent) {
            if (this.progress) {
                this.progress.style.width = percent + "%";
            } else {
                console.error("Not Initialized Error-Please Call `init` First!");
            }
        },
        clearProgress: function () {
            if (this.progress) {
                this.progress.style.opacity = 0;
                setTimeout(function () { SakiProgress.progress.style.width = "0%"; }, 500);
                setTimeout(function () { SakiProgress.progress.style.opacity = 1; }, 750);
            } else {
                console.error("Not Initialized Error-Please Call `init` First!")
            }
        },
        hideDiv: function () {
            if (this.pgDiv) {
                if (this.alertMode) {
                    setTimeout(function () {
                        SakiProgress.pgDiv.style.opacity = 0;
                        SakiProgress.first.style.marginTop = "";
                        setTimeout(function () {
                            SakiProgress.pgDiv.style.display = "none";
                        }, 500);
                    }, 3000);
                } else {
                    this.pgDiv.style.opacity = 0;
                    this.first.style.marginTop = "";
                    setTimeout(function () {
                        SakiProgress.pgDiv.style.display = "none";
                    }, 500);
                }
            }
            else {
                console.error("Not Initialized Error-Please Call `init` First!");
            }
        },
        showDiv: function () {
            if (this.pgDiv) {
                this.pgDiv.style.display = "";
                setTimeout(function () { SakiProgress.pgDiv.style.opacity = 1; }, 10);
                this.first.style.marginTop = (this.pgDiv.clientHeight + 8) + "px";
            }
            else {
                console.error("Not Initialized Error-Please Call `init` First!");
            }
        },
        setText: function (text) {
            if (this.textSpan) {
                if (this.alertMode) {
                    setTimeout(function () {
                        if (!SakiProgress.alertMode) {
                            SakiProgress.textSpan.innerText = text;
                        }
                    }, 3000);
                } else {
                    this.textSpan.innerText = text;
                }
            }
            else {
                console.error("Not Initialized Error-Please Call `init` First!");
            }
        },
        setTextAlert: function (text) {
            if (this.textSpan) {
                this.textSpan.innerText = text;
                this.alertMode = true;
                setTimeout(function () { this.alertMode = false; }, 3000);
            }
            else {
                console.error("Not Initialized Error-Please Call `init` First!");
            }
        },
        setColor: function (color) {
            if (this.progress) {
                this.progress.style.backgroundColor = color;
            }
            else {
                console.error("Not Initialized Error-Please Call `init` First!");
            }
        },
        addBtn: function (img) {
            if (this.pgDiv) {
                var btn = document.createElement("img");
                btn.style = "display: inline-block;right:0px;float:right;height:32px;width:32px;transition:background-color 0.2s;"
                btn.className = "barBtn"
                btn.src = img;
                this.pgDiv.appendChild(btn);
                return btn;
            }
            else {
                console.error("Not Initialized Error-Please Call `init` First!");
            }
        },
        removeBtn: function (btn) {
            if (this.pgDiv) {
                if (btn) {
                    this.pgDiv.removeChild(btn);
                }
            }
            else {
                console.error("Not Initialized Error-Please Call `init` First!");
            }
        }
    }
    const XHRDL = {
        isLoaded: false,
        dlList: [],
        listBtn: false,
        listDiv: false,
        listBar: false,
        clsBtn: false,
        init: function () {
            if (!this.isLoaded) {
                console.info("WebXHRDL Initializing!\nVersion:Preview0.1.0\nQinlili Tech:Github@qinlili23333")
                try {
                    SakiProgress.init();
                } catch {
                    console.error("Initialize Failed!Is SakiProgress Loaded?")
                    return false;
                }
                this.isLoaded = true;
                //this.listBtn = SakiProgress.addBtn("data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIGVuYWJsZS1iYWNrZ3JvdW5kPSJuZXcgMCAwIDI0IDI0IiBoZWlnaHQ9IjQ4cHgiIHZpZXdCb3g9IjAgMCAyNCAyNCIgd2lkdGg9IjQ4cHgiIGZpbGw9IiMwMDAwMDAiPjxnPjxyZWN0IGZpbGw9Im5vbmUiIGhlaWdodD0iMjQiIHdpZHRoPSIyNCIvPjwvZz48Zz48Zz48cGF0aCBkPSJNMTguMzIsNC4yNkMxNi44NCwzLjA1LDE1LjAxLDIuMjUsMTMsMi4wNXYyLjAyYzEuNDYsMC4xOCwyLjc5LDAuNzYsMy45LDEuNjJMMTguMzIsNC4yNnogTTE5LjkzLDExaDIuMDIgYy0wLjItMi4wMS0xLTMuODQtMi4yMS01LjMyTDE4LjMxLDcuMUMxOS4xNyw4LjIxLDE5Ljc1LDkuNTQsMTkuOTMsMTF6IE0xOC4zMSwxNi45bDEuNDMsMS40M2MxLjIxLTEuNDgsMi4wMS0zLjMyLDIuMjEtNS4zMiBoLTIuMDJDMTkuNzUsMTQuNDYsMTkuMTcsMTUuNzksMTguMzEsMTYuOXogTTEzLDE5LjkzdjIuMDJjMi4wMS0wLjIsMy44NC0xLDUuMzItMi4yMWwtMS40My0xLjQzIEMxNS43OSwxOS4xNywxNC40NiwxOS43NSwxMywxOS45M3ogTTEzLDEyVjdoLTJ2NUg3bDUsNWw1LTVIMTN6IE0xMSwxOS45M3YyLjAyYy01LjA1LTAuNS05LTQuNzYtOS05Ljk1czMuOTUtOS40NSw5LTkuOTV2Mi4wMiBDNy4wNSw0LjU2LDQsNy45Miw0LDEyUzcuMDUsMTkuNDQsMTEsMTkuOTN6Ii8+PC9nPjwvZz48L3N2Zz4=");
                //this.listBtn.onclick = XHRDL.showList;
                SakiProgress.showDiv();
                SakiProgress.setText("初始化下载器...");
                SakiProgress.setPercent(20);
                this.listDiv = document.createElement("div");
                this.listDiv.style = "z-index:9999;position:fixed;background-color:white;width:auto;margin-top:32px;height:100%;left:0px;right:0px;top:0px;transition:opacity 0.5s;display:none;";
                this.listDiv.style.opacity = 0;
                this.listBar = document.createElement("div");
                this.listBar.style = "z-index:10000;position:fixed;background-color:white;min-height:32px;margin-top:32px;width:auto;height:32px;left:0px;right:0px;top:0px;box-shadow:0px 2px 2px 1px rgba(0, 0, 0, 0.5);";
                this.listDiv.appendChild(this.listBar);
                document.body.appendChild(this.listDiv);
                var btn = document.createElement("img");
                btn.style = "display: inline-block;right:0px;float:right;height:32px;width:32px;transition:background-color 0.2s;"
                btn.className = "barBtn"
                btn.src = "data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIGVuYWJsZS1iYWNrZ3JvdW5kPSJuZXcgMCAwIDI0IDI0IiBoZWlnaHQ9IjQ4cHgiIHZpZXdCb3g9IjAgMCAyNCAyNCIgd2lkdGg9IjQ4cHgiIGZpbGw9IiMwMDAwMDAiPjxyZWN0IGZpbGw9Im5vbmUiIGhlaWdodD0iMjQiIHdpZHRoPSIyNCIvPjxwYXRoIGQ9Ik0yMiwzLjQxbC01LjI5LDUuMjlMMjAsMTJoLThWNGwzLjI5LDMuMjlMMjAuNTksMkwyMiwzLjQxeiBNMy40MSwyMmw1LjI5LTUuMjlMMTIsMjB2LThINGwzLjI5LDMuMjlMMiwyMC41OUwzLjQxLDIyeiIvPjwvc3ZnPg==";
                this.listBar.appendChild(btn);
                btn.onclick = function () {
                    XHRDL.hideList();
                }
                this.clsBtn = btn;
                SakiProgress.setPercent(100);
                SakiProgress.setText("下载器已加载!");
                setTimeout(function () { SakiProgress.clearProgress(); SakiProgress.hideDiv(); }, 1000);
                console.info("WebXHRDL Initialized!");
            } else {
                console.error("Multi Instance Error-WebXHRDL Already Loaded!")
            }
        },
        destroy: function (saki) {
            if (this.isLoaded) {
                if (saki) {
                    SakiProgress.destroy();
                }
                this.isLoaded = false;
                this.dlList = [];
                this.listBtn = false;
                this.listDiv = false;
                this.listBar = false;
                this.clsBtn = false;
                console.info("WebXHRDL Destroyed!You Can Reload Later!");
            }
        },
        showList: function () {
            if (XHRDL.isLoaded) {
                XHRDL.listDiv.style.display = "";
                setTimeout(function () { XHRDL.listDiv.style.opacity = 1; }, 10);
            } else {
                console.error("Not Initialized Error-Please Call `init` First!")
            }
        },
        hideList: function () {
            if (XHRDL.isLoaded) {
                XHRDL.listDiv.style.opacity = 0;
                setTimeout(function () { XHRDL.listDiv.style.display = "none"; }, 500);
            } else {
                console.error("Not Initialized Error-Please Call `init` First!")
            }
        },
        saveTaskList: function () {
            if (XHRDL.isLoaded) {
                var storage = window.localStorage;
                storage.setItem("XHRDL_List", JSON.stringify(this.dlList));
            } else {
                console.error("Not Initialized Error-Please Call `init` First!")
            }
        },
        loadTaskList: function () {
            if (XHRDL.isLoaded) {
                var storage = window.localStorage;
                this.dlList = JSON.parse(storage.getItem("XHRDL_List"));
            } else {
                console.error("Not Initialized Error-Please Call `init` First!")
            }
        },
        newTask: function (url, name) {
            if (this.isLoaded) {
                var list = this.dlList;
                list[list.length] = {
                    taskUrl: url,
                    fileName: name
                }
                SakiProgress.showDiv();
                SakiProgress.setText("已添加新任务:" + name);
                if (!this.DLEngine.isWorking) {
                    this.DLEngine.start();
                }
            } else {
                console.error("Not Initialized Error-Please Call `init` First!")
            }
        },
        DLEngine: {
            isWorking: false,
            start: function () {
                if (!this.isWorking) {
                    console.info("Start WebXHRDL Engine...\nChecking Tasks...");
                    this.isWorking = true;
                    SakiProgress.showDiv();
                    this.dlFirstFile();
                } else {
                    console.error("WebXHRDL Engine Already Started!");
                }
            },
            stop: function () {
                this.isWorking = false;
                SakiProgress.hideDiv();
                SakiProgress.setText("");
                if (XHRDL.dlList[0]) {
                    console.info("All Tasks Done!WebXHRDL Engine Stopped!");
                } else {
                    console.info("WebXHRDL Engine Stopped!Tasks Paused!");
                }
            },
            dlFirstFile: function () {
                var taskInfo = XHRDL.dlList[0];
                SakiProgress.showDiv();
                SakiProgress.setPercent(0);
                SakiProgress.setText("正在下载" + taskInfo.fileName);
                var xhr = new XMLHttpRequest();
                xhr.responseType = "blob";
                xhr.onprogress = event => {
                    if (event.loaded && event.total) {
                        var percent = String(Number(event.loaded) / Number(event.total) * 100).substring(0, 4);
                        SakiProgress.setText(taskInfo.fileName + "已下载" + percent + "%");
                        SakiProgress.setPercent(percent)
                    }
                };
                xhr.onload = event => {
                    if (xhr.readyState === 4) {
                        if (xhr.status === 200) {
                            var bloburl = URL.createObjectURL(xhr.response);
                            SakiProgress.setText("正在写出" + taskInfo.fileName);
                            var a = document.createElement('a');
                            var filename = taskInfo.fileName;
                            a.href = bloburl;
                            a.download = filename;
                            a.click();
                            window.URL.revokeObjectURL(bloburl);
                            SakiProgress.clearProgress();
                            XHRDL.dlList.splice(0, 1);
                            XHRDL.DLEngine.checkNext();
                        } else {
                            //TODO:支持更多特殊状态处理
                            SakiProgress.setTextAlert(taskInfo.fileName + "暂不支持下载,跳过");
                            XHRDL.dlList.splice(0, 1);
                            XHRDL.DLEngine.checkNext();
                        }
                    }
                }
                xhr.onerror = function (e) {
                    //TODO:支持处理不同类别出错
                    if (!taskInfo.errorRetry) {
                        SakiProgress.setTextAlert(taskInfo.fileName + "下载失败,置入列尾等待重试");
                        taskInfo.errorRetry = true;
                        var list = XHRDL.dlList;
                        list[list.length] = taskInfo;
                    } else {
                        SakiProgress.setTextAlert(taskInfo.fileName + "下载又失败了,放弃");
                    }
                    XHRDL.dlList.splice(0, 1);
                    XHRDL.DLEngine.checkNext();
                }
                xhr.open('GET', taskInfo.taskUrl, true)
                xhr.send()
            },
            checkNext: function () {
                if (XHRDL.dlList[0]) {
                    this.dlFirstFile();
                } else {
                    this.stop();
                }
            }
        }
    }


    const sleep = delay => new Promise(resolve => setTimeout(resolve, delay));
    const openFrame=url=>{
        var searchFrame = document.createElement("iframe");
        searchFrame.frameBorder = 0;
        searchFrame.style = "padding:100%;z-index:9999;position:fixed;backdrop-filter: blur(10px) brightness(100%);background-color: rgba(255, 255, 255, .6);width:100%;margin-top:0px;height:100%;left:0px;right:0px;top:0px;";
        document.body.appendChild(searchFrame);
        searchFrame.src = url;
        var clsBtn = document.createElement("img");
        clsBtn.style = "z-index:10000;position:fixed;display: inline-block;right:0px;top:0px;float:right;height:32px;width:32px;transition:background-color 0.2s;"
        clsBtn.className = "barBtn"
        clsBtn.src = "data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIGVuYWJsZS1iYWNrZ3JvdW5kPSJuZXcgMCAwIDI0IDI0IiBoZWlnaHQ9IjQ4cHgiIHZpZXdCb3g9IjAgMCAyNCAyNCIgd2lkdGg9IjQ4cHgiIGZpbGw9IiMwMDAwMDAiPjxyZWN0IGZpbGw9Im5vbmUiIGhlaWdodD0iMjQiIHdpZHRoPSIyNCIvPjxwYXRoIGQ9Ik0yMiwzLjQxbC01LjI5LDUuMjlMMjAsMTJoLThWNGwzLjI5LDMuMjlMMjAuNTksMkwyMiwzLjQxeiBNMy40MSwyMmw1LjI5LTUuMjlMMTIsMjB2LThINGwzLjI5LDMuMjlMMiwyMC41OUwzLjQxLDIyeiIvPjwvc3ZnPg==";
        document.body.appendChild(clsBtn);
        searchFrame.addEventListener("load", async () => { await sleep(150); searchFrame.style.padding = "0px"; });
        clsBtn.onclick = () => {
            document.body.removeChild(searchFrame);
            document.body.removeChild(clsBtn);
        }
    };
    //主站检测
    if (document.location.host == "www.xuexi.cn" || document.location.host == "preview-pdf.xuexi.cn") {
        console.log("JS Loaded,Sleep 3 Sec-Qinlili");
        await sleep(3000)
        if (window.self === window.top) {
            //初始化下载工具条
            XHRDL.init();
            var dlPannel = document.createElement("div");
            var downloadBtn = document.createElement("button");
            downloadBtn.innerText = "下载本页内容";
            downloadBtn.style.display = "inline-block";
            dlPannel.appendChild(downloadBtn);
            var dlText = document.createElement("p");
            dlText.style.display = "inline-block";
            dlText.innerText = "等待检测页面类型";
            dlPannel.appendChild(dlText);
            var first = document.body.firstChild;
            document.body.insertBefore(dlPannel, first);
            //接管搜索
            if (document.getElementsByClassName("icon search-icon")[0]) {
                var scBtn = document.getElementsByClassName("icon search-icon")[0];
                var scPrt = scBtn.parentElement;
                scPrt.removeChild(scBtn);
                scBtn = document.createElement("a");
                scBtn.className = "icon search-icon";
                scPrt.appendChild(scBtn);
                scBtn.addEventListener("click", async e => {
                    var searchFrame = document.createElement("iframe");
                    searchFrame.frameBorder = 0;
                    searchFrame.style = "padding:100%;z-index:9999;position:fixed;backdrop-filter: blur(10px) brightness(100%);background-color: rgba(255, 255, 255, .6);width:100%;margin-top:0px;height:100%;left:0px;right:0px;top:0px;";
                    document.body.appendChild(searchFrame);
                    searchFrame.src = "https://static.xuexi.cn/search/online/index.html";
                    var clsBtn = document.createElement("img");
                    clsBtn.style = "z-index:10000;position:fixed;display: inline-block;right:0px;top:0px;float:right;height:32px;width:32px;transition:background-color 0.2s;"
                    clsBtn.className = "barBtn"
                    clsBtn.src = "data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIGVuYWJsZS1iYWNrZ3JvdW5kPSJuZXcgMCAwIDI0IDI0IiBoZWlnaHQ9IjQ4cHgiIHZpZXdCb3g9IjAgMCAyNCAyNCIgd2lkdGg9IjQ4cHgiIGZpbGw9IiMwMDAwMDAiPjxyZWN0IGZpbGw9Im5vbmUiIGhlaWdodD0iMjQiIHdpZHRoPSIyNCIvPjxwYXRoIGQ9Ik0yMiwzLjQxbC01LjI5LDUuMjlMMjAsMTJoLThWNGwzLjI5LDMuMjlMMjAuNTksMkwyMiwzLjQxeiBNMy40MSwyMmw1LjI5LTUuMjlMMTIsMjB2LThINGwzLjI5LDMuMjlMMiwyMC41OUwzLjQxLDIyeiIvPjwvc3ZnPg==";
                    document.body.appendChild(clsBtn);
                    searchFrame.addEventListener("load", async () => { await sleep(150); searchFrame.style.padding = "0px"; });
                    clsBtn.onclick = () => {
                        document.body.removeChild(searchFrame);
                        document.body.removeChild(clsBtn);
                        window.removeEventListener("message", msg, false);
                    }
                    function msg(e) {
                        //抄的官方JS改出来的,我对于这种非要传值到上层窗口的做法完全无法理解,但既然能跑,管他呢
                        console.log('e:', e)
                        console.log('e.data:', e.data)
                        try {
                            var params = JSON.parse(e.data);
                            if (params.type) {
                                console.log('params.type:', params.type);
                                console.log('params.data:', params.data);
                                switch (params.type) {
                                    case 'search':
                                        var useQuestionMark = false;
                                        var targetUrl = 'https://static.xuexi.cn/search/online/index.html'
                                        for (var key in params.data) {
                                            var value = params.data[key];
                                            var op = '&'
                                            if (!useQuestionMark) {
                                                op = '?';
                                                useQuestionMark = true;
                                            }
                                            targetUrl += op + key + '=' + value;
                                        }
                                        searchFrame.style.padding = "100%";
                                        searchFrame.src = targetUrl;
                                        break;
                                    default:
                                        break;
                                }
                            }
                        } catch (error) {
                            console.log(error)
                        }
                    }
                    window.addEventListener("message", msg, false);
                })
            }
            //接管登录
            if (document.getElementsByClassName("icon login-icon")[0]) {
                var dlBtn = document.getElementsByClassName("icon login-icon")[0];
                var dlPrt = dlBtn.parentElement;
                dlPrt.removeChild(dlBtn);
                dlBtn = document.createElement("a");
                dlBtn.className = "icon login-icon";
                dlPrt.appendChild(dlBtn);
                dlBtn.addEventListener("click", async e => {
                    e.preventDefault();
                    SakiProgress.showDiv();
                    SakiProgress.setPercent(5);
                    SakiProgress.setText("正在准备登录...")
                    console.log("Login Hooked!-Qinlili");
                    var closeBtn = SakiProgress.addBtn("data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIGVuYWJsZS1iYWNrZ3JvdW5kPSJuZXcgMCAwIDI0IDI0IiBoZWlnaHQ9IjQ4cHgiIHZpZXdCb3g9IjAgMCAyNCAyNCIgd2lkdGg9IjQ4cHgiIGZpbGw9IiMwMDAwMDAiPjxyZWN0IGZpbGw9Im5vbmUiIGhlaWdodD0iMjQiIHdpZHRoPSIyNCIvPjxwYXRoIGQ9Ik0yMiwzLjQxbC01LjI5LDUuMjlMMjAsMTJoLThWNGwzLjI5LDMuMjlMMjAuNTksMkwyMiwzLjQxeiBNMy40MSwyMmw1LjI5LTUuMjlMMTIsMjB2LThINGwzLjI5LDMuMjlMMiwyMC41OUwzLjQxLDIyeiIvPjwvc3ZnPg==")
                    var loginFrame = document.createElement("iframe");
                    loginFrame.frameBorder = 0;
                    loginFrame.scrolling = "no";
                    loginFrame.style = "padding:100%;z-index:9999;position:fixed;backdrop-filter: blur(10px) brightness(100%);background-color: rgba(255, 255, 255, .6);width:100%;margin-top:32px;height:100%;left:0px;right:0px;top:0px;";
                    document.body.appendChild(loginFrame);
                    loginFrame.addEventListener("load", async function () { await sleep(250); loginFrame.style.padding = "0px"; });
                    var refreshBtn = SakiProgress.addBtn("data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIGhlaWdodD0iNDhweCIgdmlld0JveD0iMCAwIDI0IDI0IiB3aWR0aD0iNDhweCIgZmlsbD0iIzAwMDAwMCI+PHBhdGggZD0iTTAgMGgyNHYyNEgweiIgZmlsbD0ibm9uZSIvPjxwYXRoIGQ9Ik0xNy42NSA2LjM1QzE2LjIgNC45IDE0LjIxIDQgMTIgNGMtNC40MiAwLTcuOTkgMy41OC03Ljk5IDhzMy41NyA4IDcuOTkgOGMzLjczIDAgNi44NC0yLjU1IDcuNzMtNmgtMi4wOGMtLjgyIDIuMzMtMy4wNCA0LTUuNjUgNC0zLjMxIDAtNi0yLjY5LTYtNnMyLjY5LTYgNi02YzEuNjYgMCAzLjE0LjY5IDQuMjIgMS43OEwxMyAxMWg3VjRsLTIuMzUgMi4zNXoiLz48L3N2Zz4=")
                    refreshBtn.onclick = () => {
                        scanLogin();
                    }
                    closeBtn.onclick = () => {
                        document.body.removeChild(loginFrame);
                        SakiProgress.removeBtn(closeBtn);
                        SakiProgress.removeBtn(refreshBtn);
                        closeBtn = false;
                        SakiProgress.clearProgress();
                        SakiProgress.hideDiv();
                    }
                    async function scanLogin() {
                        await sleep(100);
                        SakiProgress.setPercent(10);
                        SakiProgress.setText("正在获取登录口令...");
                        let token;
                        let tokenText;
                        try {
                            token = await fetch("https://pc-api.xuexi.cn/open/api/sns/sign")
                            tokenText = JSON.parse(await token.text())
                        } catch (e) {
                            SakiProgress.setPercent(100);
                            SakiProgress.setText("出错了:网络连接中断! " + e.message);
                            return;
                        }
                        if (tokenText.code = "200") {
                            token = tokenText.data.sign;
                            SakiProgress.setPercent(40);
                            SakiProgress.setText("口令获取成功,加载登录页面...");
                            loginFrame.onload = () => {
                                SakiProgress.setPercent(65);
                                SakiProgress.setText("等待扫码...");
                            }
                            loginFrame.style.padding = "100%";
                            loginFrame.src = "https://login.xuexi.cn/login/xuexiWeb?appid=dingoankubyrfkttorhpou&goto=https%3A%2F%2Foa.xuexi.cn&type=1&state=" + token + "&check_login=https%3A%2F%2Fpc-api.xuexi.cn"
                            window.addEventListener("message", event => {
                                event.preventDefault();
                                console.log(event);
                                if (event.data.success == true) {
                                    SakiProgress.setPercent(100);
                                    SakiProgress.setText("登录成功,正在刷新页面...");
                                    document.location.reload();
                                } else {
                                    SakiProgress.setPercent(100);
                                    SakiProgress.setText("出错了:扫码登录失败,错误码为" + event.data.errorCode);
                                }
                            }, false);
                        } else {
                            SakiProgress.setPercent(100);
                            SakiProgress.setText("出错了:获取口令失败!");
                        }
                    }
                    scanLogin();
                })
            }
            //检测爬取页面类型
            console.log("Detecting Page " + document.location.pathname + "-Qinlili");
            var detected = false;
            //旧慕课列表
            if ((document.body.innerText.indexOf("课程介绍") >= 1) || (document.body.innerText.indexOf("课程详情") >= 1)) {
                console.log("Old Mooc List Detected " + document.location.pathname + "-Qinlili");
                detected = true;
                dlText.innerText = "页面类型:旧慕课列表,支持全部批量下载,请开启网站自动下载权限";
                downloadBtn.onclick = () => {
                    OldMoocListDL();
                }
            }
            //页面有播放器
            if (window.Aliplayer) {
                //旧慕课、电视剧播放单页
                if (document.getElementsByClassName("radio-inline")[0]) {
                    console.log("Old Video Player Detected " + document.location.pathname + "-Qinlili");
                    detected = true;
                    dlText.innerText = "页面类型:旧视频播放单页,支持全部批量下载,请开启网站自动下载权限";
                    downloadBtn.onclick = () => {
                        OldMoocVideoDL();
                    }
                }
                //新慕课、影视总页
                if (document.getElementsByClassName("video-article-content")[0] || document.getElementsByClassName("videoSet-article-wrap")[0]) {
                    console.log("New Video Player Detected " + document.location.pathname + "-Qinlili");
                    detected = true;
                    dlText.innerText = "页面类型:新视频总,支持全部批量下载最高清晰度,需要打开新标签页下载";
                    downloadBtn.onclick = () => {
                        NewMoocPageDL();
                    }
                }
            }
            //音频专题
            if (document.getElementsByClassName("album-play-btn")[0]) {
                console.log("Audio Detected " + document.location.pathname + "-Qinlili");
                detected = true;
                dlText.innerText = "页面类型:音频,支持全部批量下载,需要打开新标签页下载";
                downloadBtn.onclick = () => {
                    AudioDL();
                }
            }
            //页面上就一个音频
            //解锁音频播放器下载按钮
            if (document.getElementsByTagName("audio").length) {
                detected = true;
                dlText.innerText = "页面类型:单个音频,已经解锁播放器下载能力,点击播放器右侧菜单下载";
                downloadBtn.style.display = "none";
                for (var la = 0; document.getElementsByTagName("audio")[la]; la++) {
                    document.getElementsByTagName("audio")[la].removeAttribute("controlslist");
                }
            }
            //电子书下载
            if (document.location.host == "preview-pdf.xuexi.cn") {
                detected = true;
                dlText.innerText = "页面类型:电子书,支持打包下载";
                downloadBtn.onclick = () => {
                    PDFDL();
                }
            }
            if (!detected) {
                console.log("Unsupported Page " + document.location.pathname + "-Qinlili");
                dlText.innerText = "本页面不支持下载";
                downloadBtn.innerText = "暂不支持";
            }

            //下载器部分
            //旧慕课列表
            function OldMoocListDL() {
                //读取全部视频列表
                var videoList = globalCache[Object.keys(globalCache)[0]];
                console.log("Found " + videoList.length + " Videos-Qinlili")
                for (var i = 0; videoList[i]; i++) {
                    console.log("Try Analysis " + i + " Video-Qinlili")
                    getInfoAndDL(pagetoinfourl(videoList[i].static_page_url));
                }

            }
            //旧慕课播放单页
            function OldMoocVideoDL() {
                console.log("Analysis Page Info-Qinlili")
                getInfoAndDL(pagetoinfourl(document.location.href))
            }
            function NewMoocPageDL() {
                console.log("Open DL Page-Qinlili");
                var searchParams = new URLSearchParams(document.location.search);
                var dlurl = "https://boot-source.xuexi.cn/newmoocdown?id=" + searchParams.get("id");
                openFrame(dlurl);
            }
            function AudioDL() {
                console.log("Open DL Page-Qinlili");
                var searchParams = new URLSearchParams(document.location.search);
                var dlurl = "https://boot-source.xuexi.cn/audiodown?id=" + searchParams.get("id");
                openFrame(dlurl);
            }
            async function PDFDL() {
                //webp压缩用处和顶碗人一样大,所以换成灰度压缩
                let enableGreyCompress = confirm("是否启用灰度压缩?\n适合保存以黑白文本内容的书籍或用于Kindle等墨水屏阅读,可大幅削减文件体积,需额外消耗压缩时间。\n根据琴梨梨自己的测试可削减大约44%大小,可用于解决Chrome无法爬512M以上书的问题。")
                let lowQuality = confirm("是否启用低质量模式?\n轻微降低画质换取显著的文件缩小");
                SakiProgress.showDiv();
                SakiProgress.setText("正在加载依赖...");
                await sleep(100)
                console.log("Preparing jsPDF Library...-Qinlili");
                var jsPDF = jspdf.jsPDF;
                try {
                    console.log(jsPDF)
                    console.log("jsPDF Ready!")
                } catch {
                    console.error("jsPDF Not Ready!")
                    alert("jsPDF加载失败,请检查网络并尝试重新安装脚本!")
                }
                SakiProgress.setText("正在调整尺寸...");
                SakiProgress.setPercent(2);
                await sleep(100)
                if(confirm("是否开启高清模式?请观察原书清晰度,若原书清晰度较高建议开启。\n对超过300页的书开启可能导致无法生成文件。")){
                    //按钮循环点击得稍微延迟一点否则可能卡死
                    //放大到最大保障清晰度
                    for (; document.getElementsByClassName("ctrl-icon")[0].className.animVal.indexOf("disabled") < 0;) {
                        document.getElementsByClassName("ctrl-icon")[0].parentElement.click()
                        await sleep(50)
                    }
                }
                SakiProgress.setText("正在回到第一页...");
                SakiProgress.setPercent(4);
                await sleep(100)
                //回到第一页
                for (; document.getElementsByClassName("ctrl-icon")[2].className.animVal.indexOf("disabled") < 0;) {
                    document.getElementsByClassName("ctrl-icon")[2].parentElement.click()
                    await sleep(50)
                }
                //创建文件
                SakiProgress.setText("正在创建文件...");
                SakiProgress.setPercent(6);
                await sleep(100)
                var samplePage = document.getElementsByTagName("canvas")[0]
                var ori;
                let wP = samplePage.width;
                let hP = samplePage.height;
                if (wP > hP) { ori = "l" } else { ori = "p" }
                var PDFfile = new jsPDF({
                    orientation: ori,
                    unit: 'px',
                    format: [wP, hP],
                    putOnlyUsedFonts: true,
                });
                console.log("Preparing PDF File...-Qinlili");
                console.log(PDFfile);
                //监听函数
                SakiProgress.setText("正在设置监听函数...");
                SakiProgress.setPercent(8);
                await sleep(100)
                var onPageChange = function () { };
                var val = document.getElementsByTagName("input")[0];
                function waitPageChange() {
                    return new Promise(resolve => {
                        onPageChange = () => {
                            resolve();
                        }
                    });
                }
                //加载完成后页码显示才会变化,监听页码显示来等待加载
                Object.defineProperty(val, 'value', {
                    set: newValue => {
                        var valueProp = Object.getOwnPropertyDescriptor(HTMLInputElement.prototype, 'value');
                        valueProp.set.call(val, newValue);
                        onPageChange();
                    }
                });
                //循环保存
                var page = 1;
                var totalPage = parseInt(document.getElementsByClassName("total")[0].innerText.substr(1));
                const saveCurrent=()=>{
                    //不管有几页,把当前全部canvas保存再说
                    for (var i = 0; document.getElementsByTagName("canvas")[i]; i++) {
                        if (enableGreyCompress) {
                            //灰度压缩
                            let cnv = document.getElementsByTagName("canvas")[i];
                            let cnx = cnv.getContext('2d');
                            let width = cnv.width;
                            let height = cnv.height;
                            var imgPixels = cnx.getImageData(0, 0, width, height);
                            for (var y = 0; y < height; y++) {
                                for (var x = 0; x < width; x++) {
                                    let i = (y * 4) * width + x * 4;
                                    var avg = (imgPixels.data[i] + imgPixels.data[i + 1] + imgPixels.data[i + 2]) / 3;
                                    imgPixels.data[i] = avg;
                                    imgPixels.data[i + 1] = avg;
                                    imgPixels.data[i + 2] = avg;
                                }
                            }
                            cnx.putImageData(imgPixels, 0, 0, 0, 0, imgPixels.width, imgPixels.height);

                        }
                        if(lowQuality){
                            PDFfile.addImage(document.getElementsByTagName("canvas")[i].toDataURL("image/webp",0.75),"WEBP",0,0,wP,hP,null,"SLOW");
                        }else{
                            PDFfile.addImage(document.getElementsByTagName("canvas")[i], "WEBP", 0, 0, wP, hP, null, "SLOW");
                        }
                        PDFfile.addPage();
                        page++
                        console.log("Saved One Page!-Qinlili");
                    }
                }
                for (; document.getElementsByClassName("ctrl-icon")[3].className.animVal.indexOf("disabled") < 0;) {
                    SakiProgress.setText("正在保存第" + page + "页...");
                    SakiProgress.setPercent(10 + 80 * (page / totalPage));
                    console.log("Work Current Page:" + page + "...-Qinlili");
                    await sleep(100)
                    saveCurrent();
                    //虽然不知道为什么加了延迟半秒就不会卡住,但既然能用管他为什么呢
                    setTimeout(() => { document.getElementsByClassName("ctrl-icon")[3].parentElement.click(); }, 500)
                    //显示的正在加载的页面可能比实际加载页面小一页,但估计1919810个用户里也没一个意识到,不影响保存效果这种细节就不管了,问就是爷懒的写
                    SakiProgress.setText("正在等待加载第" + (page + 1) + "页...");
                    console.log("Waiting For Loading...-Qinlili");
                    await waitPageChange();
                };
                saveCurrent();
                PDFfile.setFontSize(40);
                PDFfile.text('Powered By Qinlili',35, 65);
                PDFfile.textWithLink('https://greasyfork.org/zh-CN/scripts/429991',35, 25,{align: 'center', url: 'https://greasyfork.org/zh-CN/scripts/429991'});
                //生成文件导出
                SakiProgress.setText("正在导出文件...");
                SakiProgress.setPercent(90);
                PDFfile.save("学习强国电子书导出.pdf", { returnPromise: true }).then(finish => {
                    SakiProgress.clearProgress;
                    SakiProgress.hideDiv();
                });
            }
        } else {
            console.log("Iframe Page " + document.location.pathname + "\nSkip Detect-Qinlili");
        }
    }

    //全局共享函数
    //读取视频信息并下载
    function getInfoAndDL(infourl) {
        console.log("Get Video Info:" + infourl + "\n-Qinlili")
        var xhr = new XMLHttpRequest();
        xhr.onload = event => {
            console.log("Success Get Video Info:" + infourl + "\n-Qinlili")
            if (xhr.readyState === 4 && xhr.status === 200) {
                var videoInfo = JSON.parse(xhr.response.replace("globalCache = ", "").replace(";", ""));
                stringToObject(videoInfo);
                //判断慕课
                if (videoInfo[Object.keys(videoInfo)[0]].info) {
                    videoInfo = videoInfo[Object.keys(videoInfo)[0]].info;
                    for (var vi = 0; videoInfo.ossUrl[vi]; vi++) {
                        console.log("Video Name:" + videoInfo.frst_name + "-" + (vi + 1) + "\nChapter Name:" + videoInfo.mooc_class + "\nMooc Name:" + videoInfo.mooc + "\nVideo Url:" + videoInfo.ossUrl[vi] + "\n-Qinlili");
                        var filename = videoInfo.frst_name + "-" + (vi + 1) + "-" + videoInfo.mooc_class + "-" + videoInfo.mooc + ".mp4"
                        console.log("File Name:" + filename + "\nPrepare Download-Qinlili");
                        downloadFile(videoInfo.ossUrl[vi], filename);
                    }
                }
                //判断电视剧
                if (videoInfo[Object.keys(videoInfo)[0]].list) {
                    videoInfo = videoInfo[Object.keys(videoInfo)[0]].list;
                    for (var vii = 0; videoInfo[vii]; vii++) {
                        console.log("Video Name:" + videoInfo[vii].frst_name + "-" + (vi + 1) + "\nList Name:" + videoInfo[vii].title + "\nVideo Url:" + videoInfo[vii].ossUrl + "\n-Qinlili");
                        var filename2 = videoInfo[vii].frst_name + "-" + (vi + 1) + "-" + videoInfo[vii].title + ".mp4"
                        console.log("File Name:" + filename2 + "\nPrepare Download-Qinlili");
                        downloadFile(videoInfo[vii].ossUrl, filename2);
                    }
                }
            }
            xhr.onerror = () => {
                console.log("Fail Get Video Info:" + infourl + "\n-Qinlili")
            }
        }
        xhr.open('GET', infourl, false);
        xhr.send();

        //平整化Array工具,从学习强国本身的js里抄过来的,大概原理就是尝试把值作为json解析,解析成功就把解析结果替换回去,总之我大受震撼
        function stringToObject(params) {
            for (var key in params) {
                var value = params[key];
                if (isString(value)) {
                    try {
                        if (typeof JSON.parse(value) == 'object') {
                            params[key] = JSON.parse(value);
                        }
                    } catch (e) { }
                } else if (isArray(value)) {
                    try {
                        for (var index = 0; index < value.length; index++) {
                            stringToObject(value[index]);
                        }
                    } catch (e) { }
                } else if (isObject(value)) {
                    try {
                        stringToObject(params[key])
                    } catch (e) { }
                }
            }
        }

        function isArray(o) {
            return Object.prototype.toString.call(o) === '[object Array]';
        }

        function isString(o) {
            return Object.prototype.toString.call(o) === '[object String]';
        }

        function isObject(o) {
            return Object.prototype.toString.call(o) === '[object Object]';
        }
    }

    //跳转避免跨域问题
    if (document.location.host == "article.xuexi.cn") {
        //检测是不是PDF页面,移动端分享文章也是这个域名,之前没发现
        var obj = document.getElementsByTagName("link");
        var isPDF = false;
        for (var pdfJS = 0; obj[pdfJS]; pdfJS++) {
            if (obj[pdfJS].href.indexOf("js/pdf") > 1) {
                isPDF = true;
            }
        }
        if (isPDF) {
            console.log("JS Loaded,Sleep 5 Sec-Qinlili");
            var tip = document.createElement("H1");
            tip.innerText = "即将跳转页面,请等待五秒";
            tip.style = "z-index:9999;position:fixed;backdrop-filter: blur(10px) brightness(100%);background-color: rgba(255, 255, 255, .6);width:100%;margin-top:0px;height:100%;left:0px;right:0px;top:0px;";
            document.body.appendChild(tip);
            await sleep(5000)
            document.location.href = document.getElementsByClassName("pdf-iframe")[0].src;
        }
    }


    //404注入页面避免cors
    if (document.location.host == "boot-source.xuexi.cn") {
        console.log("JS Domain Detected, Prepare Inject-Qinlili");
        document.querySelector("body").innerHTML = "<H2>本页面仅用于CORS注入,分享网址没有用,请允许下载多个文件</H2><H4 id=\"logcat\"></H4>"
        XHRDL.init();
        logcat("Initializing Downloader...");
        await sleep(3000)
        var searchParams = new URLSearchParams(document.location.search);
        var vid = searchParams.get("id");
        document.title = "下载:" + vid;
        logcat("Found ID:" + vid);
        logcat("Get Video Info:" + vid)
        var xhr = new XMLHttpRequest();
        xhr.onload = event => {
            if (xhr.readyState === 4 && xhr.status === 200) {
                logcat("Success Get JSON Info:" + vid)
                var videoInfo = JSON.parse(xhr.response.replace("callback(", "").replace("})", "}"));
                console.log(videoInfo);
                logcat("List Name:" + videoInfo.normalized_title)
                logcat("List Origin:" + videoInfo.show_source)
                //文件名后缀
                var filenamesource = "-" + videoInfo.normalized_title + "-" + videoInfo.show_source;
                //视频下载模式
                if (document.location.pathname == "/newmoocdown") {
                    //检测是否为多视频
                    if (videoInfo.sub_items) {
                        logcat("Found " + videoInfo.sub_items.length + " Videos");
                        //循环解析并下载视频
                        for (var vi = 0; videoInfo.sub_items[vi]; vi++) {
                            logcat("Analysis Video " + (vi + 1));
                            //currentVideo,缩写为cV看起来清爽点
                            var cV = videoInfo.sub_items[vi];
                            var vName = cV.title;
                            logcat("Video Name:" + vName);
                            //检测多个清晰度
                            var vurl = getHighest(cV.videos[0].video_storage_info);
                            logcat("Video Url:" + vurl);
                            var fName = vName + filenamesource + ".mp4";
                            logcat("File Name:" + fName);
                            logcat("Call Downloader, Downloader Log Output In F12");
                            downloadFile(vurl, fName);
                        }
                    } else {
                        //单个视频
                        var singlevurl = getHighest(videoInfo.videos[0].video_storage_info);
                        logcat("Video Url:" + singlevurl);
                        vName = videoInfo.title;
                        var sfName = vName + filenamesource + ".mp4";
                        logcat("File Name:" + sfName);
                        logcat("Call Downloader, Downloader Log Output In F12");
                        downloadFile(singlevurl, sfName);
                    }
                }
                //音频下载模式
                if (document.location.pathname == "/audiodown") {
                    if (videoInfo.sub_items) {
                        logcat("Found " + videoInfo.sub_items.length + " Audios");
                        //循环解析并下载音频
                        for (var ai = 0; videoInfo.sub_items[ai]; ai++) {
                            logcat("Analysis Video " + (ai + 1));
                            //currentAudio,缩写为cA看起来清爽点
                            var cA = videoInfo.sub_items[ai];
                            var aName = cA.title;
                            logcat("Audio Name:" + aName);
                            //音频不区分清晰度
                            var aurl = cA.audios[0].audio_storage_info[0].url;
                            logcat("Audio Url:" + aurl);
                            var afName = aName + filenamesource + ".mp3";
                            logcat("File Name:" + fName);
                            logcat("Call Downloader, Downloader Log Output In F12");
                            downloadFile(aurl, afName);
                        }
                    }
                }
            }
        }
        xhr.onerror = () => {
            logcat("Fail Get Json Info:" + vid)
        }
        xhr.open('GET', "https://boot-source.xuexi.cn/data/app/" + vid + ".js?callback=callback&_st=" + Date.now());
        xhr.send();
        //打印日志方法,空页面就不用console.log了
        function logcat(text) {
            //获取时间参考https://www.jianshu.com/p/067469a4eed8,稍微整合了一下
            document.getElementById("logcat").innerText = new Date().toTimeString().substring(0, 8) + "  " + text + "\n" + document.getElementById("logcat").innerText;
        }
        //分析最高清晰度
        function getHighest(vObj) {
            var maxHeight = 1;
            var maxId = 0;
            for (var vii = 0; vObj[vii]; vii++) {
                if (!(vObj[vii].format == "m3u8")) {
                    if (vObj[vii].height > maxHeight) {
                        maxHeight = vObj[vii].height;
                        maxId = vii;
                    }
                }
            }
            logcat("Max Vide Height:" + maxHeight);
            return vObj[maxId].normal
        }
    }


    //地址转换函数
    function pagetoinfourl(pageurl) {
        var tempurl = pageurl.replace(".html", ".js");
        tempurl = insertStr(tempurl, tempurl.indexOf("/", 21) + 1, "data");
        return tempurl;
    }
    //插入
    function insertStr(soure, start, newStr) {
        return soure.slice(0, start) + newStr + soure.slice(start);
    }
    //下载
    function downloadFile(url, name) {
        XHRDL.newTask(url, name);
    }
}
)();