1

I'm new to JavaScript, and am trying to write some code that uses the google drive API (via the gapi client) to transform an existing slide into a pdf document, upload it to a specific folder, and return the pdf file id. This is all to be done in the browser, if possible.

I've already done this on python for another use case, and the code looks something like this:

import googleapiclient.http as client_methods
from io import BytesIO
...
data = drive.files().export(fileId=slideId, mimeType='application/pdf').execute()
body = {'name': fileName, 'mimeType': 'application/pdf', 'parents': [folderId]}
# wrapping the binary (data) file with BytesIO class
fh = io.BytesIO(data)
# creating the Media Io upload class for the file
media_body = client_methods.MediaIoBaseUpload(fh, mimetype='application/pdf')
pdfFileId = drive.files().create(body=body, media_body=media_body, supportsAllDrives=True).execute(['id'])

I've tried to replicate the same steps using JavaScript and my limited knowledge, and can successfully upload a pdf file into the desired folder, but the file shows as empty (doesn't even open in the drive).

I believe it might be due to the way I'm handling the binary data that I get from exporting the initial slide.

The last iteration of my JavaScript code is shown below (I have all the necessary permissions to use the gapi client):

async function createPdfFile() {
 gapi.client.load("drive", "v3", function () {
   // Set the MIME type for the exported file
   const mimeType = "application/pdf";

   // Set the file name for the exported PDF file
   const fileName = "Trial upload.pdf";

   // Export the Google Slides presentation as a PDF file
   gapi.client.drive.files.export({
     fileId,
     mimeType
   }).then(async function (response) {
     // Get the binary data of the PDF file
     const pdfData = await response.body;
    
     const blob = await new Blob([pdfData], {type: 'application/pdf'})
     const file = new File([blob], "presentation.pdf");

     // Create a new file in the specified Google Drive folder with the PDF data
     await gapi.client.drive.files.create({
       name: fileName,
       parents: [folderId],
       mimeType: mimeType,
       media: {mimeType: 'application/pdf', body: file},
       supportsAllDrives: true
     }).then(function (response) {
       // Get the ID of the created PDF file
       const pdfFileId = response.result.id;
       console.log("PDF file created with ID: " + pdfFileId);
     })
   })
 })
}
await createPdfFile() 

As for the output, and as stated, it does create a pdf file, and logs the pdf file id, but the file itself is empty. I'd really appreciate it if someone could help me make sense of this (similar thread here, but can't replicate his success).

Tanaike
  • 181,128
  • 11
  • 97
  • 165
John Neves
  • 13
  • 4

1 Answers1

0

I believe your goal is as follows.

  • You want to convert Google Slides to PDF format using googleapis for Javascript.
  • Your access token can be exported and uploaded to Google Drive.

Issue and workaround:

When I tested your script, unfortunately, response.body from gapi.client.drive.files.export is binary data, and in this case, this cannot be correctly converted to the blob. And also, in the current stage, it seems that a file cannot be uploaded using gapi.client.drive.files.create. I thought that these might be the reason for your current issue.

From these situations, I would like to propose the flow for achieving your goal using fetch API. The modified script is as follows.

In this case, the access token is retrieved from the client like gapi.auth.getToken().access_token.

Modified script:

Please modify your script as follows.

From:

gapi.client.drive.files.export({
  fileId,
  mimeType
}).then(async function (response) {
  // Get the binary data of the PDF file
  const pdfData = await response.body;

  const blob = await new Blob([pdfData], { type: 'application/pdf' })
  const file = new File([blob], "presentation.pdf");

  // Create a new file in the specified Google Drive folder with the PDF data
  await gapi.client.drive.files.create({
    name: fileName,
    parents: [folderId],
    mimeType: mimeType,
    media: { mimeType: 'application/pdf', body: file },
    supportsAllDrives: true
  }).then(function (response) {
    // Get the ID of the created PDF file
    const pdfFileId = response.result.id;
    console.log("PDF file created with ID: " + pdfFileId);
  })
})

To:

