Grab Answer Key from Short Answer Google Forms - google-apps-script

I am attempting to grab the answer key from the short answer in Google forms.
I am attempting to grab the following answer key from a short answer question (refer to the screenshot below). Below the Short answer text, it shows you the correct answer.
However, when reading the documentation, there seems there is no way to do so. Is there an alternative way to grab the answer key from the short answer in Google Forms?
The following is the script I am using to grab the information from the quiz:
function extractFormData(fullNme) {
var form = FormApp.getActiveForm();
var items = form.getItems();
var quizAdded = new Date().toLocaleString("en-US");
var uniqueQuizID = Utilities.getUuid(), question_listID = uniqueQuizID.concat("-questions");
var constructedJSON = {};
var answer_val = false;
var errorJSON = {};
var email = form.getEditors()[0].getEmail();
var quizInfo = {
"quiz_name": form.getTitle(),
"quiz_owner": fullNme,
"quiz_description": form.getDescription(),
"quiz_owner_email": email,
"quiz_submited_date":quizAdded
};
//console.log(JSON.stringify(quizInfo));
for (var i = 0; i < items.length; i++) {
var item = items[i];
switch(item.getType()) {
case FormApp.ItemType.MULTIPLE_CHOICE:
var question = item.asMultipleChoiceItem();
var ques = question.getTitle();
var question_type = "Multiple Choice";
var optns = [];
var answr;
var answers = question.getChoices();
answer_val = false;
for (var j = 0; j < answers.length; j++) {
var clean = answers[j].getValue();
optns.push(clean);
if(answers[j].isCorrectAnswer()){
answr = answers[j].getValue();
for(var x = 0; x < optns.length; x++){
if(answr == optns[x]){
answer_val = true;
break;
}
}
}
}
var multiJSON = makeJSON(ques, question_type, optns, answr);
constructedJSON[i+1] = multiJSON;
break;
case FormApp.ItemType.CHECKBOX:
var question = item.asCheckboxItem();
var ques = question.getTitle();
var question_type = "CheckBox";
var optns = [];
var answr = [];
var answers = question.getChoices();
for (var j = 0; j < answers.length; j++) {
var clean = answers[j].getValue();
optns.push(clean);
if(answers[j].isCorrectAnswer()){
answr.push(answers[j].getValue());
}
}
var checkJSON = makeJSON(ques, question_type, optns, answr);
constructedJSON[i+1] = checkJSON;
break;
case FormApp.ItemType.PARAGRAPH_TEXT:
var question = item.asParagraphTextItem();
var ques = question.getTitle();
var question_type = "free response";
var optns = [];
var answr;
var paraJSON = makeJSON(ques, question_type, optns, answr);
constructedJSON[i+1] = paraJSON;
break;
case FormApp.ItemType.TEXT:
var question = item.asTextItem();
var ques = question.getTitle();
var question_type = "free response";
var optns = "";
var answr = "";
var textJSON = makeJSON(ques, question_type, optns, answr);
constructedJSON[i+1] = textJSON;
break;
}
if(!answer_val){
errorJSON = {"Question":ques, "Options":optns, "Answer": answr, "Sucess": false};
//error(ques);
break;
}
}
if(answer_val){
notifyUser();
} else {
return errorJSON;
}
}

The method that you are looking for is not yet supported in Apps Script.
Therefore, you have two options in this situation:
Create a feature request on Google's Issue Tracker here and provide all the necessary details.
Make use of Forms API by using the Forms API advanced service in Apps Script and try Tanaike's proposed solution.
If you check the documentation, you can see that resources of type CorrectAnswer have a field available:
{
"value": string
}
A single correct answer for a question. For multiple-valued (CHECKBOX) questions, several CorrectAnswers may be needed to represent a single correct response option.
Reference
Forms API.

