Статьи

Развлечения с роботами, Литой и HipChat

Лита

Могу поспорить, что вы смотрели какой-то научно-фантастический фильм, где герой загружает суперский ПК и вместо того, чтобы выполнять все задачи вручную с помощью клавиатуры и мыши, просто использует голосовые команды (во время питья кофе или чистки супергероя). костюм). Это выглядит действительно круто. Разве мы не можем создать что-то похожее, но более простое, с Ruby?

Например, есть какой-нибудь робот, который будет реагировать на наши команды, которые мы (на данный момент :)) вводим с клавиатуры. Также было бы неплохо добавить новые команды для расширения функциональности нашего робота. Это можно сделать довольно легко!

Встречайте Литу , милого компаньона-робота в чате. Он может подключаться к любой службе чата и легко расширяться с помощью пользовательских плагинов.

В этой статье я собираюсь показать вам, как настроить Lita, подключить его к HipChat (популярной чат-платформе) и усовершенствовать нашего чат-бота новыми командами для взаимодействия с API примера приложения Rails.

Исходный код для примера приложения todo доступен по адресу github.com/bodrovis/SitePointReminder .

Исходный код для бота доступен на github.com/bodrovis/SitePointBot .

Особенности Литы

Ладно, какие вкусности может нам предложить Лита? Как я уже сказал, он может работать с любым сервисом чата. В настоящее время IRC , HipChat ,
Поддерживаются Campfire , Slack , Twitter и Japaneese Idobata , но вы можете написать свой собственный адаптер для подключения к любой платформе, которая вам нужна.

Уже существует множество плагинов, которые предоставляют различные функциональные возможности (например, получение информации whois, запросы к Google, взаимодействие с JIRA и многие другие), и вы можете легко написать свой собственный.

С Lita вы можете автоматизировать утомительные задачи и выполнять их, просто набрав «Lita, пожалуйста, сделайте это» в чате. Конечно, проект с открытым исходным кодом и написан на Ruby, лучшем языке программирования в мире :).

Я надеюсь, что вы готовы начать возиться с Литой прямо сейчас!

Соединение с HipChat

Для этой демонстрации я решил использовать HipChat, поэтому вам нужно будет посетить hipchat.com и создать две учетные записи: одну для вас и одну для вашего верного робота. Подтвердите и отложите их пока.

Затем установите саму Литу , запустив:

$ gem install lita 

Если вы работаете в Windows, то появятся ошибки, связанные с установкой Puma , так как этот веб-сервер является одной из зависимостей Lita. Чтобы преодолеть эту проблему, обратитесь к этому руководству . Короче говоря, вам нужно скачать и извлечь этот пакет OpenSSL . Затем скопируйте libeay32.dll и ssleay32.dll в каталог ruby/bin и выполните следующую команду:

 $ gem install puma -- --with-opt-dir=c:\openssl 

Замена c:\openssl на путь, по которому был извлечен OpenSSL. Когда вы закончите, новая команда lita должна быть доступна из вашей командной строки.

Перед созданием нового робота также необходимо установить СУБД Redis, так как Lita использует ее в качестве хранилища данных. Посетите страницу redis.io/download, чтобы выбрать подходящую версию (также доступна неофициальная версия для Windows). После запуска Redis вы готовы продолжить!

Введите следующую команду, чтобы создать новый проект Lita (назовем его Bot):

 $ lita new bot 

Будет создан каталог бота и пара файлов. Прежде всего отредактируйте Gemfile , раскомментировав следующую строку:

Gemfile

 [...] # gem "lita-hipchat" [...] 

Следующий прогон

 $ bundle install 

