Open a file from Google Sheets script - google-apps-script

I've had a Google Sheets script running for some time (a year) that needs to read an HTML file from it's Google Drive directory. The code to open the file looks like this:
var myHtmlFile = UrlFetchApp.fetch("https://googledrive.com/host/0B1m........JtZzQ/myfile.htm");
... and I could use the HTM file for further parsing.
Suddenly, the code above is throwing an error 404.
Has anything changed recently, stopping me from opening the file?

After a discussion with 'azawaza' (thanks for all the tips), I have finally solved this, so I'm posting the resolution in case others fall into this.
It looks like the construct
https://googledrive.com/host/{public_folder_id}/myfile.htm
in UrlFetchApp.fetch(url, true) can no longer be used. It gives error 404.
I was getting it from the following construct (for simplicity, assuming there is only one parent folder of my spreadsheet):
...
var myId = DocsList.getFileById(SpreadsheetApp.getActive().getId());
var folderId = myId.getParents()[0].getId();
var url = "https://googledrive.com/host/" + folderId + "/myfile.htm";
// url looks like: https://googledrive.com/host/0B1m....JtZzQ/myfile.htm"
var httpResp = UrlFetchApp.fetch(url,true); //throws 404 !!!
// now, parse 'httpResp'
The solution that worked for me, is to find the file directly using this construct (again, assuming there is only one file of given name) :
var htmlCont = DocsList.find("myfile.htm")[0].getContentAsString();
// now, parse htmlCont
I don't know why the 'old' solution no longer works. As I mentioned it worked for a year.
UPDATE (May 2015)
The 'DocsList' has been deprecated, a new construct:
var files = DriveApp.getFilesByName(myURL);
if (files.hasNext()) {
var htmlCont = files.next().getBlob().getDataAsString()
}
has to be used instead

I find it strange that it ever worked before! If it did, it was probably a bug - pretty sure it was never intended to work like that with "local" files... I have never seen it mentioned anywhere that UrlFetchApp.fetch() can fetch "local" files like that.
A simple fix would be to just use proper full url of the file:
var myHtmlFile = UrlFetchApp.fetch("https://googledrive.com/host/{public_folder_id}/myfile.htm");
That will ensure your code complies with the API and does not break next time Google changes something.

Related

DriveApp createFile from GmailApp attachment not working

First time posting, so please be gentle.
I'm using bits of code from:
https://www.splitbrain.org/blog/2017-01/30-save_gmail_attachments_to_google_drive
https://ctrlq.org/code/19053-send-to-google-drive
I'm trying to save PDF attachments from Gmail directly to Google drive. I'm getting an Invalid Argument error when trying to save the file in the last line of code below.
I have a loop to go through message threads and then within that loop, this is the code that is saving the attachments:
var message = threads[x].getMessages()[0];
var saveFolder = getFolder(driveFolderID,message);//this is valid folder
var att = message.getAttachments();
Logger.log(att[0].getContentType()); //this returns 'pdf'
file = saveFolder.createFile(att[0]);
Anyone know what I'm doing wrong, or where to look next?
Thanks
Ok. So I managed to figure this out so just updating.
Apparently the contentType 'pdf' was causing the problem. If I do a att[0].setContentType('application/pdf') before trying to save the file, then the code works as expected.

Inserting Image into Google Sheets from Google Drive using Apps Script

I have been trying for 9 days to add an image that is uploaded to my drive (via the use of a google form) into my Google sheet using Apps Script, but it isn't working and I have no idea why, this is my code below:
function getImage(){
var folderImage = DriveApp.getFolderById("0B7gxdApLS0TYfm1pRHpHSG4yTm96bm1PbTZQc1VmdGpxajY4N1J4M1gtR1BiZ0lOSl9NMjQ");
Logger.log(folderImage.getFiles().next().setSharing(DriveApp.Access.ANYONE, DriveApp.Permission.VIEW));
Logger.log(folderImage.getFiles().next().getSharingAccess());
Logger.log(folderImage.getFiles().next().getSharingPermission());
var imageFile = folderImage.getFiles().next().getBlob();
detailSheet.insertImage(imageFile, 1, 13);
}
I have even tried making the sharing and access permissions of the to be as open as possible but I keep getting this error message:
"We're sorry, a server error occurred. Please wait a bit and try again"
I find the error message ambiguous which leads me at a dead end. Usually the message gives me a good idea of where I have gone wrong.
I believe my code is correct, and during my research I have found no definitive reason this shouldn't work. Does anybody know where I am going wrong?
A solution would be great but preferably a critique on my code so I can learn :)
Couple of issues with your script:
You never bind to a specific file, so to work with the same file you have to reinitialize the iterator each time.
You don't verify its mimetype prior to using it as an image
An example that resolves those issues:
function addFolderPNGs_(sheet, folderId) {
const folder = folderId ? DriveApp.getFolderById(folderId) : DriveApp.getRootFolder(); // scope only to the root or given folder.
const imgs = folder.getFilesByType(MimeType.PNG);
var targetRow = sheet.getLastRow();
while (imgs.hasNext()) {
var img = imgs.next();
Logger.log(img.getName())
sheet.insertImage(img.getBlob(), 1, ++targetRow);
}
}
References
getFilesByType is either folder-specific (above) or operates on all of Google Drive
DriveApp
Folder
MimeTypes
Sheet#insertImage

