Всем привет! В какой-то момент обучения вам хочется перейти от решения задач к созданию реальных проектов, которые лягут в основу вашего портфолио. Когда я начинал учиться на стажировке (которую я всем очень рекомендую), на фрилансе поступило предложение написать телеграм- бота. Ввиду своих малых познаний написал довольно простого бота (последний коммит до миграции на 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