Greasy Fork is available in English.

电信网上大学超级学习

更快、更全、更好用的电信网上大学(知学云zhixueyun)学习工具。

// ==UserScript==
// @name        电信网上大学超级学习
// @namespace   remain_true_to_our_original_aspiration
// @version     1.3.1
// @description 更快、更全、更好用的电信网上大学(知学云zhixueyun)学习工具。
// @author      Ghost River
// @match       https://*.zhixueyun.com/*
// @icon        data:image/gif;base64,R0lGODlhIAAgAPcAAAAAAAAAMwAAZgAAmQAAzAAA/wArAAArMwArZgArmQArzAAr/wBVAABVMwBVZgBVmQBVzABV/wCAAACAMwCAZgCAmQCAzACA/wCqAACqMwCqZgCqmQCqzACq/wDVAADVMwDVZgDVmQDVzADV/wD/AAD/MwD/ZgD/mQD/zAD//zMAADMAMzMAZjMAmTMAzDMA/zMrADMrMzMrZjMrmTMrzDMr/zNVADNVMzNVZjNVmTNVzDNV/zOAADOAMzOAZjOAmTOAzDOA/zOqADOqMzOqZjOqmTOqzDOq/zPVADPVMzPVZjPVmTPVzDPV/zP/ADP/MzP/ZjP/mTP/zDP//2YAAGYAM2YAZmYAmWYAzGYA/2YrAGYrM2YrZmYrmWYrzGYr/2ZVAGZVM2ZVZmZVmWZVzGZV/2aAAGaAM2aAZmaAmWaAzGaA/2aqAGaqM2aqZmaqmWaqzGaq/2bVAGbVM2bVZmbVmWbVzGbV/2b/AGb/M2b/Zmb/mWb/zGb//5kAAJkAM5kAZpkAmZkAzJkA/5krAJkrM5krZpkrmZkrzJkr/5lVAJlVM5lVZplVmZlVzJlV/5mAAJmAM5mAZpmAmZmAzJmA/5mqAJmqM5mqZpmqmZmqzJmq/5nVAJnVM5nVZpnVmZnVzJnV/5n/AJn/M5n/Zpn/mZn/zJn//8wAAMwAM8wAZswAmcwAzMwA/8wrAMwrM8wrZswrmcwrzMwr/8xVAMxVM8xVZsxVmcxVzMxV/8yAAMyAM8yAZsyAmcyAzMyA/8yqAMyqM8yqZsyqmcyqzMyq/8zVAMzVM8zVZszVmczVzMzV/8z/AMz/M8z/Zsz/mcz/zMz///8AAP8AM/8AZv8Amf8AzP8A//8rAP8rM/8rZv8rmf8rzP8r//9VAP9VM/9VZv9Vmf9VzP9V//+AAP+AM/+AZv+Amf+AzP+A//+qAP+qM/+qZv+qmf+qzP+q///VAP/VM//VZv/Vmf/VzP/V////AP//M///Zv//mf//zP///wAAAAAAAAAAAAAAACH5BAEAAPwALAAAAAAgACAAAAj/APcJHEiw4D5i9AwqXLiwnR9UD3nVY8iQXsKFxGr5eXWRIsFCfgrSs1XrFS93+2z54eVx4LBXqAqiKsSrly2QLDe23DfzIU2BtQbqQ/XqlcBXhDxqTGjrVch9fughpPfn51GjC3v5mbiPECquKmfSc5VqGEE/vRbaGuiwo621+3oRKtRxn0OGL/38cbWRZS+W+4rGLPgKbkGVtdwFDexnWDunM18VMkjvKeG1vP4E3ZjX1jCVdQf6aWfwMy/O9ZwWdKzW8EB6Md1F5UXIFkqplfcNa7x6cMFCw1wVmomqskaOm1G53pfbIC9Uvcz6Wcv5YNFCy5lbJuiu48WtApXNrSStcLtHVPqG9SpZEKHA5gYz+rFM6E9MrQV5ASbme2AviETB1RQqZg0FmECpAMbLYgM9R0haBM1kWCGpECJZZAhCSJCGZ2H13kaFIKWcdjsJRIxCnxVmVkoMltiSMuC5uNOILSVkkY0HMUfYZDthtg9La7mSEkEauWjLIhOl1Ys+xNQkWioyrhXUWrbUY5ZRp7W400XK0LMkc/QM9wd5MopE4Xy1rFgmZY6FtlBAADs=
// @license     GPLv3
// @grant       GM_addStyle
// @grant       GM_openInTab
// @grant       GM_getValue
// @grant       GM_setValue
// @grant       GM_deleteValue
// @grant       GM_listValues
// @grant       GM_addValueChangeListener
// @grant       GM_removeValueChangeListener
// @grant       window.close
// @grant       window.focus
// @grant       unsafeWindow
// ==/UserScript==

