Пишем Telegram Bot для оповещения о коммите в git репозитарий на базе Gitea и разворачиваем его в Google Cloud Platform

от автора

Здравствуйте как и обещал в продолжение моей статьи о Автоматической публикации приложения в Google Play , рассмотрю в деталях процесс написания Telegram Bot`a для оповещения команды тестировщиков о выпуске новой версии.

Регистрация Bota в Telegram и получение ID

Просто напишите пользователю @BotFather и следуйте его инструкциям.

Выполните последовательно следующий команды

/start /newbot bot_name
В итоге вы должны получить сообщение

Из этого сообщение нам понадобятся собственно

  • t.me/bot_name — Имя бота по которому мы будем добавлять его в каналы или писать в ЛС

  • token — это наш ключ для доступа к API

Подготовка проекта и подключение необходимых библиотек

Наш бот будет написан на Java и будет представлять из себя Spring Boot Web приложение, в качестве системы сборки будет использоваться maven

1) Создайте обычный Spring Boot проект, проще всего это сделать через встроенный конфигуратор в IntelliJ IDEA , либо используя Spring Initializr.

Выберите те зависимости которые посчитаете нужными, но для начала нам подойдёт минимальный набор

pom.xml
    <parent>         <groupId>org.springframework.boot</groupId>         <artifactId>spring-boot-starter-parent</artifactId>         <version>2.1.6.RELEASE</version>         <relativePath/> <!-- lookup parent from repository -->     </parent>      <properties>         <java.version>1.8</java.version>     </properties>      <dependencies>         <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>         <dependency>             <groupId>org.telegram</groupId>             <artifactId>telegrambots</artifactId>             <version>5.0.0</version>         </dependency>         <dependency>             <groupId>com.google.code.gson</groupId>             <artifactId>gson</artifactId>             <version>2.8.6</version>         </dependency>          <dependency>             <groupId>org.springframework.boot</groupId>             <artifactId>spring-boot-configuration-processor</artifactId>             <optional>true</optional>         </dependency>          <dependency>             <groupId>org.projectlombok</groupId>             <artifactId>lombok</artifactId>             <optional>true</optional>         </dependency>       </dependencies>      <build>         <plugins>             <plugin>                 <groupId>org.springframework.boot</groupId>                 <artifactId>spring-boot-maven-plugin</artifactId>             </plugin>         </plugins>     </build> 

Минимальный проект будет иметь примерно такую структуру :

А теперь детальней по каждому классу :

BotConfig — Конфигурация подтягивающая настройки бота из application.properties
@Configuration @Data @PropertySource("classpath:application.properties") public class BotConfig {      // Имя бота заданное при регистрации     @Value("${botUserName}")     String botUserName;      // Токен полученный при регистрации     @Value("${token}")     String token; }

BotInitializer — Component регистрации/инициализации бота в системе Telegram
@Component @Slf4j public class BotInitializer {     @Autowired     Bot bot;      @EventListener({ContextRefreshedEvent.class})     public void Init() throws TelegramApiException {         TelegramBotsApi telegramBotsApi = new TelegramBotsApi(DefaultBotSession.class);         try {             telegramBotsApi.registerBot(bot);         } catch (TelegramApiRequestException e) {             log.error(exceptionStackTraceToString(e));         }     } }

Телеграм API должно быть зарегистрировано в системе и делать это нужно уже после поднятия контекста Spring, по этому вешаем слушатель на ContextRefreshedEvent

Bot — Сервис инкапсулирующий в себе реализацию TelegramLongPollingBot,
@Component @Slf4j /**  * Касс является основным пулом взаимодействия с Telegram  * получение, обработка, отправка сообщений  */ public class Bot extends TelegramLongPollingBot {      final     BotConfig config;      public Bot(BotConfig config) {         this.config = config;     }      public void onUpdateReceived(Update update) {         update.getUpdateId();         SendMessage.SendMessageBuilder builder =SendMessage.builder();         String messageText;         String chatId;         if (update.getMessage() != null) {             chatId = update.getMessage().getChatId().toString();             builder.chatId(chatId);             messageText = update.getMessage().getText();         } else {             chatId = update.getChannelPost().getChatId().toString();             builder.chatId(chatId);             messageText = update.getChannelPost().getText();         }          if (messageText.contains("/hello")) {             builder.text("Привет");             try {                 execute(builder.build());             } catch (TelegramApiException e) {                 log.debug(e.toString());             }         }          if (messageText.contains("/chartId")) {             builder.text("ID Канала : " + chatId);             try {                 execute(builder.build());             } catch (TelegramApiException e) {                 log.debug(e.toString());             }         }     }       public String getBotUsername() {         return config.getBotUserName();     }      public String getBotToken() {         return config.getToken();     } } 

Данный класс представляет из себя основной функционал для взаимодействия с Telegram

  • метод onUpdateReceived принимает и обрабатывает сообщения пришедшие в личку или в канал где бот администратор

WebHook — RestController обслуживающий API для реакции на события WebHook Gitea
@Slf4j @RestController @RequestMapping("/api/public/gitea") @RequiredArgsConstructor @PropertySource("classpath:application.properties") public class WebHook {     Bot bot;      // Канал в который будем слать уведомления     @Value("${chartId}")     String chartId;      // Секретный ключ который придёт в нутри JSON от Gitea,     // что бы левые люди не имели доступа к боту т.к. API публичное без авторизации     @Value("${secret}")     String secret;      @Autowired     public WebHook(Bot bot) {         this.bot = bot;     }      @PostMapping(value = "/webhook")     public ResponseEntity<?> webhook(@RequestBody String json){         Gson gson = new Gson();         GiteaWebHook giteaWebHook = null;         try {             giteaWebHook = gson.fromJson(json, GiteaWebHook.class);         } catch (JsonSyntaxException e) {             log.error(Utils.exceptionStackTraceToString(e));             return new ResponseEntity<>(Utils.exceptionStackTraceToString(e), HttpStatus.BAD_REQUEST);         }         if (validationWebHookContent(giteaWebHook)) {             SendMessage.SendMessageBuilder messageBuilder =SendMessage.builder();             messageBuilder.chatId(chartId);              messageBuilder.parseMode(ParseMode.HTML);             StringBuilder builder = new StringBuilder();             builder.append("<b>Проект</b> : " + giteaWebHook.getRepository().getName()+"\n");             for (Commit commit : giteaWebHook.getCommits()) {              builder.append("<b>Автор</b> : " + commit.getAuthor().getName()+"\n");              builder.append("<b>Комментарий</b> : " + commit.getMessage()+"\n");             }             builder.append("<a href=\"https://play.google.com/store/apps/details?id=URL_ВАШЕГО_ПРИЛАЖЕНИЯ\">Обновление будет доступно в Play Market через пару минут</a>\n");             messageBuilder.text(buildToCorrectString(builder));             try {                 bot.execute(messageBuilder.build());             } catch (TelegramApiException e) {                 log.error(Utils.exceptionStackTraceToString(e));                 return new ResponseEntity<>(Utils.exceptionStackTraceToString(e), HttpStatus.INTERNAL_SERVER_ERROR);             }         } else return new ResponseEntity<>(HttpStatus.BAD_REQUEST);          HttpHeaders headers = new HttpHeaders();         headers.add("Content-Type", "application/json; charset=utf-8");         return new ResponseEntity<>(headers, HttpStatus.OK);     }      /**      * Проверка пришедших JSON данных на валидность      * @param giteaWebHook - GiteaWebHook      * @return true - если не null, PUSH в master, совпал секретный ключ      */     private boolean validationWebHookContent(GiteaWebHook giteaWebHook){         return giteaWebHook != null && // Если вообще что то есть                giteaWebHook.getRef().contains(giteaWebHook.getRepository().getDefaultBranch()) && // Есть был PUSH в /master                giteaWebHook.getSecret().equals(secret); // Если совпал секретный ключ     }      private String buildToCorrectString(StringBuilder builder){         return builder.toString()                 .replace("_", "\\_")                 .replace("*", "\\*")                 .replace("[", "\\[")                 .replace("`", "\\`")                 .replace("&nbsp;", " ")                 .replace("&frac", " ")                 .replaceAll(" \\u003c","");     } } 

Данный RestController обслуживает RestAPI с точкой входа http://you_ip:port/api/public/gitea/webhook , сюда наша система контроля версий Gitea будет делать PUSH запрос с JSON данными WebHook возникающего при различных событиях происходящих с вашим репозитарием.

TelegramBotGiteaApplication — Стартовый метод нашего Spring Boot проекта
@SpringBootApplication @AutoConfigurationPackage public class TelegramBotGiteaApplication extends SpringBootServletInitializer {      public static void main(String[] args) {         new SpringApplicationBuilder(TelegramBotGiteaApplication.class)                 .run(args);     } }

Все те классы что вы наблюдаете в пакете Model , представляют сгенерированную модель GiteaWebHook по JSON-Schema взятому из оф документации по GiteaWebHookApi , удобнее всего это делать при помощи http://www.jsonschema2pojo.org/

Полный исходный код вы можете взять ЗДЕСЬ

Настройка Gitea для выполнения WebHook к нашему боту

Данная статья рассматривает вариант обслуживания API предоставляемое системой контроля версий Gitea но это не значит что вы не сможете сделать оповещение и без неё. Проявив некую долю усердия всё те-же WebHook можно реализовать через .git\hooks\post-update и curl , или некое API GitHub но эти реализации я доверяю вам, и здесь мы рассмотрим лишь вариант с Gitea:

  1. Итак, перейдите в репозитарий вашего проекта и войдите в его Настройки в раздел Автоматическое обновление . Нажмите на кнопку Добавить Webhook , выберете вариант Gitea

  2. В качестве URL обработчика укажите URL на котором у нас висит RestController http://you_ip:port/api/public/gitea/webhook

  3. Тип содержимого — application/json

  4. Секретный ключ — любой набор символов который мы в последующем внесём в наш application.properties

  5. На какие события этот webhook должен срабатывать? — выберите PUSH

  6. Галочку Активности оставьте включённой.

Всё это хорошо бот написан, Gitea настроена но нашему боту нужно где-то жить.

Публикация нашего бота в Google Cloud Platform, бесплатно

New customers get $300 in free credits to spend on Google Cloud. All customers get free usage of 20+ products. See offer details.

Такое сообщение мы видим на главной страницы этого сервиса, а именно они дают 300$ бесплатно на год для функционирования нашего приложения у них в облаке, и нам этого вполне себе хватит.

И так приступим, авторизуйтесь в Google Cloud Platform

1) В боковом меню перейдите в раздел Сompute Engine, и дождитесь его инициализации

2) В разделе Экземпляры ВМ, нажмите создать

Выберете конфигурацию инстанса к примеру вот такую, это будет оптимальным решением для нашего не требовательного приложения

Давайте зададим сразу правило Брандмауэра разрешающего трафик по порту 8080

Зайдите Cеть VPS — Брандмауэр

Создайте правило для порта 8080 по аналогии с 80 портом

Подключитесь к VM через SSH , непосредственно через браузер

Теперь мы может перейти к настройке нашей виртуальной машины

Для обновления информации об новейших версиях пакетов вводим

sudo apt update

Установим Java

sudo apt install default-jdk

Проверьте это выполнив

java - version

Если всё прошло хорошо вы должны наблюдать версию Java в консоли SSH

Теперь установим maven

sudo apt install maven

Сделаем clone репозитария с нашим ботом заготовкой

git clone https://legan.by/gitea/leganas/TelegramBotGiteaLesson.git

Сделайте необходимые манипуляции по настройке appliation.properties , укажите валидный bot_name и token

nano ./TelegramBotGiteaLesson/src/main/resources/application.properties

Зайдём в папку с нашим ботом и соберём его при помощи maven

cd TelegramBotGiteaLesson/ mvn package

После того как maven соберёт наш проект

Бот полностью готов к запуску, выполните команду

java -jar ./target/telegrambotgitea-0.0.1-SNAPSHOT.jar

Используя внешний IP указанный в web консоли управления виртуальными машинами, проверьте его работоспособность открыв в браузере страницу http://YOU_IP:8080/

Если наш бот успешно подключился к Telegram мы можем написать ему проверочное сообщение в ЛС /hello , на что он должен ответить нам Привет

И так теперь у нас всё готово для того что бы добавить нашего бота в Telegram канал, дать ему права администратора (что бы он мог читать сообщения) и протестировать оповещение !

Добавил бота в канал и дав ему права , вы можете написать

/chartId

Теперь это ID можно добавить в application.properties и либо пересобрать проект , либо подкинуть этот файл рядом с jar. Внесите необходимые правки IP адреса в WebHook в настройках Gitea и опробуйте вашего бота.

У кого получилось вот так , тот молодец, о том как сделать jar сервисом писать уже не буду, уж очень много про это написано на хабре, а если не найдёте пишите в личку.

Надеюсь моё повествование было достаточно подробным, чтобы любой смог запустить свою зверушку.

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


Комментарии

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

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