Google Script base64 decode and save file to Drive - google-apps-script

I'm receiving a base64encoded image in a Google Sheets script. I can successfully generate an image by logging
e.postData.contents
then using https://www.base64decode.org/ to decode and generate a .jpg file. But when I try to do the decoding inside the google script and write a jpg file to drive, it is corrupted. It looks similar, but there are some replacement characters inserted. Here's my code:
function doPost(e) {
var decoded = Utilities.base64Decode(e.postData.contents, Utilities.Charset.UTF_8);
var blob = Utilities.newBlob(decoded);
DriveApp.createFile('img_'+date+'.jpg', blob.getDataAsString('Windows-1252'), MimeType.JPEG);
}
I also tried blob.getAs('image/jpeg'), but that just returns a 4-byte file containing the text Blob.
Any ideas? I've tried a few different charsets, and Windows-1252 seems to give the closest results.

How about this sample script? When you use this, please define date. In my test, also I could confirm that the base64 encoded jpeg file could be decoded at https://www.base64decode.org/.
And when you updated doPost(), please be careful as follows. Please redeploy your script to Web Apps as a new version. If this is not carried out, script of the deployed Web Apps is not updated.
Sample script :
function doPost(e) {
var decoded = Utilities.base64Decode(e.parameters.contents, Utilities.Charset.UTF_8);
var blob = Utilities.newBlob(decoded, "image/jpeg", 'img_'+date+'.jpg'); // Please define date.
DriveApp.createFile(blob);
}
Curl sample :
If you want to try your doPost() using curl, you can use following command. When you use this, please input filename which is base64 encoded file and Web Apps URL.
curl -L -F "contents=`cat ### Filename ###`" "https://script.google.com/macros/s/#####/exec"
If I misunderstand your question, I'm sorry.

I am facing the same issue. my spreadsheet is showing an image but broken and unsupported. see the screenshot columns
I am sending the base64 image from autohotkey script. here is my autohotkey snippet:
Base64Enc(File="") { ; By SKAN / 18-Aug-2017 https://www.autohotkey.com/boards/viewtopic.php?t=35964
Local Rqd := 0, B64, B := "", N := 0 - LineLength + 1 ; CRYPT_STRING_BASE64 := 0x1
FileGetSize,nBytes,%File%
FileRead,Bin,*c %File%
DllCall( "Crypt32.dll\CryptBinaryToString", "Ptr",&Bin ,"UInt",nBytes, "UInt",0x1, "Ptr",0, "UIntP",Rqd )
VarSetCapacity( B64, Rqd * ( A_Isunicode ? 2 : 1 ), 0 )
DllCall( "Crypt32.dll\CryptBinaryToString", "Ptr",&Bin, "UInt",nBytes, "UInt",0x1, "Str",B64, "UIntP",Rqd )
B64:=StrReplace(B64,"`r`n")
return RTrim(B64,"`n")}
; Convert the screenshot to base64 encoded text
screenshotBase64 := Base64Enc(screenshotFile)
; Send the input, file attachment, and screenshot to the Google App Script
WinHttp := ComObjCreate("WinHttp.WinHttpRequest.5.1")
WinHttp.Open("POST", "https://script.google.com/macros/s/AKfycbypXKVB6DQi0xdWNjR1KM3q3A5w1flSedSkqHH3GpvlDCAirn9e_BOgUwhLbuf1LSEHZg/exec")
WinHttp.SetRequestHeader("Content-Type", "application/x-www-form-urlencoded")
fileSize := StrLen("input=" . input . "&screenshot=" . screenshotBase64 . "&file=" . fileBase64 . "&filename=" . fileName)
;WinHttp.SetRequestHeader("Content-Length", fileSize)
WinHttp.Send("input=" . input . "&screenshot=" . screenshotBase64 . "&file=" . fileBase64 . "&filename=" . fileName)
; Get the response from the script and display it
response := WinHttp.ResponseText
msgbox % response
and this is my google app script code:
// Check if a screenshot was sent
if (screenshot) {
// Decode the base64 data
var decodedImage = Utilities.base64Decode(screenshot, Utilities.Charset.UTF_8) || null;
// Check if the decoded data is not null
if (decodedImage) {
// Create a timestamp for the filename
var timestamp = new Date().getTime();
// Create an image from the decoded data
var img = Utilities.newBlob(decodedImage, "image/jpeg", 'screenshot-'+timestamp+'.jpg');
// Get the folder where you want to upload the image
var folder = DriveApp.getFolderById("1Xmkr8vXTsTIQPMUr2UR1TpzJ2YtTHwOu");
// Upload the image to the folder and get the file object
var driveFile = folder.createFile(img,img.getDataAsString('Windows-1252'),);
// Make the file publicly accessible
driveFile.setSharing(DriveApp.Access.ANYONE, DriveApp.Permission.VIEW);
// Get the download URL of the file
imgUrl = "https://drive.google.com/uc?id="+driveFile.getId();
} else {
throw new Error("Error decoding base64 data.");
}
}

