JavaRush /Java блог /Java-проекты /Добавляем телеграм-бота на проект - "Java-проект от А до ...
Roman Beekeeper
35 уровень

Добавляем телеграм-бота на проект - "Java-проект от А до Я"

Статья из группы Java-проекты
Приветствую вас, мои дорогие друзья. Да-да, именно друзья. Я уже так сросся с этой серией статей, что те люди, которые регулярно пишут в комментариях свою благодарность и/или показывают, что прочли и поняли материал, стали уже близки. Мы с вами идем с двух сторон к одной цели. Вы хотите понять, а я хочу объяснить. И конечная цель у нас одна — написанное приложение, которое понятно вам от начала до конца. О многом из того, что опишу в этой статье вы, возможно, уже наслышаны. Не думаю, что я расскажу что-то новое и неординарное (но в рамках проекта знать/повторить это необходимо). "Java-проект от А до Я": добавляем телеграм-бота на проект - 1Весной я писал бота для себя, так что будем опираться на его “лекала”.

Пишем JRTB-2

Будем делать так же, как делали в статье с задачей JRTB-0:
  1. Обновляем main ветку в локальном проекте через комбинацию ctrl + t."Java-проект от А до Я": добавляем телеграм-бота на проект - 2
  2. На основе main ветки создаем:"Java-проект от А до Я": добавляем телеграм-бота на проект - 3
  3. Добавляем бота.
  4. Создаем новый коммит с описанием сделанного и пушим на гитхаб.
  5. Создаем пул-реквест на main ветку и еще раз проверяем. Ждем, чтобы прошел билд (github actions), мержим в мейн ветку.
  6. Закрываем соответствующую задачу.

Что такое телеграм-бот

Мы, разработчики, можем представить работу с телеграм-ботом так: мы используем их клиент для работы с ними. У нас есть готовая библиотека для работы. Есть набор действий, после которых телеграм-бот будет знать, что он связан нашей программой. А уже внутри программы мы научимся получать письма, команды и как-то их обрабатывать. Есть такая вещь как команда в телеграм-ботах: она начинается со слеша “/”. После нее сразу слитно пишем слово, и это будет считаться командой. Например, есть две команды, которые все должны знать:
  • /start — начало работы с ботом;
  • /stop — конец работы с ботом.
Остальное мы сделаем сами. Сразу оговорюсь: мы будем делать именно то и таким способ, каким научился я. И в работе с ботом я уверен, что можно будет сделать лучше. И если кто-то захочет это сделать — я буду только рад и всячески поддержу это начинание. Кстати, самое первое, что было бы круто, если бы кто-то мне объяснил, как запрограммировать описание команд через код, а не через настройки бота в телеграме. Этому я не научился. У нас на ресурсе есть несколько статей, в которых есть описание, как сделать элементарного бота: мы сегодня сделаем что-то похожее. Если будут еще вопросы, рекомендую бегло пробежаться по этой статье.

Создаем бота у BotFather

Чтобы подключить бота, его вначале нужно создать. У Телеграма есть подход — создание бота со своим уникальным именем. К нему будет прилагаться еще токен (большая строка, которая работает как пароль). Я уже создал бота для JavaRush — @javarush_community_bot. Этот бот еще пустой и ничего не умеет. Главное, чтобы в конце имени было _bot. Чтобы показать, как это сделать, я создам бота, на котором мы будем тестировать наш функционал. В терминах реальных проектов это будет test environment (тестовое окружение). А наш основной будет будет prod environment (prod — production, то есть реальное окружение, на котором будет выполняться проект). Конечно, можно было бы добавить еще одно окружение — sandbox environment: общую песочницу, более изменяемую и доступную всем участникам разработки. Но это лишь усложнит ситуацию на этапе создания проекта. Пока что создадим еще два бота для test и для sandbox окружения. Первый шаг — создать (зарегистрировать) бота в самом Телеграме. Нужно найти бота: @BotFather и написать ему команду: /newbot"Java-проект от А до Я": добавляем телеграм-бота на проект - 4Далее нас просят дать имя этому боту. Так как это бот для тестовых задач, то и имя у него будет соответствующее: [TEST] JavarushBot"Java-проект от А до Я": добавляем телеграм-бота на проект - 5Теперь пришло время дать уникальное имя, по которому его всегда можно будет найти — его username: test_javarush_community"Java-проект от А до Я": добавляем телеграм-бота на проект - 6Как я и говорил выше, нужно добавлять суффикс _bot для username, поэтому пишем еще раз: test_javarush_community_bot"Java-проект от А до Я": добавляем телеграм-бота на проект - 7И все! Бот создан. Теперь по username и token его можно подключить к нашему проекту. Разумеется для бесперебойной работы тестового сервера я не буду выставлять token (по сути это пароль к доступу к боту) этого бота на общее обозрение.

