Мотивация
У разработчика, предпочитающего индивидуальные проекты, есть множество способов самореализации. Для меня, например, приоритетным является создание небольших Telegram-ботов на java. Ведь помимо того, что в процессе разработки всегда можно наглядно проверить работу программы на любом этапе ее реализации, Telegram-боты, на мой взгляд, имеют потребительский потенциал в основном за счет того, что, являясь программой, они не нуждаются в отдельной установке на устройство. Достаточно пользоваться мессенджером Telegram и запустить в нем бот с подходящим функционалом, что, как минимум, экономит ресурсы самого устройства.
Ранее на Хабре я уже делилась с читателями своим опытом самостоятельного создания несложных Telegram-ботов на java, а также небольшими пошаговыми инструкциями по решению отдельных вопросов, возникающих при написании кода. После новогодней разработки очередного чат-бота хочу продолжить сложившуюся традицию и рассказать немного о некоторых нюансах одного из распространенных способов взаимодействия с ботом – принятие и обработка от пользователя запроса с сообщением.
Детализация
Тем, кто так или иначе уже сталкивался с разработкой Telegram-ботов на java, известно, что для того, чтобы класс, содержащий логику бота, реализовывал взаимодействие с сервисами Telegram, его необходимо унаследовать от класса TelegramLongPollingBot и реализовать следующие его базовые методы:
public void onUpdateReceived(Update update);
public String getBotUsername();
public String getBotToken().
В своей сегодняшней статье я как раз остановлюсь на некоторых деталях реализации метода onUpdateReceived (Update update).
Каждый раз, когда кто-то отправляет личное сообщение боту, этот метод будет вызываться автоматически, и вы сможете обработать параметр, который содержит сообщение, а также множество другой информации.
Основной функционал моего последнего бота прост: он принимает от пользователя сообщение с двумя параметрами (вес и рост, указанными целыми цифрами и разделенными одним пробелом), рассчитывает по формуле индекс массы тела (ИМТ) и высылает пользователю результат с характеристикой и краткими рекомендациями. То есть, помимо команд, которые предусмотрены в моем боте, обрабатывается только одно текстовое сообщение от пользователя.
Казалось бы, все предельно просто – достаточно лишь проверить при реализации метода onUpdateReceived, есть ли во входящем обновлении (update) сообщение (метод getMessage()) и есть ли в таком сообщении текст (метод hasText()). Но текст должен быть с определенными параметрами – это должно быть 2 целые цифры, разделенные пробелом. Все остальные запросы должны блокироваться, а пользователю высылаться сообщение об ошибке и повторная просьба корректно направить запрос.
Реализация
Для себя я нашла следующий вариант обработки и запроса:
1. Проверить входящее сообщение на наличие в нем пробелов и, при наличии таковых, разделить строку на части и создать из них массив.
2. В созданном массиве проверить длину такого массива и принадлежность элементов к целым положительным числам и, при соблюдении условий, вызвать метод, в котором производится расчет ИМТ, присвоив его параметрам соответствующие значения элементов массива.
Наиболее оптимальным способом для проверки принадлежности элементов текстового массива к целым положительным числам я выбрала применение регулярных выражений, именно с ними код выглядел компактно и отрабатывал корректно.
Синтаксис регулярных выражений основан на использовании символов <([{\^-=$!|]})?*+.>, которые можно комбинировать с буквенными символами.
Поскольку меня интересовали только целые числа, я использовала следующие символы регулярного выражения:
‘\d’ - соответствует любой одной цифре и заменяет собой выражение [0-9];
‘+’ – частота появления элемента (1 или более цифра в выражении),
и matches() – метод, который возвращает true только тогда, когда вся строка соответствует заданному регулярному выражению.
3. Во всех остальных случаях, не подпадающих под заданные проверки, пользователь должен получать сообщение об ошибке и необходимости корректно ввести запрос.
В итоге, если опустить реализацию методов, участвующих в обработке команд, расчете ИМТ и формировании рекомендаций, передаче данных в базу данных, у меня получилась вот такая реализация метода onUpdateReceived:
@SneakyThrows
@Override
public void onUpdateReceived(Update update) {
// проверка, содержит ли обновление сообщение и содержит ли сообщение текст
if (update.hasMessage() && update.getMessage().hasText()) {
String message_text = update.getMessage().getText();
Long chat_id = update.getMessage().getChatId();
Message message = update.getMessage();
User from = message.getFrom();
SendMessage sendMessage = new SendMessage();
sendMessage.setChatId(update.getMessage().getChatId().toString());
if (commandType.types().contains(message_text)) {
commandHandler.onUpdateReceived(update);
// если сообщение с текстом содержит пробел
} else if (message_text.contains(" ")) {
// создаем строковый массив, в котором элементы образуются черед разделить - пробел
String[] weightAntHeight = update.getMessage().getText().split(" ");
// если длина массива 2 элемента, которые соответствуют целым числам
if (weightAntHeight.length == 2
&& weightAntHeight[0].matches("\\d+")
&& weightAntHeight[1].matches("\\d+")) {
// производим расчет ИМТ, высылаем пользователю результаты и рекомендации, заносим результаты в базу данных
String resultImt = String.format("%.1f", ImtCount.imt(weightAntHeight[0], weightAntHeight[1]));
String resultWithDescription = ImtCount.description(weightAntHeight[0], weightAntHeight[1]);
WriteUser.writeUserIntoDb(LocalDateTime.now().withNano(0),
from.getId(), from.getFirstName()
, resultImt
);
sendMessage.setText(resultWithDescription);
execute(sendMessage);
// во всех остальных случаях выдается сообщение об ошибке и инструкция с правилами направления запроса.
} else {
execute(Sender.sendMessage(chat_id, UNKNOWN + INSTRUCTION));
}
}
else {
execute(Sender.sendMessage(chat_id, UNKNOWN + INSTRUCTION));
}
}
}
Описанный способ может применяться, например, и при регистрации пользователя для формирования параметров обработки логина и пароля.
Резюмирование
Да, статей, посвященный разработке Telegram-ботов, великое множество, но, как показывает мой личный опыт поиска нужной информации, они в большинстве своем однотипны и зачастую не содержат ответов (разъяснений) на практические вопросы. В своих публикациях я делюсь самостоятельно пройденным путем больше с новичками и с теми, кому в принципе как и мне интересна разработка Telegram-ботов.
Надеюсь, этот разбор и реализация обработки текстовых запросов пользователя в проекте, код которого выложен на GitHub, кому-нибудь поможет при разработке своих телеграмм ботов.
Если интересно, как работает мой последний телеграмм бот, то милости прошу: Индекс массы тела.
MisterKot
Раз статья для новичков, то выделю то, что их хорошему не научит.
"message_text" и "chat_id" — в джаве принят другой формат именования переменных.
Смущает ".split(" ");" — а если пользователь ввел два и более пробелов случайно? А если клиент телеграма перестанет убирать пробелы до и после текста сообщения, которые тоже случайно были добавлены? А обработка текста со спецсимволами? А если вес на одной строке, а рост на другой? Так что надо тримить, а после из двух и более пробелов делать одинарный. А еще может потом захочется сделать другой разделитель, точка с запятой или еще что-то. А еще проверка на валидность роста, например, что выше 250 быть не может. Кейсов накидать можно много, но хотело бы видеть хотя бы намеки на проверки, хотя бы комментариями. Понятно, что это пет проджект и не более, но привычка подумать о том, как реальный человек будет пользоваться — всегда пригодится. "Все остальные запросы должны блокироваться" — все же это слишком искусственное требование.
Не очень понятно, в чем смысл проверки на наличие пробела ("// если сообщение с текстом содержит пробел"). Она вообще не нужна. Вы же в любом случае делаете массив строк через split и если длина не 2, то отправляете ошибку.
Название переменной "weightAntHeight" тоже не отражает сути массива и может путать.
И неплохо было бы добавить обработку, что если сообщение "/start", то высылать инструкцию. для тех, кто впервые открыл его.
Читая статью, я не знаю, что такое "WriteUser" и "ImtCount". Для этого надо лезть на гитхаб и изучать уже там. Но вот в самой статье это мешает и не несет никакой смысловой нагрузки. Лучше расписать про базу в следующих статьях, материала-то можно много сделать.
Если без этих мелочей, то статья хорошая, пишите еще.
Polesmih Автор
1. Про отход от camelCase согласна, спасибо, что указали (порой переклинивает…).
2. Насчет ".split(" ");" — такова была изначально задумка – жесткие требования к формату запроса пользователя, тем более сам запрос очень простой. Но в будущем да, согласна, что должно быть больше вариаций на некорректные действия пользователя + проверка на валидность роста и даже веса.
3. Насчет обработки "/start" в статье специально не раскрывается тема обработки команд, о чем так и указано: «.. если опустить реализацию методов, участвующих в обработке команд, …». А так по коду в onUpdateReceived это есть, и увидеть можно в полной версии кода на ГитХаб.
4. Возможно, в демонстрационной части кода нужно было оставить пробелы в том месте, где идет реализация различных методов после обработки сообщений пользователя, но мне казалось достаточным комментария «// производим расчет ИМТ, высылаем пользователю результаты и рекомендации, заносим результаты в базу данных». И если кто-то захочет самостоятельно разобраться в подробностях, выходящих за рамки обозначенной темы статьи, может пройти в полную версию кода на ГитХаб.
5. А в целом обсуждения чьих-то предложенных решений очень полезно, поскольку со стороны можно разглядеть то, на чем «замыливается» взгляд изнутри. Поэтому спасибо за комментарий.