zhyDaDa_超星网课助手

[个人向] 刷超星尔雅的网课, 现在支持pdf/ppt/视频/音频的处理, 提供了设置面板可以调节, 解决了鼠标移开视频暂停的问题

目前为 2023-03-27 提交的版本。查看 最新版本

// ==UserScript==
// @name         zhyDaDa_超星网课助手
// @namespace    http://zhydada.github.io/
// @version      1.10
// @description  [个人向] 刷超星尔雅的网课, 现在支持pdf/ppt/视频/音频的处理, 提供了设置面板可以调节, 解决了鼠标移开视频暂停的问题
// @author       zhyDaDa
// @license      personal - use only

// @match        *://mooc.s.ecust.edu.cn/*
// @match        *://mooc1.chaoxing.com/*

// @require      https://code.jquery.com/jquery-3.6.0.min.js
// @icon         data:image/jpeg;base64,/9j/4AAQSkZJRgABAQEAkACQAAD/4QAiRXhpZgAATU0AKgAAAAgAAQESAAMAAAABAAEAAAAAAAD/2wBDAAIBAQIBAQICAgICAgICAwUDAwMDAwYEBAMFBwYHBwcGBwcICQsJCAgKCAcHCg0KCgsMDAwMBwkODw0MDgsMDAz/2wBDAQICAgMDAwYDAwYMCAcIDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAz/wAARCAAgACADASIAAhEBAxEB/8QAHwAAAQUBAQEBAQEAAAAAAAAAAAECAwQFBgcICQoL/8QAtRAAAgEDAwIEAwUFBAQAAAF9AQIDAAQRBRIhMUEGE1FhByJxFDKBkaEII0KxwRVS0fAkM2JyggkKFhcYGRolJicoKSo0NTY3ODk6Q0RFRkdISUpTVFVWV1hZWmNkZWZnaGlqc3R1dnd4eXqDhIWGh4iJipKTlJWWl5iZmqKjpKWmp6ipqrKztLW2t7i5usLDxMXGx8jJytLT1NXW19jZ2uHi4+Tl5ufo6erx8vP09fb3+Pn6/8QAHwEAAwEBAQEBAQEBAQAAAAAAAAECAwQFBgcICQoL/8QAtREAAgECBAQDBAcFBAQAAQJ3AAECAxEEBSExBhJBUQdhcRMiMoEIFEKRobHBCSMzUvAVYnLRChYkNOEl8RcYGRomJygpKjU2Nzg5OkNERUZHSElKU1RVVldYWVpjZGVmZ2hpanN0dXZ3eHl6goOEhYaHiImKkpOUlZaXmJmaoqOkpaanqKmqsrO0tba3uLm6wsPExcbHyMnK0tPU1dbX2Nna4uPk5ebn6Onq8vP09fb3+Pn6/9oADAMBAAIRAxEAPwD9yL68aMTFZP3i7toZ9q55xk9hWLovi+6vtRjhurdrdZQQrbzywGcc+wP5Vf1U/Jc/Vv5msK/t5JfIaFo98Mqy/McA4zx+tfP0uG8HTcqNZt8+0m2nHfVWaT6bpnPnGbPBYulS05Grt/Mv634vubDUHhtbdrlYwN7bjwxGcfkQfxrb0+4kkWBnb5m27grZXPfB71ytlbyRtcNKybppmk+Vs4zjj8MVv6VJu+yjPRl/mK58Zw3RqSp0cJpyfFO7bloul7Xv2svuDJ82ljcZVpK3IldP5ianGzpdemW/ma53Wb99PtlePaxZ9vzDpwT/AErsZ4CzyK0bFSTxtrI8ReFTrFnHHCogZJA5JjPIwRj9R+VZ4HiinVxyWNhy0722bSXn1fyX+YZ1k8sbiIV7rlirNdWYujag+oWztJtVlfaNo68A+/rXRaT9+25/jX+Yqv4d8KnR7OSOdVuGeQuCIzwMAY/T9a1rW18uWMLCyhWGPl6V0YjijCU8ZNYenJwvZNKy6d9fvDJcnlgq9Su2uWSskt1sf//Z
// @grant        unsafeWindow
// @grant        GM_registerMenuCommand
// @grant        GM_unregisterMenuCommand
// @grant        GM_getValue
// @grant        GM_setValue
// @run-at       document-end
// ==/UserScript==

