Trying to understand if I'm doing something wrong here.
I'm trying to unsubscribe from a calendar using Apps Scripts, but it won't let me, I receive the error
Calendar test.user#cadillacfairview.com cannot be unsubscribed from. Try deleting instead.
Code:
function anotherTest(){
var cal = CalendarApp.getCalendarById('test.user#cadillacfairview.com');
cal.unsubscribeFromCalendar();
}
I don't own the calendar, but I do have rights to it.
I tried issuing the cal.deleteCalendar() but it said "action not allowed".
I ran in to the exact same problem. It seems like the Google Apps Script Calendar service produces those errors because it's trying to delete the actual calendar, rather than just unsubscribe from it.
My working solution involves using the CalendarList: delete method of the Google Calendar API instead of the Apps Script Calendar Service.
That deletes an entry from your list of calendars in the Google Calendar sidebar, which is the same thing as "unsubscribing". Also, it does not simply "hide" it from the list, it removes it altogether.
const calendarList = CalendarApp.getAllCalendars();
for (var i = 0; i < calendarList.length; i++) {
const calendar = calendarList[i];
// calendar.unsubscribeFromCalendar(); // results in "...cannot be unsubscribed from. Try deleting instead"
// calendar.deleteCalendar(); // results in "...Action not allowed"
Logger.log(`will remove calendar: ${calendar.getName()}`);
const url = `https://www.googleapis.com/calendar/v3/users/me/calendarList/${encodeURIComponent(calendar.getId())}`;
try {
const response = UrlFetchApp.fetch(url, {
headers: { Authorization: `Bearer ${ScriptApp.getOAuthToken()}` },
method: 'DELETE',
muteHttpExceptions: false
});
const responseStatusCode = response.getResponseCode();
Logger.log(`responseStatusCode: ${responseStatusCode}`);
if (responseStatusCode === 200) {
Logger.log('removed calendar');
}
} catch(e) {
Logger.log(`failed to remove calendar: ${e.message}`);
}
}
Related
I am able to make the POST request to my google apps script web app, but I can't access my e.parameters when I log them.
HTML CODE:
<form onsubmit="submitForm(event)">
<input type="text" name="fname" required>
<button type="submit">Submit</button>
</form>
JS CODE:
function submitForm(e){
e.preventDefault()
var url = "https://xxxxxxxxxxxxxxxxxxxxxx/exec"
var params = "employeeStatus='Active'&name='Henry'";
var xhr = new XMLHttpRequest()
xhr.open("POST",url,true)
xhr.setRequestHeader("Content-type","application/x-www-form-urlencoded")
xhr.onreadystatechange = ()=>{
var readyState = xhr.readyState
var status = xhr.status
if(readyState == 4 && status == 200){
var response = JSON.parse(xhr.responseText)
console.log(response)
}
}
xhr.send(params)
}
APPS SCRIPT CODE:
function doPost(e){
var values = e.parameters;
Logger.log(values)
return ContentService.createTextOutput(JSON.stringify({"a":5,"b":2}))
}
Can anyone please tell me what I'm doing wrong here? I've iterated the apps script code to try and log the e.parameters but I'm unable to get anything to work.
***NOTES:
I'm aware that the "params" value is NOT the same as the form input value
I return the JSON string just to ensure that the code is running all the way through and I can practice JSON.parse/JSON.stringify on the client-side.
When I saw your script, I think that your value of e.parameters is {"employeeStatus":["'Active'"],"name":["'Henry'"]}.
About I've iterated the apps script code to try and log the e.parameters but I'm unable to get anything to work., I think that the reason for your issue is due to that your request of "XMLHttpRequest" include no access token. From your request, I thought that the settings of Web Apps might be Execute as: Me and Who has access to the app: Anyone with V8 runtime. If my understanding is correct, the reason for your issue is due to that.
If you want to show Logger.log(values) in the log, please include the access token to the request header as follows.
Modified script:
function submitForm(e) {
e.preventDefault();
var url = "https://script.google.com/macros/s/###/exec"; // Your web apps URL.
url += "?access_token=###your access token###";
var params = "employeeStatus=Active&name=Henry";
var xhr = new XMLHttpRequest();
xhr.open("POST", url, true);
xhr.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
xhr.onreadystatechange = () => {
var readyState = xhr.readyState;
var status = xhr.status;
if (readyState == 4 && status == 200) {
var response = JSON.parse(xhr.responseText);
console.log(response);
}
}
xhr.send(params);
}
In this case, as a test, you can simply retrieve your access token by the following Google Apps Script. // DriveApp.getFiles() is put for automatically detecting the scope of Drive API for accessing Web Apps. The expiration time of this access token is 1 hour. Please be careful about this. When the expiry time is over, please retrieve the access token again.
const sample = _ => {
console.log(ScriptApp.getOAuthToken());
// DriveApp.getFiles()
}
When the above script is run, the following value is shown in the log by Logger.log(values).
{access_token=[###], employeeStatus=[Active], name=[Henry]} : This is due to Logger.log.
When console.log(values) is used, { access_token: [ '###' ], employeeStatus: [ 'Active' ], name: [ 'Henry' ]} is shown.
Note:
As another approach, for example, when you want to check the value of e of doPost, I think that you can store the value in a Spreadsheet as a log as follows. By this, when doPost is run, the value of e is stored in the Spreadsheet as a log. In this case, the access token is not required to be used.
function doPost(e) {
SpreadsheetApp.openById("###spreadsheetId###").getSheets()[0].appendRow([new Date(), JSON.stringify(e)]);
return ContentService.createTextOutput(JSON.stringify({ "a": 5, "b": 2 }))
}
Note
When you modified the Google Apps Script, please modify the deployment as a new version. By this, the modified script is reflected in Web Apps. Please be careful this.
You can see the detail of this in the report of "Redeploying Web Apps without Changing URL of Web Apps for new IDE".
Reference:
Taking advantage of Web Apps with Google Apps Script
I have a Google Sheets workspace addon and recently did some work to integrate BigQuery. Essentially BigQuery hold a record of books each of which has an author, title etc and my Addon allows people to pull the books that they have read into their sheet. The first column in the sheet allows people to choose from all the authors in the DB, based on that selection the second column is populated with data from BigQuery with all books by that author etc etc. There is no need for my AddOn to access a user's BigQuery, they only access 'my' BgQuery.
This all works fine, but when I submitted my addon for approval I was told
Unfortunately, we cannot approve your request for the use of the following scopes
https://www.googleapis.com/auth/bigquery
We recommend using service accounts for this type of information exchange.
This seems fair and reading up on Service Accounts it seems a much better fit for my use case. I've gone through the process of creating the service accounts and downloaded my security details json file, however I just can't figure out how to actually query BigQuery from AppScript.
In my non-service account method I have the BigQuery Library installed in AppScript and basically run
var queryResults = BigQuery.Jobs.query(request, projectId);
I've been trying to work from an example at https://developers.google.com/datastudio/solution/blocks/using-service-accounts
function getOauthService() {
var serviceAccountKey = getServiceAccountCreds('SERVICE_ACCOUNT_KEY');// from private_key not private_key_id of JSON file
var serviceAccountEmail = getServiceAccountCreds('SERVICE_ACCOUNT_EMAIL');
return OAuth2.createService('RowLevelSecurity')
.setAuthorizationBaseUrl('https://accounts.google.com/o/oauth2/auth')
.setTokenUrl('https://accounts.google.com/o/oauth2/token')
.setPrivateKey(serviceAccountKey)
.setIssuer(serviceAccountEmail)
.setPropertyStore(scriptProperties)
.setCache(CacheService.getScriptCache())
.setScope(['https://www.googleapis.com/auth/bigquery.readonly']);
}
function getData(request) {
var accessToken = getOauthService().getAccessToken();
var billingProjectId = getServiceAccountCreds('BILLING_PROJECT_ID');
// var email = Session.getEffectiveUser().getEmail();
// return cc
// .newBigQueryConfig()
// .setAccessToken(accessToken)
// .setBillingProjectId(billingProjectId)
// .setUseStandardSql(true)
// .setQuery(BASE_SQL)
// .addQueryParameter('email', bqTypes.STRING, email)
// .build();
}
I've commented out the code in the above which relates to
var cc = DataStudioApp.createCommunityConnector();
in the above tutorial since I'm not using DataStudio but I'm really not sure what to replace it with so I can query BigQuery with AppScript via a Service Account. Can anyone offer any advice?
Based on the advice from #TheAddonDepot in the comments above my revised code now looks like:
function getBigQueryService() {
return (
OAuth2.createService('BigQuery')
// Set the endpoint URL.
.setTokenUrl('https://accounts.google.com/o/oauth2/token')
// Set the private key and issuer.
.setPrivateKey(JSON_CREDS.private_key) // from the json file downloaded when you create service account
.setIssuer(JSON_CREDS.client_email). // from the json file downloaded when you create service account
// Set the property store where authorized tokens should be persisted.
.setPropertyStore(PropertiesService.getScriptProperties())
// Caching
.setCache(CacheService.getUserCache())
// Locking
.setLock(LockService.getUserLock())
// Set the scopes.
.setScope(['https://www.googleapis.com/auth/bigquery.readonly'])
// .setScope('https://www.googleapis.com/auth/bigquery')
)
}
function queryData(){
const bigQueryService = getBigQueryService()
if (!bigQueryService.hasAccess()) {
Logger.log("BQ ERROR IS "+ bigQueryService.getLastError())
}
//const projectId = bigqueryCredentials.project_id
var projectId = "<yourprojectid>"
let url = 'https://bigquery.googleapis.com/bigquery/v2/projects/<yourprojectid>/queries'; //projectID is taken from the security json file for the service account, although it doesn't seem to matter if you use the project code
const headers = {
Authorization: `Bearer ${bigQueryService.getAccessToken()}`,
'Content-Type': 'application/json',
}
var data = {query:"<your query>",useLegacySql:false};
const options = {
method: 'post',
headers,
//contentType: 'application/json',
payload: JSON.stringify(data),
muteHttpExceptions: true // on for debugging
}
try {
const response = UrlFetchApp.fetch(url, options)
const result = JSON.parse(response.getContentText())
Logger.log("here is result "+ JSON.stringify(result))
} catch (err) {
console.error(err)
}
}
I'm looking to see if anyone has created a chatbot in google apps script to handle webhooks for hangouts? I have a bot created but I'm not sure on how to input the webhook url into the bots code so that the message can be deployed into the chat room.
I have not exactly created what you are looking for, however I believe the answer you are looking for can be found here. Basically there is an event whenever your bot enters a space. When that event is triggered you can add the space id to a list stored somewhere.(Spreadsheet , PropertiesService,etc)
Once the list is stored you can deploy your application as a web app. You can read more about web apps here but two things you need to know is google gives you a url to make web requests to, as well as pair of events called doGet (when someone makes a get request) and doPost (when someone makes a post request). You can create the function do post and get the parameters when your web app is posted to.
Finally upon receiving the post you can do a fetch call to the google api to post the message you just received from the request to all of the spaces you are in by doing a api fetch call to each ID.
Below will be code directly posted from the API in the first link.
// Example bot for Hangouts Chat that demonstrates bot-initiated messages
// by spamming the user every minute.
//
// This bot makes use of the Apps Script OAuth2 library at:
// https://github.com/googlesamples/apps-script-oauth2
//
// Follow the instructions there to add the library to your script.
// When added to a space, we store the space's ID in ScriptProperties.
function onAddToSpace(e) {
PropertiesService.getScriptProperties()
.setProperty(e.space.name, '');
return {
'text': 'Hi! I\'ll post a message here every minute. ' +
'Please remove me after testing or I\'ll keep spamming you!'
};
}
// When removed from a space, we remove the space's ID from ScriptProperties.
function onRemoveFromSpace(e) {
PropertiesService.getScriptProperties()
.deleteProperty(e.space.name);
}
// Add a trigger that invokes this function every minute via the
// "Edit > Current Project's Triggers" menu. When it runs, it will
// post in each space the bot was added to.
function onTrigger() {
var spaceIds = PropertiesService.getScriptProperties()
.getKeys();
var message = { 'text': 'Hi! It\'s now ' + (new Date()) };
for (var i = 0; i < spaceIds.length; ++i) {
postMessage(spaceIds[i], message);
}
}
var SCOPE = 'https://www.googleapis.com/auth/chat.bot';
// The values below are copied from the JSON file downloaded upon
// service account creation.
var SERVICE_ACCOUNT_PRIVATE_KEY = '-----BEGIN PRIVATE KEY-----\n...\n-----END PRIVATE
KEY-----\n';
var SERVICE_ACCOUNT_EMAIL = 'service-account#project-id.iam.gserviceaccount.com';
// Posts a message into the given space ID via the API, using
// service account authentication.
function postMessage(spaceId, message) {
var service = OAuth2.createService('chat')
.setTokenUrl('https://accounts.google.com/o/oauth2/token')
.setPrivateKey(SERVICE_ACCOUNT_PRIVATE_KEY)
.setClientId(SERVICE_ACCOUNT_EMAIL)
.setPropertyStore(PropertiesService.getUserProperties())
.setScope(SCOPE);
if (!service.hasAccess()) {
Logger.log('Authentication error: %s', service.getLastError());
return;
}
var url = 'https://chat.googleapis.com/v1/' + spaceId + '/messages';
UrlFetchApp.fetch(url, {
method: 'post',
headers: { 'Authorization': 'Bearer ' + service.getAccessToken() },
contentType: 'application/json',
payload: JSON.stringify(message),
});
}
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);
Below my sample code
function getResponseData()
{
var service = googleOAuth();
var attachmentURL = https://sites.google.com/feeds/content/domainname/sitename?kind=attachment&start-index=1&max-results=500
if(service.hasAccess())
{
var dataResponse;
try
{
dataResponse = UrlFetchApp.fetch(attachmentURL, {method: 'get',headers: {Authorization: 'Bearer ' + service.getAccessToken()}});
}
catch(e)
{
Logger.log(e);
}
}
function googleOAuth()
{
var service = OAuth2.createService('sites')
.setAuthorizationBaseUrl('')
.setTokenUrl('').setClientId('').setClientSecret('').setProjectKey('').setCallbackFunction('authCallback')
.setPropertyStore(PropertiesService.getScriptProperties()).setScope('')
.setParam('login_hint', Session.getActiveUser().getEmail()).setParam('access_type', 'offline')
.setParam('approval_prompt', 'force');
return service;
}
function authCallback(request)
{
var driveService = googleOAuth();
var isAuthorized = driveService.handleCallback(request);
if(isAuthorized)
{
return HtmlService.createHtmlOutput('Success! You can close this tab.');
}
else
{
return HtmlService.createHtmlOutput('Denied. You can close this tab');
}
}
I have attached the google sites page 1500 documents and but get the lastest attachment document below 1000. how to get all documents. please help me.I have get the 996 document only retrieve. how to another document retrieve.please help me.
I created a google site of my own, uploaded 1500 files and wrote google apps script (similar to yours) to replicate this issue. As it turns out, I too am not able to retrieve files after the first 1000. This is happening even if I change the start-index and max-results. The only reason I can think of is that this is an undocumented limitation. There must be a limit of 1000 for the number of attachments. I might be wrong.
The limits for attachments to a Google Site are mentioned in this link - Storage and file limits