Прошло уже какое-то время с момента, когда я публиковал свой первый туториал по tgbotapi и пришло время начать уже серию статей, которая должна разложить по полочкам, как можно разбивать логику Telegram ботов (а потенциально, любых ботов :) ) в целом и как это делать в вышеупомянутой библиотеке в связке с надстройкой PlaguBot.
С чего бы начать?
Начнем с того, какой инструментарий будет использован:
Все исходники туториалов будут доступны в этом репозитории, запуск описан там же, но если вкратце - просто замените значение по ключу botToken
в конфиге репозитория и вызовите ./gradlew run --args="config.json"
. В идеале, нужно создать отдельный конфигурационный файл local.config.json
и положить конфигурацию в него, потому что он не попадет в репозиторий, но если вы хотите использовать config.json
- не забудьте вычистить это токен, если захотите пушить изменения в свой форк или репозиторий
О хитрости local.config.json
В рамках репозитория в .gitignore
внесены две строчки:
local.*
local.*/
Это значит, что все файлы и папки, начинающиейся с local.
будут проигнорированы гитом. Таким образом, достаточно назвать вашу конфигурацию local.config.json
и она точно не попадёт в репозиторий
Моё рабочее окружение
Для собственно кодинга я использую Intellij IDEA
из-под линукс (Elementary OS), но практика показывает, что обычно можно работать и под любыми другими системами (а обладатели Linux могут обойтись текстовым редактором и терминалом для сборки/запуска)
Код данного туториала доступен в модуле introduction, а конкретно нас больше всего будет интересовать создаваемый там плагин.
А? (Или как оно там работает)
PlaguBot устроен достаточно просто: он берёт путь до файла, превращает его в конфиг, берёт все плагины из конфига и дальше в два этапа загружает бота:
Этап загрузки конфигураций - на этом этапе в Koin DI необходимо объявить свои объекты и прочие настройки.
Этап конфигурирования поведения бота - тут мы уже настраиваем реакцию на сообщения от пользователей и прочее такое.
Теоретически, можно выгрузить все настройки в поля самого плагина, но...
это не очень соответствует общей парадигме плагинов. Плагины по своей сути это фабрики, которые должны при необходимости настраивать работу в рамках нескольких ботов
А теперь введение
Первое (почти), что мы делаем в рамках плагина - объявляем его класс конфигурации:
@Serializable
private class Config(
val onStartCommandMessage: String
)
Тут у нас приватный класс, который будет недоступен в других классах никак кроме чёрной магии рефлексии. Собственно, в этом классе у нас есть только одно поле onStartCommandMessage
, которое мы будем использовать далее для ответа пользователям.
Почти?
На самом деле, первое, что мы делаем - определяем логгер для плагина
private val log = logger
logger создаст нам объект KSLog для автоматического логгирования с тэгом нашего плагина. Подробнее можно посмотреть тут.
Пояснение
Отдельный логгер удобнее создавать так, поскольку logger - это метод-расширение к любому объекту, а посколько мы не хотим, чтобы тэги перепутались, лучше создать отдельный логгер. Кроме прочего, это еще и будет оптимальней в плане быстродействия :)
Вторая вещь, которую мы делаем - это переопределяем setupDI
метод для собственно десериализации конфига и его передачи в Koin
.
override fun Module.setupDI(database: Database, params: JsonObject) {
single { /*1*/get<Json>()./*4*/decodeFromJsonElement(/*3*/Config.serializer(), /*2*/params["introduction"] ?: return@single null) }
}
Для начала, на вход setupDI мы получаем три параметра:
Module
является ресивером (receiver
) функции и доступен в её рамках какthis
.database: Database
- собственно, Exposed Database, которую можно будет использовать для сохранения данных (но в этой части мы этого делать не будем).params: JsonObject
- конфигурация PlaguBot'а как она была декодирована из конфигурационного файла.
А теперь построчно разберем, что тут происходит:
get<Json>()
- получаем объект типа Json для дальнейшей десериализации конфига.params["introduction"] ?: return@single null
- берем элемент из PlaguBot конфига по ключуintroduction
, а если такого нет - возвращаем null (то есть нельзя создать конфиг).Config.serializer()
- получаем сериализатор конфига (он доступен благодаря аннотации@Serializable
).decodeFromJsonElement(сериализатор, наш элемент из конфига)
- собственно, превращаем элемент из конфига в объект конфигурации нашего плагина.
Собственно, вызов single
скажет Koin
вызвать этот код один раз и сохранить его результат.
Уф, да чего так сложно/просто-то?
Во введении я старался максимально подробно расписать все пришедшие в голову важные моменты, поэтому с одной стороны информации много, а с другой - она вся очень скучная ????
Ну и третья, самая сложная часть - это настройка бота. Собственно, код:
override suspend fun BehaviourContext.setupBotPlugin(koin: Koin) {
val config = koin.getOrNull<Config>()
if (config == null) {
log.w("Plugin has been disabled due to absence of \"introduction\" field in config or some error during configuration loading")
return
}
onCommand("start", initialFilter = { it.chat is PrivateChat }) {
sendMessage(
it.chat,
config.onStartCommandMessage
)
}
}
Ну что ж, а вот здесь давайте построчно
(1) На вход получаем два параметра:
BehaviourContext
, сочетающий в себе контекст для асинхронности, бота и информацию об обновлениях, приходящих в бота; иkoin: Koin
- наш источник зависимостей и параметров, в том числе тех, что мы зарегистрировали вsetupDI
.(2)
val config = koin.getOrNull<Config>()
- получение текущего конфига нашего плагина либоnull
- то самое отсутствие конфига в случае, если полеintroduction
было пропущено в json конфигурации.(4 - 7) Если конфиг не был получен - ругаемся об этом в консоль и заканчиваем настройку плагина.
(9) -
onCommand("start", initialFilter = { it.chat is PrivateChat })
- конфигурация ожидания команды start. Кроме того, тут используется фильтрация - блок, который будет описан далее, вызовется только для приватных сообщений. Подробнее можно почитать тут.sendMessage(it.chat, config.onStartCommandMessage)
- отправка сообщений в чат, в котором нам пришла команда старта. Тут it - это CommonMessage<TextContent> - то есть обычное сообщение с текстом.
Вот, в общем-то, и всё ????
Итоги
В данном туториале мы:
Разобрались, как запускать ботов и конфигурировать их.
Создали свой плагин для
PlaguBot
.Научились отвечать пользователям на команду
/start
.
Кроме прочего, немного разобрались в терминологии и строении ботов в рамках этой серии туториалов. Для создания своего бота с нуля можно использовать вот этот шаблон, а если вы хотите использовать tgbotapi
без каких-либо лишних обвязок вроде Koin/PlaguBot/Exposed - можно воспользоваться этим шаблоном.
Спасибо за внимание к статье и буду рад комментариям/замечаниям/предложениям ????