Приветствую вас, мои дорогие друзья. Да-да, именно друзья. Я уже так сросся с этой серией статей, что те люди, которые регулярно пишут в комментариях свою благодарность и/или показывают, что прочли и поняли материал, стали уже близки.
Мы с вами идем с двух сторон к одной цели. Вы хотите понять, а я хочу объяснить. И конечная цель у нас одна — написанное приложение, которое понятно вам от начала до конца.
О многом из того, что опишу в этой статье вы, возможно, уже наслышаны. Не думаю, что я расскажу что-то новое и неординарное (но в рамках проекта знать/повторить это необходимо).
Весной я писал бота для себя, так что будем опираться на его “лекала”.
Далее нас просят дать имя этому боту. Так как это бот для тестовых задач, то и имя у него будет соответствующее:
[TEST] JavarushBot
Теперь пришло время дать уникальное имя, по которому его всегда можно будет найти — его username:
test_javarush_community
Как я и говорил выше, нужно добавлять суффикс _bot для username, поэтому пишем еще раз:
test_javarush_community_bot
И все! Бот создан. Теперь по username и token его можно подключить к нашему проекту. Разумеется для бесперебойной работы тестового сервера я не буду выставлять token (по сути это пароль к доступу к боту) этого бота на общее обозрение.
Исходя из описания, нам нужно просто создать новый класс, унаследоваться от TelegramLongPollingBot и добавить этот класс в Application Context нашего SpringBoot.
Application Context — это место, где хранятся созданные объекты для работы проекта. Чтобы добавить какой-то класс, нужно использовать одну из аннотаций: @Component, @Service, @Repository, @Controller. Или аннотацию @Bean, если создается через метод в конфигурационном классе (то есть в классе, который помечен аннотацией Configuration).
Я понимаю, что все это пока может казаться непонятным. Но когда вы начнете разбираться, увидите, что ничего сложного там нет. Чтобы быстро разобраться со Spring Boot, советую крутую книжку — Spring In Action 5th edition. Если будет желание, я могу написать серию статей по этой книге.
Возвращаемся обратно. В пакете, в котором лежит JavarushTelegramBotApplication, создаем пакет bot, в котором будет лежать наш телеграм-бот. Имя у него будет JavaRushTelegramBot:
У SpringBoot есть отличная аннотация — @Value. Если ее правильно использовать, то она подтянет нам значения из application.properties файла. Обновляем проект под это:
Из логов видим, что бот запустился. Значит, пришло время идти в Телеграм и написать боту:
Нажимаем начать и нам сразу же приходит ответ:
Напишем еще какую-то лабуду, чтобы проверить:
И все, на этом моменте можно сказать, что задача наша JRTB-2 завершена. Здесь пока что особенно тесты не напишешь, поэтому оставим все как есть.
Далее нужно создать новый коммит:
Обратите внимание на имя коммита: опять заостряю ваше внимание на этом. Коммит вначале содержит имя задачи, а потом уже более детальное описание, что сделано.
Нажимаем Commit and Push… и подтверждаем, еще раз нажав Push:
Переходим в наш проект. Как и раньше, GitHub уже увидел новую ветку и предлагает создать пул-реквест на main. Не противимся и создаем его:
Уже как обычно выбрали лейбу, проект и назначили ее на меня. В конце нажимаем Create Pull Request.
Немного подождем, пока пройдет билд — и все, пул-реквест готов к слиянию:!["Java-проект от А до Я": добавляем телеграм-бота на проект - 17]()
Стоит версия 0.0.1-SNAPSHOT. Это дежурная версия. А мы начнем с того, что будем обновлять версию проекта с каждой новой решенной задачей.
Пока мы не вышли в MVP, версия будет идти с суффиксом -SNAPSHOT.
Какая будет схема версионирования?
X.Y.Z-SNAPSHOT
Где:
Идем в файл RELEASE_NOTES, где будем описывать изменения проекта с каждой новой версией:
Наша первая запись. Теперь при каждом последующем обновлении версии будем здесь описывать что именно произошло.
Делаем коммит этого дела, пишем описание:
JRTB-2: updated project version and added to RELEASE_NOTES
Все точно так же, как и до этого. Ждем, пока билд пройдет, и мы сможем смержить наши изменения.
Только здесь будет несколько иначе. Я хочу сделать так, чтобы каждая задача в main ветке была отдельным коммитом, поэтому просто нажать в пул-реквест Merge pull request нам не подойдет.
В гите есть опция git squash, которая собирает все коммиты в один и мержит.
Выбираем эту опцию:
Нажимаем Squash and Merge, и нам предлагают еще отредактировать сообщение, которое будет в итоге:
Очень удобно и главное, что востребовано. К слову, на битбакете такой фичи я не видел =/
Подтверждаем мерж.
Осталась самая малость — перевести задачу в статус Done у нас в доске, написать комментарий со ссылкой на пул-реквест и закрыть его:
Наша доска теперь выглядит так:!["Java-проект от А до Я": добавляем телеграм-бота на проект - 24]()
Спасибо им за это! Что же теперь делать? Удалить из гита уже не получится, так как даже если я накачу новый коммит уже без этого токена, то он все равно останется в старом. А удалять и откатывать назад коммит я не хочу. Поэтому я пошел и деактивировал токен у уже упомянутого BotFather. Теперь токен есть, но он уже недействителен.
Подписывайтесь на мой гитхаб аккаунт, чтобы раньше публикации статьи увидеть весь код по ней.
Всем спасибо за чтение, до встречи.

