// ==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()
})();