Canvas All Info

Place all information on a single page (https://canvas.example.com/all or https://example.instructure.com/all)

  1. // ==UserScript==
  2. // @name Canvas All Info
  3. // @namespace https://theusaf.github.io
  4. // @version 1.4.0
  5. // @icon https://canvas.instructure.com/favicon.ico
  6. // @copyright 2020-2021, Daniel Lau
  7. // @license MIT
  8. // @description Place all information on a single page (https://canvas.example.com/all or https://example.instructure.com/all)
  9. // @author theusaf
  10. // @include /^https:\/\/canvas\.[a-z0-9]*?\.[a-z]*?\/all\/?(\?.*)?$/
  11. // @include /^https:\/\/[a-z0-9]*?\.instructure\.com\/all\/?(\?.*)?$/
  12. // @inject-into page
  13. // @grant none
  14. // ==/UserScript==
  15.  
  16. /*
  17. Note:
  18. - This userscript uses public APIs accessed by canvas
  19. - Gets class information
  20. - Gets assignments
  21. - Gets basic teacher information
  22. - This userscript does not store or upload any information gathered by the script
  23. - This userscript overwrites /all in canvas
  24. - This userscript was originally developed for Oregon State University
  25. */
  26.  
  27. /* Useful Links (for use later?)
  28. /api/v1/conversations?scope=inbox&filter_mode=and&include_private_conversation_enrollments=false
  29. - Canvas mail
  30. /api/v1/conversations/(mailbox_id)?include_participant_contexts=false&include_private_conversation_enrollments=false
  31. - Specific mail
  32. /courses/(class_id)/modules/items/assignment_info
  33. - Module items
  34. */
  35.  
  36. /**
  37. * log - logs info to console
  38. * @param {*} str
  39. * @param {...any} data
  40. */
  41. const log = (str, ...data) => {
  42. if (data.length > 0) {
  43. console.log(`[CANVAS-ALL] ${str}`, data);
  44. }
  45. console.log(`[CANVAS-ALL] ${str}`);
  46. };
  47.  
  48. /**
  49. * load - Loads everything
  50. */
  51. async function load() {
  52. document.title = "Dashboard - All";
  53.  
  54. /**
  55. * mainElement - the main application div
  56. * iFrameLoader - The div for loading iframes for getting data
  57. * styles - A style element
  58. */
  59. const mainElement = document.getElementById("main"),
  60. iFrameLoader = document.createElement("div");
  61. mainElement.innerHTML = `<style>
  62. #canvas-all-iframe-loader{
  63. visibility: hidden;
  64. position: fixed;
  65. width: 100%;
  66. height: 100%;
  67. pointer-events: none;
  68. left: 0;
  69. }
  70. #canvas-all-iframe-loader > iframe{
  71. width: 100%;
  72. height: 100%;
  73. position: absolute;
  74. left: 0;
  75. top: 0;
  76. }
  77. #main{
  78. display: flex;
  79. flex-flow: column;
  80. padding: 1rem;
  81. }
  82. #main>span{
  83. flex: 0;
  84. margin-bottom: 1rem;
  85. }
  86. #main>div{
  87. flex: 1;
  88. }
  89. #canvasall_main_wrapper{
  90. display: flex;
  91. }
  92. #canvasall_main_wrapper>div{
  93. flex: 75%;
  94. }
  95. #canvasall_class_grades{
  96. display: flex;
  97. flex-flow: column;
  98. background: #fff5e0;
  99. padding: 0.5rem;
  100. border-radius: 0.5rem;
  101. }
  102. #canvasall_assignments_wrapper{
  103. padding: 0.5rem;
  104. background: #fff5e0;
  105. border-radius: 0.5rem;
  106. }
  107. #canvasall_main_wrapper>#canvasall_announcement_wrapper{
  108. flex: 25%;
  109. padding: 0.5rem;
  110. }
  111. #canvasall_assignment_filter_list_chosen{
  112. display: inline-block;
  113. }
  114. #canvasall_assignment_filter_list_chosen>option{
  115. display: inline-block;
  116. background: grey;
  117. color: white;
  118. padding: 0.25rem;
  119. margin: 0.25rem;
  120. cursor: pointer;
  121. }
  122. #canvasall_assignment_filter_list_chosen>option::before{
  123. content: "x ";
  124. }
  125. .canvasall_announcement_wrapper{
  126. margin-bottom: 0.5rem;
  127. padding: 0.5rem;
  128. border-radius: 0.5rem;
  129. }
  130. .canvasall_announcement_wrapper:nth-child(2n+1){
  131. background: #fff5e0;
  132. }
  133. .canvasall_announcement_wrapper:nth-child(2n){
  134. background: #ddd;
  135. }
  136. .canvasall_announcement_title{
  137. font-size: 1.25rem;
  138. font-weight: bold;
  139. }
  140. .canvasall_announcement_class,
  141. .canvasall_announcement_when{
  142. font-size: 0.75rem;
  143. }
  144. .canvasall_announcement_class>a{
  145. color: grey;
  146. }
  147. .canvasall_class_grade_wrapper:nth-child(2n+1){
  148. background: white;
  149. }
  150. .canvasall_class_grade_wrapper:nth-child(2n){
  151. background: #eee;
  152. }
  153. .canvasall_class_grade_wrapper{
  154. flex: 1;
  155. display: flex;
  156. border-radius: 0.5rem;
  157. }
  158. .canvasall_class_grade_wrapper>div{
  159. flex: 1;
  160. padding: 0.5rem;
  161. word-break: break-all;
  162. }
  163. .canvasall_assignment_wrapper:nth-child(2n+1){
  164. background: rgba(255,255,255,0.8);
  165. }
  166. .canvasall_assignment_wrapper:nth-child(2n){
  167. background: rgba(255,255,255,0.4);
  168. }
  169. .canvasall_assignment_wrapper{
  170. display: flex;
  171. padding: 0.5rem;
  172. border-radius: 0.5rem;
  173. }
  174. .canvasall_assignment_wrapper>div{
  175. flex: 1;
  176. padding-right: 0.25rem;
  177. padding-left: 0.25rem;
  178. }
  179. .canvasall_assignment_title{
  180. display: flex;
  181. align-items: center;
  182. }
  183. .canvasall_assignment_title>img{
  184. height: 1.5rem;
  185. width: 1.5rem;
  186. margin-right: 0.5rem;
  187. }
  188. .canvasall_assignment_icon {
  189. min-height: 1.5rem;
  190. min-width: 1.5rem;
  191. margin-right: 0.5rem;
  192. }
  193. .canvasall_assignment_type_assignment:before {
  194. content: url("data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZlcnNpb249IjEuMSIgeD0iMCIgeT0iMCIgdmlld0JveD0iMCAwIDI4MCAyNTkiIGVuYWJsZS1iYWNrZ3JvdW5kPSJuZXcgMCAwIDI4MCAyNTkiIHhtbDpzcGFjZT0icHJlc2VydmUiPjxwYXRoIGQ9Ik03My4zMSwxOThjLTExLjkzLDAtMjIuMjIsOC0yNCwxOC43M2EyNi42NywyNi42NywwLDAsMC0uMywzLjYzdi4zYTIyLDIyLDAsMCwwLDUuNDQsMTQuNjUsMjIuNDcsMjIuNDcsMCwwLDAsMTcuMjIsOEgyMDBWMjI4LjE5aC0xMzRWMjEzLjA4SDIwMFYxOThabTIxLTEwNS43NGg5MC42NFY2Mkg5NC4zWk03OS4xOSwxMDcuMzRWNDYuOTJIMjAwdjYwLjQyWm03LjU1LDMwLjIxVjEyMi40NUgxOTIuNDl2MTUuMTFaTTcxLjY1LDE2LjcxQTIyLjcyLDIyLjcyLDAsMCwwLDQ5LDM5LjM2VjE5MC44OGE0MS4xMiw0MS4xMiwwLDAsMSwyNC4zMi04aDE1N1YxNi43MVpNMzMuODgsMzkuMzZBMzcuNzgsMzcuNzgsMCwwLDEsNzEuNjUsMS42SDI0NS4zNlYxOThIMjE1LjE1djQ1LjMyaDIyLjY2VjI1OC40SDcxLjY1YTM3Ljg1LDM3Ljg1LDAsMCwxLTM3Ljc2LTM3Ljc2WiI+PC9wYXRoPjwvc3ZnPgo=");
  195. }
  196. .canvasall_assignment_type_quiz:before {
  197. content: url("data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZlcnNpb249IjEuMSIgeD0iMCIgeT0iMCIgdmlld0JveD0iMCAwIDI4MCAyODAiIGVuYWJsZS1iYWNrZ3JvdW5kPSJuZXcgMCAwIDI4MCAyODAiIHhtbDpzcGFjZT0icHJlc2VydmUiPjxwYXRoIGQ9Ik05MS43MiwxMjAuNzVoOTYuNTZWMTA0LjY1SDkxLjcyWm0wLDQ4LjI4aDgwLjQ3VjE1Mi45NEg5MS43MlptMC05Ni41Nmg4MC40N1Y1Ni4zN0g5MS43MlptMTYwLjk0LDM0Ljg4SDIyOC41MlYxMC43OGgtMTc3djk2LjU2SDI3LjM0QTI0LjE3LDI0LjE3LDAsMCwwLDMuMiwxMzEuNDhWMjQ0LjE0YTI0LjE3LDI0LjE3LDAsMCwwLDI0LjE0LDI0LjE0SDI1Mi42NmEyNC4xNywyNC4xNywwLDAsMCwyNC4xNC0yNC4xNFYxMzEuNDhBMjQuMTcsMjQuMTcsMCwwLDAsMjUyLjY2LDEwNy4zNFptMCwxNi4wOWE4LjA2LDguMDYsMCwwLDEsOCw4djUxLjc3bC0zMi4xOSwxOS4zMVYxMjMuNDRaTTY3LjU4LDIwMy45MXYtMTc3SDIxMi40MnYxNzdaTTI3LjM0LDEyMy40NEg1MS40OHY3OS4xM0wxOS4yOSwxODMuMjZWMTMxLjQ4QTguMDYsOC4wNiwwLDAsMSwyNy4zNCwxMjMuNDRaTTI1Mi42NiwyNTIuMTlIMjcuMzRhOC4wNiw4LjA2LDAsMCwxLTgtOFYyMDJsMzAsMThIMjMwLjc1bDMwLTE4djQyLjEyQTguMDYsOC4wNiwwLDAsMSwyNTIuNjYsMjUyLjE5WiI+PC9wYXRoPjwvc3ZnPgo=");
  198. }
  199. .canvasall_assignment_type_discussion_topic:before {
  200. content: url("data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz4KPHN2ZyB3aWR0aD0iMWVtIiBoZWlnaHQ9IjFlbSIgcm90YXRlPSIwIiBzdHlsZT0iaGVpZ2h0OjFlbTt3aWR0aDoxZW0iIHZpZXdCb3g9IjAgMCAxOTIwIDE5MjAiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+CjxnPgo8cGF0aCBkPSJtNjc3LjY1IDE2djMzOC45NGgxMTIuOTR2LTIyNS44OGgxMDE2LjV2NzkwLjQ4aC0yMjZ2MjU5Ljc2bC0yNTkuNjUtMjU5Ljc2aC03OS4xNzJ2LTQ1MS42NWgtNTY0LjU5LTY3Ny42NXYxMDE2LjVoMzM4LjcxdjQxOC45bDQxOC00MTguOWg0ODUuNTN2LTQ1MS44OGgzMi43NTNsNDE5LjEyIDQxOS4xMnYtNDE5LjEyaDIyNS44OHYtMTAxNi41aC0xMjQyLjR6bS0zMzguODYgOTAzLjU2aDU2NC43MXYtMTEyLjk0aC01NjQuNzF2MTEyLjk0em0wIDIyNS44OGgzMzguOTR2LTExMy4wNWgtMzM4Ljk0djExMy4wNXptLTIyNS44NS01NjQuNzRoMTAxNi41djc5MC43aC00MTkuMDFsLTI1OC43NSAyNTkuNjV2LTI1OS42NWgtMzM4Ljcxdi03OTAuN3oiIGZpbGwtcnVsZT0iZXZlbm9kZCIvPgo8L2c+Cjwvc3ZnPgo=")
  201. }
  202. .canvasall_status_submit,
  203. .canvasall_status_submit a{
  204. color: green;
  205. }
  206. .canvasall_status_done{
  207. text-decoration: line-through;
  208. }
  209. .canvasall_status_late{
  210. background: orange !important;
  211. color: white;
  212. }
  213. .canvasall_status_late a{
  214. color: white;
  215. }
  216. .canvasall_status_missing{
  217. background: red !important;
  218. color: white;
  219. }
  220. .canvasall_status_missing a{
  221. color: white;
  222. }
  223. .canvasall_status_feedback>.canvasall_assignment_title::after{
  224. content: " (Feedback available)"
  225. }
  226. </style>
  227. <span id="canvasall_fetching_information">Fetching information... please wait...</span>
  228. <div id="canvasall_main_wrapper">
  229. <div>
  230. <div id="canvasall_class_wrapper">
  231. <!-- Courses, Grades, etc. -->
  232. <h3>Courses</h3>
  233. <div id="canvasall_class_grades">
  234. <div id="canvasall_class_grade_header" class="canvasall_class_grade_wrapper">
  235. <div><span>Class</span></div>
  236. <div><span>Grade</span></div>
  237. <div><span>Professor</span></div>
  238. </div>
  239. </div>
  240. </div>
  241. <h3>Current Assignments</h3>
  242. <div>
  243. <span>Filters:</span>
  244. <select id="canvasall_assignment_filter_status">
  245. <option value="">Select</option>
  246. <option value="submit">Hide Submitted</option>
  247. <option value="done">Hide Graded</option>
  248. <option value="late">Hide Late</option>
  249. <option value="missing">Hide Missing</option>
  250. <option value="quiz">Hide Quiz</option>
  251. <option value="assignment">Hide Assignment</option>
  252. </select>
  253. <div id="canvasall_assignment_filter_list_chosen">
  254. </div>
  255. </div>
  256. <div id="canvasall_assignments_wrapper">
  257. <div class="canvasall_assignment_wrapper">
  258. <div>
  259. <span>Assignment Name</span>
  260. </div>
  261. <div>
  262. <span>Class</span>
  263. </div>
  264. <div>
  265. <span>Due Date</span>
  266. </div>
  267. </div>
  268. </div>
  269. </div>
  270. <div id="canvasall_announcement_wrapper">
  271. <h3>Announcements</h3>
  272. </div>
  273. </div>`;
  274. iFrameLoader.id = "canvas-all-iframe-loader";
  275. mainElement.append(iFrameLoader);
  276.  
  277. /**
  278. * courses - Class information
  279. * classAssignments - Assignments for courses
  280. * mainFrame - The main iframe
  281. * ENV - Global variables with useful data
  282. * currentUserID - The current user id
  283. */
  284. log("Getting courses");
  285. const courses = await getCourses(),
  286. courseAssignments = [],
  287. courseGrades = {},
  288. { ENV } = window,
  289. { currentUserID } = ENV,
  290. courseGradeDiv = mainElement.querySelector("#canvasall_class_grades"),
  291. assignmentsDiv = mainElement.querySelector("#canvasall_assignments_wrapper"),
  292. announcementsDiv = mainElement.querySelector(
  293. "#canvasall_announcement_wrapper"
  294. ),
  295. statusFilter = mainElement.querySelector(
  296. "#canvasall_assignment_filter_status"
  297. ),
  298. statusFilterList = mainElement.querySelector(
  299. "#canvasall_assignment_filter_list_chosen"
  300. ),
  301. localStorageConfigStr = localStorage.canvasAllConfig ?? "{}",
  302. localStorageConfig = JSON.parse(localStorageConfigStr);
  303.  
  304. function hideType(value) {
  305. console.log(`Hiding '${value}'`);
  306. if (value === "") {
  307. // ignore reset to empty value
  308. console.log("Ignoring empty value");
  309. return;
  310. }
  311. const elem = statusFilter.querySelector(
  312. `option[value="${value}"]`
  313. ),
  314. temp = document.createElement("template"),
  315. now = `${Math.random()}`.substring(2);
  316. if (!elem) {
  317. console.error("Could not find element");
  318. return;
  319. }
  320. temp.innerHTML = `<style id="canvasall_filter_${now}">
  321. .canvasall_status_${value}{
  322. display: none;
  323. }
  324. </style>`;
  325. localStorageConfig[value] = true;
  326. localStorage.canvasAllConfig = JSON.stringify(localStorageConfig);
  327. setTimeout(() => {
  328. document.body.append(temp.content.cloneNode(true));
  329. statusFilterList.append(elem);
  330. elem.addEventListener("click", click);
  331. statusFilter.value = "";
  332. });
  333. function click() {
  334. localStorageConfig[value] = false;
  335. localStorage.canvasAllConfig = JSON.stringify(localStorageConfig);
  336. // remove style
  337. document.querySelector(`#canvasall_filter_${now}`).remove();
  338. elem.removeEventListener("click", click);
  339. setTimeout(() => {
  340. statusFilter.append(elem);
  341. statusFilter.value = "";
  342. });
  343. }
  344. }
  345.  
  346. // Filters
  347. statusFilter.addEventListener("change", () => {
  348. hideType(statusFilter.value);
  349. });
  350.  
  351. // Begin writing class information
  352. for (const [i, course] of Object.entries(courses)) {
  353. const template = document.createElement("template");
  354. template.innerHTML = `<div class="canvasall_class_grade_wrapper" canvasall-class-id="${
  355. course.id
  356. }">
  357. <div class="canvasall_class_grade_name">
  358. <span>${+i + 1}. <a href="/courses/${course.id}">${
  359. course.name
  360. }</a></span>
  361. </div>
  362. <div class="canvasall_class_grade_score">
  363. <span>(Loading scores...)</span>
  364. </div>
  365. <div class="canvasall_class_grade_instructor">
  366. <span>(Loading instructors...)</span>
  367. </div>
  368. </div>`;
  369. courseGradeDiv.append(template.content.cloneNode(true));
  370. }
  371.  
  372. log("Getting course assignments and grades");
  373. let courseCount = 0,
  374. finishedCollections = 0;
  375. for (const course of courses) {
  376. courseCount++;
  377. getCourseAssignments(course.id, currentUserID).then(assignments => {
  378. courseAssignments.push.apply(
  379. courseAssignments,
  380. assignments
  381. );
  382. finishedCollections++;
  383. if (finishedCollections === courseCount) {
  384. // Write assignments
  385. courseAssignments.sort((a, b) => {
  386. // sort by "due date"
  387. // also places non-due-date at end. (+1 week)
  388. const dueA = new Date(
  389. a.plannable.due_at || b.plannable.created_at || Date.now() + 604800e3
  390. ),
  391. dueB = new Date(
  392. b.plannable.due_at || b.plannable.created_at || Date.now() + 604800e3
  393. );
  394. return dueA.getTime() - dueB.getTime();
  395. });
  396.  
  397. log("Compiling assignments");
  398. for (const assignment of courseAssignments) {
  399. const template = document.createElement("template");
  400. if (assignment.plannable_type === "announcement") {
  401. // Put in announcement thing
  402. template.innerHTML = `<div class="canvasall_announcement_wrapper">
  403. <div class="canvasall_announcement_title">
  404. <a href="${assignment.html_url}">${assignment.plannable.title}</a>
  405. </div>
  406. <div class="canvasall_announcement_class">
  407. <a href="/courses/${assignment.course_id}">${
  408. assignment.context_name
  409. }</a>
  410. </div>
  411. <div class="canvasall_announcement_when">
  412. <span>${new Date(assignment.plannable.created_at)
  413. .toString()
  414. .split(" ")
  415. .slice(0, 5)
  416. .join(" ")
  417. .replace(/:\d{2}$/, "")
  418. .replace(/\s(?=\w{3}\s\d{2})/, ", ")
  419. .replace(/\d{4}/, "at")}</span>
  420. </div>
  421. </div>`;
  422. announcementsDiv.append(template.content.cloneNode(true));
  423. continue;
  424. }
  425. const divClasses = [],
  426. { submissions } = assignment;
  427. if (submissions) {
  428. const { excused, graded, has_feedback, late, missing, submitted } =
  429. submissions;
  430. if (excused || graded) {
  431. divClasses.push("canvasall_status_done");
  432. }
  433. if (submitted) {
  434. divClasses.push("canvasall_status_submit");
  435. }
  436. if (late) {
  437. divClasses.push("canvasall_status_late");
  438. }
  439. if (missing) {
  440. divClasses.push("canvasall_status_missing");
  441. }
  442. if (has_feedback) {
  443. divClasses.push("canvasall_status_feedback");
  444. }
  445. }
  446.  
  447. divClasses.push("canvasall_status_" + assignment.plannable_type);
  448. template.innerHTML = `<div class="canvasall_assignment_wrapper ${divClasses.join(
  449. " "
  450. )}" class-id="${assignment.course_id}">
  451. <div class="canvasall_assignment_title">
  452. <div class="canvasall_assignment_icon canvasall_assignment_type_${
  453. assignment.plannable_type
  454. }"></div>
  455. <a href="${assignment.html_url}">${assignment.plannable.title}</a>
  456. </div>
  457. <div class="canvasall_assignment_class">
  458. <a href="/courses/${assignment.course_id}">${
  459. assignment.context_name
  460. }</a>
  461. </div>
  462. <div class="canvasall_assignment_due">
  463. <span>${
  464. assignment.plannable.due_at
  465. ? new Date(assignment.plannable.due_at)
  466. .toString()
  467. .split(" ")
  468. .slice(0, 5)
  469. .join(" ")
  470. .replace(/:\d{2}$/, "")
  471. .replace(/\s(?=\w{3}\s\d{2})/, ", ")
  472. .replace(/\d{4}/, "at")
  473. : "No due date"
  474. }</span>
  475. </div>
  476. </div>`;
  477. assignmentsDiv.append(template.content.cloneNode(true));
  478. }
  479.  
  480. // Restore hide
  481. for (const [key, value] of Object.entries(localStorageConfig)) {
  482. if (value && key !== "") {
  483. hideType(key);
  484. }
  485. }
  486.  
  487. courseAssignments.splice(0); // Free up memory
  488. document.querySelector("#canvasall_fetching_information").outerHTML = "";
  489. }
  490. });
  491. // Get grades
  492. loadFrame(iFrameLoader, `/courses/${course.id}/grades`).then(
  493. (ClassGradeFrame) => {
  494. const { document: d, window: w, frame } = ClassGradeFrame;
  495. let overallGrade = d
  496. .querySelector(
  497. "#student-grades-right-content .student_assignment.final_grade > .grade"
  498. )
  499. ?.textContent?.replace(/%|\s/g, ""),
  500. titles = d.querySelectorAll(".title"),
  501. possiblePoints = d.querySelectorAll(".points_possible"),
  502. dueDates = d.querySelectorAll(".due");
  503. courseGrades[course.id] = {
  504. Grades: overallGrade + "%",
  505. Titles: titles,
  506. PossiblePoints: possiblePoints,
  507. DueDates: dueDates,
  508. };
  509. // Write grades
  510. const gradeDiv = courseGradeDiv.querySelector(
  511. `[canvasall-class-id="${course.id}"] > .canvasall_class_grade_score`
  512. ),
  513. { ENV } = w,
  514. { grading_scheme } = ENV,
  515. [letter] =
  516. (grading_scheme ?? ["N/A", 0]).find((scheme) => {
  517. try {
  518. return +overallGrade / 100 >= scheme[1];
  519. } catch (e) {
  520. console.error(e);
  521. }
  522. }) ?? ["?"];
  523. gradeDiv.innerHTML =
  524. overallGrade !== "N/A" && overallGrade
  525. ? `<span>${letter} (${overallGrade}%)</span>`
  526. : `<span>N/A</span>`;
  527. // Done using data from iframe, attempt to clean up memory usage
  528. overallGrade = titles = possiblePoints = dueDates = null;
  529. for (const i in courseGrades) {
  530. delete courseGrades[i];
  531. }
  532. w.location = "about:blank";
  533. setTimeout(() => {
  534. frame.remove();
  535. }, 500);
  536. }
  537. );
  538. // Get instructors
  539. getCourseTeacher(course.id)
  540. .then((teacher) => {
  541. const teacherDiv = courseGradeDiv.querySelector(
  542. `[canvasall-class-id="${course.id}"] > .canvasall_class_grade_instructor`
  543. );
  544. teacherDiv.innerHTML = `<span>${teacher.name}</span>`;
  545. })
  546. .catch(() => {
  547. // no teacher, or failed to get teacher
  548. const teacherDiv = courseGradeDiv.querySelector(
  549. `[canvasall-class-id="${course.id}"] > .canvasall_class_grade_instructor`
  550. );
  551. teacherDiv.innerHTML = "<span>No instructor found.</span>";
  552. });
  553. }
  554. }
  555.  
  556. // /api/v1/users/:user_id/communication_channels
  557. /**
  558. * getCourses - Gets all the classes of the user
  559. *
  560. * @returns {Promise<Array>} A list of class information
  561. */
  562. function getCourses() {
  563. return new Promise((res, rej) => {
  564. const x = new XMLHttpRequest();
  565. x.open(
  566. "GET",
  567. "/api/v1/users/self/favorites/courses?include[]=term&exclude[]=enrollments&sort=nickname"
  568. );
  569. x.setRequestHeader("X-Requested-With", "XMLHttpRequest");
  570. x.setRequestHeader(
  571. "accept",
  572. "application/json, text/javascript, application/json+canvas-string-ids, */*; q=0.01"
  573. );
  574. x.onload = () => {
  575. res(JSON.parse(x.responseText));
  576. };
  577. x.onerror = () => {
  578. rej();
  579. };
  580. x.send();
  581. });
  582. }
  583.  
  584. /**
  585. * GetClassAssignments - Gets the class assignments
  586. *
  587. * @param {String} courseID The class id
  588. * @param {String} userID The user id
  589. * @returns {Promise<Array>} The list of assignments
  590. */
  591. function getCourseAssignments(courseID, userID) {
  592. const x = new XMLHttpRequest(),
  593. now = new Date(),
  594. offset = now.getTimezoneOffset() / 60,
  595. nextUrlRegex = /[^<>]*(?=>; rel="next")/;
  596. now.addDays(-14); // To get assignments from 2 weeks ago
  597. now.setHours(8 - offset);
  598. now.setMinutes(0);
  599. now.setSeconds(0);
  600. now.setMilliseconds(0);
  601.  
  602. function makeHttpRequest(url) {
  603. return new Promise((res, rej) => {
  604. x.open("GET", url);
  605. x.setRequestHeader("X-Requested-With", "XMLHttpRequest");
  606. x.setRequestHeader(
  607. "accept",
  608. "application/json+canvas-string-ids, application/json+canvas-string-ids, application/json, text/plain, */*"
  609. );
  610. x.onerror = () => {rej();}
  611. x.onload = () => {
  612. res(x);
  613. }
  614. x.send();
  615. });
  616. }
  617.  
  618. async function gatherRecursiveRequests(url, list = []) {
  619. const response = await makeHttpRequest(url),
  620. data = JSON.parse(response.responseText),
  621. linkHeader = response.getResponseHeader("link");
  622. list.push(...data);
  623. if (linkHeader) {
  624. const lastLink = linkHeader.match(nextUrlRegex);
  625. if (lastLink) {
  626. return gatherRecursiveRequests(lastLink[0], list);
  627. } else {
  628. return list;
  629. }
  630. } else {
  631. return list;
  632. }
  633. }
  634.  
  635. return gatherRecursiveRequests(`/api/v1/planner/items?start_date=${now.toISOString()}&order=asc&context_codes[]=course_${courseID}`) //&context_codes[]=user_${userID}
  636. .catch(() => []);
  637. }
  638.  
  639. /**
  640. * getCourseTeacher - Gets the teacher of the course
  641. *
  642. * @param {String} courseID The class id
  643. * @returns {Object} The teacher
  644. */
  645. function getCourseTeacher(courseID) {
  646. return new Promise((res, rej) => {
  647. const x = new XMLHttpRequest();
  648. x.open(
  649. "GET",
  650. `/api/v1/search/recipients?search=&per_page=20&permissions[]=send_messages_all&messageable_only=true&synthetic_contexts=true&context=course_${courseID}_teachers`
  651. );
  652. x.setRequestHeader("X-Requested-With", "XMLHttpRequest");
  653. x.setRequestHeader(
  654. "Accept",
  655. "application/json, text/javascript, application/json+canvas-string-ids, */*; q=0.01"
  656. );
  657. x.onload = () => {
  658. res(JSON.parse(x.responseText)[0]);
  659. };
  660. x.onerror = () => {
  661. rej();
  662. };
  663. x.send();
  664. });
  665. }
  666.  
  667. /**
  668. * loadFrame - Loads an iframe
  669. *
  670. * @param {HTMLElement} iFrameLoader The element to append iframes to
  671. * @param {String} src The iframe url to load
  672. * @returns {Promise<Object>} returns an object with the "window" and "document" of the iframe
  673. */
  674. function loadFrame(iFrameLoader, src) {
  675. return new Promise((res) => {
  676. const mainFrame = document.createElement("iframe");
  677. mainFrame.src = src || "/";
  678. iFrameLoader.append(mainFrame);
  679. mainFrame.addEventListener("load", () => {
  680. const frameContext = {
  681. document: mainFrame.contentDocument,
  682. window: mainFrame.contentWindow,
  683. frame: mainFrame
  684. };
  685. res(frameContext);
  686. });
  687. });
  688. }
  689.  
  690. load();