Адаптер HipChat теперь на месте. Затем откройте файл lita_config.rb , который содержит все необходимые настройки. Измените эти параметры:

  • config.robot.name (который вы config.robot.name при создании аккаунта бота в HipChat)
  • config.robot.adapter (установите его в :hipchat )
  • config.redis (Передайте здесь хеш, например: {host: "127.0.0.1", port: 6379} )
  • config.adapters.hipchat.jid (Чтобы найти его, посетите hipchat.com , откройте настройки учетной записи и перейдите на страницу информации о XMPP / Jabber. Поле «Jabber ID» — это то, что вам нужно)
  • config.adapters.hipchat.password (пароль бота)
  • config.adapters.hipchat.debug (Установите значение true для просмотра отладочной информации в консоли. Это полезно, если ваш бот не может подключиться к чат-платформе, например)
  • config.adapters.hipchat.rooms (Имя комнаты чата или :all )

Большой! После изменения этих настроек вы готовы к подключению. Тип

 $ lita 

в вашей консоли (из каталога, где находятся файлы бота), чтобы загрузить приложение. Затем перейдите на hipchat.com и войдите в свой личный кабинет. Присоединяйтесь к чату, ваш бот уже будет там в ожидании ваших заказов! Конечно, на данный момент это не очень полезно. Вы можете проверить версию Литы, набрав:

 @lita info # or lita, info # or lita: info # or lita info 

Не забудьте заменить lita вашего бота. Кстати, если вы не хотите всегда указывать имя своего бота при config.adapters.shell.private_chat команд, установите для параметра config.adapters.shell.private_chat значение true . Это обрабатывает все сообщения как команды, поэтому вам не нужно добавлять к имени Lita префикс.

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

Интегрирующий плагин Lita

Ваш бот может реагировать на очень ограниченный диапазон команд (например, показ версии или присоединение к комнате — узнайте больше о них здесь ). К счастью, для Lita уже доступно множество плагинов . Они делятся на три типа:

  • Адаптеры используются для подключения к определенной службе чата (например, lita-hipchat ).
  • Обработчики добавляют новую функциональность, с которой пользователи будут взаимодействовать во время выполнения. В частности, они могут работать с чатом и HTTP- маршрутами. Мы обсудим маршруты чата позже.
  • Расширения предоставляют новые возможности для разработки других плагинов и расширения базовой платформы Lita. Они используются в сложных сценариях, поэтому во многих случаях вам не нужно их использовать.

Давайте попробуем интегрировать пару обработчиков в наш проект. Например, существует обработчик lita-google-images, который запрашивает изображения Google с указанными ключевыми словами и в результате возвращает одно из релевантных изображений. Этот плагин действительно легко интегрировать. Просто добавьте его в свой Gemfile:

Gemfile

 [...] gem "lita-google-images" [...] 

бегать

 $ bundle install 

и перезапустите свой бот. Теперь вы можете выдавать такие команды:

 @lita image cat @lita img cat 

Здорово!

Еще одно потенциально полезное и все же очень простое в интеграции расширение — это lita-whois, который ищет записи WHOIS (с помощью клиента Whois ). Еще раз просто добавьте эту строку в ваш Gemfile:

Gemfile

 [...] gem "lita-whois" [...] 

бегать

 $ bundle install 

и перезапустите Литу. Теперь вы можете выдавать такие команды:

 @lita whois example.com @lita whois 8.8.8.8 

просмотреть полную информацию.

Если вы хотите записать всю активность в чате где-нибудь на вашем локальном сервере (чтобы узнать, что ваши коллеги говорят о вас, когда вы не в сети :)), то возьмите lita-logger . Для этого требуется базовая настройка через файл lita_config.rb :

lita_config.rb

 [...] config.handlers.logger.log_file = "chat.log" # replace this with a path to your log file config.handlers.logger.enable_http_log = true [...] 

Не забудь бежать

 $ bundle install 

и перезапустите ваше приложение. Теперь все действия в чате будут записываться в указанный файл. Большой Брат (или Сестра?) Следит за тобой, а?