gapi.client.drive.files.get({ fileId, fields: "exportLinks", supportsAllDrives: true }).then(function (response) {
  const obj = JSON.parse(response.body);
  if (Object.keys(obj).length == 0) throw new Error("This file cannot be converted to PDF format.");
  const url = obj.exportLinks["application/pdf"];
  if (!url) throw new Error("No exported URL.");
  const accessToken = gapi.auth.getToken().access_token;
  fetch(url, {
    method: 'GET',
    headers: { 'Authorization': 'Bearer ' + accessToken },
  })
    .then(res => res.blob())
    .then(blob => {
      const metadata = { name: fileName, parents: [folderId], mimeType };
      const form = new FormData();
      form.append('metadata', new Blob([JSON.stringify(metadata)], { type: 'application/json' }));
      form.append('file', blob);
      fetch('https://www.googleapis.com/upload/drive/v3/files?uploadType=multipart&supportsAllDrives=true', {
        method: 'POST',
        headers: { 'Authorization': 'Bearer ' + accessToken },
        body: form
      })
        .then(res => res.json())
        .then(obj => console.log("PDF file created with ID: " + obj.id));
    });
});
  • When this script is run, the export URL of PDF data is retrieved from the file ID. And, the PDF data is downloaded and uploaded to Google Drive.

Note:

  • In your script, fileId is not declared. Please be careful about this.

  • If the file size is more than 5 MB, please use the resumable upload.

Reference:

Added:

From your following reply,

?uploadType=multipart also returns a 404 type error

I'm worried about that in your situation, new FormData() might not be able to be used. If my understanding is correct, please test the following script. In this script, the request body of multipart/form-data is manually created.

Modified script:

gapi.client.drive.files.get({ fileId, fields: "exportLinks", supportsAllDrives: true }).then(function (response) {
  const obj = JSON.parse(response.body);
  if (Object.keys(obj).length == 0) throw new Error("This file cannot be converted to PDF format.");
  const url = obj.exportLinks["application/pdf"];
  if (!url) throw new Error("No exported URL.");
  const accessToken = gapi.auth.getToken().access_token;
  fetch(url, {
    method: 'GET',
    headers: { 'Authorization': 'Bearer ' + accessToken },
  })
    .then(res => res.blob())
    .then(blob => {
      const metadata = { name: fileName, parents: [folderId], mimeType };
      const fr = new FileReader();
      fr.onload = e => {
        const data = e.target.result.split(",");
        const req = "--xxxxxxxx\r\n" +
          "Content-Type: application/json\r\n\r\n" +
          JSON.stringify(metadata) + "\r\n" +
          "--xxxxxxxx\r\n" +
          "Content-Transfer-Encoding: base64\r\n\r\n" +
          data[1] + "\r\n" +
          "--xxxxxxxx--";
        fetch('https://www.googleapis.com/upload/drive/v3/files?uploadType=multipart&supportsAllDrives=true', {
          method: 'POST',
          headers: { 'Authorization': 'Bearer ' + accessToken, "Content-Type": "multipart/related; boundary=xxxxxxxx" },
          body: req
        })
          .then(res => res.json())
          .then(obj => {
            console.log("PDF file created with ID: " + obj.id)
          });
      }
      fr.readAsDataURL(blob);
    });
});
  • When I tested this script, no error occurs. I confirmed that the Google Slides file could be converted to a PDF file and the PDF file was uploaded to the specific folder.
