Greasy Fork is available in English.

在浙学自动刷课

可以快速地(默认16倍速)自动看完在浙学上一门科目的所有视频和文档。

// ==UserScript==
// @name         在浙学自动刷课
// @namespace    Vikrant
// @version      1.2.1
// @description  可以快速地(默认16倍速)自动看完在浙学上一门科目的所有视频和文档。
// @author       Vikrant
// @match        https://www.zjooc.cn/ucenter/student/course/study/*/plan/detail/*
// @grant        unsafeWindow
// @grant        GM_addStyle
// @grant        GM_setValue
// @grant        GM_getValue
// @icon         https://www.zjooc.cn/favicon.ico
// @license      GNU GPLv3
// @run-at       document-start
// ==/UserScript==

(function () {
    'use strict';
    let videoRate = 16                          //倍速播放视频的倍数,最大为16倍,默认为16倍,网速慢的话可以调小一些,防止卡顿
    let delay = 2000                            //某些环节等待加载的延迟,如果网络卡顿可以调大一些(单位ms)

    let win = unsafeWindow
    let winDoc = unsafeWindow.document

    let labelList = []
    //let buttonList = []
    let dirList = []

    let labelNow = null
    //let buttonNow = null
    let dirNow = null

    let dirIndex = 0
    //let buttonIndex = 0
    let labelIndex = 0

    if (videoRate < 0 || videoRate > 16) {                      //防小天才
        videoRate = 16
        console.log('视频倍速不得大于16倍或小于0!')
    }
    if (delay < 0) {
        delay = 2000
        console.log('delay不得小于0!')
    }

    let nullFunction = function () { }                          //空函数
    let find = {                                                //查找类函数的集合
        _dir: "#pane-Chapter>div>ul>li.el-submenu>ul>li>span",
        dir: function () {                                      //获取每一个小章节存入数组
            let list = winDoc.querySelectorAll(
                "#pane-Chapter>div>ul>li.el-submenu>ul>li>span"
            )
            return list
        },

        _videoLabel: "div>span.label>i.icon-shipin:not(.complete)+span",
        videoLabel: function () {                               //获取每一个小章节里上方内容是视频(未看)的标签存入数组
            let list = winDoc.querySelectorAll(
                "div>span.label>i.icon-shipin:not(.complete)+span"
            )
            return list
        },

        _docLabel: "div>span.label>i:not(.icon-shipin):not(.complete)+span",
        docLabel: function () {                                 //获取每一个小章节里上方内容是文档(未看)的标签存入数组
            let list = winDoc.querySelectorAll(
                "div>span.label>i:not(.icon-shipin):not(.complete)+span"
            )
            return list
        },

        label: function () {                                    //获取小章节上所有标签(先所有视频,再所有文档)
            let videoLabel = find.videoLabel()
            let docLabel = find.docLabel()
            let list = new Array(videoLabel.length + docLabel.length)
            for (let i = 0; i < videoLabel.length; ++i) {
                list[i] = videoLabel[i]
            }
            for (let i = 0; i < docLabel.length; ++i) {
                list[videoLabel.length + i] = docLabel[i]
            }
            return list
        },

        _button: "div>div>div.contain-bottom>button",
        button: function () {                                   //获取当前文档的“完成学习”按钮并返回
            let btn = winDoc.querySelector(
                "div>div>div.contain-bottom>button"
            )
            return btn
        }
    }

    function doAfterLoad(selector, event, interval = 1000) { //当元素加载后执行特定函数
        let scan = setInterval(() => {
            let load = winDoc.querySelector(selector)
            if (load) {
                stop(scan)
                event()
            }
        }, interval)
        function stop(obj) {                              //函数内定义的stop(),方便在setInterval内部停止自身
            clearInterval(obj)
        }
    }

    function nextDir() {                                  //跳转至下一个小章节
        if (++dirIndex > dirList.length - 1) {
            end()
        } else {
            dirList[dirIndex].click()
            dirNow = dirList[dirIndex]
            setTimeout(() => {
                labelList = find.label()             //初始化labelList相关的值
                labelIndex = 0
                labelNow = labelList[0]
                //buttonList = find.button()           //初始化buttonList相关的值
                //buttonIndex = 0
                //buttonNow = buttonList[0]
                if (labelList.length == 0) {
                    nextDir()
                } else {
                    labelNow.click()
                    setTimeout(() => {
                        if (winDoc.querySelector("video")) {
                            videoPlay(nextLabel)
                        } else {
                            //buttonNow.click()
                            find.button().click()
                            setTimeout(() => {
                                nextLabel()
                            }, delay);
                        }
                    }, delay)
                }
            }, delay)
        }
    }

    function nextLabel() {                                //点击下一个未观看的视频标签
        if (++labelIndex > labelList.length - 1) {
            nextDir()
        } else {
            labelList[labelIndex].click()
            labelNow = labelList[labelIndex]
            setTimeout(() => {
                if (winDoc.querySelector("video")) {
                    videoPlay(nextLabel)
                } else {
                    //buttonList = find.button()             //不知道为什么要重新初始化一遍buttonList才能正确点击
                    //buttonNow = buttonList[++buttonIndex]
                    //buttonNow.click()
                    find.button().click()
                    setTimeout(() => {
                        nextLabel()
                    }, delay);
                }
            }, delay)
        }
    }

    function videoPlay(afterEvent = nullFunction) {       //播放当前页面的视频并指定播放完之后执行的函数
        doAfterLoad("video", () => {
            let video = winDoc.querySelector("video")
            video.muted = true                            //静音
            video.playbackRate = videoRate                //调倍速
            video.play()                                  //开始播放视频
            video.addEventListener('ended', () => {       //监听视频是否播放完毕
                afterEvent()
            })
        })
    }
    /*
        function docPass() {                                   //点击“完成学习”按钮
            doAfterLoad(find._button, () => {
                let button = find.button()
                for (let i = 0; i < button.length; i++) {
                    button[i].click()
                }
            })
        }
    */
    function end() {                                        //结束函数
        winDoc.querySelector("#passButton").innerHTML = "完成!"
        GM_addStyle(`
                #passButton{
                    background-color:#e67e22
                }
            `)
    }

    function main() {                                       //主函数
        setTimeout(() => {
            dirList = find.dir()                            //初始化dirList以及相关的值
            dirIndex = 0
            dirNow = dirList[0]
            dirList[0].click()
            setTimeout(() => {
                labelList = find.label()
                labelIndex = 0
                labelNow = labelList[0]
                //buttonList = find.button()
                //buttonIndex = 0
                //buttonNow = buttonList[0]
                if (labelList.length == 0) {
                    nextDir()
                } else {
                    labelNow.click()
                    setTimeout(() => {
                        if (winDoc.querySelector("video")) {
                            videoPlay(nextLabel)
                        } else {
                            //buttonNow.click()
                            find.button().click()
                            setTimeout(() => {
                                nextLabel()
                            }, delay);
                        }
                    }, delay);
                }
            }, delay);
        }, delay);
    }

    let passButton = winDoc.createElement('button')            //创建按钮
    passButton.id = 'passButton'
    passButton.innerHTML = '开始刷课'
    win.onload = () => {                                       //在页面加载时添加按钮
        let header = winDoc.querySelector("#app>div>section>section>header")
        header.appendChild(passButton)
        passButton.onclick = () => {                           //指定按钮点击事件
            main()
            passButton.innerHTML = '刷课中…'
            GM_addStyle(`
                #passButton{
                    background-color:#53555e
                }
            `)
            passButton.onclick = nullFunction              //按钮点击后移除点击事件
        }
    }                                                      //定义按钮样式
    GM_addStyle(`                                          
        #passButton{
            background-color: #1192ff;
            color: white;
            text-align: center;
            padding: 0px 32px;
            text-decoration: none;
            display: inline-block;
            font-size: 14px;
        }
    `)
})();

/*
 .o8                   
"888                   
 888oooo.  oooo    ooo 
 d88' `88b  `88.  .8'  
 888   888   `88..8'   
 888   888    `888'    
 `Y8bod8P'     .8'     
           .o..P'      
           `Y8P'       
                       
oooooo     oooo  o8o  oooo                                           .   
 `888.     .8'   `"'  `888                                         .o8   
  `888.   .8'   oooo   888  oooo  oooo d8b  .oooo.   ooo. .oo.   .o888oo 
   `888. .8'    `888   888 .8P'   `888""8P `P  )88b  `888P"Y88b    888   
    `888.8'      888   888888.     888      .oP"888   888   888    888   
     `888'       888   888 `88b.   888     d8(  888   888   888    888 . 
      `8'       o888o o888o o888o d888b    `Y888""8o o888o o888o   "888" 
*/