Google Apps Script: "Script function not found" error - google-apps-script

I have been searching around for a solution for my issue but have not been able to find one, and as a result am writing my first question in Stack Overflow.
I am currently writing a simple program on Google Apps Script that sends out an email reminder to a user if they forget to submit a Google Form by a certain date. Right now I have 3 different functions: onFormSubmit, scheduleTrigger, and reminderEmail. Below is the code I have written out thus far:
/**
This function searches the Google Form for the date when the previous
session was held & the date when the next session will be held.
This function also calculates the date when to remind the user if they
forget to submit the Google Form. This reminder date is calculated to be 2
days after the date when the next session is held.
This function calls the next function: scheduleTrigger.
*/
function onFormSubmit(e) {
var form = FormApp.getActiveForm();
var frm = FormApp.getActiveForm().getItems();
var futureDate = new Date(e.response.getResponseForItem(frm[3]).getResponse());
var pastDate = new Date(e.response.getResponseForItem(frm[0]).getResponse());
var reminderDate = new Date(futureDate.setDate(futureDate.getDate() + 2));
futureDate.setDate(futureDate.getDate() - 2);
scheduleTrigger(reminderDate, futureDate, pastDate, form);
}
/**
This function schedules the reminder email trigger at a specific date. The
specific date is the reminder date that was calculated in the previous function.
This function calls the next function: reminderEmail.
*/
function scheduleTrigger(reminderDate, futureDate, pastDate, form) {
ScriptApp.newTrigger('reminderEmail(reminderDate, futureDate, pastDate, form)').timeBased().atDate(reminderDate.getFullYear(), reminderDate.getMonth(), reminderDate.getDate()).inTimezone('America/New_York').create();
}
/**
This function checks the submissions if the form has been submitted.
If there is no submission, then it sends out an email reminder.
*/
function reminderEmail(reminderDate, futureDate, pastDate, form) {
var count = 0;
var formResponses = form.getResponses();
for (var i = 0; i < formResponses.length; i++) {
var formResponse = formResponses[i];
var itemResponses = formResponse.getItemResponses();
for (var j = 0; j < itemResponses.length; j++) {
var itemResponse = itemResponses[j];
if (itemResponse == futureDate) {
count++;
}
}
}
if (count != 2) {
MailApp.sendEmail("test#gmail.com",
"Submit Form Reminder",
"Hey! You didn't fill out the form yet!");
};
}
The issue I am running into is that I receive an error message stating "The selected function cannot be found" for function reminderEmail whenever I run the program. I have looked around and followed previous posts made to solve this issue, such as renaming the function and restarting with a whole new different Google From but nothing has worked. I also have full ownership and authorization to the script so permissions shouldn't be an issue.
Please let me know if you have any ideas on how to solve this problem. Any and all feedback is welcome! If there is any part of my question that is unclear please let me know. Thank you for taking the time to read through this lengthy post.

The reason it's failing is because you need to only pass the function name to ScriptApp.newTrigger. You can't send parameters with it, that's something you'll have to handle in other ways.
Documentation
/**
This function searches the Google Form for the date when the previous session was held & the date when the next session will be held.
This function also calculates the date when to remind the user if they forget to submit the Google Form. This reminder date is calculated to be 2 days after the date when the next session is held.
This function calls the next function: scheduleTrigger.
*/
function onFormSubmit(e) {
var form = FormApp.getActiveForm();
var frm = FormApp.getActiveForm().getItems();
var futureDate = new
Date(e.response.getResponseForItem(frm[3]).getResponse());
var pastDate = new
Date(e.response.getResponseForItem(frm[0]).getResponse());
var reminderDate = new Date(futureDate.setDate(futureDate.getDate() + 2));
futureDate.setDate(futureDate.getDate() - 2);
scheduleTrigger(reminderDate);
}
/**
This function schedules the reminder email trigger at a specific date. The specific date is the reminder date that was calculated in the previous function.
This function calls the next function: reminderEmail.
*/
function scheduleTrigger(reminderDate) {
ScriptApp.newTrigger('reminderEmail').timeBased().atDate(reminderDate.getFullYear(), reminderDate.getMonth(), reminderDate.getDate()).inTimezone('America/New_York').create();
}
/**
This function checks the submissions if the form has been submitted. If there is no submission, then it sends out an email reminder.
*/
function reminderEmail() {
var form = FormApp.getActiveForm();
var count = 0;
var formResponses = form.getResponses();
for (var i = 0; i < formResponses.length; i++) {
var formResponse = formResponses[i];
var itemResponses = formResponse.getItemResponses();
for (var j = 0; j < itemResponses.length; j++) {
var itemResponse = itemResponses[j];
if (itemResponse == futureDate) { // Unsure what this part is for, perhaps you should find another way to calculate this.
count++;
}
}
}
if (count != 2) {
MailApp.sendEmail("test#gmail.com",
"Submit Form Reminder",
"Hey! You didn't fill out the form yet!");
};
}
I was able to clean up your code to a way it should work, but not sure about futureDate in the reminderEmail function.

