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

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

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

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

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

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

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

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

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

		<dependency>
			<groupId>org.telegram</groupId>
			<artifactId>telegrambots</artifactId>
			<version>6.5.0</version>
		</dependency>

		<dependency>
			<groupId>org.projectlombok</groupId>
			<artifactId>lombok</artifactId>
			<version>1.18.26</version>
			<scope>provided</scope>
		</dependency>

    	<dependency>
			<groupId>org.json</groupId>
			<artifactId>json</artifactId>
			<version>20220924</version>
		</dependency>

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

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

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

@Configuration
@Data
@PropertySource("application.properties")
public class BotConfig {
    @Value("${bot.name}")
    String botName;
    @Value("${bot.token}")
    String token;
}

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

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

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

@Data
public class CurrencyModel {
    Integer cur_ID;
    Date date;
    String cur_Abbreviation;
    Integer cur_Scale;
    String cur_Name;
    Double cur_OfficialRate;
}

Далее создадим класс 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({ContextRefreshedEvent.class})
    public void init()throws TelegramApiException{
        TelegramBotsApi telegramBotsApi = new TelegramBotsApi(DefaultBotSession.class);
        try{
            telegramBotsApi.registerBot(telegramBot);
        } catch (TelegramApiException e){

        }
    }
}

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

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

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

Запускаем проект.

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

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

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

Комментарии (8)


  1. HemulGM
    00.00.0000 00:00

    А я уместился в 52 строки (+14 строк модель) на Delphi. Но это вряд ли кому-то интересно. Что вы хотели этой статьей сказать?

    Код
    program TGBOT_CURRENCY;
    
    uses
      System.SysUtils, REST.Json, TgBotApi.Client, TgBotApi;
    
    begin
      Client := TtgClient.Create({$INCLUDE BOT_TOKEN.key});
    
      Client.Subscribe(
        function(Update: TtgUpdate): Boolean
        begin
          var Answer := 'Hi, ' + Update.Message.Chat.FirstName + ', nice to meet you!' + #13#10 +
            'Enter the currency whose official exchange rate' + #13#10 +
            'you want to know in relation to BYN.' + #13#10 +
            'For example: USD';
    
          Client.SendMessageToChat(Update.Message.Chat.Id, Answer);
          Result := True;
        end, '/start');
    
      Client.Subscribe(
        function(Update: TtgUpdate): Boolean
        begin
          if Update.Message.Text.IsEmpty then
            Exit(False);
          var Answer := '';
          try
            Answer := Client.Get('https://www.nbrb.by/api/exrates/rates/' + Update.Message.Text + '?parammode=2');
            var Model := TJson.JsonToObject<TCurrencyModel>(Answer);
            if not Assigned(Model) then
              raise Exception.Create('JSON is empty');
            try
              Answer := 'Official rate of BYN to ' + Model.CurAbbreviation + #13#10 +
                'on the date: ' + FormatDateTime('c', Model.Date) + #13#10 +
                'is: ' + Model.CurOfficialRate.ToString + ' BYN per ' + Model.CurScale.ToString + ' ' + Model.CurAbbreviation;
            finally
              Model.Free;
            end;
          except
            Answer :=
              'We have not found such a currency.' + #13#10 +
              'Enter the currency whose official exchange rate' + #13#10 +
              'you want to know in relation to BYN.' + #13#10 +
              'For example: USD';
          end;
          Client.SendMessageToChat(Update.Message.Chat.Id, Answer);
          Result := True;
        end);
    
      Client.Polling;
      Client.Free;
    end.


    1. MiSta1984 Автор
      00.00.0000 00:00

      Спасибо за комментарий. Классный пример на Delphi


  1. LeshaRB
    00.00.0000 00:00
    -2

    Ты уже во всех чатах Беларуси со своим ботом отметился, и решил на Хабр выйти?!

    Так сказать российский рынок подмять....


    1. MiSta1984 Автор
      00.00.0000 00:00

      Спасибо за комментарий. Данное API для курсов валют было использовано, так как до него можно достучаться без каких-либо дополнительных регистраций, и исключительно для примера. Никакой "Теории заговора" тут нет)))


  1. Davidchanz
    00.00.0000 00:00
    +1

    Спасибо) мне понравилась статья.


    1. MiSta1984 Автор
      00.00.0000 00:00

      Спасибо за комментарий))


  1. Kubig
    00.00.0000 00:00

    Спасибо за статью.

    Нет желание развить тему с кнопочками?


    1. MiSta1984 Автор
      00.00.0000 00:00

      Спасибо за комментарий. Пока в планах нет такого, но я подумаю))) Хотя тут уже есть каркас, на который при желании и с учетом того, что хотите получить на выходе, можно дальше "пилить": кнопки, базу данных подключать и т.д.