I believe your goal is as follows.
You want to retrieve the correct answer from each question of Google Form using Google Apps Script.
In this case, when I checked the method for directly achieving your goal using Google Form service (FormApp), unfortunately, I couldn't find it. But fortunately, I confirmed that when Google Forms API is used, your goal can be achieved. So, in this answer, I would like to propose to achieve your goal using Google Forms API.
Usage:
1. Linking Google Cloud Platform Project to Google Apps Script Project for New IDE.
In order to use Forms API, please link Google Cloud Platform Project to Google Apps Script Project for New IDE, and please enable Forms API at API console. Ref
2. Sample script.
function myFunction() {
const formId = "###"; // Please set your Google Form ID.
const url = "https://forms.googleapis.com/v1/forms/" + formId + "?fields=*";
const res = UrlFetchApp.fetch(url, { headers: { authorization: "Bearer " + ScriptApp.getOAuthToken() } });
const obj = JSON.parse(res.getContentText());
const values = obj.items.map(({ title, questionItem }) => (
{ title, point: questionItem.question.grading.pointValue, answer: questionItem.question.grading.correctAnswers ? questionItem.question.grading.correctAnswers.answers.map(({ value }) => value) : [] }
));
console.log(values)
}
In this script, please include the scope of https://www.googleapis.com/auth/forms.body.readonly.
3. Testing.
When this script is run to the Google Form, the following sample value is obtained.
[
{"title":"sample question 1","point":5,"answer":["sample1","sample2"]},
{"title":"sample question 2","point":3,"answer":["Option 2","Option 3"]},
{"title":"sample question 3","point":3,"answer":["Option 2"]}
]
This is a sample value.
Note:
When you want to simply check this, you can also use "Try this method" of Forms API.
When the correct answer is not set, an error occurred. So I modified it. By this, when the correct answer is not set, [] is returned.
References:
Linking Google Cloud Platform Project to Google Apps Script Project for New IDE
Method: forms.get

Related

How to continue running the script even if a user exists in Google Group using google scripts?

I need help with Google groups. The code currently checks if a member already exists but it stop when it does find one. How can I modify the code to allow it to process the next row without stopping?
TIA!
function updateGroup() {
const s = SpreadsheetApp.openById("ID HERE");
const sheet_name = s.getSheetByName("REPORT HERE");
const sheet_data = sheet_name.getRange(2,1,sheet_name.getLastRow(),sheet_name.getLastColumn());
const sheet_dataVal = sheet_data.getValues();
for (var i = 0; i < sheet_dataVal.length-1; i++) {
var member_Email = sheet_dataVal[i][9]; // REWS Projects Email Address
var groupEmail = "GROUP EMAIL ADDRESS HERE";
var member_Role = "MEMBER";
var member_Type = "USER";
var group = GroupsApp.getGroupByEmail(groupEmail);
var comment = sheet_dataVal[i][13];
if (comment === "Member Added to Group" || comment === "Member already exists") {continue;}
var checkMembers = AdminDirectory.Members.list(groupEmail).members
for (var m in checkMembers) {
if (checkMembers[m].email == member_Email);
return sheet_name.getRange(i+2,14).setValue("Member already exists");
}
addNewMembersToGroup(member_Email,member_Role,groupEmail,i,sheet_name);
}
}
function addNewMembersToGroup(member_Email,member_Role,groupEmail,i,sheet_name) {
/* Member does not exists in group, add */
var addNewMember = {
kind: "admin#directory#member",
email: member_Email,
role: member_Role
};
AdminDirectory.Members.insert(addNewMember,groupEmail);
sheet_name.getRange(i+2,14).setValue("Member Added to Group");
}
When you are using a return, the execution of the function is stopped - hence the issue you are getting.
In order to fix this, you should remove the return and have the if statement like this:
if (checkMembers[m].email == member_Email);
sheet_name.getRange(i+2,14).setValue("Member already exists");
Reference
JavaScript return statement.

.getEditors() returns 'DriveUser,DriveUser'