(function() {
    'use strict';

    /**
     * 1.0 视频基本操作
     * 1.1 寻找猎物多次更新
     * 1.2 确保在看到绿色勾勾出现再跳
     * 1.3 自动判断视频有没有放过, 只挑没放过的视频看
     * 1.4 克服浏览器禁止自动播放的限制, 尽力剔除bug
     * 1.5 可以完成ppt和pdf的任务了
     * 1.6 提供设置面板(保存设置完善成功)
     * 1.7 第二种pdf完善, 全局变量问题解决
     * 1.8 解决了多开和节流问题
     * 1.9 可以处理audio音频任务了
     * 1.10 面板最小化
     */

    //#region 防止多次启动
    if (!(window === window.top) || typeof zhy_settings !== 'undefined') return;
    //#endregion
    //#region /*======================div:函数定义==========================*/
    unsafeWindow.zhy_settings = GM_getValue("zhy_settings", {
        skipGreen: true,
        playbackRate: '1',

    });
    zhy_settings.done = false;
    unsafeWindow.zhyDaDa = {};
    unsafeWindow.lastCallTime = 0;
    /**
     * 醒目的控制台输出
     * @param {"string"} log 要打印到控制台的话
     * @param {"报错"|"警告"|"启动"|"提示"|"幽灵白"} color 字体颜色 可选项为["报错"|"警告"|"启动"|"提示"|"幽灵白"] 默认绿色
     * @param {"int"} fontSize 字体大小, 默认24
     */
    zhyDaDa.sendLog = (log, color, fontSize) => {
        switch (color) {
            case "报错":
                color = "red";
                break;
            case "警告":
                color = "#F2AB26";
                break;
            case "启动":
                color = "#A162F7";
                break;
            case "提示":
                color = "#35D4C7";
                break;
            case "幽灵白":
                color = "ghostwhite";
                break;
            default:
                color = color || "#43bb88";
                break;
        }

        fontSize = fontSize || 24;
        console.log('%c' + log, 'color: ' + color + ';font-size: ' + fontSize + 'px;font-weight: bold;'); //text-decoration: underline;
    }

    zhyDaDa.getBaseDocument = () => {
        var currentWindow = window;

        while (currentWindow !== currentWindow.parent) {
            currentWindow = currentWindow.parent;
        }

        return currentWindow.document;
    }

    /**
     * 模拟cmd中的sleep函数
     * @param {"number"} d deltaTime 即要等待的时间差 照旧以毫秒为单位
     */
    function sleep(d) {
        return new Promise((success, fail) => {
            setTimeout(success, d);
        });
    }
    //#endregion
    /*======================div:全局效果==========================*/



    zhyDaDa.findPray = () => {
        // 节流
        let currentTime = Date.now();
        if (currentTime - unsafeWindow.lastCallTime < 3600) return false;
        unsafeWindow.lastCallTime = currentTime;
        try {
            let points = $("#coursetree .roundpoint,.roundpointStudent", zhyDaDa.getBaseDocument()).get();
            // 0有任务 1任务完成 2无任务
            let currentPointIndex = 0;
            let pointsCataList = points.map((e, i) => {
                if (e.parentNode.className == "currents") {
                    currentPointIndex = i;
                }
                if (e.className.indexOf("jobCount") >= 0) {
                    return 0;
                } else if (e.className.indexOf("blue") >= 0) {
                    return 1;
                } else if (e.className.indexOf("noJob") >= 0) {
                    return 2;
                }
            });


            if (!unsafeWindow.zhy_settings.skipGreen) {
                let next = points[currentPointIndex + 1];
                next.parentNode.querySelector('a').click();
                unsafeWindow.setTimeout(() => { zhyDaDa.main(); }, 4800);
            } else if (points.length > 0) {
                let i = currentPointIndex;
                while (pointsCataList[++i] != 0 && i < points.length);
                if (i == points.length && pointsCataList[i] != 0) {
                    i = 0;
                    while (pointsCataList[++i] != 0 && i < currentPointIndex);
                    if (i == currentPointIndex) zhy_settings.done = true;
                }
                let next = points[i];
                next.parentNode.querySelector('a').click();
                unsafeWindow.setTimeout(() => { zhyDaDa.main(); }, 4800);
            }
        } catch { console.log("找不到猎物了"); }
    }

    zhyDaDa.turnToNextPage = () => {
        let focus = $(".currents", zhyDaDa.getBaseDocument()).get()[0];
        let neighbour = focus.parentNode.nextElementSibling;
        neighbour.firstElementChild.querySelector('a').click();
    }

    zhyDaDa.getIframes = () => {
        let aaa = $("iframe", zhyDaDa.getBaseDocument()).get();
        if (aaa.length < 1) {
            return false;
        } else if (aaa[0].id == "iframe") {
            aaa = $("iframe", aaa[0].contentWindow.document).get();
        }
        return aaa;
    }

    zhyDaDa.classifyTasks = (taskIframes) => {
        let tasks = []; //[$iframe,"catagory"]
        tasks = taskIframes.map((iframe) => {
            let className = iframe.className;
            let catagory;
            if (className.indexOf("video") > 0) catagory = "video";
            if (className.indexOf("pdf") > 0) catagory = "pdf";
            if (className.indexOf("ppt") > 0) catagory = "ppt";
            if (className.indexOf("audio") > 0) catagory = "audio";

            return [iframe, catagory];
        });
        return tasks;
    }

    zhyDaDa.dealTasks = (tasksList) => {
        return new Promise((resolve, reject) => {
            if (tasksList.length < 1) {
                resolve();
            } else {
                let iframeNode = tasksList[0][0];
                let iframeCata = tasksList[0][1];
                switch (iframeCata) {
                    case "video":
                        zhyDaDa.dealVideo(iframeNode).then(() => {
                            tasksList.shift();
                            zhyDaDa.dealTasks(tasksList).then(resolve);
                        });
                        break;

                    case "pdf":
                        zhyDaDa.dealPdf(iframeNode).then(() => {
                            tasksList.shift();
                            zhyDaDa.dealTasks(tasksList).then(resolve);
                        });
                        break;

                    case "ppt":
                        zhyDaDa.dealPpt(iframeNode).then(() => {
                            tasksList.shift();
                            zhyDaDa.dealTasks(tasksList).then(resolve);
                        });
                        break;
                    case "audio":
                        zhyDaDa.dealAudio(iframeNode).then(() => {
                            tasksList.shift();
                            zhyDaDa.dealTasks(tasksList).then(resolve);
                        });
                        break;

                    default:
                        reject("No such catagory");
                        break;
                }
            }

        })
    }

    //#region div: Video
    zhyDaDa.dealVideo = (iframeNode) => {
        return new Promise((resolve, reject) => {
            let iconNode = iframeNode.parentNode;
            if (iconNode.classList.length > 1 && zhy_settings.skipGreen) resolve();
            else {
                let videoNode = iframeNode.contentWindow.document.querySelector("video");
                zhyDaDa.watchVideo(videoNode).then(resolve);
            }

        })

    }

    zhyDaDa.watchVideo = (video) => {
            return new Promise((resolve, reject) => {
                video.addEventListener('ended', onEnded); // 添加 ended 事件处理程序

                video.volume = 0;
                video.playbackRate = Number(zhy_settings.playbackRate);
                video.pause = () => { return true }
                video.play(); // 开始播放视频

                function onEnded() {
                    video.removeEventListener('ended', onEnded); // 删除 ended 事件处理程序
                    resolve(); // 视频播放完成,设置 Promise 状态为 fulfilled
                }
            });
        }
        //#endregion 

    //#region div: Pdf
    zhyDaDa.dealPdf = (iframeNode) => {
            return new Promise((resolve, reject) => {
                let iconNode = iframeNode.parentNode;
                let img = iframeNode.contentWindow.document.querySelector("#img");
                if (iconNode.classList.length > 1 && zhy_settings.skipGreen) resolve();
                else {
                    img.scrollTop = img.scrollHeight;
                    let btn = iframeNode.contentWindow.document.querySelector(".mkeRbtn");
                    let num = iframeNode.contentWindow.document.querySelector(".all").innerText;
                    num = Number(num);
                    for (let i = 0; i < num + 2; i++) {
                        btn.click();
                    }
                }
            });
        }
        //#endregion

    //#region div: Ppt
    zhyDaDa.dealPpt = (iframeNode) => {
            return new Promise((resolve, reject) => {
                let iconNode = iframeNode.parentNode;
                if (iconNode.classList.length > 1 && zhy_settings.skipGreen) resolve();
                else {
                    let btn = iframeNode.contentWindow.document.querySelector(".nextBtn");
                    let num = iframeNode.contentWindow.document.querySelector(".all").innerText;
                    num = Number(num);
                    for (let i = 0; i < num + 2; i++) {
                        btn.click();
                    }
                }

            })

        }
        //#endregion

    //#region div: Audio
    zhyDaDa.dealAudio = (iframeNode) => {
            return new Promise((resolve, reject) => {
                let iconNode = iframeNode.parentNode;
                if (iconNode.classList.length > 1 && zhy_settings.skipGreen) resolve();
                else {
                    let btn = iframeNode.contentWindow.document.querySelector(".vjs-play-control");
                    let audioElement = iframeNode.contentWindow.document.querySelector("audio");

                    audioElement.addEventListener('ended', onEnded); // 添加 ended 事件处理程序

                    audioElement.volume = 0;
                    audioElement.playbackRate = Number(zhy_settings.playbackRate);
                    audioElement.pause = () => { return true }
                    audioElement.play(); // 开始播放音频

                    function onEnded() {
                        audioElement.removeEventListener('ended', onEnded); // 删除 ended 事件处理程序
                        resolve(); // 音频播放完成,设置 Promise 状态为 fulfilled
                    }
                }

            })

        }
        //#endregion

    zhyDaDa.main = () => {

            // 节流
            let currentTime = Date.now();
            if (currentTime - unsafeWindow.lastCallTime < 3600) return false;
            unsafeWindow.lastCallTime = currentTime;
            if (zhy_settings.done) return true;
            let taskIframes = zhyDaDa.getIframes();
            if (!taskIframes) return false;
            let classifiedTasksList = zhyDaDa.classifyTasks(taskIframes);
            zhyDaDa.dealTasks(classifiedTasksList).then(() => { unsafeWindow.setTimeout(zhyDaDa.findPray, 6400); });
        }
        // 插入控制面板浮窗
    zhyDaDa.insertPanel = () => {
        let _style = `
        <style>
            #zhy_settings_panel {
            position: fixed;
            top: 50px;
            left: 50px;
            background-color: #fff;
            border: 1px solid #ccc;
            border-radius: 5px;
            padding: 10px;
            font-size: 12px;
            z-index: 99999;
            }

            #zhy_settings_panel h3 {
                margin-top: 0;
                margin-bottom: 10px;
            }
            #drag-handle {
                width: 100%;
                height: 18px;
                cursor: move;
                background: grey;
                border-radius: 10px;
                margin-bottom: 10px;    
                text-align: center;
                color: #bbb;
                user-select: none;
                min-width: 80px;
            }

            #zhy_settings_panel .zhy_settings_item {
            margin-bottom: 10px;
            }

            #zhy_settings_panel label {
            display: inline-block;
            width: 120px;
            }

            #playbackRateContainer {
            display: grid;
            }

            #playbackRateContainer input[type="radio"] {
            display: none;
            }

            #playbackRateContainer label {
            flex: 1;
            text-align: center;
            padding: 5px 10px;
            border: 1px solid #ccc;
            border-radius: 5px;
            cursor: pointer;
            }

            #playbackRateContainer input[type="radio"]:checked + label {
            background-color: #ccc;
            }

        </style>
        `;
        let _html = `
        <div id="zhy_settings_panel">
        <div id="drag-handle" flag="0">双击最小化</div>
        <h3>设置面板</h3>
        <div class="zhy_settings_item">
            <label for="skipGreenToggle">跳过已经看完的任务点:</label>
            <input type="checkbox" id="skipGreenToggle" checked>
        </div>
        <div class="zhy_settings_item">
            <label for="startWork">启动刷课:</label>
            <button id="startWork">启动</button>
        </div>
        <div class="zhy_settings_item">
            <label>视频播放速率:</label>
            <div id="playbackRateContainer">
            <input type="radio" id="rate1" name="playbackRate" value="1" checked>
            <label for="rate1">1</label>

            <input type="radio" id="rate1.25" name="playbackRate" value="1.25">
            <label for="rate1.25">1.25</label>

            <input type="radio" id="rate1.5" name="playbackRate" value="1.5">
            <label for="rate1.5">1.5</label>

            <input type="radio" id="rate2" name="playbackRate" value="2">
            <label for="rate2">2</label>
            </div>
        </div>
        </div>
        `;

        document.documentElement.insertAdjacentHTML('beforeend', _style + _html);


        // 获取设置面板元素
        var panel = document.getElementById("zhy_settings_panel");

        // 获取浮窗标题栏元素
        let panelHeader = document.getElementById("drag-handle");

        // 定义变量记录鼠标按下时的坐标和面板的初始位置
        let startX, startY, panelX, panelY;

        // 给标题栏添加鼠标按下事件监听器
        panelHeader.addEventListener("mousedown", function(e) {
            // 记录鼠标按下时的坐标和面板的初始位置
            startX = e.clientX;
            startY = e.clientY;
            panelX = panel.offsetLeft;
            panelY = panel.offsetTop;

            // 给 document 添加鼠标移动和松开事件监听器
            document.addEventListener("mousemove", movePanel);
            document.addEventListener("mouseup", stopPanel);
        });

        // 双击标题栏最小化面板
        panelHeader.addEventListener("dblclick", function() {
            // 判断面板是否已经最小化
            // 如果panel的flag属性值不为"1", 说明面板没有最小化
            if (panel.getAttribute("flag") != "1") {
                // 将panel中除了标题栏之外的元素隐藏
                for (let i = 1; i < panel.children.length; i++) {
                    panel.children[i].style.display = "none";
                }
                panelHeader.innerHTML = "双击恢复";
                // 修改panel的flag属性为"1"
                panel.setAttribute("flag", "1");
            } else {
                for (let i = 1; i < panel.children.length; i++) {
                    panel.children[i].style.display = "block";
                }
                panelHeader.innerHTML = "双击最小化";
                panelHeader.style.width = "100%";
                // 修改panel的flag属性为"0"
                panel.setAttribute("flag", "0");
            }
        });

        // 移动浮窗的函数
        function movePanel(e) {
            // 计算鼠标移动的距离
            var deltaX = e.clientX - startX;
            var deltaY = e.clientY - startY;

            // 更新面板的位置
            panel.style.left = panelX + deltaX + "px";
            panel.style.top = panelY + deltaY + "px";
        }

        // 停止移动浮窗的函数
        function stopPanel() {
            // 移除鼠标移动和松开事件监听器
            document.removeEventListener("mousemove", movePanel);
            document.removeEventListener("mouseup", stopPanel);
        }


        // 获取设置面板中的元素
        let skipGreenToggle = document.getElementById("skipGreenToggle");
        let startWork = document.getElementById("startWork");
        let playbackRateRadios = document.getElementsByName("playbackRate");

        skipGreenToggle.checked = zhy_settings.skipGreen;
        let inputElement = $(`input[name='playbackRate'][value='${zhy_settings.playbackRate}']`).get();
        (inputElement.length > 0) && (inputElement[0].checked = true);

        // 定义回调函数
        function settingsChanged(ratio) {
            zhy_settings.skipGreen = zhyDaDa.getBaseDocument().getElementById("skipGreenToggle").checked ? 1 : 0;
            zhy_settings.playbackRate = ratio || zhy_settings.playbackRate;
            GM_setValue("zhy_settings", zhy_settings);
        }

        // 给skipGreenToggle添加change事件监听器,当状态发生改变时调用回调函数
        skipGreenToggle.addEventListener("change", () => { settingsChanged(); });

        // 给startWork添加click事件监听器,当点击时调用回调函数
        startWork.addEventListener("click", () => { zhyDaDa.main(); });

        // 给playbackRateRadios中的每个单选按钮添加click事件监听器,当点击时调用回调函数
        playbackRateRadios.forEach(function(radio) {
            radio.addEventListener("click", (event) => { settingsChanged(event.target.value); });
        });
    }

    zhyDaDa.sendLog("\n###################\n##zhyDaDa 网课助手##\n###################", "启动", 32);
    zhyDaDa.insertPanel();
    unsafeWindow.setTimeout(() => { zhyDaDa.main() }, 4800);
    unsafeWindow.setInterval(() => { zhyDaDa.main() }, 15000);
})();

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址