SJTU-Course Selection Assistant

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

Você precisará instalar uma extensão como Tampermonkey, Greasemonkey ou Violentmonkey para instalar este script.

Você precisará instalar uma extensão como Tampermonkey para instalar este script.

Você precisará instalar uma extensão como Tampermonkey ou Violentmonkey para instalar este script.

Você precisará instalar uma extensão como Tampermonkey ou Userscripts para instalar este script.

Você precisará instalar uma extensão como o Tampermonkey para instalar este script.

Você precisará instalar um gerenciador de scripts de usuário para instalar este script.

(Eu já tenho um gerenciador de scripts de usuário, me deixe instalá-lo!)

Você precisará instalar uma extensão como o Stylus para instalar este estilo.

Você precisará instalar uma extensão como o Stylus para instalar este estilo.

Você precisará instalar uma extensão como o Stylus para instalar este estilo.

Você precisará instalar um gerenciador de estilos de usuário para instalar este estilo.

Você precisará instalar um gerenciador de estilos de usuário para instalar este estilo.

Você precisará instalar um gerenciador de estilos de usuário para instalar este estilo.

(Eu já possuo um gerenciador de estilos de usuário, me deixar fazer a instalação!)

// ==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)

})();