Trouble with getting script to recognize a JSON file in the directory (Google API)

So I am attempting to learn how to use the Google Sheets API with Node.js. In order to get an understanding, I followed along with the node.js quick start guide supplied by Google. I attempted to run it, nearly line for line a copy of the guide, just without documentation. I wind up encountering this: cmd console output that definitely didn't work.
Just in case anyone wants to see if I am not matching the guide, which is entirely possible since I am fairly new to this, here is a link to the Google page and my code.
https://developers.google.com/sheets/api/quickstart/nodejs
var fs = require('fs');
var readline = require('readline');
var google = require('googleapis');
var googleAuth = require('google-auth-library');
var SCOPES = ['https://www.googleapis.com/auth/spreadsheets.readonly'];
var TOKEN_DIR = (process.env.HOME || process.env.HOMEPATH ||
process.env.USERPROFILE) + '/.credentials/';
var TOKEN_PATH = TOKEN_DIR + 'sheets.googleapis.com-nodejs-quickstart.json';
fs.readFile('client_secret.json', function processClientSecrets(err, content) {
if (err) {
console.log('Error loading client secret file: ' + err);
}
authorize(JSON.parse(content), listMajors);
});
I have tried placing the JSON file in each and every part of the directory, but it still won't see it. I've been pulling hairs all day, and a poke in the right direction would be immensely appreciated.
From your command output:
Error loading client secret file
So your if (err) line is being triggered. But since you don't throw the error, the script continues anyway (which is dangerous in general).
SyntaxError: Unexpected token u in JSON at position 0
This means that the data you are passing to JSON.parse() is undefined. It is not a valid JSON string.
You could use load-json-file (or the thing it uses, parse-json) to get more helpful error messages. But it's caused by the fact that your content variable has nothing since the client_secret.json you tried to read could not be found.
As for why the file could not be found, there could be a typo in either the script or the filename you saved the JSON in. Or it may have to do with the current working directory. You may want to use something like this to ensure you end up with the same path regardless of the current working directory.
path.join(__dirname, 'client_secret.json')
Resources
path.join()
__dirname

Edit on Google Docs without converting

I'm integrating my system with Google Drive. Everything is working so far, but one thing. I cannot edit the uploaded Word documents without converting them to Google Docs first.
I've read here it's possible using a Chrome plugin:
https://support.google.com/docs/answer/6055139?hl=en
But that's not my goal. I'm storing the file's information on my database and then I just request the proper URL for editing and previewing. Previewing is working fine, but when I try the edit URL it says the file does not exist. If I convert the file (using Google Drive's interface) and pass the new ID it works. I don't want to convert the user's documents to Google Drive because they still use Word as their main editing software.
Is there a way to accomplish this?
This is how I'm doing right now:
public static File UploadFile(FileInfo fileInfo, Stream stream, string googleAccount)
{
var mimetype = GetValidMimetype(fileInfo.MimeType);
var parentFolder = GetParentFolder(fileInfo);
var file = new File { Title = fileInfo.Title, MimeType = mimetype, Parents = parentFolder };
var uploadRequest = _service.Files.Insert(file, stream, mimetype);
uploadRequest.Upload();
file = uploadRequest.ResponseBody;
ShareFileWith(file.Id, googleAccount);
return file;
}
This is the URL for editing (where {0} is the file ID):
https://docs.google.com/document/d/{0}/edit?usp=drivesdk
I know that in order to convert the file I just need to:
uploadRequest.Convert = true;
But again, that's not what I want. Is it possible?
Thanks!
EDIT
Just an update. Convert = true should've worked but it's not. I've raised an issue for that here https://github.com/google/google-api-dotnet-client/issues/712
Bottomline, it only works if I open the file on Google Docs and then use its Id...

Script to automatically make a copy of a Google Document for editing

