Google Apps Script create calendar event from sheet forbidden? - google-apps-script

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);

Related

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.

Copy data from one sheet of Google Sheet to another with script and web-app

This is my first script experience. I have to make two types of Spreadsheet. One is for sellers, another is for manager. The data from seller's sheet with script button are importing to manager's sheet. I need to use web-app because seller shouldn't see manager's spreadsheet.
This is my broken code. The part for sellers script:
var spreadsheet = SpreadsheetApp.getActive();
var TEST = spreadsheet.getRange("B4").getValue();
var TWO = spreadsheet.getRange("B5").getValue();
var THREE = spreadsheet.getRange("B6").getValue();
var FOUR = spreadsheet.getRange("B7").getValue();
var FIVE = spreadsheet.getRange("B8").getValue();
function myFunction() {
var data = "[new Date(), TEST, TWO, FIVE, FOUR, THREE ];"
UrlFetchApp.fetch('https://script.google.com/a/***/exec', {payload: data});
};
And web-app:
var SHEET_ID = '***';
function doPost(e){
SpreadsheetApp.openById(SHEET_ID).getSheets()[0].appendRow(e.postData);
}
Could you tell me, what's wrong with it?
I'm afraid there were a few things wrong with the code. The following example works OK. But it uses the model that the seller's (source) spreadsheet pushes the data to the Manager's (destination) spreadsheet, which is the published web app.
You could of course do this using a pull model, where the web app is in the source spreadsheets and the data is pulled into the destination speadsheet. Which is best all depends upon factors like how many spreadsheets are there going to be, how often do they change, and your overall security model, etc.
Button handling code in the source / sending spreadsheet.
function called_by_button(data_to_be_sent){
// Make some test data.
var data = {
'date': new Date(),
'first': 'data1',
'second': 'data2'
};
var options = {
'method' : 'post',
'payload' : data,
muteHttpExceptions: true
};
// This is probably the best way to use UrlFetchApp() and handle errors.
var url = 'https://script.google.com/macros/s/your-url-here/exec';
try {
var response = UrlFetchApp.fetch(url, options); // Post the data (make the HTTP Request)
var responseCode = response.getResponseCode();
if (responseCode === 200) { // 200 = SUCCESS
Logger.log("url_fetch() response code %s ", responseCode);
return response;
} else {
Logger.log(Utilities.formatString("url_fetch() Request failed for: %s, Expected 200, got %d",url,responseCode ));
return false;
//
}
}// end Try
catch (err) {
Logger.log(Utilities.formatString("url_fetch() Request failed (underlying network error). %s, response code: %s",err, responseCode));
return false;
}
}
doPost() published as a web app from the destination spreadsheet
This picks apart the values sent and logs one in each row. Note that because the destination app is bound to the destination spreadsheet it doesn't need to find and open it using openById(). If you made this a standalone script then it would have to do that.
function doPost(e){
var ss = SpreadsheetApp.getActive();
var ws = ss.getActiveSheet();
ws.appendRow([e.parameter.date]);
ws.appendRow([e.parameter.first]);
ws.appendRow([e.parameter.second]);
}

Missing publish to Chrome Web Store option for web application

