Пишем простого slack-бота на Java
Привет! Я — Саша Казанцев, разработчик в hh.ru. В статье я расскажу, как сделать простого бота в Slack на java и немного о других вариантах использования slack api.
Слак обладает обширной и всеобъемлющей документацией и туториалами, и чтобы написать эталонного бота, лучше прочитать вообще все. Но у нас лапки, поэтому запилим по-простому.
Структура будет интересна тем, кому лень читать лишнее: в самом начале будет инструкция по созданию бота, а после — лирика про наших.
Slack тебе в руки!
Кручу ручку, пишу бота
Без лишних слов и прелюдий перейдем сразу к делу. Сначала создадим приложение в «личном кабинете». Больше деталей в официальном туториале.
Выбираем From scratch. Вариант с манифестом пока выглядит недоработанным.
Дальше нужно заполнить имя и workspace.
Из меню OAuth & Permissions забираем Bot User OAuth Token вида xoxb-YOUR-TOKEN. Он пригодится нам позже.
Теперь добавим права нашему боту. В том же меню ниже.
slack экспериментирует с интерфейсом, поэтому вид может вскоре измениться.
Общий алгоритм работы с правами
Зайдите в документацию требуемого метода.
Вам могут понадобиться не все разрешения метода. Лучше прочитать про каждое отдельно. Для нашего примера будет достаточно chat: write. Все разрешения.
Запускаем приложение
pom.xml
4.0.0
org.springframework.boot
spring-boot-starter-parent
2.6.0
ru.hh.example
slack-example
0.0.1-SNAPSHOT
slack-example
11
1.13.0
org.springframework.boot
spring-boot-starter
org.springframework.boot
spring-boot-starter-web
org.springframework.boot
spring-boot-starter-test
test
com.slack.api
bolt
${slack.bolt.version}
com.slack.api
bolt-servlet
${slack.bolt.version}
com.slack.api
bolt-socket-mode
${slack.bolt.version}
org.glassfish.tyrus.bundles
tyrus-standalone-client
1.9
javax.websocket
javax.websocket-api
1.1
org.springframework.boot
spring-boot-maven-plugin
SlackController.java
package ru.hh.example.slack;
import com.slack.api.Slack;
import com.slack.api.methods.MethodsClient;
import com.slack.api.methods.SlackApiException;
import com.slack.api.methods.response.chat.ChatPostMessageResponse;
import com.slack.api.methods.response.conversations.ConversationsListResponse;
import com.slack.api.model.Conversation;
import java.io.IOException;
import java.util.Map;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import static org.springframework.http.MediaType.APPLICATION_FORM_URLENCODED_VALUE;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class SlackController {
private static final Logger LOGGER = LoggerFactory.getLogger(SlackController.class);
@PostMapping(path = "/", consumes = {APPLICATION_FORM_URLENCODED_VALUE})
public String slackCommand(@RequestParam Map slackRequest) {
LOGGER.info("slackRequest: {}", slackRequest);
return "hello " + slackRequest.get("user_name");
}
@PostMapping(path = "/send-to-chat")
public void sendMessage() throws SlackApiException, IOException {
// https://api.slack.com/authentication/oauth-v2
String token = "xoxb-YOUR-TOKEN";
String channelName = "YOUR-CHANNEL-NAME";
String message = "hello";
var client = Slack.getInstance().methods(token);
var channelId = findChannel(client, channelName);
ChatPostMessageResponse chatPostMessageResponse = postMessage(client, channelId, message);
LOGGER.info("chatPostMessageResponse.isOk() : {}", chatPostMessageResponse.isOk());
}
// https://api.slack.com/messaging/retrieving#finding_conversation
private String findChannel(MethodsClient client, String channelName) throws SlackApiException, IOException {
String nextCursor = null;
do {
var result = getConversationsList(client, nextCursor);
nextCursor = result.getResponseMetadata().getNextCursor();
for (Conversation channel : result.getChannels()) {
if (channel.getName().equalsIgnoreCase(channelName)) {
return channel.getId();
}
}
} while (nextCursor != null);
throw new IllegalStateException();
}
private ConversationsListResponse getConversationsList(MethodsClient client, String nextCursor) throws SlackApiException, IOException {
return client.conversationsList(r -> r.cursor(nextCursor));
}
// https://api.slack.com/messaging/sending#publishing
private ChatPostMessageResponse postMessage(MethodsClient client, String conversationId, String message) throws SlackApiException, IOException {
return client.chatPostMessage(r -> r.channel(conversationId).text(message));
}
}
Нужно прописать token (Bot User OAuth Token) и имя тестового канала (channelName)
Для того, чтобы Слак смог достучаться до вашего сервиса, можно запустить его на хостинге или для простоты поднять локально ngrok.
./ngrok $PORT
Spring boot по умолчанию поднимается на 8080.
Зачем сервису «торчать наружу»? В web api Слака предполагается, что он отправляет запрос в ваше приложение, когда получает команду (действие) от пользователя.
Добавляем команду в »лк» слака
В меню Slash Commands. Нужно прописать имя команды и url сервиса. От вашего сервера или ngrok.
Доступ к боту можно получить через стандартного Slackbot, либо добавив бота к себе в приложения. Если работать через Slackbot , то команда должна быть уникальной в пространстве вашей компании. Описание команд тут.
Последний штрих в личном кабинете — Install App и Reinstall to Workspace.
Проверяем как работает бот:
Метод sendMessage
Можно вызвать локально. Он показывает, как можно работать без действий юзера. Например, для отправки сообщений из системы мониторинга.
curl -X POST http://localhost:8080/send-to-chat
Получили сообщение в канале
Что еще можно запилить
Slack предоставляет несколько api:
Webhook — самый простой вариант. Хорош тем, что ваше приложение может ничего не знать про slack и просто дергать его по http. Мы используем его для отправки алертов из okmeter и из самописных скриптов деплоймента сервисов;
Conversations api имеет более широкие возможности, но требует больше усилий для входа;
Events API необходим для работы с командами и другими событиями из Cлака (например вход в канал, отправка определенного текста). Есть два способа взаимодействия — web, как был использован в примере и socket. Пожалуй, в следующий раз я бы использовал socket, поскольку он не требует выставления вашего сервиса наружу.
Не затронул большой раздел про оформление сообщений от бота. Почитать здесь
И еще эксперимент с socket mode
В моем боте он пока не нужен, но было интересно разобраться
В меню Socket Mode
Выставить enable
Алиас для токена
Забрать токен (нужно будет подложить приложению)
Теперь нужно подписаться на ивенты. Для моего примера нужно событие app_mention
В меню Event Subscriptions
SocketExample.javapackage ru.hh.example.slack;
import com.slack.api.bolt.App;
import com.slack.api.bolt.AppConfig;
import com.slack.api.bolt.socket_mode.SocketModeApp;
import com.slack.api.model.event.AppMentionEvent;
public class SocketExample {
public static void main(String[] args) throws Exception {
String botToken = "xoxb-YOUR-BOT-TOKEN";
String appToken = "xapp-APPTOKEN";
AppConfig appConfig = AppConfig.builder().singleTeamBotToken(botToken).build();
App app = new App(appConfig);
app.event(AppMentionEvent.class, (req, ctx) -> {
System.out.println("HI! I received event!" + req.getEvent());
ctx.say("I was mentioned by user with id " + req.getEvent().getUser());
return ctx.ack();
});
SocketModeApp socketModeApp = new SocketModeApp(appToken, app);
// #start() method establishes a new WebSocket connection and then blocks the current thread.
// If you do not want to block this thread, use #startAsync() instead.
socketModeApp.start();
}
}
Нужно поменять две переменные
xoxb-YOUR-BOT-TOKEN — токен из первого раздела
xapp-APPTOKEN — получили несколькими строками выше
Можно проверять
Схема взаимодействия через socket api
Из неочевидных особенностей — через сокет нельзя совершать любые действия. Только отвечать на событие. А все остальное нужно делать через web api
Больше примеров здесь и здесь
PS: Если не приходят определенные ивенты, возможно вы не подписались на них в разделе Event Subscriptions(я потратил какое то время, чтобы это понять))
В заключение
Наши слак боты позволяют автоматизировать работу, упростить выход новых сотрудников, добавить ламповости в каналы.
Самый популярный бот — для работы с тестовой инфраструктурой. Он может отдавать текущий статус стендов и позволяет их «бронировать». А еще рассылает уведомления, если что-то пошло не так: с тестами, релизами etc.
Есть более «камерные» боты, например троттлер уведомлений или планинг покер. Все десятка полтора.
Программисты же любят сделать свои ваниальные велосипеды)
И напоследок мой личный рейтинг ботов:
Polly — позволяет создавать красивые опросы
Zoom и Teams боты делают жизнь чуть прощеx
Disco — имитация корпоративного общения