SJTU-Course Selection Assistant

添加一个打开选课社区(course.sjtu.plus)的按钮

  1. // ==UserScript==
  2. // @name SJTU-Course Selection Assistant
  3. // @namespace http://tampermonkey.net/
  4. // @version 0.4.0
  5. // @description 添加一个打开选课社区(course.sjtu.plus)的按钮
  6. // @author Me
  7. // @match https://i.sjtu.edu.cn/xsxk/zzxkyzb_cxZzxkYzbIndex.html*
  8. // @connect f002.backblazeb2.com
  9. // @grant GM_setValue
  10. // @grant GM_getValue
  11. // @grant GM_xmlhttpRequest
  12. // @homepageURL https://github.com/dzx-dzx/Course-Selection-Assistant
  13. // @license Apache
  14. // ==/UserScript==
  15.  
  16. (async function () {
  17. 'use strict';
  18. // Your code here...
  19. //From https://stackoverflow.com/questions/21271997/how-to-overwrite-a-function-using-a-userscript
  20. function showHideJxb(obj){
  21. if($(obj).children(".expand_close").attr("class").indexOf("expand1")>0){
  22. $(obj).children(".expand_close").removeClass('expand1').addClass('close1');
  23. $(obj).next(".panel-body").slideDown();
  24. }else{
  25. $(obj).children(".expand_close").removeClass('close1').addClass('expand1');
  26. $(obj).next(".panel-body").slideUp();
  27. }
  28. }
  29. addJS_Node (showHideJxb);
  30. function addJS_Node (text, s_URL, funcToRun, runOnLoad) {
  31. var D = document;
  32. var scriptNode = D.createElement ('script');
  33. if (runOnLoad) {
  34. scriptNode.addEventListener ("load", runOnLoad, false);
  35. }
  36. scriptNode.type = "text/javascript";
  37. if (text) scriptNode.textContent = text;
  38. if (s_URL) scriptNode.src = s_URL;
  39. if (funcToRun) scriptNode.textContent = '(' + funcToRun.toString() + ')()';
  40.  
  41. var targ = D.getElementsByTagName ('head')[0] || D.body || D.documentElement;
  42. targ.appendChild (scriptNode);
  43. }
  44. const courseToIdRaw = (await (async () => {
  45. const courseListTimestampResponse = await new Promise((resolve, reject) => {
  46. GM_xmlhttpRequest({
  47. method: "GET",
  48. url: "https://f002.backblazeb2.com/file/course/course.json",
  49. headers: { "Range": "bytes=0-0" },
  50. onload: function (response) { resolve(response) }
  51. })
  52. })
  53. const latestCourseListTimestamp = parseInt(courseListTimestampResponse.responseHeaders.split('\n').filter((i,/*教务系统似乎私自修改了Array的原型*/s) => s.includes("x-bz-info-src_last_modified_millis"))[0].split(":")[1])
  54. if (GM_getValue('course_list_timestamp') !== latestCourseListTimestamp) {
  55. const courseListResponse = await new Promise((resolve, reject) => {
  56. GM_xmlhttpRequest({
  57. method: "GET",
  58. url: "https://f002.backblazeb2.com/file/course/course.json",
  59. onload: function (response) { resolve(response) }
  60. })
  61. })
  62. const courseToIdRaw = JSON.parse(courseListResponse.response)
  63. GM_setValue("course_list", courseToIdRaw)
  64. GM_setValue("course_list_timestamp", latestCourseListTimestamp)
  65. return courseToIdRaw
  66. }
  67. else return GM_getValue("course_list")
  68. })())
  69.  
  70. const courseToIdMap = new Map(Object.entries(courseToIdRaw))
  71.  
  72. function addButton(tr) {
  73. const classCodeRaw = tr.querySelector("td.jxbmc").textContent
  74. const classCode = classCodeRaw.split("-").at(-2)//什么你说兼容性?能吃吗?
  75.  
  76. if (!courseToIdMap.has(classCode)) {
  77. console.warn(`课程${classCode}无法找到,请向开发者联系.`); return;
  78. }
  79. const classes = courseToIdMap.get(classCode)
  80.  
  81. const teachers = Array.from(tr.querySelector("td.jsxmzc").querySelectorAll("a"), a => a.textContent)
  82.  
  83. const id = teachers.reduce((pre, teacher) => {
  84. if (pre != null) return pre
  85. const res = classes.find((c) => c.teacher === teacher)
  86. if (res) return res.id
  87. else return null
  88. }, null)
  89.  
  90. if (!id) {
  91. console.warn(`课程${classCode}下教师${teachers}均未找到,请向开发者联系.`); return;
  92. }
  93.  
  94. const button = document.createElement("button")
  95. button.onclick = function () { window.open(`https://course.sjtu.plus/course/${id}`, "_blank"/*始终新建窗口,改成"course"以覆盖前一窗口*/) }
  96. button.setAttribute("class", "btn btn-primary btn-sm")
  97. button.textContent = "跳转到选课社区"
  98. tr.querySelector("td.jxbmc").append(document.createElement("br"), button)
  99. }
  100. document.querySelectorAll("div.panel-body > table > tbody > tr ").forEach((tr => {
  101. if (tr.querySelector(".kkxymc").textContent !== "") addButton(tr)
  102. }))
  103.  
  104. const observer = new MutationObserver((mutationList) => {
  105. mutationList.forEach((mutation) => { if (mutation.target.getAttribute("class") === "kkxymc") addButton(mutation.target.parentElement) })
  106. })
  107. observer.observe(document.querySelector("#displayBox"), { "childList": true, "subtree": true })
  108. const saveSelectedCourseButton = document.createElement("button")
  109. saveSelectedCourseButton.onclick = function(){
  110. function getSelectedCourseCode(){
  111. return [...document.querySelectorAll("div.right_div > div > ul td> p.jxb").values()].map(p=>p.getAttribute("title").split("-").at(-2))
  112. }
  113. let selectedCourseCode=getSelectedCourseCode().join(" ")
  114. selectedCourseCode=window.prompt("将保存以下课程代号:", selectedCourseCode)
  115. if(selectedCourseCode) GM_setValue("selected_course_code",selectedCourseCode)
  116. }
  117. saveSelectedCourseButton.setAttribute("class", "btn btn-primary btn-sm")
  118. saveSelectedCourseButton.textContent = "保存已选课到本地"
  119.  
  120. const loadPreSelectedCourseButton = document.createElement("button")
  121. loadPreSelectedCourseButton.onclick = function(){
  122. let selectedCourseCode=GM_getValue("selected_course_code")
  123. selectedCourseCode=window.prompt("将加载以下课程代号:", selectedCourseCode)
  124. if(selectedCourseCode){
  125. document.querySelector(".form-control.input-sm.filter-input").value=selectedCourseCode
  126. document.querySelector(".input-group-btn").querySelector(".btn.btn-primary.btn-sm").click()
  127. }
  128. }
  129. loadPreSelectedCourseButton.setAttribute("class", "btn btn-primary btn-sm")
  130. loadPreSelectedCourseButton.textContent = "加载之前保存的课程"
  131.  
  132. document.querySelector("div.col-sm-8.col-md-8.buttons").prepend(saveSelectedCourseButton,loadPreSelectedCourseButton)
  133.  
  134. })();