I feel like a total noob posting here. I know CSS, HTML, and XML pretty well but have always avoided JS. I know very little javascript and recently started a Lynda.com course to catch up. Sorry for my ignorance. As such, I am really struggling learning Google Apps Script. Obviously, I need to learn JS before I can make sense of any of it.
The school I work for (5000 students) has set up an online curriculum. I created the curriculum in the form of thousands of google document worksheets. These worksheets are linked on various websites.
The problem we are facing is that when students open the documents, they have to make a copy of them before they can edit them (I of course don't want them to be able to edit the originals). This really sucks for students using mobile browsers on their tablets as making a copy in Google Docs doesn't really work well when using the desktop UI on mobile devices.
I know this kind of thing can be automated with script. I've looked here, and low and behold, it works! I'm pissing my pants with joy as I've been searching for such functionality for three years. (Yes, I know that's sad).
So, what I'm asking is, would anyone be willing to help a noob figure out how to adapt this code so that students click a button on a website lesson and it automatically makes and opens a copy of the worksheet in a new tab?
/**
* Copy an existing file.
*
* #param {String} originFileId ID of the origin file to copy.
* #param {String} copyTitle Title of the copy.
*/
function copyFile(originFileId, copyTitle) {
var body = {'title': copyTitle};
var request = gapi.client.drive.files.copy({
'fileId': originFileId,
'resource': body
});
request.execute(function(resp) {
console.log('Copy ID: ' + resp.id);
});
}
Spending all day yesterday learning Javascript, I've still got a long way to go. Not sure how long it'll take for me to be able to figure this out on my own.
You can certainly do this with Apps Script. Only takes a couple of lines. In fact, you can use just the version I wrote below.
Here is how I would do it -
Ensure you original document is at least read enabled for the folks that will be accessing it.
Grab the fileId from the URL -
Write a web app in Apps Script with the following code -
function doGet(e) {
//file has to be at least readable by the person running the script
var fileId = e.parameters.fileId;
if(!fileId){
//have a default fileId for testing.
fileId = '1K7OA1lnzphJRuJ7ZjCfLu83MSwOXoEKWY6BuqYitTQQ';
}
var newUrl = DocsList.getFileById(fileId).makeCopy('File copied to my drive').getUrl();
return HtmlService.createHtmlOutput('<h1>Open Document</h1>');
}
Deploy it to run as the person accessing the app.
One key thing to remember is that a web app built by Apps Script cannot force open a new window automatically. Instead we can show a link which is clickable into the document in edit mode.
You can see it in action here (will create some dummy file) -
https://script.google.com/macros/s/AKfycbyvxkYqgPQEb3ICieywqWrQ2-2KWb-V0MghR2xayQyExFgVT2h3/exec?fileId=0AkJNj_IM2wiPdGhsNEJzZ2RtZU9NaHc4QXdvbHhSM0E
You can test this by putting in your own fileId.
Since DocsList is deprecated, currently you can make a copy of a file using the following code:
File file=DriveApp.getFileById(fileId).makeCopy(fileName, folder);
where fileId can be obtained as explained in the answer by Arun Nagarajan.
Update as of 2015, Google Script removed the fileId for reasons unknown. The previous method of appending "/copy" to the URL of the Google doc has been re-enabled. Ex) https://docs.google.com/document/d/1GTGuLqahAKS3ptjrfLSYCjKz4FBecv4dITPuKfdnrmY/copy
Here is the code to do it properly (works in 2019,2020,2021):
/**
* Create custom menu when document is opened.
*/
function onOpen() {
DocumentApp.getUi()
.createMenu('For Students')
.addItem('Make a copy', 'makeACopy')
.addToUi();
}
function makeACopy() {
var templateId = DocumentApp.getActiveDocument().getId();
DriveApp.getFileById(templateId).makeCopy();
}
Here is the code I use to make an auto copy of my google Docs.
function makeCopy() {
// generates the timestamp and stores in variable formattedDate as year-month-date hour-minute-second
var formattedDate = Utilities.formatDate(new Date(), "GMT", "yyyy-MM-dd' 'HH:mm:ss");
// gets the name of the original file and appends the word "copy" followed by the timestamp stored in formattedDate
var name = DocumentApp.getActiveDocument().getName() + " Copy " + formattedDate;
// gets the destination folder by their ID. REPLACE xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx with your folder's ID that you can get by opening the folder in Google Drive and checking the URL in the browser's address bar
var destination = DriveApp.getFolderById("16S8Gp4NiPaEqZ0Xzz6q_qhAbl_thcDBF");
// gets the current Google Docs file
var file = DriveApp.getFileById(DocumentApp.getActiveDocument().getId())
Add a trigger and enjoy!
The same code works for google Sheets. Only you need to replace
DocumentApp.getActiveDocument().getName()
with
SpreadsheetApp.getActiveSpreadsheet()
You can find more details here.