Пишем JRTB-2
Будем делать так же, как делали в статье с задачей JRTB-0:- Обновляем main ветку в локальном проекте через комбинацию ctrl + t.
- На основе main ветки создаем:
- Добавляем бота.
- Создаем новый коммит с описанием сделанного и пушим на гитхаб.
- Создаем пул-реквест на main ветку и еще раз проверяем. Ждем, чтобы прошел билд (github actions), мержим в мейн ветку.
- Закрываем соответствующую задачу.
Что такое телеграм-бот
Мы, разработчики, можем представить работу с телеграм-ботом так: мы используем их клиент для работы с ними. У нас есть готовая библиотека для работы. Есть набор действий, после которых телеграм-бот будет знать, что он связан нашей программой. А уже внутри программы мы научимся получать письма, команды и как-то их обрабатывать. Есть такая вещь как команда в телеграм-ботах: она начинается со слеша “/”. После нее сразу слитно пишем слово, и это будет считаться командой. Например, есть две команды, которые все должны знать:- /start — начало работы с ботом;
- /stop — конец работы с ботом.
Создаем бота у BotFather
Чтобы подключить бота, его вначале нужно создать. У Телеграма есть подход — создание бота со своим уникальным именем. К нему будет прилагаться еще токен (большая строка, которая работает как пароль). Я уже создал бота для JavaRush — @javarush_community_bot. Этот бот еще пустой и ничего не умеет. Главное, чтобы в конце имени было _bot. Чтобы показать, как это сделать, я создам бота, на котором мы будем тестировать наш функционал. В терминах реальных проектов это будет test environment (тестовое окружение). А наш основной будет будет prod environment (prod — production, то есть реальное окружение, на котором будет выполняться проект). Конечно, можно было бы добавить еще одно окружение — sandbox environment: общую песочницу, более изменяемую и доступную всем участникам разработки. Но это лишь усложнит ситуацию на этапе создания проекта. Пока что создадим еще два бота для test и для sandbox окружения. Первый шаг — создать (зарегистрировать) бота в самом Телеграме. Нужно найти бота: @BotFather и написать ему команду: /newbot



