UOOC assistant

【使用前先看介绍/有问题可反馈】【欢迎一键三连(好评+打赏+收藏),你的支持是作者维护下去的最大动力!】UOOC优课联盟助手(UOOC assistant):可选是否倍速(若取消勾选则一倍速播放),可选是否静音(若取消勾选则恢复原音量),可选是否播放(若取消勾选则暂停播放),可选是否连播(若取消勾选则循环播放),离开页面保持状态,自动回答视频中途弹出问题,可复制测验题目及答案;键盘左右方向键可以控制视频快进/快退,上下方向键可以控制音量增大/减小,空格键可以控制播放/暂停;自动签到慕课;如果视频标题下面出现 倍速/静音/播放/连播 选项说明脚本正常启动运行。

目前為 2021-01-22 提交的版本,檢視 最新版本

// ==UserScript==
// @name         UOOC assistant
// @name:en      UOOC assistant
// @namespace    http://tampermonkey.net/
// @version      1.0.1
// @description  【使用前先看介绍/有问题可反馈】【欢迎一键三连(好评+打赏+收藏),你的支持是作者维护下去的最大动力!】UOOC优课联盟助手(UOOC assistant):可选是否倍速(若取消勾选则一倍速播放),可选是否静音(若取消勾选则恢复原音量),可选是否播放(若取消勾选则暂停播放),可选是否连播(若取消勾选则循环播放),离开页面保持状态,自动回答视频中途弹出问题,可复制测验题目及答案;键盘左右方向键可以控制视频快进/快退,上下方向键可以控制音量增大/减小,空格键可以控制播放/暂停;自动签到慕课;如果视频标题下面出现 倍速/静音/播放/连播 选项说明脚本正常启动运行。
// @description:en  【使用前先看介绍/有问题可反馈】【欢迎一键三连(好评+打赏+收藏),你的支持是作者维护下去的最大动力!】UOOC优课联盟助手(UOOC assistant):可选是否倍速(若取消勾选则一倍速播放),可选是否静音(若取消勾选则恢复原音量),可选是否播放(若取消勾选则暂停播放),可选是否连播(若取消勾选则循环播放),离开页面保持状态,自动回答视频中途弹出问题,可复制测验题目及答案;键盘左右方向键可以控制视频快进/快退,上下方向键可以控制音量增大/减小,空格键可以控制播放/暂停;自动签到慕课;如果视频标题下面出现 倍速/静音/播放/连播 选项说明脚本正常启动运行。
// @author       cc
// @include      http://www.uooc.net.cn/*
// @grant        none
// @require      https://greasyfork.org/scripts/418193-coder-utils.js
// ==/UserScript==

