Пишем простого slack-бота на Java

—

от автора

Привет! Я — Саша Казанцев, разработчик в hh.ru. В статье я расскажу, как сделать простого бота в Slack на java и немного о других вариантах использования slack api.

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

Структура будет интересна тем, кому лень читать лишнее: в самом начале будет инструкция по созданию бота, а после — лирика про наших.

Slack тебе в руки!
Slack тебе в руки!

Кручу ручку, пишу бота

Без лишних слов и прелюдий перейдем сразу к делу. Сначала создадим приложение в “личном кабинете”. Больше деталей в официальном туториале.

Выбираем From scratch. Вариант с манифестом пока выглядит недоработанным.

Дальше нужно заполнить имя и workspace.

Из  меню OAuth & Permissions забираем Bot User OAuth Token вида xoxb-YOUR-TOKEN. Он пригодится нам позже.

Теперь добавим права нашему боту. В том же меню ниже.

Slack экспериментирует с интерфейсом, поэтому вид может вскоре измениться.

Общий алгоритм работы с правами

Зайдите в документацию требуемого метода. 

Вам могут понадобиться не все разрешения метода. Лучше прочитать про каждое отдельно. Для нашего примера будет достаточно chat:write. Все разрешения.

Запускаем приложение

pom.xml
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"          xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">     <modelVersion>4.0.0</modelVersion>     <parent>         <groupId>org.springframework.boot</groupId>         <artifactId>spring-boot-starter-parent</artifactId>         <version>2.6.0</version>     </parent>     <groupId>ru.hh.example</groupId>     <artifactId>slack-example</artifactId>     <version>0.0.1-SNAPSHOT</version>     <name>slack-example</name>      <properties>         <java.version>11</java.version>         <slack.bolt.version>1.13.0</slack.bolt.version>     </properties>      <dependencies>         <dependency>             <groupId>org.springframework.boot</groupId>             <artifactId>spring-boot-starter</artifactId>         </dependency>         <dependency>             <groupId>org.springframework.boot</groupId>             <artifactId>spring-boot-starter-web</artifactId>         </dependency>         <dependency>             <groupId>org.springframework.boot</groupId>             <artifactId>spring-boot-starter-test</artifactId>             <scope>test</scope>         </dependency>          <!--        slack base and web-->         <dependency>             <groupId>com.slack.api</groupId>             <artifactId>bolt</artifactId>             <version>${slack.bolt.version}</version>         </dependency>         <dependency>             <groupId>com.slack.api</groupId>             <artifactId>bolt-servlet</artifactId>             <version>${slack.bolt.version}</version>         </dependency>          <!--        socket mode-->         <dependency>             <groupId>com.slack.api</groupId>             <artifactId>bolt-socket-mode</artifactId>             <version>${slack.bolt.version}</version>         </dependency>         <dependency>             <groupId>org.glassfish.tyrus.bundles</groupId>             <artifactId>tyrus-standalone-client</artifactId>             <version>1.9</version>         </dependency>         <dependency>             <groupId>javax.websocket</groupId>             <artifactId>javax.websocket-api</artifactId>             <version>1.1</version>         </dependency>     </dependencies>      <build>         <plugins>             <plugin>                 <groupId>org.springframework.boot</groupId>                 <artifactId>spring-boot-maven-plugin</artifactId>             </plugin>         </plugins>     </build>  </project>
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<String, String> 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.java
package 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.

Больше примеров здесь и здесь.

P.S.: Если не приходят определенные ивенты, возможно вы не подписались на них в разделе Event Subscriptions (я потратил какое то время, чтобы это понять).

В заключение 

Наши слак боты позволяют автоматизировать работу, упростить выход новых сотрудников, добавить ламповости в каналы.

Самый популярный бот — для работы с тестовой инфраструктурой. Он может отдавать текущий статус стендов и позволяет их “бронировать”. А еще рассылает уведомления, если что-то пошло не так: с тестами, релизами etc.

Есть более “камерные” боты, например троттлер уведомлений или планинг покер. Всего десятка полтора.

Программисты же любят создавать свои ванильные велосипеды.

И напоследок мой личный рейтинг ботов:

  • Polly — позволяет создавать красивые опросы;

  • Zoom и Teams боты  делают жизнь чуть проще;

  • Disco — имитация корпоративного общения.


ссылка на оригинал статьи https://habr.com/ru/articles/595837/


Комментарии

Добавить комментарий

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