Как написать телеграмм бота на java
Перейти к содержимому

Как написать телеграмм бота на java

  • автор:

Простой Telegram-бот на Java и Spring Boot

Тема разработки собственного бота для Telegram хоть и не новая, но всегда актуальная и востребованная. Думаю каждый разработчик рано или поздно сталкивается с такой задачей, поэтому сегодня я расскажу как разработать простой Telegram-бот на Java с использованием Spring Boot.

Функционал бота, который я буду разрабатывать в этой статье, очень простой — с помощью него можно будет получить официальные курсы валют с сайта ЦБ РФ (в нашем случае это доллары и евро). После прочтения данной статьи вы сможете написать уже собственный Telegram-бот с гораздо более сложным функционалом.

Так же данный материал есть в видео-формате на моём YouTube-канале:

Основа Telegram-бота

Для начала понадобится BotFather, чтобы зарегистрировать новый бот в Telegram: https://t.me/BotFather

Выполняем команду /newbot и вводим имя будущего бота, после чего BotFather сгенерирует уникальный токен, который далее потребуется для организации взаимодействия приложения с созданным Telegram-ботом:

Далее открываем Spring Initializr и генерируем новое Spring Boot приложение:

На начальном этапе я не стал включать в проект какие-либо зависимости — всё необходимое буду добавлять постепенно, чтобы было более наглядно.

Скачиваем и открываем архив со сгенерированными исходниками, после чего добавляем первую зависимость — библиотеку TelegramBots в файл build.gradle:

implementation group: 'org.telegram', name: 'telegrambots', version: '6.5.0'

Эта библиотека серьёзно упрощает взаимодействие с Telegram API и предоставляет удобные интерфейсы, которые я и буду использовать для написания бота.

TelegramBots позволяет взаимодействовать с Telegram API в двух режимах:

  1. Long Polling (Длинные опросы) — необходимо наследоваться от класса org.telegram.telegrambots.bots.TelegramLongPollingBot
  2. Webhook (Вебхук) — необходимо наследоваться от класса org.telegram.telegrambots.bots.TelegramWebhookBot

Я предпочитаю использовать первый вариант, поэтому в своём примере буду наследоваться от абстрактного класса TelegramLongPollingBot. Но прежде чем идти дальше, я хочу остановиться на исходниках библиотеки, а конкретно на конструкторах абстрактного класса TelegramLongPollingBot. На момент написания статьи реализация TelegramLongPollingBot выглядит следующим образом (версия библиотеки 6.5.0):

public abstract class TelegramLongPollingBot extends DefaultAbsSender implements LongPollingBot < /** * If this is used getBotToken has to be overridden in order to return the bot token! * @deprecated Overwriting the getBotToken() method is deprecated. Use the constructor instead */ @Deprecated() public TelegramLongPollingBot() < this(new DefaultBotOptions()); >/** * If this is used getBotToken has to be overridden in order to return the bot token! * @deprecated Overwriting the getBotToken() method is deprecated. Use the constructor instead */ @Deprecated() public TelegramLongPollingBot(DefaultBotOptions options) < super(options); >public TelegramLongPollingBot(String botToken) < this(new DefaultBotOptions(), botToken); >public TelegramLongPollingBot(DefaultBotOptions options, String botToken) < super(options, botToken); >. . . >

Обратите особое внимание что первые два конструктора помечены как @Deprecated(), а это значит что их уже не рекомендуется использовать. Это важный момент, так как в более ранних статьях по написанию бота для Telegram очень часто используется конструктор без параметров. Так как теперь у данного конструктора имеется аннотация @Deprecated(), то я его не буду использовать и вместо него возьму конструктор, у которого в сигнатуре всего один параметр — botToken:

 public TelegramLongPollingBot(String botToken)

После того, как разобрались с конструкторами, можно начинать разработку. Для начала я создам в проекте 2 новых пакета — bot и configuration. Вот так на начальном этапе у меня выглядит структура папки src:

Структура папки src

В пакет bot я добавил новый класс ExchangeRatesBot, который как раз и наследуется от абстрактного класса TelegramLongPollingBot:

package ru.akutepov.exchangeratesbot.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.objects.Update; @Component public class ExchangeRatesBot extends TelegramLongPollingBot < public ExchangeRatesBot(@Value("$") String botToken) < super(botToken); >@Override public void onUpdateReceived(Update update) < >@Override public String getBotUsername() < return "kutepov_exchange_rates_bot"; >> 

Тут пока всё предельно просто, разберём основные моменты:

  • в конструктор передаётся переменная botToken, которая будет заполняться из параметра bot.token (чуть позже я пропишу в resources/application.properties). Конечно же сам класс ExchangeRatesBot помечен аннотацией @Component, для того чтобы был создан соответствующий бин.
  • метод onUpdateReceived(Update update) вызывается всякий раз, когда пользователь отправляет в бот сообщение. В этом методе я и буду обрабатывать поступающие от пользователя команды.
  • метод getBotUsername() должен возвращать название бота, которое тоже можно поместить в проперти. Но лично я в этом смысла не вижу, так как название бота не является конфиденциальной информацией в отличие от токена и его вполне можно и захардкодить. Если пихать в проперти-файл всё подряд, то можно получить антипаттерн Мягкое кодирование (Soft code), о нём я рассказывал в одном из своих видео: https://youtu.be/os7VVNsX5pI

И следующим логичным шагом будет добавление параметра bot.token в файл resources/application.properties:

application.properties

Завершающим шагом подготовки «скелета» Telegram-бота будет добавление класса ExchangeRatesBotConfiguration в пакет configuration:

package ru.akutepov.exchangeratesbot.configuration; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.telegram.telegrambots.meta.TelegramBotsApi; import org.telegram.telegrambots.meta.exceptions.TelegramApiException; import org.telegram.telegrambots.updatesreceivers.DefaultBotSession; import ru.akutepov.exchangeratesbot.bot.ExchangeRatesBot; @Configuration public class ExchangeRatesBotConfiguration < @Bean public TelegramBotsApi telegramBotsApi(ExchangeRatesBot exchangeRatesBot) throws TelegramApiException < var api = new TelegramBotsApi(DefaultBotSession.class); api.registerBot(exchangeRatesBot); return api; >> 