Подключаем бота в проект
Мы не будем подключать библиотеку как обычно, а сразу воспользуемся преимуществами нашего скелета — SpringBoot. У него есть такая вещь, как Starter. Подключив библиотеку, с его помощью можно дать знать SpringBoot’у, что мы хотим настроить проект правильно. Если бы мы пошли по обычному пути, который описан во множестве мест, нам бы нужно было где-то создать конфигурацию, в которой было бы что-то такое:
ApiContextInitializer.init();
TelegramBotsApi telegramBotsApi = new TelegramBotsApi();
try {
telegramBotsApi.registerBot(Bot.getBot());
} catch (TelegramApiRequestException e) {
e.printStackTrace();
}
Здесь создается объект, при помощи которого можно установить связь с ботом.
В нашем случае стартер, который мы захотим подключить, сделает все за нас где-то “под капотом” (это тоже перевод часто используемой фразы в IT — under the hood).
Вот ссылка на этот стартер.
Сразу же по README.md файлу видно, что это, зачем и как его использовать.
Чтобы его подключить, нужно просто добавить эту зависимость в помник. И все :)
Вот нужная зависимость:
<dependency>
<groupId>org.telegram</groupId>
<artifactId>telegrambots-spring-boot-starter</artifactId>
<version>5.0.1</version>
</dependency>
Добавляем ее в наш помник. Выносим версию как положено и обновляем мавен проект.
package com.github.javarushcommunity.jrtb.bot;
import org.telegram.telegrambots.bots.TelegramLongPollingBot;
import org.telegram.telegrambots.meta.api.objects.Update;
/**
* Telegrambot for Javarush Community from Javarush community.
*/
@Component
public class JavarushTelegramBot extends TelegramLongPollingBot {
@Override
public void onUpdateReceived(Update update) {
}
@Override
public String getBotUsername() {
return null;
}
@Override
public String getBotToken() {
return null;
}
}
Этот класс был абстрактный и нужно было реализовать три метода. Поговорим о них подробнее:- onUpdateReceived(Update update) — это и есть точка входа, куда будут поступать сообщения от пользователей. Отсюда будет идти вся новая логика;
- getBotUsername() — здесь нужно добавить username нашего бота, к которому будем соединяться;
- getBotToken() — а это, соответственно, токен бота.
- bot.username;
- bot.token.

package com.github.javarushcommunity.jrtb.bot;
import org.springframework.beans.factory.annotation.Value;
import org.telegram.telegrambots.bots.TelegramLongPollingBot;
import org.telegram.telegrambots.meta.api.objects.Update;
/**
* Telegram bot for Javarush Community from Javarush community.
*/
@Component
public class JavarushTelegramBot extends TelegramLongPollingBot {
@Value("${bot.username}")
private String username;
@Value("${bot.token}")
private String token;
@Override
public void onUpdateReceived(Update update) {
}
@Override
public String getBotUsername() {
return username;
}
@Override
public String getBotToken() {
return token;
}
}
Видно, что мы передали в аннотацию значение переменной. И вот когда SpringBoot будет создавать объект нашего бота, то и значения будут взяты с пропертей (опять калька с английского — properties).
Мы уже почти у цели. Нужно сделать так, чтобы бот что-то отвечал. Поэтому обновим метод onUpdateReceived. Нужно, чтобы мы извлекли сообщение, которое пришло к боту, и передали его обратно. Так мы будем знать, что бот работает.
Для этого мы грубо и быстро напишем то, что нужно:
@Override
public void onUpdateReceived(Update update) {
if(update.hasMessage() && update.getMessage().hasText()) {
String message = update.getMessage().getText().trim();
String chatId = update.getMessage().getChatId().toString();
SendMessage sm = new SendMessage();
sm.setChatId(chatId);
sm.setText(message);
try {
execute(sm);
} catch (TelegramApiException e) {
//todo add logging to the project.
e.printStackTrace();
}
}
}
Здесь все предельно просто: мы проверяем, что сообщение реально существует, потому извлекаем само сообщение (message) и айдишник чата (chatId), в котором идет переписка.
Далее мы создаем объект для отправки сообщения SendMessage, передаем в него само сообщение и айдишник чата — то есть то, что отправить боту и куда.
Этого нам уже хватает. Далее запускаем main метод в класс JavarushTelegramBotApplication и ищем нашего бота в Телеграме:







Версионирование
Я как-то упустил момент, что нам нужно вести версионирование. Для этого сделаем еще несколько изменений в нашей ветке. Заходим обратно в IDEA и смотрим на версию проекта в помнике:
- X — мажорное обновление версии, зачастую содержит проблемы с обратной совместимостью с предыдущей версией;
- Y — не сильно большие изменения, полностью совместимые с предыдущей версией;
- Z — счетчик дефектов, которые мы нашли и починили.






Вывод
Сегодня мы шаг за шагом создали телеграм-бота и внедрили его в наш SpringBoot проект. Бот работает, отдает ответы. Сделали сразу же доступ к данным бота через проперти. Дальше больше: будем делать большой кусок — выполнять JRTB-3 — добавление Command Pattern для нашего проекта. Ох, еще один нюанс…. Я же говорил, что не буду публиковать token, чтобы им не воспользовались. Но так как я писал статью уже ближе к полуночи и после рабочего дня, то вышло, что я выложил-таки в репозиторий действующий токен, а сказала мне об этом GitGuardian в письме:
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