Before adding new editors to a Google drive folder, I'd like to check first which ones already exist in that folder's editors. This is to avoid unnecessary share notification if user is already an existing editor.
However, .getEditors() always returns 'DriveUser,DriveUser' so all editors get added even if existing already.
Thanks to advise if you have a solution to this.
Here's my code:
var dropboxID = "zzxxccvv112233";
var folderList = DriveApp.getFolderById(dropboxID).getFoldersByName(employee);
if (folderList.hasNext()) {
var employeeFolder = folderList.next(); //folder already exists so just add new Editors, if any
var currentEditors = employeeFolder.getEditors();
Logger.log("currentEditors = " + currentEditors);
var newEditors = emailTo + "," + emailCc;
newEditors = newEditors.replace(/\s/g, '');
newEditors = newEditors.split(',');
var editorsToAdd = [];
for (var i=0 ; i<newEditors.length ; i++) {
if (currentEditors.indexOf(newEditors[i]) < 0) {
editorsToAdd.push(newEditors[i]);
}
}
employeeFolder.addEditors(editorsToAdd);
} else {
and Logger shows this:
[20-03-05 21:23:46:637 HKT] currentEditors = DriveUser,DriveUser
You should be comparing by email address with User.getEmail()
// Get all of the current editor emails in one array
var currentEditorEmails = currentEditors.map(function (editor) { return editor.getEmail() });
// Check if the new editor emails exist in the currentEditorEmails array
for (var i=0 ; i<newEditors.length ; i++) {
if (currentEditorEmails.indexOf(newEditors[i]) < 0) {
editorsToAdd.push(newEditors[i]);
}
}
If you're using V8, you could use an arrow function instead.
const currentEditorEmails = currentEditors.map(editor => editor.getEmail());

GmailApp (Google Apps Script) Displays Inline Images as Attachments

Hello friendly StackOverflow folks,
I am having the most difficult time getting the GmailApp sendEmail() method to successfully use an existing email (e.g. a draft) that contains images inline as a template for new messages.
It seems like this is a problem, as I have found devs having this problem here and here and proposed solutions here, here, and here. Each of these solutions is 4+ years old, so perhaps they're out of date. I have been unable to use any of these solutions to replicate a success.
Currently, I'm running this code from my Google Scripts backend:
function generateMessageFromTemplate () {
var selectedTemplate = GmailApp.getMessageById('MESSAGE_ID');
//////////////////////////////////////////////////////////////////////////////
// Get inline images and make sure they stay as inline images (via Romain Vialard)
//////////////////////////////////////////////////////////////////////////////
var emailTemplate = selectedTemplate.getBody();
var rawContent = selectedTemplate.getRawContent();
var attachments = selectedTemplate.getAttachments();
var regMessageId = new RegExp(selectedTemplate.getId(), "g");
if (emailTemplate.match(regMessageId) != null) {
var inlineImages = {};
var nbrOfImg = emailTemplate.match(regMessageId).length;
var imgVars = emailTemplate.match(/<img[^>]+>/g);
var imgToReplace = [];
if(imgVars != null){
for (var i = 0; i < imgVars.length; i++) {
if (imgVars[i].search(regMessageId) != -1) {
var id = imgVars[i].match(/realattid=([^&]+)&/);
if (id != null) {
var temp = rawContent.split(id[1])[1];
temp = temp.substr(temp.lastIndexOf('Content-Type'));
var imgTitle = temp.match(/name="([^"]+)"/);
if (imgTitle != null) imgToReplace.push([imgTitle[1], imgVars[i], id[1]]);
}
}
}
}
for (var i = 0; i < imgToReplace.length; i++) {
for (var j = 0; j < attachments.length; j++) {
if(attachments[j].getName() == imgToReplace[i][0]) {
inlineImages[imgToReplace[i][2]] = attachments[j].copyBlob();
attachments.splice(j, 1);
var newImg = imgToReplace[i][1].replace(/src="[^\"]+\"/, "src=\"cid:" + imgToReplace[i][2] + "\"");
emailTemplate = emailTemplate.replace(imgToReplace[i][1], newImg);
}
}
}
}
//////////////////////////////////////////////////////////////////////////////
GmailApp.sendEmail('test#email.com', selectedTemplate.getSubject(), '', {
attachments: attachments,
htmlBody: emailTemplate,
inlineImages: inlineImages
});
};
The Google Scripts documentation on the sendEmail() method is here.
This is the Output of this Function as Is
When I send emails from Apps Script as is, I get emails that look like this:
screenshot
I've replicated the test with an old yahoo.com email account and had the exact same results as a Gmail account.
Again, this dev also has this same issue.
If you can help, I would be extremely grateful!

Remove notes in Google Slides using Google Apps Script and Slides API