Подключаем бота в проект

Мы не будем подключать библиотеку как обычно, а сразу воспользуемся преимуществами нашего скелета — 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>
Добавляем ее в наш помник. Выносим версию как положено и обновляем мавен проект."Java-проект от А до Я": добавляем телеграм-бота на проект - 8Исходя из описания, нам нужно просто создать новый класс, унаследоваться от TelegramLongPollingBot и добавить этот класс в Application Context нашего SpringBoot. Application Context — это место, где хранятся созданные объекты для работы проекта. Чтобы добавить какой-то класс, нужно использовать одну из аннотаций: @Component, @Service, @Repository, @Controller. Или аннотацию @Bean, если создается через метод в конфигурационном классе (то есть в классе, который помечен аннотацией Configuration). Я понимаю, что все это пока может казаться непонятным. Но когда вы начнете разбираться, увидите, что ничего сложного там нет. Чтобы быстро разобраться со Spring Boot, советую крутую книжку — Spring In Action 5th edition. Если будет желание, я могу написать серию статей по этой книге. Возвращаемся обратно. В пакете, в котором лежит JavarushTelegramBotApplication, создаем пакет bot, в котором будет лежать наш телеграм-бот. Имя у него будет JavaRushTelegramBot:

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() — а это, соответственно, токен бота.
По сути это как логин и пароль к сайту. На данный момент мы не будем явно записывать это значение. Это называется “захардкодить” (то есть, привязать какое-то частное значение — как обычно, калька с английского hard code). Так делать не стоит. Мы пойдем другим путем — запишем эти данные в application.properties класс и отсюда будем их считывать. Зачем это нужно? Затем, чтобы при запуске приложения мы могли эти значения задать извне. Это гибко, это правильно. Идем в файл src/main/resources/application.properties. Там придумаем имена этим переменным. Файлы с расширением .properties считываются как структура ключ-значение с разделителем “=”, каждая пара — это отдельная строка. Поэтому я придумал такие переменные:
  • bot.username;
  • bot.token.
Вот так будет это выглядеть:"Java-проект от А до Я": добавляем телеграм-бота на проект - 9У SpringBoot есть отличная аннотация — @Value. Если ее правильно использовать, то она подтянет нам значения из application.properties файла. Обновляем проект под это:

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 и ищем нашего бота в Телеграме:"Java-проект от А до Я": добавляем телеграм-бота на проект - 10Из логов видим, что бот запустился. Значит, пришло время идти в Телеграм и написать боту:"Java-проект от А до Я": добавляем телеграм-бота на проект - 11Нажимаем начать и нам сразу же приходит ответ:"Java-проект от А до Я": добавляем телеграм-бота на проект - 12Напишем еще какую-то лабуду, чтобы проверить:"Java-проект от А до Я": добавляем телеграм-бота на проект - 13И все, на этом моменте можно сказать, что задача наша JRTB-2 завершена. Здесь пока что особенно тесты не напишешь, поэтому оставим все как есть. Далее нужно создать новый коммит:"Java-проект от А до Я": добавляем телеграм-бота на проект - 14Обратите внимание на имя коммита: опять заостряю ваше внимание на этом. Коммит вначале содержит имя задачи, а потом уже более детальное описание, что сделано. Нажимаем Commit and Push… и подтверждаем, еще раз нажав Push:"Java-проект от А до Я": добавляем телеграм-бота на проект - 15Переходим в наш проект. Как и раньше, GitHub уже увидел новую ветку и предлагает создать пул-реквест на main. Не противимся и создаем его:"Java-проект от А до Я": добавляем телеграм-бота на проект - 16Уже как обычно выбрали лейбу, проект и назначили ее на меня. В конце нажимаем Create Pull Request. Немного подождем, пока пройдет билд — и все, пул-реквест готов к слиянию:"Java-проект от А до Я": добавляем телеграм-бота на проект - 17

