BUPT GPA

Calculate GPA in URP system

当前为 2020-07-09 提交的版本,查看 最新版本

  1. // ==UserScript==
  2. // @name BUPT GPA
  3. // @namespace https://ssine.cc/
  4. // @version 3.1
  5. // @description Calculate GPA in URP system
  6. // @author Liu Siyao
  7. // @include *://jwxt.bupt.edu.cn/jwLoginAction.do
  8. // @include *://jwxt.bupt.edu.cn/caslogin.jsp
  9. // @include *://vpn.bupt.edu.cn/http/jwxt.bupt.edu.cn/jwLoginAction.do
  10. // @include *://vpn.bupt.edu.cn/https/jwxt.bupt.edu.cn/jwLoginAction.do
  11. // @include *://jwgl.bupt.edu.cn/jsxsd/framework/xsMain.jsp
  12. // @include *://vpn.bupt.edu.cn/http/jwgl.bupt.edu.cn/jsxsd/framework/xsMain.jsp
  13. // @include *://vpn.bupt.edu.cn/https/jwgl.bupt.edu.cn/jsxsd/framework/xsMain.jsp
  14. // @include *://webvpn.bupt.edu.cn/*/jsxsd/framework/xsMain.jsp
  15. // @grant none
  16. // @require https://cdn.bootcss.com/jquery/3.3.1/jquery.min.js
  17. // @require https://cdn.jsdelivr.net/npm/vue
  18. // ==/UserScript==
  19.  
  20. (function () {
  21. 'use strict';
  22.  
  23. const is_old_system = /jwxt/.test(window.location.href);
  24.  
  25. function run() {
  26.  
  27. let promises = [];
  28. if (is_old_system) {
  29. promises = promises.concat([
  30. $.get('/gradeLnAllAction.do?type=ln&oper=qbinfo'),
  31. $.get('/gradeLnAllAction.do?type=ln&oper=lnFajhKcCjInfo&lnxndm=*')
  32. ]);
  33. } else {
  34. promises = promises.concat([
  35. $.post('/jsxsd/kscj/cjcx_list', {
  36. kksj: "",
  37. kcxz: "",
  38. kcmc: "",
  39. xsfs: "all"
  40. })
  41. ]);
  42. }
  43.  
  44. Promise.all(promises).then((data) => {
  45. let algoNames = ['北邮官方', '标准4.0', '改进4.0', '北大4.0', '加拿大4.3', '中科大4.3', '上海交大4.3'];
  46. let algoArea = [
  47. [59, 60, 60.5, 61, 61.5, 62, 62.5, 63, 63.5, 64, 64.5, 65, 65.5, 66, 66.5, 67, 67.5, 68, 68.5, 69, 69.5, 70, 70.5, 71, 71.5, 72, 72.5, 73, 73.5, 74, 74.5, 75, 75.5, 76, 76.5, 77, 77.5, 78, 78.5, 79, 79.5, 80, 80.5, 81, 81.5, 82, 82.5, 83, 83.5, 84, 84.5, 85, 85.5, 86, 86.5, 87, 87.5, 88, 88.5, 89, 89.5, 90, 90.5, 91, 91.5, 92, 92.5, 93, 93.5, 94, 94.5, 95, 95.5, 96, 96.5, 97, 97.5, 98, 98.5, 99, 99.5, 100],
  48. [59, 69, 79, 89, 100],
  49. [59, 69, 84, 100],
  50. [59, 63, 67, 71, 74, 77, 81, 84, 89, 100],
  51. [59, 64, 69, 74, 79, 84, 89, 100],
  52. [59, 60, 63, 64, 67, 71, 74, 77, 81, 84, 89, 94, 100],
  53. [59, 61, 64, 66, 69, 74, 79, 84, 89, 94, 100]
  54. ];
  55. let algoGp = [
  56. [0, 1.00, 1.07, 1.15, 1.22, 1.29, 1.36, 1.43, 1.50, 1.57, 1.64, 1.70, 1.77, 1.83, 1.90, 1.96, 2.02, 2.08, 2.14, 2.20, 2.26, 2.31, 2.37, 2.42, 2.48, 2.53, 2.58, 2.63, 2.68, 2.73, 2.78, 2.83, 2.87, 2.92, 2.96, 3.01, 3.05, 3.09, 3.13, 3.17, 3.21, 3.25, 3.29, 3.32, 3.36, 3.39, 3.43, 3.46, 3.49, 3.52, 3.55, 3.58, 3.61, 3.63, 3.66, 3.68, 3.71, 3.73, 3.75, 3.77, 3.79, 3.81, 3.83, 3.85, 3.86, 3.88, 3.89, 3.91, 3.92, 3.93, 3.94, 3.95, 3.96, 3.97, 3.98, 3.98, 3.99, 3.99, 4.00, 4.00, 4.00, 4.00],
  57. [0, 1, 2, 3, 4],
  58. [0, 2, 3, 4],
  59. [0, 1, 1.5, 2, 2.3, 2.7, 3, 3.3, 3.7, 4],
  60. [0, 2.3, 2.7, 3, 3.3, 3.7, 4, 4.3],
  61. [0, 1, 1.3, 1.5, 1.7, 2, 2.3, 2.7, 3, 3.3, 3.7, 4, 4.3],
  62. [0, 1, 1.7, 2, 2.3, 2.7, 3, 3.3, 3.7, 4, 4.3]
  63. ];
  64.  
  65. function getGP(score, i) {
  66. let area = algoArea[i];
  67. let gp = algoGp[i];
  68. for (let idx in area) {
  69. if (score <= area[idx])
  70. return gp[idx];
  71. }
  72. return score;
  73. };
  74.  
  75.  
  76. class course {
  77. constructor(no, name, semester, type, credit, grade) {
  78. this.no = no;
  79. this.name = name;
  80. this.semester = semester;
  81. this.type = type;
  82. this.credit = credit;
  83. this.grade = grade;
  84. }
  85. }
  86.  
  87. let calc_mat = [];
  88. let course_lst = [];
  89. let course_lst_csv = 'Name,Credit,Grade\n'
  90. let semesters = [];
  91. let course_types = ['必修', '选修', '任选'];
  92. let semester_name = '';
  93.  
  94. function fakeClick(obj) {
  95. let ev = document.createEvent("MouseEvents");
  96. ev.initMouseEvent("click", true, false, window, 0, 0, 0, 0, 0, false, false, false, false, 0, null);
  97. obj.dispatchEvent(ev);
  98. }
  99.  
  100. function exportRaw() {
  101. let urlObject = window.URL || window.webkitURL || window;
  102. let export_blob = new Blob([course_lst_csv]);
  103. let save_link = document.createElementNS("http://www.w3.org/1999/xhtml", "a")
  104. save_link.href = urlObject.createObjectURL(export_blob);
  105. save_link.download = "my_grade.csv";
  106. fakeClick(save_link);
  107. }
  108.  
  109. function showResult() {
  110. // show courses in course_lst to div
  111.  
  112. let sum = 0,
  113. total_credit = 0;
  114. let gpLst = [0, 0, 0, 0, 0, 0];
  115.  
  116. let used_couse_num = 0;
  117. for (let idx = 0; idx < course_lst.length; idx++) {
  118. let course = course_lst[idx];
  119. if (!calc_mat[semesters.indexOf(course.semester)][course_types.indexOf(course.type)])
  120. continue;
  121. total_credit += course.credit;
  122. sum += course.credit * course.grade;
  123. for (let j in gpLst) {
  124. gpLst[j] += course.credit * getGP(course.grade, j);
  125. }
  126. used_couse_num ++;
  127. };
  128.  
  129. $('#gpa-res').empty();
  130. $('#gpa-res').append($('<table>\
  131. <tr><th>算法</th><th>GPA</th></tr>\
  132. </table>'));
  133.  
  134. for (let idx in gpLst) {
  135. let newTr = "<tr><td>" + algoNames[idx] + "</td><td>" + (gpLst[idx] / total_credit).toFixed(2) + "</td></tr>";
  136. $('#gpa-res table').append($(newTr));
  137. }
  138. let contentStr = "特殊加权学分绩: " + (sum / total_credit).toFixed(2);
  139. contentStr += "<br>已修读学分: " + total_credit.toString();
  140. contentStr += "<br>计算的课程数: " + used_couse_num;
  141. contentStr += "<br>总课程数: " + course_lst.length;
  142. $('#gpa-res').append($('<p>' + contentStr + '</p>'));
  143. }
  144.  
  145. let parser = new DOMParser();
  146. if (is_old_system) {
  147.  
  148. // prepare fallback grades when normal grade is one of 优良中差
  149. let course_no_to_grade = {};
  150.  
  151. parser.parseFromString(data[1], "text/html").querySelectorAll('.odd').forEach((row) => {
  152. if (row.childNodes.length == 11) {
  153. let course_no = row.childNodes[1].innerText.trim();
  154. let grade = parseFloat(row.childNodes[7].innerText.trim());
  155. if (course_no && grade)
  156. course_no_to_grade[course_no] = grade;
  157. }
  158. });
  159.  
  160. // parse grades
  161. let body_lst = parser.parseFromString(data[0], "text/html").getElementsByTagName('body')[0].childNodes;
  162.  
  163. for (let i = 0; i < body_lst.length; i++) {
  164. if (body_lst[i].tagName == 'A') {
  165. semester_name = body_lst[i].name;
  166. if (semesters.indexOf(semester_name) == -1) {
  167. semesters.push(semester_name);
  168. }
  169. } else if (body_lst[i].className == 'titleTop2') {
  170. let entry = $(body_lst[i]).find('.odd');
  171. for (let j = 0; j < entry.length; j++) {
  172. let lst = entry[j].getElementsByTagName('td');
  173. let grade_text = entry[j].getElementsByTagName('p')[0].innerText.trim();
  174. let grade = parseFloat(grade_text);
  175. if (grade_text in ['优', '良', '中', '差'])
  176. grade = course_no_to_grade[lst[0].innerText.trim()];
  177. if (isNaN(grade)) continue;
  178. let course_no = lst[0].innerText.trim();
  179. let course_name_zh = lst[2].innerText.trim();
  180. let course_name_en = lst[3].innerText.trim();
  181. let course_type = lst[5].innerText.trim();
  182. let course_credit = lst[4].innerText.trim();
  183. course_lst.push(new course(
  184. course_no,
  185. course_name_zh,
  186. semester_name,
  187. course_type,
  188. parseFloat(course_credit),
  189. grade
  190. ));
  191. course_lst_csv += (course_name_en + ',' + course_credit + ',' + grade + '\n');
  192. }
  193. }
  194. }
  195.  
  196. } else {
  197. let named_grade = {
  198. '差': 65,
  199. '及格': 65,
  200. '合格': 65,
  201. '中': 75,
  202. '良': 85,
  203. '优': 95
  204. };
  205. // parse grades
  206. let body_lst = parser.parseFromString(data[0], "text/html").querySelector('#dataList tbody').childNodes;
  207. body_lst = Array.prototype.slice.call(body_lst, 0).filter((_, idx) => idx % 2 === 0).slice(1);
  208. body_lst = body_lst.map(it => it.cells);
  209.  
  210. for (let item of body_lst) {
  211. if (item[6].innerText.trim() === '免修') continue;
  212. semester_name = item[1].innerText.trim();
  213. if (semesters.indexOf(semester_name) == -1) {
  214. semesters.push(semester_name);
  215. }
  216. let grade_text = item[5].innerText.trim();
  217. let grade = parseFloat(grade_text);
  218. if (grade_text in named_grade)
  219. grade = named_grade[grade_text];
  220. if (isNaN(grade)) continue;
  221. let course_no = item[2].innerText.trim();
  222. let course_name_zh = item[3].innerText.trim();
  223. let course_name_en = item[3].innerText.trim(); // not found yet...
  224. let course_type = item[13].innerText.trim();
  225. let course_credit = item[7].innerText.trim();
  226. course_lst.push(new course(
  227. course_no,
  228. course_name_zh,
  229. semester_name,
  230. course_type,
  231. parseFloat(course_credit),
  232. grade
  233. ));
  234. course_lst_csv += (course_name_en + ',' + course_credit + ',' + grade + '\n');
  235. }
  236. }
  237.  
  238.  
  239. for (let i = 0; i < semesters.length; i++)
  240. calc_mat.push([true, true, false]);
  241.  
  242. // vue & ui stuff
  243. let gpa_div = $(`<div id="gpa">
  244. <div id="gpa-side">
  245. <div id="gpa-modify">
  246. <h2>课程属性:</h2>
  247. <table>
  248. <tr>
  249. <th>课程名</th>
  250. <th>类型</th>
  251. <th>成绩</th>
  252. <th>学分</th>
  253. </tr>
  254. <tr v-for="c in courses">
  255. <td>{{c.name}}</td>
  256. <td>
  257. <select v-model="c.type">
  258. <option>必修</option>
  259. <option>选修</option>
  260. <option>任选</option>
  261. </select>
  262. </td>
  263. <td>{{c.grade}}</td>
  264. <td>{{c.credit}}</td>
  265. </tr>
  266. </table></div>
  267. </div>
  268. <div id="gpa-main-frame">
  269. <div id="calc-app">
  270. <h2>要计算的课程:</h2>
  271. <table>
  272. <tr>
  273. <th>学期</th>
  274. <th>必修</th>
  275. <th>选修</th>
  276. <th>任选</th>
  277. </tr>
  278. <tr v-for="(r, idx) in mat">
  279. <td>{{ semesters[idx] }}</td>
  280. <td><input type="checkbox" id="checkbox" v-model="r[0]"></td>
  281. <td><input type="checkbox" id="checkbox" v-model="r[1]"></td>
  282. <td><input type="checkbox" id="checkbox" v-model="r[2]"></td>
  283. </tr>
  284. </table></div>
  285. <h2>结果:</h2>
  286. <div id="gpa-res">
  287. </div>
  288. <div id="csv-download">
  289. </div>
  290. <hr>
  291. <p>程序完全基于前端,不会存储个人信息。</p>
  292. <p>使用过程中有问题请在<a target="_blank" href="https://github.com/ssine/BUPT-GPA">代码仓库</a>提 issue</p>
  293. <p>没问题也欢迎来点个 star ヽ(✿゚▽゚)ノ</p>
  294. <p>欢迎把<a target="_blank" href="https://greasyfork.org/zh-CN/scripts/369550-bupt-gpa">这个脚本</a>分享给你的朋友哦(*/ω\*)</p>
  295. </div>
  296. </div>`);
  297.  
  298.  
  299. let sheet_css = $(`<style>
  300. #gpa {
  301. position: absolute;
  302. right: 70px;
  303. bottom: 20px;
  304. height: 80%;
  305. background-color: rgba(255,255,255,0.9);
  306. font-size: 16px;
  307. line-height: 23px;
  308. }
  309. #gpa table {
  310. border-collapse: separate;
  311. border-spacing: 15px 0;
  312. }
  313. #gpa-side {
  314. float: left;
  315. margin-right: 20px;
  316. height: 100%;
  317. overflow: auto;
  318. }
  319. #gpa-main-frame {
  320. float: left;
  321. height: 100%;
  322. overflow: auto;
  323. }
  324. #gpa-modify table tr td:first-child, #gpa-modify table tr th:first-child {
  325. width: 200px;
  326. }
  327. #calc-app {
  328. }
  329. #res-app {
  330. margin-top: 50px;
  331. }
  332. #gpa-btn {
  333. position: absolute;
  334. right: 20px;
  335. bottom: 20px;
  336. background-color: RGB(119,119,119);
  337. color: rgb(255,255,255);
  338. height: 50px;
  339. width: 50px;
  340. border-radius: 50px;
  341. font-family: sans-serif;
  342. }
  343. </style>`);
  344.  
  345. let btn_download = $('<button id="download-btn">下载CSV成绩单</button>');
  346. btn_download.click(() => {
  347. exportRaw();
  348. });
  349.  
  350. $('head').append(sheet_css);
  351. gpa_div.hide();
  352. $('html').append(gpa_div);
  353.  
  354. let btn = $('<button id="gpa-btn">GPA</button>');
  355. btn.click(() => {
  356. let app = $('#gpa');
  357. if (app.css('display') == 'none')
  358. app.css('display', '');
  359. else
  360. app.css('display', 'none');
  361. });
  362. $('html').append(btn);
  363. $('#csv-download').append(btn_download);
  364. showResult();
  365.  
  366. const gpa_modify = new Vue({
  367. el: '#gpa-modify',
  368. data: {
  369. courses: course_lst
  370. },
  371. watch: {
  372. courses: {
  373. handler(newValue, oldValue) {
  374. showResult();
  375. },
  376. deep: true
  377. }
  378. }
  379. });
  380.  
  381. let calc_app = new Vue({
  382. el: '#calc-app',
  383. data: {
  384. mat: calc_mat,
  385. semesters: semesters
  386. },
  387. watch: {
  388. mat: showResult
  389. }
  390. });
  391. })
  392. }
  393.  
  394. if (is_old_system) window.parent.frames[1].onload = run;
  395. else window.onload = run;
  396.  
  397. })();