Get metadata from file not created by Google Apps Script - google-apps-script

I am trying to create a Google Apps Script, that fetches the version number of a single file. Basically to monitor if the file was changed/opened, etc.
I want to do this by just using the drive.file API, to avoid having to go through the security assessment check, which becomes necessary (?) when using restricted access APIs.
I got it working if I create the file directly in the GAS but couldn't figure out how to get the metadata from user-created files.
This is the code (super-simple, though):
function myFunction() {
var test = {
title: 'myFile',
"parents": [{'id':"[ID]"}],
mimeType: 'text/plain'
};
file = Drive.Files.insert(test);
file_id = file.getId();
i = 0;
while (i<5) {
Utilities.sleep(5000);
output = Drive.Files.get(file_id).version;
Logger.log(output);
i += 1;
}
}
The APIs are set in the appsscript.json file via:
{
"timeZone": "Asia/Tokyo",
"oauthScopes": [
"https://www.googleapis.com/auth/drive.file",
"https://www.googleapis.com/auth/drive.appfolder",
"https://www.googleapis.com/auth/drive.appdata"
],
"dependencies": {
"enabledAdvancedServices": [{
"userSymbol": "Drive",
"serviceId": "drive",
"version": "v2"
}]
},
"exceptionLogging": "STACKDRIVER"
}
Is there any way to achieve getting this info without full API access to the user's Google Drive?
Your help is much appreciated!
- Fabian

You can use Google picker to let the user choose what file he may share with your project with no more permissions than the ones listed in the question.

You can use the drive.metadata.readonly, or drive.metadata scope. Of course you need at least read access to that file.
var params = {
supportsTeamDrives: true,
includeTeamDriveItems: true
};
params.fields = 'modifiedDate, version';
var modTime = Drive.Files.get(fileId, params);
Logger.log(modTime);

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

Container bound apps script deploy from manifest can allow other user read data from container spreadsheet without sharing file

I am developing chatbot through container (Spreadsheet) bound apps script deploy from manifest for may internal organization members, the members can request info stored on the sheets by simply typing and sending through google chat.
Members can get the info automatically by the chatbot if I share the spreadsheet, otherwise the bot is not responding message replied.
When I share the file, the shard copy also appear to all of the members drive too. I want make that content read by user without sharing the Container spreadsheet.
What code I have to add to my script so that user get the permission to read my file during chatting. Is this possible?
My manifest code is as follows
{
"dependencies": {
"enabledAdvancedServices": [
{
"userSymbol": "Sheets",
"serviceId": "sheets",
"version": "v4"
}
],
"libraries": [
{
"userSymbol": "OAuth2",
"libraryId": "1B7FSrk5Zi6L1rSxxTDgDEUsPzlukDsi4KGuTMorsTQHhGBzBkMun4iDF",
"version": "40"
}
]
},
"exceptionLogging": "STACKDRIVER",
"runtimeVersion": "V8",
"chat": {
"addToSpaceFallbackMessage": "Hi ! Thanks for adding me. Type help to get more.."
}
}
and user oauth2 ..
/**
* Configures the Chatbot service.
*/
function getChatbotService() {
return OAuth2.createService("chat-sheet-bot")
// Set the endpoint URL.
.setTokenUrl("https://accounts.google.com/o/oauth2/token")
// Set the private key and issuer.
.setPrivateKey(PRIVATE_KEY)
.setIssuer(CLIENT_EMAIL)
// Set the property store where authorized tokens should be persisted.
.setPropertyStore(PropertiesService.getScriptProperties())
// Set the scope.
.setScope("https://www.googleapis.com/auth/chat.bot https://www.googleapis.com/auth/spreadsheets");
}
Finally I have solved the problem as follows:
added get service method with spreadsheet scope as
/**
* Configures the spreadsheet service.
*/
function getSpreasheetService() {
return OAuth2.createService("spreadsheet")
// Set the endpoint URL.
.setTokenUrl("https://accounts.google.com/o/oauth2/token")
// Set the private key and issuer.
.setPrivateKey(PRIVATE_KEY)
.setIssuer(CLIENT_EMAIL)
// Set the property store where authorized tokens should be persisted.
.setPropertyStore(PropertiesService.getScriptProperties())
// Set the scope.
.setScope("https://www.googleapis.com/auth/spreadsheets");
}
added method read sheet to get data from spreadsheet by the service account using spreadsheet api
function readSheet(){
var service = getSpreasheetService();
var ssID = 'your Spreadsheet Id'
var range = 'Sheet1!A3:E';
var url = 'https://sheets.googleapis.com/v4/spreadsheets/' + ssId +'/values/' + range;
var response = UrlFetchApp.fetch(url, { headers: {Authorization: 'Bearer ' + service.getAccessToken() } });
var rep = JSON.parse(response.getContentText());
var values = rep.values;
for(row in values)
Logger.log(values[row][0] + ":" + values[row][2] + ":" + values[row][3] + ":" + values[row][4]);
//now i can use these data to reply back to end user as message
}
I shared the spreadsheet to the service account which looks like "your-service-account#sys................iam.gserviceaccount.com"
I enabled the Sheet API in Google Cloud Project console on API & Services
oh! it works with great charm.

