Skip to content

Instantly share code, notes, and snippets.

@idoleat
Last active December 26, 2023 15:14
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/32ca51fe5d4a433a4f76b2b4958d1507 to your computer and use it in GitHub Desktop.
Save idoleat/32ca51fe5d4a433a4f76b2b4958d1507 to your computer and use it in GitHub Desktop.
version 1
let HOMEWORK_NOTE_ID = 'your note id';
let HACKMD_API_KEY = 'your key';
let EMAIL = {
'recipient': '',
'subject': 'Thanks for handing in your home work!',
'body': ''
}
function onOpen(e) {
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 published note link')
.requireTextMatchesPattern('https:\/\/hackmd\.io\/@[a-zA-Z0-9\-_]+\/\[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();
const items = currentForm.getItems();
items[0].asTextItem().setValidation(usernameValidation);
items[1].asTextItem().setValidation(noteValidation);
items[2].asTextItem().setValidation(githubValidation);
items[3].asTextItem().setValidation(noteValidation);
console.log(`Text validation set on questions`);
// 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(emailResult(checkNotePermission(note_1)) && emailResult(checkNotePermission(note_2))){
// second one won't be check if failed on the first one
let content_to_insert = ` * [開發紀錄](${note_1}) / [GitHub](${repo})\n * [開發紀錄 (Quiz)](${note_2})`;
emailResult(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}`
}
};
//do{
// response = UrlFetchApp.fetch(`${url}/edit`, {'muteHttpExceptions': true});
//}
//while(response.getResponseCode() !== 302);
response = UrlFetchApp.fetch(`${url}/edit`, {'muteHttpExceptions': true}); // May 403 (even with public note)
if(response.getResponseCode() !== 302){
return response.getResponseCode();
}
let noteID = response.getHeaders()['hackmd-note-id'];
console.log(`Note ID: ${noteID}`);
response = UrlFetchApp.fetch(`https://api.hackmd.io/v1/notes/${noteID}`, GET_options); // May fail
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;
}
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);
// Shallow copy
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 emailResult(result){
let msg;
switch(result){
case 200:
console.log('ok. continue');
return true;
case 403:
console.log('We do not have permisison to view the note or something wrong with HackMD.');
EMAIL.body += 'We do not have permisison to view the note or something wrong with HackMD.';
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.');
EMAIL.body += 'Not responded with 200 while getting the note.';
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('superman'));
formResponse.withItemResponse(items[1].asTextItem().createResponse('https://hackmd.io/@idoleat/AudioAdventureMap'));
formResponse.withItemResponse(items[2].asTextItem().createResponse('https://github.com/idoleat/Christmas-card'));
formResponse.withItemResponse(items[3].asTextItem().createResponse('https://hackmd.io/@idoleat/AudioAdventureMap'));
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