Terp Course Helper

Integrate Rate My Professor to Testudo Schedule of Classes

目前为 2019-04-12 提交的版本。查看 最新版本

  1. // ==UserScript==
  2. // @name Terp Course Helper
  3. // @author DickyT
  4. // @license WTFPL
  5. // @encoding utf-8
  6. // @date 04/12/2019
  7. // @modified 04/12/2019
  8. // @include https://app.testudo.umd.edu/soc/*
  9. // @grant GM_xmlhttpRequest
  10. // @run-at document-end
  11. // @version 0.0.5
  12. // @description Integrate Rate My Professor to Testudo Schedule of Classes
  13. // @namespace dkt.umdrmp.testudo
  14. // @require https://unpkg.com/ajax-hook/dist/ajaxhook.min.js
  15. // ==/UserScript==
  16.  
  17. const DATA = {};
  18. let ALIAS = {};
  19.  
  20. function loadAliasTable() {
  21. return new Promise((resolve) => {
  22. const url = 'https://raw.githubusercontent.com/DickyT/Terp-Course-Helper/master/alias.json';
  23. GM_xmlhttpRequest({
  24. method: 'GET',
  25. url,
  26. onload: (data) => {
  27. if (data.status == 200) {
  28. ALIAS = JSON.parse(data.responseText);
  29. }
  30. resolve();
  31. }
  32. });
  33. });
  34. }
  35.  
  36. function getInstructorName(elem) {
  37. const container = elem.childNodes[0];
  38. if (container instanceof HTMLAnchorElement) {
  39. return container.innerText;
  40. }
  41. return container.wholeText;
  42. }
  43.  
  44. function updateInstructorRating() {
  45. // unsafeWindow.console.log(DATA);
  46. const instructorElements = unsafeWindow.document.querySelectorAll('.section-instructor');
  47. Array.prototype.map.call(instructorElements, (elem) => {
  48. const instructorName = getInstructorName(elem);
  49. if (DATA[instructorName]) {
  50. const oldElem = elem.querySelector('.rmp-rating-box');
  51. if (oldElem) {
  52. oldElem.remove();
  53. }
  54. const rating = DATA[instructorName].rating;
  55. const ratingElem = document.createElement('a');
  56. ratingElem.className = 'rmp-rating-box';
  57. ratingElem.href = rating ? `https://www.ratemyprofessors.com/ShowRatings.jsp?tid=${DATA[instructorName].recordId}` : '';
  58. ratingElem.title = instructorName;
  59. ratingElem.target = '_blank';
  60. ratingElem.innerText = rating ? rating.toFixed(1) : 'N/A';
  61. elem.appendChild(ratingElem);
  62. }
  63. });
  64. }
  65.  
  66. function getRecordId(name) {
  67. return new Promise((resolve, reject) => {
  68. // unsafeWindow.console.log(ALIAS, name);
  69. if (ALIAS[name]) {
  70. const recordId = ALIAS[name].rmpId;
  71. if (recordId) {
  72. return resolve(recordId);
  73. }
  74. }
  75. const url = `https://search-production.ratemyprofessors.com/solr/rmp/select?q=${encodeURIComponent(name)}&defType=edismax&qf=teacherfullname_t%5E1000%20autosuggest&bf=pow%28total_number_of_ratings_i%2C2.1%29&siteName=rmp&rows=20&start=0&fl=pk_id%20teacherfirstname_t%20teacherlastname_t%20total_number_of_ratings_i%20schoolname_s%20averageratingscore_rf%20averageclarityscore_rf%20averagehelpfulscore_rf%20averageeasyscore_rf%20chili_i%20schoolid_s%20teacherdepartment_s&fq=schoolid_s%3A1270&wt=json`;
  76. GM_xmlhttpRequest({
  77. method: 'GET',
  78. url,
  79. onload: (data) => {
  80. if (data.status == 200) {
  81. const res = JSON.parse(data.responseText);
  82.  
  83. const suggestionList = res.response.docs;
  84. const [instructorInfo] = suggestionList.filter(d => d.schoolid_s === '1270');
  85. if (instructorInfo) {
  86. // unsafeWindow.console.log(instructorInfo);
  87. return resolve(instructorInfo.pk_id);
  88. }
  89. }
  90. reject();
  91. }
  92. });
  93. });
  94. }
  95.  
  96. function getRating(recordId) {
  97. return new Promise((resolve, reject) => {
  98. const url = `https://www.ratemyprofessors.com/ShowRatings.jsp?tid=${recordId}`;
  99. GM_xmlhttpRequest({
  100. method: 'GET',
  101. url,
  102. onload: (data) => {
  103. if (data.status == 200) {
  104. const res = data.responseText;
  105. const reader = document.implementation.createHTMLDocument('reader'); // prevent loading any resources
  106. const fakeHtml = reader.createElement('html');
  107. fakeHtml.innerHTML = res;
  108. const ratingRawElem = fakeHtml.querySelector('#mainContent div.grade');
  109. if (ratingRawElem) {
  110. return resolve(Number(ratingRawElem.innerText));
  111. }
  112. }
  113. reject();
  114. }
  115. });
  116. });
  117. }
  118.  
  119. function loadRateData() {
  120. const instructorElements = unsafeWindow.document.querySelectorAll('.section-instructor');
  121. Array.prototype.map.call(instructorElements, (elem) => {
  122. const instructorName = getInstructorName(elem);
  123. if (!DATA[instructorName]) {
  124. DATA[instructorName] = {
  125. name: instructorName,
  126. };
  127. getRecordId(instructorName).then((recordId) => {
  128. getRating(recordId).then((rating) => {
  129. DATA[instructorName].recordId = recordId;
  130. DATA[instructorName].rating = rating;
  131.  
  132. updateInstructorRating();
  133. }).catch(() => {
  134. updateInstructorRating();
  135. });
  136. }).catch(() => {
  137. updateInstructorRating();
  138. });
  139. }
  140. });
  141. }
  142.  
  143. function main() {
  144. loadAliasTable().then(() => {
  145. // First load
  146. loadRateData();
  147. // Add hook to HTTP events
  148. const hookAjax = unsafeWindow.window.hookAjax;
  149. hookAjax({
  150. onreadystatechange: (xhr) => {
  151. if (/https?:\/\/app.testudo.umd.edu\/soc\/[0-9]{6}\/sections\?*/.test(xhr.responseURL)) {
  152. if(xhr.readyState === XMLHttpRequest.DONE && xhr.status === 200) {
  153. setTimeout(loadRateData, 200);
  154. }
  155. }
  156. },
  157. });
  158. });
  159. }
  160.  
  161. const ajaxHookLib = document.createElement('script');
  162. ajaxHookLib.addEventListener('load', main);
  163. ajaxHookLib.src = 'https://unpkg.com/ajax-hook/dist/ajaxhook.min.js';
  164. document.head.appendChild(ajaxHookLib);
  165.  
  166. const styleInject = `
  167. .rmp-rating-box {
  168. border-radius: 5px;
  169. padding: 1px 5px;
  170. margin-left: 10px;
  171. background-color: #FF0266;
  172. color: #FFFFFF !important;
  173. font-family: monospace;
  174. font-weight: bold;
  175. }
  176. `;
  177. const styleInjectElem = document.createElement('style');
  178. styleInjectElem.id = 'umd-rmp-style-inject';
  179. styleInjectElem.innerHTML = styleInject;
  180. document.head.appendChild(styleInjectElem);