KTB-16 Разработать кнопки и отправлять их пользователю в телеграм¶
📝 Описание¶
Необходимо реализовать функциональность создания и отправки кнопок в сообщениях Telegram-бота. Процесс включает следующие этапы:
- Формирование объектов кнопок в коде на основе примеров из документации Telegram API.
- Сериализация объектов кнопок в формат JSON для последующей отправки.
- Отправка сообщений с кнопками с использованием метода
sendMessageи параметраreply_markup. - Обработка обратных вызовов (callback) при нажатии пользователем на кнопки.
// видео
Ключевые моменты реализации
- Использование метода
sendMessageдля отправки кнопок:- Метод
sendMessageприменяется для отправки сообщений в чат. - Опциональный параметр
reply_markupпозволяет добавить кнопки к сообщениям. - Существуют два типа кнопок:
- Inline-клавиатура: кнопки отображаются внутри сообщения.
- Reply-клавиатура: кнопки отображаются в интерфейсе клиента.
- Метод
- Создание объекта кнопок и сериализация в JSON:
- Формирование объекта кнопок на основе примеров из документации Telegram API.
- Преобразование (сериализация) объекта кнопок в JSON-строку для передачи в запросе.
- Пример создания JSON-объекта с кнопками для корректной передачи данных.
- Создание и отправка POST-запроса с телом в формате JSON:
- Разработка метода
sendMenu()для отправки меню с кнопками пользователю. - Использование
OkHttpClientдля создания и отправки POST-запроса.
- Разработка метода
- Обработка ответа на нажатие кнопок (callback):
- Получение объекта
callback_query, представляющего ответ от нажатой кнопки пользователем. - Использование поля
dataв объектеcallback_queryдля определения нажатой кнопки и выполнение соответствующего действия на основе нажатой кнопки.
- Получение объекта
🗒 Задание¶
- Добавить отображение основного меню с кнопками в ответ на команду
/start, используя созданный методsendMenu(). - Переместить класс
Wordв файл трейнера (LearnWordsTrainer.kt), если это ещё не было сделано ранее. Для соблюдения базовых архитектурных рекомендаций нужно переиспользовать ключевую сущность. - Создать и инициализировать переменную
trainerэкземпляром классаLearnWordsTrainerперед циклом, в котором происходит получение обновлений от Telegram Bots API. Это потребуется для дальнейшей реализации основного функционала бота.
❌ Ошибки¶
✏️ Комментарии¶
Если в каждом методе внутриTelegramBotService создает новый client
На случай если студент не вынес код в TelegramBotService
Давай разместим все методы для работы с `Telegram API` внутри класса `TelegramBotService`. Тогда внутри `TelegramBotService` можно сделать поле `botToken`, чтобы не передавать в каждый метод. А также вынести в константу `https://api.telegram.org/bot`.
⌨️ Решение¶
import java.net.URI
import java.net.URLEncoder
import java.net.http.HttpClient
import java.net.http.HttpRequest
import java.net.http.HttpResponse
import java.nio.charset.StandardCharsets
const val TELEGRAM_BASE_URL = "https://api.telegram.org"
const val LEARN_WORDS_CLICKED = "learn_words_clicked"
const val STATISTICS_CLICKED = "statistics_clicked"
class TelegramBotService(
private val botToken: String,
) {
private val client: HttpClient = HttpClient.newHttpClient()
fun getUpdates(updateId: Int): String {
val urlGetUpdates = "$TELEGRAM_BASE_URL/bot$botToken/getUpdates?offset=$updateId"
val httpRequest: HttpRequest = HttpRequest.newBuilder().uri(URI.create(urlGetUpdates)).build()
val response: HttpResponse<String> = client.send(httpRequest, HttpResponse.BodyHandlers.ofString())
return response.body()
}
fun sendMessage(chatId: Int, text: String): String {
// энкодинг нужен, потому что параметр передается через query GET-запроса
val encodedText = URLEncoder.encode(text, StandardCharsets.UTF_8.toString())
val urlSendMessage = "$TELEGRAM_BASE_URL/bot$botToken/sendMessage?chat_id=$chatId&text=$encodedText"
val httpRequest: HttpRequest = HttpRequest.newBuilder().uri(URI.create(urlSendMessage)).build()
/* Рефакторинг в KTB-16 (общий клиент и возврат body у метода)
val client: HttpClient = HttpClient.newHttpClient()
client.send(httpRequest, HttpResponse.BodyHandlers.ofString())
*/
val response = client.send(httpRequest, HttpResponse.BodyHandlers.ofString())
return response.body()
}
fun sendMenu(chatId: Int): String {
val urlSendMessage = "$TELEGRAM_BASE_URL/bot$botToken/sendMessage"
val sendMenuBody = """
{
"chat_id": $chatId,
"text": "Основное меню",
"reply_markup": {
"inline_keyboard": [
[
{
"text": "Learn words",
"callback_data": "$LEARN_WORDS_CLICKED"
},
{
"text": "Statistics",
"callback_data": "$STATISTICS_CLICKED"
}
]
]
}
}
""".trimIndent()
val httpRequest: HttpRequest = HttpRequest.newBuilder().uri(URI.create(urlSendMessage))
.header("Content-type", "application/json")
.POST(HttpRequest.BodyPublishers.ofString(sendMenuBody))
.build()
val response = client.send(httpRequest, HttpResponse.BodyHandlers.ofString())
return response.body()
}
}
fun main(args: Array<String>) {
val botToken = args[0]
var updateId = 0
val telegramBotService = TelegramBotService(botToken)
val trainer = LearnWordsTrainer()
val regexFindText: Regex = "\"text\":\"(.+?)\"".toRegex()
val regexFindUpdateId = "\"update_id\":(\\d+)".toRegex()
val regexFindChatId = "\"chat\":\\{\"id\":(.+?)\"".toRegex()
val regexFindData = "\"data\":\"(.+?)\"".toRegex()
while (true) {
Thread.sleep(2000)
// KTB-15 (сократили)
// val updates = getUpdates(botToken, updateId)
val updates = telegramBotService.getUpdates(updateId)
println(updates)
// ?: continue обязательно, чтобы пропускать, если нет updateId. тк должен присутствовать в каждом ответе
val updateIdInt = regexFindUpdateId.find(updates)?.groups?.get(1)?.value?.toIntOrNull() ?: continue
updateId = updateIdInt + 1
val text = regexFindText.find(updates)?.groups?.get(1)?.value
// ?: continue обязательно аналогично
val chatId = regexFindChatId.find(updates)?.groups?.get(1)?.value?.toIntOrNull() ?: continue
val data = regexFindData.find(updates)?.groups?.get(1)?.value
updateId = updateIdInt + 1
if (text.equals("/start", true)) {
telegramBotService.sendMenu(chatId)
}
if (data.equals(STATISTICS_CLICKED, true)) {
telegramBotService.sendMessage(chatId, "Выучено 10 из 10 слов")
}
}
}
/* Перенесено в отдельный класс TelegramBotService в KTB-15
fun getUpdates(botToken: String, updateId: Int): String {
val urlGetUpdates = "https://api.telegram.org/bot$botToken/getUpdates?offset=$updateId"
val client: HttpClient = HttpClient.newHttpClient()
val httpRequest: HttpRequest = HttpRequest.newBuilder().uri(URI.create(urlGetUpdates)).build()
val response: HttpResponse<String> = client.send(httpRequest, HttpResponse.BodyHandlers.ofString())
return response.body()
}
*/