I'm trying to write a function to delete notes from Google Slides presentation using Google Apps Script.
I went through the examples and assume I need to match that to something like https://developers.google.com/slides/samples/writing#delete_a_page_or_page_element by calling speakers notes using https://developers.google.com/slides/how-tos/notes, but I can't make the link.
New to Google Apps Script, any help appreciated.
Thanks for the initial script!
Unfortunately it didn't work for me, and after several attempts, I've made it work:
function clearNotes(){
var presentation = SlidesApp.getActivePresentation();
var presentationId = presentation.getId();
var slides = presentation.getSlides();
var requests = [];
slides.forEach(function(slide, i) {
var slideNote = slide.getObjectId();
var slideNotesPage = slide.getNotesPage();
var shape = slideNotesPage.getSpeakerNotesShape();
var shapeText = shape.getText();
if(shapeText != undefined){
shapeText.clear();
}
})
if(requests.length > 0){
var batchUpdateResponse = Slides.Presentations.batchUpdate({requests: requests}, presentationId);
}
}
As a newbie, it involved lots of try and error, debug and follow the guide here: https://developers.google.com/apps-script/reference/slides/text-range.html#clear()
So far, it's the only solution I've found to batch delete all notes in a Google Slides presentation.
Hope this helps,
Rafa.
Here is how I did it.
function clearNotes(){
var presentation = SlidesApp.getActivePresentation();
var presentationId = presentation.getId();
var slides = presentation.getSlides();
var requests = [];
slides.forEach(function(slide, i) {
var slideNote = Slides.Presentations.Pages.get(presentationId, slide.getObjectId());
var slideNoteId = JSON.parse(slideNote).slideProperties.notesPage.notesProperties.speakerNotesObjectId;
var slideNotesPage = JSON.parse(slideNote).slideProperties.notesPage;
var shapeText = slideNotesPage.pageElements[1].shape.text;
if(shapeText != undefined){
requests.push({
deleteText: {objectId: slideNoteId,textRange:{type: 'ALL'}}
});
}
})
if(requests.length > 0){
var batchUpdateResponse = Slides.Presentations.batchUpdate({requests: requests}, presentationId);
}
}
Hope it helps.
Google has now integrated this option into the Slides program. When you make a copy you can choose/check to include notes or not.

Is there a limit to how many G-Forms i can link to a singe google spreadshee?