В качестве домашней работы интегрируйте расширение lita-chuck_norris, чтобы прочитать несколько анекдотов об этом крутом парне, когда вам скучно (их можно получить, выполнив запрос GET по http://api.icndb.com/jokes/random.json ). Кстати, есть много других подобных плагинов, созданных просто для удовольствия, так что загляните на страницу плагинов . Может быть, вы будете вдохновлены, чтобы создать что-то самостоятельно!

Создание (еще одного) приложения Todo

Да ладно, не смотри на меня так. Возможно, вы устали от приложений Todo, но в этой демонстрации он будет выполнять исключительно вспомогательную роль. Нам нужно приложение для нашего бота, чтобы взаимодействовать с ним через API.

Это приложение будет поддерживать следующее:

  • Создать задачу
  • Редактировать задачу
  • Список задач
  • Отметить задачу как выполненную (что по сути означает уничтожить ее)

Все эти действия будут доступны через простой API, который будет использовать наш бот. Никакой аутентификации, авторизации или тому подобного.

Это приложение может быть написано на любом языке / фреймворке, но я собираюсь придерживаться Rails 4.1. Создайте новое приложение под названием «Напоминание» без набора тестов по умолчанию:

 $ rails new reminder -T 

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

 [...] gem 'bootstrap-sass' [...] 

Также убедитесь, что gem 'jbuilder' , потому что он нам понадобится для возврата результатов через API.

Тогда беги

 $ bundle install 

Перетащите эти строки в файл application.css.scss

application.css.scss

 [...] @import 'bootstrap'; @import 'bootstrap/theme'; [...] 

и настройте макет, чтобы воспользоваться преимуществами стилей Bootstrap, если вы хотите:

макеты / application.html.erb

 [...] <div class="container"> <% flash.each do |key, value| %> <div class="alert alert-<%= key %>"> <%= value %> </div> <% end %> <div class="page-header"> <h1><%= yield :page_header %></h1> </div> <%= yield %> </div> [...] 

Теперь давайте сгенерируем простую модель Todo и применим соответствующую миграцию:

 $ rails g model Todo title $ rake db:migrate 

Добавьте несколько необходимых маршрутов:

конфиг / routes.rb

 [...] resources :todos, only: [:new, :create, :index, :destroy] root to: 'todos#index' [...] 

На контроллере. Давайте начнем с действия index :

todos_controller.rb

 class TodosController < ApplicationController def index respond_to do |format| @todos = Todo.order('created_at DESC') format.html format.json end end end 

Мы решили создать простой API, поэтому используем метод respond_to чтобы предоставить пользователю необходимый формат. Соответствующие представления также должны быть созданы. Во-первых, формат HTML:

Todos / index.html.erb

 <% content_for(:page_header) { "Your todos" } %> <%= link_to 'Add todo', new_todo_path, class: 'btn btn-primary' %> <div class="panel"> <div class="panel-body"> <ul> <% @todos.each do |todo| %> <li><%= todo.title %> <%= link_to 'Done', todo_path(todo), method: :delete %></li> <% end %> </ul> </div> </div> 

И формат JSON:

Todos / index.json.jbuilder

 json.array! @todos.each do |todo| json.id todo.id json.title todo.title end 

Мы рендерим массив задач; у каждого есть id и title . Не забудьте, что для корректной работы вам понадобится подключить jBuilder в проекте.

ОК, теперь new и create действия:

todos_controller.rb

 class TodosController < ApplicationController [...] def new @todo = Todo.new end def create @todo = Todo.new(todo_params) respond_to do |format| if @todo.save format.html do flash[:success] = 'Todo created!' redirect_to root_path end format.json { head :no_content } else format.html { render :new } format.json { render json: @todo.errors.full_messages, status: :unprocessable_entity } end end end [...] private def todo_params params.require(:todo).permit(:title) end end 

Еще раз, мы используем respond_to . Обратите внимание, что new действие не нуждается в соответствующем представлении .json поскольку ответ на него отображается прямо в контроллере.

Наконец, destroy действие:

todos_controller.rb

 def destroy todo = Todo.find_by_id(params[:id]) respond_to do |format| if todo && todo.destroy format.html do flash[:success] = 'Todo marked as done!' redirect_to root_path end format.json { head :no_content } else format.html do flash[:warning] = 'There was an error.' redirect_to root_path end format.json { head 500 } end end 

Еще одна вещь: настройте ApplicationController чтобы исключение не возникало, если токен CSRF не предоставлен (например, при создании записи). В реальном приложении вы бы настроили какую-то аутентификацию, чтобы пользователи не могли делать то, что они не должны делать, но сегодня мы проигрываем:

application_controller.rb

 [...] protect_from_forgery [...] 

Я только что удалил with: exception здесь, так что :null_session используется (это опция по умолчанию). Кстати, над этой строкой есть подсказка:

 # Prevent CSRF attacks by raising an exception. # For APIs, you may want to use :null_session instead. 

чтобы ты не забыл об этом. Подробнее об этом читайте здесь .

Brilliant! Еще одно приложение todo создано, и мы можем научить нашего робота работать с ним.

Создание собственного обработчика Lita

Помните, что есть три типа плагинов для Lita: адаптеры, обработчики и расширения. Для добавления пользовательских функций создайте расширение.

Если вы планируете поделиться своим новым блестящим плагином с миром, используйте одну из следующих трех команд:

  • lita adapter NAME
  • lita handler NAME
  • lita extension NAME

Это установит для вас базовую файловую структуру и lita- префикс имени плагина к lita- (это соглашение, введенное автором Lita). Позже вы можете упаковать свой код как драгоценный камень, опубликовать его в RubyGems и начать получать отклики от пользователей.

Тем не менее, наш обработчик предназначен только для внутреннего использования, поэтому давайте сделаем все просто. Создайте новый файл lita-Remder.rb в каталоге ботов, который в настоящее время содержит файлы lita_config.rb и Gemfile .

Измените файл конфигурации, добавив следующую строку вверху:

lita_config.rb

 require './lita-reminder' [...] 

Теперь Лита знает о нашем новом, но пустом обработчике. Вставьте следующий код в lita-Remder.rb :

Лита-reminder.rb

 module Lita module Handlers class Reminder < Handler end end Lita.register_handler(Reminder) end 

Обработчик теперь будет зарегистрирован при загрузке робота. Настало время конкретизировать это. Давайте реализуем три действия:

  • добавить todo
  • уничтожить (отметить как выполненное) todo
  • список задач

Маршрут чата потребуется, чтобы сообщить нашему роботу, когда выполнять какое действие. По сути, маршрут чата является регулярным выражением. Если Лита находит совпадение в одном из сообщений, она выполняет соответствующую операцию. Давайте создадим один:

Лита-reminder.rb

 [...] class Reminder < Handler route(/^remind plz$/, :index, command: true, help: { "remind plz" => "Remind about your todos." }) end [...] 

Когда Лита видит сообщение, которое начинается с «remind plz», она вызывает метод index , который будет создан в ближайшее время. Команда command: true здесь означает, что этот маршрут будет запускаться только в том случае, если в сообщении упоминается бот (сообщение является личным или имеет префикс с именем бота). help содержит пример вызова маршрута и того, что он делает. В нашем случае введите

 @lita help remind plz 

чтобы увидеть это сообщение помощи. Обратите внимание, что ответ с помощью всегда будет отправляться как личное сообщение — не уверен, является ли это ошибкой или ожидаемым поведением.

Теперь мы можем реализовать метод index и некоторые служебные методы:

Лита-reminder.rb

 [...] def index(response) todos = parse get("http://127.0.0.1:3000/todos.json") if todos.any? response.reply("Your todos:") todos.each do |todo| response.reply("# #{todo['id']}: #{todo['title']}") end else response.reply('You have done all the todos! Good job!') end end private def get(url) Net::HTTP.get make_uri(url) end def parse(obj) MultiJson.load(obj) end def make_uri(url) URI(url) end [...] 

Эти служебные методы просты: get используется для отправки запроса GET на предоставленный URI, анализирует JSON-ответ ( MultiJson подключен в ядре Lita), а make_uri создает URI из предоставленного URL-адреса.

Какой параметр response передается в метод index ? Это экземпляр класса Lita::Response и является основным интерфейсом для проверки деталей сообщения и ответа на него. У него есть пара полезных методов. Здесь мы используем только один — reply , который отвечает обратно предоставленной строкой.

По сути, метод index отправляет запрос GET по http://127.0.0.1:3000/todos.json , анализирует ответ и либо отображает список задач в формате ID - title , либо сообщает, что задачи не найдены.

Что если в будущем наше приложение todos будет перенесено на рабочий сервер? Нам нужно изменить ссылку на него везде, где он используется. Не могли бы мы просто создать какую-то настройку, чтобы пользователь мог предоставить там ссылку на приложение todo? Похоже, мы можем!

Лита-reminder.rb

 [...] config :server [...] def index(response) todos = parse get("#{config.server}/todos.json") [...] end [...] 

Здесь config :server означает, что server является опцией, доступной для пользователей. Затем к нему можно получить доступ с помощью config.server (кстати, все переменные конфигурации заморожены во время выполнения). Укажите этот новый параметр:

lita_config.rb

 [...] Lita.configure do |config| [...] config.handlers.reminder.server = 'http://127.0.0.1:3000' end 

Ницца! Теперь добавьте маршрут для добавления новой задачи:

Лита-reminder.rb

 [...] module Lita module Handlers class Reminder < Handler route(/^todo\s+(.+)$/, :create, command: true, help: { "todo TODO" => "Adds new todo to the list." }) def create(response) todo = response.match_data[1] result = post "#{config.server}/todos.json", 'todo[title]' => todo if result.code.to_i.success? response.reply("#{todo} was added.") else response.reply("I've encountered the following errors while saving your todo: #{parse(result.body)}") end end private def post(url, data = {}) Net::HTTP.post_form(make_uri(url), data) end end Lita.register_handler(Reminder) end end class Numeric def success? self > 199 && self < 300 end end 

В маршруте я использую группу захвата, чтобы получить имя новой задачи. Метод create использует response.match_data[1] для извлечения этой группы захвата из данных о совпадении ( match_data — еще один метод, доступный из response ). Остальная часть кода довольно проста: мы выдаем POST-запрос, предоставляющий заголовок задачи.

Наконец, маршрут уничтожения:

Лита-reminder.rb

 [...] route(/^done todo\s+(\d+)$/, :destroy, command: true, help: { "done todo TODO_ID" => "Marks todo with the specified number as done." }) [...] def destroy(response) todo_id = response.match_data[1] result = delete "#{config.server}/todos/#{todo_id}.json" if result.code.to_i.success? response.reply("Todo # #{todo_id} was marked as done.") else response.reply("I've encountered an error while marking your todo as done.") end end private def delete(url) uri = make_uri(url) http = Net::HTTP.new(uri.host, uri.port) request = Net::HTTP::Delete.new(uri.path) http.request(request) end [...] 

Должен быть указан идентификатор уничтожаемой задачи. Позже, todo выбирается с использованием конструкции match_data[1] . Затем отправляется запрос DELETE.

Кстати, если вы не хотите, чтобы ваши задачи были полностью уничтожены, тогда может быть реализовано действие Act_as_paranoid или подобное решение.

Загрузите своего робота и попробуйте управлять своими задачами. Не было бы здорово, если бы Лита тоже приготовила для тебя ужин?

Вывод

В этой статье мы познакомились с Lita, расширяемым компаньоном-роботом. Мы интегрировали его с HitChat, добавили несколько плагинов и даже написали собственный дескриптор для взаимодействия с API-интерфейсом приложения todo. Не стесняйтесь экспериментировать с этим кодом или создавать что-то более сложное (не забудьте поделиться ссылкой в ​​комментариях!). Также, пожалуйста, поделитесь своим опытом, если вы когда-либо использовали Lita в производстве для выполнения каких-то реальных задач — это было бы интересно!