Related

Using Google Apps Script to get responses from multiple forms and send an email once to each respondent

I have been puzzling this over for some time and have searched extensively, but found no solution.
I'm using Google Apps Script and I run events for a large organization and we have about 80 different registration Google Forms for these events. I want to get the registrant's email address and send them an email when they submit their form. This is easy to accomplish by setting up each individual form. Ideally, I would set up the onSubmit trigger for the form and then copy that form for each new event. However, it seems you cannot install a trigger programmatically without going to the form and running the script manually, and then authorize it. When a form is copied it also loses all its triggers. Am I wrong about this? Doing this for each form is not realistic given that events are added all the time and I have other responsibilities.
Is there no way to set a trigger for these files without running and authorizing each one?
My other solution is:
I am trying to get all the forms in a folder and then get the responses and send a single email to each registrant. This seems overly complicated and requires checking all the forms regularly since there are no triggers for the individual forms. I tried setting triggers in my spreadsheet for the forms and this works, but the number of triggers for a spreadsheet is limited to 20, so doesn't work here. Running my script every minute and then checking if an email has been sent to each respondent seems possible, but complex and possibly prone to errors...
Thanks for any help you can offer!
pgSystemTester's answer worked for me.
I added two bits of code.
One, to declare the time stamp value to zero if there wasn't one
there.
Two, the code needed a "-1" when you get dRange or you insert a new
row which each run.
function sendEmailsCalendarInvite() {
const dRange = sheet.getRange(2, registrationFormIdId, sheet.getLastRow() - 1, 2);
var theList = dRange.getValues();
for (i = 0; i < theList.length; i++) {
if (theList [i][1] == ''){
theList[i][1] = 0;
}
if (theList[i][0] != '') {
var aForm = FormApp.openById(theList[i][0]);
var latestReply = theList[i][1];
var allResponses = aForm.getResponses();
for (var r = 0; r < allResponses.length; r++) {
var aResponse = allResponses[r];
var rTime = aResponse.getTimestamp();
if (rTime > theList[i][1]) {
//run procedure on response using aForm and aResponse variables
console.log('If ran')
if (rTime > latestReply) {
//updates latest timestamp if needed
latestReply = rTime;
}
//next reply
}
}
theList[i][1] = latestReply;
//next form
}
}
//updates timestamps
dRange.setValues(theList);
}
This is probably a simple solution that will work. Setup a spreadsheet that holds all Form ID's you wish to check and then a corresponding latest response. Then set this below trigger to run every ten minutes or so.
const ss = SpreadsheetApp.getActiveSheet();
const dRange = ss.getRange(2,1,ss.getLastRow(),2 );
function loopFormsOnSheet() {
var theList = dRange.getValues();
for(i=0;i<theList.length;i++){
if(theList[i][0]!=''){
var aForm = FormApp.openById(theList[i][0]);
var latestReply = theList[i][1];
var allResponses = aForm.getResponses();
for(var r=0;r<allResponses.length;r++){
var aResponse = allResponses[r];
var rTime = aResponse.getTimestamp();
if(rTime > theList[i][1]){
//run procedure on response using aForm and aResponse variables
if(rTime >latestReply){
//updates latest timestamp if needed
latestReply=rTime;
}
//next reply
}
}
theList[i][1] = latestReply;
//next form
}
}
//updates timestamps
dRange.setValues(theList);
}

How to filter out all emails that came from a mailing list in Gmail

