"setOwner" not a function error - App Script - google-apps-script

new to Google Scripts and I've looked through other posts on Stack Overflow as well but couldn't find a good answer.
I'm using data collected in Google Sheets to search for a file in Google Drive and transfer ownership of the file. I have google form that my users fill out, once submitted using an add-on I create a file based on the data that was submitted on the form. Now with the script, I'm trying to go gather certain information from sheets such as name, email, and company name -
Sample data image here.
What I have thus far:
function myFunction() {
//Get google sheets
var spreadsheetId = '1WvIIoYdmuIB5BQ3KgSYOOIiEn-K_GTzCkb7rITzRFck';
//get certain values from sheets
var rangeName = 'MDP Form!C25:E';
var values = Sheets.Spreadsheets.Values.get(spreadsheetId, rangeName).values;
if (!values) {
Logger.log('No data found.');
} else {
Logger.log('Name, Email, Customer:');
for (var row = 0; row < values.length; row++) {
// Print columns C and E, which correspond to indices 0 and 4.
Logger.log('Name: %s, Email: %s, Company: %s', values[row][0], values[row][1], values[row][2]);
//Utilities.sleep(90000);
//Searching through google drive
var name = (values[row][0]);
var email = (values[row][1]);
Logger.log(email);
var company = (values[row][2]);
var fileName = ('Mutual Delivery Plan ' + company + ' - ' + name);
Logger.log(fileName);
//add a 1 minute delay
//Utilities.sleep(90000);
//search for target folder
var folder = DriveApp.getFolderById('1whvRupu9hWdyl2CqSF-KvdVj8VE6iiQu');
//search for file by name within folder
var mdpFile = folder.searchFiles(fileName);
//transfer ownership
mdpFile.setOwner(email);
}
}
}
Problem:
The script works for the most part except for the last line "setOwner" is not a function. I've tried creating a separate function for this, used some other suggestions on other posts but still cannot get this to work. If anyone has ideas around what might I be missing here or suggestions that would be super helpful. Thanks!

I believe your goal as follows.
You want to transfer the owner of the file when the file with fileName is found in folder.
For this, how about this answer?
Modification points:
Although you say The script works for the most part except for the last line "setOwner" is not a function., if your script in your question is the current script, how about the following modification?
In your script, fileName is 'Mutual Delivery Plan ' + company + ' - ' + name, and fileName is used with var mdpFile = folder.searchFiles(fileName);. In this case, an error occurs. Because params of searchFiles(params) is required to be the query string.
I think that in your case, it's "title='" + fileName + "'".
Also searchFiles(fileName) returns FileIterator. This has already mentioned by the existing answer. Because at Google Drive, the same filenames can be existing in the same folder and each files are managed by the unique ID. So here, it is required to be modified as follows.
I think that in your case, the following flow is useful.
Confirm whether the file is existing using hasNext().
When the file is existing and the owner is you, the owner of the file is changed to email.
When above points are reflected to your script, please modify as follows.
Modified script:
From:
var mdpFile = folder.searchFiles(fileName);
//transfer ownership
mdpFile.setOwner(email);
To:
var mdpFile = folder.searchFiles("title='" + fileName + "'");
while (mdpFile.hasNext()) {
var file = mdpFile.next();
if (file.getOwner().getEmail() == Session.getActiveUser().getEmail()) {
file.setOwner(email);
}
}
If you don't need to check whether the owner is you, please remove if (file.getOwner().getEmail() == Session.getActiveUser().getEmail()) {.
Note:
In this case, when the file with the filename of fileName is not existing in folder, the script in the if statement is not run. Please be careful this.
Also, when there are several files with the same filename in folder, the owner of those is changed to email.
References:
searchFiles(params)
FileIterator
hasNext()
next()
getActiveUser()

Folder.searchFiles() returns a fileIterator not a file. If it's the only file with that name then you can usually getaway with mdpFile.next();
File Iterator

Related

Efficient Way of sending Spreadsheet over email using GAS function?