Related

Exception: Blob object must have non-null data for this operation. (line 19)

Hi I'm trying to attach multiple Google Doc files from a folder to send to an Email. However, the above exception arises.
The code is as follows
function email() {
// Get attachments folder
var attachementFolderId = "xyz";
Logger.log("Attachement Folder ID: " + attachementFolderId)
var getAttachementFolder = DriveApp.getFolderById(attachementFolderId);
// Get all files from the folder
var files = DriveApp.getFolderById(attachementFolderId).getFiles();
//Get All attachments
var attachements = [];
while (files.hasNext()) {
var file = files.next();
attachements.push(file.getAs(MimeType.GOOGLE_DOCS));
}
MailApp.sendEmail({
to: email,
subject: subject,
attachments: attachements
})
}
This code works fine if the file is either a pdf or a Microsoft Word doc but causes an issue for a Google Doc.
Modification points:
When the official document of getAs(contentType)
) is seen, it says as follows.
The MIME type to convert to. For most blobs, 'application/pdf' is the only valid option. For images in BMP, GIF, JPEG, or PNG format, any of 'image/bmp', 'image/gif', 'image/jpeg', or 'image/png' are also valid.
In this case, unfortunately, MimeType.GOOGLE_DOCS cannot be used for this.
When you want to retrieve the blob from Google Document. It is required to convert it to other mimeType.
When you want to convert it to PDF format, please modify file.getAs(MimeType.GOOGLE_DOCS) to file.getBlob().
When you want to convert it to DOCX format, I think that this thread might be useful.
References:
getAs(contentType)
Related question
Convert Google Doc to Docx using Google Script

How to upload an existing file in Drive to Drive using Apps Script

