Search code examples
javascriptgoogle-drive-apigoogle-slidesgoogle-api-javascript-client

Trouble using JavaScript and the google drive API to convert a google slide into a pdf, and upload the pdf onto a folder


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).


Solution

  • 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.