Greasy Fork

Greasy Fork is available in English.

edX网课下载器

一键下载edX网课视频和字幕,并保存为相同的文件名(除了文件后缀)。<https://github.com/jks-liu/edx-downloader.monkey.js>

当前为 2021-07-01 提交的版本,查看 最新版本

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Greasemonkey 油猴子Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Userscripts ,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==UserScript==
// @name         edX Downloader
// @name:zh-CN   edX网课下载器
// @name:zh-TW	 edX網課下載器
// @name:ja      edXダウンローダー
// @namespace    https://github.com/jks-liu/edx-downloader.monkey.js
// @supportURL   https://github.com/jks-liu/edx-downloader.monkey.js
// @version      2.0.0
// @description  Download edX course mp4 and srt in one click, and save them as same file name (except the file suffix). <https://github.com/jks-liu/edx-downloader.monkey.js>
// @description:zh-CN  一键下载edX网课视频和字幕,并保存为相同的文件名(除了文件后缀)。<https://github.com/jks-liu/edx-downloader.monkey.js>
// @description:zh-TW  一鍵下載edX網課視頻和字幕,並保存爲相同的文件名(除了文件後綴)。<https://github.com/jks-liu/edx-downloader.monkey.js>
// @description:ja     edXオンラインコースのビデオと字幕をワンクリックでダウンロードし、同じファイル名で保存します(ファイル拡張子を除く)。<https://github.com/jks-liu/edx-downloader.monkey.js>
// @author       Jks Liu (https://github.com/jks-liu)
// @license      MIT
// @match        https://edx.org/*
// @match        https://www.edx.org/*
// @match        https://learning.edx.org/*
// @match        https://courses.edx.org/*
// @icon         https://www.google.com/s2/favicons?domain=edx.com
// @grant        GM_download
// @grant        GM_setValue
// @grant        GM_getValue
// ==/UserScript==

// https://stackoverflow.com/questions/14308588/simple-jquery-selector-only-selects-first-element-in-chrome
// If jQuery isn't present on the webpage, and of course no other code assigns something to $, Chrome's JS console assigns $ a shortcut to document.querySelector().
// You can achieve what you want with $$(), which is assigned by the console a shortcut to document.querySelectorAll().

// TODO: Add real title
// TODO: Download in floder
// TODO: label of already download
// TODO: Process bar
// TODO: Count of current course
// TODO: Download html
// TODO: Auto goto nextpage and download

// jks_ is the namespace

// The structure is as below
// learning.edx.org -> Containing title
//      ifram, courses.edx.org -> Containing video address


function jks_is_host(site) {
    return window.location.href.indexOf(site) != -1
}


function jks_learning_edu_org() {
    'use strict';

    // Original page has no jQuery
    // $$ can be used in broswer, but not here
    // below line from https://github.com/akhodakivskiy/VimFx/issues/841#issuecomment-263197162
    const $$ = (...args) => Array.from(document.querySelectorAll(...args));

    var checkExist = setInterval(function() {
        if ($$("ol.list-unstyled a").length && $$("div.sequence-navigation-tabs > button").length) {     
            let course_name = $$("ol.list-unstyled a")[1].text;
            let paragraph_name = $$("ol.list-unstyled a")[2].text;
        
            let all_sections = $$("div.sequence-navigation-tabs > button").map(button=>button.title);
            let active_section_index = $$("div.sequence-navigation-tabs > button").findIndex(button=>button.classList.contains("active"));
            let active_section = all_sections[active_section_index];
        
            console.log("jks edx names", course_name, paragraph_name, active_section);
            GM_setValue("names", [course_name, paragraph_name, active_section, active_section_index])

            clearInterval(checkExist);
        }
     }, 100); // check every 100ms
}

