Google Sheet Script not triggered when sheet is edited by service account? - google-apps-script

So I have written a gs function that sends an email when someone inserts a new row in the sheet. It works fine when users manually insert data, however that sheet is also used by a service account which inserts a new row through the API, and the edit event is not triggered in that case.
This is the trigger that I'm using
I created the script and the trigger as the owner of the sheet, but that didn't fix anything, so I'm out of ideas.

The only way to trigger a user event with code is to use the Sheets API, with a special setting to set the value as USER_ENTERED And it only works with the "On Change" event. So, you'll need to create a second trigger for "On Change" but you can use the same function name. Although you may need to modify the function to deal with a different event object. Or you could use a different function.
So, your service account will need to run code that uses the Sheets API to set values in your Google Sheet.
You can use either the REST API or the Sheets Advanced Service.
To use the Advance Sheets Service the code would look like the following:
function writeToSheet() {
id = "Put the Sheet ID here";
var rowValues = [
["one","two"],
];
var request = {
'valueInputOption': 'USER_ENTERED',
'data': [
{
"range": "Sheet1!A2:B2",
"majorDimension": "ROWS",
"values": rowValues,
},
],
};
var response = Sheets.Spreadsheets.Values.batchUpdate(request, id);
Logger.log('response ' + JSON.stringify(response))
}
For the REST API the basic code is as follows:
function writeToSheet() {
var id,options,range,response,sh,ss,url,values;
id = 'Put the spreadsheet ID here';
range = "Sheet1!A1:A1";
values = {values: [['3','two','nine']]}; // Modified
url = "https://sheets.googleapis.com/v4/spreadsheets/" +
id + "/values/" + range + ":append?valueInputOption=USER_ENTERED";
options = {
"method":"post",
"muteHttpExceptions": true,
"headers": {
"Authorization": "Bearer " + ScriptApp.getOAuthToken()
},
"contentType": "application/json", // Added
"payload": JSON.stringify(values) // Added
}
response = UrlFetchApp.fetch(url,options)
response = JSON.parse(response);
//Logger.log('response ' + JSON.stringify(response))
}

Related

Google Script to send GET request to Trello API and import data to Google Sheet

I have a small script to pull information from a Trello API.
The script works fine when I've assigned the full URL to a String variable. However, when I pass the params separately, I get an error "unauthorized permission requested".
Working code:
var url = "https://api.trello.com/1/boards/57c68c1beaab4c676adfaeb1/lists?key=myTrelloKey&token=myTrelloToken";
var response = UrlFetchApp.fetch(url);
Logger.log(response.getContentText());
Problematic code:
var url = "https://api.trello.com/1/boards/57c68c1beaab4c676adfaeb1/lists";
var options =
{
"key": "myTrelloKey",
"token": "myTrelloToken",
"muteHttpExceptions" : true
};
var response = UrlFetchApp.fetch(url,options);
Logger.log(response.getContentText());
I have tried to understand if it's an Authentication issue, but could not get my way around it. Am I doing something wrong in the second version? Thanks in advance!
I believe your current situation and goal as follows.
From your question, I understood that the script of Working code: works.
You want to use "key": "myTrelloKey" and "token": "myTrelloToken" as the query parameter.
Modification points:
At UrlFetchApp.fetch(url, params), params has no properties of key and token.
I think that this is the reason of your issue.
Even when these values are used in payload, in that case, the request becomes POST method.
When you want to use "key": "myTrelloKey" and "token": "myTrelloToken" as the query parameter, in the current stage, it is required to prepare a script.
Although I test whether key and token can be directly used for the request body and the headers instead of the query parameter, unfortunately, these were not succeeded. It seems that these are used as the query parameters. Ref
When above points are reflected to your script, it becomes as follows.
Modified script:
function myFunction() {
// This is from https://gist.github.com/tanaikech/70503e0ea6998083fcb05c6d2a857107
String.prototype.addQuery = function(obj) {
return this + Object.keys(obj).reduce(function(p, e, i) {
return p + (i == 0 ? "?" : "&") +
(Array.isArray(obj[e]) ? obj[e].reduce(function(str, f, j) {
return str + e + "=" + encodeURIComponent(f) + (j != obj[e].length - 1 ? "&" : "")
},"") : e + "=" + encodeURIComponent(obj[e]));
},"");
}
var url = "https://api.trello.com/1/boards/57c68c1beaab4c676adfaeb1/lists";
var query = {
"key": "myTrelloKey",
"token": "myTrelloToken",
};
var endpoint = url.addQuery(query);
Logger.log(endpoint); // <--- https://api.trello.com/1/boards/57c68c1beaab4c676adfaeb1/lists?key=myTrelloKey&token=myTrelloToken
var response = UrlFetchApp.fetch(endpoint, {"muteHttpExceptions" : true});
Logger.log(response.getContentText());
}
References:
UrlFetchApp.fetch(url, params)
Adding Query Parameters to URL using Google Apps Script