I am creating an addon for Google Sheets that my local High School's volunteer clubs can use to keep track of their member's volunteer hours. Most of the code is done and works very nicely, and I am currently working on a system that will send a member a spreadsheet listing all of the volunteer events that they have logged. I have GAS create a separate spreadsheet, and then send an email with that separate spreadsheet attached in PDF. When the email is received, the PDF is empty except for a singular empty cell at the top left of the page.
I am pretty new to GAS but have been able to grasp the content pretty easily. I have only tried one method of sending the Spreadsheet and that is by using the .getAs(MimeType.PDF). When I changed the "PDF" to "GOOGLE_SHEETS," GAS returned the error: "Blob object must have non-null data for this operation." I am not entirely sure what a Blob object is, and have not found any website or video that has fully explained it, so I am not sure how to go about troubleshooting that error.
I think I'm having a problem grabbing the file because it either sends an empty PDF or it returns an error claiming it needs "non-null data."
function TigerMail()
{
var Drive = DriveApp;
var app = SpreadsheetApp;
var LOOKUP = app.getActiveSpreadsheet().getSheetByName("Student
Lookup");
var Name = LOOKUP.getRange("E1").getValue();
Name = Name + "'s Hours";
//app.openById(Name+"'s Hours");
var HOURS = app.create(Name);
var ESheet = HOURS.getSheets()[0];
var ROW = LOOKUP.getLastRow();
var arr = LOOKUP.getRange("D1:J"+ROW).getValues();
var cell = ESheet.getRange("A1:G"+ROW);
cell.setValues(arr);
////////////////////////////////////////////////////
var LOOKUP = app.getActiveSpreadsheet().getSheetByName("Student
Lookup");
var cell = LOOKUP.getRange("D1");
var Addr = cell.getValue();
var ROW = LOOKUP.getLastRow();
var file = Drive.getFilesByName(Name);
var file = file.next();
var FORMAT = file.getAs(MimeType.GOOGLE_SHEETS);
TigerMail.sendEmail(Addr, "Hours", "Attached is a list of all of the
events you have volunteered at:", {attachments: [FORMAT]} );
}
the final four lines are where the errors are occurring at. I believe I am misunderstanding how the .next() and .getFilesByName() work.
(above the comment line: creating a spreadsheet of hours)
(below the comment line: grabbing the spreadsheet and attaching it to an email)
Here is the link to the Google Sheet:
https://docs.google.com/spreadsheets/d/1qlUfTWaj-VyBD2M45F63BtHaqF0UOVkwi04XwZFJ4vg/edit?usp=sharing
In your script, new Spreadsheet is created and put values.
You want to sent an email by attaching the file which was converted from the created Spreadsheet to PDF format.
If my understanding is correct, how about this modification? Please think of this as just one of several answers.
Modification points:
About Drive.getFilesByName(Name), unfortunately, there is no method of getFilesByName() in Drive.
I think that when you want to use the created Spreadsheet, HOURS of var HOURS = app.create(Name) can be used.
About var FORMAT = file.getAs(MimeType.GOOGLE_SHEETS), in the case of Google Docs, when the blob is retrieved, the blob is automatically converted to PDF format. This can be also used for your situation.
In order to save the values put to the created Spreadsheet, it uses SpreadsheetApp.flush().
When above points are reflected to your script, it becomes as follows.
Modified script:
Please modify as follows.
From:
var file = Drive.getFilesByName(Name);
var file = file.next();
var FORMAT = file.getAs(MimeType.GOOGLE_SHEETS);
To:
SpreadsheetApp.flush();
var FORMAT = HOURS.getBlob();
Note:
In your script, it seems that var ROW = LOOKUP.getLastRow() is not used.
References:
flush()
getBlob()
If I misunderstood your question and this was not the result you want, I apologize.

Google Apps Script - get URL of File in Drive with File Name

I am attempting to create a form in Google Spreadsheets which will pull an image file from my Drive based on the name of the file and insert it into a cell. I've read that you can't currently do this directly through Google Scripts, so I'm using setFormula() adn the =IMAGE() function in the target cell to insert the image. However, I need the URL of the image in order to do this. I need to use the name of the file to get the URL, since the form concatenates a unique numerical ID into a string to use the standardized naming convention for these files. My issue is that, when I use getFilesByName, it returns a File Iteration, and I need a File in order to use getUrl(). Below is an snippet of my code which currently returns the error "Cannot find function getUrl in object FileIterator."
var poNumber = entryFormSheet.getRange(2, 2);
var proofHorizontal = drive.getFilesByName('PO ' + poNumber + ' Proof Horizontal.png').getUrl();
packingInstructionsSheet.getRange(7, 1).setFormula('IMAGE(' + proofHorizontal + ')');
If you know the file name exactly, You can use DriveApp to search the file and getUrl()
function getFile(name) {
var files = DriveApp.getFilesByName(name);
while (files.hasNext()) {
var file = files.next();
//Logs all the files with the given name
Logger.log('Name:'+file.getName()+'\nUrl'+ file.getUrl());
}
}
If you don't know the name exactly, You can use DriveApp.searchFiles() method.
You're close - once you have the FileIterator, you need to advance it to obtain a File, i.e. call FileIterator.next().
If multiple files can have the same name, the file you want may not be the first one. I recommend checking this in your script, just in case:
var searchName = "PO + .....";
var results = DriveApp.getFilesByName(searchName);
var result = "No matching files";
while (results.hasNext()) {
var file = results.next();
if (file.getMimeType() == MimeType. /* pick your image type here */ ) {
result = "=IMAGE( .... " + file.getUrl() + ")");
if (results.hasNext()) console.warn("Multiple files found for search '%s'", searchName);
break;
}
}
sheet.getRange( ... ).setFormula(result);
You can view the available MimeTypes in documentation

Share issue with script

I have this script I am using to make a copy of a sheet and then send me that copy thru email as a xslx file, if I have the sheet set for share to anyone with a link the script works great, but if I have it set to specific people it runs but gives a Value# instead of the data on the page. the page I am trying to send is a query importrange formula pulling the data on to the sheet. any help would be greatly appreciated.
enter code herefunction emailExcel() {
var mailTo, subject, body, id, sheetNum, sh, sourceSS, copySS, file, url,token, response;
mailTo = 'elder1104#gmail.com';
subject = 'subject';
body = 'text_in_body';
id = '1eI-p0nodA4zqP3fsxP5P6iR6gyvSIGRZDMaQSGkb2ds';
sheetNum = 2;
sourceSS = SpreadsheetApp.openById(id);
copySS = sourceSS.copy('copy of ' + sourceSS.getName());
sh = copySS.getSheets()[sheetNum];
sh.getDataRange().setValues(sh.getDataRange().getDisplayValues())
copySS.getSheets()
.forEach(function (sh, i) {
if(i != sheetNum) copySS.deleteSheet(sh);
})
file = Drive.Files.get(copySS.getId());
url = file.exportLinks[MimeType.MICROSOFT_EXCEL];
token = ScriptApp.getOAuthToken();
response = UrlFetchApp.fetch(url, {
headers: {
'Authorization': 'Bearer ' + token
}
});
MailApp.sendEmail(mailTo, subject, body, {
attachments: [response.getBlob()
.setName('TESTING.xlsx')]
});
DriveApp.getFileById(copySS.getId()).setTrashed(true);
}
To share Drive file to specific people, try to add setSharing(accessType, permissionType) method given in Class File.
As mentioned in Working with enums,
the Drive service uses the enums Access and Permission to determine which users have access to a file or folder.
So to access file or folder in DriveApp, you should add the following enumerated types to determine users who can access a file or folder, besides any individual users who have been explicitly given access
Access
Permission
It will really be helpful to try going through the given documentations.
I took the easy way out and made a new spreadsheet and I am importing to that spreadsheet using a script, so now the new document just has data on it and is working perfectly now.

Overwrite an Image File with Google Apps Script

Can I overwrite an image file with Google Apps Script? I've tried:
file.setContent(newBlobImage);
file.replace(newBlobImage);
Neither of those work. .setContent() will delete whatever data was in the file, and it looks like maybe it just writes the variable name as text, or something like that. I'm assuming that both .setContent() and .replace() are meant for text documents, and maybe that's why they don't work.
If it were a text file, or a spreadsheet, I might be able to clear it, then append new content.
I can trash the file, then create a new one, but I'd rather not if there is some other way.
If I write a file with the same name, it won't overwrite the existing file, it creates a another file with the same name.
The only way I've been able to trash the file is with DocsList and the only success I've had with creating an image file is with DriveApp. So I have to trash the file with DocsList, then create another file with DriveApp.
Well, I've figured out how to delete the file without sending it to the trash, so I won't need to clean out the trash later. The Google Drive SDK inside of Apps Script has a remove method that didn't send the file to trash, it's just gone.
var myFolder = DriveApp.getFolderById('3Bg2dKau456ySkhNBWB98W5sSTM');
thisFile = myFolder.getFilesByName(myFileName);
while (thisFile.hasNext()) {
var eachFile = thisFile.next();
var idToDLET = eachFile.getId();
Logger.log('idToDLET: ' + idToDLET);
var rtrnFromDLET = Drive.Files.remove(idToDLET);
};
So, I'm combining the DriveApp service and the DriveAPI to delete the file without sending it to the trash. The DriveAPI .remove needs the file ID, but I don't have the file ID, so the file gets looked up by name, then the file ID is retrieved, then the ID is used to delete the file. So, if I can't find a way to overwrite the file, I can at least delete the old file without it going to the trash.
I just noticed that the DriveAPI service has a Patch and an Update option.
.patch(resource, fileId, optionalArgs)
Google Documentation Patch Updates file metadata.
The resource arg is probably the metadata. The fileId is self explanatory. I'm guessing that the optionalArgs are parameters that follow the HTTP Request Patch semantics? I don't know.
It looks like both Patch and Update will update data. Update is a PUT request that will
clears previously set data if you don't supply optional parameters.
According to the documentation. So it's safer to use a Patch request because any parameters that are missing are simply ignored. I haven't tried it yet, but maybe this is the answer.
I'm getting an error with Patch, so I'll try Update:
.update(resource, fileId, mediaData)
That has a arg for mediaData in the form of a blob. And I think that is what I need. But I'm not sure what the resource parameter needs. So I'm stuck there.
An image file can be overwritten with Google Apps Script and the DriveAPI using the update() method:
.update(File resource, String fileId, Blob mediaData)
Where file resource is:
var myFileName = 'fileName' + '.jpg';
var file = {
title: myFileName,
mimeType: 'image/jpeg'
};
I'm getting the file ID with the DriveApp service, and the Blob is what was uploaded by the user.
In order to use DriveAPI, you need to add it through the Resources, Advanced Google Services menu. Set the Drive API to ON.
var allFilesByName,file,myFolder,myVar,theFileID,thisFile;
//Define var names without assigning a value
file = {
title: myFileName,
mimeType: 'image/jpeg'
};
myFolder = DriveApp.getFolderById('Folder ID');
allFilesByName = myFolder.getFilesByName(myFileName);
while (allFilesByName.hasNext()) {
thisFile = allFilesByName.next();
theFileID = thisFile.getId();
//Logger.log('theFileID: ' + theFileID);
myVar = Drive.Files.update(file, theFileID, uploadedBlob);
};
Thank you for this track !
This allowed me to find a solution to my problem : move a bound form after copying and moved his spreadsheet.
The Drive app advanced service must be activated in the "Resource Script Editor" to run this script.
function spreadsheetCopy() {
// Below is the file to be copied with a bound form.
var fileToCopy = DriveApp.getFileById("file_key"); // key is fileId
var saveFolder = DriveApp.getFolderById("folder_key"); // key is folderId
var currentFolder = "";
( fileToCopy.getParents().next() ) ? currentFolder = fileToCopy.getParents().next() : currentFolder = DriveApp.getRootFolder();
Logger.log(currentFolder)
var copyFile = fileToCopy.makeCopy(saveFolder),
copyName = copyFile.getName();
Utilities.sleep(30000);
moveFormCopy(currentFolder, saveFolder, copyName);
}
function moveFormCopy(currentFolder, saveFolder, copyName) {
var formsInFolder = currentFolder.getFilesByType(MimeType.GOOGLE_FORMS);
var form, copyForm, copyFormMimeType, copyFormName, copyFormId;
while ( formsInFolder.hasNext() ) {
form = formsInFolder.next();
if ( copyName === form.getName() ) {
copyForm = form;
copyFormMimeType = copyForm.getMimeType();
copyFormName = copyForm.getName();
copyFormId = copyForm.getId();
break;
}
};
var resource = {title: copyName, mimeType: copyFormMimeType};
Drive.Files.patch(resource, copyFormId, {addParents: saveFolder.getId(), removeParents: currentFolder.getId()})
}

Using the google drive API to download a spreadsheet in csv format

I'm sorry if this is an obvious question, I'm still pretty new to the API.
I'm using the python drive api library, and trying to download a google spreadsheet as a csv.
When I used files.get, it spat out a file with no downloadUrl, and with no 'text/csv' key in the export links field.
If it's not possible, I can find a workaround, but I'm hoping it is, since it is possible to do manually (file->download_as->csv)
Do I need to use the google document list api?
thanks,
Matt
Update: I have posted another answer that works with the Spreadsheets v4 API.
Old Answer:
The answer from Alain is correct, but you also need to set the gid=parameter to specify which worksheet to export.
For example, if your 'application/pdf' export link is like this:
docs.google.com/feeds/download/spreadsheets/Export?key=<FILE_ID>&exportFormat=pdf
You can just change it to this to download the first worksheet:
docs.google.com/feeds/download/spreadsheets/Export?key<FILE_ID>&exportFormat=csv&gid=0
There is a bit of a problem, though as there is no reliable way to get the gid for a given worksheet through the API and they are not zero based indexes. If you delete a worksheet, that gid does not get reused. You can see the gid in the URL in your browser though, so if your worksheet information is constant you can just get that from there. See http://code.google.com/a/google.com/p/apps-api-issues/issues/detail?id=1813 and http://code.google.com/a/google.com/p/apps-api-issues/issues/detail?id=3240 for more info on that problem.
As a lot of other people have pointed out, my original answer is somewhat outdated. So here is my answer updated for v4 of the Google Spreadsheets API. Now there's a way to get the gids, but we can't use the the drive files.export API because it only exports first worksheet in the spreadsheet (even if you specify the gid).
To export all of the worksheets as CSV files, you need to get the gids for the worksheets you want to export using the spreadsheets.get API. That API call returns a bunch of information about the spreadsheet including each of the worksheets. You can get the gid from the properties.sheetId property for each worksheet.
Once you have that, you can just build the same URL that the Sheets uses when you select File->Download As->CSV. You can take the data.spreadsheetUrl value from spreadsheets.get and replace /edit with /export and then add the gid as the parameter. You will also need to include Authorization Bearer <auth token> in the HTTP header in the request.
Here's a python script based on their quickstart example that downloads all of the sheets for the spreadsheet with a specified ID. You need to replace <spreadsheet id> with the ID for a spreadsheet you have access to:
import apiclient.discovery
import httplib2
import oauth2client.file
import oauth2client.tools
import re
import requests
import shutil
import urllib.parse
SCOPES = 'https://www.googleapis.com/auth/drive.readonly'
SPREADSHEET_ID = '<spreadsheet id>'
store = oauth2client.file.Storage('credentials.json')
creds = store.get()
if not creds or creds.invalid:
flow = oauth2client.client.flow_from_clientsecrets('client_secret.json', SCOPES)
creds = oauth2client.tools.run_flow(flow, store)
service = apiclient.discovery.build('sheets', 'v4', http=creds.authorize(httplib2.Http()))
result = service.spreadsheets().get(spreadsheetId = SPREADSHEET_ID).execute()
urlParts = urllib.parse.urlparse(result['spreadsheetUrl'])
path = re.sub("\/edit$", '/export', urlParts.path)
urlParts = urlParts._replace(path=path)
headers = {
'Authorization': 'Bearer ' + creds.access_token,
}
for sheet in result['sheets']:
params = {
'id': SPREADSHEET_ID,
'format': 'csv',
'gid': sheet['properties']['sheetId'],
}
queryParams = urllib.parse.urlencode(params)
urlParts = urlParts._replace(query=queryParams)
url = urllib.parse.urlunparse(urlParts)
response = requests.get(url, headers = headers)
filePath = '/tmp/foo-%s.csv' % (+ params['gid'])
with open(filePath, 'wb') as csvFile:
csvFile.write(response.content)
The exportLinks collection doesn't expose the CSV format as this will only export the first worksheet of a spreadsheet. If retrieving the first worksheet as a CSV is the behavior you are looking for, you can build the link manually and set the ?exportFormat= query parameter to ?exportFormat=csv.
Here's an implementation of Alain's suggestion that works for me:
downloadUrl = entry.get('exportLinks')['application/pdf']
# Strip "=pdf" and replace with "=csv"
downloadUrl = downloadUrl[:-4] + "=csv"
resp, content = drive_service._http.request(downloadUrl)
Not sure if it's what the OP needed, but in the new Google Sheets version it seems that it became a little hard to hot link a csv version of your spreadsheet.
In case you are interested in a Google apps script that will export all sheets in a spreadsheet to individual csv files (instead of downloading each one individually), Here you go:
function onOpen() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var csvMenuEntries = [{name: "export as csv files", functionName: "saveAsCSV"}];
ss.addMenu("csv", csvMenuEntries);
};
function saveAsCSV() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheets = ss.getSheets();
// create a folder from the name of the spreadsheet
var folder = DocsList.createFolder(ss.getName().toLowerCase().replace(/ /g,'_') + '_csv_' + new Date().getTime());
for (var i = 0 ; i < sheets.length ; i++) {
var sheet = sheets[i];
// append ".csv" extension to the sheet name
fileName = sheet.getName() + ".csv";
// convert all available sheet data to csv format
var csvFile = convertRangeToCsvFile_(fileName, sheet);
// create a file in the Docs List with the given name and the csv data
folder.createFile(fileName, csvFile);
}
Browser.msgBox('Files are waiting in a folder named ' + folder.getName());
}
function convertRangeToCsvFile_(csvFileName, sheet) {
// get available data range in the spreadsheet
var activeRange = sheet.getDataRange();
try {
var data = activeRange.getValues();
var csvFile = undefined;
// loop through the data in the range and build a string with the csv data
if (data.length > 1) {
var csv = "";
for (var row = 0; row < data.length; row++) {
for (var col = 0; col < data[row].length; col++) {
if (data[row][col].toString().indexOf(",") != -1) {
data[row][col] = "\"" + data[row][col] + "\"";
}
}
// join each row's columns
// add a carriage return to end of each row, except for the last one
if (row < data.length-1) {
csv += data[row].join(",") + "\r\n";
}
else {
csv += data[row];
}
}
csvFile = csv;
}
return csvFile;
}
catch(err) {
Logger.log(err);
Browser.msgBox(err);
}
}
Note: This script uses the DocsList.createFile() method, which is only available for Google Apps accounts.
If you need further explanation, go here: http://drzon.net/export-all-google-sheets-to-csv/
(Jul 2016) This question is phrased correctly, but in essence is a duplicate of another thread (Download a spreadsheet from Google Docs using Python). While some of the previous answers to this question below may still work (although answers are in JS/Apps Script not Python), a new Drive API version (v3) and new Sheets API version (v4) make them slightly outdated although the previous versions of both have not been deprecated (yet). Modern Google API access occurs using API keys or OAuth2 authorization, primarily with the Google APIs Client Libraries, including the one for Python.
To perform the task requested in/by the OP, you would perhaps query for specific Sheets to download, then perform the actual export(s) with the Drive API. Since this is likely a common operation, I wrote a blogpost sharing a code snippet that does this for you. If you wish to pursue exporting further, I've got another pair of posts along with a video that outlines how to upload files to and download files from Google Drive.
Note that there is also a Google Sheets API, but it's primarily for spreadsheet-oriented operations, i.e., inserting data, reading spreadsheet rows, cell formatting, creating charts, adding pivot tables, etc., not file-based requests like exporting where the Drive API is the correct one to use.
Note, as of April 2015 DocsList was depreciated, and has been replaced by DriveApp. Many of the DriveApp methods are identical to DocsList. So, in many cases, you can simply replace DocsList with DriveApp. So replace DocsList.createFile() with DriveApp.createFile()
How to update DocsList to DriveApp in my code