Версионирование

Я как-то упустил момент, что нам нужно вести версионирование. Для этого сделаем еще несколько изменений в нашей ветке. Заходим обратно в IDEA и смотрим на версию проекта в помнике:"Java-проект от А до Я": добавляем телеграм-бота на проект - 18Стоит версия 0.0.1-SNAPSHOT. Это дежурная версия. А мы начнем с того, что будем обновлять версию проекта с каждой новой решенной задачей. Пока мы не вышли в MVP, версия будет идти с суффиксом -SNAPSHOT. Какая будет схема версионирования? X.Y.Z-SNAPSHOT Где:
  • X — мажорное обновление версии, зачастую содержит проблемы с обратной совместимостью с предыдущей версией;
  • Y — не сильно большие изменения, полностью совместимые с предыдущей версией;
  • Z — счетчик дефектов, которые мы нашли и починили.
Исходя из этого у нас будет первая версия — 0.1.0-SNAPSHOT — то есть, у нас не было еще мажорных обновлений, всего понемногу, и мы еще не вышли в MVP, поэтому есть суффикс -SNAPSHOT. Меняем в помнике это дело:"Java-проект от А до Я": добавляем телеграм-бота на проект - 19Идем в файл RELEASE_NOTES, где будем описывать изменения проекта с каждой новой версией:"Java-проект от А до Я": добавляем телеграм-бота на проект - 20Наша первая запись. Теперь при каждом последующем обновлении версии будем здесь описывать что именно произошло. Делаем коммит этого дела, пишем описание: JRTB-2: updated project version and added to RELEASE_NOTES Все точно так же, как и до этого. Ждем, пока билд пройдет, и мы сможем смержить наши изменения. Только здесь будет несколько иначе. Я хочу сделать так, чтобы каждая задача в main ветке была отдельным коммитом, поэтому просто нажать в пул-реквест Merge pull request нам не подойдет. В гите есть опция git squash, которая собирает все коммиты в один и мержит. Выбираем эту опцию:"Java-проект от А до Я": добавляем телеграм-бота на проект - 21Нажимаем Squash and Merge, и нам предлагают еще отредактировать сообщение, которое будет в итоге:"Java-проект от А до Я": добавляем телеграм-бота на проект - 22Очень удобно и главное, что востребовано. К слову, на битбакете такой фичи я не видел =/ Подтверждаем мерж. Осталась самая малость — перевести задачу в статус Done у нас в доске, написать комментарий со ссылкой на пул-реквест и закрыть его:"Java-проект от А до Я": добавляем телеграм-бота на проект - 23Наша доска теперь выглядит так:"Java-проект от А до Я": добавляем телеграм-бота на проект - 24

Вывод

Сегодня мы шаг за шагом создали телеграм-бота и внедрили его в наш SpringBoot проект. Бот работает, отдает ответы. Сделали сразу же доступ к данным бота через проперти. Дальше больше: будем делать большой кусок — выполнять JRTB-3 — добавление Command Pattern для нашего проекта. Ох, еще один нюанс…. Я же говорил, что не буду публиковать token, чтобы им не воспользовались. Но так как я писал статью уже ближе к полуночи и после рабочего дня, то вышло, что я выложил-таки в репозиторий действующий токен, а сказала мне об этом GitGuardian в письме:"Java-проект от А до Я": добавляем телеграм-бота на проект - 25Спасибо им за это! Что же теперь делать? Удалить из гита уже не получится, так как даже если я накачу новый коммит уже без этого токена, то он все равно останется в старом. А удалять и откатывать назад коммит я не хочу. Поэтому я пошел и деактивировал токен у уже упомянутого BotFather. Теперь токен есть, но он уже недействителен. Подписывайтесь на мой гитхаб аккаунт, чтобы раньше публикации статьи увидеть весь код по ней. Всем спасибо за чтение, до встречи.

Список всех материалов серии в начале этой статьи.