Is there a way to filter out all emails that came from a mailing list within Gmail or Google Apps Script using a search query. I know you can filter out a specific email address using list:info#example.com. But I want a catch-all type of query or even a query to catch-all from a specific domain such as list:#example.com. However, this does not work. Any ideas? Any help is greatly appreciated, thank you!
This function will trash all messages from all inbox thread that are not in the list.
function emailFilter() {
var list=['a#company.com','b#company.com','c#company.com','d#company.com','e#company.com'];
var threads=GmailApp.getInboxThreads();
var token=null;
for(var i=0;i<threads.length;i++) {
if(threads[i].getMessageCount()) {
var messages=threads[i].getMessages();
for(var j=0;j<messages.length;j++) {
if(list.indexOf(messages[j].getFrom()==-1)) {
messages[j].moveToTrash();
}
}
}
}
}
I haven't tested it because I keep my inbox empty all of the time. You might want to replace 'moveToTrash()' to 'star()' for testing
What I could understand from your question and your comments, you need to filter the emails in a user's inbox that he has received, which don't only contain a certain label, but also a certain domain. If I understood well this code can help you:
function checkLabels() {
// Get the threads from the label you want
var label = GmailApp.getUserLabelByName("Label Test List");
var threadArr = label.getThreads();
// Init variable for later use
var emailDomain = '';
// Iterate over all the threads
for (var i = 0; i < threadArr.length; i++) {
// for each message in a thread, do something
threadArr[i].getMessages().forEach(function(message){
// Let's get the domains from the the users the messages were from
// example: list:#example.com -> Result: example.com
emailDomain = message.getFrom().split('<').pop().split('>')[0].split('#')[1];
// if emailDomain is equal to example.com, then do something
if(emailDomain === 'example.com'){
Logger.log(message.getFrom());
}
});
}
}
Using the Class GmailApp I got a certain label with the .getUserLabels() method and iterate through the threads thanks to the .getInboxThreads method. With a second loop and the .getMessages() you can get all the messages in a thread and for knowing the one who sent them, just use the .getFrom() method.
Docs
For more info check:
Gmail Service.
Class GmailMessage.
Class GmailThread.
So I was able to avoid replying to emails that come from a mailing list address by using the getRawContent() method and then searching that string for "Mailing-list:". So far the script is working like a charm.
function autoReply() {
var interval = 5; // if the script runs every 5 minutes; change otherwise
var date = new Date();
var day = date.getDay();
var hour = date.getHours();
var noReply = ["email1#example.com",
"email2#example.com"];
var replyMessage = "Hello!\n\nYou have reached me during non-business hours. I will respond by 9 AM next business day.\n\nIf you have any Compass.com related questions, check out Compass Academy! Learn about Compass' tools and get your questions answered at academy.compass.com.\n\nBest,\n\nShamir Wehbe";
var noReplyId = [];
if ([6,0].indexOf(day) > -1 || (hour < 9) || (hour >= 17)) {
var timeFrom = Math.floor(date.valueOf()/1000) - 60 * interval;
var threads = GmailApp.search('from:#example.com is:inbox after:' + timeFrom);
var label = GmailApp.getUserLabelByName("autoReplied");
var repliedThreads = GmailApp.search('label:autoReplied newer_than:4d');
// loop through emails from the last 4 days that have already been replied to
for (var i = 0; i < repliedThreads.length; i++) {
var repliedThreadsId = repliedThreads[i].getMessages()[0].getId();
noReplyId.push(repliedThreadsId);
}
for (var i = 0; i < threads.length; i++) {
var message = threads[i].getMessages()[0];
var messagesFrom = message.getFrom();
var email = messagesFrom.substring(messagesFrom.lastIndexOf("<") + 1, messagesFrom.lastIndexOf(">"));
var threadsId = message.getId();
var rawMessage = message.getRawContent();
var searchForList = rawMessage.search("Mailing-list:");
var searchForUnsubscribe = rawMessage.search("Unsubscribe now");
// if the message is unread, not on the no reply list, hasn't already been replied to, doesn't come from a mailing list, and not a marketing email then auto reply
if (message.isUnread() && noReply.indexOf(email) == -1 && noReplyId.indexOf(threadsId) == -1 && searchForList === -1 && searchForUnsubscribe === -1){
message.reply(replyMessage);
threads[i].addLabel(label);
}
}
}
}

Listing Google calendar events using API

