书加加梨酱小帮手

全自动下载资源!

// ==UserScript==
// @name         书加加梨酱小帮手
// @namespace    https://qinlili.bid/
// @version      1.4.0
// @description  全自动下载资源!
// @author       琴梨梨
// @connect      xdfsjj.com
// @match        https://dogwood.xdfsjj.com/pc/*
// @icon         https://dogwood.xdfsjj.com/qrcode/img/favicon.ico
// @run-at       document-idle
// @license      GPLv3
// @require      https://lib.baomitu.com/crypto-js/4.1.1/crypto-js.min.js#sha512-E8QSvWZ0eCLGk4km3hxSsNmGWbLtSCSUcewDQPQWZF6pEU8GlT8a5fF32wOl1i8ftdMhssTrF/OhyGWwonTcXA==
// @require      https://lib.baomitu.com/m3u8-parser/4.7.0/m3u8-parser.min.js#sha512-k+LQLfQIGmuQTgjCfnE/iU3jdv+J9sdykVF5SgKtc+aMiKWPZqGjs60Bp3lug6rh9DVhcSZefpetyVXwUny48w==
// @require      https://lib.baomitu.com/mux.js/6.0.1/mux.min.js#sha512-d52/L8+HR/4zLHARiYRxpq70V2k+AaO8CO2zAcY5BFeZtqX16cQ/N3Eof/yMSwFwfY6blTL6GcENw2TiZJSJog==
// @require      https://cdn.jsdelivr.net/npm/[email protected]/dist/jsQR.min.js
// @homepage     https://github.com/qinlili23333/SJJHelper
// @supportURL   https://github.com/qinlili23333/SJJHelper
// @grant        GM_xmlhttpRequest
// @grant        GM_registerMenuCommand
// ==/UserScript==

