SJTU-Course Selection Assistant

添加一个打开选课社区(course.sjtu.plus)的按钮

Tendrás que instalar una extensión para tu navegador como Tampermonkey, Greasemonkey o Violentmonkey si quieres utilizar este script.

You will need to install an extension such as Tampermonkey to install this script.

Tendrás que instalar una extensión como Tampermonkey o Violentmonkey para instalar este script.

Necesitarás instalar una extensión como Tampermonkey o Userscripts para instalar este script.

Tendrás que instalar una extensión como Tampermonkey antes de poder instalar este script.

Necesitarás instalar una extensión para administrar scripts de usuario si quieres instalar este script.

(Ya tengo un administrador de scripts de usuario, déjame instalarlo)

Tendrás que instalar una extensión como Stylus antes de poder instalar este script.

Tendrás que instalar una extensión como Stylus antes de poder instalar este script.

Tendrás que instalar una extensión como Stylus antes de poder instalar este script.

Para poder instalar esto tendrás que instalar primero una extensión de estilos de usuario.

Para poder instalar esto tendrás que instalar primero una extensión de estilos de usuario.

Para poder instalar esto tendrás que instalar primero una extensión de estilos de usuario.

(Ya tengo un administrador de estilos de usuario, déjame instalarlo)

// ==UserScript==
// @name         SJTU-Course Selection Assistant
// @namespace    http://tampermonkey.net/
// @version      0.4.0
// @description  添加一个打开选课社区(course.sjtu.plus)的按钮
// @author       Me
// @match        https://i.sjtu.edu.cn/xsxk/zzxkyzb_cxZzxkYzbIndex.html*
// @connect f002.backblazeb2.com
// @grant GM_setValue
// @grant GM_getValue
// @grant GM_xmlhttpRequest
// @homepageURL https://github.com/dzx-dzx/Course-Selection-Assistant
// @license Apache
// ==/UserScript==

