Meekee - это крошечный slackbot, написанный на Node.js, который подключается к Google Calendar API и отправляет уведомления, когда приближается событие.

Как распределенная команда, у нас было много небольших разочарований по поводу ежедневного присоединения к удаленным звонкам. Мы сделали Meekee, чтобы упростить поиск ссылки на видеовстречу / комнату, запомнить, о чем встреча, когда она начинается и т. Д. Meekee отправляет пинг за 3 минуты до ее начала, чтобы вы могли продолжать работать до тех пор, но еще достаточно пора быть в правильном настроении, когда он начнется.

Эта статья посвящена технической стороне дела. Сначала ознакомьтесь с отличной статьей Tomomi Sasaki, чтобы понять, зачем мы ее создали. 😉



Вы можете прочитать эту статью, если:

  • вам интересно, как другие люди структурируют свою архитектуру и особенно программное обеспечение без пользовательского интерфейса, например, бот.
  • вам интересно, как работать с GoogleCalendar Api в Node.js, обновлять токены с течением времени и обрабатывать обратный вызов аутентификации вне браузера.

В Интернете уже существует множество руководств о том, как создавать ботов, поэтому я не буду вдаваться в подробности. Код Slack’s Easy-Peasy-Bot - хорошее место для начала.

Стек

  • Написано на Node.js
  • Botkit для помощи с функциями бота (установка пользователем, прослушивание, разговор и т. Д.).
  • MongoDB для хранения с использованием драйвера для Botkit botkit-storage-mongo.
  • API GoogleCalendar с библиотекой google-api-nodejs-client для отслеживания событий.
  • Размещено на Heroku с Codeship для непрерывной интеграции.

Код является открытым исходным кодом, если вы хотите заглянуть: github.com/aqworks/meekee-bot.

Это приблизительный обзор основного процесса:

Основная установка

Сначала Botkit загружает index.js, инициируется подключение к базе данных и Slack. Botkit обратится onInstallation при установке новых команд.

var controller = app.configure(process.env.PORT, process.env.SLACK_CLIENT_ID, process.env.SLACK_CLIENT_SECRET, config, onInstallation);
controller.on('rtm_open', function (bot) {
  if (!userLib)
    userLib = require('./lib/user').init(controller);
controller.webserver.get('/google/auth/callback', function(req, res) {
    res.send("<p>Next, copy and paste this Authentication Code in Slack to @meekee: <br/><strong>"+req.query.code+"</strong></p>");
  });
setTimeout(function(){
    loop(bot, true);
  }, 3000);
  setInterval(function(){
    loop(bot, false);
  }, 60000);
});

controller.on("rtm_open") вызывается для каждой отдельной команды, с которой удалось установить соединение. Это похоже на то, как будто у каждой команды есть поток.
Здесь создается UserLib, потому что он содержит контекст, специфичный для команд.

Также есть небольшой прием для обработки обратного вызова аутентификации GoogleApi. Я вернусь к этому. ☝️

Наконец, таймер для выполнения функции loop каждую минуту для проверки отправки событий и уведомлений. Подробнее об этом позже.

Взаимодействие с ботом

Botkit предоставляет методы для прослушивания определенных ключевых слов и реагирования на них:

controller.hears(["hello", "hi", "greetings"], ["direct_mention", "mention", "direct_message"],
  function(bot,message) {
    bot.reply(message, "Hello! :simple_smile:");
  }
);

Meekee реагирует на простое общение, но это не чат-бот, поэтому он обрабатывает только простые ключевые слова. Не было необходимости иметь бота со способностью обработки естественного языка, поскольку основные функции - это уведомления о событиях.

Meekee понимает start и stop как «включить / выключить уведомления».

Процесс установки

Когда новая команда устанавливает бота, Botkit берет на себя установку и подключение к Slack.

Затем Meekee выполняет дополнительный шаг: запрашивает у пользователей доступ к их Календарю:

Как я уже упоминал во вступлении, было немного сложно понять, как обрабатывать обратный вызов из Google Calendar API. Обычный процесс аутентификации будет проверять связь с конечной точкой, которую вы предоставили с кодом аутентификации.

Однако Meekee не «живет» в браузере. Настроена конечная точка для прослушивания этого кода (controller.webserver.get('/google/auth/callback') в controller.rtm(on_open)), поэтому код извлекается. Но поскольку контекст не используется совместно браузером и ботом, невозможно узнать, какому пользователю принадлежит код… Пользователя просят вставить его обратно в Slack. Затем авторизация завершается.

Это была часть, которую я пытался решить со многих сторон. В конце концов я согласился на этот неэлегантный способ обращения с вещами. Meekee изначально задумывался как внутренний инструмент, так что этого было достаточно.

Примечание о токенах Google Calendar API

Meekee один раз устанавливает соединение с Google Calendar. Было бы неприятно, если бы доступ приходилось предоставлять снова каждую неделю или если бы уведомления пропускались из-за истечения срока действия токена, верно? 😅
Вот как сделать токен «липким».

oauth2Client.generateAuthUrl({
  access_type: 'offline',
  approval_prompt: 'force',
  scope: [ 'https://www.googleapis.com/auth/calendar.readonly' ]
});