Introduction
Let me first introduce the goal of what I am trying to do.
I had a file split into two parts earlier
Size of both of these files together may exceed 50 MB (as a long term goal). Since UrlFetchApp.fetch() has restriction regarding the size of the request, I want to upload them separately, where each file will be less than 50 MB and consequently merge them. For now (to try the Drive API), I am using small files.
First file is of 640000 bytes (a multiple of 256) 524288 bytes. I realise I did a mistake earlier i.e I was using the size of the file as a multiple of 256 but it should be a multiple of 256*1024
Second file is of 47626 bytes 163339 bytes.
I had split the files using curl and uploaded them to my drive (normal web upload).
My intention is to upload the partial files one by one using Resumable Upload to Google Drive using the Google Drive API from Google Apps Script so that they maybe merged into one file.
What I have tried so far?
Yesterday, I had asked a question here. I was trying to perform a resumable upload using Drive.Files.insert and a user pointed out it is not possible using Drive.Files.insert which is quoted below.
Unfortunately, in the current stage, the resumable upload cannot be achieved using Drive.Files.insert. It seems that this is the current specification at Google side
What I am trying now is using Google Drive API. Enclosed below is the code.
function myFunction() {
var token = ScriptApp.getOAuthToken();
var f1_id = '1HkBDHV1oXXXXXXXXXXXXXXXXXXXXXXXX';
var f2_id = '1twuaKTCFTXXXXXXXXXXXXXXXXXXXX';
var putUrl = 'https://www.googleapis.com/drive/v3/files?uploadType=resumable';
var fileData = {
name : 'Merged-file-from-GAS',
file : DriveApp.getFileById(f1_id).getBlob()
}
var options = {
method : 'put',
contentType:"application/json",
headers : {
Authorization: 'Bearer ' + token,
'X-Upload-Content-Type' : 'application/octet-stream',
'Content-Type' : 'application/json; charset=UTF-8'
},
muteHttpExceptions: true,
payload : fileData
};
var response = UrlFetchApp.fetch(putUrl, options);
Logger.log(response.getResponseCode());
Logger.log(response.getAllHeaders());
}
I also tried changing the method to patch
I added Content-Length : 640000 inside headers and in that case I receive an error as provide below.
Exception: Attribute provided with invalid value: Header:Content-Length
I tried to create a file using Drive.Files.insert(resource) using blank resource. Then I tried to update it using UrlFetchApp(patchUrl,options) while having the variable
var patchUrl = 'https://www.googleapis.com/upload/drive/v3/files/' + fileId + '?uploadType=resumable';
Result
It does not create any file.
Logger logs for the result of the attached code (initial code) are provided below:
[20-05-12 21:05:37:726 IST] 404.0
[20-05-12 21:05:37:736 IST] {X-Frame-Options=SAMEORIGIN, Content-Security-Policy=frame-ancestors 'self', Transfer-Encoding=chunked, alt-svc=h3-27=":443"; ma=2592000,h3-25=":443"; ma=2592000,h3-Q050=":443"; ma=2592000,h3-Q049=":443"; ma=2592000,h3-Q048=":443"; ma=2592000,h3-Q046=":443"; ma=2592000,h3-Q043=":443"; ma=2592000,quic=":443"; ma=2592000; v="46,43", X-Content-Type-Options=nosniff, Date=Tue, 12 May 2020 15:35:37 GMT, Expires=Mon, 01 Jan 1990 00:00:00 GMT, X-XSS-Protection=1; mode=block, Content-Encoding=gzip, Pragma=no-cache, Cache-Control=no-cache, no-store, max-age=0, must-revalidate, Vary=[Origin, X-Origin], Server=GSE, Content-Type=text/html; charset=UTF-8}
Question
What is the proper way of initiating a upload of a file in Drive to Drive using Drive API from Apps Script while keeping the upload type as resumable?
What should subsequent requests be like? So that files above 50 MB can be subsequently uploaded to the merged file?
Edit 1
Tried it again using corrected file chunks sizes. Same problem persists.
Edit 2
To understand the code in the answer, I used the code in // 2 of Tanaike's code alone to understand how Location is retrieved.
function understanding() {
var token = ScriptApp.getOAuthToken();
const filename = 'understanding.pdf';
const mimeType = MimeType.PDF;
const url = 'https://www.googleapis.com/drive/v3/files?uploadType=resumable';
const res1 = UrlFetchApp.fetch(url, {
method: "post",
contentType: "application/json",
payload: JSON.stringify({name: filename, mimeType: mimeType}),
headers: {authorization: "Bearer " + ScriptApp.getOAuthToken()
}});
const location = res1.getHeaders().Location;
Logger.log(location);
}
This creates a file understanding.pdf of size 0 bytes. However, Logger.log(location) logs null.
Why is it so?
The mistake was in the end point. Setting it to
https://www.googleapis.com/upload/drive/v3/files?uploadType=resumable works. It retrieves the location.
From your question and replying, I could understand your situation and goal like below.
In your sample files, "file A" and "file B" are 524,288 bytes and 163,339 bytes, respectively.
Your tested script is the script showing in your question.
You want to merge the files using the resumable upload.
The mimeType of the merged file is PDF.
For this, how about this answer?
Modification points:
Unfortunately, your script is incomplete for achieving the resumable upload. The flow of the resumable upload at Google Drive API is as follows. Ref
Request for retrieving the location which is used as the endpoint for uploading data.
In your case, the new file is created. So it is required to use the POST method.
Request to the retrieved location by including the data (in your case, it's each file.).
In this case, it is required to upload the data using a loop. And the PUT method is used.
Here, each file size is most important. If the file size except for the last file is not the multiples of 262,144 bytes, the resumable upload cannot be run by an error. Please be careful this.
For above flow, when the sample script is prepared, it becomes as follows.
Usage:
1. Enable Drive API.
In this case, Drive API is used. So please enable Drive API at Advanced Google Services. By this, the Drive API is automatically enabled at API console.
The flow of the sample script is as follows.
Create an object for using at the resumable upload.
Retrieve "location" for starting the resumable upload.
Upload each file and merge them.
2. Sample script.
Please copy and paste the following script. And please set the file IDs. In this case, please set them in order for merging. Please be careful this.
function myFunction() {
const fileIds = ["###", "###"]; // Please set the file IDs of the file "A" and "B" in order.
const filename = "sample.pdf";
const mimeType = MimeType.PDF;
// 1. Create an object for using at the resumable upload.
const unitSize = 262144;
const fileObj = fileIds.reduce((o, id, i, a) => {
const file = DriveApp.getFileById(id);
const size = file.getSize();
if (i != a.length - 1 && (size % unitSize != 0 || size > 52428800)) {
throw new Error("Size of each file is required to be the multiples of 262,144 bytes and less than 52,428,800 bytes.");
}
o.files.push({data: file.getBlob().getBytes(), range: `bytes ${o.size}-${o.size + size - 1}\/`, size: size.toString()});
o.size += size;
return o;
}, {size: 0, files: []});
// 2. Retrieve "location" for starting the resumable upload.
const url = "https://www.googleapis.com/upload/drive/v3/files?uploadType=resumable";
const res1 = UrlFetchApp.fetch(url, {
method: "post",
contentType: "application/json",
payload: JSON.stringify({name: filename, mimeType: mimeType}),
headers: {authorization: "Bearer " + ScriptApp.getOAuthToken()
}});
const location = res1.getHeaders().Location;
// 3. Upload each file and merge them.
fileObj.files.forEach((e, i) => {
const params = {
method: "put",
headers: {"Content-Range": e.range + fileObj.size},
payload: e.data,
muteHttpExceptions: true,
};
const res = UrlFetchApp.fetch(location, params);
const status = res.getResponseCode();
if (status != 308 && status != 200) {
throw new Error(res.getContentText());
}
if (status == 200) {
console.log(res.getContentText())
}
});
// DriveApp.createFile() // This comment line is used for automatically detecting the scope of "https://www.googleapis.com/auth/drive" by the script editor. So please don't remove this line.
}
Result:
When the resumable upload is finished, the following result can be seen at the log. And you can see the merged file at the root folder.
{
"kind": "drive#file",
"id": "###",
"name": "sample.pdf",
"mimeType": "application/pdf"
}
Note:
This is a simple sample script. So please modify this for your actual situation.
I tested above script for your sample situation that "file A" and "file B" are 524,288 bytes and 163,339 bytes. So when several files with about 50 MB in size are merged using this script, an error occurs.
If the memory error occurs when the large files are used, in the current stage, it seems that this is the specification of Google side. So please be careful this.
Reference:
Perform a resumable upload
Tanaike's answer is more than perfect. It's elegant and has even helped me to learn about array.reduce function. Before I asked this question, I had minimal knowledge about JavaScript and almost zero knowledge in using Google Drive API.
My intention was to learn the whole process of resumable upload step by step using Google Apps Script as the language. Using Tanaike's code as reference I wrote a script which instead of being productive, manageable, and elegant would provide myself (at least) an idea of how resumable upload works step by step. I have used no loops, no objects, and even no arrays.
Step 1 ( Declare the necessary variables )
var fileId1 = "XXXXXXXXXXX"; //id of the first file
var fileId2 = "YYYYYYYYYYY"; //id of the second file
var filename = "merged.pdf"; //name of the final merged file
var mimeType = MimeType.PDF; //Mime type of the merged file
Step 2 ( Initiate the resumable upload )
//declare the end point
const url = "https://www.googleapis.com/upload/drive/v3/files?uploadType=resumable";
//Send the request
//Method to be used is Post during initiation
//No file is to be sent during initiation
//The file name and the mime type are sent
const res1 = UrlFetchApp.fetch(url, {
method: "post",
contentType: "application/json",
payload: JSON.stringify({name: filename, mimeType: mimeType}),
headers: {authorization: "Bearer " + ScriptApp.getOAuthToken()
}});
Step 3 ( Save the resumable session URI )
const location = res1.getHeaders().Location;
Step 4 (a) ( Upload file 1 )
Note : Step 4 (a) and (b) can be performed using a loop. In my case, I used it two times without loop
var file = DriveApp.getFileById(fileId1); //get the first file
var data = file.getBlob().getBytes(); //get its contents in bytes array
//Method used is PUT not POST
//Content-Range will contain the range from starting byte to ending byte, then a slash
//and then file size
//bytes array of file's blob is put in data
var params = {
method : "put",
headers : {
'Content-Range' : `bytes 0-524287/687627`
},
payload : data,
muteHttpExceptions: true
};
//Request using Resumable session URI, and above params as parameter
var result = UrlFetchApp.fetch(location,params);
Step 4 (b) ( Upload the second file )
//Almost same as Step 4 (a)
//The thing that changes is Content Range
file = DriveApp.getFileById(fileId2);
data = file.getBlob().getBytes();
params = {
method : "put",
headers : {
'Content-Range' : `bytes 524288-687626/687627`
},
payload : data,
muteHttpExceptions : true
};
result = UrlFetchApp.fetch(location, params);
Now instead of doing step 4 n number of times, it's better to use a loop.
Also, this code doesn't checks for possible error that might have occurred during the process.
Hope this code helps someone, even though it was more of a self-teaching experiment. :)

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

