Icourse163 Downloader

add download button on icourse163.org to download videos

As of 2019-01-12. See the latest version.

// ==UserScript==
// @name              中国大学 MOOC 下载助手(需配合Aria2使用)
// @name:en           Icourse163 Downloader
// @namespace         http://mofiter.com/
// @version           0.3
// @description       在中国大学 MOOC 的课程学习页面添加批量下载按钮,方便将视频下载到本地学习
// @description:en    add download button on icourse163.org to download videos
// @author            mofiter
// @require           https://cdn.bootcss.com/jquery/1.12.4/jquery.min.js
// @match             *://www.icourse163.org/learn/*
// @grant             unsafeWindow
// @grant             GM_getValue
// @grant             GM_setValue
// @grant             GM_xmlhttpRequest
// @grant             GM_openInTab
// ==/UserScript==

(function() {
    'use strict';
    var $ = $ || window.$;
    var log_count = 1;
    var hasOpenAriac2Tab = false;
    var video_quality = 2; //视频清晰度
    var video_format = 'mp4'; //视频格式
    var aria2_url = "http://127.0.0.1:6800/jsonrpc"; //Aria2 地址
    var course_save_path = '/Users/mofiter/Downloads/icourse163'; //课程保存路径
    var video_save_path; //每个视频保存路径
    var video_download_url = ""; //视频下载地址
    var course_info = {'course_id': {},'course_name': {},'chapter_info': []}; //课程信息
    var cookies = document.cookie;
    var sessionId = cookies.match(/NTESSTUDYSI=(\w+)/)[1];

    //自定义 log 函数
    function mylog(param1,param2){
        param1 = param1 ? param1 : "";
        param2 = param2 ? param2 : "";
        console.log("#" + log_count++ + "-Icourse163Downloader-log:",param1,param2);
    }

    setTimeout(function(){
        getCourseIdAndName();
        getCourseContentInfo();
        loadSetting();
        addDownloadAssistant();
        mylog("中国大学 MOOC 下载助手加载完成~");
    },2000); //页面加载完成后延时2秒执行

    //获取课程名称
    function getCourseIdAndName(){
        var courseCardDto = unsafeWindow.courseCardDto;
        course_info.course_name = courseCardDto.name.replace(/\/|:|\?|\*|"|<|>|\|/g," ");
        if(location.href.match(/tid=(\d+)/)[1]){
            course_info.course_id = location.href.match(/tid=(\d+)/)[1];
        }else{
            course_info.course_id = courseCardDto.currentTermId;
        }
    }

    //添加批量下载和下载设置按钮
    function addDownloadAssistant(){
        var batch_download_li = $("<li class='u-greentab'></li>");
        var batch_download = $("<a>批量下载</a>");
        batch_download_li.append(batch_download);
        var assistant_setting_li = $("<li class='u-greentab'></li>");
        var assistant_setting = $("<a>下载设置</a>");
        assistant_setting_li.append(assistant_setting);
        $('#j-courseTabList').append(batch_download_li).append(assistant_setting_li);
        batch_download_li.click(function(){
            loadSetting();
            if(course_save_path==""){
                alert("请点击下载设置去填写文件保存位置");
            }else if(aria2_url==""){
                alert("请点击下载设置去填写 Aria2 地址");
            }else{
                batchDownload();
            }
        });
        assistant_setting_li.click(function(){
            showSetting();
        });
    }

    //加载个人设置
    function loadSetting(){
        video_quality = GM_getValue('video_quality', 2);
        video_format = GM_getValue('video_format','mp4');
        aria2_url = GM_getValue('aria2_url','http://127.0.0.1:6800/jsonrpc');
        course_save_path = GM_getValue('course_save_path','');
    }

    //打开设置
    function showSetting(){
        if(document.querySelector('#dl-setting') == null){
            var container = document.createElement("div");
            container.id = "dl-setting";
            container.style = "position:fixed;z-index:999999;bottom:10%;right:40px;width:220px;height:auto;background-color:#f8f8f8;padding:5px 10px;font-size:14px;border:1px solid #ccc;";
            container.innerHTML =
            "<div style='line-height:25px;'>" +
            "<legend style='text-align:center;font-size:16px;'>下载设置</legend>" +
            "<ul>" +
            "<li>Aria2 地址:</li>" +
            "<li><input type='text' id='aria2_url' name='aria2_url' value='" + aria2_url + "' style='width:100%;background:#ffffff;'></input></li>" +
            "<li>文件保存位置:</li>\n" +
            "<li><input type='text' id='save_path' name='save_path' value='" + course_save_path + "' style='width:100%;background:#ffffff;'></input></li>" +
            "<li>清晰度:<label title='高清'><input id='video-quality-2' name='video-quality' value='2' type='radio' style='margin:0 5px;'" + (video_quality==2 ? "checked":"") + "></input>高清</label>\n" +
            "<label title='标清' style='padding:0 5px;'><input id='video-quality-1' name='video-quality' value='1' type='radio' style='margin:0 5px;'" + (video_quality==1 ? "checked":"") + "></input>标清</label></li>\n" +
            "<li>格式:<label title='mp4' style='padding:0 0 0 14px;'><input id='video-format-mp4' name='video-format' value='mp4' type='radio' style='margin:0 5px;'" + (video_format=='mp4' ? "checked":"") + "></input>mp4</label>" +
            "<label title='flv' style='padding:0 5px;'><input id='video-format-flv' name='video-format' value='flv' type='radio' style='margin:0 5px 0 10px;'" + (video_format=='flv' ? "checked":"") + "></input>flv</label></li>" +
            "</ul>\n" +
            "<input type='button' value='取消' id='cancel_button' style='position:relative;float:left;border:1px solid #ccc;padding:0 2px;background:#ffffff;'></input>\n" +
            "<input type='button' value='保存' id='save_button' style='position:relative;float:right;border:1px solid #ccc;padding:0 2px;background:#ffffff;'></input>\n" +
            "</div>";
            document.body.appendChild(container);
        }else{
            loadSetting();
            if(video_quality==2){
                $('#video-quality-2').prop('checked',true);
            }else{
                $('#video-quality-1').prop('checked',true);
            }
            if(video_format=='mp4'){
                $('#video-format-mp4').prop('checked',true);
            }else{
                $('#video-format-flv').prop('checked',true);
            }
            $('#aria2_url').value = aria2_url;
            $('#save_path').value = course_save_path;
            $('#dl-setting').show();
        }
        $('#save_button').click(function(){
            GM_setValue('video_quality',$('input[name="video-quality"]:checked').val());
            GM_setValue('video_format',$('input[name="video-format"]:checked').val());
            GM_setValue('aria2_url',$('input[name="aria2_url"]').val());
            GM_setValue('course_save_path',$('input[name="save_path"]').val());
            $('#dl-setting').hide();
        });
        $('#cancel_button').click(function(){
            $('#dl-setting').hide();
        });
    }

    //获取课程信息
    function getCourseContentInfo(){
        var timestamp = new Date().getTime();
        var params = {
            "callCount":"1",
            "scriptSessionId":"${scriptSessionId}190",
            "httpSessionId":sessionId,
            "c0-scriptName":"CourseBean",
            "c0-methodName":"getLastLearnedMocTermDto",
            "c0-id":"0",
            "c0-param0":"number:" + course_info.course_id,
            "batchId":timestamp
        };
        $.ajax({
            url:'https://www.icourse163.org/dwr/call/plaincall/CourseBean.getLastLearnedMocTermDto.dwr',
            method:'POST',
            async: true,
            data: params,
            success: function (response){
                var chapter1 = response.match(/chapters=(.*?);/)[1]; //保存全部章节的变量
                var reg1 = new RegExp(chapter1 + '\\[\\d+]=(.*?);','g');
                var chapter2 = response.match(reg1); //保存各章节的变量
                chapter2.forEach(function(value){
                    var chapter3 = value.match(/=(.*?);/)[1]; //保存各章节的变量名字
                    var reg2 = new RegExp(chapter3 + '.id=(.*?);' + chapter3 + '.lessons=(.*?);' + chapter3 + '.name="(.*?)";');
                    var chapter4 = response.match(reg2); //保存章节 id,lessons,name 的变量
                    var reg3 = new RegExp(chapter4[2] + '\\[\\d+]=(.*?);','g');
                    var lessons = response.match(reg3); //保存各小节的变量
                    var chapter = {'chapter_id':chapter4[1],'chapter_name':unescape(chapter4[3].replace(/\\u/gi, '%u').replace(/\/|:|\?|\*|"|<|>|\|/g," ")),'lesson_info':[]};
                    lessons.forEach(function(value){
                        var lesson1 = value.match(/=(.*?);/)[1]; //保存各小节的变量名字
                        var reg4 = new RegExp(lesson1 + '.chapterId=(.*?);.*?' + lesson1 + '.id=(.*?);.*?' + lesson1 + '.name="(.*?)";.*?' + lesson1 + '.units=(.*?);');
                        var lesson2 = response.match(reg4);
                        var reg5 = new RegExp(lesson2[4] + '\\[\\d+]=(.*?);','g');
                        var sections = response.match(reg5);
                        var lesson = {'chapter_id':lesson2[1],'lesson_id':lesson2[2],'lesson_name':unescape(lesson2[3].replace(/\\u/gi, '%u').replace(/\/|:|\?|\*|"|<|>|\|/g," ")),'section_info':[]};
                        sections.forEach(function(value){
                            var section1 = value.match(/=(.*?);/)[1];
                            var reg6 = new RegExp(section1 + '.chapterId=(.*?);.*?' + section1 + '.contentId=(.*?);.*?' + section1 + '.contentType=(.*?);.*?' + section1 + '.id=(.*?);.*?' + section1 + '.lessonId=(.*?);.*?' + section1 + '.name="(.*?)";.*?');
                            var section2 = response.match(reg6);
                            var section = {'chapter_id':section2[1],'lesson_id':section2[5],'content_id':section2[2],'section_id':section2[4],'section_name':unescape(section2[6].replace(/\\u/gi, '%u').replace(/\/|:|\?|\*|"|<|>|\|/g," ")),'content_type':section2[3]};
                            lesson.section_info.push(section);
                        });
                        chapter.lesson_info.push(lesson);
                    });
                    course_info.chapter_info.push(chapter);
                });
                mylog(course_info);
            }
        });
    }

    //批量下载
    function batchDownload(){
        course_info.chapter_info.forEach(function(chapter,index_chapter){
            chapter.lesson_info.forEach(function(lesson,index_lesson){
                lesson.section_info.forEach(function(section,index_section){
                    var file_name = '第' + (index_section + 1) + '部分_' + section.section_name;
                    var save_path = course_save_path.replace(/\\/g,'\/') + '/' + course_info.course_name + '/第' + (index_chapter + 1) + '章_' + chapter.chapter_name + '/第' + (index_lesson + 1) + '节_' + lesson.lesson_name;
                    if(section.content_type == '1'){
                        getVideoToken(section.content_id,file_name,save_path);
                    } else if(section.content_type == '3'){
                        getCourseContentUrl(section.content_id,section.section_id,file_name,save_path);
                    }
                });
            });
        });
    }

    //获取文档下载地址
    function getCourseContentUrl(content_id,section_id,file_name,save_path){
        var timestamp = new Date().getTime();
        var params = {
            "callCount":"1",
            "scriptSessionId":"${scriptSessionId}190",
            "httpSessionId":sessionId,
            "c0-scriptName":"CourseBean",
            "c0-methodName":"getLessonUnitLearnVo",
            "c0-id":"0",
            "c0-param0":"number:" + content_id,
            "c0-param1":"number:3",
            "c0-param2":"number:0",
            "c0-param3":"number:" + section_id,
            "batchId":timestamp
        };
        $.ajax({
            url:'https://www.icourse163.org/dwr/call/plaincall/CourseBean.getLessonUnitLearnVo.dwr',
            method:'POST',
            async: true,
            data: params,
            success: function (response){
                var pdfUrl = response.match(/textOrigUrl:"(.*?)",/)[1];
                //mylog(pdfUrl);
                sendDownloadTaskToAria2(pdfUrl,file_name + ".pdf",save_path);
            }
        });
    }

    //获取视频信息
    function getVideoToken(content_id,file_name,save_path){
        var params = {
            'videoId':content_id,
            'targetId':course_info.course_id,
            'targetType':'0'
        };
        $.ajax({
            url:'https://www.icourse163.org/web/j/resourceRpcBean.getVideoToken.rpc?csrfKey=' + sessionId,
            method:'POST',
            async: true,
            data: params,
            success: function (response){
                var signature = response.result.signature;
                getVideoUrl(content_id,signature,file_name,save_path);
            }
        });
    }

    //获取视频下载地址
    function getVideoUrl(content_id,signature,file_name,save_path){
        var params = {
            'videoId':content_id,
            'signature':signature,
            'clientType':'1'
        };
        $.ajax({
            url:'https://vod.study.163.com/eds/api/v1/vod/video',
            method:'POST',
            async: true,
            data: params,
            success: function (response){
                var videoUrls = response.result.videos;
                var video_url_list = [];
                videoUrls.forEach(function(video){
                    if(video.format == video_format) {
                        video_url_list.push({'video_format': video.format,'video_quality': video.quality,'video_url': video.videoUrl});
                    }
                });
                if(video_url_list.length != 0){
                    if(video_quality=="2"){
                        video_download_url = video_url_list[video_url_list.length-1].video_url;
                    }else{
                        video_download_url = video_url_list[0].video_url;
                    }
                }
                if(video_download_url != ""){
                    //mylog(video_download_url);
                    sendDownloadTaskToAria2(video_download_url,file_name + '.' + video_format,save_path);
                }
            }
        });
    }

    // 将下载链接发送到 Aria2 下载
    function sendDownloadTaskToAria2(download_url,file_name,save_path){
        var json_rpc = {
            id:'',
            jsonrpc:'2.0',
            method:'aria2.addUri',
            params:[
                [download_url],
                {
                    dir:save_path,
                    out:file_name
                }
            ]
        };
        GM_xmlhttpRequest({
            url:aria2_url,
            method:'POST',
            data:JSON.stringify(json_rpc),
            onerror:function(response){
                mylog(response);
            },
            onload:function(response){
                mylog(response);
                if (!hasOpenAriac2Tab){
                    GM_openInTab('http://aria2c.com/',{active:true});
                    hasOpenAriac2Tab = true;
                }
            }
        });
    }

})();