您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
方便抢课的微辅助功能
// ==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); } }