0

I have a Google Spreadsheet attached to a Form. On Form Submit, a script takes information from the submission and applies it into carefully placed textboxes in a Google Slide. There are two separate forms which are used to submit information into two separate slides on the SAME presentation.

After the information is entered, the script runs a function that is supposed to export the edited slide ONLY as a PDF and email it to an address provided on the Google Form.

I had a similar setup on a different program of mine and had someone from an online forum help me write the export PDF / Email code. The code they helped me write was for exporting and emailing an individual Spreadsheet sheet.

Here is the email code that I copied from my Spreadsheet program.

function Email3UpPDF () {
  Logger.log("Emailing!");
  var Sapp = SpreadsheetApp;
  var FS = Sapp.getActiveSpreadsheet().getSheetByName("3UP Submissions");
  var ssID = "10Up_PcLxVopXont9Qcu-yk-PrFGWfWPQ3ETsgys4v0Y"
  var shID = "g2580bcdba17_0_22"
  
  var Addy1 = FS.getRange("N"+FS.getLastRow()).getValue();
  
  var requestData = {"method": "GET", "headers":{"Authorization":"Bearer "+ScriptApp.getOAuthToken()}}; 
  var url = "https://docs.google.com/presentation/d/"+ ssID + "/export?format=pdf&id="+shID; //This creates the PDF export url
  var result = UrlFetchApp.fetch(url , requestData);  
  var contents = result.getContent();
  
  MailApp.sendEmail(Addy1,"Local Sign Template" ,"Here is your custom sign Template.", {attachments:[{fileName: "LST3UP.pdf", content:contents, mimeType:"application/pdf"}]});

}

My HOPE was that I could simply substitute the Spreadsheet ID with a Presentation ID, and the Sheet ID with a Slide ID. When I tried that I was met with this error:

Exception: Request failed for https://docs.google.com returned code 404. Truncated server response: <meta name="viewport" c... (use muteHttpExceptions option to examine full response)

The error points to line 13: var contents = result.getContent();

I should note that when I put JUST the Presentation ID in both the ssID and the shID variables, the program runs successfully but ends up sending a 2 page PDF with both slides on it. Which makes sense to me. My intention is to only have one slide though.

2 Answers2

2

I'm worried that your endpoint might not be able to used for exporting the specific slide from the Google Slide as a PDF format. So, in your situation, how about the following modification?

Modified script 1:

function Email3UpPDF() {
  Logger.log("Emailing!");
  var Sapp = SpreadsheetApp;
  var FS = Sapp.getActiveSpreadsheet().getSheetByName("3UP Submissions");
  var Addy1 = FS.getRange("N" + FS.getLastRow()).getValue();

  var presentationId = "10Up_PcLxVopXont9Qcu-yk-PrFGWfWPQ3ETsgys4v0Y"; // File ID of Google Slide.
  var pageObjectIds = ["g2580bcdba17_0_22"]; // Page object ID of Googe Slide.
  var temp = DriveApp.getFileById(presentationId).makeCopy("temp");
  var s = SlidesApp.openById(temp.getId());
  s.getSlides().forEach(s => {
    if (!pageObjectIds.includes(s.getObjectId())) {
      s.remove();
    }
  });
  s.saveAndClose();
  var contents = temp.getBlob().getBytes();
  temp.setTrashed(true);

  MailApp.sendEmail(Addy1, "Local Sign Template", "Here is your custom sign Template.", { attachments: [{ fileName: "LST3UP.pdf", content: contents, mimeType: "application/pdf" }] });
}
  • In this modification, the current Google Slide is copied as a temporal file. And, all slides except for the specific slide are removed. And, the temporal Google Slide is exported as a PDF format. I used this flow because I'm worried that your Google Slide might have a specific theme.

  • In this modification, when you modify var pageObjectIds = ["g2580bcdba17_0_22"]; to var pageObjectIds = ["g2580bcdba17_0_22", "### other page ID ###",,,];, you can include several pages in a PDF file.

Modified script 2:

In this modification, the specific pages are retrieved from Google Slide and converted to a PDF format using a Javascript library of pdf-lib.

// Ref: https://gist.github.com/tanaikech/9d77f7d634d2d31914396d7dc84b79c3
async function exportSpecificPages_(fileId, pageNumbers) {
  const cdnjs = "https://cdn.jsdelivr.net/npm/pdf-lib/dist/pdf-lib.min.js";
  eval(UrlFetchApp.fetch(cdnjs).getContentText()); // Load pdf-lib
  const setTimeout = function (f, t) {
    Utilities.sleep(t);
    return f();
  }
  const blob = DriveApp.getFileById(fileId).getBlob();
  const pdfDoc = await PDFLib.PDFDocument.create();
  const pdfData = await PDFLib.PDFDocument.load(new Uint8Array(blob.getBytes()));
  const pages = await pdfDoc.copyPages(pdfData, [...Array(pdfData.getPageCount())].map((_, i) => i));
  pages.forEach((page, i) => {
    if (pageNumbers.includes(i + 1)) {
      pdfDoc.addPage(page);
    }
  });
  const bytes = await pdfDoc.save();
  return [...new Int8Array(bytes)];
}

