How to create a spreadsheet with an associated script? - google-apps-script

I'd like to programatically create a batch of spreadsheets which contain different data, but all of which contain a button that is associated with a custom backend function.
For example, each spreadsheet should have a button that, when pressed exports the data to another sheet.
Is such a thing possible?
One idea I had was maybe to create a template that includes the button and associated Apps Script and then make a copy of that spreadsheet and fill it with the custom data.

The Apps-Script API allows you to programmatically create Apps Script projects with the option of binding them to a Google Sheet
Convert your project into a Cloud Platform project and enable Apps Script API for this project
Give your projects the necessary scopes in the manifest file
Incorporate Apps Script API into Apps Script with a Urlfetch request
Create a new Apps Script Project with Method: projects.create specifying the parentId
Add contents to the project with Method: projects.updateContent. You can store the contents in a variable and thus add the same content to all of your Apps Script projects.
Sample:
JSON file
{
"timeZone": "America/New_York",
"oauthScopes": [
"https://www.googleapis.com/auth/spreadsheets",
"https://www.googleapis.com/auth/script.projects",
"https://www.googleapis.com/auth/script.external_request"
],
"dependencies": {
},
"exceptionLogging": "STACKDRIVER"
}
.gs file
function createSpreadsheetwithScript() {
var ss=SpreadsheetApp.create('mySpreadsheet');
var id=ss.getId();
var token = ScriptApp.getOAuthToken();
var url = "https://script.googleapis.com/v1/projects";
var payload = {
"title": "myAutoCreatedScript",
"parentId": id
}
var options = {
"method" : "POST",
"muteHttpExceptions": true,
"headers": {
'Authorization': 'Bearer ' + token
},
"contentType": "application/json",
"payload": JSON.stringify(payload)
}
var response = UrlFetchApp.fetch(url,options);
var scriptId=JSON.parse(response).scriptId;
var url2="https://script.googleapis.com/v1/projects/"+scriptId+"/content";
//test content
var source="function myFunction() {\n var x=1;\n}";
var JSONsource="{\"timeZone\":\"America/New_York\",\"exceptionLogging\":\"STACKDRIVER\"}";
var payload2 = {
"files": [
{
"name": "this is the gs. file",
"type": "SERVER_JS",
"source": source
},
{
"name": "appsscript",
"type": "JSON",
"source": JSONsource,
"updateTime":"2018-03-04T19:49:08.871Z",
"functionSet":{
"values":[{"name":"myFunction"}]}
}
]
}
var options2 = {
"headers": {
'Authorization': 'Bearer ' + token,
},
"contentType": "application/vnd.google-apps.script+json",
"method" : "PUT",
"payload": JSON.stringify(payload2)
}
var response2 = UrlFetchApp.fetch(url2,options2);
}
Make sure to enable the Apps-script API before using it under https://script.google.com/home/usersettings and that your upadteContent request inclusdes a manifest file
More samples

To write a custom function:
Create or open a spreadsheet in Google Sheets.
Select the menu item Tools > Script editor. If you are presented with a welcome screen, click Blank Project on the left to start a new project.
Delete any code in the script editor....
Select the menu item File > Save....
All done!

Related

Update gs files from script using API