I'm trying to make an inbox-listener element to automatically pull message content with a specific subject line into a Google Spreadsheet for reporting. I'm hoping this GitHub Project will do the trick, and I've made the Google Apps Script project that should handle it. Part of the process is to verify the project ownership by publishing it as draft on the Chrome web store. According to Google's documentation, that should be under the publish function, but I don't see it there, or anywhere in the script editor. Could anyone tell me what I'm doing wrong, or if this feature is disabled?
Here is what the menu looks like in my IDE.
Adapted from a OP's comment to the accepted answer
The script is definitely not contained in a Google Doc or Spreadsheet, but it is in a workspace tied to a Google Site. Interestingly, I've tried following the link in the KENdi's answer to edit Apps Scripts under my work account, and I'm told I don't have >permission to generate app scripts on that level. I'll send an email to my Admin to see if I can elevate privileges so I can publish through this channel.
GAS Code
main.gs
<meta name="google-site-verification" content="eA0WbBgImGB_wcsnSADjvwnCBaNyrSifyyxuNhHSXf8" />
var PROJECTID = 'api-project-...';
var WEBHOOK_URL = 'My custom project URL'
function doPost(e){
var postBody = JSON.parse(e.postData.getDataAsString());
var messageData = Utilities.newBlob(Utilities.base64Decode(postBody.message.data)).getDataAsString();
var ss = SpreadsheetApp.openById('...').getSheetByName("Log");
ss.appendRow([new Date(), messageData, JSON.stringify(postBody,undefined,2)])
return 200;
}
function setupPubSub(){
var newTopic = CreateTopic("mailTrigger");
newTopic.setIamPolicy(addGmailPolicy());
Logger.log(newTopic.getName());
var newSub = CreateSubscription("mailTrigger",newTopic.getName(),WEBHOOK_URL);
}
function disEnrollEmail(email){
var email = email || "me";
var res = UrlFetchApp.fetch("https://www.googleapis.com/gmail/v1/users/"+email+"/stop",{method:"POST",headers:{authorization:"Bearer "+ScriptApp.getOAuthToken()}});
Logger.log(res.getContentText());
}
function enrollEmail(email){
var email = email || "me";
PubSubApp.setTokenService(getTokenService())
var topicName = PubSubApp.PublishingApp(PROJECTID).getTopicName("mailTrigger")
Logger.log(watchEmail(topicName,{labelIds:["INBOX"], email:email}));
}
helper.gs
function addGmailPolicy(Policy){
return PubSubApp.policyBuilder()
[(Policy)?"editPolicy":"newPolicy"](Policy)
.addPublisher("SERVICEACCOUNT", 'gmail-api-push#system.gserviceaccount.com')
.getPolicy();
}
function addDomainSubs(Domain,Policy){
return PubSubApp.policyBuilder()
[(Policy)?"editPolicy":"newPolicy"](Policy)
.addPublisher("DOMAIN", Domain)
.getPolicy();
}
function getSubscriptionPolicy(){
return PubSubApp.policyBuilder()
.newPolicy()
.addSubscriber("DOMAIN","ccsknights.org")
}
function watchEmail(fullTopicName,watchOptions){
var options = {email:"me",token:ScriptApp.getOAuthToken(),labelIds:[]};
for(var option in watchOptions){
if(option in options){
options[option] = watchOptions[option];
}
}
Logger.log(options);
var url = "https://www.googleapis.com/gmail/v1/users/"+options.email+"/watch"
var payload = {
topicName: fullTopicName,
labelIds: options.labelIds
}
var params = {
method:"POST",
contentType: "application/json",
payload: JSON.stringify(payload),
headers:{Authorization: "Bearer "+ options.token
},
muteHttpExceptions:true
}
var results = UrlFetchApp.fetch(url, params);
if(results.getResponseCode() != 200){
throw new Error(results.getContentText())
}else{
return JSON.parse(results.getContentText());
}
}
function CreateTopic(topicName) {
var topic;
PubSubApp.setTokenService(getTokenService());
var pubservice = PubSubApp.PublishingApp(PROJECTID);
try{topic = pubservice.newTopic(topicName)}
catch(e){topic = pubservice.getTopic(topicName);}
return topic;
}
function CreateSubscription(subscriptionName,topicName,webhookUrl){
var sub;
PubSubApp.setTokenService(getTokenService());
var subService = PubSubApp.SubscriptionApp(PROJECTID);
try{sub = subService.newSubscription(subscriptionName,topicName,webhookUrl)}
catch(e){sub = subService.getSubscription(subscriptionName,topicName,webhookUrl)}
return sub;
}
function getTokenService(){
var jsonKey = JSON.parse(PropertiesService.getScriptProperties().getProperty("jsonKey"));
var privateKey = jsonKey.private_key;
var serviceAccountEmail = jsonKey.client_email;
var sa = GSApp.init(privateKey, ['https://www.googleapis.com/auth/pubsub'], serviceAccountEmail);
sa.addUser(serviceAccountEmail)
.requestToken();
return sa.tokenService(serviceAccountEmail);
}
function requestGmailScope_(){GmailApp.getAliases()}
The "Register in Chrome Web Store" option is only available in the standalone scripts.
Meaning, if this script is bound to a Google Sheets, Docs, Forms or scripts appear among your files in Google Drive, then this "Register in Chrome Web Store" is not available.
You can verify it by checking the publish in this "https://www.google.com/script/start/". This one will have an option of "Register in Chrome Web Store" in the publish.
While by checking the script editor of a spreadsheet, created in your Drive, the "Register in Chrome Web Store" will not be found in the Publish section.
Hope this information helps you.
It turns out I couldn't build Apps Scripts under my work account because my browser was defaulting to my personal Gmail account. Resetting the browser and connecting as my work account first fixed the problem. For future reference, you have to make your scripts under Script.Google.com in order for them to be shareable via the web store. You can bypass an authentication problems like this by resetting your browser history or running Chrome in incognito mode if you have to.
Great catch, KENDi!

Calling a bound script's method using the Execution API