Can I copy a script from a Form to the linked spreadsheet?

I have a form template which I will be duplicating as needed, modifying, and sending out for responses. I have written a script for the linked responses spreadsheet which rearranges the response data in a specific way. The script itself works fine, and it only needs to run on my account.
The problem is that the script is tied to the spreadsheet, not the form; but it’s the form and not the spreadsheet that gets duplicated. I tried linking my script to the form template and using the code below to create a linked spreadsheet and copy the script over. I set up a trigger to run this function on form submit, but the trigger disappears when the form template is duplicated. This code is largely copied from this answer: https://stackoverflow.com/a/48353155/12131953 with a few lines added at the beginning to create the linked spreadsheet.
function copyScript() {
//create destination spreadsheet
var form = FormApp.getActiveForm();
var ss = SpreadsheetApp.create(form.getTitle() + " (Responses)");
form.setDestination(FormApp.DestinationType.SPREADSHEET, ss.getId());
var srcProjectId = "###my project id###"; // Source project ID
var dstGoogleDocsId = ss.getId(); // Destination spreadsheet ID
var baseUrl = "https://script.googleapis.com/v1/projects";
var accessToken = ScriptApp.getOAuthToken();
// Retrieve filename of bound-script project.
var srcName = JSON.parse(UrlFetchApp.fetch(baseUrl + "/" + srcProjectId, {
method: "get",
headers: {"Authorization": "Bearer " + accessToken}
}).getContentText()).title;
// Retrieve bound-script project.
var obj = UrlFetchApp.fetch(baseUrl + "/" + srcProjectId + "/content", {
method: "get",
headers: {"Authorization": "Bearer " + accessToken}
}).getContentText();
// Create new bound script and retrieve project ID.
var dstId = JSON.parse(UrlFetchApp.fetch(baseUrl, {
method: "post",
contentType: 'application/json',
headers: {"Authorization": "Bearer " + accessToken},
payload: JSON.stringify({"title": srcName, "parentId": dstGoogleDocsId})
}).getContentText()).scriptId;
// Upload a project to bound-script project.
var res = JSON.parse(UrlFetchApp.fetch(baseUrl + "/" + dstId + "/content", {
method: "put",
contentType: 'application/json',
headers: {"Authorization": "Bearer " + accessToken},
payload: obj
}).getContentText());
}
Then I tried to create the trigger programmatically when the duplicated form is opened, but as far as I can tell creating installable triggers is outside the authorization of the simple trigger onOpen.
function onOpen(); {
ScriptApp.newTrigger("copyScript").forForm(FormApp.getActiveForm()).onFormSubmit().create();
.
.
.
}
I am not a developer and am self-taught on this stuff; I’m pretty comfortable with the scripting aspect but have no familiarity at all with APIs and web deployments.
So my question is: Is there a way to copy a script to a new, form-linked spreadsheet from the form? I’m also fine with a solution that somehow applies my script (maybe as a standalone) to all new spreadsheets, because the code creates a menu option; so making the script an add-on may be the answer here, but that seems like overkill for a script that only one person is ever going to run.
Instead of rebounding your old script from the spreadsheet to the form, leave as it is, but add the following script to your Form:
function onOpen() {
FormApp.getActiveForm().setDestination(FormApp.DestinationType.SPREADSHEET, 'ID of your destination spreadsheet');
}
Run this script once manually in order to give necessary permissions to the script
Copy your form as much as you like - each copy will contain a copy of the script
The script will fire onFormOpen and set the destination of the respective form copy to the same spreadsheet - the one containing your rearranging script
Form responses from different forms will be automatically inserted in different sheets of the spreadsheet

