Делаем своего Slack бота на Go
Перевод статьи "Build your own slack bot in Go"
Боты для Slack это довольно веселая и простая в написании штука. В этой статье мы посмотрим, как написать одного из таких ботов.
Вот что сможет делать наш свеженаписанный бот:
Для создания и использования ботов вам нужен Slack аккаунт с высокими привилегиями(для включения ботов) и немного знаний по Go.
Чтобы добавить нового бота, вам нужно пройти по ссылке https://YOURORG.slack.com/services/new/bot
(вместо YOURORG нужно подставить ваш реальный домен), затем выбрать имя. Не забудьте сохранить токен для доступа к API, позже он нам очень пригодится.
После всех этих, манипуляций вы увидите имя бота в списке пользователей(в состоянии залогинен). По умолчанию бот будет добавлен в канал #general.
Теперь нужно скачать код с github. Мы будем использовать его для понимания основных принципов.
go get github.com/rapidloop/mybot
После выполнения команды, у вас появится бинарный файл в папке $GOPATH/bin
. Запускаем и смотрим, что получится:
$ mybot
usage: mybot slack-bot-token
$ mybot xxxx-99999999999-USE.THE.TOKEN.FROM.ABOVE
mybot ready, ^C exits
теперь mybot
готов показывать вам котировки акций, как показано на скриншоте выше. Когда наиграетесь с ботом, достаточно нажать комбинацию клавиш CTR+C для выхода из программы.
Теперь можем погружаться в глубины кода.
Код
Предупреждение: Код приложения mybot
используется только для иллюстрации возможностей, в нем нет всех проверок ошибок и многого другого. Не используйте его в реальных проектах!
Для начала, нам нужно использовать токен для запуска сессии Real Time Message API. Для этого используем rtm.start API. Как сказано в документации, нам нужно передать токен как параметр при вызове rtm.start
и в ответ мы получим целую кучу всякой всячины. Из этого всего нам нужна только пара вещей.
Пример того, как мы делаем вызов (файл slack.go
, метод slackStart
)
url := fmt.Sprintf("https://slack.com/api/rtm.start?token=%s", token)
resp, err := http.Get(url)
//...
body, err := ioutil.ReadAll(resp.Body)
//...
var respObj responseRtmStart
err = json.Unmarshal(body, &respObj)
В этом куске кода мы делаем HTTP GET запрос, читаем тело ответа и декодируем его как JSON в отдельный объект responseRtmStart
. Как было сказано выше, нам нужно всего несколько вещей. Метод Unmarshal
из пакета encoding/json
позволяет игнорировать ненужные поля, что позволяет определить структуру, состоящую только из необходимых полей:
type responseRtmStart struct {
Ok bool `json:"ok"`
Error string `json:"error"`
Url string `json:"url"`
Self responseSelf `json:"self"`
}
type responseSelf struct {
Id string `json:"id"`
}
Поле Url
это специальный Websocket URL к которому мы можем подключится для запуска сессии RTM. Также, нам нужен параметр Id
, который является уникальным идентификатором нашего бота. Он должен выглядеть примерно так: U1A2B3C4D. В будущем, мы будем использовать этот идентификатор для получения всех упоминаний.
Используем API для работы с сообщениями в реальном времени
Специальные Go библиотеки golang.org/x
включают в себя пакет для работы с веб-сокетами: golang.org/x/net/websocket
. В этом пакете есть JSON кодек, который предоставляет обертку для отправки и получения JSON объектов через веб-сокеты.
Запускаем websocket соединение с использованием этого пакета (файл slack.go, метод slackConnect):
ws, err := websocket.Dial(wsurl, "", "https://api.slack.com/")
Через это соединение, Slack сообщает нам [почти обовсем]https://api.slack.com/rtm), что происходит в чатах. Эти нотификации включают в себя и сообщения, которые пользователи отправляют в каналы, и сообщения, которые пользователи отправляют лично нашему боту. Личные сообщения нам особенно интересны.
Каждая нотификация - это JSON объект, котрый содержит поле type
. Нам нужны нотификации, у которых в этом поле указан тип message
. Как и раньше, мы можем получить значения из нужных полей JSON объекта с помощью правильной структуры (файл slack.go):
type Message struct {
Id uint64 `json:"id"`
Type string `json:"type"`
Channel string `json:"channel"`
Text string `json:"text"`
}
Для работы с веб-сокет соединениями, нам нужен вот такой код(файл slack.go):
func getMessage(ws *websocket.Conn) (m Message, err error) {
err = websocket.JSON.Receive(ws, &m)
return
}
Для отправки сообщений в канал нам нужно записать JSON объект обратно в веб-сокет. Обратите внимание, нет никакой возможности отвечать на сообщения. Все что мы можем - это отправлять сообщения в канал. Для каждого сообщения нам нужен уникальный идентификатор. Для этого будем использовать атомарный инкремент счетчика.
var counter uint64
func postMessage(ws *websocket.Conn, m Message) error {
m.Id = atomic.AddUint64(&counter, 1)
return websocket.JSON.Send(ws, m)
}
Главный цикл
Давайте теперь соберем все вместе. Напишем главный цикл, который будет выглядеть как-то так (файл main.go):
ws, id := slackConnect(token)
for {
msg, err := getMessage(ws)
// ...
// обрабатываем сообщение и отвечаем при необходимости
// ...
postMessage(ws, reply)
}
Мы читаем все сообщения, но нам нужны только те, в которых упомянули нашего бота. Slack хранить все упоминания пользователя как строки вида <@U1A2B3C4D>(U1A2B3C4D это идентификатор пользователя, которого упомянули) в тексте сообщения. Таким образом, сообщение вида "@mybot: stock goog" превратиться в "<@U1A2B3C4D>: stock goog". Мы можем использовать эту информацию для распознавания упоминаний нашего бота.
if m.Type == "message" && strings.HasPrefix(m.Text, "<@"+id+">") {
И пример обработки самого сообщения:
parts := strings.Fields(m.Text)
if len(parts) == 3 && parts[1] == "stock" {
// получаем содержимое второй части сообщения через parts[2]
} else {
m.Text = fmt.Sprintf("sorry, i didn't get that\n")
postMessage(ws, m)
}
Для обработки сообщения и формирования ответа используем go-рутину:
go func(m Message) {
m.Text = getQuote(parts[2])
postMessage(ws, m)
}(m)
Метод getQuote
получает данные котировок по указанному тиккеру используя Yahoo API. Конечно же, вы можете заменить этот функционал.
Вот несколько вещей, которые может делать бот и неописанных в этой статье.
Удачи вам в создании новых Slack ботов. Вот еще чутка ссылок, которыебудут вам интересны: