Пишем Telegram Bota для оповещения о коммите в 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 можно реализовать через .githookspost-update и curl , или некое API GitHub но эти реализации я доверяю вам, и здесь мы рассмотрим лишь вариант с Gitea

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

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

3) Тип содержимого – application/json

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

4) На какие события этот webhook должен срабатывать? – выберите PUSH

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

Всё это хорошо бот написан, 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 сервисом писать уже не буду, уж очень много про это написано на хабре, а если не найдёте пишите в личку.

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

Let’s block ads! (Why?)

Read More

Recent Posts

Apple возобновила переговоры с OpenAI и Google для интеграции ИИ в iPhone

Apple возобновила переговоры с OpenAI о возможности внедрения ИИ-технологий в iOS 18, на основе данной операционной системы будут работать новые…

2 дня ago

Российская «дочка» Google подготовила 23 иска к крупнейшим игрокам рекламного рынка

Конкурсный управляющий российской «дочки» Google подготовил 23 иска к участникам рекламного рынка. Общая сумма исков составляет 16 млрд рублей –…

2 дня ago

Google завершил обновление основного алгоритма March 2024 Core Update

Google завершил обновление основного алгоритма March 2024 Core Update. Раскатка обновлений была завершена 19 апреля, но сообщил об этом поисковик…

2 дня ago

Нейросети будут писать тексты объявления за продавцов на Авито

У частных продавцов на Авито появилась возможность составлять текст объявлений с помощью нейросети. Новый функционал доступен в категории «Обувь, одежда,…

2 дня ago

Объявлены победители международной премии Workspace Digital Awards-2024

24 апреля 2024 года в Москве состоялась церемония вручения наград международного конкурса Workspace Digital Awards. В этом году участниками стали…

3 дня ago

Яндекс проведет гик-фестиваль Young Con

27 июня Яндекс проведет гик-фестиваль Young Con для студентов и молодых специалистов, которые интересуются технологиями и хотят работать в IT.…

3 дня ago