(async function () {
    'use strict';
    // Your code here...
    
    //From https://stackoverflow.com/questions/21271997/how-to-overwrite-a-function-using-a-userscript
    function showHideJxb(obj){
        if($(obj).children(".expand_close").attr("class").indexOf("expand1")>0){
            $(obj).children(".expand_close").removeClass('expand1').addClass('close1');
            $(obj).next(".panel-body").slideDown();
        }else{
            $(obj).children(".expand_close").removeClass('close1').addClass('expand1');
            $(obj).next(".panel-body").slideUp();
        }
    }
    addJS_Node (showHideJxb);
    function addJS_Node (text, s_URL, funcToRun, runOnLoad) {
        var D                                   = document;
        var scriptNode                          = D.createElement ('script');
        if (runOnLoad) {
            scriptNode.addEventListener ("load", runOnLoad, false);
        }
        scriptNode.type                         = "text/javascript";
        if (text)       scriptNode.textContent  = text;
        if (s_URL)      scriptNode.src          = s_URL;
        if (funcToRun)  scriptNode.textContent  = '(' + funcToRun.toString() + ')()';

        var targ = D.getElementsByTagName ('head')[0] || D.body || D.documentElement;
        targ.appendChild (scriptNode);
    }
    
    const courseToIdRaw = (await (async () => {
        const courseListTimestampResponse = await new Promise((resolve, reject) => {
            GM_xmlhttpRequest({
                method: "GET",
                url: "https://f002.backblazeb2.com/file/course/course.json",
                headers: { "Range": "bytes=0-0" },
                onload: function (response) { resolve(response) }
            })
        })
        const latestCourseListTimestamp = parseInt(courseListTimestampResponse.responseHeaders.split('\n').filter((i,/*教务系统似乎私自修改了Array的原型*/s) => s.includes("x-bz-info-src_last_modified_millis"))[0].split(":")[1])
        if (GM_getValue('course_list_timestamp') !== latestCourseListTimestamp) {
            const courseListResponse = await new Promise((resolve, reject) => {
                GM_xmlhttpRequest({
                    method: "GET",
                    url: "https://f002.backblazeb2.com/file/course/course.json",
                    onload: function (response) { resolve(response) }
                })
            })
            const courseToIdRaw = JSON.parse(courseListResponse.response)
            GM_setValue("course_list", courseToIdRaw)
            GM_setValue("course_list_timestamp", latestCourseListTimestamp)
            return courseToIdRaw
        }
        else return GM_getValue("course_list")
    })())

    const courseToIdMap = new Map(Object.entries(courseToIdRaw))

    function addButton(tr) {
        const classCodeRaw = tr.querySelector("td.jxbmc").textContent
        const classCode = classCodeRaw.split("-").at(-2)//什么你说兼容性?能吃吗?

        if (!courseToIdMap.has(classCode)) {
            console.warn(`课程${classCode}无法找到,请向开发者联系.`); return;
        }
        const classes = courseToIdMap.get(classCode)

        const teachers = Array.from(tr.querySelector("td.jsxmzc").querySelectorAll("a"), a => a.textContent)

        const id = teachers.reduce((pre, teacher) => {
            if (pre != null) return pre
            const res = classes.find((c) => c.teacher === teacher)
            if (res) return res.id
            else return null
        }, null)

        if (!id) {
            console.warn(`课程${classCode}下教师${teachers}均未找到,请向开发者联系.`); return;
        }

        const button = document.createElement("button")
        button.onclick = function () { window.open(`https://course.sjtu.plus/course/${id}`, "_blank"/*始终新建窗口,改成"course"以覆盖前一窗口*/) }
        button.setAttribute("class", "btn btn-primary btn-sm")
        button.textContent = "跳转到选课社区"
        tr.querySelector("td.jxbmc").append(document.createElement("br"), button)
    }
    document.querySelectorAll("div.panel-body > table > tbody > tr ").forEach((tr => {
        if (tr.querySelector(".kkxymc").textContent !== "") addButton(tr)
    }))

    const observer = new MutationObserver((mutationList) => {
        mutationList.forEach((mutation) => { if (mutation.target.getAttribute("class") === "kkxymc") addButton(mutation.target.parentElement) })
    })
    observer.observe(document.querySelector("#displayBox"), { "childList": true, "subtree": true })
    
    const saveSelectedCourseButton = document.createElement("button")
    saveSelectedCourseButton.onclick = function(){
        function getSelectedCourseCode(){
            return [...document.querySelectorAll("div.right_div > div > ul td> p.jxb").values()].map(p=>p.getAttribute("title").split("-").at(-2))
        }
        let selectedCourseCode=getSelectedCourseCode().join(" ")
        selectedCourseCode=window.prompt("将保存以下课程代号:", selectedCourseCode)
        if(selectedCourseCode) GM_setValue("selected_course_code",selectedCourseCode)
    }
    saveSelectedCourseButton.setAttribute("class", "btn btn-primary btn-sm")
    saveSelectedCourseButton.textContent = "保存已选课到本地"

    const loadPreSelectedCourseButton = document.createElement("button")
    loadPreSelectedCourseButton.onclick = function(){
        let selectedCourseCode=GM_getValue("selected_course_code")
        selectedCourseCode=window.prompt("将加载以下课程代号:", selectedCourseCode)
        if(selectedCourseCode){
            document.querySelector(".form-control.input-sm.filter-input").value=selectedCourseCode
            document.querySelector(".input-group-btn").querySelector(".btn.btn-primary.btn-sm").click()
        }
    }
    loadPreSelectedCourseButton.setAttribute("class", "btn btn-primary btn-sm")
    loadPreSelectedCourseButton.textContent = "加载之前保存的课程"

    document.querySelector("div.col-sm-8.col-md-8.buttons").prepend(saveSelectedCourseButton,loadPreSelectedCourseButton)

})();