Тут я создаю новый бин TelegramBotsApi и регистрирую в нём класс бота — ExchangeRatesBot. После этого можно собрать приложение и запустить, чтобы убедиться что ничего не падает.

Получение данных для бота

Прежде чем обрабатывать команды пользователя, необходимо разработать механизм получения данных, которые планируется отправлять пользователю. В моём случае это курсы доллара и евро, которые можно взять с официального сайта ЦБ РФ: http://www.cbr.ru/scripts/XML_daily.asp

Данная ссылка возвращает XML, который содержит информацию о курсах валют, установленных на текущий день. Telegram-бот должен будет уметь запрашивать данный XML с сервиса ЦБ РФ и парсить его. Для реализации такой возможности я решил использовать простые библиотеки okhttp и XPath.

И если XPath доступен из коробки, то зависимость для okhttp придётся добавить в файл build.gradle:

implementation group: 'com.squareup.okhttp3', name: 'okhttp', version: '4.11.0'

После этого можно приступить к разработке клиента, который будет ходить в ЦБ РФ за данными.

Сперва нужно сконфигурировать бин OkHttpClient в классе ExchangeRatesBotConfiguration, чтобы иметь возможности выполнять http-запросы с помощью библиотеки okhttp:

 @Bean public OkHttpClient okHttpClient()

Затем прописать в resources/application.properties новый параметр:

cbr.currency.rates.xml.url=http://www.cbr.ru/scripts/XML_daily.asp

Кроме этого, для удобства я решил создать checked-исключение, поэтому добавил в проект пакет exception и класс ServiceException в него:

package ru.akutepov.exchangeratesbot.exception; public class ServiceException extends Exception < public ServiceException(String message) < super(message); >public ServiceException(String message, Throwable cause) < super(message, cause); >> 