Комментарии (33)
ЧТОБЫ ПОСМОТРЕТЬ ВСЕ КОММЕНТАРИИ ИЛИ ОСТАВИТЬ КОММЕНТАРИЙ,
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ
Anonymous #2856674 Уровень 18
10 марта 2024
Если у кого-то не работает - мой рабочий пример https://github.com/Naroru/JavaRushTelegramBot/tree/JRTB-3 (ветка JRTB-3) По поводу завершения программы с выводом сообщения Process finished with exit code 0 - как написали, надо понизить версию спринг бута до 2.7.6 например + не забыть пометить класс с ботом как @Component Если прога висит на этапе сборке - просто заново пересобирать, предварительно выполнив команду clean в Maven Если ошибка Error removing old webhook - также, выполняйте clean в Maven и пробуйте запустить, мне помогло Если вообще ничего не понятно и всё не работает - вы просто рановато сели за этот проект. Продолжайте учить спринг бут, мэйвен и т д. У меня такое было. Сейчас всё более менее ясно
Dolphin Уровень 17
18 октября 2023
Вообщем тоже проблема Process finished with exit code 0 чтобы решить надо добавить над классом с методом main аннотацию @ComponentScan и указать пакет для сканирования. примерно так @ComponentScan(basePackages = { "package com.github.javarushcommunity.jrtb", "org.telegram.telegrambots" }) либо пробуйте менять версию Spring Boot и telegrambots-spring-boot-starter причина в этом
Anton Smirnov Уровень 2
25 ноября 2022
Привет и спасибо за материал! Поделюсь проблемой, с которой столкнулся, - после запуска main ветки приложение поднимается и сразу гаснет с сообщением "Process finished with exit code 0" Покопавшись, понял, что причина в версии Spring Boot'а - у меня стояла 3.0.0. Поставил более раннюю версию в помнике (2.7.6) и все полетело. Всем успехов в обучении!
Denis Уровень 33
11 ноября 2022
У меня не проходит "Build with Maven". Может ли быть причиной то, что я в файле "application.properties" удалил информацию о пользователе и token перед Push?
hidden #234285 Уровень 12
15 сентября 2022
2022-09-15 12:28:39.440 WARN 10444 --- [ main] s.c.a.AnnotationConfigApplicationContext : Exception encountered during context initialization - cancelling refresh attempt: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'telegramBotInitializer' defined in class path resource [org/telegram/telegrambots/starter/TelegramBotStarterConfiguration.class]: Invocation of init method failed; nested exception is java.lang.RuntimeException: org.telegram.telegrambots.meta.exceptions.TelegramApiRequestException: Error removing old webhook Что делать?
Константин Уровень 34
2 марта 2022
Выскакивает ошибка Error removing old webhook Как я понял, что дело в блокировке, наверняка кто-нибудь сталкивался. Как решали, подскажите, пожалуйста? Спасибо!
LsdLuciffer Уровень 15
23 июля 2021
начало не понятно от слова совсем. "Будем делать так же, как делали в статье с задачей JRTB-0:" и как мне то, что мы делали в предыдущих статьях связать с тем, что мы делаем в этой?))))))))))))))))))) какие-то обрывки информации разбросаны по каждой статье но до ума не доведена ни одна из них.
Maxim Tikhonenko Уровень 31
27 апреля 2021
Давно увидел твои статьи, но только недавно начал активно писать этот проект по методичке. Это то, что я искал, что мне нужно сейчас, да и большинству на этом ресурсе) Пощупать руками, разглядеть со всех сторон реальный проект, познакомиться с технологиями это здорово!) Огромная благодарность тебе, Роман! Всем остальным удачи!
Roman Beekeeper Уровень 35
11 марта 2021
⚡️UPDATE⚡️ Друзья, создал телеграм-канал 🤓, в котором освещаю свою писательскую деятельность и свою open-source разработку в целом. Не хотите пропустить новые статьи? Присоединяйтесь ✌️
Mister S Уровень 35
4 марта 2021
Respect to the author!!! У меня все прошло, но пришлось добавить import org.springframework.stereotype.Component; Кто знает, что надо было бы сделать, чтобы обойтись без этого? (скачать этот плагин и добавить в папку Мавена?)