google-apps-script - 当用户发送或编辑时,如何制作通过电子邮件发送 PDF 的脚本
问题描述
这是上一个问题的附录:Problems generate PDFs from Google Forms after it is submit
我正在创建一个谷歌脚本,它根据谷歌表单的字段填充谷歌文档模板,并通过电子邮件将生成的 PDF 发送给用户。现在我需要实现用户在按下“发送表单”后可以编辑其提交的可能性。
这是文档模板示例:
这就是电子表格的样子(我为列枚举添加了第二行):
时间戳 | 电子纠错方向 | 姓 | 名 | 年 | 模型 | 生日 (1) | 汽车 (1) | 添加新行? | 生日 (2) | 汽车 (2) | 添加新行? | 生日 (3) | 汽车 (3) | 什么时候 |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 |
2021 年 2 月 4 日 20:30:53 | example1@gmail.com | 一个 | F1 | 2005年 | Z | 2008 年 3 月 15 日 | 毫米 | 是 | 1996 年 12 月 12 日 | - | ñ | 2009 年 3 月 5 日 | ||
2021 年 2 月 4 日 20:31:05 | example2@gmail.com | 乙 | F2 | 2006年 | X | 2005 年 3 月 2 日 | 万维网 | ñ | 2000 年 8 月 24 日 | |||||
2021 年 3 月 4 日 21:40:04 | example3@gmail.com | C | F3 | 2018 | 是 | - | TT | 是 | 2004 年 3 月 3 日 | 54 | 是 | - | 43 | 2019 年 12 月 24 日 |
感谢鲁本的回答,我可以处理一些领域。到目前为止,这是我的代码:
function onSubmit(e) { // Function taken from https://medium.com/swlh/hacking-it-generate-pdfs-from-google-forms-3ca4fcc5a0aa
const rg = e.range;
const sh = rg.getSheet();
const values = e.range.getValues().flat();
const cEmail = e.namedValues['Dirección de correo electrónico'][0] ? e.namedValues['Dirección de correo electrónico'][0] : values[1];
const cSurname = e.namedValues['Surname'][0] ? e.namedValues['Surname'][0] : values[2];
const cFirstName = e.namedValues['First name'][0] ? e.namedValues['First name'][0] : values[3];
const cYear = e.namedValues['Year'][0] ? e.namedValues['Year'][0] : values[4];
const cModel = e.namedValues['Model'][0] ? e.namedValues['Model'][0] : values[5];
// EXTRACT DAY, MONTH AND YEAR from https://stackoverflow.com/a/66909780/11617040
const monthNames = ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"];
// Birthday 1
var dateStrBirthday1 = e.namedValues['Birthday (1)'][0] ? e.namedValues['Birthday (1)'][0] : values[6];
var dateArrBirthday1 = dateStrBirthday1.split("/");
var newDateBirthday1 = dateArrBirthday1[1] + "/" + dateArrBirthday1[0] + "/" + dateArrBirthday1[2];
var dateBirthday1 = new Date(newDateBirthday1);
var cBDay1 = dateBirthday1.getDate();
var cBMonth1 = dateBirthday1.getMonth(); // To use month with letters write monthNames[cMonth]
var cBYear1 = dateBirthday1.getFullYear();
// Birthday 2
var dateStrBirthday2 = e.namedValues['Birthday (2)'][0] ? e.namedValues['Birthday (2)'][0] : values[9];
var dateArrBirthday2 = dateStrBirthday2.split("/");
var newDateBirthday2 = dateArrBirthday2[1] + "/" + dateArrBirthday2[0] + "/" + dateArrBirthday2[2];
var dateBirthday2 = new Date(newDateBirthday2);
var cBDay2 = dateBirthday2.getDate();
var cBMonth2 = dateBirthday2.getMonth(); // To use month with letters write monthNames[cMonth]
var cBYear2 = dateBirthday2.getFullYear();
// Birthday 3
var dateStrBirthday3 = e.namedValues['Birthday (3)'][0] ? e.namedValues['Birthday (3)'][0] : values[12];
var dateArrBirthday3 = dateStrBirthday3.split("/");
var newDateBirthday3 = dateArrBirthday3[1] + "/" + dateArrBirthday3[0] + "/" + dateArrBirthday3[2];
var dateBirthday3 = new Date(newDateBirthday3);
var cBDay3 = dateBirthday3.getDate();
var cBMonth3 = dateBirthday3.getMonth(); // To use month with letters write monthNames[cMonth]
var cBYear3 = dateBirthday3.getFullYear();
// ^ This code should be done with a for loop
const cCar = [];
for (var i=1; i<=3; i++) {
// Here I should do the same but for Birthday (i)
cCar.push(e.namedValues['Car (' + i + ')'][0] ? e.namedValues['Car (' + i + ')'][0] : values[7+(i-1)*3]);
}
const cWhen = e.namedValues['When'][0] ? e.namedValues['When'][0] : values[14];
//Build a new invoice from the file
//Folder and file IDs
const folderAnswersID = '........';
const folderAnswers = DriveApp.getFolderById(folderAnswersID);
// Add actual time to the file name
var today = new Date();
const newFileName = 'Example - ' + cSurname + ' ' + cFirstName + ' ' + today.getDate() + ' ' + (today.getMonth()+1) + ' ' + today.getFullYear() + ' ' + today.getHours() + '_' + today.getMinutes();
const temapleFileID = '...';
//Make a copy of the template file
const newAnswersFileID = DriveApp.getFileById(temapleFileID).makeCopy(newFileName, folderAnswers).getId();
//Get the invoice body into a variable
var document = DocumentApp.openById(newAnswersFileID);
var body = document.getBody();
//Replace all the < > text in the invoice body
body.replaceText('<Surname>', cSurname);
body.replaceText('<FirstName>', cFirstName);
body.replaceText('<Year>', cYear);
body.replaceText('<Model>', cModel);
body.replaceText('<BDay1>', cBDay1);
body.replaceText('<BDay2>', cBDay2);
body.replaceText('<BDay3>', cBDay3);
body.replaceText('<BMonth1>', cBMonth1);
body.replaceText('<BMonth2>', cBMonth2);
body.replaceText('<BMonth3>', cBMonth3);
body.replaceText('<BYear1>', cBYear1);
body.replaceText('<BYear2>', cBYear2);
body.replaceText('<BYear3>', cBYear3);
for (var i=1; i<=3; i++) {
body.replaceText('<Car' + i + '>', cCar[i-1]);
}
body.replaceText('<When>', cWhen);
document.saveAndClose();
// From https://stackoverflow.com/a/66862676/11617040
var docblob=document.getAs('application/pdf').setName(newFileName + '.pdf');
var dupdocs=folderAnswers.getFilesByName(newFileName);
while(dupdocs.hasNext()) {
dupdocs.next().setTrashed(true);
}
var file=folderAnswers.createFile(docblob);
var emailTitle = 'Your answer was submitted!';
var emailBody = 'Dear <b>' + cFirstName + '</b>:<br><br>The document <b>' + newFileName + '</b> was submitted correctly. Please see the attachment.';
var attachment = DriveApp.getFileById(newAnswersFileID);
GmailApp.sendEmail(
cEmail,
emailTitle,
"",
{
htmlBody: emailBody,
attachments: [attachment.getAs(MimeType.PDF)]
}
);
}
我也有 2 个触发器:
- 提交时:当用户按下“发送表单”时。
- 编辑:当用户在按下“发送表单”后编辑其表单时。
我在代码上标记了 2 个问题。我需要关心两种类型的“字段”:
- “非重复字段”:姓氏、名字、年份、型号、时间。这些字段不需要循环
for
,因此代码不会那么长:- “非日期字段”:姓、名、年。它们并不特别。
- “日期字段”:WHEN。这是 Google 表单上的日期字段,但没有什么特别之处。
- “重复字段”:日、月、年、车。这些字段确实需要一个
for
循环,因此代码不会那么长:- “非日期字段”:Car。我
for
为这个字段创建了一个循环。 - “日期字段”:日、月、年。这些字段是日期字段,但它们需要分开,所以我分别有天、月和年。这已在上一个问题中解决,但我不知道如何
for
为这些问题创建一个循环。
- “非日期字段”:Car。我
我的问题是:我们如何使用表单,用户可以在按下“发送表单”后编辑其提交,而不是对列标题进行硬编码,也不关心分解日期字段并for
以奇特的方式使用循环?
如果for
循环问题太难回答,你可以忘记它,我需要尽快实现用户所做的编辑。
解决方案
方法一
为了让接收 PDF 的用户能够编辑他们之前提交的表单,您可以使用该getEditResponseUrl()
方法包含为他们的表单响应生成的编辑 URL。
例如,如果您要修改文档以包含标签,则可以按如下方式插入 URL:
function onSubmit(e){
/*
snip
*/
var myFormId = "1234567890abcdefghijklmn"; // replace with your Form ID
var allResponses = FormApp.openById(myFormId).getResponses();
var lastResponse = allResps[allResps.length-1];
var editUrl = lastResponse.getEditResponseUrl();
body.replaceText('<editURL>', editUrl);
/*
snip
*/
}
代码故意冗长以使步骤清晰;您可以将其合并为 2 行。
上面的代码获取最后一个响应的编辑 URL,这可能是一个问题......
方法二
如果你在短时间内有很多提交,有可能最后一次提交不是触发代码的提交。那么方法 1 不是推荐的方法。
相反,您必须使用一些标识符(例如时间戳,甚至可能是用户名)来获取与事件对象中的信息相关的特定响应。
您可能还需要在格式化时间戳方面做一些工作以便比较它们。
例如,
function onSubmit(e){
/*
snip
*/
var myFormId = "1234567890abcdefghijklmn"; // replace with your Form ID
// handle the timestamp
var timestampParts = e.namedValues.Timestamp.toString().split(" ");
var tParts = timestampParts[1].split(":");
var dParts = timestampParts[0].split("/");
var tstamp = new Date(dParts[2], dParts[1]-1, dParts[0], tParts[0], tParts[1], tParts[2]);
var tEarlier = new Date(tstamp);
// get the time 3 seconds earlier, tEarlier
tEarlier.setSeconds(tstamp.getSeconds() - 3); // tune "3" to appropriate value
// get all responses since tEarlier
var allResponses = FormApp.openById(myFormId).getResponses(tEarlier);
// loop over the responses and check the event object timestamp and username match the response item timestamp and username
for (let i=0;i<allResponses.length;i++){
if( Date.parse(allResponses[i].getTimestamp()) === Date.parse(tstamp)
&& e.namedValues["Email address"] === allResponses[i].getRespondentEmail()
){
// Match! Grab the edit URL
var editUrl = allResps[i].getEditResponseUrl();
}else{
// Not a match! Something went wrong (e.g. event object missing, triggered twice, etc.)
var editUrl = "Not found";
// log somewhere and investigate
}
}
body.replaceText('<editURL>', editUrl);
/*
snip
*/
考虑点:
- 如果您不小心,时间戳可能会很麻烦。我必须执行上述操作,因为时间戳来自 dd/mm/yy 格式的表单,但 JS 使用 yy/mm/dd(至少对我而言 - 它可能因您的语言环境而异)。
- 第二个标识符(如电子邮件地址)不是必需的,但建议使用。我之前看到过表单的奇怪行为,其中事件触发了两次,或者被延迟了等,因此进行此检查对于确保某人不会获得其他人的编辑 URL 非常重要,以防出现您无法控制的问题。
一些参考
有关这些方法的更多信息(和示例),请参阅FormResponse API 参考。
推荐阅读
- spring-boot - Keycloak 和 Spring Boot
- javascript - 如何使用 vanilla Javascript 将文件的内容放入数组中?
- c++ - 如何在 C++ 中解析 8 位日期?
- reactjs - 有没有办法在 ReactJS 中向动态表中添加行?
- installation - 在 MAC 上安装 CPLEX Studio 20 时出现问题
- arrays - 从文本文件中的一些字符串创建数组 - Ruby
- xforms - XFDL 日期约束
- sql-server - 创建触发器以将数据从一个表获取到另一个表并生成时间戳
- nginx - nginx 插件不工作;您现有的配置可能有问题
- c# - 读取和写入多个名为管道c#的消息的问题