generateAuthUrl возвращает URL-адрес, который будет отправлен пользователю для аутентификации в Google API.

Параметры access_type: 'offline' и approval_prompt: 'force' используются для включения refresh_token. В этом режиме Gcal отправит новый токен по истечении срока действия предыдущего. ✨

При каждом запросе к календарю updateToken() подтверждает, что токен Google все еще действителен, или обновляет токен в базе данных, если он был обновлен.

loop

Боковое примечание: Heroku перезапускает экземпляр сервера каждые 24 часа, поэтому при первом запуске meekee проверяет часовые пояса пользователя, при необходимости обновляет. Люди в AQ много путешествуют, и им нравятся их уведомления в правильном часовом поясе!

Ядро Meekee - это цикл, который каждую минуту проверяет события каждого пользователя.

checkCalendar призван:

  • Запросите API Календаря Google для текущего пользователя,
  • Фильтровать события,
  • Форматировать уведомления,
  • Обновите базу данных (ведите подсчет отправленных уведомлений),
  • Обратный вызов в цикл и отправка уведомлений.
checkCalendar = function(user_object, callback){
  var notifications = [];
  var error = null;
  updateToken(user_object);
  var args = calendar.getEventArgs(oauth2Client);
  google.calendar('v3').events.list(args, function(err, res){
    if (err)
      error = calendar.handleQueryError(err, args);
    else
      notifications = calendar.filterEvents(args, res, user_object);
if (notifications.length){
      user_object.notifications += notifications.length;
      saveUser(user_object, function(){
        if (err)
            console.error(err);
        }, false);
      }
    }
    callback(user_object, notifications, error);
  });
}

Анатомия запроса к календарю Конечная точка событий:

  • auth содержит идентификацию для Gcal, такую ​​как токен пользователя, идентификатор клиента, секрет клиента, а также URL-адрес перенаправления для обратного вызова.
  • calendarId: primary Сейчас просматривается только главный календарь.
  • maxResults: 5 Предполагается, что одновременно происходит не более 5 событий.
  • singleEvents: true Включает повторяющиеся события регулярные события.
  • orderBy: startTime Событие, начинающееся первым, отображается первым в результатах.
  • timeZone: utc События обрабатываются по часовому поясу UTC. При форматировании уведомлений применяется часовой пояс пользователя.
  • _22 _, _ 23_ Выберите все события, которые начинаются через 3 минуты или происходят в настоящее время (включая события «Весь день»).
    События, которые фактически не начинаются в это время, будут отфильтрованы.

Если не произойдет ошибка en, этот запрос вернет набор событий.

Отфильтровать события и форматировать уведомления

Вызов tocalendar.filterEvents затем отфильтровывает события, которые соответствуют этим критериям и формату как уведомления:

  • События, которые фактически начинаются через 3-4 минуты.
  • События, на которые приглашены другие лица, кроме пользователя (и ответили «да» или «возможно»). Meekee не сообщает о событиях, в которых больше никого нет.

Отправлять уведомления!
Вернувшись в цикл, отправьте уведомление через обратный вызов.

Slack предоставляет множество опций для форматирования сообщений столбцами, изображениями и т. Д.

Meekee также имеет 3 контрольных точки, чтобы помочь каждому пользователю в процессе. Цикл также используется для их отправки.

2 часа: предоставил ли пользователь доступ к Календарю Google?
Если нет, напомним.

1 неделя: пользователю рекомендуется оставить отзыв в нашей учетной записи Twitter @aqbots и сообщить своим коллегам о боте.

4 недели: Meekee отправляет небольшое сообщение, чтобы поблагодарить их, сообщить, сколько уведомлений они получили на данный момент и кто использует бота в их команде.

Что я выучил

Я узнал, что «крошечный бот» может потребовать много работы, особенно если он чувствителен ко времени, находится между двумя API и имеет несколько пользователей и команд.

Выявление ошибки и устранение неполадок во времени и контексте может быть непросто из-за большого количества асинхронных процессов. Классическое ведение журнала здесь не очень помогает.
Я не реализовал полноценные тесты, потому что, как я уже сказал, это внутренний инструмент, и имитировать API-интерфейсы было бы довольно сложно.
Но это еще раз доказало, что написание тестов никогда не бывает пустой тратой времени. Это почти всегда сэкономит вам время позже. И это особенно когда сложно проверить код вручную.

Auth - это сложно. Несмотря на существующие стандарты, бывает сложно понять, как установить соединение и поддерживать его в течение долгого времени.

Небольшие проекты могут стать прекрасной возможностью внести свой вклад в Open Source.
Мне потребовались функции, которых не было в botkit-storage-mongo. Я закодировал их и сделал небольшой запрос на вытягивание, который позже был добавлен в исходный код. Это действительно должны быть большие изменения. Если вам это нужно, изменения могут быть внесены и кем-то другим.

Вот и все!

Спасибо, что прочитали этот пост. Надеюсь, вы нашли в этом какую-то ценность.

Вы можете найти meekee на meekee.io, @aqbots в твиттере и просмотреть источник на Github meekee-bot.

Вы можете написать мне в Twitter @oiorain.
У меня также есть тамблер, где я иногда публикую полезные советы и рекомендации: script-it-friday.tumblr.com