Is it possible to load google photos metadata into google sheets?

I have a project where I have scanned 10,000 family pictures from as far back as the 1900's and I am organizing them in Google Photos. I have a spreadsheet where I was keeping track of the proper dates and captions for the entire collection. I would organize a few at a time but then recently found out about the google photos API.
I would like to use something like the methods Method: mediaItems.list or Method: mediaItems.search to get the data from my photos into the spreadsheet to manage.
The output from these examples is exactly what I'm looking for and would want to load that into a spreadsheet.
It would be super awesome if there was a way to update back from the sheet again as well.
I found this article but the code provided does not work for me.
I have this function now in my sheet
function photoAPI() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var albums_sh = ss.getSheetByName("albums") || ss.insertSheet("albums", ss.getSheets().length);
albums_sh.clear();
var narray = [];
var api = "https://photoslibrary.googleapis.com/v1/albums";
var headers = { "Authorization": "Bearer " + ScriptApp.getOAuthToken() };
var options = { "headers": headers, "method" : "GET", "muteHttpExceptions": true };
var param= "", nexttoken;
do {
if (nexttoken)
param = "?pageToken=" + nexttoken;
var response = UrlFetchApp.fetch(api + param, options);
var json = JSON.parse(response.getContentText());
json.albums.forEach(function (album) {
var data = [
album.title,
album.mediaItemsCount,
album.productUrl
];
narray.push(data);
});
nexttoken = json.nextPageToken;
} while (nexttoken);
albums_sh.getRange(1, 1, narray.length, narray[0].length).setValues(narray);
}
When I run it in debug mode, I get the following error
({error:{code:403, message:"Request had insufficient authentication scopes.", status:"PERMISSION_DENIED"}})
I know this means I need to authenticate but don't know how to make that happen.
I have an API key and a secret from the Google photos API pages.
Edit
I used the links from #Tanaike to figure out how to add scopes to my project.
I added these three.
spreadsheets.currentonly
photoslibrary
script.external_request
Now when I run in debug mode, I get a 403 error indicating I need to set up my API. Summary of the error is below:
error:
code:403
Photos Library API has not been used in project 130931490217 before or it is disabled. Enable it by visiting
https://console.developers.google.com/apis/api/photoslibrary.googleapis.com/overview?project=130931490217
Google developers console API activation
type.googleapis.com/google.rpc.Help
"PERMISSION_DENIED"
When I try to go to the listed URL though, I just get a message that says "Failed to load."
I got my code working with the help of #Tanaike in my comments above. I had two issues.
1) I needed to specify the oauthScopes in appsscript.json which is hidden by default in google scripts. It can be revealed by going to the menu and selecting View > Show Manifest File.
2) I was using a default GCP project which did not have authorization to use the photos API and could not be enabled. I needed to switch to a standard GCP project which I had created earlier and had enabled the photos API.
Here is my original posted function with additional comments after I got it working:
function photoAPI_ListAlbums() {
// Modified from code by Stackoverflow user Frç Ju at https://stackoverflow.com/questions/54063937/0auth2-problem-to-get-my-google-photos-libraries-in-a-google-sheet-of-mine
// which was originally Modified from http://ctrlq.org/code/20068-blogger-api-with-google-apps-script
/*
This function retrieves all albums from your personal google photos account and lists each one with the name of album, count of photos, and URL in a new sheet.
Requires Oauth scopes. Add the below line to appsscript.json
"oauthScopes": ["https://www.googleapis.com/auth/spreadsheets.currentonly", "https://www.googleapis.com/auth/photoslibrary", "https://www.googleapis.com/auth/photoslibrary.readonly", "https://www.googleapis.com/auth/script.external_request"]
Also requires a standard GCP project with the appropriate Photo APIs enabled.
https://developers.google.com/apps-script/guides/cloud-platform-projects
*/
//Get the spreadsheet object
var ss = SpreadsheetApp.getActiveSpreadsheet();
//Check for presence of target sheet, if it does not exist, create one.
var albums_sh = ss.getSheetByName("albums") || ss.insertSheet("albums", ss.getSheets().length);
//Make sure the target sheet is empty
albums_sh.clear();
var narray = [];
//Build the request string. Default page size is 20, max 50. set to max for speed.
var api = "https://photoslibrary.googleapis.com/v1/albums?pageSize=50";
var headers = { "Authorization": "Bearer " + ScriptApp.getOAuthToken() };
var options = { "headers": headers, "method" : "GET", "muteHttpExceptions": true };
var param= "", nexttoken;
//Make the first row a title row
var data = [
"Title",
"Item Count",
"ID",
"URL"
];
narray.push(data);
//Loop through JSON results until a nextPageToken is not returned indicating end of data
do {
//If there is a nextpagetoken, add it to the end of the request string
if (nexttoken)
param = "&pageToken=" + nexttoken;
//Get data and load it into a JSON object
var response = UrlFetchApp.fetch(api + param, options);
var json = JSON.parse(response.getContentText());
//Loop through the JSON object adding desired data to the spreadsheet.
json.albums.forEach(function (album) {
var data = [
"'"+album.title, //The prepended apostrophe makes albums with a name such as "June 2007" to show up as that text rather than parse as a date in the sheet.
album.mediaItemsCount,
album.id,
album.productUrl
];
narray.push(data);
});
//Get the nextPageToken
nexttoken = json.nextPageToken;
//Continue if the nextPageToaken is not null
} while (nexttoken);
//Save all the data to the spreadsheet.
albums_sh.getRange(1, 1, narray.length, narray[0].length).setValues(narray);
}
And here is another function which I created in the same style to pull photo metadata directly. This is what I was originally trying to accomplish.
function photoAPI_ListPhotos() {
//Modified from above function photoAPI_ListAlbums
/*
This function retrieves all photos from your personal google photos account and lists each one with the Filename, Caption, Create time (formatted for Sheet), Width, Height, and URL in a new sheet.
it will not include archived photos which can be confusing if you happen to have a large chunk of archived photos some pages may return only a next page token with no media items.
Requires Oauth scopes. Add the below line to appsscript.json
"oauthScopes": ["https://www.googleapis.com/auth/spreadsheets.currentonly", "https://www.googleapis.com/auth/photoslibrary", "https://www.googleapis.com/auth/photoslibrary.readonly", "https://www.googleapis.com/auth/script.external_request"]
Also requires a standard GCP project with the appropriate Photo APIs enabled.
https://developers.google.com/apps-script/guides/cloud-platform-projects
*/
//Get the spreadsheet object
var ss = SpreadsheetApp.getActiveSpreadsheet();
//Check for presence of target sheet, if it does not exist, create one.
var photos_sh = ss.getSheetByName("photos") || ss.insertSheet("photos", ss.getSheets().length);
//Make sure the target sheet is empty
photos_sh.clear();
var narray = [];
//Build the request string. Max page size is 100. set to max for speed.
var api = "https://photoslibrary.googleapis.com/v1/mediaItems?pageSize=100";
var headers = { "Authorization": "Bearer " + ScriptApp.getOAuthToken() };
var options = { "headers": headers, "method" : "GET", "muteHttpExceptions": true };
//This variable is used if you want to resume the scrape at some page other than the start. This is needed if you have more than 40,000 photos.
//Uncomment the line below and add the next page token for where you want to start in the quotes.
//var nexttoken="";
var param= "", nexttoken;
//Start counting how many pages have been processed.
var pagecount=0;
//Make the first row a title row
var data = [
"Filename",
"description",
"Create Time",
"Width",
"Height",
"ID",
"URL",
"NextPage"
];
narray.push(data);
//Loop through JSON results until a nextPageToken is not returned indicating end of data
do {
//If there is a nextpagetoken, add it to the end of the request string
if (nexttoken)
param = "&pageToken=" + nexttoken;
//Get data and load it into a JSON object
var response = UrlFetchApp.fetch(api + param, options);
var json = JSON.parse(response.getContentText());
//Check if there are mediaItems to process.
if (typeof json.mediaItems === 'undefined') {
//If there are no mediaItems, Add a blank line in the sheet with the returned nextpagetoken
//var data = ["","","","","","","",json.nextPageToken];
//narray.push(data);
} else {
//Loop through the JSON object adding desired data to the spreadsheet.
json.mediaItems.forEach(function (MediaItem) {
//Check if the mediaitem has a description (caption) and make that cell blank if it is not present.
if(typeof MediaItem.description === 'undefined') {
var description = "";
} else {
var description = MediaItem.description;
}
//Format the create date as appropriate for spreadsheets.
var d = new Date(MediaItem.mediaMetadata.creationTime);
var data = [
MediaItem.filename,
"'"+description, //The prepended apostrophe makes captions that are dates or numbers save in the sheet as a string.
d,
MediaItem.mediaMetadata.width,
MediaItem.mediaMetadata.height,
MediaItem.id,
MediaItem.productUrl,
json.nextPageToken
];
narray.push(data);
});
}
//Get the nextPageToken
nexttoken = json.nextPageToken;
pagecount++;
//Continue if the nextPageToaken is not null
//Also stop if you reach 400 pages processed, this prevents the script from timing out. You will need to resume manually using the nexttoken variable above.
} while (pagecount<400 && nexttoken);
//Continue if the nextPageToaken is not null (This is commented out as an alternative and can be used if you have a small enough collection it will not time out.)
//} while (nexttoken);
//Save all the data to the spreadsheet.
photos_sh.getRange(1, 1, narray.length, narray[0].length).setValues(narray);
}
Because of the limitations of the ListPhotos function and the fact that my library is so enormous, I am still working on a third function to pull photo metadata from all the photos in specific albums. I'll edit this answer once I pull that off.