// Please run this function.
async function Email3UpPDF() {
  Logger.log("Emailing!");
  var Sapp = SpreadsheetApp;
  var FS = Sapp.getActiveSpreadsheet().getSheetByName("3UP Submissions");
  var Addy1 = FS.getRange("N" + FS.getLastRow()).getValue();

  var presentationId = "10Up_PcLxVopXont9Qcu-yk-PrFGWfWPQ3ETsgys4v0Y"; // File ID of Google Slide.
  var pageObjectIds = ["g2580bcdba17_0_22"]; // Page object ID of Googe Slide.

  var pages = SlidesApp.openById(presentationId).getSlides().reduce((ar, s, i) => {
    if (pageObjectIds.includes(s.getObjectId())) {
      ar.push(i + 1);
    }
    return ar;
  }, []);
  var contents = await exportSpecificPages_(presentationId, pages);

  MailApp.sendEmail(Addy1, "Local Sign Template", "Here is your custom sign Template.", { attachments: [{ fileName: "LST3UP.pdf", content: contents, mimeType: "application/pdf" }] });
}

Note:

Tanaike
  • 181,128
  • 11
  • 97
  • 165
  • Thank you for your help! I will give your methods a try when I get the chance. I did have one question though: On your first method, when you create and then trash the temporary slide, do you know of a way to permanently delete it using scripts rather than trashing it? I am worried that my trash can will fill up my Google Drive space, as this script will see HEAVY use for about 2-3 months of the year if everything goes as planned. Thank you again for your help. – Noah Galvin Jul 15 '23 at 20:59
  • 1
    @Noah Galvin Thank you for replying. About `do you know of a way to permanently delete it using scripts rather than trashing it?`, in this case, when Drive API is used, the file can be completely deleted with a script of `Drive.Files.remove(fileId)`. In this case, please enable Drive API at Advanced Google services. By the way, in the case of pattern 2, the temporal file is not required to be used. – Tanaike Jul 15 '23 at 23:42
1

Alternative Method

I have found this related post that you could utilise to work around this matter using a specific method mentioned in the post. I have tweaked your script below & performed a quick test run on my end.

Sample Tweaked Script

function extractPdfData() {
  Logger.log("Emailing!");

  //Replace this with your API key, this is my temporary limited 'api key' for testing purposes.
  const apiSecret = 'ZQ2A9pbkh6w3xuBY'; 
  var Sapp = SpreadsheetApp;
  var FS = Sapp.getActiveSpreadsheet().getSheetByName("3UP Submissions");
  var ssID = '10Up_PcLxVopXont9Qcu-yk-PrFGWfWPQ3ETsgys4v0Y';

  //define the number of slide page(s) that you want. E.g. slide number '2'
  var pages = [2].join('%2C');

  var Addy1 = FS.getRange("N"+FS.getLastRow()).getValue();
  var requestData = { "method": "GET", "headers": { "Authorization": "Bearer " + ScriptApp.getOAuthToken() } };
  var pdfUrl = "https://docs.google.com/presentation/d/" + ssID + "/export?format=pdf";
  var slidePDF = UrlFetchApp.fetch(pdfUrl, requestData).getBlob();

  //Retrieve the number of PDF page(s) you only want to attach on your email.
  var pdfPages = processPDF(apiSecret, pages, slidePDF);

  //Send the page(s) in your email
  pdfPages.forEach(page => MailApp.sendEmail(Addy1,"Local Sign Template" ,"Here is your custom sign Template.", {attachments:[{fileName: "LST3UP.pdf", content:UrlFetchApp.fetch(page.Url).getContent(), mimeType:"application/pdf"}]}));
}

//Utilizing the 'convertapi' method
processPDF(apiSecret, pages, slidePDF) {
  var url = `https://v2.convertapi.com/convert/pdf/to/split?Secret=${apiSecret}&ExtractPages=${pages}&StoreFile=true`;
  var options = {
    method: "post",
    payload: { File: slidePDF },
  }
  var res = UrlFetchApp.fetch(url, options);
  var rawData = JSON.parse(res.getContentText());

  return rawData.Files;
}

Demo

Dummy Slide.
E.g. you want the slide number 2 to be the only PDF file sent to your recipient.

enter image description here

After running the script.
My dummy email account received this email:

enter image description here

The whole PDF attachment is only the slide number 2:

enter image description here

SputnikDrunk2
  • 3,398
  • 1
  • 5
  • 17
  • Thank you for your help! I will give your method a try when I get the chance. But I do have one question: Why isn't the code I shared in my original post able to be converted to work with individual slides? The code works for an entire Presentation PDF when presented with only the Presentation ID, and it also works on individual sheets when given a sheet ID and Spreadsheet ID. Would you be able to explain why the logic doesn't carry over? Any help is appreciated. Thank you. – Noah Galvin Jul 15 '23 at 20:00