Pull to refresh

Comments 37

Перечитайте, пожалуйста, статью. Судя по комментарию, Вы совсем не поняли сути. Сравнивать Telegraff и TelegramBots просто бессмысленно. Библиотека, которую Вы указали, не способна решить проблемы, которые решает Telegraff.

В TelegramBots все кончается на получении сообщения из Telegram в метод onUpdateReceived() (это первый раздел этой статьи об устройстве Telegraff). Я уже писал – эта задача тривиальна и на мой взгляд не требует сторонних зависимостей в принципе. Взгляните на пример TelegramBots (WeatherHandlers) и на перечень методов там: `onNewCurrentWeatherCommand`, `onLocationCurrentWeatherCommand`, `onCancelCommand` и т.д. и на то, как они вызываются:

Обработка команд в TelegramBots
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 это нельзя.

как минимум на Котлин можно написать гораздо компактнее

Сути это не меняет. Признаться, я считаю, и на Java можно это гораздо компактнее написать.
Эм… Я кагбэ даже не одного бота на этой либе написал. Поэтому и говорю — ваша переусложнена. Шаги, фильтры. Что?!

Я не хочу размазывать бизнес-логику по нескольким компонентам. Самый максимум — зарезолвить по команде и 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 — ни фига не оператор

Вы можете написать следующее:


Пример
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 { ... }
Стремиться, однозначно, есть куда (и думаю, всегда будет). Из ближайшего – inline-клавиатура.

Идея с ключами шагов прекрасна, благодарствую! Попробую что-нибудь такое!

В своё время, тоже упаковал описание клавиатуры в DSL, но только поверх rubenlagus/TelegramBots


val someKeyboard = keyboard {
    row {
        button("Yes") { callbackData = "yes" }
        button("No") { callbackData = "no" }
    }
    row {
        button("Cancel") { callbackData = "cancel" }
    }
}

Это очень похоже на то, как планируется решить вопрос с inline-клавиатурой. Спасибо, хороший пример!

Подскажите, а параметризованные команды поддерживаются?
Что-то вроде /send <chat_id> <text_message> и подобное.

Спасибо за вопрос! Сегодня — нет. Но уже такой запрос был, будет интерес (а вы своим вопросом его проявили), реализую. В принципе, если не заморачиваться и просто прокидывать содержимое сообщения (параметры), то это можно сделать очень быстро.

Подход выглядит очень интересно, сам испытывал боль с описанием сценариев пользуясь чистым API. Бота можно безопасно перезапускать или всё состоянии только в памяти?

Приятно отвечать на умные комментарии/вопросы. Спасибо.


Состояние хранится в памяти и при перезапуске оно обнуляется (разумно, наверно, описать это в разделе про особенности). Но на деле, эту задачу также просто решить, как невозможно. С одной стороны, можно хранить текущий обработчик, шаг и ответы в БД, с другой стороны, непонятно как реагировать, если вы (разработчик) изменили ключ шага, например, или тип, или добавили шаг перед. В таком случае персистентное состояние только навредит, а не поможет, потому что не будет консистентно с новой кодовой базой.


Отвечая на ваш вопрос, подумал, что наиболее разумным будет вынести эту конкретную функциональность под интерфейс, вроде StateProvider и отдать на откуп пользователю, добавив в поставку in-memory хранилище по умолчанию. Захотел своё – переопределил bean.

Интересно) но мне кажется не очень рационально описывать диалоги через DSL — для изучения написания своего DSL норм, но для бота, у которого диалоги постоянно расширяются/изменяются, вариант плохой. Такое лучше в базу вынести или хотя бы в файл.

Чем же вариант с DSL плох? Мне кажется, DSL в принципе для подобного рода задач существует. А так да, это в отдельном файле, посмотрите здесь.

По крайней мере в тех примерах, ссылку на которые вы скинули, есть синтаксис языка программирования, а в реальном мире обучением (построение диалогов, ответы на вопросы) ботов всё-таки заниматься должны не программисты (вариант с нейронками сейчас опустим), а какие-нибудь редакторы текстов и тп, и они не должны писать такие вещи. Я пару лет назад работал в компании, занимающейся разработкой диалоговых систем (чат-боты), где как раз редакторы заполняли файлики, у них там за день генерилось сотни диалоговых сценариев, далее парсили эти файлы и в базу закидывали. В таком случае проще что-то подправить что-то в тексте и диалоге, просто загружаем новую версию файла или правим текст прямо в базе и сразу получаем исправленный вариант диалога, без необходимости что-то пересобирать/перезапускать

Ну, смотрите. Как я упоминал, можно сделать Hot Reload этих DSL скриптов без всякой перезагрузки. С другой стороны, не очень понимаю, как можно писать сценарии редакторами в отрыве от программистов, но, в принципе, это возможно и в Telegraff. Сделать, например, в методе process вызов единственного сервисного метода с передачей всех ответов туда и все. Тогда редактора могут делать все, что хотят в скриптах. Им и синтаксис языка знать не нужно.

А чем dsl — не файл? И если выносить в файл — в каком формате это всё хранить? Ещё один dsl придумывать?


Так же можно вынести котлин код в kts скрипт — и хоба, расширяйте бота сколько хотите, можно даже нескольких ботов / разные их задачи разносить по своим файлам.

Sign up to leave a comment.

Articles