воскресенье, 24 февраля 2013 г.

Дополняем RSS-ленту оповещениями по почте


Недавно я озаботился вопросом, как бы получать уведомления о появлении новостей в RSS-фиде. Можно, конечно, постоянно пастись в RSS-клиенте, но мне хотелось именно уведомлений. Почтовые вполне подойдут, потому что со смартфоном я не расстаюсь.

Пока думал как это сделать, нашёл очень интересный сервис ifttt.com (If This Then That). Но в конечном итоге, у меня родилось другое решение: скрипт на Google Apps Script. Никогда раньше мне не приходило в голову использовать этот сервис Гугла. Почему бы не попробовать?



Правда, появился ещё один вопрос: как Google Apps используют другие? Просто любопытство, вдруг я что-то ещё интересное упустил. По этому поводу я опубликовал небольшой пост на Хабрахабре. Там был маленький пример скрипта, разбирающего RSS-фид, и вопрос кто-что придумал с Google Apps. Странно, но идеями меня не завалили. Был только один ответ. А через некоторое время, была ещё статья о применении Google Apps. Применение не бытовое, конечно, но возьму на вооружение. И всё, других идей пока не подкинули.

С тех пор я дополнил скрипт. Теперь он понимает два протокола (RSS версии 2.0 и Atom) и работает с несколькими фидами, а не с одним.

Как это сделано... На Google Диске создаём таблицу (spreadsheet) следующего вида:


Фидов может быть, естественно, сколько угодно. Скрипт будет работать со второго ряда вниз до первой пустой ячейки в колонке “A”. Колонка “Время последней новости” служебная, её можно скрыть. И её не нужно заполнять при добавлении нового фида.

Далее в созданную таблицу (spreadsheet) добавляем сценарий (меню “Инструменты” => “Редактор скриптов...”):

function checkFeeds() {
  const mailAddress = "mail@mail.me";
  const feedColumn = 1;
  const mailSubjectColumn = 2;
  const lastPublicationDateColumn = 3
  
  var row = 2;
  var sheet = SpreadsheetApp.getActiveSheet();
  while (true) {
    var currentRow = row++;
    
    var feed = sheet.getRange(currentRow, feedColumn).getValue();
    if (feed == "") {
      break;
    }
    
    var mailSubject = sheet.getRange(currentRow, mailSubjectColumn).getValue();
    var mailBody = "";
    var lastPublicationDateCell = sheet.getRange(currentRow, lastPublicationDateColumn);
    
    var feedContent = UrlFetchApp.fetch(feed).getContentText();
    var doc = Xml.parse(feedContent, false);
    
    var root = doc.getElement();
    if (root == null) {
      break;
    }
    
    var rootName = root.getName().getLocalName();
    if (rootName == "rss") {
      var rssVersion = root.getAttribute("version");
      if (rssVersion == null) {
        break;
      }
      
      if (rssVersion.getValue() == "2.0") {
        mailBody = parseRssFeed(root, mailSubject, lastPublicationDateCell);
      }
    } else if (rootName == "feed") {
      mailBody = parseAtomFeed(root, mailSubject, lastPublicationDateCell);
    }
    
    if (mailBody != "") {
      GmailApp.sendEmail(mailAddress, mailSubject, mailBody);
    }
  }
}

function parseRssFeed(root, mailSubject, lastPublicationDateCell) {
  var maxPubDateText = lastPublicationDateCell.getValue();
  var maxPubDate = "2000-01-01T00:00:00Z";
  if (maxPubDateText != "") {
    maxPubDate = maxPubDateText;
  }
      
  var channel = root.getElement("channel");
  
  var mailBody = "";
  var items = channel.getElements("item")
  var curMaxPubDate = maxPubDate;
  for (var i in items) {
    var dateText = items[i].getElement("pubDate").getText();
    var pubDate = Utilities.formatDate(new Date(dateText), "GMT", "yyyy-MM-dd'T'HH:mm:ss'Z'");
    
    if (pubDate > maxPubDate) {
      if (pubDate > curMaxPubDate) {
        curMaxPubDate = pubDate
      }
      mailBody += "\nЗаголовок: " + items[i].getElement("title").getText();
      mailBody += "\nСсылка: " + items[i].getElement("link").getText();
      mailBody += "\nДата публикации: " + pubDate;
      mailBody += "\n";
    }
  }

  lastPublicationDateCell.setValue(curMaxPubDate);
  return mailBody;
}

function parseAtomFeed(root, mailSubject, lastPublicationDateCell) {
  var maxPubDateText = lastPublicationDateCell.getValue();
  var maxPubDate = "2000-01-01T00:00:00Z";
  if (maxPubDateText != "") {
    maxPubDate = maxPubDateText;
  }
      
  var mailBody = "";
  var entries = root.getElements("entry")
  var curMaxPubDate = maxPubDate;
  for (var i in entries) {
    var pubDate = entries[i].getElement("updated").getText();
    
    if (pubDate > maxPubDate) {
      if (pubDate > curMaxPubDate) {
        curMaxPubDate = pubDate
      }
      mailBody += "\nЗаголовок: " + entries[i].getElement("title").getText();
      mailBody += "\nСсылка: " + entries[i].getElement("link").getAttribute("href").getValue();
      mailBody += "\nДата публикации: " + pubDate;
      mailBody += "\n";
    }
  }

  lastPublicationDateCell.setValue(curMaxPubDate);
  return mailBody;
}

Теперь надо задать период автоматического запуска скрипта. Например, каждые 10 минут. Для этого в редакторе сценариев выбираем пункт меню “Ресурсы” => “Триггеры текущего проекта...” и добавляем “динамический минутный таймер”.

Всё-таки программировать, это прекрасно. Такой самодельный вариант оповещения мне нравится гораздо больше, чем ifttt. Ведь я его сам сделал! Ну и Google, по правде...

4 комментария:

  1. А есть похожий скрипт, только чтобы письмо приходило если в фиде присутствует заданная фраза или слово?

    ОтветитьУдалить
  2. Сделал всё по инструкции. вроде заработало, но на почту пока ничего не приходило. Только я вот не совсем понял как скрипт обратится к моей таблице с atom фидом...

    ОтветитьУдалить
    Ответы
    1. Арсений, я мог неправильно понять вопрос... Скрипт последовательно проходит по строкам нашей таблицы с фидами. Берёт фид (некий URL) и отправляет его в просторы Интернета. В ответ получает какой-то XML. Далее скрипт идентифицирует этот XML. Если это XML со структурой RSS, вызывается функция parseRssFeed, если это Atom - то функция parseAtomFeed. Каждая из этих функций умеет парсить (разбирать) свой формат фида. Ну а по результатам разбора функции формируют текст для письма мне (вам).

      Удалить
  3. Спасибо, очень удобно.

    ОтветитьУдалить