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
Related
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)
I wrote a test web app using apps script. I deployed it to execute as "User accessing the web app" and set access to "Anyone who has a Google account". I then tried to call the web app function from another script which acts as the client. I did all of this while logged into my google account, both web app and client are in the same browser window (in different tabs). But I always get a 401 error. I then tried adding some oauthScopes to the appsscript.json file for the client script, but this didn't help. Any help solving this would be appreciated.
Web app code:
function doPost(e) {
if(typeof e !== 'undefined') {
let par = e.parameter;
let retval = par.x*3;
let retobj = {"retval":retval};
return ContentService.createTextOutput(JSON.stringify(retobj));
}
}
Client code:
function callWebApp(x) {
Logger.log("starting call webapp");
var payload = {
"info" : "some text",
"x" : 3,
"type" : "post",
};
var options = {
"method" : "POST",
"payload" : payload,
"followRedirects" : true,
"muteHttpExceptions": true
};
var result = UrlFetchApp.fetch(urlString, options);
Logger.log(result.getResponseCode());
if (result.getResponseCode() == 200) {
Logger.log('got response');
var params = JSON.parse(result.getContentText());
Logger.log(params.retval);
}
return 1;
}
appscript.json for client:
{
"timeZone": "America/New_York",
"dependencies": {
},
"exceptionLogging": "STACKDRIVER",
"runtimeVersion": "V8",
"oauthScopes": [
"https://www.googleapis.com/auth/script.deployments",
"https://www.googleapis.com/auth/script.deployments.readonly",
"https://www.googleapis.com/auth/script.external_request",
"https://www.googleapis.com/auth/script.projects",
"https://www.googleapis.com/auth/script.processes",
"https://www.googleapis.com/auth/script.projects.readonly"
]
}
Execution log:
7:47:30 PM Notice Execution started
7:47:30 PM Info starting call webapp
7:47:30 PM Info 401.0
7:47:30 PM Notice Execution completed
Modification points:
When you deployed the Web Apps as Execute as: User accessing the web app and Who has access: Anyone with Google account and you want to access to the Web Apps using the script (in this case, it's Google Apps Script.), it is required to request to the Web Apps by including the access token in the request header.
And, when the access token is used for requesting to the Web Apps, please include the scope of https://www.googleapis.com/auth/drive.readonly and/or https://www.googleapis.com/auth/drive to your current scopes in your client.
In your script, it seems that urlString is not declared. Although I'm not sure whether this might be declared elsewhere, please confirm it again.
When these points are reflected to your script, it becomes as follows.
Modified script:
From:
var options = {
"method" : "POST",
"payload" : payload,
"followRedirects" : true,
"muteHttpExceptions": true
};
To:
var options = {
"method": "POST",
"payload": payload,
"muteHttpExceptions": true,
"headers": {authorization: "Bearer " + ScriptApp.getOAuthToken()}
};
Note:
If you want to make users request to your Web Apps using a script, it is also required to request to the Web Apps by including the access token in the request header. And also, it is required to share the Google Apps Script project with the user. Please be careful this.
If you cannot retrieve your expected result when above script is run, please redeploy the Web Apps as new version and test it again.
References:
Web Apps
Taking advantage of Web Apps with 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!
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
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)
}
};