Небольшое отступление: ситуация с checked-исключенями на самом деле неоднозначная. С одной стороны они гарантируют что исключения будут обработаны, с другой стороны негативно влияют на читаемость кода, так как логика обработки checked-исключений расползается по всему приложению. Например Sonar Qube в дефолтных настройках будет ругаться на участки кода, которые выбрасывают checked-исключения (https://rules.sonarsource.com/java/RSPEC-1162). Моё мнение такое — в отдельных ситуациях checked-исключения бывают очень полезны и прекрасно встраиваются в бизнес-логику, однако если ими злоупотреблять, то действительно можно получить очень переусложнённый код.

И наконец я добавил новый пакет client и создал в нём класс CbrClient, в котором написал следующий код:

package ru.akutepov.exchangeratesbot.client; import okhttp3.OkHttpClient; import okhttp3.Request; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Component; import ru.akutepov.exchangeratesbot.exception.ServiceException; import java.io.IOException; import java.util.Optional; @Component public class CbrClient < @Autowired private OkHttpClient client; @Value("$") private String cbrCurrencyRatesXmlUrl; public Optional getCurrencyRatesXML() throws ServiceException < var request = new Request.Builder() .url(cbrCurrencyRatesXmlUrl) .build(); try (var response = client.newCall(request).execute()) < var body = response.body(); return body == null ? Optional.empty() : Optional.of(body.string()); >catch (IOException e) < throw new ServiceException("Ошибка получения курсов валют от ЦБ РФ", e); >> > 

Класс CbrClient будет далее использоваться как отдельный бин, поэтому не забываем про аннотацию @Component, чтобы Spring смог его найти. Для того чтобы выполнить запрос к сервису ЦБ РФ, требуется url, который я достаю из пропертей с помощью аннотации @Value и записываю в переменную cbrCurrencyRatesXmlUrl и бин OkHttpClient, который получаю с помощью аннотации @Autowired.

В методе getCurrencyRatesXML() я формирую запрос в виде объекта класса Request и далее его передаю в метод newCall(request) и вызываю метод execute() для выполнения http-запроса с помощью библиотеки okhttp. Метод execute() возвращает объект класса Response, который имплиментирует интерфейс AutoClosable, поэтому я использовал конструкцию try с параметрами.

У класса Response имеется метод body(), который возвращает тело сообщения. В данном случае сервис ЦБ РФ вернёт XML с курсами валют, вот его я и вытаскиваю из тела в виде строки, если тело не пустое:

 . . . 840 USD 1 Доллар США 77,2041  978 EUR 1 Евро 84,2500  . . . 

Клиент для API ЦБ РФ готов и теперь нужно создать сервисный слой, в котором из XML будут вытаскиваться необходимые данные. В приложении я создал пакет service, в него положил интерфейс ExchangeRatesService и пакет impl, в котором хранится имплементация интерфейса — ExchangeRatesServiceImpl:

В интерфейсе ExchangeRatesService всего 2 метода:

package ru.akutepov.exchangeratesbot.service; import ru.akutepov.exchangeratesbot.exception.ServiceException; public interface ExchangeRatesService

Как вы можете догадаться, getUSDExchangeRate() будет возвращать курс доллара, а getEURExchangeRate() — курс евро. Поскольку курсы валют потребуются для формирования текстового сообщения, то я не заморачиваюсь и возвращаю их сразу в виде строки. А так работа с деньгами в приложениях это отдельная интересная тема 🙂

Реализация сервисного класса ExchangeRatesServiceImpl, который имплиментирует интерфейс ExchangeRatesService, выглядит так:

package ru.akutepov.exchangeratesbot.service.impl; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.w3c.dom.Document; import org.xml.sax.InputSource; import ru.akutepov.exchangeratesbot.client.CbrClient; import ru.akutepov.exchangeratesbot.exception.ServiceException; import ru.akutepov.exchangeratesbot.service.ExchangeRatesService; import javax.xml.xpath.XPathConstants; import javax.xml.xpath.XPathExpressionException; import javax.xml.xpath.XPathFactory; import java.io.StringReader; @Service public class ExchangeRatesServiceImpl implements ExchangeRatesService < private static final String USD_XPATH = "/ValCurs//Valute[@ID='R01235']/Value"; private static final String EUR_XPATH = "/ValCurs//Valute[@ID='R01239']/Value"; @Autowired private CbrClient client; @Override public String getUSDExchangeRate() throws ServiceException < var xmlOptional = client.getCurrencyRatesXML(); String xml = xmlOptional.orElseThrow( () ->new ServiceException("Не удалось получить XML") ); return extractCurrencyValueFromXML(xml, USD_XPATH); > @Override public String getEURExchangeRate() throws ServiceException < var xmlOptional = client.getCurrencyRatesXML(); String xml = xmlOptional.orElseThrow( () ->new ServiceException("Не удалось получить XML") ); return extractCurrencyValueFromXML(xml, EUR_XPATH); > private static String extractCurrencyValueFromXML(String xml, String xpathExpression) throws ServiceException < var source = new InputSource(new StringReader(xml)); try < var xpath = XPathFactory.newInstance().newXPath(); var document = (Document) xpath.evaluate("/", source, XPathConstants.NODE); return xpath.evaluate(xpathExpression, document); >catch (XPathExpressionException e) < throw new ServiceException("Не удалось распарсить XML", e); >> > 

Вначале я вынес в константы выражения для XPath:

 private static final String USD_XPATH = "/ValCurs//Valute[@ID='R01235']/Value"; private static final String EUR_XPATH = "/ValCurs//Valute[@ID='R01239']/Value";

По сути это правила, по которым выполняется поиск нужных данных в XML.

Далее через @Autowired я подтягиваю CbrClient с помощью которого я буду отправлять запросы к API ЦБ РФ.

Теперь перейдём в конец и рассмотрим самый интересный метод — extractCurrencyValueFromXML(String xml, String xpathExpression). Он получает на вход строку с XML и выражение для XPath, которое представляет из себя правило, по которому в XML будет осуществлён поиск нужного значения (в нашем случае это курс валюты).

Методы getUSDExchangeRate() и getEURExchangeRate() сначала получают через клиентский бин актуальный XML, вызывая метод getCurrencyRatesXML(), а затем через метод extractCurrencyValueFromXML вытаскивают курс нужной валюты, передавая в него соответствующее выражение для XPath.

Важно! При каждом вызове метода getUSDExchangeRate() или getEURExchangeRate() выполняется запрос к API ЦБ РФ и в ответ приходит один и тот же XML, данные в котором изменятся только на следующий день. В реальном проекте такой подход не приемлем и для таких случаев следует использовать кэширование. Это увеличит быстродействие приложения и снизит нагрузку на внешние сервисы.

Обработка команд

Так как возможность получения необходимых данных в приложении реализована, то можно приступить к обработке пользовательских команд. Для этого вернёмся в класс ExchangeRatesBot и допишем его. Кода будет много, но поэтапно всё разберём:

package ru.akutepov.exchangeratesbot.bot; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; 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; import ru.akutepov.exchangeratesbot.exception.ServiceException; import ru.akutepov.exchangeratesbot.service.ExchangeRatesService; import java.time.LocalDate; @Component public class ExchangeRatesBot extends TelegramLongPollingBot < private static final Logger LOG = LoggerFactory.getLogger(ExchangeRatesBot.class); private static final String START = "/start"; private static final String USD = "/usd"; private static final String EUR = "/eur"; private static final String HELP = "/help"; @Autowired private ExchangeRatesService exchangeRatesService; public ExchangeRatesBot(@Value("$") String botToken) < super(botToken); >@Override public void onUpdateReceived(Update update) < if (!update.hasMessage() || !update.getMessage().hasText()) < return; >String message = update.getMessage().getText(); Long chatId = update.getMessage().getChatId(); switch (message) < case START -> < String userName = update.getMessage().getChat().getUserName(); startCommand(chatId, userName); >case USD -> usdCommand(chatId); case EUR -> eurCommand(chatId); case HELP -> helpCommand(chatId); default -> unknownCommand(chatId); > > @Override public String getBotUsername() < return "kutepov_exchange_rates_bot"; >private void startCommand(Long chatId, String userName) < var text = """ Добро пожаловать в бот, %s! Здесь Вы сможете узнать официальные курсы валют на сегодня, установленные ЦБ РФ. Для этого воспользуйтесь командами: /usd - курс доллара /eur - курс евро Дополнительные команды: /help - получение справки """; var formattedText = String.format(text, userName); sendMessage(chatId, formattedText); >private void usdCommand(Long chatId) < String formattedText; try < var usd = exchangeRatesService.getUSDExchangeRate(); var text = "Курс доллара на %s составляет %s рублей"; formattedText = String.format(text, LocalDate.now(), usd); >catch (ServiceException e) < LOG.error("Ошибка получения курса доллара", e); formattedText = "Не удалось получить текущий курс доллара. Попробуйте позже."; >sendMessage(chatId, formattedText); > private void eurCommand(Long chatId) < String formattedText; try < var usd = exchangeRatesService.getEURExchangeRate(); var text = "Курс евро на %s составляет %s рублей"; formattedText = String.format(text, LocalDate.now(), usd); >catch (ServiceException e) < LOG.error("Ошибка получения курса евро", e); formattedText = "Не удалось получить текущий курс евро. Попробуйте позже."; >sendMessage(chatId, formattedText); > private void helpCommand(Long chatId) < var text = """ Справочная информация по боту Для получения текущих курсов валют воспользуйтесь командами: /usd - курс доллара /eur - курс евро """; sendMessage(chatId, text); >private void unknownCommand(Long chatId) < var text = "Не удалось распознать команду!"; sendMessage(chatId, text); >private void sendMessage(Long chatId, String text) < var chatIdStr = String.valueOf(chatId); var sendMessage = new SendMessage(chatIdStr, text); try < execute(sendMessage); >catch (TelegramApiException e) < LOG.error("Ошибка отправки сообщения", e); >> > 

В самом начале я добавил логгер, вынес все команды в константы и подтянул ExchangeRatesService для того чтобы была возможность получать актуальные курсы валют:

 private static final Logger LOG = LoggerFactory.getLogger(ExchangeRatesBot.class); private static final String START = "/start"; private static final String USD = "/usd"; private static final String EUR = "/eur"; private static final String HELP = "/help"; @Autowired private ExchangeRatesService exchangeRatesService;

Всего будет 4 команды, которые можно будет отправить в бот:

  • /start — начало работы с ботом, первичная инструкция пользователю
  • /usd — получение курса доллара
  • /eur — получение курса евро
  • /help — справка

Далее я в самом конце добавил метод sendMessage(Long chatId, String text), который будет отправлять пользователю сообщение:

 private void sendMessage(Long chatId, String text) < var chatIdStr = String.valueOf(chatId); var sendMessage = new SendMessage(chatIdStr, text); try < execute(sendMessage); >catch (TelegramApiException e) < LOG.error("Ошибка отправки сообщения", e); >>

В метод передаются 2 параметра:

  • chatId — идентификатор чата с пользователем, в который необходимо отправить сообщение
  • text — текст сообщения

Теперь добавляем методы-обработчики для каждой команды. Если разрабатывать серьёзный Telegram-бот, то скорее всего придётся создавать отдельные классы-обработчики для каждой команды, чтобы соответствовать Single-Responsibility Principle (SRP) из SOLID. Но так как проект учебный и обработчики у нас не сложные, то пойдём по более простому пути.

Метод-обработчик команды /start:

 private void startCommand(Long chatId, String userName) < var text = """ Добро пожаловать в бот, %s! Здесь Вы сможете узнать официальные курсы валют на сегодня, установленные ЦБ РФ. Для этого воспользуйтесь командами: /usd - курс доллара /eur - курс евро Дополнительные команды: /help - получение справки """; var formattedText = String.format(text, userName); sendMessage(chatId, formattedText); >

В данном методе просто формируется сообщение пользователю и отправляется с помощью метода sendMessage(Long chatId, String text). В сигнатуре метода обработчика startCommand 2 параметра:

  • chatId — идентификатор чата с пользователем, в который необходимо отправить сообщение
  • userName — уникальное имя пользователя в Telegram (ник).

Метод-обработчик команды /usd:

 private void usdCommand(Long chatId) < String formattedText; try < var usd = exchangeRatesService.getUSDExchangeRate(); var text = "Курс доллара на %s составляет %s рублей"; formattedText = String.format(text, LocalDate.now(), usd); >catch (ServiceException e) < LOG.error("Ошибка получения курса доллара", e); formattedText = "Не удалось получить текущий курс доллара. Попробуйте позже."; >sendMessage(chatId, formattedText); >

В этом методе пробуем получить актуальный курс доллара и отправить пользователю сообщение. В случае ошибки — пишем её в лог и честно признаёмся пользователю что что-то пошло не так. В сигнатуре метода только параметр chatId.

Метод-обработчик команды /eur:

 private void eurCommand(Long chatId) < String formattedText; try < var usd = exchangeRatesService.getEURExchangeRate(); var text = "Курс евро на %s составляет %s рублей"; formattedText = String.format(text, LocalDate.now(), usd); >catch (ServiceException e) < LOG.error("Ошибка получения курса евро", e); formattedText = "Не удалось получить текущий курс евро. Попробуйте позже."; >sendMessage(chatId, formattedText); >

Метод аналогичен предыдущему, только получает актуальный курс евро, а не доллара.

Метод-обработчик команды /help:

 private void helpCommand(Long chatId) < var text = """ Справочная информация по боту Для получения текущих курсов валют воспользуйтесь командами: /usd - курс доллара /eur - курс евро """; sendMessage(chatId, text); >

Просто формируем сообщение со справочной информацией о командах бота и отправляем это сообщение пользователю.

Ну и наивно полагать что пользователи не будут слать всякую фигню в бот. Поэтому сделаем обработчик и на этот случай:

 private void unknownCommand(Long chatId)

Методы обработчики готовы, теперь осталось доработать метод onUpdateReceived:

 @Override public void onUpdateReceived(Update update) < if (!update.hasMessage() || !update.getMessage().hasText()) < return; >String message = update.getMessage().getText(); Long chatId = update.getMessage().getChatId(); switch (message) < case START -> < String userName = update.getMessage().getChat().getUserName(); startCommand(chatId, userName); >case USD -> usdCommand(chatId); case EUR -> eurCommand(chatId); case HELP -> helpCommand(chatId); default -> unknownCommand(chatId); > >

Как вы видите, данный метод получает команды от пользователя и решает какому обработчику какую команду передать с помощью оператора выбора switch case. Команды приходят в бот в виде текстового сообщения:

String message = update.getMessage().getText();

Идентификатор чата с пользователем можно получить следующим образом:

Long chatId = update.getMessage().getChatId();

А узнать как зовут пользователя, можно так:

update.getMessage().getChat().getUserName(); // ник update.getMessage().getChat().getFirstName(); // имя update.getMessage().getChat().getLastName(); // фамилия

Вот и всё, Telegram-бот готов! Осталось только запустить приложение и радоваться жизни:

Пишем простой Telegram bot на java, который показывает официальный курс по отношению к иностранным валютам

Всем привет! Да-да тема не очень актуальна и на просторах habr-a есть очень много статей про то, как написать простейшего telegram bota, но я все-таки рискну написать еще одну. Может кому-то она будет интересна и полезна.

Для написания бота нужен аккаунт в telegram.

И так начнем: наш бот будет «ходить» на официальную страницу национального банка и выдавать текущий официальный курс к запрашиваемой валюте.

Создадим проект, я это сделаю с помощью https://start.spring.io/

Генерируем и открываем в среде разработки.

Сейчас нужно зайти в telegram и найти там @BotFather.

Пишем команду /newbot и вводим имя для нашего нового бота. Имя должно быть уникальным и заканчиваться на bot. По данному имени нас будут искать в telegram.

После этого BotFather сгенерирует нам уникальный токен с помощью которого мы получим доступ к нему. Данный токен лучше держать в секрете и никому не показывать, потому что узнав его можно получить доступ к этому боту. Конечно после написания данной статьи я его поменяю.

Далее идем в наш проект и добавляем зависимости для telegram, lombok и json. Библиотеку json будем использовать для парсинга jsona, который будем получать через официальный API банка.

  org.telegram telegrambots 6.5.0  org.projectlombok lombok 1.18.26 provided  org.json json 20220924 

В application.properties прописываем наши данные для доступу к боту.

bot.name=OfficialRateOfBYNBot bot.token=5888893679:AAGQWAbS0zCMtnQrTeWrVYz9KHj2nUlyVIw

Создадим папку config, и там создадим класс BotConfig. Он нужен для использования имени и токена нашего бота для подключения к нему.

@Configuration @Data @PropertySource("application.properties") public class BotConfig < @Value("$") String botName; @Value("$") String token; >

Сейчас поговорим про API через которую мы будем получать данные о курсах валют. Я нашел такой https://www.nbrb.by/apihelp/exrates на официальном сайте национального банка.

Отправив get запрос на данный API через postman мы получим вот такой json.

Чтобы легче обрабатывать json создадим класс CurrencyModel в папке model

@Data public class CurrencyModel

Далее создадим класс CurrencyService в папке service.

public class CurrencyService < public static String getCurrencyRate(String message, CurrencyModel model) throws IOException, ParseException < URL url = new URL("https://www.nbrb.by/api/exrates/rates/" + message + "?parammode=2"); Scanner scanner = new Scanner((InputStream) url.getContent()); String result = ""; while (scanner.hasNext())< result +=scanner.nextLine(); >JSONObject object = new JSONObject(result); model.setCur_ID(object.getInt("Cur_ID")); model.setDate(new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss").parse(object.getString("Date"))); model.setCur_Abbreviation(object.getString("Cur_Abbreviation")); model.setCur_Scale(object.getInt("Cur_Scale")); model.setCur_Name(object.getString("Cur_Name")); model.setCur_OfficialRate(object.getDouble("Cur_OfficialRate")); return "Official rate of BYN to " + model.getCur_Abbreviation() + "\n" + "on the date: " + getFormatDate(model) + "\n" + "is: " + model.getCur_OfficialRate() + " BYN per " + model.getCur_Scale() + " " + model.getCur_Abbreviation(); > private static String getFormatDate(CurrencyModel model) < return new SimpleDateFormat("dd MMM yyyy").format(model.getDate()); >>

В данном классе мы напишем статический метод getCurrencyRate, делаем его статическим чтобы обращаться к нему без создания объекта данного класса. В данном методе мы «идем» на сайт НБ и там по запрашиваемой валюте находим её курс и сохраняем полученную информацию в нашу модель CurrencyModel. На выходе выдаем данные, из этой модели уже в таком виде, в котором их будут видеть наши пользователи.

Далее создаем класс TelegramBot и наследуемся от TelegramLongPollingBot.

@Component @AllArgsConstructor public class TelegramBot extends TelegramLongPollingBot < private final BotConfig botConfig; @Override public String getBotUsername() < return botConfig.getBotName(); >@Override public String getBotToken() < return botConfig.getToken(); >@Override public void onUpdateReceived(Update update) < CurrencyModel currencyModel = new CurrencyModel(); String currency = ""; if(update.hasMessage() && update.getMessage().hasText())< String messageText = update.getMessage().getText(); long chatId = update.getMessage().getChatId(); switch (messageText)< case "/start": startCommandReceived(chatId, update.getMessage().getChat().getFirstName()); break; default: try < currency = CurrencyService.getCurrencyRate(messageText, currencyModel); >catch (IOException e) < sendMessage(chatId, "We have not found such a currency." + "\n" + "Enter the currency whose official exchange rate" + "\n" + "you want to know in relation to BYN." + "\n" + "For example: USD"); >catch (ParseException e) < throw new RuntimeException("Unable to parse date"); >sendMessage(chatId, currency); > > > private void startCommandReceived(Long chatId, String name) < String answer = "Hi, " + name + ", nice to meet you!" + "\n" + "Enter the currency whose official exchange rate" + "\n" + "you want to know in relation to BYN." + "\n" + "For example: USD"; sendMessage(chatId, answer); >private void sendMessage(Long chatId, String textToSend) < SendMessage sendMessage = new SendMessage(); sendMessage.setChatId(String.valueOf(chatId)); sendMessage.setText(textToSend); try < execute(sendMessage); >catch (TelegramApiException e) < >> >

В данном классе внедряем класс BotConfig с нашими настройками для подключения к боту. Переопределяем три метода класса TelegramLongPollingBot:

getBotUsername() — возвращаем имя нашего бота.

getBotToken() — возвращаем токен нашего бота.

onUpdateReceived(Update update) — данный метод вызывается каждый раз при отправке сообщения пользователем. Он вызывается с параметром update, с помощью которого мы можем получить текст сообщения, id чата для отправки ответного сообщения и другую информацию.

Также я написал дополнительно два метода:

startCommandReceived(Long chatId, String name) — данный метод отправляет приветствие после набора команды /start в telegram.

sendMessage(Long chatId, String textToSend) — данный метод через id чата отправляет пользователю сообщение в telegram.

И последний класс, который мы создадим BotInitializer в папке config.

@Component public class BotInitializer < private final TelegramBot telegramBot; @Autowired public BotInitializer(TelegramBot telegramBot) < this.telegramBot = telegramBot; >@EventListener() public void init()throws TelegramApiException < TelegramBotsApi telegramBotsApi = new TelegramBotsApi(DefaultBotSession.class); try< telegramBotsApi.registerBot(telegramBot); >catch (TelegramApiException e) < >> >

В данном классе создаем сессию и регистрируем нашего telegram бота.

Наш бот готов! Будем тестировать!

Находим нашего бота в telegram.

Переходим в telegram и нажимаем start или набираем /start и получаем

Можем тестировать дальше. Наш функционал работает.

Спасибо Всем кто читал данную статью. Пока.

Telegram-бот счётчик сообщений на Java и Spring Boot

Пишем простой Телеграм-бот на Java, который будет подсчитывать сообщения от пользователей чата и записывать их в БД через PostgreSQL.

В этой статье я покажу, как написать Telegram-бот на Java с использованием Spring Boot, PostgreSQL и JPA. Также создадим исполняемый jar-файл. Сам же бот будет подсчитывать сообщения от пользователей и записывать эти данные в БД.

  1. Создаём Spring проект на Java
  2. Реализация базового функционала
  3. Добавление кнопок
  4. Подключение Telegram-бота на Java к базе данных
  5. Создание исполняемого jar-файла в Intellij IDEA
  6. Выводы

Создаём Spring проект на Java

Для этого воспользуемся сервисом быстрого создания Spring Initializr: он предоставляет интерфейс для генерации заготовки проекта с добавлением стандартных зависимостей. При необходимости в дальнейшем их можно настроить под свои нужды.

Мои настройки Spring Initializr выглядят так:

Обратите внимание на кнопку Add Dependencies: с её помощью можно добавить важные зависимости уже на старте.

После того, как вы всё указали, нажмите Generate, разархивируйте стартовый проект и откройте его с помощью удобной IDE. У меня это IntelliJ IDEA.

Реализация базового функционала

Для начала напишем на Java самый примитивный Telegram bot, который будет отвечать на наши сообщения.

Создание Telegram-бота и конфигурация

Начнём с того, что это Maven-проект. Сразу добавим в pom.xml дополнительные зависимости для работы с Телеграм ботом и базами данных:

  • Telegram Bots
  • Hibernate Core Relocation
  • PostgreSQL JDBC Driver
  • Lombok

В каталоге resources создадим файл config.properties , где будут храниться данные для подключения к боту и в будущем к БД.

Примечание Данный файл не следует включать в коммиты.

Теперь создадим бота. Для этого перейдём в Telegram в BotFather и создадим нового бота командой /newbot . Выбираем для него название, которое будет отображаться для всех, и его username. После этого BotFather выдаст токен для взаимодействия с бэкендом Телеграмма.

Теперь запишем в файл config.properties следующее:

bot.name = юзернейм_вашего_бота bot.token = токен_вашего_бота bot.chatId = id_нужного_чата 

Добавим в основной каталог проекта пакет config , а внутри него создадим новый класс BotConfig .

Вы наверняка заметили, что мы добавили в pom.xml Lombok. Это популярная библиотека для сокращения кода и расширения функциональности Java. С ней и Spring наш класс BotConfig будет выглядеть очень лаконично:

@Configuration @Data @PropertySource("config.properties") public class BotConfig < @Value("$") String botName; @Value("$") String token; @Value("$") String chatId; > 

Что здесь происходит?

  1. @Configuration указывает, что класс содержит методы определения @Bean (наши @Value ).
  2. @Data на этапе компиляции генерирует для всех полей геттеры, сеттеры, toString и предопределяет equals и hashCode.

С остальным, думаю, всё понятно.

Класс Телеграм бота на Java

Давайте теперь выйдем из пакета config и создадим в основном пакете проекта класс бота. Поскольку это бот-счётчик, назовём его CounterTelegramBot.

Сразу унаследуемся от TelegramLongPollingBot — класса, который позволяет взаимодействовать с Telegram. И имплементируем методы getBotUsername , getBotToken и onUpdateReceived . Создадим конструктор и добавим две аннотации перед классом: @Component (авто-создание экземпляра) и @Slf4j (для работы с логером).

На старте получаем следующий класс:

@Slf4j @Component public class CounterTelegramBot extends TelegramLongPollingBot < final BotConfig config; public CounterTelegramBot(BotConfig config) < this.config = config; >@Override public String getBotUsername() < return config.getBotName(); >@Override public String getBotToken() < return config.getToken(); >@Override public void onUpdateReceived(@NotNull Update update) <> > 

Для начала сделаем так, чтобы на команду /start Telegram-бот что-то нам отвечал и выводил в логи сообщение об успехе. Другие сообщения будут выводить в логи «Unexpected message» :

@Override public void onUpdateReceived(@NotNull Update update) < if(update.hasMessage() && update.getMessage().hasText())< String messageText = update.getMessage().getText(); long chatId = update.getMessage().getChatId(); String memberName = update.getMessage().getFrom().getFirstName(); switch (messageText)< case "/start": startBot(chatId, memberName); break; default: log.info("Unexpected message"); >> > private void startBot(long chatId, String userName) < SendMessage message = new SendMessage(); message.setChatId(chatId); message.setText("Hello, " + userName + "! I'm a Telegram bot."); try < execute(message); log.info("Reply sent"); >catch (TelegramApiException e) < log.error(e.getMessage()); >> 

И последним штрихом является инициализация бота. Добавим в пакет config класс Initializer :

@Slf4j @Component public class Initializer < @Autowired CounterTelegramBot bot; @EventListener() public void init() < try < TelegramBotsApi telegramBotsApi = new TelegramBotsApi(DefaultBotSession.class); telegramBotsApi.registerBot((LongPollingBot) bot); >catch (TelegramApiException e) < log.error(e.getMessage()); >> 
  1. @Autowired обеспечивает контроль над тем, где и как осуществить автосвязывание (чтобы Spring автоматически подключил бота).
  2. @EventListener — слушатель, который вешаем на изменение класса.

Запустите и проверьте работу бота.

Примечание Если возникает ошибка Failed to configure a DataSource , измените аннотацию в исполняемом классе на @SpringBootApplication(exclude = ) . Ошибка исчезнет, как только мы добавим информацию для доступа к БД в config.properties .

Добавление кнопок

Чтобы Telegram bot на Java и Spring Boot выглядел по-настоящему серьёзным, давайте добавим ему команду /help и пару кнопок.

Создадим в основной директории проекта пакет components . В него добавим:

1. Интерфейс BotCommands :

public interface BotCommands < ListLIST_OF_COMMANDS = List.of( new BotCommand("/start", "start bot"), new BotCommand("/help", "bot info") ); String HELP_TEXT = "This bot will help to count the number of messages in the chat. " + "The following commands are available to you:\n\n" + "/start - start the bot\n" + "/help - help menu"; > 

2. Класс Buttons :

public class Buttons < private static final InlineKeyboardButton START_BUTTON = new InlineKeyboardButton("Start"); private static final InlineKeyboardButton HELP_BUTTON = new InlineKeyboardButton("Help"); public static InlineKeyboardMarkup inlineMarkup() < START_BUTTON.setCallbackData("/start"); HELP_BUTTON.setCallbackData("/help"); ListrowInline = List.of(START_BUTTON, HELP_BUTTON); List rowsInLine = List.of(rowInline); InlineKeyboardMarkup markupInline = new InlineKeyboardMarkup(); markupInline.setKeyboard(rowsInLine); return markupInline; > > 

В классе мы создаём две кнопки, которые будут расположены в одной линии. Одна из них отвечает за команду старта, а вторая — за вызов меню помощи.

Теперь немного улучшим класс CounterTelegramBot :

@Slf4j @Component public class CounterTelegramBot extends TelegramLongPollingBot implements BotCommands < final BotConfig config; public CounterTelegramBot(BotConfig config) < this.config = config; try < this.execute(new SetMyCommands(LIST_OF_COMMANDS, new BotCommandScopeDefault(), null)); >catch (TelegramApiException e) < log.error(e.getMessage()); >> @Override public String getBotUsername() < return config.getBotName(); >@Override public String getBotToken() < return config.getToken(); >@Override public void onUpdateReceived(@NotNull Update update) < long chatId = 0; long userId = 0; //это нам понадобится позже String userName = null; String receivedMessage; //если получено сообщение текстом if(update.hasMessage()) < chatId = update.getMessage().getChatId(); userId = update.getMessage().getFrom().getId(); userName = update.getMessage().getFrom().getFirstName(); if (update.getMessage().hasText()) < receivedMessage = update.getMessage().getText(); botAnswerUtils(receivedMessage, chatId, userName); >//если нажата одна из кнопок бота > else if (update.hasCallbackQuery()) < chatId = update.getCallbackQuery().getMessage().getChatId(); userId = update.getCallbackQuery().getFrom().getId(); userName = update.getCallbackQuery().getFrom().getFirstName(); receivedMessage = update.getCallbackQuery().getData(); botAnswerUtils(receivedMessage, chatId, userName); >> private void botAnswerUtils(String receivedMessage, long chatId, String userName) < switch (receivedMessage)< case "/start": startBot(chatId, userName); break; case "/help": sendHelpText(chatId, HELP_TEXT); break; default: break; >> private void startBot(long chatId, String userName) < SendMessage message = new SendMessage(); message.setChatId(chatId); message.setText("Hi, " + userName + "! I'm a Telegram bot.'"); message.setReplyMarkup(Buttons.inlineMarkup()); try < execute(message); log.info("Reply sent"); >catch (TelegramApiException e) < log.error(e.getMessage()); >> private void sendHelpText(long chatId, String textToSend) < SendMessage message = new SendMessage(); message.setChatId(chatId); message.setText(textToSend); try < execute(message); log.info("Reply sent"); >catch (TelegramApiException e) < log.error(e.getMessage()); >> > 

switch вынесли в отдельный метод, добавили обработку команд, в том числе и нажатие кнопок.

Подключение Telegram-бота на Java к базе данных

Перед началом работы установите PostgerSQL, если СУБД ещё не установлена. В случае, если вы работаете с другими СУБД, просто измените настройки доступа в файле config.properties. Для тех же, кто работает с PostgerSQL, config.properties будет выглядеть примерно так:

bot.name = юзернейм_вашего_бота bot.token = токен_вашего_бота bot.chatId = id_нужного_чата #db related settings spring.jpa.database = PostgreSQL spring.jpa.show-sql = false # для автоматического создания/обновления таблицы в бд spring.jpa.hibernate.ddl-auto = update spring.datasource.driverClassName = org.postgresql.Driver # ниже прописываете порт и название бд spring.datasource.url = jdbc:postgresql://localhost:5432/tg # ваши кредлы для доступа к бд spring.datasource.username = postgres spring.datasource.password = root 

В директорию проекта добавляем пакет database . В нём следует создать:

@Data @Entity(name = "tg_data") //привязываемся к существующей таблице с готовыми колонками public class User < @Id private long id; //BigInt private String name; //Text private int msg_numb; //Integer > 

2. Интерфейс UserRepository :

public interface UserRepository extends CrudRepository

Данный интерфейс нам нужен для удобной работы с CrudRepository — интерфейсом данных Spring для общих операций CRUD. Сюда же вшиваем запрос на апдейт нашей таблицы: добавление +1 сообщения пользователю в случае, если он написал в чат.

В классе CounterTelegramBot объявим новый интерфейс с аннотацией @Autowired , которая говорит Spring, что в это поле нужно инжектнуть бин:

@Autowired private UserRepository userRepository; 

Там же создаём метод добавления пользователя в базу данных, если он написал впервые, и просто обновление столбца сообщений, если пользователь уже существует:

private void updateDB(long userId, String userName) < if(userRepository.findById(userId).isEmpty())< User user = new User(); user.setId(userId); user.setName(userName); //сразу добавляем в столбец каунтера 1 сообщение user.setMsg_numb(1); userRepository.save(user); log.info("Added to DB: " + user); >else < userRepository.updateMsgNumberByUserId(userId); >> 

Финально обновим метод onUpdateReceived в классе CounterTelegramBot :

@Override public void onUpdateReceived(@NotNull Update update) < long chatId = 0; long userId = 0; String userName = null; String receivedMessage; if(update.hasMessage()) < chatId = update.getMessage().getChatId(); userId = update.getMessage().getFrom().getId(); userName = update.getMessage().getFrom().getFirstName(); if (update.getMessage().hasText()) < receivedMessage = update.getMessage().getText(); botAnswerUtils(receivedMessage, chatId, userName); >> else if (update.hasCallbackQuery()) < chatId = update.getCallbackQuery().getMessage().getChatId(); userId = update.getCallbackQuery().getFrom().getId(); userName = update.getCallbackQuery().getFrom().getFirstName(); receivedMessage = update.getCallbackQuery().getData(); botAnswerUtils(receivedMessage, chatId, userName); >if(chatId == Long.valueOf(config.getChatId())) < updateDB(userId, userName); >> 

Примечание Вы можете не делать ограничение по chatId , но тогда следует дополнительно прописать логику для создания отдельной таблицы под каждый чат. В моём случае бот писался под конкретный чат.

Важно Не забудьте предоставить боту права администратора чата.

Создание исполняемого jar-файла в Intellij IDEA

У Telegram API есть одно неприятное ограничение, в соответствии с которым наш бот на Java позволяет достучаться только до сообщений, отправленных за последние 24 часа. Всё, что было отправлено раньше, не учтётся.

Поэтому после вы можете либо создать exe-файл с установкой времени выполнения, либо воспользоваться удалённым сервером. Например, в статье о Telegram-боте на Python мы рассказали, как настроить Docker и задеплоить бота на AWS.

Здесь же я просто покажу, как создать исполняемый jar-файл для ручного запуска. Костыльно, но для периодического подсчёта из конкретного чата подходит, а далее можно масштабировать по своему усмотрению.

Инструкция по созданию jar-файла:

  1. File – Project Structure – Project Settings – Artifacts – Кликаем по кнопке + – Jar – From modules with dependencies.
  2. Выбираем главный класс проекта и жмем ОK.
  3. После этого собираем Jar файл: Build – Build Artifact.
  4. Это создаст .jar, который при двойном клике запустит JVM, если она установлена в ОС.

На первом же скрине вы можете посмотреть структуру проекта.

Выводы

Создание Telegram-бота на Java возможно благодаря специальному классу TelegramLongPollingBot , а Spring Boot и Lombok сильно упрощают этот процесс.

Но стоит отметить, что тот же бот, написанный на Python или PHP, обойдётся вам в меньшее количество строк кода, да и туториалов по таким Телеграм-ботам значительно больше. А вот в качестве практики Java и небольшого пет-проекта, который можно представить в своём резюме, такая программа вполне подойдёт.

Остались вопросы? Задавайте их в комментариях к этой статье.

Следите за новыми постами по любимым темам

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

Особенности создания телеграм-бота на Java

В статье пойдет разговор о том, что такое боты, для чего они используются, как работают и чем отличаются от обычных аккаунтов. Также рассмотрим порядок создания телеграм-бота на «Джава».

Ботами (bot) и чат ботами (chat bots) называют специальные аккаунты в Телеграмм, используемые для автоматической обработки и отправки сообщений. На практике пользователи взаимодействуют с ботами посредством сообщений, которые они отправляют как через обычные, так и через групповые чаты. Бот работает по определенной логике — она контролируется с помощью HTTPS-запросов к специальному API для ботов от Телеграм.

Возможности ботов

Приведем несколько классических примеров применения ботов в Телеграмм:

  1. Утилиты и инструменты. Телеграм-бот может переводить тексты, отображать актуальную погоду, предупреждать о каких-либо предстоящих событиях, использоваться для проведения опросов.
  2. Интеграция с сервисами. Бота можно использовать для отправки комментариев либо уведомлений, управления «умным домом».
  3. Игры (как одно-, так и многопользовательские). Бот без проблем поиграет с вами в шахматы/шашки, проведет викторину и т. п.
  4. Социальные сервисы. При необходимости специальный бот найдет вам собеседника, взяв за основу для поиска ваши интересы и увлечения.
  5. Все остальное. Это «все остальное» ограничивается лишь вашей фантазией. На деле вы можете запрограммировать бота практически для чего угодно. Однако стоит понимать, что он все равно останется ботом, а значит, не сможет помыть посуду вместо вас.

Если резюмировать вышеперечисленное одним предложением, то бот в Телеграм умеет оставлять комментарии к записям и постам, переводить тексты, искать информацию и аудио/видеоданные, спрашивать и отвечать на вопросы, подключаться к сети, обходить блокировки роутеров и сервисов, создавать чаты для общения, транслировать презентации и т. д. — всего не перечислишь.

Как функционируют боты?

Боты — особые аккаунты, по сути, представляющие собой интерфейс к вашему сервису, работающему на удаленном сервере. Плюс в том, что для создания бота вам совершенно не обязательно изучать низкоуровневые технологии, так как все взаимодействие основано на обычном HTTPS-интерфейсе с упрощенными методами API — его называют Bot API .

В реальности вы можете создать бота в Телеграмм с помощью… бота. Для этого потребуется написать пользователю @BotFather , а потом следовать его инструкциям. После создания вы получите специальный ключ авторизации (токен). Выполнить необходимые настройки можно будет в разделе документации Bot API .

Особенности создания бота с помощью Java

Если вы не ищете легких путей, хотите прокачаться в Java и привыкли все творить своими руками, вы можете написать бота, используя язык программирования Java («Джава», «Ява»). Ниже рассмотрим один из возможных алгоритмов действий.

Пишем бот на Java

На деле написать бота для Телеграмм, используя Java, не так уже сложно. Рассмотрим пример создания бота посредством Webhook.

Общая последовательность действий будет следующей:

  1. Открываем «Эклипс», создаем новый Java-проект.
  2. Находим и загружаем базу, необходимую для создания Telegram-ботов.
  3. Импортируем загруженную библиотеку в проект.
  4. Создаем класс test.SimpleBot со следующим содержимым:

— открытие веб-браузера, переход по ссылке: https://telegram.me/botfather;

— нажатие кнопки «Send message»;

— выбор BotFather в Телеграме;

— ввод имени бота на Webhook.

Также надо будет придумать имя пользователя для вновь созданного бота. Тут главное, чтобы это имя было уникальным. После ввода имени надо будет нажать кнопку подтверждения, в результате чего появится сообщение об успешной конфигурации. Обратите внимание, что после «Use this token to access the HTTP API:» выведется ваш токен, который надо будет ввести в требуемом месте.

  1. Переходим в «Эклипс», запускаем бота.
  2. В адресной строке веб-браузера набираем https://telegram.me/имя_вашего_бота (это необходимо для тестирования работоспособности).
  3. Нажимаем «Send message».
  4. Возвращаемся в Телеграм, выбираем созданного бота.
  5. Кликаем «Старт».

Все, Telegram-bot Webhook, написанный на «Джава», готов. На данном этапе на любое обращение робот должен отвечать что-то в стиле «Я не знаю, что ответить на это», однако эту фразу можно поменять путем дополнения базы.

Каковы плюсы Telegram-бота на Java

Можно перечислить ряд преимуществ такой реализации:

  1. Простота.
  2. Минимум выполняемых операций.
  3. Минимум требуемых знаний и умений.

В сети вы можете найти целый спектр уже готовых решений в виде программного кода, поэтому написать бота на Java будет не сложно. Вот, к примеру, полезное обучающее видео , где подробно рассказывается о том, как создать погодного бота на «Джава». Преимущество именно этого решения — легкость, доступность, простота создания. Дерзайте!

  • https://stelegram.ru/faq/pravila-sozdaniya-telegramm-bota-na-java;
  • https://tlgrm.ru/docs/bots.

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *