Телеграм-бот на Java + деплой в Railway: простой старт

Хотелось попробовать что-нибудь новое в программировании и я задумался: а как делать телеграм-ботов? Их нынче так много, и некоторые из них даже очень полезные и не кривые. Это должно быть не слишком сложно, – подумал я и принялся изучать вопрос. В этой статье и расскажу, как сделать телеграм-бот на Java (даже не ожидал, что на джаве их тоже можно писать).

Описание проекта

В этой статье создам простого бота, который для начала просто будет повторять за вами ваши слова. Позже придумаем какую-нибудь «логику» для реакции на разные команды пользователя (а не только на стандартную команду /start).

Проект пишу на Java, размещать его буду на GitHub и оттуда уже .jar файл запускать на railway (о том, как работать с этим сервисом, уже писал).

Создание бота с @BotFather

Для начала нужно создать бот в телеграме с помощью @BotFather. Отец всех ботов подскажет, что делать, но по шагам (вдруг вы, как и я, не особо часто пользуетесь телегой) требуется:

  1. Запустить бот командой /start
  2. Выбрать создание нового бота командой /newbot.
  3. Написать имя бота – то, под каким названием он будет появляться в списке чатов пользователя.
  4. Задать юзернейм бота – то, что будет появляться после @.
  5. Прочитать поздравления от BotFather. В сообщении будет токен, который нам пригодится. Его нужно сохранить и не показывать никому, чтобы случайно не дать к боту несанкционированный доступ.

Код для бота-попугая на Java (без Spring)

Для работы с Telegram API уже существуют готовые библиотеки. Я буду использовать TelegramBots от rubenlagus.

Для сборки проектов я использую Maven, и поэтому в новом джава-проекте в файл pom.xml добавляю необходимую зависимость:

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

Теперь можно создать класс MyBot, в котором будет прописана «логика». Класс является потомком TelegramLongPollingBot и переопределяет несколько его методов.

getBotUsername() возвращает юзернейм бота (должен в точности совпадать с тем, что вы ввели в BotFather)

getBotToken() должен возвращать токен доступа. Но раскрывать его было бы плохой практикой, поэтому вместо самого токена пока верну переменную BOT_TOKEN: System.getenv(«BOT_TOKEN»). Позже в railway добавим эту переменную среды, чтобы не возвращался null и всё работало. Если вы запускаете проект локально, то эту переменную необходимо добавить в переменные среды.

onUpdateReceived() вызывается каждый раз, когда пользователь выполняет какое-то действие: пишет сообщение, команду, нажимает на кнопки. Это основная точка входа, где мы пишем логику.

В итоге класс выглядит следующим образом:

public class MyBot extends TelegramLongPollingBot {

    @Override
    public String getBotUsername() {
        return "MyBot"; 
    }

    @Override
    public String getBotToken() {
        return System.getenv("BOT_TOKEN"); 
    }

    @Override
    public void onUpdateReceived(Update update) {
        if (update.hasMessage() && update.getMessage().hasText()) {
            String message = update.getMessage().getText();
            long chatId = update.getMessage().getChatId();

            SendMessage response = new SendMessage();
            response.setChatId(String.valueOf(chatId));
            response.setText("Ты написал: " + message);

            try {
                execute(response);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
}

В строчке update.hasMessage() && update.getMessage().hasText() мы проверяем, действительно ли пользователь прислал текст. Ведь он может прислать стикер или фото – тогда мы его проигнорируем.

Из апдейта мы достаём chatId – уникальный идентификатор чата, который нужен, чтобы отправить ответ туда же (ведь наш бот одновременно может использоваться не одним пользователем).

И в конце мы создаём объект SendMessage, в котором указывает этот chatId (куда отправлять) и текст (что отправлять).

Отправляем это сообщение через метод execute(). Telegram API может выдать ошибку (например, если токен неверный), поэтому оборачиваем это в try/catch.

Метод execute – это ключевой способ отправить запрос к Telegram Bot API. Он используется, чтобы:

  • отправить сообщение (SendMessage)
  • удалить сообщение (DeleteMessage)
  • редактировать сообщение (EditMessageText, EditMessageReplyMarkup)
  • отправить фото, стикер и т.д.

Теперь создадим главный класс Main. Именно здесь мы подключаемся к Telegram API и регистрируем нашего бота.

public class Main {
    public static void main(String[] args) {
        try {
            TelegramBotsApi botsApi = new TelegramBotsApi(DefaultBotSession.class);
            botsApi.registerBot(new MyBot());
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

Создаём объект TelegramBotsApi, передавая DefaultBotSession.class. Бот будет использовать стандартную сессию на long polling, то есть будет постоянно опрашивать сервер Telegram на наличие новых обновлений.

Регистрируем нашего бота в Telegram API с botsApi.registerBot(). После этой строки бот начинает слушать входящие сообщения и реагировать через метод onUpdateReceived().

В итоге мы получили код, который считывает сообщение пользователя и возвращает его обратно. Теперь нужно проект собрать в .jar файл. Для этого я пишу следующее:

<build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.10.1</version>
                <configuration>
                    <release>16</release>
                </configuration>
            </plugin>

            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-assembly-plugin</artifactId>
                <version>3.3.0</version>
                <configuration>
                    <descriptorRefs>
                        <descriptorRef>jar-with-dependencies</descriptorRef>
                    </descriptorRefs>
                    <archive>
                        <manifest>
                            <mainClass>Main</mainClass>
                        </manifest>
                    </archive>
                </configuration>
                <executions>
                    <execution>
                        <id>make-assembly</id>
                        <phase>package</phase>
                        <goals>
                            <goal>single</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>

Если ваша версия джавы отличается (я использую 16-ю джаву – не спрашивайте, почему), то не забудьте поменять номер релиза. Если название главного класса (где находится метод main()) тоже другое, то не забудьте изменить <mainClass>Main</mainClass>.

После этого собираем проект: mvn clean package. В папке target должен появиться наш .jar файл с зависимостями. Он нам и нужен для запуска на railway, поэтому при загрузке на github кода не добавляйте весь /target в файл .gitignore.

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

После этого заходим на вкладку Variables и добавляем переменную BOT_TOKEN, которую мы хотели использовать ранее. Значение переменной должно в точности повторять токен, который выдал BotFather.


Опционально. На всякий случай пропишем ещё одну команду для запуска нужного .jar. Открываем вкладку Settings, находим там Start Command и пишем:

java -jar target/mybot-1.0-jar-with-dependencies.jar

Если имя .jar-файла откличается, то замените его – оно должно быть верным, чтобы railway запустил нужный архив.


После этого снова делаем деплой, и может проверять наш бот. Он должен отвечать вам. Готово!

Добавление команд к боту

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

Сделаем так, чтобы пользователь мог писать не только текст, но и команды (они в телеграме начинаются со слэша / ). По команде /question бот будет возвращать тот же текст, но с вопросительным знаком. По команде /exclaim – с восклицательным. Конечно, это не самое умное поведение, но неплохо покажет, как работать с разными командами.

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

    @Override
    public void onUpdateReceived(Update update) {
        if (update.hasMessage() && update.getMessage().hasText()) {
            String message = update.getMessage().getText();
            long chatId = update.getMessage().getChatId();

            String reply;

            if (message.startsWith("/question")) {
                String text = message.replaceFirst("/question\\s*", "");
                reply = text + "?";
            } else if (message.startsWith("/exclaim")) {
                String text = message.replaceFirst("/exclaim\\s*", "");
                reply = text + "!";
            } else {
                reply = "Ты написал: " + message;
            }

            SendMessage response = new SendMessage();
            response.setChatId(String.valueOf(chatId));
            response.setText(reply);

            try {
                execute(response);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

В коде мы просто убираем команду из начала строки (заменяем её со всеми последующими пробелами на пустую строку).

Теперь бот в ответ на /question ты дурак спросит ты дурак? А в ответ на /exclaim вау закричит вау!


Всё довольно просто, но со временем можно добавлять больше команд, или реализовать более сложную логику (забирать часть данных на хранение). Сегодня остановимся на этом достижении, а в следующей статье покажу, как добавлять кнопки меню (чтобы пользователю было удобнее ориентироваться в возможных действиях в боте) и начнём хранить информацию о его действиях (например, для поэтапного ввода данных – и бот при этом не будет забывать, на каком этапе мы находимся).

Оставьте комментарий