Can you please tell me if it is possible to change the contents of the gs files of the same project from the google app script. This is required to receive code updates.
I am looking for opportunities to use the API, similar to how google clasp does it.
How realistic is it to do this?
[UPDATE]
Try this code:
var scriptID = ScriptApp.getScriptId();
var url = 'https://script.googleapis.com/v1/projects/' + scriptID + '/content';
var token = ScriptApp.getOAuthToken();
var options = {
'method' : 'get',
'headers' : {'Authorization':'Bearer '+ token},
'muteHttpExceptions' : true
};
var response = UrlFetchApp.fetch(url, options);
var json = JSON.parse(response.getContentText());
Logger.log(json);
In Logger:
{error={details=[{domain=googleapis.com, #type=type.googleapis.com/google.rpc.ErrorInfo, reason=ACCESS_TOKEN_SCOPE_INSUFFICIENT, metadata={service=script.googleapis.com, method=google.apps.script.management.v1.ProjectsService.GetContent}}], message=Request had insufficient authentication scopes., code=403.0, status=PERMISSION_DENIED}}
As Tanaike mentioned, you can in fact edit gs files from Google Apps Script using the Google Apps Script API.
Actually Google Clasp does use the Google Apps Script API to make the exact same type of changes.
You can take the following request as an example using the method projects.updateContent to make a simple change in the source parameter. In addition to that, you need to make sure that the Google Apps Script API is enabled from your Apps Script Settings, and not only add the gs file you want to send in the request, but also the appsscript.json file from the manifest is required in the request.
{
"files": [
{
"name": "appsscript",
"type": "JSON",
"source": "{\n \"timeZone\": \"America/El_Salvador\",\n \"dependencies\": {},\n \"exceptionLogging\": \"STACKDRIVER\",\n \"runtimeVersion\": \"V8\"\n}"
},
{
"name": "Code",
"type": "SERVER_JS",
"source": "function myFunction() {\n Logger.log(\"This is a test\");\n}\n",
"functionSet": {
"values": [
{
"name": "myFunction"
}
]
}
}
]
}
References:
Method: projects.updateContent
Clasp

How to request specific data from the Google Search Console API using Google Apps Script?

I'm trying to recover Google Discover data from the Google Search Console through its API and display it in a Google Sheet.
I'm following a tutorial that walked me through setting up the needed authentications in the Google Cloud platform and showed me how to set up a basic request that retrieves a list of websites I have access to in the Search Console.
What I don't understand is how to modify the code below so that the API retrieves Google Discover data instead of a list of websites I have access to.
The Google Apps Script code gets the mentioned websites and populates two columns, one with the website link and another with the access level I have for that website.
function listAccountSites() {
var service = getService();
if (service.hasAccess()) {
var apiURL = "https://www.googleapis.com/webmasters/v3/sites";
var headers = {
"Authorization": "Bearer " + getService().getAccessToken()
};
var options = {
"headers": headers,
"method" : "GET",
"muteHttpExceptions": true
};
var response = UrlFetchApp.fetch(apiURL, options);
var json = JSON.parse(response.getContentText());
Logger.log(json)
var URLs = []
for (var i in json.siteEntry) {
URLs.push([json.siteEntry[i].siteUrl, json.siteEntry[i].permissionLevel]);
}
s_sites.getRange(2,1,URLs.length,2).setValues(URLs);
} else {
var authorizationUrl = service.getAuthorizationUrl();
Logger.log('Open the following URL and re-run the script: %s', authorizationUrl);
Browser.msgBox('Open the following URL and re-run the script: ' + authorizationUrl)
}
}
Using this Google documentation page I was able to construct an API request that would get the Google Discover data I need, but as I said I cannot figure out how to do this. The mentioned request gets the data I need is the following one:
{
"startDate": "2021-11-01",
"endDate": "2021-11-05",
"dimensions": [
"PAGE"
],
"type": "DISCOVER"
}
I believe your goal is as follows.
You want to achieve the following request using Google Apps Script. Ref
POST https://www.googleapis.com/webmasters/v3/sites/https%3A%2F%2Fwww.example.com%2F/searchAnalytics/query?key={MY_API_KEY}
{
"startDate": "2015-04-01",
"endDate": "2015-05-01",
"dimensions": ["country","device"]
}
In your situation, you want to use the following request body.
{
"startDate": "2021-11-01",
"endDate": "2021-11-05",
"dimensions": [
"PAGE"
],
"type": "DISCOVER"
}
If my understanding is correct, how about the following sample script?
Sample script:
This sample script supposes that your access token can be used and you have already been able to use the API. Please be careful this.
var siteUrl = "https://www.example.com/"; // Please set the site URL.
var apiURL = `https://www.googleapis.com/webmasters/v3/sites/${encodeURIComponent(siteUrl)}/searchAnalytics/query`;
var headers = { "Authorization": "Bearer " + getService().getAccessToken() };
var payload = {
"startDate": "2021-11-01",
"endDate": "2021-11-05",
"dimensions": ["PAGE"],
"type": "DISCOVER"
};
var options = {
"headers": headers,
"method": "POST",
"muteHttpExceptions": true,
"contentType": "application/json",
"payload": JSON.stringify(payload),
};
var response = UrlFetchApp.fetch(apiURL, options);
References:
Search Analytics: query
fetch(url, params)

getOAuthToken returns null when called from Google Sheets

I'm working on a way to limits some Google Sheets for specific users, with the oAuth specification and AWS API Gateway calls, but i'm facing a problem with the ScriptApp.getOAuthToken() function.
When i'm running the code with the Google Apps Script debugger, everything's fine, ScriptApp.getOAuthToken() returns me a token i can pass to my AWS API. The expected result for now is just to recieve the username.
But if i try to use my function as a macro in a Google Sheets cell, i have the following error Header:null (line 13)
Here is the code in the Code.gs file
function HelloW() {
var token = ScriptApp.getOAuthToken();
var headers = {
'Authorization' : token
}
var options = {
'headers' : headers,
'method' : 'post',
'contentType': 'application/json',
'payload' : JSON.stringify(data)
};
var response = UrlFetchApp.fetch('https://###/demo-lambda', options);
var txt = response.getContentText();
var json = JSON.parse(txt);
var name = json.Message;
return name;
}
And the manifest just in case
{
"timeZone": "Europe/Paris",
"dependencies": {
},
"exceptionLogging": "STACKDRIVER",
"oauthScopes": ["https://www.googleapis.com/auth/script.external_request",
"https://www.googleapis.com/auth/spreadsheets",
"https://www.googleapis.com/auth/userinfo.email",
"https://www.googleapis.com/auth/drive"],
"sheets": {
"macros": [{
"menuName": "HelloW",
"functionName": "HelloW"
}]
}
}
I've got an error because token is null, but i don't understand why it runs well with the debugger, and it doesn't in the Sheets document. I'm missing something and i don't find what.
Any help would be much appreciated.
You cannot make calls inside macros that require user authorization.
Unlike most other types of Apps Scripts, custom functions never ask
users to authorize access to personal data. Consequently, they can only call services that do not have access to personal data.
Source

Google Apps Standalone Script to use Google Apps Script API to update many bound scripts

I am trying to write a standalone Google Apps script that uses the Google Apps Script API to update the the bound script contents of many Google Sheets.
I have the Sheet IDs of roughly 200 Google Sheets that I've created from a template. I would like to update the project contents of the bound scripts on each of these sheets to be identical to a set of master scripts.
I am stuck with an authentication error while using the urlFetchApp to get the contents of the bound script of one sheet as a test. The error looks like:
Request failed for
https://script.googleapis.com/v1/projects/<SCRIPTID>/content returned code 401.
Truncated server response: { "error": { "code": 401,
"message": "Request is missing required authentication credential.
Expected OAuth 2 access token, login cookie ...
(use muteHttpExceptions option to examine full response) (line 34, file "AddScriptsToSheets")
The test function I'm using looks like:
function getSheetScriptContent(sheetId) {
var sheet = SpreadsheetApp.openById(sheetId);
// Make a POST request with a JSON payload.
// Make a GET request and log the returned content.
var url = PROJECTS_GET_CONTENT_URL.format(sheetId);
var response = UrlFetchApp.fetch(url);
Logger.log(response.getContentText());
}
I think this OAuth2 library may be useful in this case, I'm just not sure how to use it. Could anyone point me in the right direction?
If you own all the files, then you don't need to use an OAuth library or any special code to get the access token. You can get the access token from the ScriptApp class.
var theAccessTkn = ScriptApp.getOAuthToken();
You may need to manually edit the appsscript.json manifest file and add the scope:
https://www.googleapis.com/auth/script.projects
appsscript.json
{
"timeZone": "Yours will display here",
"dependencies": {
},
"webapp": {
"access": "MYSELF",
"executeAs": "USER_DEPLOYING"
},
"exceptionLogging": "STACKDRIVER",
"oauthScopes": [
"https://www.googleapis.com/auth/drive",
"https://www.googleapis.com/auth/script.projects",
"https://www.googleapis.com/auth/drive.scripts",
"https://www.googleapis.com/auth/script.container.ui",
"https://www.googleapis.com/auth/script.external_request"
],
"runtimeVersion": "DEPRECATED_ES5"
}
Code to overwrite an Apps Script file:
function updateContent(scriptId,content,theAccessTkn) {
try{
var options,payload,response,url;
if (!content) {
//Error handling function
return;
}
if (!theAccessTkn) {
theAccessTkn = ScriptApp.getOAuthToken();
}
//https://developers.google.com/apps-script/api/reference/rest/v1/projects/updateContent
url = "https://script.googleapis.com/v1/projects/" + scriptId + "/content";
options = {
"method" : "PUT",
"muteHttpExceptions": true,
"headers": {
'Authorization': 'Bearer ' + theAccessTkn
},
"contentType": "application/json",//If the content type is set then you can stringify the payload
"payload": JSON.stringify(content)
};
response = UrlFetchApp.fetch(url,options);
//Logger.log('getResponseCode ' + response.getResponseCode())
//Logger.log("Response content: " + response.getContentText())
} catch(e) {
console.log("Error: " + e + "\nStack: " + e.stack)
}
};

can't open Slack dialog through google apps scripts

I'm trying to use google apps scripts and slack to automate my work. And I wish to enter some text with the Slack dialog to modify my google spreadsheet with google apps scripts. However, with the below code, I can't open a Dialog via Slack-API's Slash command. Is my code have some problems?
function doPost(e){
var params = e.parameter;
var token = params.token;
var text = params.text;
var trigger_id = params.trigger_id;
var slackUrl = ["https://slack.com/api/dialog.open"];
if (token == "[token from slack]"){
var dialog = {
"token": "[OAuth Token]",
"trigger_id":trigger_id,
"dialog":{
"callback_id": "ryde-46e2b0",
"title": "Request a Ride",
"submit_label": "Request",
"elements": [
{
"type": "text",
"label": "Pickup Location",
"name": "loc_origin"
},
{
"type": "text",
"label": "Dropoff Location",
"name": "loc_destination"
}
]
}
};
var options = {
'method' : 'POST',
'contentType': 'application/json',
'payload' : dialog};
UrlFetchApp.fetch(slackUrl, options);
}
else{
var res = {"text":"failed token verification!"}
return ContentService.createTextOutput(JSON.stringify(res)).setMimeType(ContentService.MimeType.JSON);
}}
How about this modification?
Modification points :
Use a string to "url" of "UrlFetchApp.fetch(url, params)".
Use JSON.stringify() for dialog of the object dialog.
'contentType': 'application/json', is not required.
Modified script :
function doPost(e) {
var params = e.parameter;
var token = params.token;
var text = params.text;
var trigger_id = params.trigger_id;
var slackUrl = "https://slack.com/api/dialog.open";
if (token == "[token from slack]"){ // Please input this.
var dialog = {
"token": "[OAuth Token]", // Please input this.
"trigger_id": trigger_id,
"dialog": JSON.stringify({
"callback_id": "ryde-46e2b0",
"title": "Request a Ride",
"submit_label": "Request",
"elements": [
{
"type": "text",
"label": "Pickup Location",
"name": "loc_origin"
},
{
"type": "text",
"label": "Dropoff Location",
"name": "loc_destination"
}
]
})
}
var options = {
'method' : 'post',
'payload' : dialog,
};
UrlFetchApp.fetch(slackUrl, options);
}
else{
var res = {"text":"failed token verification!"}
return ContentService.createTextOutput(JSON.stringify(res)).setMimeType(ContentService.MimeType.JSON);
}
return ContentService.createTextOutput(); // Important
}
Note :
When there are no exceptions within the dialog submission, your app must respond with 200 OK with an empty body. This will complete the dialog.
When it uses dialog, it returns the empty body using ContentService.createTextOutput() for above, because the status code cannot be customized by Google Apps Script. When the empty body is not returned, the error occurs.
This modified script supposes that your setting for using Slack dialog has already been done.
If you modified your script, please redeploy Web Apps as a new version. By this, the script of the latest version is reflected to Web Apps.
References :
UrlFetchApp.fetch()
In my environment, I confirmed that this modified script works. But if this didn't work, I'm sorry.