I have a script that is designed to create about 120 forms and link them to a single spreadsheet where I will analyze the data. I don't have any issues with the script until my spreadsheet has about a dozen forms linked to it. Then I get an error saying the destination ID is invalid, after logging the id, and entering it manually into a url I see no issues with the ID....
var ssSummaryId = '******redacted*******';
var form = FormApp.create('RM#' + rmNumber).setDestination(FormApp.DestinationType.SPREADSHEET, ssSummaryId);
form.setDescription(valuesSummary[ii][2])
.setConfirmationMessage('Thanks for the update on room ' + rmNumber)
.setShowLinkToRespondAgain(false);
// form.setDestination(FormApp.DestinationType.SPREADSHEET, ssSummaryId);
var formId = form.getId();
var formUrl = form.getPublishedUrl();
EDIT I'm adding my complete script and some additional info, just in case someone wants to check my code out and point out all it rookie mistakes.
I'm using Google scripts to build a spreadsheet and then create 120 slightly altered Google forms that are linked to a single spreadsheet, all the responses are by design on separate sheets named "form responses n". I consistently hit a wall once I exceed 10 forms linked to one sheet. Note; in initial testing I remember having a spreadsheet with 46 forms (and therefore sheets) linked to it. As you can see in the code below, I have the app restart from where it's left off after every 5 forms are created, so I'm not getting any 'extended runtime errors". Just the error below, typically after the script runs twice from Google Scripts IDE.
I'm just finally getting the hand of basic javascript after years of using and modifying js in web developing. So I apologize in advanced for the poor code.
Failed to set response destination. Verify the destination ID and try again. (line 54, file "Code")
function spreadsheet_builder() {
var ssSummaryId = '<<REDACTED>>';
var ssFormDataId = '<<REDACTED>>';
var batchSize = 5;
var buildStatus = false;
var ssSummary = SpreadsheetApp.openById(ssSummaryId);
SpreadsheetApp.setActiveSpreadsheet(ssSummary);
if (ssSummary.getSheetByName('Summary') == null) {
var sheetSummary = ssSummary.getSheetByName('Sheet1');
} else {
var sheetSummary = ssSummary.getSheetByName('Summary');
}
var rangeSummary = sheetSummary.getDataRange();
var valuesSummary = rangeSummary.getValues();
buildStatus = get_last_position(valuesSummary, buildStatus); //either returns last position in array or 'true' if task is complete
if (buildStatus != true || buildStatus > 0) {
var formCreation = [];
var formData = get_form_data(ssFormDataId); // Get form questions from form Data ss, might be better to keep everything on the same sheet
batchSize = buildStatus + batchSize;
for ( var ii = buildStatus; ii < batchSize; ii++ ) {
if (valuesSummary[ii][1] != '') {
var formCreationReturn = form_builder(formData, valuesSummary, ii, ssSummaryId);
formCreation.push(formCreationReturn);
}
}
var aSum = [ssSummary, sheetSummary, rangeSummary];
final_storing_operation(formCreation, aSum, buildStatus);
}
if (sheetSummary.getName() != 'Summary') {
SpreadsheetApp.setActiveSpreadsheet(ssSummary);
sheetSummary.activate().setName('Summary');
sheetSummary.setFrozenColumns(3);
sheetSummary.setFrozenRows(1);
sheetSummary.hideColumns(1);
//var sSumIndex = sheetSummary.getIndex();
}
SpreadsheetApp.setActiveSpreadsheet(ssSummary);
sheetSummary.activate();
ssSummary.moveActiveSheet(1);
}
function form_builder(formData, valuesSummary, ii, ssSummaryId) {
var lastFormCreated = ii;
var rmNumber = valuesSummary[ii][1];
var form = FormApp.create('RM#' + rmNumber).setDestination(FormApp.DestinationType.SPREADSHEET, ssSummaryId);
form.setDescription(valuesSummary[ii][2])
.setConfirmationMessage('Thanks for the update on room ' + rmNumber)
.setShowLinkToRespondAgain(false);
// form.setDestination(FormApp.DestinationType.SPREADSHEET, ssSummaryId);
var formId = form.getId();
var formUrl = form.getPublishedUrl();
var sectionHeader = 'SECTION_HEADER'; //preformatted form question types.
var list = 'LIST';
var paragraphText = 'PARAGRAPH_TEXT';
for (var j = 1; j < formData.length; j++) { //top row is header
switch (formData[j][0]) {
case sectionHeader:
form.addSectionHeaderItem().setTitle(formData[j][1]);
break;
case list:
var item = form.addListItem();
item.setTitle(formData[j][1]).setHelpText(formData[j][2]);
item.setChoices([
item.createChoice(formData[j][3]),
item.createChoice(formData[j][4]),
item.createChoice(formData[j][5])
]);
break;
case paragraphText:
form.addParagraphTextItem().setTitle(formData[j][1]);
break;
default:
form.addSectionHeaderItem().setTitle('OOPS u\'don MESSED up');
break;
}
}
return [formId, formUrl, lastFormCreated, rmNumber];
}
function final_storing_operation(formCreation, aSum, buildStatus) {
SpreadsheetApp.setActiveSpreadsheet(aSum[0]);
aSum[1].activate();
var startRow = formCreation[0][2] + 1;
var newRange = aSum[1].getRange(startRow, 1, formCreation.length, 2); //row, clmn, rows, columns
var newValues = [];
for ( var ij = 0; ij < formCreation.length; ij++) {
var values = [formCreation[ij][0], "\=HYPERLINK(\"" + formCreation[ij][1] + "\", \"RM#" + formCreation[ij][3] + "\")"];
newValues.push(values);
}
newRange.setValues(newValues);
}
function get_last_position (valuesSummary, buildStatus) {
var rowPos = 1; // start at 1 to ignore headers
while (valuesSummary[rowPos][1] != '') {
if (valuesSummary[rowPos][0] == '') {
return rowPos;
}
rowPos++;
}
if(valuesSummary[rowPos][0] != '' && valuesSummary[rowPos][1] != '') {
buildStatus = true;
return buildStatus;
}
}
function get_form_data (ssFormDataId) {
var ssFormData = SpreadsheetApp.openById(ssFormDataId);
SpreadsheetApp.setActiveSpreadsheet(ssFormData);
var sheetFormData = ssFormData.getSheets()[0];
var rangeFormData = sheetFormData.getDataRange();
var valuesFormData = rangeFormData.getValues();
return valuesFormData;
}
As an alternative, you could create the forms, and intentionally not link them to a spreadsheet, then have some code that looped through every form, and extracted the data. You'd probably want to put the forms into their own folder.
Or, you'd need to build a form with Apps Script HTML Service, embed it into a Apps Script Gadget in a Google Site, and have everyone fill out the form from Sites.