(function() {
    'use strict';
    if (!window.location.href.match('/train-new/class-detail/|/study/subject/detail/|/study/course/detail/|/study/course/out-detail/')) return;
    //非学习页面退出。

    const ver = '1.3.0';

    const superCss = '.study_box{position:fixed;top:2%;left:1%;z-index:99999}.view_box{display:none;color:red;}.progress_box{display:none;background-color:skyblue;width:400px}.progress_item{display:flex;justify-content:space-between}.progress_item_title{overflow:hidden;white-space:nowrap;text-overflow:ellipsis;width:280px}dialog{width:500px;padding:10px;border:solid 1px #000;border-radius:10px;background-color:#fff;box-shadow:0 0 9px #666777;}';
    GM_addStyle(superCss);

    const max = 1;
    //当前只能为1
    var lessons = [];
    var lessonsinfo = {};
    var sections = [];
    var isTrain = false;
    var taskID = 0;
    var initID = '';
    var hasExam = [];
    var hasError = [];
    var currentSection = '';
    var studying = {};
    var count = 0;
    var listenerId = null
    var urls = { 'normal':`${window.location.origin}/#/study/course/detail/10&$id$/6/1`,
                'course':`${window.location.origin}/#/study/course/detail/11&$id$/5/1`}

    var isCourse = window.location.href.match("/study/course/detail/|/study/course/out-detail/")
    //是否学习页

    function checkStatus(){
        setTimeout(function () {
            let r = GM_getValue("g.r:task" + initID, 0);
            if ( r != taskID ) window.close();
            //学习任务页与控制页失联,有新的页面打开,关闭本任务页。
            let requires = $(".item.pointer.item22[style$='margin-right:12px']");
            if(requires.length==0){
                GM_sendMessage('remain.true.to.our.original.aspiration', initID,"finished with exam is "+ ($('.chapter-list-box.required[data-sectiontype=9]').length>0));
                $.post('https://kc.zhixueyun.com/api/v1/course-study/course-front/score',`score=10&businessId=${initID}&businessType=1`);
                GM_deleteValue("g.r:task" + initID);
                window.close();
            }else{
                if(!document.title.endsWith('🟩')) document.title += '🟩';
                GM_sendMessage('remain.true.to.our.original.aspiration', initID,"studying|" + Date.now() + "|" + $('div[style$="margin-right:12px"]').find('span').text().match(/ \d+/g).reduce((accumulator, current)=>accumulator + parseInt(current),1));
                autoPlay();
                checkStatus();
            }
        }, 17000);
    }

    function autoPlay() {
        if($('video').length>0) {
            if ($('video')[0].onplay === null) {
                $('video')[0].onplay = function() {
                    $('video')[0].muted = true;
                };
            };
            if ($('video')[0].onpause === null) {
                $('video')[0].onpause = function() {
                    $('video')[0].play();
                };
            };
            $('video')[0].muted = true;
            if( $('video')[0].paused) {
                $('video')[0].play();
            }
        }
    }

    function clearLostTasks() {
        const keys = GM_listValues();
        let nt = Date.now();
        for(let k in keys) {
            if (keys[k].startsWith('g.r:task')) {
                let id = GM_getValue(keys[k], nt);
                if((nt - id) > 43200000) GM_deleteValue(keys[k]);
            }
        }
    }

    if( !isCourse ) {
        let r = sessionStorage.getItem(window.location.href.slice(-36));
        if (r) {
            let rhtml = JSON.parse(r);
            $(rhtml).appendTo("body");
			sessionStorage.removeItem(window.location.href.slice(-36));
        } else {
            GM_deleteValue('remain.true.to.our.original.aspiration');
            clearLostTasks();
            let notice='';
            if(ver>GM_getValue("g.r:version", '')) {
                notice = `<a href="https://greasyfork.org/zh-CN/scripts/472634-%E7%94%B5%E4%BF%A1%E7%BD%91%E4%B8%8A%E5%A4%A7%E5%AD%A6%E8%B6%85%E7%BA%A7%E5%AD%A6%E4%B9%A0" target="_blank"><span style="color:red;">⚠️超级学习 ${ver} 版有重要更新说明,去查看➡️</span></a>`;
                GM_setValue("g.r:version", ver);
            }
            $(`<div id="autostudydiv" class="study_box">
              <a id="autostudy" class="cd-btn cd-btn-primary" >自动学习</a>
              <a id="wsView" class="view_box">查看详细情况⯆</a>${notice}<div id="progress" class="progress_box"></div>
              </div>`).appendTo("body");
            //课程列表页,增加学习按钮
        }
    } else {
        initID = GM_getValue("g.r:current", 0);
        if(initID) GM_deleteValue("g.r:current");
        let r = GM_getValue("g.r:task" + initID, 0);
        if (r) {
            taskID = r;
        } else {
            console.log('非自动学习打开的页面不处理。');
            return;
        }
        //非自动学习打开的页面不处理。
        let waitApp = setInterval(function() {
            if( window.location.href.match('error-page')) {
                //资源不存在
                GM_sendMessage('remain.true.to.our.original.aspiration', initID,"finished with error");
                window.close();
            };
            try {
                let modleId = Object.keys(unsafeWindow.app._modules).find(value => value.startsWith('study/course/detail--'));
                unsafeWindow.app._modules[modleId].store.models.course.data.courseChapters.forEach(function(Chapter){
                    if(Chapter.learnSequence) Chapter.learnSequence = null;
                });
                //去除章节顺序限制
            }
            catch {
                return;
            }
            clearInterval(waitApp);
            let requires = $(".item.pointer.item22[style$='margin-right:12px']");
            if(requires.length>0){
                if($('.focus').find('div[style$="margin-right:12px"]').length == 0) {
                    requires[0].click();
                }
            }
            document.title += '🟩';
            checkStatus();
        },1000);
        let c = 0;
        let waitPlay = setInterval(function() {
            try {
                if (!$('video')[0].muted) {
                    autoPlay();
                    clearInterval(waitPlay);
                }
            }
            catch {
                console.log('no muted');
                c++;
                console.log(c);
                if(c>4) clearInterval(waitPlay);
                return;
            }
        },1000);
    }

    $("#wsView").click(function () {
        if($('#progress').is(':hidden')){
            $("#progress").show();
            $("#wsView").text('查看详细情况▲');
        }else{
            $("#progress").hide();
            $("#wsView").text('查看详细情况▼');
        }
    });

    function GM_onMessage(label, callback) {
        listenerId = GM_addValueChangeListener(label, function() {
            callback.apply(undefined, arguments[2]);
        });
    }

    function GM_sendMessage(label) {
        GM_setValue(label, Array.from(arguments).slice(1));
    }

    function studyFinised() {
        if(lessonsinfo == null) return;
        let resultStr = '所有课程学习完成!'
        let url = '';
        if(isTrain) {
            url = urls['course'];
        } else {
            url = urls['normal'];
        }
        let ul = '';
        if (hasError.length>0) {
            resultStr += '部分课程资源不存在!';
            hasError.forEach(function(id){
                ul += `<li><a href="${url.replace('$id$',id)}" target="_blank">${lessonsinfo[id]}</a></li>`
            })
        }
        if (hasExam.length>0) {
            resultStr += '注意!部分课程包含考试!';
            hasExam.forEach(function(id){
                ul += `<li><a href="${url.replace('$id$',id)}" target="_blank"><span style="color:red;">${lessonsinfo[id]}</span></a></li>`
            })
        }
        $("#autostudy").text(resultStr)
        lessonsinfo = null;
        if(listenerId != null) GM_removeValueChangeListener(listenerId);
        GM_deleteValue('remain.true.to.our.original.aspiration');
        if(resultStr == '所有课程学习完成!') {
            $("#wsView").remove();
            $('#progress').remove();
            if(Math.random()>0.9) {
                $("#autostudydiv").append('<a href="https://greasyfork.org/zh-CN/scripts/472634-%E7%94%B5%E4%BF%A1%E7%BD%91%E4%B8%8A%E5%A4%A7%E5%AD%A6%E8%B6%85%E7%BA%A7%E5%AD%A6%E4%B9%A0" target="_blank"><span style="color:red;">的确好用,给作者打赏。</span></a>')
            }
        } else {
            $('#progress').append(`<ul>${ul}</ul>`);
            $("#wsView").show();
            $('#progress').show();
        }
        sessionStorage.setItem(window.location.href.slice(-36),JSON.stringify($('#autostudydiv').prop("outerHTML")))
        location.reload();
    }

    function return2Contral() {
        window.focus();
        if($('#progress').is(':hidden')){
            $("#wsView").click();
        }
    }

    function doStudy() {
        if(lessons.length == 0) {
            if(count == 0) studyFinised();
            return2Contral()
            return;
        }
        if(listenerId === null) {
            GM_onMessage('remain.true.to.our.original.aspiration', function(src, message) {
                //console.log((new Date()).toLocaleString() + ' ' + src + ' : ' + message);
                if( !isCourse && (src in studying)){
                    if(message.startsWith('studying')) {
                        //let per = message.replace('studying ','');
                        let per = '还需学 ' + message.split("|")[2] + ' 分钟';
                        if(per) $('#p'+ src).text(per)
                        studying[src] = true;
                        return;
                    }
                    if( message.endsWith("true")) hasExam.push(src)
                    if( message.endsWith("error")) hasError.push(src)
                    count--;
                    $('#' + src).remove();
                    delete studying[src]
                    if(lessons.length == 0) {
			if (count == 0) studyFinised();
		    } else {
			doStudy();
			$("#autostudy").text('自动学习中,学习中课程:' + count + ',待学习课程:' + lessons.length + '。')
		    }
                }
            })
        }
        $("#wsView").show();
        if (count < max) {
            let url = ''
            if(isTrain) {
                url = urls['course']
            } else {
                url = urls['normal']
            }
            let id = lessons.shift()
            url = url.replace('$id$',id)
            studying[id] = true;
            $('#progress').append(`<div id="${id}" class="progress_item"><span class="progress_item_title">${lessonsinfo[id]}</span><span id="p${id}" style="float:right;">待上报</span></div>`)
            count++;
            $("#autostudy").text(`自动学习中,学习中课程:${count},待学习课程:${lessons.length}。`)
            //console.log(id)
            GM_setValue("g.r:current", id);
            GM_setValue("g.r:task" + id, Date.now());
            GM_openInTab(url,{ active: true, insert: true, setParent :true });
            setTimeout(function(){
               doStudy();
            },9000);
        } else {
            return2Contral()
        }
    }

    function getlessons(callback) {
        if (sections.length==0)
        {
            doStudy();
            return;
        }
        currentSection = sections.shift();
        let tk = $('#' + currentSection);
        console.log('当前学习板块: ' + currentSection)
        if(tk.attr('title') != '收起') {
            tk.click();
        }
        setTimeout(function(){
            $('div.train-citem:contains(未完成)').each(function() {
                $(this).find('.m-bottom.row-title-a.pointer').each(function() {
                    if(!$(this).attr('title').includes('考试')) {
                        let id = $(this).attr('id').slice(-36);
                        console.log('待学习课程id: ' + id);
                        lessons.push(id);
                        lessonsinfo[id] = $(this).attr("title");
                    }
                })
            })
            getlessons(callback);
        },2000);
    }

    function heartbeat() {
        if(count == 0) return;
        setTimeout(function(){
            console.log('检查心跳')
            let needRestart = false;
            for(var key in studying) {
                if(!studying[key]) {
                    taskID++;
                    console.log(key + ' 异常停止,重启')
                    needRestart = true;
                    lessons.unshift(key);
                    delete studying[key];
                     $('#' + key).remove();
                    count--;
                }
                studying[key] = false;
            }
            if(needRestart) doStudy()
            if( taskID > max) {
                $("#wsView").after('任务页失联次数过多!请参考<a href="https://greasyfork.org/zh-CN/scripts/472634-%E7%94%B5%E4%BF%A1%E7%BD%91%E4%B8%8A%E5%A4%A7%E5%AD%A6%E8%B6%85%E7%BA%A7%E5%AD%A6%E4%B9%A0" target="_blank"><span style="color:red;">浏览器问题</span></a>检测是否关闭浏览器节能功能。');
            } else {
                heartbeat();
            }
        },179000)
    }

    $("#autostudy").click(function () {
        if($(this).text() != "自动学习") return;
        //if (('WebSocket' in unsafeWindow) && (max > 1)) {
        //    $(this).text('❌未检测到多开支持!');
        //    $("#autostudydiv").append('请先安装并启用 <a href="https://greasyfork.org/zh-CN/scripts/472577-%E7%94%B5%E4%BF%A1%E7%BD%91%E4%B8%8A%E5%A4%A7%E5%AD%A6%E8%B6%85%E7%BA%A7%E5%AD%A6%E4%B9%A0%E5%A4%9A%E5%BC%80%E6%94%AF%E6%8C%81" target="_blank"><span style="color:red;">电信网上大学超级学习多开支持</span></a>');
        //    return;
        //}
        document.title += '🟥';
        $(this).text('获取课程中');
        console.log('开始');
        if( lessons.length > 0) return;
        $('.pointer.iconfont[id][title]').each(function() {
            sections.push($(this).attr('id'))
        })
        if(sections.length == 0) {
            console.log('普通课程');
            $("div.item.current-hover").each(function(index,element){
                if($(this).find("i.icon-reload").length==0 && $(this)[0].firstElementChild.querySelector("span.section-type").innerText == "课程"){
                    let id = $(this).attr("data-resource-id").slice(-36);
                    lessons.push(id);
                    lessonsinfo[id] = $(this).find('.inline-block.name-des.default-skin.text-overflow').attr("title");
                    //console.log(lessonsinfo);
                }
            })
            doStudy();
        } else {
            console.log('培训班');
            isTrain = true;
            getlessons(doStudy);
        }
        heartbeat()
    });
})();