(function () {
    'use strict';
    const jsName = 'UOOC-assistant.js';
    const RECURSION_DURATION = 500;
    if (location.host == 'www.uooc.net.cn') {
        console.log(`excute ${jsName}`);
        let recursion = () => {
            let extraTime = 0;
            try {
                let done = false;
                let video = document.querySelector('#player_html5_api');
                if (video) {
                    if (document.getElementById('rate').checked)
                        video.playbackRate = 2;
                    else
                        video.playbackRate = 1;
                    if (document.getElementById('volume').checked)
                        video.muted = true;
                    else
                        video.muted = false;
                    if (document.getElementById('play').checked && !video.ended)
                        video.play();
                    else
                        video.pause();
                    if (video.ended)
                        done = true;
                    let quizLayer = document.querySelector('#quizLayer');
                    if (quizLayer && quizLayer.style.display != 'none') {
                        if (done) {
                            setTimeout(() => {
                                document.querySelectorAll('.layui-layer-shade').forEach(e => e.style.display = 'none');
                            }, RECURSION_DURATION << 1);
                        };
                        let source = JSON.parse(document.querySelector('div[uooc-video]').getAttribute('source'));
                        let quizList = source.quiz;
                        let quizIndex = 0;
                        let quizQuestion = document.querySelector('.smallTest-view .ti-q-c').innerHTML;
                        for (let i = 0; i < quizList.length; i++) {
                            if (quizList[i].question == quizQuestion) {
                                quizIndex = i;
                                break;
                            };
                        };
                        let quizAnswer = eval(quizList[quizIndex].answer);
                        let quizOptions = quizLayer.querySelector('div.ti-alist');
                        for (let ans of quizAnswer) {
                            let labelIndex = ans.charCodeAt() - 'A'.charCodeAt();
                            quizOptions.children[labelIndex].click();
                        }; // end for
                        quizLayer.querySelector('button').click();
                        extraTime = 1000;
                    }; // end if
                    if (!done) {
                        if (video.paused && document.getElementById('play').checked) {
                            video.play();
                        } else {
                            document.querySelectorAll('.layui-layer-shade, #quizLayer').forEach(e => e.style.display = 'none');
                        };
                    };
                }; // end if (video)
                if (!done) {
                    setTimeout(recursion, RECURSION_DURATION + extraTime);
                } else if (video) {
                    if (!document.getElementById('continue').checked) {
                        video.currentTime = 0;
                        // video.ended = false;
                        setTimeout(recursion, RECURSION_DURATION + extraTime);
                    } else {
                        let current_video = document.querySelector('.basic.active');
                        let next_part = current_video.parentNode;
                        let next_video = current_video;
                        // 定义判断是否视频的函数
                        let isVideo = node => Boolean(node.querySelector('span.icon-video'));
                        // 定义是否可返回上一级目录的函数
                        let canBack = () => {
                            return Boolean(next_part.parentNode.parentNode.tagName === 'LI');
                        };
                        // 定义更新至后续视频的函数
                        let toNextVideo = () => {
                            next_video = next_video.nextElementSibling;
                            while (next_video && !isVideo(next_video)) {
                                next_video = next_video.nextElementSibling;
                            };
                        };
                        // 定义判断是否存在视频的函数
                        let isExistsVideo = () => {
                            let _video = next_part.firstElementChild;
                            while (_video && !isVideo(_video)) {
                                _video = _video.nextElementSibling;
                            };
                            return Boolean(_video && isVideo(_video));
                        };
                        // 定义判断是否存在后续视频的函数
                        let isExistsNextVideo = () => {
                            let _video = current_video.nextElementSibling;
                            while (_video && !isVideo(_video)) {
                                _video = _video.nextElementSibling;
                            };
                            return Boolean(_video && isVideo(_video));
                        };
                        // 定义检查文件后是否存在后续目录的函数
                        let isExistsNextListAfterFile = () => {
                            let part = next_part.nextElementSibling;
                            return Boolean(part && part.childElementCount > 0);
                        };
                        // 定义更新文件后的后续目录的函数
                        let toNextListAfterFile = () => {
                            next_part = next_part.nextElementSibling;
                        };
                        // 定义返回上一级的函数
                        let toOuterList = () => {
                            next_part = next_part.parentNode.parentNode;
                        };
                        // 定义返回主条目的函数
                        let toOuterItem = () => {
                            next_part = next_part.parentNode;
                        };
                        // 定义检查列表后是否存在后续目录的函数
                        let isExistsNextListAfterList = () => {
                            return Boolean(next_part.nextElementSibling);
                        };
                        // 定义进入列表后的后续目录的函数
                        let toNextListAfterList = () => {
                            next_part = next_part.nextElementSibling;
                        };
                        // 定义展开目录的函数
                        let expandList = () => {
                            next_part.firstElementChild.click();
                        };
                        // 定义进入展开目录的第一个块级元素的函数
                        let toExpandListFirstElement = () => {
                            next_part = next_part.firstElementChild.nextElementSibling;
                            if (next_part.classList.contains('unfoldInfo')) {
                                next_part = next_part.nextElementSibling;
                            };
                        };
                        // 定义判断块级元素是否目录列表的函数
                        let isList = () => {
                            return Boolean(next_part.tagName === 'UL');
                        };
                        // 定义目录列表的第一个目录的函数
                        let toInnerList = () => {
                            next_part = next_part.firstElementChild;
                        };
                        // 定义进入文件列表的第一个视频的函数
                        let toFirstVideo = () => {
                            next_video = next_part.firstElementChild;
                            while (next_video && !isVideo(next_video)) {
                                next_video = next_video.nextElementSibling;
                            };
                        };
                        // 定义模式
                        let mode = {
                            FIRST_VIDEO: 'FIRST_VIDEO',
                            NEXT_VIDEO: 'NEXT_VIDEO',
                            LAST_LIST: 'LAST_LIST',
                            NEXT_LIST: 'NEXT_LIST',
                            INNER_LIST: 'INNER_LIST',
                            OUTER_LIST: 'OUTER_LIST',
                            OUTER_ITEM: 'OUTER_ITEM',
                        }
                        // 定义搜索函数
                        let search = (_mode) => {
                            switch (_mode) {
                                case mode.FIRST_VIDEO: // mode = 0
                                    if (isExistsVideo()) {
                                        toFirstVideo();
                                        next_video.click();
                                        setTimeout(recursion, RECURSION_DURATION);
                                    } else if (isExistsNextListAfterFile()) {
                                        search(mode.LAST_LIST);
                                    } else {
                                        // perhaps there is an exam, end recursion
                                    };
                                    break;
                                case mode.NEXT_VIDEO: // mode == 1
                                    if (isExistsNextVideo()) {
                                        toNextVideo();
                                        next_video.click();
                                        setTimeout(recursion, RECURSION_DURATION);
                                    } else if (isExistsNextListAfterFile()) {
                                        search(mode.LAST_LIST);
                                    } else {
                                        search(mode.OUTER_ITEM);
                                    };
                                    break;
                                case mode.LAST_LIST: // mode == 2
                                    toNextListAfterFile();
                                    toInnerList();
                                    search(mode.INNER_LIST);
                                    break;
                                case mode.NEXT_LIST: // mode == 3
                                    toNextListAfterList();
                                    search(mode.INNER_LIST);
                                    break;
                                case mode.INNER_LIST: // mode == 4
                                    expandList();
                                    (function waitForExpand () {
                                        if (next_part.firstElementChild.nextElementSibling) {
                                            toExpandListFirstElement();
                                            if (isList()) {
                                                toInnerList();
                                                search(mode.INNER_LIST);
                                            } else {
                                                search(mode.FIRST_VIDEO);
                                            };
                                        } else {
                                            setTimeout(waitForExpand, RECURSION_DURATION);
                                        };
                                    })();
                                    break;
                                case mode.OUTER_LIST: // mode == 5
                                    toOuterList();
                                    if (isExistsNextListAfterList()) {
                                        search(mode.NEXT_LIST);
                                    } else if (canBack()) {
                                        search(mode.OUTER_LIST);
                                    } else {
                                        // perhaps there is no next list
                                    };
                                    break;
                                case mode.OUTER_ITEM: // mode == 6
                                    toOuterItem();
                                    if (isExistsNextListAfterList()) {
                                        toNextListAfterList();
                                        search(mode.INNER_LIST);
                                    } else if (canBack()){
                                        search(mode.OUTER_LIST);
                                    } else {
                                        // perhaps there is no list
                                    };
                                    break;
                                default:
                                    break;
                            };
                        };
                        try {
                            search(mode.NEXT_VIDEO);
                        } catch (err) {
                            console.log(err);
                        };
                    };
                };
            } catch (err) {
                console.error(err);
            };
        }; // end recursion
        let wait = () => {
            if (document.readyState == 'complete') {
                console.log('ready to set checkboxes.');
                let getCheckbox = (name, text) => {
                    let p = HTMLElement.$mkel('p', {}, {}, {
                        'color': '#cccccc',
                        'padding-left': '10px',
                    });
                    let checkbox = HTMLElement.$mkel('input', {
                        id: name,
                        type: 'checkbox',
                        name: name,
                        value: name,
                    }, {
                        checked: true,
                    }, {
                        'margin-left': '15px',
                        'width': '12px',
                        'height': '12px',
                    });
                    p.append(checkbox);
                    let label = HTMLElement.$mkel('label', {
                        for: name,
                    }, {
                        innerText: text,
                    }, {
                        'margin-left': '13px',
                        'font-size': '12px',
                    });
                    p.append(label);
                    return p;
                };
                let getContainer = (_id) => {
                    return HTMLElement.$mkel('div', {id: _id}, {}, {
                        'display': 'flex',
                        'flex-direction': 'row',
                        'align-items': 'center',
                    });
                };
                // set checkbox container
                let checkboxContainer = getContainer('checkbox-container');
                let rateCheckbox = getCheckbox('rate', '倍速');
                let volumeCheckbox = getCheckbox('volume', '静音');
                let playCheckbox = getCheckbox('play', '播放');
                let continueCheckbox = getCheckbox('continue', '连播');
                let copyCheckbox = HTMLElement.$mkel('p', {}, {}, {
                    'color': '#cccccc',
                    'padding-left': '10px',
                });
                let btn = HTMLElement.$mkel('button', {}, {innerHTML: '复制题目答案'}, {
                    'margin-left': '13px',
                    'padding': '0 5px 0',
                    'font-size': '12px',
                    'cursor': 'pointer',
                }, {
                    click: function(event) {
                        let testPaperTop = frames[0] ? frames[0].document.querySelector('.testPaper-Top') : document.querySelector('.testPaper-Top');
                        if (!testPaperTop) {
                            alert('该页面不是测验页面,无法复制内容');
                        } else {
                            if (testPaperTop.querySelector('.fl_right')) {
                                let queItems = frames[0] ? Array.from(frames[0].document.querySelectorAll('.queItems')) : Array.from(document.querySelectorAll('.queItems'));
                                let content = queItems.map(queType => {
                                    let res = '';
                                    if (queType.querySelector('.queItems-type').innerText.indexOf('选') >= 0) {
                                        let questions = queType.querySelectorAll('.queContainer');
                                        res += Array.from(questions).map(question => {
                                            let que = question.querySelector('.queBox').innerText.replace(/\n{2,}/g, '\n').replace(/(\w\.)\n/g, '$1 ');
                                            let ans = question.querySelector('.answerBox div:first-child').innerText.replace(/\n/g, '');
                                            let right = question.querySelector('.scores').innerText.match(/\d+\.?\d+/g).map(score => eval(score));
                                            right = right[0] === right[1];
                                            return `${que}\n${ans}\n是否正确:${right}\n`;
                                        }).join('\n');
                                    };
                                    return res;
                                }).join('\n');
                                content.$copyToClipboard();
                                alert('题目及答案已复制到剪切板');
                            } else {
                                alert('该测验可能还没提交,无法复制');
                            };
                        };
                    },
                });
                copyCheckbox.appendChild(btn);
                let head = document.querySelector('.learn-head');
                checkboxContainer.appendChild(rateCheckbox);
                checkboxContainer.appendChild(volumeCheckbox);
                checkboxContainer.appendChild(playCheckbox);
                checkboxContainer.appendChild(continueCheckbox);
                checkboxContainer.appendChild(copyCheckbox);
                // set prompt container
                let promptContainer = getContainer('prompt-container');
                let div = HTMLElement.$mkel('div', {}, {
                    innerHTML: '提示:键盘的 \u2190 和 \u2192 可以控制快进/快退,\u2191 和 \u2193 可以控制音量增大/减小,空格键可以控制播放/暂停',
                }, {
                    'color': '#cccccc',
                    'height': 'min-height',
                    'margin': '0 20px 0',
                    'padding': '0 5px',
                    'border-radius': '5px',
                    'font-size': '12px',
                });
                promptContainer.appendChild(div);
                let appreciationCodeContainer = getContainer('appreciation-code-container');
                let a = HTMLElement.$mkel('a', {
                    href: 'https://s1.ax1x.com/2020/11/08/BTeRqe.png',
                    target: '_blank',
                }, {
                    innerHTML: '<u>本脚本使用完全免费,您的打赏是作者维护下去的最大动力!点此打赏作者😊</u>',
                }, {
                    'color': '#cccccc',
                    'font-weight': 'bold',
                    'height': 'min-height',
                    'margin': '0 20px 0',
                    'padding': '0 5px',
                    'border-radius': '5px',
                    'font-size': '11px',
                });
                appreciationCodeContainer.appendChild(a);
                // set head
                head.appendChild(checkboxContainer);
                head.appendChild(promptContainer);
                head.appendChild(appreciationCodeContainer);
                head.style.height = `${head.offsetHeight + 30}px`;
                // information
                console.log('checkboxes have been set.');
                // auto login
                let cid = location.href.match(/\d+/g)[0];
                let httpRequest = new XMLHttpRequest();
                httpRequest.open('GET', `http://www.uooc.net.cn/home/course/info?cid=${cid}`, true);
                httpRequest.send();
                httpRequest.onreadystatechange = function () {
                    if (httpRequest.readyState == 4 && httpRequest.status == 200) {
                        console.log('Automatic sign-in succeeded.');
                    };
                };
                document.onkeydown = (event) => {
                    let k = event.key;
                    let complete = false;
                    let div = document.querySelector('div.basic.active');
                    if (div && div.classList.contains('complete'))
                        complete = true;
                    let video = document.getElementById('player_html5_api');
                    if (video) {
                        switch (k) {
                            case 'ArrowLeft': {
                                video.currentTime -= 10;
                                break;
                            };
                            case 'ArrowRight': {
                                if (complete)
                                    video.currentTime += 10;
                                break;
                            };
                            case 'ArrowUp': {
                                if (video.volume + 0.1 <= 1.0)
                                    video.volume += 0.1;
                                else
                                    video.volume = 1.0;
                                break;
                            }
                            case 'ArrowDown': {
                                if (video.volume - 0.1 >= 0.0)
                                    video.volume -= 0.1;
                                else
                                    video.volume = 0.0;
                                break;
                            };
                            case ' ': {
                                let continueCheckbox = document.getElementById('play');
                                continueCheckbox.checked = !continueCheckbox.checked;
                                break;
                            };
                        };
                    };
                };
                recursion();
            } else {
                setTimeout(wait, RECURSION_DURATION);
            };
        }; // end wait
        wait();
    }
})();