(async function() {
    'use strict';
    //这里是配置区,你可以对脚本进行一些功能上的自定义
    const config={
        //文档下载格式,origin为下载最原始的文件,pdf为下载pdf格式的导出文件
        docFormat:localStorage.getItem("docFormat")||"origin",
        //m3u8视频下载格式,ts为Binary合并的文件,兼容好,体积大,mp4为使用mux.js混流后的文件,体积小,但和部分播放器有兼容问题
        m3u8Format:localStorage.getItem("m3u8Format")||"mp4"
    };
    GM_registerMenuCommand("文档格式:"+config.docFormat, () => {
        localStorage.setItem("docFormat",(localStorage.getItem("docFormat")=="pdf")?"origin":"pdf");
        document.location.reload();
    });
    GM_registerMenuCommand("m3u8视频格式:"+config.m3u8Format, () => {
        localStorage.setItem("m3u8Format",(localStorage.getItem("m3u8Format")=="ts")?"mp4":"ts");
        document.location.reload();
    });


    //尝试解锁
    (function(open) {
        XMLHttpRequest.prototype.open = function(method, url, async, user, pass) {
            if(url.indexOf("userOrderService/isBuy.do")>0){
                url= "data:application/json;base64,eyJkYXRhIjp7InJlc3VsdCI6InRydWUifSwic3VjY2VzcyI6dHJ1ZX0=";
            }
            open.call(this, method, url, async, user, pass);
        };
    })(XMLHttpRequest.prototype.open);


    if(!localStorage.getItem("first.qinlili")){
        alert(`你第一次启用梨酱小帮手!
本脚本包含复杂功能,建议打开脚本阅读顶端注释
建议根据脚本顶端注释对脚本功能进行一些配置以符合个人偏好
如果本脚本对你有帮助,请在GreasyFork给个好评或在Github给个星星
可以访问琴梨梨的小站qinlili.bid获取更多实用工具`)
        if(confirm(`你是否同意本脚本用户协议?
0.本脚本代码所有权归琴梨梨,请参阅GPLv3许可进行代码的二次使用,本项目使用了一些其他开源项目作为依赖,请同时注意这些项目的许可
1.本脚本用于出于合理的学习目的批量下载你已购的书籍或课程资源,请勿借助本脚本进行大规模资源抓取或其他可能消耗服务器资源的操作
2.书加加网站内一切内容版权均归网站持有者拥有,请合理使用本脚本下载的资源,分发或用于其他非个人学习欣赏用途可能会产生版权风险
3.本脚本不保证功能可用性,亦不保证对使用者的无害性,使用本脚本视为接受本脚本带来的一切损失由使用者承担`)&&confirm(`你是否同意本脚本隐私须知?
0.本脚本不收集任何信息
1.但如果你需要反馈,你可能需要提供浏览器版本等必要信息以便于琴梨梨排查`)){
            localStorage.setItem("first.qinlili",true)
        }else{
            console.log("用户拒绝了脚本加载")
            return false;
        }
    }

    //共享库
    const sleep = delay => new Promise(resolve => setTimeout(resolve, delay))
    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-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.3\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, start, showadd) {
            if (this.isLoaded) {
                var list = this.dlList;
                list[list.length] = {
                    taskUrl: url,
                    fileName: name
                }
                if (showadd) {
                    SakiProgress.showDiv();
                    SakiProgress.setText("已添加新任务:" + name);
                }
                if (!this.DLEngine.isWorking && start) {
                    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();
                }
            }
        }
    }

    window.history.pushState=(a,b,c)=>{
        location.href=c;
    }
    //公共方法区
    //https://stackoverflow.com/questions/49129643/how-do-i-merge-an-array-of-uint8arrays
    const concat=(arrays)=> {
        // sum of individual array lengths
        let totalLength = arrays.reduce((acc, value) => acc + value.length, 0);

        if (!arrays.length) return null;

        let result = new Uint8Array(totalLength);

        // for each array - copy it over result
        // next array is copied right after the previous one
        let length = 0;
        for(let array of arrays) {
            result.set(array, length);
            length += array.length;
        }

        return result;
    }
    const utils={
        //处理请求的方法,就嗯抄就完事了嘛
        nonce:()=>{return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, (function(e) {
            var t = 16 * Math.random() | 0;
            return ("x" == e ? t : 3 & t | 8).toString(16)
        }))},
        sign:data=>{
            var t = data
            , n = Object.keys(t).sort().reduce((function(e, n) {
                return e + (null === t[n] || void 0 === t[n] ? "" : t[n]) + n
            }), "");
            return CryptoJS.MD5(n).toString().toUpperCase().substring(0, 20)},
        //自己根据逆向推测做的构造方法
        buildPageBody:id=>{
            const signBody={bookId:id,
                            v:2,
                            _timestamp:Date.now(),
                            _nonce:utils.nonce()}
            signBody._sign=utils.sign(signBody)
            return new URLSearchParams(signBody).toString()},
        buildLessonBody:id=>{
            const signBody={lessonId:id,
                            v:2,
                            _timestamp:Date.now(),
                            _nonce:utils.nonce()}
            signBody._sign=utils.sign(signBody)
            return new URLSearchParams(signBody).toString()},
        buildLiveBody:(liveid,targetid,pcrid)=>{
            const signBody={liveId: liveid,
                            targetId: targetid,
                            pcrId: pcrid,
                            v:2,
                            _timestamp:Date.now(),
                            _nonce:utils.nonce()}
            signBody._sign=utils.sign(signBody)
            return new URLSearchParams(signBody).toString()},
        buildFileBody:(id,resid,ressign,pcrid,type)=>{
            const signBody={id:id,
                            type:14,
                            resId:resid,
                            resSign:ressign,
                            v:2,
                            _timestamp:Date.now(),
                            _nonce:utils.nonce()}
            if(pcrid){
                signBody.pcrId=pcrid
            }
            if(type){
                signBody.type=type
            }
            signBody._sign=utils.sign(signBody)
            return new URLSearchParams(signBody).toString()},
        //根据地址推测后缀
        getExtFromUrl:url=>{
            let ext=url.substr(url.lastIndexOf("."))
            if(ext.indexOf("?")>0){
                ext=ext.substr(0,ext.indexOf("?"))
            }
            return ext
        },
        m3u8Downloader:async (url,title)=>{
            SakiProgress.setPercent(0);
            SakiProgress.setText("[m3u8下载]正在读取m3u8信息");
            let dlLink=new URL(url);
            const m3u8Text=await (await fetch(dlLink)).text();
            const parser = new m3u8Parser.Parser();
            parser.push(m3u8Text);
            const segments=parser.manifest.segments;
            console.log(segments);
            const cdn=dlLink.origin;
            let tsCache=[];
            let onFullDL = function () { };
            function waitFullDL() {
                return new Promise(resolve => {
                    onFullDL = () => {
                        resolve();
                    }
                });
            }
            SakiProgress.setPercent(5);
            SakiProgress.setText("[m3u8下载]Init Neeko Engine...");
            await sleep(250)
            const batchDL=async (url,position)=>{
                tsCache[position]=await (await fetch(url)).blob()
                SakiProgress.setPercent(5+Object.keys(tsCache).length/segments.length*80);
                SakiProgress.setText("[m3u8下载]多线程极速下载切片,已完成"+(Object.keys(tsCache).length)+"个,共"+segments.length+"个");
                if(Object.keys(tsCache).length==segments.length){
                    switch (config.m3u8Format){
                        case "mp4":{
                            SakiProgress.setPercent(90);
                            SakiProgress.setText("[m3u8下载]正在转换为MP4");
                            let transmuxer = new muxjs.mp4.Transmuxer({remux: true});
                            transmuxer.on('data', segment=> {
                                console.log(segment)
                                let data = new Uint8Array(segment.initSegment.byteLength + segment.data.byteLength);
                                data.set(segment.initSegment, 0);
                                data.set(segment.data, segment.initSegment.byteLength);
                                console.log(muxjs.mp4.tools.inspect(data));
                                let mp4File=new Blob([data])
                                SakiProgress.setPercent(97);
                                SakiProgress.setText("[m3u8下载]正在导出下载");
                                let eleLink = document.createElement('a');
                                eleLink.download = title+".mp4";
                                eleLink.style.display = 'none';
                                eleLink.href = URL.createObjectURL(mp4File);
                                document.body.appendChild(eleLink);
                                eleLink.click();
                                document.body.removeChild(eleLink);
                                SakiProgress.setPercent(100);
                                onFullDL();
                            })
                            for(let seg=0;tsCache[seg];seg++){
                                transmuxer.push(new Uint8Array(await tsCache[seg].arrayBuffer()))
                            }
                            transmuxer.flush();
                            break;
                        }
                        case "ts":{
                            SakiProgress.setPercent(85);
                            SakiProgress.setText("[m3u8下载]正在合并TS文件");
                            let tsFile=new Blob(tsCache)
                            SakiProgress.setPercent(97);
                            SakiProgress.setText("[m3u8下载]正在导出下载");
                            let eleLink = document.createElement('a');
                            eleLink.download = title+".ts";
                            eleLink.style.display = 'none';
                            eleLink.href = URL.createObjectURL(tsFile);
                            document.body.appendChild(eleLink);
                            eleLink.click();
                            document.body.removeChild(eleLink);
                            SakiProgress.setPercent(100);
                            onFullDL();
                            break
                        }
                    }
                }
            }
            for(let current=0;segments[current];current++){
                batchDL(cdn+segments[current].uri,current)
            }
            await waitFullDL();
        },
    }
    const searchParams=new URLSearchParams(document.location.search);

    switch (location.pathname){
        case "/pc/personCenter.html":{
            const parentDiv=document.getElementsByClassName("centerRouter-content")[0];
            const observer = new MutationObserver(function(){
                if(document.getElementsByClassName("container").length){
                    const scannerHTML=`<div class="container"><div class="tagPoint"></div><div class="contentContainer"><div class="top"><div class="tagTitleContainer"><div class="tagText">扫码器</div></div><div class="bookContentContainer" id="scanContainer" style="display: flex;flex-direction: column;justify-content: center;"><div class="emptyViewBookMsg" id="scanMsg">点我启动扫码器</div><div></div></div></div></div></div>`;
                    document.getElementsByClassName("ant-spin")[0].insertAdjacentHTML("afterend",scannerHTML)
                    const msg=document.getElementById("scanMsg")
                    msg.addEventListener("click",()=>{
                        msg.innerText="正在启动扫码器,请允许使用摄像头"
                        const scanCanvas=document.createElement("canvas")
                        const canvas = scanCanvas.getContext('2d');
                        document.getElementById("scanContainer").insertBefore(scanCanvas,msg);
                        const video = document.createElement("video");
                        navigator.mediaDevices.getUserMedia({ video: { facingMode: "environment" } }).then(function(stream) {
                            video.srcObject = stream;
                            video.setAttribute("playsinline", true);
                            video.play();
                            const tick=()=>{if (video.readyState === video.HAVE_ENOUGH_DATA) {
                                scanCanvas.height = video.videoHeight;
                                scanCanvas.width = video.videoWidth;
                                canvas.drawImage(video, 0, 0, scanCanvas.width, scanCanvas.height);
                                let imageData = canvas.getImageData(0, 0, scanCanvas.width, scanCanvas.height)
                                let code = jsQR(imageData.data, imageData.width, imageData.height, {
                                    inversionAttempts: "dontInvert",
                                });
                                if (code) {
                                    if(code.data.startsWith("http")){
                                        const link=new URL(code.data)
                                        if(link.host.endsWith("bookln.cn") || link.host.endsWith("xdfsjj.com")){
                                            msg.innerText=link;
                                            location.href=link;
                                        }
                                    }
                                }
                            }
                                            requestAnimationFrame(tick);
                                           }
                            requestAnimationFrame(()=>{
                                msg.innerText="正在识别二维码"
                                tick();
                            });
                        });
                    })
                }
            });
            observer.observe(parentDiv, { childList: true });
            break
        }
        case "/pc/courseDetail.html":{
            XHRDL.init();
            await sleep(1500)
            if(document.getElementsByClassName("ytButton-container button-exit")[0]){
                //已登录
                console.log("已登录,下载器已初始化")
                if(CryptoJS){
                    const dlBtn=document.getElementsByClassName("studyCount")[0]
                    dlBtn.innerText="点我下载本书全部资源"
                    dlBtn.addEventListener("click",async event=>{
                        SakiProgress.showDiv();
                        //解析下载核心逻辑
                        event.preventDefault();
                        event.stopPropagation();
                        SakiProgress.setPercent(2);
                        SakiProgress.setText("正在读取页面信息...");
                        const pageDetail=await (await fetch("https://dogwood.xdfsjj.com/lessonService/detail.do", {
                            "headers": {
                                "accept": "application/json, text/plain, */*",
                                "content-type": "application/x-www-form-urlencoded",
                            },
                            "referrer": document.location.href,
                            "body": utils.buildLessonBody(searchParams.get("lessonId")),
                            "method": "POST",
                            "mode": "cors",
                            "credentials": "include"
                        })).json();
                        SakiProgress.setPercent(5);
                        SakiProgress.setText("页面信息已下载");
                        console.log(pageDetail);
                        let fileList=[]
                        if(pageDetail.success&&pageDetail.data.lessonDTO.chapters){
                            pageDetail.data.lessonDTO.chapters.forEach(chapter=>{
                                if(chapter.ress){
                                    //逐段下载
                                    chapter.ress.forEach(file=>{
                                        //逐文件下载
                                        //添加文件到列表
                                        fileList[fileList.length]=file;
                                    })
                                }
                            })
                        }else{
                            SakiProgress.setPercent(10);
                            SakiProgress.setTextAlert("页面信息解析失败!")
                        }
                        //处理文件列表
                        console.log(fileList);
                        SakiProgress.setText("文件列表初始化完成,共计"+fileList.length+"个文件");
                        //文件列表解析
                        for(let i=0;fileList[i];i++){
                            SakiProgress.setText("正在解析"+i+"/"+fileList.length+"文件");
                            SakiProgress.setPercent(10+(i/fileList.length*80))
                            switch(fileList[i].type){
                                case 1:{
                                    const fileInfo=await(await fetch("https://dogwood.xdfsjj.com/resourceService/getLinkUrl.do", {
                                        "headers": {
                                            "accept": "application/json, text/plain, */*",
                                            "content-type": "application/x-www-form-urlencoded",
                                        },
                                        "referrer": "https://dogwood.xdfsjj.com/pc/audioDetail.html?id="+searchParams.get("lessonId")+"&pcrId="+fileList[i].pcrId+"&resId="+fileList[i].id+"&resSign="+fileList[i].idSign+"&type=47",
                                        "referrerPolicy": "strict-origin-when-cross-origin",
                                        "body": utils.buildFileBody(searchParams.get("lessonId"),fileList[i].id,fileList[i].idSign,null,47),
                                        "method": "POST",
                                        "mode": "cors",
                                        "credentials": "include"
                                    })).json();
                                    if(fileInfo.success){
                                        let dlLink=""
                                        if(fileInfo.encrypted && fileInfo.encryptedData){
                                            let decryptData=JSON.parse(CryptoJS.AES.decrypt(fileInfo.encryptedData.replace(/\r\n/g, ""), CryptoJS.enc.Utf8.parse("Suj4XDDt3jPsH9Jj"), { mode: CryptoJS.mode.ECB}).toString(CryptoJS.enc.Utf8))
                                            switch(decryptData.mediaType){
                                                case 1:{
                                                    dlLink=decryptData.url;
                                                    break
                                                }
                                                case 2:{
                                                    //m3u8视频
                                                    await utils.m3u8Downloader(decryptData.url,fileList[i].title);
                                                    dlLink="blob"
                                                    break
                                                }
                                            }
                                        }else{
                                            dlLink=fileInfo.url;
                                        }
                                        if(!(dlLink=="blob")){
                                            console.log(fileList[i].title+":"+dlLink)
                                            XHRDL.newTask(dlLink,fileList[i].title+".mp4")
                                        }
                                    }else{
                                        console.error("获取地址失败了");
                                        console.error(fileInfo);
                                    }
                                    break;
                                }
                                case 2:{
                                    const fileInfo=await(await fetch("https://dogwood.xdfsjj.com/resourceService/getLinkUrl.do", {
                                        "headers": {
                                            "accept": "application/json, text/plain, */*",
                                            "content-type": "application/x-www-form-urlencoded",
                                        },
                                        "referrer": "https://dogwood.xdfsjj.com/pc/audioDetail.html?id="+searchParams.get("lessonId")+"&pcrId="+fileList[i].pcrId+"&resId="+fileList[i].id+"&resSign="+fileList[i].idSign+"&type=47",
                                        "referrerPolicy": "strict-origin-when-cross-origin",
                                        "body": utils.buildFileBody(searchParams.get("lessonId"),fileList[i].id,fileList[i].idSign),
                                        "method": "POST",
                                        "mode": "cors",
                                        "credentials": "include"
                                    })).json();
                                    if(fileInfo.success){
                                        let dlLink=""
                                        if(fileInfo.encrypted && fileInfo.encryptedData){
                                            dlLink=JSON.parse(CryptoJS.AES.decrypt(fileInfo.encryptedData.replace(/\r\n/g, ""), CryptoJS.enc.Utf8.parse("Suj4XDDt3jPsH9Jj"), { mode: CryptoJS.mode.ECB}).toString(CryptoJS.enc.Utf8)).url;
                                        }else{
                                            dlLink=fileInfo.url;
                                        }
                                        console.log(fileList[i].title+":"+dlLink)
                                        XHRDL.newTask(dlLink,fileList[i].title+".mp3")
                                    }else{
                                        console.error("获取地址失败了");
                                        console.error(fileInfo);
                                    }
                                    break;
                                }
                                case 3:{
                                    //XHRDL.newTask("data:text,[InternetShortcut]%0AURL=https://app.xdfsjj.com/resourceservice/mediaplay.do?resId="+fileList[i].id+"&resIdSign="+fileList[i].idSign+"&mediaType=4",fileList[i].title+".url")
                                    //处理跨域问题
                                    function asyncFetchPic(){
                                        return new Promise(resolve => {
                                            let pic=GM_xmlhttpRequest({
                                                method: "GET", url: "https://app.xdfsjj.com/resourceservice/mediaplay.do?resId="+fileList[i].id+"&resIdSign="+fileList[i].idSign+"&mediaType=4", responseType: "blob", onload: (res) => {
                                                    console.log(res.response)
                                                    XHRDL.newTask(URL.createObjectURL(res.response),fileList[i].title+".png")
                                                    resolve();
                                                }
                                            })
                                            });
                                    }
                                    await asyncFetchPic();
                                    break;
                                }
                                case 27:{
                                    const fileInfo=await (await fetch("https://dogwood.xdfsjj.com/resourceService/detail.do", {
                                        "headers": {
                                            "accept": "application/json, text/plain, */*",
                                            "content-type": "application/x-www-form-urlencoded",
                                        },
                                        "referrer": "https://dogwood.xdfsjj.com/pc/resourceRichText.html?id="+searchParams.get("lessonId")+"&pcrId="+fileList[i].pcrId+"&resId="+fileList[i].id+"&resSign="+fileList[i].idSign+"&type=47",
                                        "body": utils.buildFileBody(searchParams.get("lessonId"),fileList[i].id,fileList[i].idSign,fileList[i].pcrId,47),
                                        "method": "POST",
                                        "mode": "cors",
                                        "credentials": "include"
                                    })).json()
                                    if(fileInfo.success){
                                        //读取Content写入HTML
                                        fileInfo.data.resourceDTOList.forEach(doc=>{
                                            let htmlDoc=doc.content;
                                            XHRDL.newTask(URL.createObjectURL(new Blob([htmlDoc])),fileList[i].title+".html");
                                        })
                                    }else{
                                        console.error("获取地址失败了");
                                        console.error(fileInfo);
                                    }
                                    break;
                                }
                                case 7:
                                case 19:{
                                    const fileInfo=await (await fetch("https://dogwood.xdfsjj.com/resourceService/detail.do", {
                                        "headers": {
                                            "accept": "application/json, text/plain, */*",
                                            "content-type": "application/x-www-form-urlencoded",
                                        },
                                        "referrer": "https://dogwood.xdfsjj.com/pc/resourceRichText.html?id="+searchParams.get("lessonId")+"&pcrId="+fileList[i].pcrId+"&resId="+fileList[i].id+"&resSign="+fileList[i].idSign+"&type=47",
                                        "body": utils.buildFileBody(searchParams.get("lessonId"),fileList[i].id,fileList[i].idSign,fileList[i].pcrId,47),
                                        "method": "POST",
                                        "mode": "cors",
                                        "credentials": "include"
                                    })).json()
                                    if(fileInfo.success){
                                        fileInfo.data.resourceDTOList.forEach(doc=>{
                                            let format=config.docFormat;
                                            if(fileList[i].type==7){
                                                format="pdf";
                                            }
                                            switch(format){
                                                case "origin":{
                                                    const rawUrl=new URL(doc.content).searchParams.get("furl");
                                                    XHRDL.newTask(rawUrl,fileList[i].title+utils.getExtFromUrl(rawUrl))
                                                    console.log(rawUrl)
                                                    break;
                                                }
                                                case "pdf":{
                                                    let filename=fileList[i].title+".pdf"
                                                    if(fileList[i].type==7){
                                                        filename=fileList[i].title+utils.getExtFromUrl(doc.downUrl)
                                                    }
                                                    XHRDL.newTask(doc.downUrl,filename)
                                                    console.log(doc.downUrl)
                                                    break;
                                                }
                                            }
                                        })
                                    }else{
                                        console.error("获取地址失败了");
                                        console.error(fileInfo);
                                    }
                                    break;}
                                case 4:{
                                    console.log("跳过广告链接");
                                    break;
                                }
                                case 24:{
                                    const fileInfo=await (await fetch("https://dogwood.xdfsjj.com/liveService/queryById.do", {
                                        "headers": {
                                            "accept": "application/json, text/plain, */*",
                                            "content-type": "application/x-www-form-urlencoded",
                                        },
                                        "referrer": "https://dogwood.xdfsjj.com/pc/livePlayback.html?liveId="+fileList[i].fkId+"&pcrId="+fileList[i].pcrId+"&targetId"+searchParams.get("lessonId"),
                                        "body": utils.buildLiveBody(fileList[i].fkId,searchParams.get("lessonId"),fileList[i].pcrId),
                                        "method": "POST",
                                        "mode": "cors",
                                        "credentials": "include"
                                    })).json();
                                    if(fileInfo.success){
                                        let dlLink=""
                                        if(fileInfo.encrypted && fileInfo.encryptedData){
                                            let decryptData=JSON.parse(CryptoJS.AES.decrypt(fileInfo.encryptedData.replace(/\r\n/g, ""), CryptoJS.enc.Utf8.parse("Suj4XDDt3jPsH9Jj"), { mode: CryptoJS.mode.ECB}).toString(CryptoJS.enc.Utf8))
                                            if(decryptData.playbacks){
                                                dlLink=decryptData.playbacks;
                                            }else{
                                                console.error("该直播尚未结束,无回放解析");
                                                console.error(fileInfo);
                                                break;
                                            }
                                        }else{
                                            dlLink=fileInfo.url;
                                        }
                                        console.log(fileList[i].title+":"+dlLink)
                                        XHRDL.newTask(dlLink,fileList[i].title+".mp4")
                                    }else{
                                        console.error("获取地址失败了");
                                        console.error(fileInfo);
                                    }
                                    break;
                                }
                                default:{
                                    console.error("遇到了不支持的文件类型");
                                    console.error(fileList[i]);
                                    break;}
                            }
                            await sleep(100)
                        }
                        SakiProgress.setPercent(100);
                        SakiProgress.setText("文件信息全部获取完成!三秒后开始下载")
                        await sleep(3000)
                        XHRDL.DLEngine.start();
                    })
                }else{
                    SakiProgress.setTextAlert("没有加载CryptoJS")
                }
            }else{
                console.log("没有登录,下载器不可用")
            }
            break
        }
        case "/pc/bookDetail.html":{
            XHRDL.init();
            await sleep(1500)
            if(document.getElementsByClassName("ytButton-container button-exit")[0]){
                //已登录
                console.log("已登录,下载器已初始化")
                if(CryptoJS){
                    const dlBtn=document.getElementsByClassName("studyCount")[0]
                    dlBtn.innerText="点我下载本书全部资源"
                    dlBtn.addEventListener("click",async event=>{
                        SakiProgress.showDiv();
                        //解析下载核心逻辑
                        event.preventDefault();
                        event.stopPropagation();
                        SakiProgress.setPercent(2);
                        SakiProgress.setText("正在读取页面信息...");
                        const pageDetail=await (await fetch("https://dogwood.xdfsjj.com/bookService/detail.do", {
                            "headers": {
                                "accept": "application/json, text/plain, */*",
                                "content-type": "application/x-www-form-urlencoded",
                            },
                            "referrer": document.location.href,
                            "body": utils.buildPageBody(searchParams.get("bookId")),
                            "method": "POST",
                            "mode": "cors",
                            "credentials": "include"
                        })).json();
                        SakiProgress.setPercent(5);
                        SakiProgress.setText("页面信息已下载");
                        console.log(pageDetail);
                        let fileList=[]
                        if(pageDetail.success&&pageDetail.data.chapters){
                            pageDetail.data.chapters.forEach(chapter=>{
                                if(chapter.sections){
                                    //逐章节下载
                                    chapter.sections.forEach(section=>{
                                        if(section.ress){
                                            //逐段下载
                                            section.ress.forEach(file=>{
                                                //逐文件下载
                                                //添加文件到列表
                                                fileList[fileList.length]=file;
                                            })
                                        }
                                    })
                                }
                            })
                        }else{
                            SakiProgress.setPercent(10);
                            SakiProgress.setTextAlert("页面信息解析失败!")
                        }
                        //处理文件列表
                        console.log(fileList);
                        SakiProgress.setText("文件列表初始化完成,共计"+fileList.length+"个文件");
                        //文件列表解析
                        for(let i=0;fileList[i];i++){
                            SakiProgress.setText("正在解析"+i+"/"+fileList.length+"文件");
                            SakiProgress.setPercent(10+(i/fileList.length*80))
                            switch(fileList[i].type){
                                case 2:{
                                    const fileInfo=await(await fetch("https://dogwood.xdfsjj.com/resourceService/getLinkUrl.do", {
                                        "headers": {
                                            "accept": "application/json, text/plain, */*",
                                            "content-type": "application/x-www-form-urlencoded",
                                        },
                                        "referrer": "https://dogwood.xdfsjj.com/pc/audioDetail.html?id="+searchParams.get("bookId")+"&pcrId="+fileList[i].pcrId+"&resId="+fileList[i].id+"&resSign="+fileList[i].idSign+"&type=14",
                                        "referrerPolicy": "strict-origin-when-cross-origin",
                                        "body": utils.buildFileBody(searchParams.get("bookId"),fileList[i].id,fileList[i].idSign),
                                        "method": "POST",
                                        "mode": "cors",
                                        "credentials": "include"
                                    })).json();
                                    if(fileInfo.success){
                                        let dlLink=""
                                        if(fileInfo.encrypted && fileInfo.encryptedData){
                                            dlLink=JSON.parse(CryptoJS.AES.decrypt(fileInfo.encryptedData.replace(/\r\n/g, ""), CryptoJS.enc.Utf8.parse("Suj4XDDt3jPsH9Jj"), { mode: CryptoJS.mode.ECB}).toString(CryptoJS.enc.Utf8)).url;
                                        }else{
                                            dlLink=fileInfo.url;
                                        }
                                        console.log(fileList[i].title+":"+dlLink)
                                        XHRDL.newTask(dlLink,fileList[i].title+".mp3")
                                    }else{
                                        console.error("获取地址失败了");
                                        console.error(fileInfo);
                                    }
                                    break;}
                                case 7:
                                case 19:{
                                    const fileInfo=await (await fetch("https://dogwood.xdfsjj.com/resourceService/detail.do", {
                                        "headers": {
                                            "accept": "application/json, text/plain, */*",
                                            "content-type": "application/x-www-form-urlencoded",
                                        },
                                        "referrer": "https://dogwood.xdfsjj.com/pc/resourceRichText.html?id="+searchParams.get("bookId")+"&pcrId="+fileList[i].pcrId+"&resId="+fileList[i].id+"&resSign="+fileList[i].idSign+"&type=14",
                                        "body": utils.buildFileBody(searchParams.get("bookId"),fileList[i].id,fileList[i].idSign,fileList[i].pcrId),
                                        "method": "POST",
                                        "mode": "cors",
                                        "credentials": "include"
                                    })).json()
                                    if(fileInfo.success){
                                        fileInfo.data.resourceDTOList.forEach(doc=>{
                                            let format=config.docFormat;
                                            if(fileList[i].type==7){
                                                format="pdf";
                                            }
                                            switch(format){
                                                case "origin":{
                                                    const rawUrl=new URL(doc.content).searchParams.get("furl");
                                                    XHRDL.newTask(rawUrl,fileList[i].title+utils.getExtFromUrl(rawUrl))
                                                    console.log(rawUrl)
                                                    break;
                                                }
                                                case "pdf":{
                                                    let filename=fileList[i].title+".pdf"
                                                    if(fileList[i].type==7){
                                                        filename=fileList[i].title+utils.getExtFromUrl(doc.downUrl)
                                                    }
                                                    XHRDL.newTask(doc.downUrl,filename)
                                                    console.log(doc.downUrl)
                                                    break;
                                                }
                                            }
                                        })
                                    }else{
                                        console.error("获取地址失败了");
                                        console.error(fileInfo);
                                    }
                                    break;}
                                case 4:{
                                    console.log("跳过广告链接");
                                }
                                default:{
                                    console.error("遇到了不支持的文件类型");
                                    console.error(fileList[i]);
                                    break;}
                            }
                            await sleep(100)
                        }
                        SakiProgress.setPercent(100);
                        SakiProgress.setText("文件信息全部获取完成!三秒后开始下载")
                        await sleep(3000)
                        XHRDL.DLEngine.start();
                    })
                }else{
                    SakiProgress.setTextAlert("没有加载CryptoJS")
                }
            }else{
                console.log("没有登录,下载器不可用")
            }
            break;
        }
    }
})();