Save ip camera video to google drive using apps script UrlFetchApp

My idea was to setup a script with GAS to record videos of my ip camera to google drive. Currently it can access the camera videostream, take data and save it do drive. It's not working because instead of getting a limited amount of data due to the http request header range parameter it takes the maximum size the http request could receive. Furthermore the asf video seems to get corrupted, and can't be played on VLC.
Any idea for making the script download a defined video size and in the correct format?
function myFunction() {
var URL = 'http://201.17.122.01:82/videostream.asf?user=xxxx&pwd=xxxxxx&resolution=32&rate=1'; // file to backup
var chunkSize = 1048576; // read the file in pieces of 1MB
var chunkStart = 0, chunkEnd = chunkStart + chunkSize;
var chunkHTTP = UrlFetchApp.fetch(URL, {
method: "get",
contentType: "video/x-ms-asf",
headers: {
"Range": "bytes=" + chunkStart + "-" + chunkEnd
}
})
var chunk = chunkHTTP.getContentText();
// write to Drive
try {
var folder = DocsList.getFolder('bakfolder');
} catch(err) {
var folder = DocsList.createFolder('bakfolder');
}
fileOnDrive = folder.createFile('ipcamera.asf', chunk);
Logger.log(" %s bytes written to drive", chunk.length);
}
I do not have an ip camera, so can't test and help you with the chunking of received data. But I suspect that you are using wrong methods for extracting received video data and saving it to Drive:
a) .getContentText() method gets the content of an HTTP response encoded as a string - probably wrong for working with video stream data. Try using .getBlob() or .getContent() instead;
b) folder.createFile(name, content) method creates a text file in the current folder. Use .createFile(blob) method instead.
You will most likely have t experiment with the stream bytes to get it as a blob Apps Script can work with. If I find an ip camera or another video stream I can access, I will test it out and update this.

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()})
}