function jks_courses_edx_org() {
    'use strict';

    // Original page has jQuery

    // The content is under an iframe
    // So @match is https://courses.edx.org, not https://learning.edx.org
    // Can not use `window.$` as below ajax, don't know why
    let video = $('.video-download-button')[0];
    let video_url = video.href;

    // Create a button
    let download_all_button = document.createElement("div");
    download_all_button.innerHTML = `
        <label>
            <input id="jks_checkbox_mp4" type="checkbox", checked=true>
            <span>*.mp4</span>
            <input id="jks_checkbox_srt" type="checkbox", checked=true>
            <span>*.srt</span>
            <input id="jks_checkbox_txt" type="checkbox">
            <span>*.txt</span>
        </label>
        <button id="jks_button">Download All</button>
        <form>
            <label for="jks_input_folder">Folder</label>
            <input type="text" id="jks_input_folder" name="jks_input_folder" size="72">
            <label for="jks_input_mp4">*.mp4 location:</label>
            <input type="text" id="jks_input_mp4" name="jks_input_mp4" size="72">
            <label for="jks_input_srt">*.srt location:</label>
            <input type="text" id="jks_input_srt" name="jks_input_srt" size="72">
            <label for="jks_input_txt">*.txt location:</label>
            <input type="text" id="jks_input_txt" name="jks_input_txt" size="72">
        </form> 
        `;
    // Video download event is at parent element 
    // So add button after parent, or it's button click will be hide by parent
    // Add 2 other parentElement to widen input box
    video.parentElement.parentElement.parentElement.append(download_all_button)

    // Cannot use $, don't know why
    let srt_url = document.querySelector("li.transcript-option a").href;

    // Use $ instead of window.$, previously window.$ is ok, don't know why
    $.ajax({
        url: document.querySelector("li.transcript-option a").href,
        type: "HEAD",
        success: function(res, status, xhr) {
            let srt_header = xhr.getResponseHeader("content-disposition");
            // attachment; filename="02_TinyML_C02_03-01-01-en.srt" => 02_TinyML_C02_03-01-01-en.srt
            // let srt_file_name = srt_header.slice(22, -1);
            // let mp4_file_name = srt_file_name.slice(0, -4)+".mp4";
            // let txt_file_name = srt_file_name.slice(0, -4)+".txt";

            let names = GM_getValue("names", ["edx-unknown-course", "edx-unknown-paragraph", "edx-unknown-section"]);
            let folder_name = names[0]+"/"+names[1]
            let base_name = String(names[3]).padStart(2, "0")+"-"+names[2];
            let mp4_file_name = base_name + ".mp4";
            let srt_file_name = base_name + ".srt";
            let txt_file_name = base_name + ".txt";
            $("#jks_input_folder")[0].value = folder_name;
            $("#jks_input_mp4")[0].value = mp4_file_name;
            $("#jks_input_srt")[0].value = srt_file_name;
            $("#jks_input_txt")[0].value = txt_file_name;

            $("#jks_button").click(function() {
                if ($("#jks_checkbox_mp4")[0].checked) {
                    // video is CORS (Cross-origin resource)
                    // Set tampermonkey `Download Mode` option to `Browser API`
                    // https://www.tampermonkey.net/faq.php?ext=dhdg#Q302
                    GM_download(video_url, $("#jks_input_folder")[0].value+"/"+$("#jks_input_mp4")[0].value);
                }

                if ($("#jks_checkbox_srt")[0].checked) {
                    GM_download(srt_url, $("#jks_input_folder")[0].value+"/"+$("#jks_input_srt")[0].value);
                }

                if ($("#jks_checkbox_txt")[0].checked) {
                // Txt and srt and the same file
                    GM_download(srt_url, $("#jks_input_folder")[0].value+"/"+$("#jks_input_txt")[0].value);
                }
            })
        }
    })
}

(function() {
    'use strict';

    if (jks_is_host("courses.edx.org")) {
        jks_courses_edx_org();
    }

    if (jks_is_host("learning.edx.org")) {
        jks_learning_edu_org();
    }
})();