Slack Webhooks Connected to Google Sheets Get Next Row

I have this code that works great for using an outgoing webhook in slack to fill in a google sheet, and then bounce back a formatted response from the google sheet into a slack channel, but I can't figure out how to get it to pull any other columns in the google sheet. Here is the google sheet link. So it goes as follows:
In a slack chanel you can use the outgoing webhook and post"nextrow;test;test;test
This information is filled into the google sheet with a new row
google script formats this info into a payload and posts a formatted version of the info into the slack channel
This all occurs in columns A:F and in row G there is an array formula and I would like for the google script to pull that columns value in that new row and post it back in the slack response. I tried entering in sheets.getRangeByName('test').getValue(nR,1)
but that didn't work, and I also tried sheet.getRange(noteTakerCell).getValue() but that also didn't work and it also seems to keep the whole thing from working anymore. Here is an example of the response posted back in Slack, and I would like this to include the test column new row.
Here is the code that currently works for columns A:F, I removed script I wrote for trying to get column G new row since it seems to stop everything from working. Any help would be greatly appreciated. Thanks!
function doPost(req) {
var sheets = SpreadsheetApp.openById('1P4goTvi2a7yjh-fBccRJPJ9ZFNly8OhxmABXkuhfbBQ');
var params = req.parameters;
var nR = getNextRow(sheets) + 1;
if (params.token == "[Slack Outgoing Webhook]") {
// PROCESS TEXT FROM MESSAGE
var textRaw = String(params.text).replace(/^\s*update\s*:*\s*/gi,'');
var text = textRaw.split(/\s*;\s*/g);
// FALL BACK TO DEFAULT TEXT IF NO UPDATE PROVIDED
var project = text[0] || "No Project Specified";
var yesterday = text[1] || "No update provided";
var today = text[2] || "No update provided";
var blockers = text[3] || "No update provided";
// RECORD TIMESTAMP AND USER NAME IN SPREADSHEET
sheets.getRangeByName('timestamp').getCell(nR,1).setValue(new Date());
sheets.getRangeByName('user').getCell(nR,1).setValue(params.user_name);
// RECORD UPDATE INFORMATION INTO SPREADSHEET
sheets.getRangeByName('project').getCell(nR,1).setValue(project);
sheets.getRangeByName('yesterday').getCell(nR,1).setValue(yesterday);
sheets.getRangeByName('today').getCell(nR,1).setValue(today);
sheets.getRangeByName('blockers').getCell(nR,1).setValue(blockers);
var channel = "[Slack Channel]";
postResponse(channel,params.channel_name,project,params.user_name,yesterday,today,blockers);
} else {
return;
}
}
function getNextRow(sheets) {
var timestamps = sheets.getRangeByName("timestamp").getValues();
for (i in timestamps) {
if(timestamps[i][0] == "") {
return Number(i);
break;
}
}
}
function postResponse(channel, srcChannel, project, userName, yesterday, today, blockers) {
var payload = {
"channel": "#" + channel,
"username": "New Update",
"icon_emoji": ":white_check_mark:",
"link_names": 1,
"attachments":[
{
"fallback": "This is an update from a Slackbot integrated into your organization. Your client chose not to show the attachment.",
"pretext": "*" + project + "* posted an update for stand-up. (Posted by #" + userName + " in #" + srcChannel + ")",
"mrkdwn_in": ["pretext"],
"color": "#D00000",
"fields":[
{
"title":"Yesterday",
"value": yesterday,
"short":false
},
{
"title":"Today",
"value": today,
"short":false
},
{
"title":"Blockers",
"value": blockers,
"short": false
}
]
}
]
};
var url = '[Slack Incoming Webhook]';
var options = {
'method': 'post',
'payload': JSON.stringify(payload)
};
var response = UrlFetchApp.fetch(url,options);
}

