Будуємо телеграм чат-бот на Java: від ідеї до деплою. Частина 1
Усі статті, обговорення, новини для початківців — в одному місці. Підписуйтеся на телеграм-канал!
Усім привіт! Мене звати Тарас. Приблизно два роки тому я познайомився з розробкою телеграм-ботів і від того часу, окрім основної роботи (розробка на Java), у вільний час пишу чат-ботів. Це стало моїм хобі. Розробляти їх приносить велике задоволення, оскільки це цікаво. Також дуже приємно бачити, коли вони приносять користь, люди дають відгуки і т. д.
Маю за спиною кілька розроблених чат-ботів, один з них призначений для внутрішніх цілей компанії (розробляли разом з невеликою командою), а також бот для волонтерських потреб.
Телеграм чат-боти мали певну популярність і раніше, проте останнім часом ця тема набула ще більшої актуальності. Про телеграм-ботів дізналася набагато ширша аудиторія, оскільки з’явилася велика кількість різних корисних чат-ботів (єВорог, Stop Russian War bot, Шось летить та інші). Якщо вам цікаво дізнатися, як такі чат-боти реалізовані «під капотом», я спробую про це розказати, а також показати, що розробляти їх не складно і цікаво.
Або ж якщо ви уже маєте певний досвід у розробці ботів, ввважаю, що ви також знайдете тут для себе щось корисне.
У статті поділюся своїми знаннями і разом подивимося, як можна побудувати простого чат-бота на Java. Також надам деякі поради і розкрию нюанси, які можуть виникнути при розробці. А в наступній статті покажу, як швидко і безкоштовно задеплоїти бота на віддалений сервер, використовуючи Heroku, а також з додаванням мінімального CI/СD на BitBucket. У кінці залишу посилання на корисних чат-ботів, якими сам користуюся і з яких можна черпати ідеї при розробці.
Чи варто читати статтю, якщо ви не Java-розробник
Зазначу, що хоча для розробки бота використовуватимемо Java, але концепції будуть досить загальні і ви зможете конвертувати знання на будь-яку іншу мову. На деяких мовах будувати ботів буде простіше, з меншою кількістю коду.
Якщо ви, наприклад, Python-розробник, то одразу скажу, що з використанням Python бот будується досить просто, оскільки сама мова мінімалістична і бібліотеки надають більш ширші можливості, яких не має в бібліотеках для Java. Але якщо вже ваша доля склалася так, що ви Java-розробник, то доведеться пописати трошки більше коду 😄
Розроблятимемо звичайного бота (без NLP). Це буде так званий кнопочний варіант, який зараз є найпопулярнішим в телеграмі і з якими ви, скоріше за все, стикалися.
Перш ніж почати розробку..
Вибір бібліотеки
Думаю, більшість погодиться з тим, що телеграм є найзручнішим месенджером. Те ж саме стосується можливостей платформи для розробки чат-ботів.
Для розробки використовуватимемо найпопулярнішу Java бібліотеку для телеграм-ботів (їх насправді всього дві).
Якщо судити за кількістю бібліотек для телеграму, то лідирують PHP і Node.js. Також знаю, що у Python є хороші функціональні бібліотеки.
Є офіційне Telegram Bot API, а є бібліотеки, які ці запити до API огортають і додають власний функціонал. Крутість бібліотеки полягає в тому, наскільки зручно на ній розробляти (якщо не знаєте, яку обрати, то беріть найпопулярнішу, скоріше за все вона буде найфункціональнішою і простіше буде знайти відповідь в інтернеті, якщо стикнетеся з якоюсь проблемою).
Схема комунікації між ботом і користувачем
Перед тим як почати розробку, варто зрозуміти, як відбувається комунікація користувача з ботом. На діаграмі нижче можна спрощено побачити, що відбувається, коли користувач пише «Привіт» і бот відповідає «Привіт, як справи».
Прямокутником виділена та частина, яку ми розроблятимемо. Наше завдання буде полягати в опрацюванні вхідних запитів від користувача і формулюванні відповіді.
Починаємо розробку
Реєструємо бота в Телеграмі
Перш за все необхідно зареєструвати бота в телеграмі. Для цього використовуємо спеціального бота BotFather 😎 Виконуємо команду з меню /newbot і дотримуємося вказівок бота. Відкладаємо токен в сторону, він нам буде потрібен на наступних кроках.
Створюємо і налаштовуємо простого чат-бота
Після реєстрації чат-бота, приступимо, безспосередньо до створення проєкту і налаштувань. Код можна знайти на GitHub.
1. Створюємо новий проєкт з використанням Java 11 і maven.
2. Додаємо бібліотеку TelegramBots.
<dependency> <groupId>org.telegram</groupId> <artifactId>telegrambots</artifactId> <version>6.0.1</version> </dependency>
3. Додаємо клас SimpleEchoBot
і передаємо username і bot-token в відповідні методи (звісно, в майбутньому, передавайте токен більш захищено, наприклад, через env variables
).
Тут також важливо зазначити, що бібліотека підтримує два способи отримування повідомлень від сервера телеграму (long polling або ж webhook). Я використовував long polling варіант в чат-ботах, оскільки він найбільш популярний і є досить ефективним механізмом. Різниця між методами описана тут.
public class SimpleEchoBot extends TelegramLongPollingBot { @Override public void onUpdateReceived(Update update) { } @Override public String getBotUsername() { return "your_bot_username "; } @Override public String getBotToken() { return "bot_token"; } }
4. Створюємо метод main, в якому зареєструємо нашого бота на отримання повідомлень.
Також додаємо необхідні залежності у pom.xml для можливості виводити логи у консоль.
public class Application { private static final Logger log = LoggerFactory.getLogger(Application.class); public static void main(String[] args) throws TelegramApiException { TelegramBotsApi telegramBotsApi = new TelegramBotsApi(DefaultBotSession.class); try { log.info("Registering bot..."); telegramBotsApi.registerBot(new SimpleEchoBot()); } catch (TelegramApiRequestException e) { log.error("Failed to register bot(check internet connection / bot token or make sure only one instance of bot is running).", e); } log.info("Telegram bot is ready to accept updates from user......"); } }
5. Після цього структура проєкту виглядатиме наступним чином:
6. Запускаємо main
метод і якщо ви все зробили правильно, то мої вітання, ваш перший бот запущений!
7. Тепер перейдіть до вашого бота і спробуйте написати будь-який текст.
Звісно, бот нічого не відповість, але ми зараз це виправимо :)
9. Звернемо увагу на метод onUpdateReceived
. Параметром цього методу є об’єкт Update
. Це дані, які телеграм-сервер надсилає для обробки чат-ботом і там містяться такі поля як
@Override public void onUpdateReceived(Update update) { if (update.hasMessage() && update.getMessage().hasText()) { String textFromUser = update.getMessage().getText(); Long userId = update.getMessage().getChatId(); String userFirstName = update.getMessage().getFrom().getFirstName(); log.info("[{}, {}] : {}", userId, userFirstName, textFromUser); SendMessage sendMessage = SendMessage.builder() .chatId(userId.toString()) .text("Hello, I've received your text: " + textFromUser) .build(); try { this.sendApiMethod(sendMessage); } catch (TelegramApiException e) { log.error("Exception when sending message: ", e); } } else { log.warn("Unexpected update from user"); } }
Важливі моменти, на які варто звернути увагу:
- При відправці повідомлення використовується метод sendApiMethod, який дозволяє виконати API запит на телеграм.
- Також зверніть увагу на об’єкт
SendMessage
, в який ми передаєм такі поля як chatId, text. - Ідеологія цієї Java-бібліотеки наступна: на кожен API-запит (список доступних API запитів) бібліотека має свою обгортку, наприклад:
- sendMessage → SendMessage
- editMessage → EditMessage
10. В результаті отримуємо наступний функціонал:
Будуємо функціональнішого бота
А тепер спробуємо трохи розширити бота додатковим функціоналом, кнопками і т. д.
Коротко про варіанти комунікації користувача з ботом
Перед тим як почнемо розширяти функціонал, потрібно зазначити, що є певний перелік варіантів, як користувач може комунікувати з ботом:
- Пишучи текст.
- Відправляючи команду яка починається з «/» (наприклад «/info»).
- Натискаючи на кнопки, які йдуть під повідомленням (
InlineKeyboard
). - Натискаючи на кнопки, які розміщені внизу, під полем для вводу (
ReplyKeyboard
). - Прикріпляючи фото/відео.
- Та інші способи.
Почнемо розробку бота для волонтерської допомоги
Для прикладу побудуємо невеличкого чат-бота для волонтерської допомоги, який буде фіксувати запити від людей (код доступний на GitHub) з підключенням Spring boot. Ось як він виглядатиме:
У бота буде два діалоги, які він підтримуватиме:
- «Start» — це базова команда для усіх чат-ботів, яка виконується завжди, як тільки користувач відкриває діалог з ботом. Зазвичай тут вказується привітальний текст і описується список можливостей чат-бота.
- «Потрібна допомога» — цей діалог буде ширшим, з запитаннями.
Коротко про варіанти обробки команд і запитів від користувачів
Команда — це текст, який починається з символу «/». Можна сказати, що це типу глобальні повідомлення, які дозволяють запустити діалог, або ж просто отримати певну текстову відповідь від бота.
Для обробки команди є кілька варіантів. Взагалі, якщо ваш бот — максимально тривіальний і йому потрібно реагувати суто на команди і просто видавати якийсь текст, можна використати TelegramLongPollingCommandBot
з суміжної бібліотеки.
Проте, скоріше за все, ваш бот буде складнішим (коли команди будуть запускати діалоги з запитаннями-відповідями і реагувати на кнопки, прикріплення фото користувачем і т. д.), то потрібно будувати власну логіку для обробки. І якщо мене читають люди, які розробляють ботів на Java, то думаю, вони погодяться, що гнучкого функціонального рішення для побудови саме діалогів, коли потрібно зберігати стан і т. д. — немає (поправте, якщо помиляюся), тому треба будувати щось своє. Є варіант з використанням State Machine з бібліотеки, проте, як на мене, вона не дає достатньої гнучкості.
Dispatcher — центральне місце обробки повідомлень
Я знайшов дуже хороше рішення, яке мені сподобалося з Python-бібліотеки, і переніс частково цю ідею на Java. Вона полягає в наступному: маємо центральний Dispatcher, який вирішує, на який з обробників має йти повідомлення від користувача і відповідно передає запит на одного з них.
Відповідна реалізація на Java:
public class Dispatcher { private final List<UserRequestHandler> handlers; public Dispatcher(List<UserRequestHandler> handlers) { this.handlers = handlers; } public boolean dispatch(UserRequest userRequest) { for (UserRequestHandler userRequestHandler : handlers) { if(userRequestHandler.isApplicable(userRequest)){ userRequestHandler.handle(userRequest); return true; } } return false; } }
Реалізуємо команду /start
Для команди /start
обробник матиме наступний вигляд:
@Component public class StartCommandHandler extends UserRequestHandler { private static String command = "/start"; public StartCommandHandler(TelegramService telegramService, KeyboardHelper keyboardHelper) { this.telegramService = telegramService; this.keyboardHelper = keyboardHelper; } @Override public boolean isApplicable(UserRequest userRequest) { return isCommand(userRequest.getUpdate(), command); } @Override public void handle(UserRequest request) { } }
- Зверніть увагу на
method isApplicable()
— саме він перевіряє, чи викличеться обробник.
У метод handle додамо логіку для опрацювання. У цьому випадку просто відправляємо користувачу вітальний текст з короткою інформацією про можливості чат-бота.
@Override public void handle(UserRequest request) { telegramService.sendMessage(request.getChatId(), "Привіт! За допомогою цього чат-бота ви зможете зробити запит про допомогу!"); }
Будуємо діалог «Потрібна допомога»
Тепер перейдемо до більш цікавих моментів і побудуємо діалог «Потрібна допомога».
Для того, щоб додати кнопку «Потрібна допомога», необхідно модифікувати обробник команди «/start».
Модифікуємо метод StartCommandHandler.handle()
@Override public void handle(UserRequest request) { ReplyKeyboard replyKeyboard = keyboardHelper.buildMainMenu(); telegramService.sendMessage(request.getChatId(), "Привіт! За допомогою цього чат-бота ти зможеш зробити запит про допомогу!", replyKeyboard); telegramService.sendMessage(request.getChatId(), "Обирай з меню нижче ⤵️"); }
Додаємо кнопки меню
KeyboardHelper.java
public ReplyKeyboardMarkup buildMainMenu() { KeyboardRow keyboardRow = new KeyboardRow(); keyboardRow.add("❗️Потрібна допомога"); return ReplyKeyboardMarkup.builder() .keyboard(List.of(keyboardRow)) .selective(true) .resizeKeyboard(true) .oneTimeKeyboard(false) .build(); }
При відправці повідомлення клавіатура передається у поле replyMarkup
:
SendMessage sendMessage = SendMessage .builder() .text(text) .chatId(chatId.toString()) .replyMarkup(replyKeyboard) .build();
Додаємо додаткові обробники повідомлень
І по цій же схемі пишемо обробники для кожної дії користувача.
INeedHelpHandler |
Оброблятиме натискання на кнопку «Потрібна допомога» |
CityEnteredHandler |
Оброблятиме текст у відповідь на запит про місто |
TextEnteredHandler |
Оброблятиме текст у відповідь на запит про текст звернення |
Готовий код можна знайти тут.
Робота зі станом діалогу
Для підтримки діалогу необхідно зберігати інформацію про стан, тобто повертаючиcь до діаграми вище, коли бот запитує користувача «Введіть місто», він повинен розуміти, що текст, який юзер відправить, потрібно розглядати як «місто». Те ж саме стосується «Введіть текст» — бот повинен розуміти, що текст від юзера є нічим іншим як текстом про допомогу. Для цього вводимо поняття стану. Це дозволить забезпечити процес діалогу (це не повноцінна state machine, але для наших потреб буде достатньо цього мінімуму).
public enum ConversationState { CONVERSATION_STARTED, WAITING_FOR_CITY, WAITING_FOR_TEXT }
Для того, щоб перейти в наступний стан, модифікуємо INeedHelpHandler
:
UserSession userSession = userRequest.getUserSession(); userSession.setState(ConversationState.WAITING_FOR_CITY); userSessionService.saveSession(userSession.getChatId(), userSession);
В усіх цих обробниках звертаємо увагу на метод isApplicable()
.
Приклад для CityEnteredHandler
:
@Override public boolean isApplicable(UserRequest userRequest) { return isTextMessage(userRequest.getUpdate()) && ConversationState.WAITING_FOR_CITY.equals(userRequest.getUserSession().getState()); }
Тобто обробник CityEnteredHandler
викличеться тільки у випадку, якщо користувач ввів текст і діалог знаходиться у стані WAITING_FOR_CITY
.
Зберігаємо сесію користувача
UserSession
— це допоміжний клас, який дозволить зберігати cесію юзера.
public class UserSession { private Long chatId; private ConversationState state; private String city; private String text; }
Для спрощення зберігатимемо дані про сесію користувача в Map<Long, UserSession>
.
З головних моментів — це все, тому додавши усі необхідні обробники, можна побудувати такого невеличкого чат-бота. Весь код прикладу можна знайти у репозиторії. Зверніть увагу, bot.token передається в application.yml.
Додаємо іконки на інші налаштування бота у BotFather
Перейшовши у BotFather і виконавши команду з меню /mybots можна перейти в меню редагування інформації про чат-бот (можна додати іконку, редагувати опис, ім’я, проте змінювати @username не можна (якщо потрібно, то можна просто видалити старого бота і створити нового). Також можна перегенерувати API-токен.
І ще з важливого: можна додати команди (які починаються з «/»), щоб вони висвічувалися в меню вашого бота.
Також зазначу, що можна створити до 20 ботів на 1 акаунт в телеграмі.
Велике недавнє оновлення Bot API 6.0
Буквально місяць тому вийшло дуже серйозне оновлення v6.0, яке дозволить будувати повноцінні веб чат-боти (з використанням HTML5 і т. д.). Як заявляють розробники Телеграму:
> Telegram bots can completely replace any website.
Я ще не мав змоги ознайомитися з цим апдейтом, але виглядає як щось дуже потужне. Можна сказати, серйозна революція в світі розробки чат-ботів. Розробники приводять як приклад DurgerKingBot, який дозволяє ознайомитися з цими можливостями.
Ділюся чат-ботами зі свого списку
@railwaybot. Коли я починав знайомство з чат-ботами, цей бот був одним з тих, з якого черпалися ідеї і який мені дуже сподобався по функціоналу і дизайну. Це бот для пошуку і купівлі залізничних квитків, але думаю, більшість використовує його для однієї дуже цікавої функції: у ньому можна підписатися на моніторинг квитків, і як тільки квиток з’явиться — він повідомляє тебе про це. Цей бот має дуже багато функціоналу, тому якщо шукаєте ідеї для натхнення, зверніть увагу на нього.
@loeserviceBot. Для львів’ян. Дозволяє передавати показники електролічильника, а також має деякі додаткові функції.
@vodokanal_lviv_bot. Теж для львів’ян. Дозволяє передавати показники лічильника в Львівводоканал.
@save_pet_bot. Дозволяє шукати новий дім для тварин, які втратили домівку у зв’язку з російським вторгненням в Україну.
@VolunteersHotlineBot. Бот, створений Українською Волонтерською Службою для допомоги військовим та населенню України 💛 (по суті, у статті ми реалізували частинку цього бота).
@ukrposhta_chatbot — чат-бот Укрпошти для відслідковування посилок.
Діліться у коментарях чат-ботами, якими ви користуєтеся 😊
Висновки
Отже, у цій статті ми спробували побудувати невеличкі чат-боти для платформи телеграм і ознайомилися з частиною можливостей побудови. Тут ще можна розповідати багато цікавого, тому що API дуже обширне і дозволяє будувати різні чат-боти. Як ми бачимо, будувати чат-боти не складно і водночас цікаво.
На сайті телеграму є вся необхідна інформація. Також там є список API методів.
Отож, пробуйте, експериментуйте, розробляйте свої цікаві, корисні, класні чат-боти. Сподіваюся, зумів трохи висвітлити тему і ознайомити з цим цікавим світом 😊
Також діліться у коментарях думками щодо статті або власним досвідом розробки телеграм-ботів, радий буду почути і відповісти.
Мій телеграм: @tarasvv
Cписок корисних посилань
- Java бібліотека, яку ми використовували для написання бота. На вкладці wiki можна знайти більш детальну інформацію про можливості бібліотеки.
- Репозиторій першого прикладу з ехо-ботом.
- Репозиторій другого прикладу з волонтерським ботом.
- Телеграм-чат для обговорень бібліотеки, яку ми використовували у цій статті.
- Телеграм-чат всіх розробників телеграм-ботів.
- Телеграм канал, у якому публікуються оновлення в Telegram Bot API.
Наступна частина статті
Для того, щоб зробити чат-бот доступним 24/7, у наступній частині подивимося, як можна швидко і безкоштовно задеплоїти бот на платформу Heroku, а також налаштуємо мінімальний процес CI/CD, використовуючи BitBucket.
update August 30, 2022: heroku закриває безкоштовні плани, деталі і обговорення альтернатив в коментарях тут
update November 20, 2022: heroku запускає mini — плани (eco dynos — 1000 dyno-hours за 5$, heroku postgres mini — 5$). Також діють спеціальні безкоштовні умови для студентів (в межах 13$ на місяць)
32 коментарі
Додати коментар Підписатись на коментаріВідписатись від коментарів