ucr cs141 grader helper script

automaticly fetch codeforce submissions and verify it.

  1. // ==UserScript==
  2. // @name ucr cs141 grader helper script
  3. // @namespace http://tampermonkey.net/
  4. // @version 2024-10-12
  5. // @description automaticly fetch codeforce submissions and verify it.
  6. // @author BugParty
  7. // @match https://www.gradescope.com/courses/873565/questions/*
  8. // @icon https://www.google.com/s2/favicons?sz=64&domain=gradescope.com
  9. // @require https://cdnjs.cloudflare.com/ajax/libs/decimal.js/9.0.0/decimal.min.js
  10. // @home-url https://github.com/bugparty/ucr_cs141_gradescope_helper
  11. // @grant none
  12. // @license MIT
  13. // ==/UserScript==
  14.  
  15.  
  16. (function() {
  17. 'use strict';
  18. /*
  19. you need to apply for codeforce api key and secret
  20. */
  21. const apiKey = '123';
  22. const apiSecret = '123';
  23. console.log("inject started");
  24. async function signCodeforcesUrl(apiKey, apiSecret, methodName, params) {
  25. // Generate a random 6-character string
  26. const rand = [...window.crypto.getRandomValues(new Uint8Array(3))].map(b => b.toString(16).padStart(2, '0')).join('');
  27.  
  28. // Current time in UNIX timestamp format
  29. const currentTime = Math.floor(Date.now() / 1000);
  30.  
  31. // Adding apiKey and time to params
  32. params.apiKey = apiKey;
  33. params.time = currentTime;
  34.  
  35. // Sort the parameters lexicographically by keys and values
  36. const sortedParams = Object.keys(params).sort().reduce((acc, key) => {
  37. acc.push(`${key}=${params[key]}`);
  38. return acc;
  39. }, []).join('&');
  40.  
  41. // Concatenating the string as per the documentation
  42. const stringToHash = `${rand}/${methodName}?${sortedParams}#${apiSecret}`;
  43. //console.log("api string", stringToHash);
  44. // Encoding the string to a Uint8Array
  45. const encoder = new TextEncoder();
  46. const dataToHash = encoder.encode(stringToHash);
  47.  
  48. // Hashing the string using SHA-512
  49. const hashBuffer = await window.crypto.subtle.digest('SHA-512', dataToHash);
  50. const hashArray = Array.from(new Uint8Array(hashBuffer)); // Convert buffer to byte array
  51. const hashHex = hashArray.map(b => b.toString(16).padStart(2, '0')).join(''); // Convert bytes to hex string
  52.  
  53. // Forming the apiSig
  54. const apiSig = `${rand}${hashHex}`;
  55.  
  56. // Creating the signed URL
  57. const signedUrl = `https://codeforces.com/api/${methodName}?${sortedParams}&apiSig=${apiSig}`;
  58.  
  59. return signedUrl;
  60. }
  61. async function fetchData(url) {
  62. try {
  63. // Sending the HTTP request to the specified URL
  64. const response = await fetch(url);
  65.  
  66. // Check if the response was successful
  67. if (!response.ok) {
  68. throw new Error(`HTTP error! Status: ${response.status}`);
  69. }
  70.  
  71. // Parsing the JSON response into an object
  72. const data = await response.json();
  73.  
  74. // Use the data object as needed
  75. console.log(data);
  76. return data;
  77. } catch (error) {
  78. console.error('Failed to fetch data:', error);
  79. }
  80. }
  81. function isValidInteger(str) {
  82. const invalid_val = {valid: false, val: -1};
  83. const regex = /^\s*\d+\s*$/;
  84. if(!regex.test(str)){
  85. return invalid_val;
  86. }
  87. const num = Number(str);
  88. return {valid: true, val: num};
  89. }
  90. $(document).ready(function(){
  91. const answers_id_queries = ["#question_41404944 > div:nth-child(3) > div:nth-child(4) > div > div > span",
  92. "#question_41404944 > div:nth-child(3) > div:nth-child(6) > div > div > span",
  93. "#question_41404944 > div:nth-child(3) > div:nth-child(8) > div > div > span",
  94. "#question_41404944 > div:nth-child(3) > div:nth-child(10) > div > div > span",
  95. "#question_41404944 > div:nth-child(3) > div:nth-child(12) > div > div > span"
  96. ];
  97. const report_title_selector = '#question_41404944_text_12';
  98. const anwers_titles = ["Lost in the Shuffle","Juice Box", "The Stairs", "Chocolate Frogs", "Wizard Chess"];
  99. const methodName = 'contest.status';
  100. const params = { contestId: '551192', asManager: true };
  101. const points_html_template = '<div id="bowman-total-points" class="form--textInput form--textInput-prominent form--textInput-med form--textInput-readOnly u-preserveWhitespace"><span> %points% </span></div>';
  102.  
  103. let endOfTitle = $("#question_41404944 > div:nth-child(2)");
  104. console.log(endOfTitle);
  105. const button_title = " Fetch Status from codeforce";
  106. endOfTitle.before('<button type="button" id="bowman_super_btn" class="tiiBtn tiiBtn-secondary actionBar--action" tabindex="0" style="display: inline-block;"><span><i class="fa" role="img" aria-hidden="true"></i><span> Fetch Status from codeforce</span></span></button>');
  107. $("#bowman_super_btn").click(function() {
  108. console.log("super button clicked, starting working");
  109. $(this).prop('disabled', true); // Disable the button
  110. $(this).text('Please wait... if not resepond for 10seconds, refresh the page'); // Change button text
  111. signCodeforcesUrl(apiKey, apiSecret, methodName, params)
  112. .then(signedUrl => {
  113. console.log('Signed URL:', signedUrl)
  114. let submissions;
  115. fetchData(signedUrl)
  116. .then(data => {
  117. if (data.status === 'OK')
  118. submissions = data.result;
  119. else{
  120. $("#bowman_super_btn").text("codeforce return an invalid response:"+ data.status)
  121. return;
  122. }
  123. const user_cf_id = $("#question_41404944 > div:nth-child(3) > div:nth-child(2) > div > div > span").text().trim()
  124. console.log("user codeforce handle is ", user_cf_id);
  125.  
  126. let answers_ids = [];
  127. let total_points = new Decimal(0);
  128. for(let i=0;i<answers_id_queries.length;i++){
  129. let element = $(answers_id_queries[i]);
  130. let the_id = element.text();
  131. console.log("problem ", anwers_titles[i], " id: ", the_id);
  132. let num_id = isValidInteger(the_id);
  133. answers_ids.push(num_id.val);
  134. if (num_id.valid){
  135. let found_submission = submissions.find(obj => obj.id === num_id.val);
  136. console.log(found_submission);
  137. let isSameUser = found_submission.author.members[0].handle === user_cf_id;
  138. if (!isSameUser) {
  139. element.append("❌ different user handle found: ", found_submission.author.members[0].handle);
  140. continue;
  141. }
  142. let problem_name = found_submission.problem.name;
  143. if (problem_name != anwers_titles[i]){
  144. element.append("❌ wrong problem: ", problem_name);
  145. continue;
  146. }
  147. if (found_submission.points == undefined){
  148. element.append("❌ no score found, verdict: ", found_submission.verdict);
  149. continue;
  150. }
  151.  
  152. element.append(" ✅ point: ", found_submission.points, " handle:", found_submission.author.members[0].handle, " problem: ", problem_name);
  153. total_points = total_points.plus(found_submission.points);
  154.  
  155.  
  156. }else{//invalid submission
  157. element.text(the_id + " ❌ invalid submission id");
  158. }
  159. }
  160. //insert total score before Report
  161.  
  162. let points_text = "total points: " + total_points + " subtract 5 by " + (Decimal.sub(5,total_points));
  163. let points_html = points_html_template.replace("%points%", points_text);
  164.  
  165. if($('#bowman-total-points').length==0){
  166. $(report_title_selector).before(points_html);
  167. }else{
  168. $('#bowman-total-points > span').text(points_text);
  169. }
  170. $("#bowman_super_btn").text(button_title);
  171. $("#bowman_super_btn").prop('disabled', false);
  172.  
  173.  
  174. })
  175. .catch(error => {
  176. console.log(error);
  177. $("#bowman_super_btn").text(error + ' ' + button_title);
  178. $("#bowman_super_btn").prop('disabled', false);
  179. //reset scores
  180. if($('#bowman-total-points').length!=0){
  181. $('#bowman-total-points > span').text("no points available");
  182. }
  183. }
  184.  
  185. );
  186. }
  187. )
  188. .catch(error => console.error('Error signing URL:', error));
  189. });
  190. });
  191. })();

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址