I am trying to write a Google Apps script to modify calendar events so I have modified an example to list events first. When I try debugging this it reports an error that "Calendar is not defined" on the line "events = Calendar.Events.list(calendarId, options);"
I have enabled the advanced calendar API, and am basing my script on one from the Google documentation, so I assume that one worked. Is there anything else I need to do to access the relevant objects and methods?
/*
Adapted from code in https://developers.google.com/apps-script/advanced/calendar
*/
function syncColourCode() {
var calendarId = CalendarApp.getDefaultCalendar();
var properties = PropertiesService.getUserProperties();
var fullSync = properties.getProperty('fullSync'); // sync status is stored in user properties
var options = {
maxResults: 100
};
var syncToken = properties.getProperty('syncToken'); // pointer token from last sync also stored in user properties
if (syncToken && !fullSync) { // if there is a sync token from last time and sync status has not been set to full sync
options.syncToken = syncToken; // adds the current sync token to the list of sync options
} else {
// Sync events from today onwards.
options.timeMin = new Date().toISOString(); //change to new Date().toISOString() from getRelativeDate(-1, 0).toISOString()
}
// Retrieve events one page at a time.
var events;
var pageToken;
do {
try {
options.pageToken = pageToken;
events = Calendar.Events.list(calendarId, options);
} catch (e) {
Not a google-apps expert, but from reviewing the code, I see a possible problem. At no point do I see your code checking to see if getDefaultCalendar() actually returned a valid calendar ID. Later your code uses that ID under the assumption that it is good. Have you checked the value of calendarId that is returned?
Sometimes you have to read a little deeper into the message, but I always try to start with trusting the error return. In this case "Calendar is not defined" makes me question the value of calendarId.
It seem that Google made some change so that there is no Calendar reference from the AppScript API.
Anyway to get the event you may use this API:
CalendarApp.getEvents(startTime, endTime)
https://developers.google.com/apps-script/reference/calendar/calendar-app#geteventsstarttime-endtime
Below are my example function running within google sheet.
function listEventsWithinTwoMonth(){
var calendar = CalendarApp.getDefaultCalendar();
var spreadsheet = SpreadsheetApp.getActiveSheet();
var now = new Date();
var twoMonthFromNow = new Date(now.getTime() + (24 * 60 * 60 * 30 * 4 * 1000));
var events = calendar.getEvents(now, twoMonthFromNow);
if (events.length > 0) {
// Header Rows
spreadsheet.appendRow(["#่","id","StartTime","EndTime","Title","Description"]);
for (i = 0; i < events.length; i++) {
var event = events[i];
Logger.log("" + (i+1) + event.getId() +" "+ event.getStartTime()+" "+event.getEndTime()+" "+event.getTitle()+" "+event.getDescription())
spreadsheet.appendRow([(i+1),event.getId(),event.getStartTime(),event.getEndTime(),event.getTitle(),event.getDescription()]);
}
} else {
Logger.log('No upcoming events found.');
}
}
Hope this help.
CalendarApp.getDefaultCalendar() retrieves an object of the class Calendar that has multiple properties, among others an Id.
To retrieve the calendar Id, you need to define
var calendarId = CalendarApp.getDefaultCalendar().getId();

Adding guests to calendar event by script without invites being sent

what I would like to do is add guests to certain google calendar events via a script I created without them being notified via an invitation. I tried the addGuest() function but whenever someone is added he or she is getting an invitation email.
Is this possible?
Thank you very much in advance.
Best, Phil
EDIT:
Thanks for your answer. Yes I'm sure there ere invites being sent. Here is the relevant part of my code:
for (i = 0; i < events.length; ++i)
{
if (events[i].getLocation().match("XYZ") || events[i].getTitle().match("ABC")) {events[i].setLocation(addabc) && events[i].addGuest(mailxyz)}
}
EDIT 2: I verified again, that invites are indeed being sent out. Here is my entire script, butI have no idea which part might cause this:
function ModifyAppointments() {
var idabc = "idabc..."
// ABC
var contactabc = ContactsApp.getContactById(idabc);
var addressabc = contactabc.getAddresses();
var addabc = addressabc[0].getAddress();
var mailsabc = contactabc.getEmails();
var mailabc = mailsabc[0].getAddress();
var today = new Date();
var start = new Date();
var end = new Date(start.getTime() +(500 * 60 * 60 * 24 * 7));
var events = CalendarApp.getDefaultCalendar().getEvents(start, end);
for (i = 0; i < events.length; ++i)
{
if (events[i].getLocation().match("abc") || events[i].getLocation().match("abc") || events[i].getTitle().match("abc") || events[i].getTitle().match("abc")) {events[i].setLocation(addabc) && events[i].addGuest(mailabc)}
}
}
EDIT 3: Here is a screenshot of one of the invites that was being sent. Unfortunately it is in German:
If you wonder why there is such a lage time between the sending of the invite and the date an time of the appointment, I used to have a larger time interval in my script. Now i shortened it to a week in advance.
I've had a look at this, and I'm absolutely positive that this code is not what is sending the invite. As I said, there's an open issue for this at https://code.google.com/p/google-apps-script-issues/issues/detail?id=574 because this function does not send an invite, and I've also just tested this again myself, so we can rule that out.
Here's a test function that you can copy and paste and place someone else's (Invites are never sent to the creator of the event) email address into a new script and confirm for yourself that .addGuest() is not the culprit:
function myFunction() {
var email = 'ANOTHER-PERSONS-EMAIL-HERE';
var event = CalendarApp.getDefaultCalendar().createAllDayEvent('Apollo 11 Landing',new Date('November 20, 2014'));
event.addGuest(email);
}
The remaining assumption is that some other portion of your script is sending out the invite (Once again, it is not possible for the Calendar service in the script to send an invite).
The problem stems from how you wrote your code. You have to iterate over an array of events. I have fixed it for you.
function myFunction() {
var today = new Date();
var email = 'ANOTHER-PERSONS-EMAIL-HERE';
var events = CalendarApp.getDefaultCalendar().getEventsForDay(today);
for (i = 0; i < events.length; ++i)
{
events[i].addGuest(email);
}
}
// Let me know if you have any questions - amir#serialmetrics.com, Amir Behbehani, // Chief Scientist, Serial Metrics

Get the current response onSubmit

Is there any way to find out the current response of the Google Form (programmatically)?
In the onSubmit function.
Looked over their documentation but I have to provide the respondId.
How do I find the respondId?
A snipped code from them about this is:
// Open a form by ID and log the responses to each question.
var form = FormApp.openById('1234567890abcdefghijklmnopqrstuvwxyz');
var formResponses = form.getResponses();
for (var i = 0; i < formResponses.length; i++) {
var formResponse = formResponses[i];
var itemResponses = formResponse.getItemResponses();
for (var j = 0; j < itemResponses.length; j++) {
var itemResponse = itemResponses[j];
Logger.log('Response #%s to the question "%s" was "%s"',
(i + 1).toString(),
itemResponse.getItem().getTitle(),
itemResponse.getResponse());
}
}
UPDATE:
function onSubmit(e) {
/* Get values entered by filling the form */
var itemResponses = e.response.getItemResponses();
var myWantedValue = itemResponses[COLUMN_NUMBER].getResponse();
}
Note: By passing the e(vent) as parameter the values can be accessed by submitting the live form, NOT by the script editor!
This code gives you the values for the last form response, is that what you're looking for ?
function myFunction() {
var formResponses = FormApp.getActiveForm().getResponses();
Logger.log(formResponses.length);
var formResponse = formResponses[formResponses.length-1];
var itemResponses = formResponse.getItemResponses();
for (var j = 0; j < itemResponses.length; j++) {
var itemResponse = itemResponses[j];
Logger.log('Last response to the question "%s" was "%s"',
itemResponse.getItem().getTitle(),
itemResponse.getResponse());
}
}
You are trying to read the exact response that triggered the execution of onSubmit(), to run another process on particular data, such as sending a confirmation email.
If the above is correct:
Instead of using the form as a data source for your later processing, try to read the Google Sheet storing the responses. It may seem the problem is the same: reading either data source (the form itself, or the sheet storing the data), doesn't point you to the exact entry that has triggered the onSubmit() event.
The difference is that in a sheet you can create a "reply_sent" column, to timestamp the instant each form response has been postprocessed, for example, having sent an automated reply based on its contents.
Then, when onSubmit() is run, it goes through all responses and processes not only the last one received, or the one having triggered the onSubmit() funtion, but also any other response that may have been left unreplied for whatever reason ("reply_sent" field blank). A last benefit from this approach is that your script is logging how it is behaving, since a date is recorded along with each response as it triggers obSubmit().
You cannot set up a custom confirmation message for each user.