How to create a spreadsheet with an associated 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!

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

Drive.Files.Copy and "parents" not working

I'm trying to copy a file in Team Drives to a new folder location, also in Team Drives. I get a "File not found" error on the last line of code. The newFileID has been checked using DriveApp.getFileByID and by testing in Google API Try-It.
I think the "parents" piece is incorrectly formed. When I try Google API Try-It, the file is copied into the correct folder. Yay! So what's wrong with the Google Script code?
https://developers.google.com/drive/api/v3/reference/files/copy#try-it
Google Script code (not working):
function test() {
// find Teacher's Learner Guides folder
var destinationFolderId = "1qQJhDMlHZixBO9KZkkoSNYdMuqg0vBPU";
var newFile = {
"name": "Learner Guide - test",
"description": "New student learner guide",
"parents": [destinationFolderId]
};
// create duplicate document
var newFileID = "1g6cjUn1BWVqRAIhrOyXXsTwTmPZ4QW6qGhUAeTHJSUs";
var newDoc = Drive.Files.copy(newFile, newFileID);
}
The Google API Try-It code works. Here's the javascript (working):
return gapi.client.drive.files.copy({
"fileId": "1g6cjUn1BWVqRAIhrOyXXsTwTmPZ4QW6qGhUAeTHJSUs",
"supportsTeamDrives": true,
"resource": {
"parents": [
"1qQJhDMlHZixBO9KZkkoSNYdMuqg0vBPU"
],
"name": "Learner Test2"
}
})
What would be an efficient and/or correct way of using Drive.Files.Copy in Google Script code to place the copied file into a different folder?
The parents metadata associated with the request expects a ParentReference resource for Drive API v2, which is at minimum an object with an id property and the associated fileId, e.g. {id: "some id"}.
Since you are working with Team Drives, you must tell Google that you (i.e. your code) know how to handle the associated differences between regular & Team Drives, with the supportsTeamDrives optional parameter.
Note:
A parent does not appear in the parents list if the requesting user is a not a member of the Team Drive and does not have access to the parent. In addition, with the exception of the top level folder, the parents list must contain exactly one item if the file is located within a Team Drive.
Assuming the code runner meets the criteria, the most simple code to copy a given file to a given Team Drive folder is:
function duplicate_(newName, sourceId, targetFolderId) {
if (!newName || !sourceId || !targetFolderId)
return;
const options = {
fields: "id,title,parents", // properties sent back to you from the API
supportsTeamDrives: true, // needed for Team Drives
};
const metadata = {
title: newName,
// Team Drives files & folders can have only 1 parent
parents: [ {id: targetFolderId} ],
// other possible fields you can supply:
// https://developers.google.com/drive/api/v2/reference/files/copy#request-body
};
return Drive.Files.copy(metadata, sourceId, options);
}
Additional reading:
Standard Query Parameters (these can always be passed in the optional argument)
Partial Responses (aka "fields")
Here's the solution for copying files in Team Drives. #tehhowch had an important piece about needing the optional parameters (you need to use all three parameters for copy API v2). Then the "parents" argument requires a File object, not a string. The code below works by copying the file and moving it into another Team Drives folder.
function test() {
// find Teacher's Learner Guides folder
var destFolderId = "1qQJhDMlHZixBO9KZkkoSNYdMuqg0vBPU";
var originalDocID = "1g6cjUn1BWVqRAIhrOyXXsTwTmPZ4QW6qGhUAeTHJSUs";
var destFolder = Drive.Files.get(destFolderId, {"supportsTeamDrives": true});
var newFile = {
"fileId": originalDocID,
"parents": [
destFolder // this needed to be an object, not a string
]
};
var args = {
"resource": {
"parents": [
destFolder // this needed to be an object, not a string
],
"title": "new name of document here"
},
"supportsTeamDrives": true
};
// create duplicate Learner Guide Template document
var newTargetDoc = Drive.Files.copy(newFile, originalDocID, args);
}