5

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!

Rubén
  • 34,714
  • 9
  • 70
  • 166
Tony Adragna
  • 77
  • 1
  • 8

1 Answers1

8

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.");
  }  
}

screenshot

Community
  • 1
  • 1
Mogsdad
  • 44,709
  • 21
  • 151
  • 275
  • Thank you! I wasn't trying to be annoying with step-by-step instruction, so my apologies. – Tony Adragna Oct 24 '14 at 21:24
  • @TonyAdragna - I was only explaining why this answer has some stuff that seems out of context. No worries! – Mogsdad Oct 24 '14 at 21:34
  • The explanations really helped me understand the code, so I appreciate your time on this! For whatever reason, when I take out the comment for the sendmail function, it stops turning the cells with bad words red & doesn't trigger an email. I'm sure something simple I'm missing. Again, appreciate all of your help! – Tony Adragna Oct 24 '14 at 21:51
  • Since there's no other email functions in the script, supporting that line changes the authorization requirements. Run any function in the debugger, and you'll go through auth again. (For fun, try `playCatchUp`!) – Mogsdad Oct 24 '14 at 21:53
  • I'd imagine I'm just being an idiot because I've been staring at a computer too long...but this is what I get when I debug ReferenceError: "e" is not defined. (line 57, file "onEdit") – Tony Adragna Oct 24 '14 at 22:08
  • Nope, my bad. Should have been `fakeEvent`, not `e`... I did a last minute edit to make it clearer, and missed one. Fixed. – Mogsdad Oct 25 '14 at 01:50
  • Hey! I hope you're doing well. Whenever I try to run the "playCatchUp" function, I get this error "The coordinates or dimensions of the range are invalid. (line 90, file "onEdit")" I've toyed with trying to fix it, but I'm at a loss. Any idea? – Tony Adragna Oct 31 '14 at 02:50
  • That's because all rows have been processed already, so when we try to `offset` to the next item it's outside of the existing data. See the update starting with `if (numRows > 0)` - this will avoid the exception. If you want to play, you can change or remove property `"Last Processed Row"` so the script doesn't think you've already updated everything. OR you can copy & paste a new tweet to the end of the list of tweets. – Mogsdad Oct 31 '14 at 09:54
  • I thought it had to do with that, so I added a new tweet to the spreadsheet, but even when I do that, still getting the same error when running the script. Boggling my mind. Haha – Tony Adragna Oct 31 '14 at 12:09
  • Hello @Mogsdad, after a lot of search on the Internet, I see this answer to be life saver for me! :D However, the case handled by the code is way too custom for me. My requirement is simple: I simply want to access the row updated by IFTTT via an `event` object and I want to pass the row contents to another function. I do not quite under Javascript/Google Apps Script, so could you please help me in simplifying the solution you provided in your answer? – Akshay Maldhure May 12 '18 at 07:11