xjtu-xiaomi-courseSync

sync course info from xjtu-ehall to xiaomi-xiaoai-app!

// ==UserScript==
// @name         xjtu-xiaomi-courseSync
// @namespace    xjtuCourseSync
// @version      1.1.2
// @description  sync course info from xjtu-ehall to xiaomi-xiaoai-app!
// @author       a-student-from-xian_wise_university
// @match        http://ehall.xjtu.edu.cn/jwapp/sys/wdkb/*
// @icon         none
// @connect      i.ai.mi.com
// @grant        GM_xmlhttpRequest
// @run-at       document-end
// @license      MIT
// ==/UserScript==


(function () {
    'use strict';
    let Waitingload = setInterval(() => {
        if (document.getElementById("vue-report") && document.getElementById("set_xiaomi_URL") == null) { CodeBegin(); }
    }, 300);

    let CodeBegin = () => {
        clearInterval(Waitingload);
        //p
        let infoP = document.createElement("p");
        infoP.id = "xiaomi_infoP";
        infoP.innerText = "请在下方的第一个输入框中输入可编辑课程表的地址(请确保该课程表为空,链接在小爱课程表-我的设置-使用PC编辑课表中获取)," +
            "\n第二个输入框中按照默认值的格式填入学期(第一位年份是进入该学年的年份,如19级同学的大二第二学期是2020-2021-2,小学期在最后一位对应3)";
        //input
        let infoInput = document.createElement("textarea");
        infoInput.id = "xiaomi_URL_input";
        infoInput.value = "";
        infoInput.setAttribute("cols", "55");
        infoInput.setAttribute("rows", "2");

        let smstInput = document.createElement("textarea");
        smstInput.id = "Semester_input";
        smstInput.value = "2022-2023-1";
        smstInput.setAttribute("cols", "15");
        smstInput.setAttribute("rows", "1");
        //button
        let infoSettingstn = document.createElement("button");
        infoSettingstn.innerHTML = "确定";
        infoSettingstn.style = "background-color:gray;color:white;text-align:center;width:74px;";
        infoSettingstn.setAttribute("id", "set_xiaomi_URL");
        infoSettingstn.addEventListener("click", Main);

        //append
        let viewReportDiv = document.getElementById("vue-report");
        viewReportDiv.appendChild(document.createElement("br"));
        viewReportDiv.appendChild(document.createElement("br"));
        viewReportDiv.appendChild(infoP);
        viewReportDiv.appendChild(document.createElement("br"));
        viewReportDiv.appendChild(infoInput);
        viewReportDiv.appendChild(document.createElement("br"));
        viewReportDiv.appendChild(smstInput);
        viewReportDiv.appendChild(document.createElement("br"));
        viewReportDiv.appendChild(infoSettingstn);
        viewReportDiv.appendChild(document.createElement("br"));

    };

    async function Main() {
        let xiaomiRUL = document.getElementById("xiaomi_URL_input").value;
        xiaomiRUL = xiaomiRUL.substring(xiaomiRUL.search("token=") + 6);
        console.log(xiaomiRUL);
        xiaomiRUL = window.atob(xiaomiRUL);
        console.log(xiaomiRUL);
        let re = /(.+?)%26(.+?)%26(.+?)%26(.+)/g;
        xiaomiRUL = xiaomiRUL.matchAll(re).next().value;
        const userID = xiaomiRUL[1];
        const deviceId = xiaomiRUL[2];
        //const expire_time = xiaomiRUL[3];
        const ctId = xiaomiRUL[4];
        let Semester = document.getElementById("Semester_input").value;

        let f = await fetch("http://ehall.xjtu.edu.cn/jwapp/sys/wdkb/modules/xskcb/xskcb.do", {
            "credentials": "include",
            "headers": {
                "User-Agent": navigator.userAgent,
                "Accept": "application/json, text/javascript, */*; q=0.01", "Accept-Language": "en-US,en;q=0.5",
                "Content-Type": "application/x-www-form-urlencoded; charset=UTF-8", "X-Requested-With": "XMLHttpRequest"
            },
            "referrer": "http://ehall.xjtu.edu.cn/jwapp/sys/wdkb/*default/index.do?amp_sec_version_=1&EMAP_LANG=zh&THEME=millennium",
            "body": "XNXQDM=" + Semester, "method": "POST", "mode": "cors"
        });
        if (f && f.status == 200) {
            f = await f.json();
            f = f.datas.xskcb.rows;
            let okNum = 0, err601Num = 0, errOther = 0; let infoP_Obj = document.getElementById("xiaomi_infoP");
            let errC = document.createElement("p"); document.getElementById("vue-report").appendChild(errC);
            errC = document.getElementById("vue-report").lastChild; errC.style = "color:red";
            function myResponse(xhr) {
                let rsp = JSON.parse(xhr.responseText); console.log(rsp);
                if (rsp.code == 0) { okNum++; } else if (rsp.code == 601) { err601Num++; } else { errOther++; }
                infoP_Obj.innerText = "从ehall共获取" + f.length + "个课程单元。\n成功添加" + okNum +
                    "个,因课表非空(error code: 601)添加失败" + err601Num + "个,因其他未知错误添加失败" + errOther + "个。\n失败的课程名如下:";
                if (rsp.code != 0) { errC.innerText += ("[error code: " + rsp.code + "]" + this.para + "\n"); }
            }
            for (let i = 0; i < f.length; i++) {
                const element = f[i];
                console.log(element);
                let s0 = "{\"userId\":" + userID + ",\"deviceId\":\"" + deviceId + "\",\"ctId\":" + ctId + ",\"course\":{\"name\":\"" +
                    element.KCM + "\",\"position\":\"" + element.JASMC + "\",\"teacher\":\"" + element.SKJS + "\",\"weeks\":\"" +
                    getWeeks(element.SKZC) + "\",\"day\":" + element.SKXQ + getColorMode(element.KHC + element.KCM) +
                    getSections(element.KSJC, element.JSJC) + "\"},\"sourceName\":\"course-app-browser\"}";

                GM_xmlhttpRequest({
                    url: "https://i.ai.mi.com/course-multi/courseInfo",
                    credentials: "omit",
                    referrer: "https://i.ai.mi.com/h5/precache/ai-schedule/",
                    method: "POST",
                    data: s0,
                    headers: {
                        "User-Agent": navigator.userAgent, "Accept": "application/json", "Accept-Language": "en-US,en;q=0.5",
                        "Content-Type": "application/json", "Access-Control-Allow-Origin": "true", "Sec-Fetch-Dest": "empty",
                        "Sec-Fetch-Mode": "cors", "Sec-Fetch-Site": "same-origin"
                    },
                    onload: myResponse.bind({
                        para: "课程名:" + element.KCM + ",星期:" + element.SKXQ + ",节次:" +
                            getSections(element.KSJC, element.JSJC) + ",周数:" + getWeeks(element.SKZC)
                    })
                });
            }
        }
        else { window.alert("在ehall获取课表信息失败!"); }
    }

    let getWeeks = s => {
        //"111111111111111100"
        let res = "";
        for (let i = 0; i < s.length; i++) {
            if (s[i] - '0') {
                res += (i + 1).toString() + ',';
            }
        }
        res = res.substring(0, res.length - 1);
        return res;
        //"1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16"
    }

    let getSections = (s1, s2) => {
        //"5"
        //"6"
        let res = "";
        for (let i = parseInt(s1); i <= parseInt(s2); i++) {
            res += i.toString() + ',';
        }
        res = res.substring(0, res.length - 1);
        return res;
        //"5,6"
    }

    let colorM = ["00A6F2", "E5F4FF", "FC6B50", "FDEBDE", "3CB3C8", "DEFBF8",
        "7D7AEA", "EDEDFF", "FF9900", "FCEBCD", "EF5B75", "FFEFF0",
        "5B8EFF", "EAF1FF", "F067BB", "FFEDF8", "F067BB", "FFEDF8",
        "CBA713", "FFF8C8", "B967E3", "F9EDFF", "6E8ADA", "F3F2FD"];
    const ModeNum = 12;

    let getColorMode = courseId => {
        if (myHash(courseId) % (ModeNum + 1) == ModeNum) {
            courseId = myHash(courseId + "为了均匀,先mod质数13," +
                "如果不幸得12,再从这里mod质数11") % (ModeNum - 1);
        }
        else { courseId = myHash(courseId) % (ModeNum + 1); }
        let res = ",\"style\":\"{\\\"color\\\":\\\"#" +
            colorM[courseId * 2] +
            "\\\",\\\"background\\\":\\\"#" +
            colorM[courseId * 2 + 1] +
            "\\\"}\",\"sections\":\"";
        return res;
        //",\"style\":\"{\\\"color\\\":\\\"#00A6F2\\\",\\\"background\\\":\\\"#E5F4FF\\\"}\",\"sections\":\""
    }

    let myHash = s => {
        let hash = 0, chr = 'a';
        if (s.length === 0) return hash;
        for (let i = 0; i < s.length; i++) {
            chr = s.charCodeAt(i);
            hash = ((hash << 5) - hash) + chr;
            hash |= 0; // Convert to 32bit integer
        }
        return Math.abs(hash) + 1;
    }

    // Content below learnt from here
    // https://userscripts-mirror.org/topics/193
    // A great resource on the theory and practice of lexical closures and partial application is Brockman's
    // [Object-Oriented Event Listening through Partial Application in JavaScript](http://www.brockman.se/writing/method-references.html.utf8).
    // The title sets out to solve the same kind of issue under another set of circumstances (event listeners),
    // but the solution and all understanding of the problem applies to those circumstances and yours alike.
    // You'll have this problem in many similar situations until you understand how closures work in javascript, and then be rid of the issue.
    Function.prototype.bind = function (thisObject) {
        var method = this;
        var oldargs = [].slice.call(arguments, 1);
        return function () {
            var newargs = [].slice.call(arguments);
            return method.apply(thisObject, oldargs.concat(newargs));
        };
    }

    // for (let i = 0; i < 3; i++) {
    //     GM_xmlhttpRequest({
    //         method: 'GET',
    //         url: 'http://ehall.xjtu.edu.cn/jwapp/sys/wdkb/*default/index.do?amp_sec_version_=1&EMAP_LANG=zh&THEME=millennium',
    //         onload: myCallback.bind({ myVar:"test", v2: 0 })
    //     });
    // }


    // // variables from the {...} literal above are now found in the `this' object:
    // function myCallback(xhr) {
    //     alert(this.myVar);
    //     console.log(this.v2);
    //     console.log(xhr.responseText);
    //     //...
    // }
    //------------------------------------------------------------------------//
    //This also allows you to pass additional function arguments to myCallback,
    //bound in before the request argument, if you'd like to, perhaps like this:
    //     var url = 'http://www.somewhere.com/something.xml';
    //     GM_xmlhttpRequest({
    //         method: 'GET',
    //         url: url,
    //         onload: myCallback.bind({}, url, some_variable)
    //     });

    //     // 2nd onward argument to bind get prepended to the xhr callback's args:
    //     function myCallback(url, myVar, request) {
    //         alert(myVar);
    //   ...
    //     }
})();
长期地址
遇到问题?请前往 GitHub 提 Issues。