Setting up a trigger when a Google Sheet gets updated by IFTTT - google-apps-script

I'm using IFTTT to update my Google Sheets when I receive a SMS. Now, I would like to take a step ahead and write a Google Apps Script that would do different things with the data updated by IFTTT into my Google Sheet. I tried to achieve the same using the Google Apps Script's onEdit function, but that does not work.
I did a lot of search on multiple forums regarding this problem, and I learnt that onEdit works only when a "user" makes the changes to the Google Sheet and not when the changes are done over an API request (I believe IFTTT uses the same). I could not see even a single post with a working solution.
Any ideas? Thanks!

After a lot of Google search, I found below code to be working for me. It is inspired by this answer by Mogsdad.
function myOnEdit(e) {
if (!e) throw new Error( "Event object required. Test using test_onEdit()" );
// e.value is only available if a single cell was edited
if (e.hasOwnProperty("value")) {
var cells = [[e.value]];
}
else {
cells = e.range.getValues();
}
row = cells[cells.length - 1];
// Do anything with the row data here
}
function test_onEdit() {
var fakeEvent = {};
fakeEvent.authMode = ScriptApp.AuthMode.LIMITED;
fakeEvent.user = "hello#example.com";
fakeEvent.source = SpreadsheetApp.getActiveSpreadsheet();
fakeEvent.range = fakeEvent.source.getActiveSheet().getDataRange();
// e.value is only available if a single cell was edited
if (fakeEvent.range.getNumRows() === 1 && fakeEvent.range.getNumColumns() === 1) {
fakeEvent.value = fakeEvent.range.getValue();
}
onEdit(fakeEvent);
}
// Installable trigger to handle change or timed events
// Something may or may not have changed, but we won't know exactly what
function playCatchUp(e) {
// Build a fake event to pass to myOnEdit()
var fakeEvent = {};
fakeEvent.source = SpreadsheetApp.getActiveSpreadsheet();
fakeEvent.range = fakeEvent.source.getActiveSheet().getDataRange();
myOnEdit(fakeEvent);
}
Hope this helps someone in future. Do note that the functions playCatchUp and myOnEdit must be set as "change" and "edit" action triggers respectively in Google Apps Script.

Related

How to get an IFTTT action in Google Sheets to trigger a script?

I have an IFTTT applet that will update cell C65 in my google sheet. I'd like that sheet to run a function submitData() whenever cell C65 is changed. As many know, however, the onEdit and onChange functions do not trigger from IFTTT actions, only from manual inputs.
I've learned this from searching for answers, and nobody seems to be able to explain a workaround. The strangest thing here is that when I first put it together today, it actually worked perfectly. I then swapped google accounts and made other changes that didn't relate to the code or IFTTT applet and all of a sudden it stopped working entirely, even when switching back to the correct google account.
This is the onEdit code that works for screen inputs but not when my IFTTT enters a value into the cell:
function onEdit(e){
var cellAddress,cellAddressToTestFor;
cellAddressToTestFor = 'C65';
// Get cell edited - If it's C65 then do something
cellAddress = e.range.getA1Notation();
Logger.log('cellAddress: ' + cellAddress);
if (cellAddress === cellAddressToTestFor) {
//To Do - Code here if cell edited is correct
submitData();
};
}
Is there any way to get the IFTTT event to trigger my submitData function?
So the 'on edit' trigger won't work with IFTTT changes, but the 'on change' installable trigger will. However, the event object passed by the On Change trigger does not contain 'range', so there's no way to easily check the edited cell's location. To do so:
function onChange(e){
var cellAddress,cellAddressToTestFor;
var ss = SpreadsheetApp.getActiveSpreadsheet();
var activeSheet = ss.getActiveSheet();
var activeSheetName = activeSheet.getSheetName();
cellAddressToTestFor = 'C65';
// Get cell edited - If it's C65 then do something
cellAddress = activeSheet.getActiveRange().getA1Notation();
Logger.log('cellAddress: ' + cellAddress);
if (cellAddress === cellAddressToTestFor) {
//To Do - Code here if cell edited is correct
submitData();
};
}

My Script works for some people but not others

I created an audit template in Google sheets that uses several scripts. The script is designed to populate a cell with the active user's email and a time date stamp when they click on a check box. The script works, except for one person, the script does not recognize his active user email. Can someone tell me why the script works for some but not others and how I might fix it?
I've tested the script in a blank test spreadsheet and it works for this person. We had this same issue in another Google Sheet, but we remedied it my creating a new spreadsheet and copying all of the tabs and script. However, attempts to do the same with this spreadsheet have failed.
Here's my script:
function onEdit(e) { //this is the event Google Sheets fires when cells are changed
var email = Session.getActiveUser().getEmail(); // Get the email address of the person running the script.
var date = new Date();
var spreadsheet = SpreadsheetApp.getActive();
if (e.source.getActiveSheet().getName() == "InterimTestsOfControls") { //only do this if the changed cell is on the specific worksheet you care about
switch (e.range.getA1Notation()) { //This gets the cell that was edited
case "E5": //and this switch statement allows you to only respond to the cells you care about
if (e.value == "TRUE") {
SpreadsheetApp.getActiveSheet().getRange("F5").setValue("Prepared by " + email + " " + date);
Logger.log(email);
spreadsheet.setActiveSheet(spreadsheet.getSheetByName('Audit Planning'), true).getRange('C7').activate().setValue(email);
spreadsheet.setActiveSheet(spreadsheet.getSheetByName('InterimTestsOfControls'), true).getRange('C5').activate();
}
else {
SpreadsheetApp.getActiveSheet().getRange("F5").setValue("Click box to sign-off as preparer");
Logger.log(email);
spreadsheet.setActiveSheet(spreadsheet.getSheetByName('Audit Planning'), true).getRange('C7').activate().setValue("");
spreadsheet.setActiveSheet(spreadsheet.getSheetByName('InterimTestsOfControls'), true).getRange('C5').activate();
}
}
}
}
From the official Session class documentation:
getActiveUser()
Gets information about the current user. If security policies do not allow access to the user's identity, User.getEmail() returns a blank string.
This situation may happen under many, different circumstances:
The user's email address is not available in any context that allows a script to run without that user's authorisation, like a simple onOpen(e) or onEdit(e) trigger, a custom function in Google Sheets, or a web app deployed to "execute as me" (that is, authorized by the developer instead of the user).
These restrictions generally do not apply if the developer runs the script themselves or belongs to the same G Suite domain as the user.
In your situation I see that you are using a simple onEdit(e) trigger - so it looks like this may be the issue. You may want to check out Installable triggers instead.
function onEdit(e) {
var sh=e.range.getSheet();
var email = Session.getActiveUser().getEmail(); //This is a permissions issue. You may have to use an installable trigger and even then it's not guaranteed
var date = new Date();//you might want to use Utilities.formatDate()
//var spreadsheet = SpreadsheetApp.getActive();this is than same as e.source
if (sh.getName()=="InterimTestsOfControls") {
//you can selection your cells with information that's already in the event block rather than having to run another function
if(e.range.columnStart==6 && e.range.rowStart==5) {
if (e.value=="TRUE") {
sh.getRange("F5").setValue("Prepared by " + email + " " + date);
e.source.getSheetByName('Audit Planning').getRange('C7').setValue(email);
} else {
e.source.getRange("F5").setValue("Click box to sign-off as preparer");
e.source.getSheetByName('Audit Planning').getRange('C7').setValue("");
}
}
}
}
Getting and setting the active range is a remnant of the Macro Recorder following your every step and in general is not required in your scripts. And in fact it will slow down your scripts so please try to avoid using that approach in general for most scripting.

Writing into another Spreadsheet with SetValue fails without errors/exceptions

I'm trying to update a spreadsheet from a script running on another spreadsheet.
Nothing seems to have any effect on the table (SetValue(), SetBackgroundRGB(), etc.).
I've checked the scope, it includes "https://www.googleapis.com/auth/spreadsheets" permission; besides, this same script has no problem writing to another spreadsheet that it creates in runtime.
function updateAnotheSpreadsheet() {
var targetSpreadsheet = SpreadsheetApp.openById('<target spreadsheet id>');
var sheet = targetSpreadsheet.getSheetByName('<sheet name>');
Browser.msgBox(sheet.getRange("A1").getValue()); // Here I see that my getSheetByName worked
sheet.getRange("A1").setValue('Test value'); // But this does nothing
}
There are no errors but also no effect: nothing changes in the target spreadsheet.
Hi I was testing and was able to do what you are trying to do. You can try to declare a variable for the message, here is what I was able to do, I hope this resolves your inquiry.
function updateAnotherSpreadsheet() {
//Opening the second Spreadsheet by ID
var targetSpreadSheet = SpreadsheetApp.openById("<SpreadSheetId>");
var sheet = targetSpreadSheet.getSheetByName("<SheetName>");
//Getting the range to show on msgBox.
var msg = sheet.getRange("A1:A1").getValue();
//Displaying old data on A1:A1 from the secondary Spreadsheet.
Browser.msgBox("Old data on secondary spreadsheet: " + msg);
//Getting the range and setting new values
sheet.getRange("A1:A1").setValue('Test value').setBackground("teal").setFontColor("white");
}
I would suggest to check for more information here https://developers.google.com/apps-script/guides/sheets.
I hope this helps, greetings.
I found the problem.
A function called from OnEdit() can't ask for permissions. I needed to first update that other spreadsheet from any function called by something "active", like a button; then, after the script asks for permission once, it can do its thing from OnEdit() the next time it runs.

