Skip to content

Instantly share code, notes, and snippets.

@idoleat
Created January 20, 2024 03:36
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save idoleat/e02367bbc682a4a89ec204c4869300bf to your computer and use it in GitHub Desktop.
Save idoleat/e02367bbc682a4a89ec204c4869300bf to your computer and use it in GitHub Desktop.
version 3
let HOMEWORK_NOTE_ID = 'your note id';
let HACKMD_API_KEY = 'your api key';
let EMAIL = {
'recipient': '',
'subject': 'Thanks for handing in your home work!',
'body': ''
}
let PUBLISHED_LINKS = []; // kind of ugly but it works
function setupForm() {
let currentForm = FormApp.getActiveForm();
let usernameValidation = FormApp.createTextValidation() // To prevent injection
.setHelpText('Please provide a valid usename')
.requireTextMatchesPattern('[a-zA-Z0-9\-_]+')
.build();
let noteValidation = FormApp.createTextValidation()
.setHelpText('Please provide a valid hackmd note link')
.requireTextMatchesPattern('https:\/\/hackmd\.io\/[a-zA-Z0-9\-_]+')
.build();
let githubValidation = FormApp.createTextValidation()
.setHelpText('Please provide a valid github repository link')
.requireTextMatchesPattern('https:\/\/github\.com\/[a-zA-Z0-9\-_]+\/\[a-zA-Z0-9\-_]+')
.build();
let emailValidation = FormApp.createTextValidation()
.setHelpText('Please provide a valid email address')
.requireTextIsEmail()
.build();
let item = currentForm.addTextItem();
item.setTitle('username');
item.setRequired(true);
item.setValidation(usernameValidation);
item = currentForm.addTextItem();
item.setTitle('Note link 1');
item.setRequired(true);
item.setValidation(noteValidation);
item = currentForm.addTextItem();
item.setTitle('GitHub repo');
item.setRequired(true);
item.setValidation(githubValidation);
item = currentForm.addTextItem();
item.setTitle('Note link 2');
item.setRequired(true);
item.setValidation(noteValidation);
item = currentForm.addTextItem();
item.setTitle('Email');
item.setRequired(true);
item.setValidation(emailValidation);
// Optional. Deletes all triggers in the current project.
//let triggers = ScriptApp.getProjectTriggers(); // why sometimes get an error on no permission?
//for (var i = 0; i < triggers.length; i++) {
// ScriptApp.deleteTrigger(triggers[i]);
//}
ScriptApp.newTrigger('onSubmit')
.forForm(currentForm)
.onFormSubmit()
.create();
}
function onSubmit(e){
let reponses = e.response.getItemResponses();
let username = reponses[0].getResponse(); // should be sanitized
let note_1 = reponses[1].getResponse();
let repo = reponses[2].getResponse();
let note_2 = reponses[3].getResponse();
EMAIL.recipient = reponses[4].getResponse();
if(logError(checkNotePermission(note_1)) && logError(checkNotePermission(note_2))){
// second one won't be check if failed on the first one
let content_to_insert = ` * [開發紀錄](${PUBLISHED_LINKS[0]}) / [GitHub](${repo})\n * [開發紀錄 (Quiz)](${PUBLISHED_LINKS[1]})`;
logError(updateNote(HOMEWORK_NOTE_ID, username, content_to_insert)); // May fail
}
GmailApp.sendEmail(EMAIL.recipient, EMAIL.subject, EMAIL.body);
console.log('Email sent!');
}
function checkNotePermission(url){
console.log(`Check note: ${url}`);
let response;
let GET_options = {
'method': 'get',
'muteHttpExceptions': true,
'headers': {
'Authorization': `Bearer ${HACKMD_API_KEY}`
}
};
response = UrlFetchApp.fetch(`https://api.hackmd.io/v1/notes/${url.substring(18, url.length)}`, GET_options); // May fail
if(response.getResponseCode() !== 200){
return response.getResponseCode();
}
let body = JSON.parse(response);
if(body['readPermission'] !== 'guest'){ // should be reported at the first check though
return 10;
}
if(body['writePermission'] !== 'signed_in'){
return 11;
}
if(body['publishedAt'] === null){
return 12;
}
PUBLISHED_LINKS.push(body['publishLink']);
console.log('All check passed!'); // I don't know why this won't show
return 200;
}
/// @noteID: String
function updateNote(noteID, username, linksStr){
let reponse;
let data = {
'content': '',
'readPermission': '',
'writePermission': '',
'permalink': ''
}
let GET_options = {
'method': 'get',
'muteHttpExceptions': true,
'headers': {
'Authorization': `Bearer ${HACKMD_API_KEY}`
}
};
reponse = UrlFetchApp.fetch(`https://api.hackmd.io/v1/notes/${noteID}`, GET_options); // May fail
if(reponse.getResponseCode() !== 200){
return 13;
}
let body = JSON.parse(reponse);
data.readPermission = body.readPermission;
data.writePermission = body.writePermission;
data.permalink = body.permalink;
// Check if ID existed. Update or Add accordingly.
let pattern = new RegExp(username);
let result = body.content.match(pattern);
if(result === null){
// Add an entry to buttom
data.content = body.content + `- [ ] ${username}\n${linksStr}\n`;
console.log(`Note updated with new entry as: ${data.content}`);
}
else if(result.length === 1){
// Update the existed links
const toReplace = new RegExp(`- \\[ \\] ${username}\n.*\n.*\n`); // wildcard does not include \n
data.content = body.content.replace(toReplace, `- [ ] ${username}\n${linksStr}\n`);
console.log(`Note updated as: ${data.content}`);
}
else{
console.log('WTF');
// cases that other that 0 or 1 is unexpected and should be unreachable
return 14;
}
let PATCH_options = {
'method': 'patch',
'muteHttpExceptions': true,
'contentType': 'application/json',
'payload' : JSON.stringify(data),
'headers': {
'Authorization': `Bearer ${HACKMD_API_KEY}`
}
};
reponse = UrlFetchApp.fetch(`https://api.hackmd.io/v1/notes/${noteID}`, PATCH_options); // May fail
if(reponse.getResponseCode() !== 202){
return 15;
}
console.log('Updated successfully.');
return 200;
}
function logError(result){
switch(result){
case 200:
console.log('ok. continue');
return true;
case 403:
console.log('We do not have permisison to view the note.');
EMAIL.body += 'We do not have permisison to view the note.';
break;
case 404:
console.log('We can not find your note.');
EMAIL.body += 'We can not find your note.';
break;
case 10:
console.log('Read permission should be set to everyone.');
EMAIL.body += 'Read permission should be set to everyone.';
break;
case 11:
console.log('Write permission should be set to signed-in user.');
EMAIL.body += 'Write permission should be set to signed-in user.';
break;
case 12:
console.log('Note should be published.');
EMAIL.body += 'Note should be published.';
break;
case 13:
console.log('Not responded with 200 while getting the note for listing homeworks.');
EMAIL.body += 'Not responded with 200 while getting the note for listing homeworks.';
break;
case 14:
console.log('More than one entry in the note with same username');
EMAIL.body += 'More than one entry in the note with same username';
break;
case 15:
console.log('Not responded with 202 while updating the note.');
EMAIL.body += 'Not responded with 202 while updating the note.';
break;
default:
console.log(`Unexpected error code ${result}. Please report this problem.`);
EMAIL.body += `Unexpected error code ${result}. Please report this problem.`;
}
return false;
}
// Sometimes it won't work, with no error message attached.....
function test(){
let currentForm = FormApp.getActiveForm();
const formResponse = currentForm.createResponse();
const items = currentForm.getItems();
formResponse.withItemResponse(items[0].asTextItem().createResponse('superman4'));
formResponse.withItemResponse(items[1].asTextItem().createResponse('https://hackmd.io/qEM29uBkQ3C-iXSFdcPa6w'));
formResponse.withItemResponse(items[2].asTextItem().createResponse('https://github.com/idoleat/Christmas-card'));
formResponse.withItemResponse(items[3].asTextItem().createResponse('https://hackmd.io/qEM29uBkQ3C-iXSFdcPa6w'));
formResponse.withItemResponse(items[4].asTextItem().createResponse('idoleat@ccns.ncku.edu.tw'));
formResponse.submit();
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment