JavaRush/Java блог/Random/Создаем телеграм-бота с использованием Spring Boot
Whiskels
41 уровень

Создаем телеграм-бота с использованием Spring Boot

Статья из группы Random
участников
Всем привет! В какой-то момент обучения вам хочется перейти от решения задач к созданию реальных проектов, которые лягут в основу вашего портфолио. Когда я начинал учиться на стажировке (которую я всем очень рекомендую), на фрилансе поступило предложение написать телеграм- бота. Ввиду своих малых познаний написал довольно простого бота (последний коммит до миграции на Spring), который содержал в себе три нити:
  • нить приема сообщений;
  • нить отправки сообщений;
  • нить планирования событий (в ней проверялось наличие запланированных сообщений и обновление кешированных данных из JSON).
При написании этого функционала во многом я опирался на эту статью. Все вполне неплохо работало, но чем глубже я погружался в Spring, тем сильнее мне хотелось все отрефакторить с целью уменьшения связности программы и улучшения качества кода. Еще SonarLint (плагин для автоматической проверки качества кода) все время пытался меня убедить, что бесконечные циклы while иметь не очень хорошо. В какой-то момент я решился и все переписал, а теперь хочу поделиться полученными в процессе рефакторинга знаниями с вами. Начнем с основ, а конкретнее — c TelegramBots-Spring-Boot-Starter Итак, поехали! Создадим бота, который будет здороваться в ответ на любое сообщение. Для начала нам необходимо создать новый Maven проект. Добавим необходимые зависимости в pom.xml. Добавляем в properties версии Java и TelegramBots-Spring-Boot-Starter. И прописываем dependencies — здесь у нас будет уже упомянутый выше TelegramBots-Spring-Boot-Starter и Telegram API:Создаем телеграм бота с использованием Spring Boot - 1Библиотека TelegramBots-Spring-Boot-Starter включает в себя Spring Boot и Telegram API. Ее использование позволяет нам довольно простым образом объявить бота в нашем коде, а Spring сам создаст Bean и активирует бота. Если вам интересно, что происходит под капотом в этот момент, то посмотрите исходники библиотеки (в среде разработки или на гитхабе). Также добавляем параметры компиляции:Создаем телеграм бота с использованием Spring Boot - 2 Не забудьте после заполнения pom обновить все зависимости! Создадим два класса — App и Bot, а также файл application.yaml в папке resources. Структура моего проекта выглядит так:Создаем телеграм бота с использованием Spring Boot - 3На данном этапе добавим в application.yaml credentials нашего бота:
bot:
  name: JavaRushTelegramBot
  token: 22313424:AAF4gck4D8gDhq68E7k0UH8vlyQADhxQhYo
Иерархическая запись позволяет нам избежать повторения (bot.name, bot.token) и повысить читаемость. Если у вас еще не создан бот, то завести его можно, следуя официальной инструкции. Если вы не хотите светить креденшлы к боту в application.yaml (что правильно) — используйте переменные окружения при деплое:
bot:
  name: ${BOT_NAME}
  token: ${BOT_TOKEN}
Заполняем класс Bot:
package com.whiskels.telegram.bot;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import org.telegram.telegrambots.bots.TelegramLongPollingBot;
import org.telegram.telegrambots.meta.api.methods.send.SendMessage;
import org.telegram.telegrambots.meta.api.objects.Update;
import org.telegram.telegrambots.meta.exceptions.TelegramApiException;

// Аннотация @Component необходима, чтобы наш класс распознавался Spring, как полноправный Bean
@Component
// Наследуемся от TelegramLongPollingBot - абстрактного класса Telegram API
public class Bot extends TelegramLongPollingBot {
    // Аннотация @Value позволяет задавать значение полю путем считывания из application.yaml
    @Value("${bot.name}")
    private String botUsername;

    @Value("${bot.token}")
    private String botToken;

    /* Перегружаем метод интерфейса LongPollingBot
    Теперь при получении сообщения наш бот будет отвечать сообщением Hi!
     */
    @Override
    public void onUpdateReceived(Update update) {
        try {
            execute(new SendMessage().setChatId(update.getMessage().getChatId())
            .setText("Hi!"));
        } catch (TelegramApiException e) {
            e.printStackTrace();
        }
    }

    // Геттеры, которые необходимы для наследования от TelegramLongPollingBot
    public String getBotUsername() {
        return botUsername;
    }

    public String getBotToken() {
        return botToken;
    }
}
Заполняем класс App:
package com.whiskels.telegram;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.telegram.telegrambots.ApiContextInitializer;

// Аннотация, которая объединяет в себя @Configuration, @EnableAutoConfiguration, @ComponentScan
@SpringBootApplication
public class App {
    public static void main(String[] args) {
        // Здесь код написан по заветам
        // https://github.com/rubenlagus/TelegramBots/tree/master/telegrambots-spring-boot-starter
        ApiContextInitializer.init();

        SpringApplication.run(App.class, args);
    }
}
Если мы все сделали правильно, то можно запустить main и поздороваться с нашим ботом.Создаем телеграм бота с использованием Spring Boot - 4Готово! Мы успешно написали и запустили телеграм бота, который на каждое входящее сообщение здоровается. Если вам была полезна эта статья, то лучшей благодарностью будет, если вы загляните в мой репозиторий и поставите звездочку. Там же вы найдете мою версию телеграм-бота, который имеет много интересных особенностей:
  • хранение пользователей в базе Postgres;
  • авторизацию доступа к командам на основе ролей пользователя;
  • использование кастомных аннотаций @BotCommand и @RequiredRoles для создания обработчиков сообщений и проверки прав пользователя;
  • поддержка создания графика уведомлений.
Если что-то из этого функционала вас заинтересовало — пишите в комментарии, и я постараюсь либо ответить, либо написать развернутую статью о том, как его воссоздать. P. S. Это моя первая статья на JavaRush, и мне хотелось бы погрузиться в дебри Spring JPA и аннотации @Scheduled, но для начала мне показалось, что стоит написать это руководство о том, как вообще поднять бота с использованием Spring Boot. По ботам уже написано несколько статей, но поиск не выдал подобного гайда, так что я решил заполнить эту нишу :) Также хотелось бы отметить Miroha — спасибо за идею UpdateHandler'ов, утащил ее себе :) ЧАСТЬ 2 ЧАСТЬ 3
Комментарии (39)
  • популярные
  • новые
  • старые
Для того, чтобы оставить комментарий Вы должны авторизоваться
Anonymous #3144751
Уровень 1
8 марта, 12:31
Почему я не могу написать бота без использования spring? Я написал бота для своего школьного проекта с помощью библиотеки телеграмботс Могу ли я залить бота на удаленную машину чтобы он работал 24/7?
Алексей
Уровень 53
Expert
13 ноября 2023, 15:09
метод getBotToken в последних версия API deprecated, но при этом в самой документации на него все время ссылаются))))
hidden #3116407
Уровень 51
3 июля 2023, 23:59
Ребят. Никак не могу понять, почему IIDEA ругается. Голову сломал уже. Не пойму в чём ошибка.
hidden #3116407
Уровень 51
3 июля 2023, 23:58
Ilya.P Человек
29 июля 2023, 07:41
На скобки свои посмотри, где ты вызываешь метод setText
hidden #2592309
Уровень 2
3 апреля 2023, 15:08
Как создать переменные окружения? Нигде нет нормальной информации об этом для java, один python
Максим
Уровень 19
25 ноября 2022, 12:02
Подскажите, а можно ли принять сообщение из чата, спарсить его и данные раскидать в БД?! Как ни будь по простому, без 10000 строк кода! Спасибо!
Konstantin Medical Interpreter в Hospital
10 января 2022, 12:17
не могу сформировать запрос что бы понять аде по читать про этот англицизм Если вы не хотите светить креденшлы к боту в application.yaml (что правильно) — используйте переменные окружения при деплое: - киньте стать. пжл
Whiskels
Уровень 41
11 января 2022, 09:18
credential
Евгений Зубарев QA Automation Engineer в СберТех
13 июня 2022, 14:51
telegram.bot: name: ${BOT_NAME} token: ${TOKEN} При запуске значения telegram.bot.name и telegram.bot.token подтянутся из переменных окружения (в случае запуска в контейнере их необходимо задать в конфигурации контейнера) Загрузить в контекст класса: @Value("${telegram.bot.name}") private String botName; @Value("${telegram.bot.token}") private String botToken;
Vladislav Ryzhov
Уровень 1
1 декабря 2021, 21:19
При попытке запустить проект (поменял только токен и имя бота) получаю такую ошибку org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'bot' defined in file "путь".Instantiation of bean failed; nested exception is org.springframework.beans.BeanInstantiationException: Failed to instantiate [com.vladynush.telegram.bot.Bot]: Constructor threw exception; nested exception is com.google.common.util.concurrent.UncheckedExecutionException: java.lang.IllegalStateException: Unable to load cache item Из-за чего такое может происходить?
Whiskels
Уровень 41
3 декабря 2021, 07:03
Привет! Возможно ругается на кириллический путь, но не факт. Стоит попробовать пройтись по стектрейсу с выставленными breakpoints
Armoredsword
Уровень 20
10 марта 2021, 19:30
как прикреплять кнопки к сообщениям ? у меня есть билдер кнопок - как ее отправить пользователю вместе с сообщением, можно пример кода как это делать в onUpdateReceived ? public static InlineKeyboardButton createInlineKeyboardButton(String text, String command) { InlineKeyboardButton inlineKeyboardButton = new InlineKeyboardButton(); inlineKeyboardButton.setText(text); inlineKeyboardButton.setCallbackData(command); return inlineKeyboardButton; }
Whiskels
Уровень 41
11 марта 2021, 15:44
@Override
    public void onUpdateReceived(Update update) {
        List<InlineKeyboardButton> row = List.of(
                new InlineKeyboardButton().setText("test button").setCallbackData("callbackData"),
                new InlineKeyboardButton().setText("test button2").setCallbackData("callbackData2"));
        List<InlineKeyboardButton> rowTwo = List.of(
                new InlineKeyboardButton().setText("test button3").setCallbackData("callbackData3"),
                new InlineKeyboardButton().setText("test button4").setCallbackData("callbackData4"));
        List<List<InlineKeyboardButton>> keyboard = List.of(row, rowTwo);

        SendMessage sendMessage = new SendMessage()
                .setChatId((long) update.getMessage().getFrom().getId())
                .setText("test button")
                .setReplyMarkup(new InlineKeyboardMarkup().setKeyboard(keyboard));

        try {
            execute(sendMessage);
        } catch (TelegramApiException e) {
            e.printStackTrace();
        }
    }
Whiskels
Уровень 41
11 марта 2021, 15:45
такой код вернет сообщение с двумя рядами по две кнопки
Михаил
Уровень 27
14 февраля 2021, 11:59
Добрый день! Прошу прощения, может вопрос глупый, только начинаю изучать Spring.Не подскажете в чем может быть проблема?
Михаил
Уровень 27
14 февраля 2021, 13:37
Проблема решилась перемещением класса с main методом в отдельный пакет. Но все равно бот не хочет со мной здороваться(
Михаил
Уровень 27
14 февраля 2021, 18:34
А почему в вашем помнике нет данной зависимости? Без нее у меня, в классе Bot, не прописываются "телеграммные" методы <dependency> <groupId>org.telegram</groupId> <artifactId>telegrambots</artifactId> <version>3.6</version> </dependency>
Whiskels
Уровень 41
15 февраля 2021, 16:48
Библиотека telegrambots spring boot starter в себе содержит описанные зависимости - если они не подтянулись автоматически, то рекомендую сделать reload all maven projects, maven clean, invalidate cache/restart. Если построить дерево зависимостей проекта, то там будет отражено, какая зависимость что в себе содержит
Whiskels
Уровень 41
15 февраля 2021, 16:50
Судя по приведенному логу бот не поднялся:( при успешном запуске будет такая строчка в логе:
2020-10-02 19:27:50.831  INFO 14536 --- [           main] c.g.x.bots.TelegramBotAutoConfiguration  : Registering polling bot
Если есть возможность выложить Ваш код на гитхаб - можно мне в лс ссылку прислать и я посмотрю Первое впечатление - проблема в том, что пришлось двигать main, в обычном случае он должен в корне лежать - возможно вылечить получиться перезатягиванием зависимостей
Whiskels
Уровень 41
15 февраля 2021, 16:53
Еще можно попробовать забрать себе код из этих трех статей: ссылка и запустить его, подставив свой токен :)
Михаил
Уровень 27
16 февраля 2021, 18:57
Спасибо огромное, проблема действительно была в том, что я перенес класс с main методом в другой пакет. Проблема решилась удалением пакета и переписыванием кода в классе)