Trigger an email when a cell is written into from another app (IFTTT)

So here's what I've been working on. I'm a basketball coach and have a spreadsheet that pulls in all of my players' tweets from IFTTT.com (it basically takes the RSS feed of a twitter list and when it is updated, it updates the spreadsheet).
I have been working on coding that basically says "if a player tweets an inappropriate word, email me immediately."
I've got the code figured out that if I just type in an inappropriate word, it'll turn the cell red and email me. However, I have not figured out how to get the code to email me after IFTTT automatically updates the spreadsheet with tweets.
Here is my code thus far. Right now I've just got one "trigger" word that is "players" just to try and get the spreadsheet to work. Here's the code:
function onEdit(e) {
var ss = SpreadsheetApp.getActiveSpreadsheet();//Get the spreadsheet
var sheet = ss.getActiveSheet()//Get the active sheet
var cell = ss.getActiveCell().activate();//Get the active cell.
var badCell = cell.getA1Notation();//Get the cells A1 notation.
var badCellContent = cell.getValue();//Get the value of that cell.
if (badCellContent.match("players")){
cell.setBackgroundColor("red")
MailApp.sendEmail("antadrag#gmail.com", "Notice of possible inappropriate tweet", "This tweet says: " + badCellContent + ".");
}
}
Here is a link to the spreadsheet I'm working with right now: https://docs.google.com/spreadsheets/d/1g5XaIycy69a3T2YcWhcbBy0hYrxSfoEEz8c4-zP63O8/edit#gid=0
Any help or guidance on this is greatly appreciated! Thanks!
I originally wrote this answer for your previous question, so it includes answers to some of your comments from there, but since you're continuing to go asking the community to write this step-by-step , here's the next step.
The issue I'm running into is that if three tweets come into the spreadsheet at the same time, with my code, it's only going to update the most recent cell, not all three. Does that make sense?
Yes, it does make sense.
When an onEdit() trigger function calls Spreadsheet Service functions to get current info from the sheet, it enters a "Race condition". If any changes occur in the sheet after the change that triggered onEdit(), and the time when it gets scheduled, those changes will be visible when it runs. That's what you see when you assume that the change you're processing is in the last row - by the time you're processing it, there may be a new last row.
Good news, though - the attributes of the event object passed to onEdit contain the details of the change. (The parameter e.) See Event objects.
By using e.range and e.value you'll find you have the location and content of the edited cell that kicked the trigger. If additional tweets arrive before the trigger is serviced, your function won't be tricked into processing the last row.
In new sheets, the onEdit() can get triggered for multiple-cell changes, such as cut & paste. However unlikely that it may happen, it's worth covering.
Well, after getting the spreadsheet all setup & actually using the trigger from IFTTT, it doesn't work. :( I'm assuming it's not dubbing it as the active cell whenever it automatically pulls it into the spreadsheet. Any idea on a workaround on that?
Q: When is an edit not an edit? A: When it's made by a script. In that case, it's a change. You can add an installable on Change function to catch those events. Unfortunately, the change event is less verbose than an edit event, so you are forced to read the spreadsheet to figure out what has changed. My habit is to have the change handler simulate an edit by constructing a fake event (just as we'd do for testing), and passing it to the onEdit function.
So give this a try. This script:
handles a list of "bad words". (Could just as easily be monitoring for mentions of your product or cause.)
has an onEdit() function that uses the event object to evaluate the row(s) that triggered the function call.
colors "bad" tweets
has a function for testing the onEdit() trigger, based on How can I test a trigger function in GAS?
includes playCatchUp(e), an installable trigger function (change and/or time-based) that will evaluate any rows that have not been evaluated before. Script property "Last Processed Row" is used to track that row value. (If you plan to remove rows, you'll need to adjust the property.)
Has the sendMail function commented out.
Enjoy!
// Array of bad words. Could be replaced with values from a range in spreadsheet.
var badWords = [
"array",
"of",
"unacceptable",
"words",
"separated",
"by",
"commas"
];
function onEdit(e) {
if (!e) throw new Error( "Event object required. Test using test_onEdit()" );
Logger.log( e.range.getA1Notation() );
// e.value is only available if a single cell was edited
if (e.hasOwnProperty("value")) {
var tweets = [[e.value]];
}
else {
tweets = e.range.getValues();
}
var colors = e.range.getBackgrounds();
for (var i=0; i<tweets.length; i++) {
var tweet = tweets[i][0];
for (var j=0; j< badWords.length; j++) {
var badWord = badWords[j];
if (tweet.match(badWord)) {
Logger.log("Notice of possible inappropriate tweet: " + tweet);
colors[i][0] = "red";
//MailApp.sendEmail(myEmail, "Notice of possible inappropriate tweet", tweet);
break;
}
}
}
e.range.setBackgrounds(colors);
PropertiesService.getDocumentProperties()
.setProperty("Last Processed Row",
(e.range.getRowIndex()+tweets.length-1).toString());
}
// Test function, adapted from https://stackoverflow.com/a/16089067/1677912
function test_onEdit() {
var fakeEvent = {};
fakeEvent.authMode = ScriptApp.AuthMode.LIMITED;
fakeEvent.user = "amin#example.com";
fakeEvent.source = SpreadsheetApp.getActiveSpreadsheet();
fakeEvent.range = fakeEvent.source.getActiveSheet().getDataRange();
// e.value is only available if a single cell was edited
if (fakeEvent.range.getNumRows() === 1 && fakeEvent.range.getNumColumns() === 1) {
fakeEvent.value = fakeEvent.range.getValue();
}
onEdit(fakeEvent);
}
// Installable trigger to handle change or timed events
// Something may or may not have changed, but we won't know exactly what
function playCatchUp(e) {
// Check why we've been called
if (!e)
Logger.log("playCatchUp called without Event");
else {
// If onChange and the change is an edit - no work to do here
if (e.hasOwnProperty("changeType") && e.changeType === "EDIT") return;
// If timed trigger, nothing special to do.
if (e.hasOwnProperty("year")) {
var date = new Date(e.year, e.month, e["day-of-month"], e.hour, e.minute, e.second);
Logger.log("Timed trigger: " + date.toString() );
}
}
// Find out where to start processing tweets
// The first time this runs, the property will be null, yielding NaN
var lastProcRow = parseInt(PropertiesService.getDocumentProperties()
.getProperty("Last Processed Row"));
if (isNaN(lastProcRow)) lastProcRow = 0;
// Build a fake event to pass to onEdit()
var fakeEvent = {};
fakeEvent.source = SpreadsheetApp.getActiveSpreadsheet();
fakeEvent.range = fakeEvent.source.getActiveSheet().getDataRange();
var numRows = fakeEvent.range.getLastRow() - lastProcRow;
if (numRows > 0) {
fakeEvent.range = fakeEvent.range.offset(lastProcRow, 0, numRows);
onEdit(fakeEvent);
}
else {
Logger.log("All caught up.");
}
}

Why doesn't the trigger work for the script running a function on my google spreadsheet?

So I'm using a script on my googledoc spreadsheet that goes through a column and counts a certain number of occurrences of specified formatting. (i.e. in my spreadsheet "=myFunction()")
The function works fine, but my problem is despite setting up a trigger to run the script "onEdit", it never does it. I have to open the script up and save it each time I want it to update on my spreadsheet.
I've been looking up for hours but no one seems to have my question. There are no errors sent by the notifications. The code (though I don't think it's terribly relevant) for my function is:
function CountIfNotStrikeThrough2()
{
var ss = SpreadsheetApp.getActiveSpreadsheet();
var mysheet = ss.getActiveSheet();
var mydatarange = mysheet.getRange(1,1,390,1);
var numRows = mydatarange.getLastRow();
var rowindex = mydatarange.getRowIndex();
var columnindex = mydatarange.getColumnIndex();
var total =0;
for(i=rowindex;i<=numRows;i++)
{
if(mydatarange.offset(i-1, columnindex-1, 1, 1).isBlank() != true && mydatarange.offset(i-1, columnindex-1, 1, 1).getFontLine() != "line-through")
{
total++;
}
}
return total;
}
I know there's a thread here that has a comprehensive list of when the onEdit trigger doesn't activate. (here) For example, if you setup Data Validation and select an item from the list of values in the validation, it doesn't trigger the onEdit function.
Besides that, could you try a simple trigger instead of installable? This is to say, instead of explicitly associating CountIfNotStrikeThrough2() to the onEdit trigger, please try re-naming the function to "onEdit()" instead and see if that works. This creates an implicit or simple trigger. I've had trouble in the past with installable vs. simple triggers.
Also, please share the exact action you're performing to test the trigger.
Reference: https://developers.google.com/apps-script/understanding_triggers