// ==UserScript== // @name Canvas quiz essay answer submission download for Turnitin plagiarism check // @author WenChen Hol // @namespace https://github.com/clearnz/canvas-report-tools/ // @description For Canvas users, this tool generates a zip file download of the all quiz essay answers, for Turnitin to check for plagiarism // @downloadURL https://github.com/clearnz/canvas-report-tools/raw/master/quiz-essay-answers.user.js // @include https://*/courses/*/quizzes/* // @require https://ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js // @require https://ajax.googleapis.com/ajax/libs/jqueryui/1.12.1/jquery-ui.min.js // @require https://flexiblelearning.auckland.ac.nz/javascript/filesaver.js // @require https://flexiblelearning.auckland.ac.nz/javascript/jszip.min.js // @version 0.6 // @grant none // ==/UserScript== // based on code from James Jones' Canvancement https://github.com/jamesjonesmath/canvancement (function () { 'use strict'; var userData = { }; // questionsArray to store questions //var questionsArray = []; // answersArray to store answers of each questions //var answersArray = []; var pending = - 1; var fetched = 0; var needsFetched = 0; var reporttype; var ajaxPool; var courseId; var quizId; var today = new Date(); var dd = today.getDate(); var mm = today.getMonth() + 1; var yyyy = today.getFullYear(); var zip = new JSZip(); var debug = 0; var resultUrlArray = []; if (dd < 10) { dd = '0' + dd; } if (mm < 10) { mm = '0' + mm; } //courseId = getCourseId(); // quizId = getQuizId(); today = (yyyy-2000 ) + '-' + mm + '-' + dd + '-' + Math.floor(Date.now() /1000) ; var aborted = false; addQuizEssayAnswersButton(); var ansId = 0; function addQuizEssayAnswersButton() { if ($('#quiz-answers-report').length === 0) { $('.page-action-list').append('
  • Quiz Essay Answers Download
  • '); $('#quiz-essay-answers-report').one('click', { type: 2 }, quizEssayAnswersReport); } return; } function abortAll() { for (var i = 0; i < ajaxPool.length; i++) { ajaxPool[i].abort(); } ajaxPool = [ ]; } function setupPool() { try { ajaxPool = [ ]; $.ajaxSetup({ 'beforeSend': function (jqXHR) { ajaxPool.push(jqXHR); }, 'complete': function (jqXHR) { var i = ajaxPool.indexOf(jqXHR); if (i > - 1) { ajaxPool.splice(i, 1); } } }); } catch (e) { throw new Exception('Error configuring AJAX pool'); } } function quizEssayAnswersReport(e) { //gets the student list pending = 0; fetched = 0; reporttype = e.data.type; aborted = false; setupPool(); courseId = getCourseId(); quizId = getQuizId(); if (debug) console.log( courseId, quizId ); var url = '/api/v1/courses/' + courseId + '/sections?include[]=students&per_page=50'; //https://auckland.test.instructure.com:443/api/v1/courses/41929/quizzes/37835/submissions?include[]=submission progressbar(); pending = 0; getStudents( url, courseId, quizId ); //getAnswers( url, courseId, quizId ); } function nextURL(linkTxt) { //if more than 100 students, gets the URL for the rest of the list var url = null; if (linkTxt) { var links = linkTxt.split(','); var nextRegEx = new RegExp('^<(.*)>; rel="next"$'); for (var i = 0; i < links.length; i++) { var matches = nextRegEx.exec(links[i]); if (matches) { url = matches[1]; } } } return url; } function getStudents( url, courseId, quizId ) { //cycles through the student list try { if (aborted) { throw new Error('Aborted'); } jQuery("#doing").html( "Fetching student informaton " ); pending++; $.getJSON(url, function (udata, status, jqXHR) { url = nextURL(jqXHR.getResponseHeader('Link')); for (var i = 0; i < udata.length; i++) { var section = udata[i]; //return if no students if ( i==0 && section.students===null ){ pending--; continue; /*alert( "No student found" ); $('#jj_progress_dialog').dialog('close'); $('#quiz-essay-answers-report').one('click', { type: 2 }, quizEssayAnswersReport); resetData(); throw new Error('Failed to load list of students');*/ } try { if (section.students.length > 0) { for (var j = 0; j < section.students.length; j++) { // login_id === upi var user = section.students[j]; var splitname = user.sortable_name.split(','); user.firstname = splitname[1].trim(); user.surname = splitname[0].trim(); userData[user.id] = user; } // end for } // end if length>0 } catch(e){ continue; } } if (url) { getStudents( url, courseId, quizId ); } pending--; if (pending <= 0) { let urlAns = '/api/v1/courses/' + courseId + '/quizzes/'+ quizId + '/submissions?include[]=submission&per_page=50'; getAnswers( urlAns, courseId, quizId ); } }).fail(function () { pending--; $('#jj_progress_dialog').dialog('close'); throw new Error('Failed to load list of students'); }); } catch (e) { errorHandler(e); $('#jj_progress_dialog').dialog('close'); } } function getAnswers( url, courseId, quizId ) { //cycles through the student list let quiz_submissions = []; let tmpName = ""; try { if (aborted) { throw new Error('Aborted'); } if (debug) console.log( "student Data:", userData ); if (debug) console.log( "getting answers:", url ); jQuery("#doing").html( "Fetching question answers " ); pending++; $.getJSON(url, function (udata, status, jqXHR) { url = nextURL(jqXHR.getResponseHeader('Link')); quiz_submissions = udata.quiz_submissions; if (debug) console.log( "quiz_submissions:", quiz_submissions ); if ( quiz_submissions.length==0 || quiz_submissions===null ){ pending--; alert( "No submission found" ); $('#jj_progress_dialog').dialog('close'); $('#quiz-essay-answers-report').one('click', { type: 2 }, quizEssayAnswersReport); resetData(); throw new Error('Failed to load list of students'); } needsFetched = quiz_submissions.length; for (var i = 0; i < quiz_submissions.length; i++) { var submission = quiz_submissions[i]; let studentid = submission.user_id; progressbar(i, quiz_submissions.length); if (debug) console.log( submission, studentid ); try { tmpName = userData[ studentid ].short_name.replace(/ /g, ""); } catch(e){ tmpName = ""; continue; } if (debug) console.log( "submission:", submission ); if (debug) console.log( "tmpName:", tmpName ); try { if ( "result_url" in submission && submission.result_url!="" ) { resultUrlArray.push( [ submission.result_url, tmpName+"-"+studentid ] ); } else if ( "html_url" in submission && submission.html_url!="" ) { resultUrlArray.push( [submission.html_url, tmpName+"-"+studentid] ); } } catch(e){ continue; } } if (url) { getAnswers( url, courseId, quizId ); } pending--; if (pending <= 0) { if (debug) console.log( "resultUrlArray", resultUrlArray ); getQuizAnswerReport( courseId, quizId ); } }).fail(function () { pending--; throw new Error('Failed to load student submissions'); }); } catch (e) { errorHandler(e); } } function getQuizAnswerReport( courseId, quizId ) { //cycles through student list pending = 0; fetched = 0; let url = ""; let tmpName= ''; getQuizAns(); //for (var i=0; i" ); //pending++; progressbar(fetched, needsFetched); $.get(url, function (adata, status, jqXHR) { fetched +=1; let totalAns = ""; jQuery(adata).find('.quiz_response_text').each( function(){ var tmpAns = jQuery( this ).text().trim(); if (debug) console.log( "answer:", tmpAns); if ( tmpAns == "" ){ //pending--; return; } let tmpQuestionObj = jQuery( this ).closest('.question').find('.question_text')[0]; let tmpQuestion = jQuery(tmpQuestionObj).text().trim(); if (debug) console.log( "question:", tmpQuestion ); if (tmpQuestion=="") { //pending--; //return; } //quotation around question to avoid turnitin totalAns += '"Question:' + tmpQuestion + '"\n' + tmpAns + "\n"; progressbar(fetched, needsFetched); } ); savename = tmpName + '-course-' + courseId + '.txt'; if (debug) console.log( "savename:", savename ); if (debug) console.log( "totalAns:", totalAns ); zip.file( savename, totalAns ); pending--; if (debug) console.log( "get quizAnswers pending:", pending ); ansId+=1; if (ansId>=resultUrlArray.length && !aborted) { makeReport( courseId, quizId ); }else{ getQuizAns(); } }).fail(function () { pending--; fetched+=50; progressbar(fetched, needsFetched); ansId+=1; if (!aborted) { console.log('Some report data failed to load'); } }); } catch (e) { errorHandler(e); } } function getCourseId() { //identifies course ID from URL var courseId = null; if (debug) console.log( "in getCourseId: window.location", window.location.href ); try { var courseRegex = new RegExp('/courses/([0-9]+)'); var matches = courseRegex.exec(window.location.href); if (matches) { courseId = matches[1]; } else { throw new Error('Unable to detect Course ID'); } } catch (e) { errorHandler(e); } return courseId; } function getQuizId() { //identifies quiz ID from URL var quizId = null; if (debug) console.log( "in getQuizId: window.location", window.location.href ); try { var quizRegex = new RegExp('/quizzes/([0-9]+)'); var matches = quizRegex.exec(window.location.href); if (matches) { quizId = matches[1]; } else { throw new Error('Unable to detect quiz ID'); } } catch (e) { errorHandler(e); } return quizId; } function makeReport( courseId, quizId ) { //generates CSV of data var csv; var quizTitle=""; var courseTitle=""; var tmpArr = []; try { courseTitle=document.title.split( ":" ).slice(-1)[0].replace(/[^\w]/g, ""); quizTitle=document.title.split( ":" )[0].replace(/[^\w]/g, ""); } catch(e){} //try { if (aborted) { console.log('Process aborted'); aborted = false; return; } progressbar(); zip.generateAsync({type:"blob"}) .then(function(content) { // Force down of the Zip file saveAs(content, courseTitle+"-"+ quizTitle+ "-" + today + ".zip"); } ); $('#quiz-essay-answers-report').one('click', { type: 2 }, quizEssayAnswersReport); resetData(); //} catch (e) { // errorHandler(e); //} } function progressbar(x, n) { try { if (typeof x === 'undefined' || typeof n == 'undefined') { if ($('#jj_progress_dialog').length === 0) { $('body').append('
    '); $('#jj_progress_dialog').append('
    It may take a few mins for large courses
    '); $('#jj_progress_dialog').dialog({ 'title': 'Fetching Report', 'autoOpen': false, 'buttons': [ { 'text': 'Cancel', 'click': function () { $('#quiz-submissions-report').one('click', { type: 2 }, quizEssayAnswersReport ); if (debug) console.log( "done set submission report link" ); $(this).dialog('close'); aborted = true; abortAll(); resetData(); } } ] }); } if ($('#jj_progress_dialog').dialog('isOpen')) { $('#jj_progress_dialog').dialog('close'); } else { $('#jj_progressbar').progressbar({ //'value': false 'value': 0 }); $('#jj_progress_dialog').dialog('open'); } } else { if (!aborted) { var val = n > 0 ? Math.round(100 * x / n) : false; $('#jj_progressbar').progressbar('option', 'value', val); } } } catch (e) { errorHandler(e); } } function resetData(){ //questionsArray = []; // answersArray to store answers of each questions //answersArray = []; resultUrlArray = []; pending = - 1; fetched = 0; needsFetched = 0; } function errorHandler(e) { console.log(e.name + ': ' + e.message); } }) ();