// ==UserScript==
// @name 洛理抢课助手2.0
// @version 1.0.0
// @description 方便抢课的微辅助功能
// @author 阿加Erin【https://www.gaoajia.com】
// @namespace https://greasyfork.org/zh-CN/users/670112-ajiaerin
// @match *://172.16.99.4/*
// @match *://jw.sec.lit.edu.cn/*
// @match *://120.194.42.205:9001/*
// @match *://172.16.99.8/*
// @require https://cdn.staticfile.org/jquery/2.1.4/jquery.min.js
// @license GPL License
// @grant GM_setValue
// @grant GM_getValue
// @grant GM_deleteValue
// @grant GM_addStyle
// @grant GM_registerMenuCommand
// @grant GM_unregisterMenuCommand
// ==/UserScript==
// ==/UserScript==
const SCR_HEADER = "🐛洛理抢课微辅助"; // 用于debug
const configs = []; //* [各个功能的名字, 开关情况] 放在字典里用于生成下拉菜单时遍历
//* 各个功能的类,key为GM存储中的key, caption为在下拉菜单中显示的文字
class Config {
constructor(key, enabled, caption) {
this.key = key;
this.enabled = enabled;
this.caption = caption;
}
};
//* 各种功能开关 -------------------------------------------
//* I. 删除提交时的确认提示 ----------------------------------
var Delete_Submit_Prompt = new Config("Del_Sub_Prmpt", GM_getValue("Del_Sub_Prmpt") == null ? true : GM_getValue("Del_Sub_Prmpt"), "删除提交时的确认提示"); // 默认值为true
configs.push(Delete_Submit_Prompt);
//* II. 添加"重复上次提交"按钮 ----------------------------------
var Append_Resubmit_Button = new Config("App_Resub_Btn", GM_getValue("App_Resub_Btn") == null ? true : GM_getValue("App_Resub_Btn"), "一键重复上次提交"); // 默认值为true
configs.push(Append_Resubmit_Button);
const Last_Submit_Table_Storage_Key = //* 储存oTable的innerHTML
{
'xk' : "oTableInfo_xk", //* 存储选课程otable的信息(上次提交内容)对应的key
'yy' : "oTableInfo_yy", //* 存储选英语otable的信息(上次提交内容)对应的key
'fx' : "oTableInfo_fx", //* 存储选非限otable的信息(上次提交内容)对应的key
'ts' : "oTableInfo_ts" //* 存储选通识otable的信息(上次提交内容)对应的key
};
const Last_Submit_DOM_Storage_Key = //* 储存DOM组件checkbox和显示文字的input
{
'xk' : 'DOMInfo_xk',
'yy' : 'DOMInfo_yy',
'fx' : 'DOMInfo_fx',
'ts' : 'DOMInfo_ts'
};
// const New_DOM_Storage_Key = //* 储存当前DOM组件checkbox和显示文字的input(用于存储取消提示时的返回状态)
// {
// 'xk': 'new_DOMInfo_xk',
// 'yy': 'new_DOMInfo_yy',
// 'fx': 'new_DOMInfo_fx',
// 'ts': 'new_DOMInfo_ts'
// };
//* III. 弹出窗口中添加"快速选择"按钮 ---------------------------
var Append_Fast_Choose_Button = new Config("App_Fast_Chs_Btn", GM_getValue("App_Fast_Chs_Btn") == null ? true : GM_getValue("App_Fast_Chs_Btn"), "一键选择老师"); // 默认值为true
configs.push(Append_Fast_Choose_Button);
//* IV. 自动点击检索按钮 -------------------------------------
var Auto_Click_Search = new Config("Auto_Search", GM_getValue("Auto_Search") == null ? true : GM_getValue("Auto_Search"), "自动点击检索按钮"); // 默认值为true
configs.push(Auto_Click_Search);
//* -----------------------------
//* 开启Debug功能后会在console输出信息
var DEBUG_MODE = new Config("Debug_Mode", GM_getValue("Debug_Mode") == null ? true : GM_getValue("Debug_Mode"), "控制台输出debug信息"); // 默认值为true
configs.push(DEBUG_MODE);
//* ------------------------------------------------------
//* 根据当前功能开关情况生成插件下拉菜单,并添加对应修改函数 ---------------------
var menuIds = []; //所有下拉菜单的id
drawMenu();
// 每次按下按钮后就重绘一遍menu
function drawMenu () {
// 重绘新菜单,只有在最父层frame且没有id的时候才重绘,避免内层frame加载时重复多次调用
if (window == top) {
// debugger;
menuIds = [];
for (const config of configs) {
var pre = config.enabled ? "【✔️已启用】" : "【❌已禁用】";
var id = GM_registerMenuCommand(pre + config.caption, changeEnabled(config));
menuIds.push(id);
}
}
}
function changeEnabled(config) {
return () => {
// debugger;
config.enabled = !config.enabled;
GM_setValue(config.key, config.enabled);
// 修改功能开关后重绘下拉菜单
for (const id of menuIds) {
GM_unregisterMenuCommand(id);
}
drawMenu();
};
}
//* 需要前面的prefix才能用,如 input.button
const BTN_CLASS = "ZZJBtn";
const BTN_CSS =
`input.${BTN_CLASS} {
font-family: "宋体";
font-size: 12px;
cursor: pointer;
height: 20px;
margin-left: 7px;
}
`;
function log(msg){
if (DEBUG_MODE.enabled){
let d = new Date();
console.log("[" + SCR_HEADER + "] " + msg + " " + d.getHours() + ":" + d.getMinutes() + ":" + d.getSeconds());
}
};
function error(msg){
if (DEBUG_MODE.enabled){
let d = new Date();
console.error("[" + SCR_HEADER + "] " + msg + " " + d.getHours() + ":" + d.getMinutes() + ":" + d.getSeconds());
}
};
//* 清空脚本存储的记录,
function clearResubmitStorage() {
GM_deleteValue(Last_Submit_Table_Storage_Key['xk']);
GM_deleteValue(Last_Submit_Table_Storage_Key['yy']);
GM_deleteValue(Last_Submit_Table_Storage_Key['fx']);
GM_deleteValue(Last_Submit_Table_Storage_Key['ts']);
GM_deleteValue(Last_Submit_DOM_Storage_Key['xk']);
GM_deleteValue(Last_Submit_DOM_Storage_Key['yy']);
GM_deleteValue(Last_Submit_DOM_Storage_Key['fx']);
GM_deleteValue(Last_Submit_DOM_Storage_Key['ts']);
}
(function() {
'use strict'; // 严格模式下使用未定义的变量会报错
// if (name = 'frmMain') {
// console.log(SCR_HEADER + this.location.pathname);
// }
// if (name == 'frmRpt'){
// console.log(SCR_HEADER + this.location.pathname);
// }
//* I. 删除提交时的提示 -------------------------------------------------
if (Delete_Submit_Prompt.enabled && name == 'frmRpt'){
//* 删掉frmRpt(/wsxk/stu_btx_rpt.aspx)里的ChkValue(theObj)里的
//* str: if (!confirm('是否提交记录?'))return false;
//* regEx: /if \(!confirm\('是否提交记录?'\)\)return false;/
//* 即可
//* 专业课 frmRpt(/wsxk/stu_btx_rpt.aspx) 对应scripts[1]
//* 通识 frmRpt(/wsxk/stu_xszx_rpt.aspx)) 对应scripts[3]
//* 英语 frmRpt(/wsxk/stu_btx_rpt.aspx))
//* 非限 frmRpt(/wsxk/stu_btx_rpt.aspx))
var tpe = null;
if (self.location.pathname == "/wsxk/stu_btx_rpt.aspx"){
tpe = 1;
}else if(self.location.pathname == "/wsxk/stu_xszx_rpt.aspx"){
tpe = 3;
}
if (tpe) {
// 先找到对应的script元素,用删掉文字后的标签替换原标签
var scr = self.document.createElement("script");
scr.innerHTML = document.scripts[tpe].innerHTML.replace("if (!confirm('是否提交记录?'))return false;", "");
document.scripts[tpe].replaceWith(scr);
if (document.scripts[tpe].text.search("是否提交记录")==-1){
log("已删除提交确认框!");
}else{
error("未删除提交确认框!");
}
}
}
//* -----------------------------------------------------------------
//* II. "重复上次提交"按钮 ---------------------------------------------
if (Append_Resubmit_Button.enabled && name == 'frmMain'){
// debugger;
var leixing = null; // 是哪个类型的页面:xk, ts, yy, fx
switch (window.location.pathname) {
case "/wsxk/stu_btx.aspx": // 专业课
leixing = "xk";
break;
case "/wsxk/stu_whszk.aspx": // 英语
leixing = "ts";
break;
case "/wsxk/stu_yytgk_bx.aspx": // 通识
leixing = "yy";
break;
case "/wsxk/stu_ggrxk.aspx": // 非限
leixing = "fx";
break;
default:
break;
}
if (leixing) {
//* 专业课 (frmMain(/wsxk/stu_btx.aspx) frmRpt(/wsxk/stu_btx_rpt.aspx))
//* 通识(frmMain(/wsxk/stu_whszk.aspx) frmRpt(/wsxk/stu_xszx_rpt.aspx))
//* 英语(frmMain(/wsxk/stu_yytgk_bx.aspx) frmRpt(/wsxk/stu_btx_rpt.aspx))
//* 非限(frmMain(/wsxk/stu_ggrxk.aspx) frmRpt(/wsxk/stu_btx_rpt.aspx))
// console.log(this.location.pathname);
//* 为了保证多开网页能通用,存在GM storage里。
//* 不同域名的网站也要通用?--> 利用GM_setValue和GM_getValue, 而不存储在localStorage里
//* 在提交旁边加上重复上次提交按钮,提交按钮按下时先在 GM storage 存下 frmMain(/wsxk/stu_btx.aspx) -> frmRpt(/wsxk/stu_btx_rpt.aspx) -> id=oTable 的innerHTML,点击重复提交时先从storage里调取覆盖,再执行提交按钮对应的onclick,
var subBtn = null; // 提交按钮,含提交的窗口加载出来时就会赋值
var oTable = null; // 所有选课内容的表格
//* 找到提交按钮
for (const btn of document.querySelectorAll(".button")) {
if (btn.value == "提交"){
subBtn = btn;
log("获取提交按钮");
break;
}
}
if (subBtn != null) {
//* 找到提交按钮,新按钮才能添加
//* II.1. 设计样式并添加 "重新提交" 按钮 ------------------------------------------
//* 添加新按钮的css
GM_addStyle(BTN_CSS);
//* 新按钮的元素
var resubBtn = document.createElement("input");
resubBtn.setAttribute("class", BTN_CLASS);
// resubBtn.setAttribute("class", "button");
resubBtn.setAttribute("type", "button");
resubBtn.setAttribute("name", "submit");
resubBtn.setAttribute("value", "重复上次提交");
resubBtn.setAttribute("leixing", leixing); // 确定当前页面类型,用于存储oTable在不同的key里面
//* 修改整体宽度
subBtn.parentElement.setAttribute("width", 700);
//* 添加到 提交 后面
insertAfter(resubBtn, subBtn);
log("已添加重复上次提交" + leixing + "按钮。");
//* II.2. 设置重新提交按钮按下时的逻辑 ---------------------------------
//* 先覆盖当前oTable的信息(修改innerHTML),再调用提交按钮的onclick函数
resubBtn.onclick = function(subBtn){
return function(){
var val = GM_getValue(Last_Submit_Table_Storage_Key[leixing]);
if (val != null) {
//* 读取 oTable:
var oTable = frmRpt.document.getElementById("oTable"); // 选课表格对应的元素,含表格的窗口(frmRpt)加载出来时就会赋值
oTable.innerHTML = val;
//* 读取checkbox的checked和input的value信息
var tmp = JSON.parse(GM_getValue(Last_Submit_DOM_Storage_Key[leixing]));
var chkKCs = oTable.querySelectorAll("input[type='checkbox']");
for (const chkKCi of chkKCs) {
chkKCi.checked = tmp[chkKCi.id];
}
var chkSKBJstrs = oTable.querySelectorAll("input[type='text']");
for (const chkSKBJstr of chkSKBJstrs) {
chkSKBJstr.value = tmp[chkSKBJstr.id];
}
log("成功读取" + leixing + "_信息。");
subBtn.onclick();
}else{
error(leixing + "_信息不存在!");
}
};
}(subBtn);
}else {
error("未能添加重复上次提交按钮。");
}
}
}
//* II.3. 改写提交按钮的逻辑,把提交内容存入Storage,并post到其他hosts的相同pathname下 -----------------
//* 提交按钮按下时先在 Storage 存下 frmMain(/wsxk/stu_btx.aspx) -> frmRpt(/wsxk/stu_btx_rpt.aspx) -> id=oTable 的innerHTML。点击重复提交时先从storage里调取覆盖,再执行提交按钮对应的onclick。
if (Append_Resubmit_Button.enabled && name == 'frmRpt'){
// 通识 frmRpt(/wsxk/stu_xszx_rpt.aspx)
// 英语 frmRpt(/wsxk/stu_btx_rpt.aspx)
// 非限 frmRpt(/wsxk/stu_btx_rpt.aspx)
var tpe = null; // 选择修改的内容是scripts几
if (self.location.pathname == "/wsxk/stu_btx_rpt.aspx"){
tpe = 1;
} else if (self.location.pathname == "/wsxk/stu_xszx_rpt.aspx") {
tpe = 3;
}
if (tpe){
//* 只在加载出表格网页时才进行
for (const btn of parent.document.querySelectorAll(".button")) {
if (btn.value == "提交"){
subBtn = btn;
log("获取提交按钮");
break;
}
}
if (subBtn != null) {
//* 改变提交按钮的逻辑,先存oTable再onclick
//* frmRpt(/wsxk/stu_btx_rpt.aspx 对应scripts[1]) (/wsxk/stu_xszx_rpt.aspx 对应scripts[3]) 里的 checkbox 'chkKC' 的checked属性以及后面input 'chkSKBJstr' 的value属性 与 HTML标签里的属性不同步,
//* 可以单独记录所有chkKC的checked和chkSKBJstr的value,读取的时候也把这些信息读进来。√
//* 或修改网页的函数openWinDialog, 使得改变chkKC的属性值以及chkSKBJstr的value使修改对应的HTML标签。×
var oTable = null;
if (name == 'frmRpt') {
oTable = document.getElementById("oTable");
}else if (name == 'frmMain') {
oTable = frmRpt.document.getElementById("oTable");
}
const leixing = parent.document.querySelector("."+BTN_CLASS).getAttribute("leixing");
// debugger;
subBtn.onclick = function(oTable, leixing){
return function(){
GM_setValue(Last_Submit_Table_Storage_Key[leixing], oTable.innerHTML);
//* 存所有checkbox (chkKC#)的checked属性和后面的input (chkSKBJstr#)的value属性到JSON,key是对应的id
const chkKCs = oTable.querySelectorAll("input[type='checkbox']");
var tmp = {}; // 要存储的JSON:chkKC1: true, ...
for (const chkKCi of chkKCs) {
tmp[chkKCi.id] = chkKCi.checked;
}
const chkSKBJstrs = oTable.querySelectorAll("input[type='text']");
for (const chkSKBJstr of chkSKBJstrs) {
tmp[chkSKBJstr.id] = chkSKBJstr.value;
}
GM_setValue(Last_Submit_DOM_Storage_Key[leixing],
JSON.stringify(tmp));
log("成功保存" + leixing + "_信息");
self.document.all.Submit.onclick();
};
} (oTable, leixing);
}
}
}
//* --------------------------------------------------------------
//* III. 弹出窗口增加快速选择选项(选好后自动确定) --------------------------
if (Append_Fast_Choose_Button.enabled && (location.pathname == "/wsxk/stu_xszx_skbj.aspx" || location.pathname == "/wsxk/stu_xszx_chooseskbj.aspx")){
//* 专业课: /wsxk/stu_xszx_skbj.aspx?xxxx=xxxx
//* 英语: /wsxk/stu_xszx_chooseskbj.aspx?xxx
//* 非限: /wsxk/stu_xszx_skbj.aspx?xxx
//* 通识: /wsxk/stu_xszx_skbj.aspx?xxx
var tab = document.getElementById("pageRpt");
var sureBtn = document.getElementById("sure"); // 确定按钮
//* III.1. 最后新加一列
// 列头
var tdh = tab.querySelector(".T").insertCell();
tdh.rowSpan = "2";
tdh.textContent = "快选";
tdh.align = "center";
//* III.2. 每一行结尾添加快选按钮
var trs = tab.querySelectorAll(".B");
for (const tr of trs) {
if (tr.lastElementChild.firstElementChild.tagName.toUpperCase() == "INPUT") {
var rad = tr.lastElementChild.firstElementChild;
// 含radio的那一行,插入快选按钮
var inp = document.createElement("input");
inp.className = "button";
inp.type = "button";
inp.value = "快选";
inp.style.marginTop = "2px";
inp.style.marginBottom = "2px";
inp.disabled = rad.disabled; // 快选的按钮disabled与前面的radio相同
// 每行最后一个格子
var td = tr.insertCell();
td.rowSpan = rad.parentElement.rowSpan;
td.appendChild(inp);
//* III.3.添加按钮的逻辑
// 按下时先选中前面的rad,再点击确定按钮
// 接受参数,返回由参数确定内容的函数指针。
inp.onclick = function(rad, sureBtn){
return function(){
// 按下时先选中前面的rad,再点击确定按钮
rad.onclick(); // 选中前面的rad
sureBtn.onclick(); // 点击确定
log("快速选择了" + rad.id);
}
}(rad, sureBtn);
}
}
log("添加了快选按钮");
}
//* --------------------------------------------------------------
})();
window.onload = function(){
//* IV. 自动点击检索按钮
//* class为button, value为检索
if (Auto_Click_Search.enabled && name == 'frmMain'){
var leixing = null; // 是哪个类型的页面:xk, ts, yy, fx
switch (self.location.pathname) {
case "/wsxk/stu_btx.aspx": // 专业课
leixing = "xk";
break;
case "/wsxk/stu_whszk.aspx": // 英语
leixing = "ts";
break;
case "/wsxk/stu_yytgk_bx.aspx": // 通识
leixing = "yy";
break;
case "/wsxk/stu_ggrxk.aspx": // 非限
leixing = "fx";
break;
default:
break;
}
if (leixing) {
var searchBtn = null;
for (const btn of document.querySelectorAll(".button")) {
if (btn.value == "检索"){
searchBtn = btn;
log("已获取检索按钮");
break;
}
}
if (searchBtn){
searchBtn.click(); // 模拟点击按钮
log("已点击检索按钮");
}else{
error("未找到检索按钮");
}
}
}
};
function insertBefore(newElem, targetElem) {
var parent = targetElem.parentNode;
parent.insertBefore(newElem, targetElem);
}
function insertAfter(newElem, targetElem){
var parent = targetElem.parentNode;
if (parent.lastChild == targetElem) {
// 如果最后的节点是目标元素,则直接添加。因为默认是最后
parent.appendChild(newElem);
} else {
//如果不是,则插入在目标元素的下一个兄弟节点 的前面。也就是目标元素的后面
parent.insertBefore(newElem, targetElem.nextSibling);
}
}