I'm using PropertiesServices as variables, specifically Document Properties , in order to replace some tokens like "{client name}". Since those properties are scoped to the bound script only, I'm looking for a way to modify their values from my PHP application.
Is it possible to call a bound script's function using the Execution API, or maybe from a standalone script? Otherwise, should I instead use the Script Properties instead (although the docs make me think you can't use them if the script isn't 'standalone).
It looks like if the user that the Execution API is running under has permission to the doc that bound script ran by the execution api can read document properties.
Here is my test:
Create a new spreadsheet. Create a new script. Add some data using the menu from onOpen. Run executeAPI inside the script. The log successfully shows the document properties.
function onOpen() {
var testMenu = SpreadsheetApp.getUi().createMenu("test")
testMenu.addItem("Add some data", "addData").addToUi();
testMenu.addItem("Preview data", "getData").addToUi();
}
function getData(){
var keys = PropertiesService.getDocumentProperties().getKeys();
SpreadsheetApp.getActiveSpreadsheet().getActiveSheet().clear().appendRow(keys)
}
function returnData(){
return PropertiesService.getDocumentProperties().getKeys();
}
function addData(){
var DT = new Date().toString()
PropertiesService.getDocumentProperties().setProperty(DT,DT);
}
function executeAPI(){
var url = 'https://script.googleapis.com/v1/scripts/'+ScriptApp.getProjectKey()+':run';
var payload = JSON.stringify({"function": "returnData","parameters":[], "devMode": true});
var params={method:"POST",
headers:{Authorization: "Bearer "+ ScriptApp.getOAuthToken()},
payload:payload,
contentType:"application/json",
muteHttpExceptions:true};
var results = UrlFetchApp.fetch(url, params);
Logger.log(results)
}

403 error when executing Google Apps Script form a different google account

I have this javascript to extract html table and then pass the arrays to google apps script as parameters.
var CLIENT_ID = 'some ID';
var SCRIPT_ID = 'some ID';
var SCOPES = ['https://www.googleapis.com/auth/drive.file'];
function handleAuthClick(event) {
gapi.auth.authorize(
{'client_id': CLIENT_ID, 'scope': SCOPES, 'immediate': true},
handleAuthResult);
}
function handleAuthResult(authResult) {
if (authResult) {
// Access token has been successfully retrieved, requests can be sent to the API
} else {
// No access token could be retrieved, force the authorization flow.
gapi.auth.authorize(
{'client_id': CLIENT_ID, 'scope': SCOPES, 'immediate': false},
handleAuthResult);
}
}
function exportGsheet() {
var myTableArray = [];
$("table#fin tr").each(function() {
var arrayOfThisRow = [];
var tableData = $(this).find('td');
if (tableData.length > 0) {
tableData.each(function() { arrayOfThisRow.push($(this).text()); });
myTableArray.push(arrayOfThisRow);
}
});
var params = JSON.stringify(myTableArray);
var request = {
'function': 'setData',
'parameters': params,
'devMode': true // Optional.
};
var op = gapi.client.request({
'root': 'https://script.googleapis.com',
'path': 'v1/scripts/' + SCRIPT_ID + ':run',
'method': 'POST',
'body': request
});
op.execute(function(resp){opensheet(resp)});
}
Below is the apps script. This uses Drive API and Executable API.
var DOC_ID = 'some id';
var formattedDate = Utilities.formatDate(new Date(), "GMT", "yyyy-MM-dd'T'HH:mm:ss'Z'");
var folder = DriveApp.getFolderById('some id');
function setData(parameters) {
var getFile = DriveApp.getFileById(DOC_ID);
var file = getFile.makeCopy(formattedDate, folder);
var ss = SpreadsheetApp.open(file);
var ssId = ss.getId();
ss.getSheets()[0].getRange(5,1,50,24).clear();
var e = JSON.parse(parameters);
var outerArray = [];
for(var i = 0; i < e.length; i++) {
outerArray.push(e[i]);
}
ss.getSheets()[0].getRange(5, 2, outerArray.length, outerArray[0].length).setValues(outerArray);
return {"url":ssId};
Logger.log(ssId);
}
Everything works fine when I authorize using the gmail ID that owns the apps script and project (my own gmail account). But when I authenticate using a different gmail account I get the below error:
error: {code: 403, message: "The caller does not have permission", status: "PERMISSION_DENIED"}
code: 403
message: "The caller does not have permission"
status: "PERMISSION_DENIED"
I intend to make this application public and anyone should be able to authenticate using their gmail account and execute the script. How do I do that? Please help.
Folks, I figured out the problem later. It happened to be permission issue n Developer console. We have to assign permission under Developer Console Project for the project which the apps-script is associated. So follow these steps:
Open your apps script Go to Resources-Developers Console Project
Click on the project name appearing in blue under "This script is
currently associated with project:" It will redirect you to Developer
Console Project.
Click on Menu on the left hand side upper corner and click on
Permissions
Under Permissions click on Add members
In the member type the email ID or domain you want to provide
permission and desired permission level. Click on 'Add'
You are done.
However, I wasnt able to add the entire gmail domain, nor able to add allAuthenticatedUsers. I have raised an issue with google support