东南大学抢课助手无敌版

听说你抢不到课

// ==UserScript==
// @name        东南大学抢课助手无敌版
// @namespace   http://tampermonkey.net/
// @version     2.0.0
// @description 听说你抢不到课
// @author      WenhaoHe02
// @license     Apache License 2.0
// @match       https://newxk.urp.seu.edu.cn/xsxk/elective/grablessons?*
// @run-at      document-loaded
// @icon        https://huhu-1304907527.cos.ap-nanjing.myqcloud.com/share/qkzs
// ==/UserScript==

(function () {
    // 版本
    let version = [1, 0, 3]

    // 请求
    let request = axios.create();

    // 提示
    let tip = grablessonsVue.$message

    // 设置
    let settings = {
        auto: false
    }

    // 所选课程
    let enrollDict = {};

    // 挂载的顶层组件
    let app = document.getElementById("xsxkapp");

    // 组件生成
    (self => {
        // 生成组件
        self.mount = () => {
            self.createTag()
            self.createPanel()
            self.createMask()
        }

        // 生成节点
        self.createNode = ({ tagName, text, HTML, obj, ev, children }) => {
            let node = document.createElement(tagName)
            if (obj) {
                for (let key of Object.keys(obj)) {
                    node.setAttribute(key, obj[key])
                }
            }
            if (text) {
                node.innerText = text
            }
            if (HTML) {
                node.innerHTML = HTML
            }
            if (ev) {
                for (let key of Object.keys(ev)) {
                    node.addEventListener(key, ev[key])
                }
            }
            if (children) {
                children.map(x => node.appendChild(x))
            }
            return node
        }

        // 生成打开和关闭面板的按钮
        self.createTag = () => {
            let node = self.createNode({
                tagName: "div",
                obj: {
                    "class": "slideMenu",
                    "style": `
                position: fixed;
                top: 250px;
                left:30px;width:
                40px;z-index: 1314;
            `
                },
                children: [self.createNode(
                    {
                        tagName: "div",
                        obj: {
                            "class": "centre-btn item el-icon-date",
                            "style": `background-color: #2b2b2b`
                        },
                        ev: {
                            "mousedown": e => {
                                methods.drag(e, node)
                            }
                        }
                    })]
            })
            app.appendChild(node)
        }

        // 生成面板
        self.createPanel = () => {
            app.appendChild(self.createNode({
                tagName: "div",
                obj: {
                    "id": "panel",
                    "style": `
                position: fixed;
                right: 0;
                top:0 ;
                z-index: 520;
                width: 350px;
                height: 100%;
                background-color: rgba(61,72,105,0.8);
                display: none
            `
                },
                children: [
                    self.createNode({ tagName: "hr" }),
                    self.createNode({
                        tagName: "h1",
                        text: "东大抢课脚本",
                        obj: {
                            "style": "color: #c7e6e6; text-align: center",
                        }
                    }),
                    self.createNode({ tagName: "hr" }),
                    self.createNode({
                        tagName: "input",
                        obj: {
                            "id": "input-box",
                            "class": "el-input__inner",
                            "style": `
                    width: 96%;
                    margin-left: 2%;
                    height: 30px
                `,
                            "placeholder": "输入课程代码(不区分大小写),按回车确定"
                        },
                        ev: {
                            "keydown": methods.enter
                        }
                    }),
                    self.createNode({
                        tagName: "div",
                        obj: {
                            "id": "list-wrap",
                            "style": `
                    overflow: auto;
                    margin: 10px;
                    border:1px solid white;
                    height: 50%
                `
                        }
                    }),
                    self.createNode({
                        tagName: "button",
                        obj: {
                            "id": "enroll-button",
                            "class": "el-button el-button--primary el-button--small is-round",
                            "style": `
                    margin: 20px;
                    position: absolute;
                    right:2%;
                    bottom:25%
                `
                        },
                        text: "一键抢课",
                        ev: {
                            "click": () => {
                                methods.enroll()
                            }
                        }
                    }),
                    self.createNode({
                        tagName: "button",
                        obj: {
                            "id": "enroll-button",
                            "class": "el-button el-button--default el-button--small is-round",
                            "style": `
                    margin: 20px;
                    position: absolute;
                    right:2%;
                    bottom:20%
                `
                        },
                        text: "高级设置",
                        ev: {
                            "click": () => {
                                document.getElementById("mask").style.display = "block"
                                self.createPopUp("高级设置", self.createAdvancedPop())
                            }
                        }
                    }),
                    self.createNode({
                        tagName: "div",
                        obj: {
                            "style": `
                        margin: 20px;
                        position: absolute;
                        right:2%;
                        bottom:10%;
                        color: white;
                        float: right
                    `
                        },
                        text: "ver" + version.join(".")
                    }),
                    self.createNode({
                        tagName: "div",
                        obj: {
                            "id": "update-tip",
                            "style": `
                        margin: 20px;
                        position: absolute;
                        right:2%;
                        bottom:5%;
                        color: red;
                        float: right;
                        cursor: pointer;
                        display:none
                    `
                        },
                        text: "有新版本,点击更新。更新后请重新进入选课页面",
                        ev: {
                            "click": () => {
                                window.open("https://greasyfork.org/scripts/427237")
                            }
                        }
                    })
                ]
            }))
            self.reloadList()
        }

        // 生成遮罩
        self.createMask = () => {
            let node = self.createNode({
                tagName: "div",
                obj: {
                    "id": "mask",
                    "style": `
                position: fixed;
                left: 0;
                top: 0;
                width: 100%;
                height: 100%;
                z-index: 2002;
                background-color: rgba(66, 66, 66, 0.6);
                display: none
            `
                },
                ev: {
                    "click": () => {
                        node.style.display = "none"
                        let temp = document.getElementsByClassName("temp")[0]
                        if (temp) app.removeChild(temp)
                    }
                }
            })
            app.appendChild(node)
        }

        // 生成抢课表格
        self.reloadList = () => {
            let list_wrap = document.querySelector("#panel #list-wrap")
            list_wrap.innerHTML = ""
            if (JSON.stringify(enrollDict) === '{}') {
                list_wrap.innerHTML = "<h3 style='text-align: center;color:lightblue;margin-top: 50%'>还未选择课程</h3>"
            } else {
                list_wrap.appendChild(self.createNode({
                    tagName: "table",
                    obj: {
                        width: "100%",
                        border: "1",
                        style: `
                  background-color: rgba(0,0,0,0);
                  color: lightblue
              `
                    },
                    children: [self.createNode({
                        tagName: "tr",
                        obj: {
                            style: `
                      height: 30px;
                      background-color: #255e95
                  `
                        },
                        HTML: `
                  <th style="text-align:center;width: 55%">课程</th>
                  <th style="text-align:center;width: 15%">教师</th>
                  <th style="text-align:center;width: 30%">操作</th>
                `
                    }),
                    ...Object.keys(enrollDict).filter(key => enrollDict[key].courseBatch === grablessonsVue.lcParam.currentBatch.code).map(key => {
                        return self.createNode({
                            tagName: "tr",
                            obj: {
                                style: `height: 30px`
                            },
                            children: [
                                self.createNode({
                                    tagName: "td",
                                    obj: {
                                        style: `text-align: center`
                                    },
                                    text: enrollDict[key].courseName
                                }),
                                self.createNode({
                                    tagName: "td",
                                    obj: {
                                        style: `text-align: center`
                                    },
                                    text: enrollDict[key].teacherName
                                }),
                                self.createNode({
                                    tagName: "td",
                                    obj: {
                                        style: `text-align: center`
                                    },
                                    children: [
                                        self.createNode({
                                            tagName: "button",
                                            text: "删除",
                                            obj: {
                                                "style": `
                              color: red;
                              background: transparent;
                              border: 1px solid red;
                              border-radius: 6px;
                              text-align: center;
                              cursor: pointer;
                              text-decoration: none;
                              margin-right: 2px
                            `
                                            },
                                            ev: {
                                                "click": () => {
                                                    delete enrollDict[key]
                                                    methods.saveCourse()
                                                    tip({
                                                        type: "success",
                                                        message: "已删除",
                                                        duration: 1000
                                                    })
                                                    self.reloadList()
                                                }
                                            }
                                        }),
                                        self.createNode({
                                            tagName: "button",
                                            text: "更多",
                                            obj: {
                                                "style": `
                              color: orange;
                              background: transparent;
                              border: 1px solid orange;
                              border-radius: 6px;
                              text-align: center;
                              cursor: pointer;
                              text-decoration: none;
                              margin-left: 2px
                            `
                                            },
                                            ev: {
                                                "click": () => {
                                                    document.getElementById("mask").style.display = "block"
                                                    self.createPopUp("更多操作", self.createCourseDetailPop(enrollDict[key]))
                                                }
                                            }
                                        })
                                    ]
                                })
                            ]
                        })
                    })]
                }))
            }
        }

        // 生成弹出窗
        self.createPopUp = (title, node, width, height) => app.appendChild(self.createNode({
            tagName: "div",
            obj: {
                "class": "temp",
                "style": `
                position: fixed;
                left: ${width ? 50 - 0.5 * width : 30}%;
                top: ${height ? 50 - 0.5 * height : 30}%;
                width: ${width || 40}%;
                height: ${height || 40}%;
                z-index: 2021;
                background-color: white;
                border-radius: 30px
            `
            },
            children: [
                self.createNode({
                    tagName: "h1",
                    obj: {
                        "style": `
                    margin: 20px 0;
                    width: 100%;
                    text-align: center;
                `
                    },
                    text: title
                }),
                node,
                self.createNode({
                    tagName: "button",
                    obj: {
                        "class": "el-button el-button--default el-button--large is-round",
                        "style": `
                    margin: 20px;
                    position: absolute;
                    right:10%;
                    bottom:0
                `
                    },
                    text: "确定",
                    ev: {
                        "click": () => {
                            document.getElementById("mask").style.display = "none"
                            let temp = document.getElementsByClassName("temp")[0]
                            if (temp) app.removeChild(temp)
                        }
                    }
                })
            ]
        }))

        // 生成课程详情页
        self.createCourseDetailPop = course => self.createNode({
            tagName: "div",
            obj: {
                "style": `margin:30px`
            },
            children: [
                self.createNode({
                    tagName: "p",
                    text: `课程名称: ${course.courseName}`
                }),
                self.createNode({
                    tagName: "p",
                    text: `教师姓名: ${course.teacherName}`
                }),
                self.createNode({
                    tagName: "p",
                    text: `课程代码: ${course.courseCode}`
                }),
                // 添加更多课程详情信息
            ]
        })

        // 生成高级操作
        self.createAdvancedPop = () => self.createNode({
            tagName: "div",
            obj: {
                "style": `
              margin:50px
          `
            },
            children: [
                self.createNode({
                    tagName: "div",
                    children: [
                        self.createNode({
                            tagName: "input",
                            obj: {
                                "id": "auto",
                                "type": "checkbox",
                                "checked": settings.auto
                            }
                        }),
                        self.createNode({
                            tagName: "label",
                            obj: {
                                "for": "auto"
                            },
                            text: "自动抢课(开发中)"
                        })
                    ]
                }),
                // 其他高级设置选项
            ]
        })

    })(window.Components = window.Components || {})

    let methods = {
        isRunning: false, // 添加 isRunning 标志

        // 初始化数据
        init() {
            methods.checkVersion()
            let raw = JSON.parse(localStorage.getItem("huhu"))
            if (raw) {
                settings = raw.settings
                if (settings.jwt === sessionStorage.token) {
                    enrollDict = raw.enrollDict
                } else if (JSON.stringify(raw.enrollDict) !== "{}") {
                    tip({
                        type: "warning",
                        message: "登录信息发生变动,已清空抢课列表",
                        duration: 1000
                    })
                    enrollDict = {}
                    settings.jwt = sessionStorage.token
                    methods.saveCourse()
                }
            } else {
                settings.jwt = sessionStorage.token
            }
            window.Components.reloadList()
        },
        checkVersion() {
            request.get("https://api.seutools.com/enroll/", {
                transformRequest: [(data, headers) => {
                    delete headers.Authorization
                    delete headers.batchId
                    return data
                }]
            }).then(res => {
                let remoteVersion = res.data.version.split(".").map(x => parseInt(x))
                let currentVersion = version.map(x => parseInt(x))
                for (let i = 0; i < remoteVersion.length; i++) {
                    if (remoteVersion[i] > currentVersion[i] || (remoteVersion[i] === currentVersion[i] && i === remoteVersion.length - 1)) {
                        document.getElementById("update-tip").style.display = "block"
                        break
                    }
                }
            })
        },
        // 保存数据
        saveCourse() {
            localStorage.setItem("huhu", JSON.stringify({ enrollDict, settings }))
        },
        // 处理按钮拖动与点击
        drag(e, node) {
            let is_move = false
            let x = e.pageX - node.offsetLeft;
            let y = e.pageY - node.offsetTop;
            document.onmousemove = function (e) {
                node.style.left = e.pageX - x + 'px';
                node.style.top = e.pageY - y + 'px';
                is_move = true
            }
            document.onmouseup = function () {
                document.onmousemove = document.onmouseup = null;
                if (!is_move) {
                    let panel = document.getElementById("panel")
                    panel.style.display === "block" ? panel.style.display = "none" : panel.style.display = "block"
                }
                is_move = false
            }
        },
        // 处理输入框实践
        enter(e) {
            let evt = window.event || e;
            if (evt.keyCode === 13) {
                let currentType = grablessonsVue.teachingClassType
                let currentCourseList = grablessonsVue.courseList
                let node = document.getElementById("input-box")
                let code = node.value.toUpperCase()
                if (!code) return
                if (enrollDict[code]) {
                    tip({
                        type: "warning",
                        message: "已经添加过了",
                        duration: 1000
                    })
                    return
                }
                let courseCode = code.substring(0, 8)
                let teacherCode = code.substring(8)

                let courseFlag = false, teacherFlag = false
                for (let course of currentCourseList) {
                    if (course.KCH === courseCode) {
                        courseFlag = true
                        if (grablessonsVue.teachingClassType !== 'XGKC') {
                            for (let teacher of course.tcList) {
                                if (teacher.KXH === teacherCode) {
                                    enrollDict[code] = {
                                        courseBatch: grablessonsVue.lcParam.currentBatch.code,
                                        courseCode: teacher.JXBID,
                                        courseType: currentType,
                                        courseName: course.KCM,
                                        teacherName: teacher.SKJS,
                                        secretVal: teacher.secretVal,
                                    }
                                    teacherFlag = true;
                                }
                            }
                        } else {
                            if (course.KXH === teacherCode) {
                                enrollDict[code] = {
                                    courseBatch: grablessonsVue.lcParam.currentBatch.code,
                                    courseCode: course.JXBID,
                                    courseType: currentType,
                                    courseName: course.KCM,
                                    teacherName: course.SKJS,
                                    secretVal: course.secretVal,
                                }
                                teacherFlag = true
                            }
                        }
                    }
                }
                if (!courseFlag) {
                    tip({
                        type: "warning",
                        message: "没有查找到课程,请检查课程代码",
                        duration: 1000
                    })
                } else if (!teacherFlag) {
                    tip({
                        type: "warning",
                        message: "没有查找到该教师,请检查教师号",
                        duration: 1000
                    })
                } else {
                    tip({
                        type: "success",
                        message: "添加成功",
                        duration: 1000
                    })
                    node.value = ""
                    window.Components.reloadList()
                    methods.saveCourse()
                }

            }
        },
        // 一键抢课
        enroll() {
            if (this.isRunning) {
                tip({
                    type: "warning",
                    message: "抢课已在进行中,请稍候",
                    duration: 1000
                });
                return;
            }
            this.isRunning = true;

            let key_list = Object.keys(enrollDict).filter(key => enrollDict[key].courseBatch === grablessonsVue.lcParam.currentBatch.code);
            if (!key_list.length) {
                tip({
                    type: "warning",
                    message: "还没有输入课程",
                    duration: 1000
                });
                this.isRunning = false;
                return;
            }

            const batchSize = 3;
            const interval = 1000;
            let currentIndex = 0;
            const sendBatch = () => {
                if (currentIndex >= key_list.length) {
                    clearInterval(batchInterval)
                    this.isRunning = false
                    tip({
                        type: "success",
                        message: "所有课程抢课请求已发送",
                        duration: 1500
                    });
                    return;
                }

                const batch = key_list.slice(currentIndex, currentIndex + batchSize);
                batch.forEach(key => {
                    request({
                        url: "/elective/clazz/add",
                        method: "POST",
                        headers: {
                            'batchId': enrollDict[key].courseBatch,
                            'content-type': 'application/x-www-form-urlencoded'
                        },
                        data: Qs.stringify({
                            clazzType: enrollDict[key].courseType,
                            clazzId: enrollDict[key].courseCode,
                            secretVal: enrollDict[key].secretVal
                        })
                    })
                        .then(res => {
                            let type = res.data.code === 100 ? "success" : "warning";
                            tip({
                                type,
                                message: `${enrollDict[key].courseName}: ${res.data.msg}`,
                                duration: 1000
                            });
                            // 如果抢课成功,删除该课程
                            if (res.data.code === 100) {
                                delete enrollDict[key];
                                methods.saveCourse();
                                window.Components.reloadList();
                                tip({
                                    type: "success",
                                    message: `${enrollDict[key].courseName} 已成功抢课并从列表中移除`,
                                    duration: 1000
                                });
                            }
                        })
                        .catch(error => {
                            tip({
                                type: "error",
                                message: `${enrollDict[key].courseName}: 请求失败`,
                                duration: 2000
                            });
                        });
                });

                currentIndex += batchSize;
            }

            sendBatch();

            const batchInterval = setInterval(() => {
                sendBatch();
            }, interval);
            setTimeout(() => {
                if (currentIndex >= key_list.length && this.isRunning) {
                    clearInterval(batchInterval)
                    this.isRunning = false
                    tip({
                        type: "success",
                        message: "所有课程抢课请求已发送",
                        duration: 1500
                    });
                }
            }, interval * Math.ceil(key_list.length / batchSize) + 1000);
        }
    }

    window.Components.mount();
    methods.init()
})();