Tanaike
  • 181,128
  • 11
  • 97
  • 165
  • Hello, thank you so much for your help! This works, to an extent. Full disclosure, I tried to use the fetch API before, but noticed 2 things: 1. the files weren't getting uploaded to the desired folder, but instead were uploaded to my personal drive 2. they were uploaded as "Untitled" (no name and .pdf). These 2 still happen with your code. However, in my approach, downloading the Untitled files and changing them to Untitled.pdf still showed an empty slide, whilst yours has the desired slide data! So there's progress, at least – John Neves Feb 02 '23 at 09:54
  • Also, small correction, you have to fetch https://www.googleapis.com/upload/drive/v3/files?uploadType=media, else it won't work at all – John Neves Feb 02 '23 at 10:00
  • @John Neves Thank you for replying. About `1. the files weren't getting uploaded to the desired folder`, when I tested my script, the file is uploaded to the specific folder. About `2. they were uploaded as "Untitled" (no name and .pdf).`, when I tested my script, the correct filename is given and the file is uploaded. From this situation, I'm worried that you might have miscopied. So, can you provide your current script? By this, I would like to confirm it. I think that the reason for the no error when I tested my script is due to my poor skill. I apologize for this. – Tanaike Feb 02 '23 at 11:13
  • @John Neves When I tested my script again, no error occurs, and the Google Slides file is converted to a PDF file, and then, the converted PDF file is uploaded to the specific folder. I deeply apologize that when I test it, my script works fine. Unfortunately, I cannot still replicate your situation. I think that this is due to my poor skill. I apologize for my poor skill again. I think that in this case, it is required to know your current script. If you can cooperate to resolve your issue, I'm glad. Can you cooperate to do it? – Tanaike Feb 02 '23 at 11:21
  • no problem, no need to apologize, you're being very helpful. MY poor skill meant I had to post another answer with the code, can you please take a look and see what I'm missing? – John Neves Feb 02 '23 at 11:34
  • @John Neves When I saw your provided script, it was found that you have not correctly used my proposed script. The reason for your current issue is due to `https://www.googleapis.com/upload/drive/v3/files?uploadType=media`. In my proposed script, I used `https://www.googleapis.com/upload/drive/v3/files`. Please modify the endpoint for uploading the file, and test it again. When I tested your showing script by modifying the endpoint, I can confirm that the file is correctly uploaded. I apologize for this again. – Tanaike Feb 02 '23 at 11:40
  • I know. I changed it to `https://www.googleapis.com/upload/drive/v3/files?uploadType=media` because using `https://www.googleapis.com/upload/drive/v3/files` returns a 404 type error on the POST request – John Neves Feb 02 '23 at 11:43
  • @John Neves Thank you for replying. About `I know. I changed it to https://www.googleapis.com/upload/drive/v3/files?uploadType=media because using https://www.googleapis.com/upload/drive/v3/files returns a 404 type error`, I apologize for my poor skill again. Unfortunately, when I tested your showing script by modifying the endpoint, no error occurs. But, I would like to support you. So, can you provide the detailed flow for correctly replicating it? By this, I would like to confirm it. – Tanaike Feb 02 '23 at 11:46
  • @John Neves I think that the reason that when I tested my script, no error occurs is due to my poor skill. I deeply apologize for my poor skill again. By the way, I cannot understand the detailed issue from `returns a 404 type error`. I apologize for this. If you can cooperate to resolve your issue, I'm glad Can you cooperate to do it? – Tanaike Feb 02 '23 at 11:47
  • ,I'd like to cooperate, but I'm not sure how I can further help to reproduce it. When I use `https://www.googleapis.com/upload/drive/v3/files` I get a 404 service not found error, which is why I had to add `?uploadType=media` – John Neves Feb 02 '23 at 11:53
  • @John Neves Thank you for replying. Unfortunately, I cannot know your actual situation. At least, when I tested my script, no error occurs. So, I'm worried that your situation might be different from mine. And, I think that `?uploadType=media` is only the file content without the file metadata. So, in this case, when you use `?uploadType=multipart` instead of `?uploadType=media`, what result will you obtain? When the same issue occurs, I would like to think of another approach. Now, I would like to wait for your response. – Tanaike Feb 02 '23 at 11:57
  • `?uploadType=multipart` also returns a 404 type error, but in this case I believe it's because I'm missing some headers, like those described [here](https://developers.google.com/drive/api/guides/manage-uploads#http_1) – John Neves Feb 02 '23 at 13:04
  • @John Neves Thank you for replying. From your reply, I added one more sample script. Please confirm it. – Tanaike Feb 02 '23 at 13:26
  • updated the script but it still shows a 404 error on fetching `?uploadType=multipart`, sorry. There has to be something going on – John Neves Feb 02 '23 at 13:37
  • Ok, got it! There is something wrong with setting the folder Id here: `const metadata = { name: fileName, parents: [folderId], mimeType };`. If I delete `parents: [folderId]`, the pdf file is created properly (on my own personal drive though). I'm sure I have the right folder Id, as it's pretty obvious and it worked with the gapi client. So it must be in the way we're setting the parents. Full disclosure, it might be because my desired folder is not on my drive, but a shared folder with my team. So maybe there should be something like with the gapi `supportsAllDrives: true` – John Neves Feb 02 '23 at 13:47
  • @John Neves Thank you for replying. From your reply, I noticed that your folder ID is the folder in the shared Drive. In that case, please modify `https://www.googleapis.com/upload/drive/v3/files?uploadType=multipart` to `https://www.googleapis.com/upload/drive/v3/files?uploadType=multipart&supportsAllDrives=true` and test it again. I thought that in this case, in my 1st script, when you use this modified endpoint, the script might work. Please test both patterns. – Tanaike Feb 02 '23 at 22:57
  • both versions work now, thank you so much for your help with this, sorry we didn't get to it earlier. The matter is resolved then – John Neves Feb 03 '23 at 10:39
  • @John Neves Thank you for replying and testing it again. I'm glad your issue was resolved. Thank you, too. – Tanaike Feb 03 '23 at 11:52