Google Apps Script create calendar event from sheet forbidden?

In a document-bound Google Appscript in one of our company spreadsheets, I've created a script that turns spreadsheet lines into Google calendar appointments. The function works fine for me, but not for my coworker, even though we both have permissions to edit the calendar and change sharing permissions, and my coworker proved he can create appointments on the calendar from calendar.google.com.
He gets the following error message when he runs the script:
{"message":"Forbidden","name":"GoogleJsonResponseException","fileName":"SCHEDULER","lineNumber":204,"stack":"\tat SCHEDULER:204 (createAppointments)\n"}
Line 204 corresponds to the command:
Calendar.Events.insert(event, CAL, {sendNotifications: true, supportsAttachments:true});
If he has edit rights to the calendar, why is this forbidden? Is there a problem with the Calendar service in Google Apps Script? What is more, I changed the CAL variable to a calendar I personally created and shared out to him with the same permissions. He can edit that calendar just fine.
Here is the psuedocode for the function
function createAppointments() {
var CAL = 'companyname.com_1v033gttnxe2r3eakd8t9sduqg#group.calendar.google.com';
for(/*each row in spreadsheet*/)
{
if(/*needs appointment*/)
{
var object = {/*...STUFF...*/};
var coworker = 'coworker#companyname.com';
var timeArgs = {start: /*UTC Formatted time*/, end: /*UTC Formatted time*/}
if(/*All the data checks out*/{
var summary = 'Name of appointment'
var notes = 'Stuff to put in the body of the calendar appointment';
var location = '123 Happy Trail, Monterrey, TX 12345'
//BUILD GOOGLE CALENDAR OBJECT
var event = {
"summary": summary,
"description": notes,
"start": {
"dateTime": timeArgs.start,
"timeZone": TZ
},
"end": {
"dateTime": timeArgs.end,
"timeZone": TZ
},
"guestsCanInviteOthers": true,
"reminders": {
"useDefault": true
},
"location": location
//,"attendees": []
};
event.attendees = [{coworker#companyname.com, displayName: 'coworker name'}];
//CREATE CALENDAR IN GOOGLE CALENDAR OF CONST CAL
Calendar.Events.insert(event, CAL, {sendNotifications: true, supportsAttachments:true});
} else{/*Tell user to fix data*/}
}
}
Thank you very much!
Update 12/29/2017:
I've Tried adjusting the app according to Jason Allshorn and Crazy Ivan. Thank you for your help, so far! Interestingly, I have run into the same response using both the Advanced Calendar Service and the CalendarApp.
The error is, as shown below:
<!DOCTYPE html><html><head><link rel="shortcut icon" href="//ssl.gstatic.com/docs/script/images/favicon.ico"><title>Error</title><style type="text/css">body {background-color: #fff; margin: 0; padding: 0;}.errorMessage {font-family: Arial,sans-serif; font-size: 12pt; font-weight: bold; line-height: 150%; padding-top: 25px;}</style></head><body style="margin:20px"><div><img alt="Google Apps Script" src="//ssl.gstatic.com/docs/script/images/logo.png"></div><div style="text-align:center;font-family:monospace;margin:50px auto 0;max-width:600px">Object does not allow properties to be added or changed.</div></body></html>
Or, after parsing that through an html editor:
What does that even mean? I have the advanced service enabled, and the script is enabled to run from anyone. Any ideas?
I have confirmed after testing what the error comes back after trying to run the calendarApp/Advanced Calendar event creation command.
Here is my code that caused me to get this far:
function convertURItoObject(url){
url = url.replace(/\+/g,' ')
url = decodeURIComponent(url)
var parts = url.split("&");
var paramsObj = {};
parts.forEach(function(item){
var keyAndValue = item.split("=");
paramsObj[keyAndValue[0]] = keyAndValue[1]
})
return paramsObj; // here's your object
}
function doPost(e) {
var data = e.postData.contents;
data = convertURItoObject(data);
var CAL = data.cal;
var event = JSON.parse(data.event);
var key = data.key;
var start = new Date(event.start.dateTime);
if(ACCEPTEDPROJECTS.indexOf(key) > -1)
{
try{
var calendar = CalendarApp.getCalendarById(CAL);
calendar.createEvent(event.summary, new Date(event.start.dateTime), new Date(event.end.dateTime), {description: event.description, location: event.location, guests: event.guests, sendInvites: true});}
/*try {Calendar.Events.insert(event, CAL, {sendNotifications: true, supportsAttachments:true});} Same error when I use this command*/
catch(fail){return ContentService.createTextOutput(JSON.stringify(fail));}
e.postData.result = 'pass';
return ContentService.createTextOutput(JSON.stringify(e));
}
else {
return ContentService.createTextOutput('Execution not authorized from this source. See CONFIG of target project for details.');
}
}
Your script is using Advanced Google Services, specifically Calendar. Read the section "Enabling advanced services"; everyone will have to follow those steps to use the script.
Alternatively (in my opinion, this is a better solution), rewrite the script so that it uses the standard CalendarApp service. It also allows you to create an event and then you can add various reminders to that event.
A solution from my side would be to abstract the calendar event creation function away from your Spreadsheet bound script to a separate standalone apps-script that runs under your name with your permissions.
Then from your sheet bound script call to the standalone script with a PUT request containing the information needed to update the Calender. This way anyone using your sheet addon can update the calander without any mess with permissions.
The sheet bound script could look something like this:
function updateCalander(){
var data = {
'event': EVENT,
};
var options = {
'method' : 'post',
'contentType': 'application/json',
'payload' : data
};
var secondScriptID = 'STANDALONE_SCRIPT_ID'
var response = UrlFetchApp.fetch("https://script.google.com/macros/s/" + secondScriptID + "/exec", options);
Logger.log(response) // Expected to see sent data sent back
Then your standalone script would look something like this:
function convertURItoObject(url){
url = url.replace(/\+/g,' ')
url = decodeURIComponent(url)
var parts = url.split("&");
var paramsObj = {};
parts.forEach(function(item){
var keyAndValue = item.split("=");
paramsObj[keyAndValue[0]] = keyAndValue[1]
})
return paramsObj; // here's your object
}
function doPost(e) {
var CAL = 'companyname.com_1v033gttnxe2r3eakd8t9sduqg#group.calendar.google.com';
var data = e.postData.contents;
data = convertURItoObject(data)
var event = data.event;
try {
Calendar.Events.insert(event, CAL, {sendNotifications: true, supportsAttachments:true});
}
catch(e){
Logger.log(e)
}
return ContentService.createTextOutput(JSON.stringify(e));
}
Please note, the standalone script needs to be set to anyone can access, and when you make updates to the code be sure to re-publish the code. If you don't re-publish your calls to the standalone script are not made to the latest code.
This is a delayed response, but thanks to all who recommended using the POST method. It turns out the proper way to do this is to use URLFetchApp and pass the Script's project Key to authorize the calendar access (I believe you only need to make sure the person executing the script has rights to edit the actual calendar).
Here is basically how to do it in a functional way:
//GCALENDAR is th e unique ID of the project int it's URL when the script is open for editing
//PROJECTKEY is the unique ID of the project, found in the Project Properties Menu under FILE.
//CREATE CALENDAR IN GOOGLE CALENDAR OF CONST CAL
var data = {
'event': JSON.stringify(event),
'cal': CAL,
'key': PROJECTKEY
};
var options = {
'method' : 'post',
'contentType': 'application/json',
'payload' : data,
'muteHttpExceptions': true
};
var answer = UrlFetchApp.fetch("https://script.google.com/macros/s/" + GCALENDAR + "/exec", options).getContentText();
Logger.log(answer);