Comments 37
В TelegramBots все кончается на получении сообщения из Telegram в метод onUpdateReceived() (это первый раздел этой статьи об устройстве Telegraff). Я уже писал – эта задача тривиальна и на мой взгляд не требует сторонних зависимостей в принципе. Взгляните на пример TelegramBots (WeatherHandlers) и на перечень методов там: `onNewCurrentWeatherCommand`, `onLocationCurrentWeatherCommand`, `onCancelCommand` и т.д. и на то, как они вызываются:
private static SendMessage onForecastWeather(Message message, String language) {
SendMessage sendMessageRequest = null;
if (message.hasText()) {
if (message.getText().startsWith(getNewCommand(language))) {
sendMessageRequest = onNewForecastWeatherCommand(message.getChatId(), message.getFrom().getId(), message.getMessageId(), language);
} else if (message.getText().startsWith(getLocationCommand(language))) {
sendMessageRequest = onLocationForecastWeatherCommand(message.getChatId(), message.getFrom().getId(), message.getMessageId(), language);
} else if (message.getText().startsWith(getCancelCommand(language))) {
sendMessageRequest = onCancelCommand(message.getChatId(), message.getFrom().getId(), message.getMessageId(),
getMainMenuKeyboard(language), language);
} else {
sendMessageRequest = onForecastWeatherCityReceived(message.getChatId(), message.getFrom().getId(), message.getMessageId(),
message.getText(), language);
}
}
return sendMessageRequest;
}
Хотите такое писать каждый раз? Я – нет.
Telegraff – дополнительный уровень абстракции над этим всем. Примеры Telegraff здесь.
а это же не Котлин в примере, зачем сравнивать с джавой???
Да какая разница? Ну представьте этот код на Kotlin, что изменится? Суть в том, что TelegramBots просто доставляет вам апдейты от Телеграм и все. Дальше вы уже сами читаете текст, находите нужную команду и т.д. Собственно, блок на Java отражает эти проблемы и суть здесь не в языке. Попробуйте реализовать при таком подходе сценарий с такси из статьи и поймёте разницу. TelegramBots это лишь обёртка над Telegram API. Telegraff — фреймворк, навязывающий определённый уровень абстракции для решения этой задачи. Решение может нравиться, а может нет, но сравнивать с TelegramBots это нельзя.
Я не хочу размазывать бизнес-логику по нескольким компонентам. Самый максимум — зарезолвить по команде и userid бин и передать ему сообщение — оставим за кадром поддержание состояния контекста.
И тем более завертывать её (бизнес-логику) в лямбды.
Бизнес-логику, как раз, не нужно размазывать. Ваш «контроллер» – это ваш обработчик (сценарий), не нужно бизнес-логику класть внутрь лямбд, возьмите нужный вам сервис из контекста и все.
Повторюсь, TelegramBots это лишь обёртка над Telegram API. Telegraff — фреймворк, навязывающий определённый уровень абстракции для решения этой задачи. Решение может нравиться, а может нет, но сравнивать с TelegramBots это нельзя.
Telegraff – не серебряная пуля и всех проблем не решает. Но каждый имеет право на свое мнение. В любом случае, мне очень жаль, что мне не удалось донести до Вас идею удобства Telegraff.
Ваш «контроллер» – это ваш обработчик (сценарий)
Нет. В одном боте может быть много сценариев, они могут быть нелинейны.
step<String>("locationFrom") { // ②
question { // ③
MarkdownMessage("Откуда поедем?")
}
}
step<String>("locationTo") {
question {
MarkdownMessage("Куда поедем?")
}
}
Ваш код. Как я по коду должен догадаться, что шаг locationFrom ссылается в next на locationTo? по
next { state ->
null // ⑦
}
?Отличный момент молчаливой скрытности котлиновского кода.
Telegraff — фреймворк, навязывающий определённый уровень абстракции для решения этой задачи.
Я пока не вижу «уровень абстракции», но вижу лишь бойлерплейт который позволяет быстро наколбасить визарда. Шаг влево-вправо — ой? Вот я внутри сценария жду команду. Т.е. со стороны пользователя отправил сначала /a потом может прилететь /b, /c, /d — как ваш фреймворк предлагает решать подобные кейсы?
Статья на эту тему ничего не говорит.
Т.е. со стороны пользователя отправил сначала /a потом может прилететь /b, /c, /d — как ваш фреймворк предлагает решать подобные кейсы?
На самом деле, не очень понял Вашу проблему. Ну, опишите обработчик под каждую команду, да и все. Пример ниже. Каждую команду можно хранить в отдельном файле. В будущем будет еще и Hot Reload.
handler("/a") {
process { _, _ ->
MarkdownMessage("Привет!")
}
}
handler("/b") {
process { _, _ ->
MarkdownMessage("Привет!")
}
}
Пожалуйста, прочитайте статью. Остались вопросы – почитайте код github.com/ruslanys/telegraff. То, что вы приводите в качестве аргументов – смешно. Хотите много сценариев, так напишите много. Нелинейными они тоже могут быть. Про Котлин вообще без комментариев.
Признаться, я три дня вложил только, чтобы статью написать, про решение я вообще молчу. Мне – нравится. Указанный Вами TelegramBots я перешел вдоль и поперек, прежде, чем заниматься вот этим всем. Если остались вопросы – это больше не ко мне. От общения конкретно с Вами получаю исключительно негативные эмоции. Еще и карму себе подпортил. Такой вот он, Хабр.
Вам искренне желаю успехов во всех Ваших начинаниях! Надеюсь, на Вашем пути будет больше положительных, чем негативных знакомств!
Как я по коду должен догадаться, что шаг locationFrom ссылается в next на locationTo?
Неужели того факта, что это именно что два шага, и они идут подряд, недостаточно?
Вот next = null по умолчанию — это логично, как и явное связывание в виде next = (тут какая-то переменная).
Почему это получается не то что написано?
Написано два шага в определенном порядке. Именно в этом порядке они и выполняются...
Скажите, а когда вы в программе два оператора (которые statement) подряд пишете — вы тоже как-то указываете, что второй должен выполняться после первого?
Вы можете написать следующее:
step<String>("locationFrom") {
question {
MarkdownMessage("Откуда поедем?")
}
next { "locationTo" }
}
step<String>("locationTo") {
question {
MarkdownMessage("Куда поедем?")
}
next { "paymentMethod" }
}
Но делать такое поведение платформы по умолчанию было бы избыточно и приводило бы к упомянутому Вами «бойлерплейт».
В TelegramBots все кончается на получении сообщения из Telegram в метод onUpdateReceived()
Вы не совсем правы, потому что у TelegramBots есть abilities
В целом согласен, что в этой либе не хватает готовых решений, но почему решили написать свою библиотеку, а не построить а-ля фреймворк на основе TelegramBots?
Вы правы, но дело в том, что abilities – это не то. Гляньте #Abstraction.
Abilities, условно говоря, это кодогенератор (визард). Он не решает проблем, он упрощает API самой библиотеки, и его функциональность очень примитивна.
Что касается вопроса о велосипеде и TelegramBots, объясню.
Дело в том, что моё и ребят из TelegramBots представления о хорошем коде расходятся. Я не могу такое писать) Я вообще не понимаю, как можно было наплодить моделей с конструктором по умолчанию, игнорируя обязательные поля и пропихивать все через сеттеры, делать это везде и еще писать в доке.
Я бы переписал чуть ли не всё. Но даже если переписывать, то точно на Котлин. А кому это нужно?
Мне кажется, мы с ними концептуально различны. Я смотрю со стороны API разработчика, мне нужен удобный API, пускай и не полностью функциональный (те же inline-клавиатуры), но надежный и решающий проблему. Хотя цель TelegramBots, как раз, как мне кажется, покрыть API Telegram в первую очередь, а потом как-нибудь все прикрутить поудобнее.
Может быть они посмотрят на Telegraff и захотят у себя переделать, и мир станет лучше)
Вместе с тем, я считаю, что концептуально вся библиотека TelegramBots умещается в Telegraff в 3 класса: PollingClient, WebhookClient, TelegramApi.
К тому же Telegraff – это не просто библиотека. Это, скорее, был проект в продакшене, который нужно было перевести на свой DSL. TelegramBots по указаным выше причинам я не хотел использовать, написал для себя и долго еще не мог написать статью, привести в порядок. По этой же причине Telegraff полностью на spring-boot. И по большому счету это легко поправить, но это не приоритетная задача. Конечно, спринг не каждому проекту подходит, но для Телеграм-бота почему бы и нет?
Опять-таки, если кому-то понравилась идея, он хочет пользоваться, внес нужные для себя изменения и оформил в PR – честь и хвала.
Была идея, что если кто-то поддержит донатом проект (но для этого нужно на английский перевести), то эти деньги буду выдавать за PR. Такой фонд. Да и сам готов в этот фонд вкладываться, поэтому пишите код, высылайте PR, отблагодарю)
Есть куда стремиться. Можно еще больше упростить код и избавиться от "имён шагов", если использовать suspension и делегирование пропертей:
val from by question { ... }
val to by question { ... }
// logic
respond { ... }
Идея с ключами шагов прекрасна, благодарствую! Попробую что-нибудь такое!
В своё время, тоже упаковал описание клавиатуры в DSL, но только поверх rubenlagus/TelegramBots
val someKeyboard = keyboard {
row {
button("Yes") { callbackData = "yes" }
button("No") { callbackData = "no" }
}
row {
button("Cancel") { callbackData = "cancel" }
}
}
Подскажите, а параметризованные команды поддерживаются?
Что-то вроде /send <chat_id> <text_message>
и подобное.
Спасибо за вопрос! Сегодня — нет. Но уже такой запрос был, будет интерес (а вы своим вопросом его проявили), реализую. В принципе, если не заморачиваться и просто прокидывать содержимое сообщения (параметры), то это можно сделать очень быстро.
Приятно отвечать на умные комментарии/вопросы. Спасибо.
Состояние хранится в памяти и при перезапуске оно обнуляется (разумно, наверно, описать это в разделе про особенности). Но на деле, эту задачу также просто решить, как невозможно. С одной стороны, можно хранить текущий обработчик, шаг и ответы в БД, с другой стороны, непонятно как реагировать, если вы (разработчик) изменили ключ шага, например, или тип, или добавили шаг перед. В таком случае персистентное состояние только навредит, а не поможет, потому что не будет консистентно с новой кодовой базой.
Отвечая на ваш вопрос, подумал, что наиболее разумным будет вынести эту конкретную функциональность под интерфейс, вроде StateProvider
и отдать на откуп пользователю, добавив в поставку in-memory хранилище по умолчанию. Захотел своё – переопределил bean.
Интересно) но мне кажется не очень рационально описывать диалоги через DSL — для изучения написания своего DSL норм, но для бота, у которого диалоги постоянно расширяются/изменяются, вариант плохой. Такое лучше в базу вынести или хотя бы в файл.
Ну, смотрите. Как я упоминал, можно сделать Hot Reload этих DSL скриптов без всякой перезагрузки. С другой стороны, не очень понимаю, как можно писать сценарии редакторами в отрыве от программистов, но, в принципе, это возможно и в Telegraff. Сделать, например, в методе process
вызов единственного сервисного метода с передачей всех ответов туда и все. Тогда редактора могут делать все, что хотят в скриптах. Им и синтаксис языка знать не нужно.
А чем dsl — не файл? И если выносить в файл — в каком формате это всё хранить? Ещё один dsl придумывать?
Так же можно вынести котлин код в kts скрипт — и хоба, расширяйте бота сколько хотите, можно даже нескольких ботов / разные их задачи разносить по своим файлам.
Telegraff: Kotlin DSL для Telegram