- // ==UserScript==
- // @name ucr cs141 grader helper script
- // @namespace http://tampermonkey.net/
- // @version 2024-10-12
- // @description automaticly fetch codeforce submissions and verify it.
- // @author BugParty
- // @match https://www.gradescope.com/courses/873565/questions/*
- // @icon https://www.google.com/s2/favicons?sz=64&domain=gradescope.com
- // @require https://cdnjs.cloudflare.com/ajax/libs/decimal.js/9.0.0/decimal.min.js
- // @home-url https://github.com/bugparty/ucr_cs141_gradescope_helper
- // @grant none
- // @license MIT
- // ==/UserScript==
-
-
- (function() {
- 'use strict';
- /*
- you need to apply for codeforce api key and secret
- */
- const apiKey = '123';
- const apiSecret = '123';
- console.log("inject started");
- async function signCodeforcesUrl(apiKey, apiSecret, methodName, params) {
- // Generate a random 6-character string
- const rand = [...window.crypto.getRandomValues(new Uint8Array(3))].map(b => b.toString(16).padStart(2, '0')).join('');
-
- // Current time in UNIX timestamp format
- const currentTime = Math.floor(Date.now() / 1000);
-
- // Adding apiKey and time to params
- params.apiKey = apiKey;
- params.time = currentTime;
-
- // Sort the parameters lexicographically by keys and values
- const sortedParams = Object.keys(params).sort().reduce((acc, key) => {
- acc.push(`${key}=${params[key]}`);
- return acc;
- }, []).join('&');
-
- // Concatenating the string as per the documentation
- const stringToHash = `${rand}/${methodName}?${sortedParams}#${apiSecret}`;
- //console.log("api string", stringToHash);
- // Encoding the string to a Uint8Array
- const encoder = new TextEncoder();
- const dataToHash = encoder.encode(stringToHash);
-
- // Hashing the string using SHA-512
- const hashBuffer = await window.crypto.subtle.digest('SHA-512', dataToHash);
- const hashArray = Array.from(new Uint8Array(hashBuffer)); // Convert buffer to byte array
- const hashHex = hashArray.map(b => b.toString(16).padStart(2, '0')).join(''); // Convert bytes to hex string
-
- // Forming the apiSig
- const apiSig = `${rand}${hashHex}`;
-
- // Creating the signed URL
- const signedUrl = `https://codeforces.com/api/${methodName}?${sortedParams}&apiSig=${apiSig}`;
-
- return signedUrl;
- }
- async function fetchData(url) {
- try {
- // Sending the HTTP request to the specified URL
- const response = await fetch(url);
-
- // Check if the response was successful
- if (!response.ok) {
- throw new Error(`HTTP error! Status: ${response.status}`);
- }
-
- // Parsing the JSON response into an object
- const data = await response.json();
-
- // Use the data object as needed
- console.log(data);
- return data;
- } catch (error) {
- console.error('Failed to fetch data:', error);
- }
- }
- function isValidInteger(str) {
- const invalid_val = {valid: false, val: -1};
- const regex = /^\s*\d+\s*$/;
- if(!regex.test(str)){
- return invalid_val;
- }
- const num = Number(str);
- return {valid: true, val: num};
- }
- $(document).ready(function(){
-
- const answers_id_queries = ["#question_41404944 > div:nth-child(3) > div:nth-child(4) > div > div > span",
- "#question_41404944 > div:nth-child(3) > div:nth-child(6) > div > div > span",
- "#question_41404944 > div:nth-child(3) > div:nth-child(8) > div > div > span",
- "#question_41404944 > div:nth-child(3) > div:nth-child(10) > div > div > span",
- "#question_41404944 > div:nth-child(3) > div:nth-child(12) > div > div > span"
- ];
- const report_title_selector = '#question_41404944_text_12';
- const anwers_titles = ["Lost in the Shuffle","Juice Box", "The Stairs", "Chocolate Frogs", "Wizard Chess"];
- const methodName = 'contest.status';
- const params = { contestId: '551192', asManager: true };
- 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>';
-
- let endOfTitle = $("#question_41404944 > div:nth-child(2)");
- console.log(endOfTitle);
- const button_title = " Fetch Status from codeforce";
- 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>');
- $("#bowman_super_btn").click(function() {
- console.log("super button clicked, starting working");
- $(this).prop('disabled', true); // Disable the button
- $(this).text('Please wait... if not resepond for 10seconds, refresh the page'); // Change button text
- signCodeforcesUrl(apiKey, apiSecret, methodName, params)
- .then(signedUrl => {
- console.log('Signed URL:', signedUrl)
- let submissions;
- fetchData(signedUrl)
- .then(data => {
- if (data.status === 'OK')
- submissions = data.result;
- else{
- $("#bowman_super_btn").text("codeforce return an invalid response:"+ data.status)
- return;
- }
- const user_cf_id = $("#question_41404944 > div:nth-child(3) > div:nth-child(2) > div > div > span").text().trim()
- console.log("user codeforce handle is ", user_cf_id);
-
- let answers_ids = [];
- let total_points = new Decimal(0);
- for(let i=0;i<answers_id_queries.length;i++){
- let element = $(answers_id_queries[i]);
- let the_id = element.text();
- console.log("problem ", anwers_titles[i], " id: ", the_id);
- let num_id = isValidInteger(the_id);
- answers_ids.push(num_id.val);
- if (num_id.valid){
- let found_submission = submissions.find(obj => obj.id === num_id.val);
- console.log(found_submission);
- let isSameUser = found_submission.author.members[0].handle === user_cf_id;
- if (!isSameUser) {
- element.append("❌ different user handle found: ", found_submission.author.members[0].handle);
- continue;
- }
- let problem_name = found_submission.problem.name;
- if (problem_name != anwers_titles[i]){
- element.append("❌ wrong problem: ", problem_name);
- continue;
- }
- if (found_submission.points == undefined){
- element.append("❌ no score found, verdict: ", found_submission.verdict);
- continue;
- }
-
- element.append(" ✅ point: ", found_submission.points, " handle:", found_submission.author.members[0].handle, " problem: ", problem_name);
- total_points = total_points.plus(found_submission.points);
-
-
- }else{//invalid submission
- element.text(the_id + " ❌ invalid submission id");
- }
- }
- //insert total score before Report
-
- let points_text = "total points: " + total_points + " subtract 5 by " + (Decimal.sub(5,total_points));
- let points_html = points_html_template.replace("%points%", points_text);
-
- if($('#bowman-total-points').length==0){
- $(report_title_selector).before(points_html);
- }else{
- $('#bowman-total-points > span').text(points_text);
- }
- $("#bowman_super_btn").text(button_title);
- $("#bowman_super_btn").prop('disabled', false);
-
-
- })
- .catch(error => {
- console.log(error);
- $("#bowman_super_btn").text(error + ' ' + button_title);
- $("#bowman_super_btn").prop('disabled', false);
- //reset scores
- if($('#bowman-total-points').length!=0){
- $('#bowman-total-points > span').text("no points available");
- }
- }
-
- );
- }
- )
- .catch(error => console.error('Error signing URL:', error));
- });
- });
- })();