KTB-18 Реализовать функционал изучения слов в боте¶
📝 Описание¶
Добавить условие для обработки нажатия кнопки "Изучить слова" (аналогично обработке кнопки "Статистика").
Внутри этого условия:
- Получить следующий вопрос из объекта
trainer. - Если вопрос отсутствует, отправить пользователю сообщение: "Вы выучили все слова в базе".
- В противном случае, вызвать функцию
sendQuestion()для отправки вопроса пользователю.
Детали реализации
Необходимо реализовать функционал изучения слов в приложении. Для этого требуется:
- Создать константу
CALLBACK_DATA_ANSWER_PREFIXсо значением'answer_'. - Разработать функцию для отправки сущности “вопроса” пользователю. Эта функция должна принимать параметры
chatIdи объектQuestion.
Внутри функции необходимо сформировать тело запроса для метода sendMessage с учётом следующих требований:
- Поле
text: указать корректный ответ на английском языке. - Поле
inline_keyboard: создать JSON-объект с вариантами ответов. При этом:- Поле
textкаждой кнопки должно содержать перевод варианта ответа. - Поле
callback_dataкаждой кнопки должно быть конкатенацией константыCALLBACK_DATA_ANSWER_PREFIXи индекса варианта ответа.
- Поле
Для получения индекса каждого варианта ответа рекомендуется использовать функции forEachIndexed или mapIndexed.
🗒 Задание¶
- Создать константу
CALLBACK_DATA_ANSWER_PREFIXсо значением'answer_'. - Реализовать функцию для отправки вопроса пользователю с вариантами ответов, учитывая требования к формированию тела запроса для
sendMessage. - Добавить условие для обработки нажатия кнопки "Изучить слова":
- Получить следующий вопрос из объекта
trainer. - Если “вопрос” отсутствует, отправить сообщение: "Все слова в словаре выучены".
- Иначе, отправить “вопрос” пользователю, вызвав функцию
sendQuestion().
- Получить следующий вопрос из объекта
- Вынести логику из предыдущего шага в функцию с такой сигнатурой:
fun checkNextQuestionAndSend(
trainer: LearnWordsTrainer,
telegramBotService: TelegramBotService,
chatId: Int
)
❌ Ошибки¶
✏️ Комментарии¶
На данном этапе чаще всего ошибки происходят при формировании кнопок с вариантами ответа. Можно предложить студенту двигаться по шагам. Сначала сделать вариант без mapIndexed {} и потом улучшить. Например:
Попробуй сначала декомпозировать. Отделить код который составляет часть json для отображения кнопок от остального кода:
val variantsString = """
{
"text": "${question.variants[0].translate}",
"callback_data": "$CALLBACK_DATA_ANSWER_PREFIX${variants[0]}"
},
{
"text": "${question.variants[1].translate}",
"callback_data": "$CALLBACK_DATA_ANSWER_PREFIX${variants[1]}"
},
{
"text": "${question.variants[2].translate}",
"callback_data": "$CALLBACK_DATA_ANSWER_PREFIX${variants[2]}"
},
{
"text": "${question.variants[3].translate}",
"callback_data": "$CALLBACK_DATA_ANSWER_PREFIX${variants[3]}"
}
"""
val sendQuestionBody = """
{
"chat_id": $chatId,
"text": "${question.correctAnswer.original}",
"reply_markup": {
"inline_keyboard": [
[
$variantsString,
{
"text": "Меню",
"callback_data": "$MENU"
}
]
]
}
}
""".trimIndent()`
println(sendQuestionBody)
А теперь обрати внимание, что `{ "text": "${question.variants[3].translate}", "callback_data": "$CALLBACK_DATA_ANSWER_PREFIX${variants[3]}" }` повторяется на каждый variant из `question.variants` и соединяется запятой. Мы можем сделать данный код компактнее и универсальнее.
Для этого нам надо каждый `variant` смапить в строку, а вместо цифры использовать индекс:
val variantsString = question.variants
.mapIndexed { index, word ->
"""
{
"text": "${word.translate}",
"callback_data": "$CALLBACK_DATA_ANSWER_PREFIX${index}"
}
""".trimIndent()
}
.joinToString(separator = ",")
Еще часто не создают переменную question в checkNextQuestionAndSend(...), а вызывают trainer.getNextQuestion() каждый раз:
fun checkNextQuestionAndSend(trainer: LearnWordsTrainer, service: TelegramBotService, chatId: Int) {
if (question == trainer.getNextQuestion()) { // !!! ГРУБАЯ ОШИБКА
service.sendMessage(chatId, "Вы выучили все слова в базе")
} else {
service.sendQuestion(chatId, trainer.getNextQuestion()) // !!! ГРУБАЯ ОШИБКА
}
}
Это грубая ошибка, ведь getNextQuestion() приводит к генерации нового вопроса. Коммент может быть таким:
Так делать нельзя, ведь `getNextQuestion()` создает нам новый вопрос. Давай создадим переменную `question` перед if.
⌨️ Решение¶
// Telegram.kt
fun checkNextQuestionAndSend(trainer: LearnWordsTrainer, service: TelegramBotService, chatId: Int) {
val question = trainer.getNextQuestion()
if (question == null) {
service.sendMessage(chatId, "Вы выучили все слова в базе")
} else {
service.sendQuestion(chatId, question)
}
}
// TelegramBotService.kt
fun sendQuestion(chatId: Int, question: Question): String {
val urlSendMessage = "$TELEGRAM_BASE_URL/bot$botToken/sendMessage"
val keyboardLayout = question.variants
.mapIndexed { index: Int, word: Word -> "{ \"text\": \"${word.translate}\", \"callback_data\": \"$CALLBACK_DATA_ANSWER_PREFIX$index\" }" }
.joinToString(separator = ",")
println(keyboardLayout)
val sendMenuBody = """
{
"chat_id": $chatId,
"text": "${question.correctAnswer.original}",
"reply_markup": {
"inline_keyboard": [[ $keyboardLayout ]]
}
}
""".trimIndent()
val request = HttpRequest.newBuilder(URI.create(urlSendMessage))
.header("Content-Type", "application/json")
.POST(HttpRequest.BodyPublishers.ofString(sendMenuBody))
.build()
val response = client.send(request, HttpResponse.BodyHandlers.ofString())
return response.body()
}