
Это вторая статья из цикла о разработке T2M Bridge — бота для кросспостинга между Telegram-каналами и MAX-каналами.
В цикле планируются четыре части:
-
T2M Bridge, часть 1: как я написал Java-библиотеку для MAX Bot API с помощью Codex
-
T2M Bridge, часть 2: как я написал Java-библиотеку для Telegram Bot API вместе с Codex — уже примерно 50/50
-
T2M Bridge, часть 3: как я своими силами собрал бота для кросспостинга между Telegram и MAX
-
T2M Bridge, часть 4: как я выкатывал бота в прод и почему всё оказалось сложнее Docker Compose
В первой статье я рассказывал про maxlib — Java-библиотеку для MAX Bot API.
Там Codex сделал большую часть первичного каркаса. Это ускорило разработку, но привело к ожидаемой проблеме: ИИ периодически придумывал поля, объекты и структуры, которых не было в документации MAX Bot API. В итоге значительная часть времени ушла не на Java как таковую, а на сверку моделей с реальным API.
После этого я начал делать Telegram-часть для T2M Bridge уже аккуратнее.
Так появилась telegalib — Java-библиотека для разработки Telegram-ботов.
Репозиторий:
https://github.com/tardyon-soft/telegalib
В этой статье разберу, зачем мне понадобилась своя библиотека поверх Telegram Bot API, как она устроена, чем отличается подход от maxlib и как с её помощью можно написать бота на Java.
Зачем писать свою библиотеку для Telegram
В отличие от MAX, для Telegram уже есть готовые Java-библиотеки.
Поэтому вопрос логичный:
Зачем писать ещё одну?
Причина была не в том, что существующие решения плохие.
Мне нужен был одинаковый подход для двух платформ: Telegram и MAX. T2M Bridge должен был не просто отправлять сообщения, а вести пользователя по сценариям:
-
связать профиль Telegram с профилем MAX;
-
показать список каналов;
-
проверить, что бот добавлен администратором;
-
связать Telegram-канал и MAX-канал;
-
выбрать направление кросспостинга;
-
дальше обрабатывать события из двух мессенджеров в похожей модели.
Если для MAX у меня есть runtime с routing, FSM, screens и middleware, а для Telegram — совершенно другой стиль разработки, то бизнес-логика бота начинает расползаться.
Поэтому я решил сделать telegalib не просто как thin-wrapper над Telegram Bot API, а как библиотеку с похожими идеями:
-
typed client;
-
long polling и webhook;
-
dispatcher;
-
router;
-
filters;
-
middleware;
-
FSM;
-
screen API;
-
Spring Boot starter;
-
testkit;
-
demo-модули.
То есть задача была не «написать идеальную библиотеку для всех Telegram-ботов», а получить контролируемый runtime для своего проекта и при этом оформить его как отдельную переиспользуемую библиотеку.
Почему подход с Codex изменился
После maxlib стало понятно: если дать ИИ слишком широкую задачу, он быстро построит красивую архитектуру, но может начать фантазировать.
С Telegram ситуация была одновременно проще и сложнее.
Проще — потому что Telegram Bot API хорошо известен, вокруг него больше примеров, а часть сущностей давно устоялась.
Сложнее — потому что сам API большой. Там много типов update’ов, методов, вложенных объектов, вариантов медиа, callback’ов, платежей, business-сценариев и других деталей.
Поэтому во второй библиотеке я старался работать с Codex иначе.
Если для maxlib формат был ближе к:
Сгенерируй каркас библиотеки, а я потом поправлю.
То для telegalib подход стал ближе к 50/50:
-
архитектурные решения и границы модулей — вручную;
-
публичный API — вручную или с жесткой проверкой;
-
однотипный код — через Codex;
-
тесты, README и demo — частично через Codex;
-
спорные места — только после ручной проверки;
-
модели Telegram Bot API — аккуратно, без доверия к «правдоподобному» результату.
Главный вывод после первой библиотеки был простой:
ИИ можно использовать как ускоритель, но нельзя отдавать ему право решать, как на самом деле устроен внешний API.
Что получилось
telegalib — это многомодульная Java-библиотека для разработки Telegram-ботов.
Репозиторий собран на Java 21 и Gradle.
Основные публикуемые артефакты:
implementation("ru.tardyon.botframework:telegram-bot-framework-core:<version>")implementation("ru.tardyon.botframework:telegram-bot-framework-spring-boot-starter:<version>")
В репозитории есть несколько модулей:
-
telegram-bot-framework-core-основной runtime и client API; -
telegram-bot-framework-spring-boot-starter— интеграция со Spring Boot; -
telegram-bot-framework-testkit— тестовые утилиты; -
telegram-bot-framework-demo— demo-приложение; -
telegram-bot-framework-screen-demo— demo для screen API; -
telegram-bot-framework-botapi-generator— tooling для генерации частей Bot API.
Основная библиотека закрывает несколько уровней:
-
TelegramApiClientдля прямой работы с Telegram Bot API; -
runtime для long polling и webhook;
-
Router, filters и middleware; -
FSM с
StateStorage; -
screen API со стеком экранов;
-
Spring Boot starter с auto-configuration;
-
testkit для интеграционных тестов.
Vanilla Java: минимальный бот без Spring
Если не нужен Spring Boot, можно использовать только core.
Подключение:
repositories { mavenCentral()}dependencies { implementation("ru.tardyon.botframework:telegram-bot-framework-core:<version>")}
Минимальный пример long polling:
import ru.tardyon.botframework.telegram.api.DefaultTelegramApiClient;import ru.tardyon.botframework.telegram.api.TelegramApiClient;import ru.tardyon.botframework.telegram.bot.DefaultTelegramBot;import ru.tardyon.botframework.telegram.bot.TelegramBot;import ru.tardyon.botframework.telegram.dispatcher.DefaultDispatcher;import ru.tardyon.botframework.telegram.dispatcher.Router;import ru.tardyon.botframework.telegram.dispatcher.filter.Filters;import ru.tardyon.botframework.telegram.polling.LongPollingOptions;import ru.tardyon.botframework.telegram.polling.LongPollingRunner;public class VanillaBotMain { public static void main(String[] args) { TelegramApiClient client = new DefaultTelegramApiClient(System.getenv("BOT_TOKEN")); Router router = new Router(); router.message(Filters.command("start"), (ctx, msg) -> ctx.telegramMessage().reply("Привет")); router.message(Filters.textEquals("ping"), (ctx, msg) -> ctx.telegramMessage().reply("pong")); router.callbackQuery(Filters.callbackDataStartsWith("menu:"), (ctx, cbq) -> ctx.telegramCallbackQuery().answer("OK")); LongPollingRunner pollingRunner = new LongPollingRunner( client, LongPollingOptions.defaults() ); TelegramBot bot = new DefaultTelegramBot( pollingRunner, new DefaultDispatcher(router) ); bot.startPolling(); }}
Здесь вручную собираются основные компоненты:
-
TelegramApiClient; -
Router; -
DefaultDispatcher; -
LongPollingRunner; -
TelegramBot.
Для небольшого бота этого достаточно.
Но в T2M Bridge мне удобнее Spring Boot, потому что остальная часть сервиса тоже строится вокруг Spring-экосистемы.
Spring Boot starter
Для Spring Boot подключается отдельный starter:
repositories { mavenCentral()}dependencies { implementation("ru.tardyon.botframework:telegram-bot-framework-spring-boot-starter:<version>")}
Минимальная конфигурация polling:
telegram: bot: token: ${BOT_TOKEN} mode: polling transport: mode: cloud polling: enabled: true timeout: 30 limit: 100
После этого можно описать обработчики через аннотации:
import ru.tardyon.botframework.telegram.bot.TelegramCallbackQuery;import ru.tardyon.botframework.telegram.bot.TelegramMessage;import ru.tardyon.botframework.telegram.spring.boot.annotation.BotController;import ru.tardyon.botframework.telegram.spring.boot.annotation.OnCallbackQuery;import ru.tardyon.botframework.telegram.spring.boot.annotation.OnMessage;@BotControllerpublic class MyBotController { @OnMessage(command = "start") public void onStart(TelegramMessage message) { message.reply("Привет"); } @OnMessage(textEquals = "ping") public void onPing(TelegramMessage message) { message.reply("pong"); } @OnCallbackQuery(callbackPrefix = "menu:") public void onMenu(TelegramCallbackQuery callback) { callback.answer("OK"); }}
Starter поднимает основные runtime-компоненты:
-
TelegramApiClient; -
transport profile;
-
LongPollingOptions; -
LongPollingRunner; -
Router; -
Dispatcher; -
TelegramBot; -
lifecycle;
-
webhook processor;
-
webhook controller;
-
StateStorage; -
ScreenStateStorage; -
ScreenRegistry; -
ScreenEngine; -
ScreenMiddleware.
Это как раз тот уровень, который мне был нужен для T2M Bridge: не собирать runtime руками в каждом сервисе, а описывать сценарии бота через контроллеры и обработчики.
Webhook mode
Для production обычно удобнее webhook, если уже есть внешний URL, TLS и reverse proxy.
Конфигурация:
telegram: bot: token: ${BOT_TOKEN} mode: webhook polling: enabled: false webhook: enabled: true path: /telegram/webhook public-url: ${BOT_WEBHOOK_PUBLIC_URL} secret-token: ${BOT_WEBHOOK_SECRET_TOKEN:} drop-pending-updates: true
Если задан webhook.public-url, starter при старте приложения вызывает setWebhook.
Для локальной разработки long polling проще: не нужно поднимать публичный endpoint, думать про TLS и пробрасывать локальный порт наружу.
В T2M Bridge это удобно разделяется по окружениям: локально можно запускать polling, а в production использовать webhook.
Proxy
В starter есть поддержка HTTP и SOCKS5 proxy:
telegram: bot: proxy: enabled: true type: socks5 host: 127.0.0.1 port: 1080 username: ${PROXY_USER:} password: ${PROXY_PASSWORD:}
Для Telegram-ботов это практичная вещь.
В локальной разработке или на отдельных серверах может понадобиться явно управлять сетевым маршрутом к Telegram API.
Я не хотел зашивать это руками в прикладной код, поэтому proxy-настройки вынесены на уровень конфигурации.
Router и filters
Если не использовать аннотации, маршруты можно регистрировать вручную через Router.
Router поддерживает разные группы update-событий:
-
message; -
callbackQuery; -
inlineQuery; -
chosenInlineResult; -
myChatMember; -
chatMember; -
shippingQuery; -
preCheckoutQuery; -
businessConnection; -
businessMessage; -
editedBusinessMessage; -
deletedBusinessMessages.
Пример:
Router router = new Router();router.message(Filters.command("start"), (ctx, msg) -> ctx.telegramMessage().reply("Привет"));router.message(Filters.textEquals("ping"), (ctx, msg) -> ctx.telegramMessage().reply("pong"));router.callbackQuery(Filters.callbackDataStartsWith("menu:"), (ctx, cbq) -> ctx.telegramCallbackQuery().answer("OK"));
В Filters есть готовые предикаты:
-
command(...); -
commands(...); -
textPresent(); -
textEquals(...); -
textStartsWith(...); -
privateChat(); -
groupChat(); -
supergroupChat(); -
channelChat(); -
fromUser(...); -
fromChat(...); -
callbackDataEquals(...); -
callbackDataStartsWith(...); -
invoicePayloadEquals(...); -
preCheckoutPayloadEquals(...); -
stateEquals(...); -
inStates(...); -
noState().
Этого достаточно, чтобы покрыть большую часть типичных сценариев.
В T2M Bridge фильтры нужны для разделения пользовательских действий:
-
команда
/start; -
callback’и экранов;
-
ввод кода связывания;
-
выбор канала;
-
настройка направления кросспостинга.
UpdateContext
В обработчики передаётся UpdateContext.
Он содержит:
-
текущий update;
-
TelegramApiClient; -
StateStorage; -
bot id;
-
runtime attributes.
Через него доступны удобные обёртки:
-
ctx.telegramMessage(); -
ctx.telegramCallbackQuery().
Например:
router.message(Filters.command("start"), (ctx, msg) -> { ctx.telegramMessage().reply("Привет");});
Или callback:
router.callbackQuery(Filters.callbackDataStartsWith("menu:"), (ctx, cbq) -> { ctx.telegramCallbackQuery().answer("OK");});
Я специально хотел, чтобы обработчик работал не с голым JSON update, а с контекстом сценария.
Когда бот растёт, прямое ковыряние update’ов быстро делает код шумным.
Middleware
DefaultDispatcher может применять цепочку middleware.
Например:
import java.util.List;import ru.tardyon.botframework.telegram.dispatcher.DefaultDispatcher;import ru.tardyon.botframework.telegram.dispatcher.middleware.ErrorBoundaryUpdateMiddleware;import ru.tardyon.botframework.telegram.dispatcher.middleware.LoggingUpdateMiddleware;DefaultDispatcher dispatcher = new DefaultDispatcher( router, List.of( new ErrorBoundaryUpdateMiddleware(), new LoggingUpdateMiddleware() ));
Если нужна своя логика, можно реализовать UpdateMiddleware.
Для T2M Bridge middleware полезны для сквозных задач:
-
логирование входящих update’ов;
-
обработка ошибок;
-
загрузка текущего пользователя;
-
проверка связанного профиля;
-
enrichment контекста;
-
метрики.
Это лучше, чем размазывать одинаковые проверки по каждому обработчику.
FSM: состояние пользователя
Связывание профилей Telegram и MAX — это не одно сообщение.
Пользователь может:
-
открыть профиль;
-
нажать «Связать профили»;
-
запросить код;
-
открыть второго бота;
-
выбрать ввод кода;
-
отправить код текстом.
Такой сценарий требует состояния.
В telegalib FSM строится вокруг:
-
State; -
StateKey; -
StateStorage; -
InMemoryStateStorage.
Пример:
import ru.tardyon.botframework.telegram.dispatcher.Router;import ru.tardyon.botframework.telegram.dispatcher.filter.Filters;import ru.tardyon.botframework.telegram.fsm.State;Router router = new Router();router.message(Filters.command("startform"), (ctx, msg) -> { ctx.state().setState(State.of("form.awaiting_name")); ctx.telegramMessage().reply("Введите имя");});router.message(Filters.stateEquals("form.awaiting_name"), (ctx, msg) -> { ctx.state().putData("name", msg.text()); ctx.state().setState(State.of("form.awaiting_language")); ctx.telegramMessage().reply("Введите язык");});router.message(Filters.stateEquals("form.awaiting_language"), (ctx, msg) -> { Object name = ctx.state().getData("name").orElse("unknown"); ctx.state().clear(); ctx.telegramMessage().reply("Имя: " + name + ", язык: " + msg.text());});
Для локальной разработки можно использовать in-memory storage.
Для production-сценариев состояние лучше хранить во внешнем storage, чтобы оно переживало рестарт приложения.
В Spring Boot варианте можно включить Redis:
telegram: bot: state: storage: redis redis: key-prefix: telegram:fsm ttl-seconds: 86400spring: data: redis: host: localhost port: 6379
Screen API: бот как набор экранов
Один из важных слоёв библиотеки — screen API.
Командный интерфейс удобен для разработчика, но не всегда удобен для пользователя.
Можно сделать так:
/start/link/channels/settings
Но для обычного владельца канала лучше показать экран с кнопками:
-
профиль;
-
связать профили;
-
мои каналы;
-
настройки;
-
назад.
В telegalib screen API включает:
-
ScreenEngine; -
ScreenRegistry; -
Screen; -
ScreenView; -
ScreenAction; -
ScreenNavigator; -
ScreenStateStorage; -
ScreenMiddleware.
Для Spring Boot есть аннотации:
-
@ScreenController; -
@Screen; -
@OnScreenMessage; -
@OnScreenCallback.
Пример экрана:
import ru.tardyon.botframework.telegram.api.model.markup.Keyboards;import ru.tardyon.botframework.telegram.screen.ScreenAction;import ru.tardyon.botframework.telegram.screen.ScreenContext;import ru.tardyon.botframework.telegram.screen.ScreenView;import ru.tardyon.botframework.telegram.spring.boot.annotation.OnScreenCallback;import ru.tardyon.botframework.telegram.spring.boot.annotation.Screen;import ru.tardyon.botframework.telegram.spring.boot.annotation.ScreenController;@ScreenControllerpublic class SettingsScreenController { private static final String SETTINGS = "settings"; private static final String TOGGLE = "screen:settings:toggle"; @Screen(id = SETTINGS, main = true) public ScreenView settings(ScreenContext context) { boolean enabled = context.screenState() .getData("notifications") .map(Boolean.class::cast) .orElse(false); return ScreenView.builder() .line("Экран настроек") .line("notifications=" + enabled) .replyMarkup( Keyboards.inlineKeyboard() .row(Keyboards.callbackButton("Переключить", TOGGLE)) .build() ) .build(); } @OnScreenCallback(screen = SETTINGS, callbackEquals = TOGGLE) public ScreenAction toggle(ScreenContext context) { boolean enabled = context.screenState() .getData("notifications") .map(Boolean.class::cast) .orElse(false); context.screenState().putData("notifications", !enabled); return ScreenAction.render(); }}
ScreenAction поддерживает типовые действия:
-
handled(); -
render(); -
push(screenId); -
push(screenId, targetData); -
replace(screenId); -
replace(screenId, targetData); -
back(); -
clear(); -
unhandled().
Для T2M Bridge это один из ключевых слоёв.
Мне нужно было сделать не просто набор команд, а понятный интерфейс внутри бота:
-
профиль пользователя;
-
связывание профилей;
-
список каналов;
-
карточка канала;
-
выбор канала с другой платформы;
-
настройки кросспостинга.
Через screen API такой сценарий получается описывать ближе к обычному UI-flow, а не к набору разрозненных callback’ов.
Widgets
Когда интерфейс строится из экранов, быстро появляются повторяющиеся части.
Например:
-
главное меню;
-
список действий;
-
блок состояния;
-
кнопка возврата;
-
общий фрагмент настроек.
Для этого в библиотеке есть widgets.
В Spring Boot добавлены аннотации:
-
@WidgetController; -
@Widget; -
@OnWidgetAction.
Упрощённый пример:
import java.util.List;import ru.tardyon.botframework.telegram.screen.ScreenAction;import ru.tardyon.botframework.telegram.spring.boot.widget.OnWidgetAction;import ru.tardyon.botframework.telegram.spring.boot.widget.Widget;import ru.tardyon.botframework.telegram.spring.boot.widget.WidgetButtons;import ru.tardyon.botframework.telegram.spring.boot.widget.WidgetController;import ru.tardyon.botframework.telegram.spring.boot.widget.WidgetView;@WidgetControllerpublic class MenuWidgetController { record MenuItem(String label, String target) { } @Widget(id = "home_menu") public WidgetView homeMenu(List<MenuItem> items) { return WidgetView.builder() .line("Меню") .replyMarkup( WidgetButtons.objectList( "home_menu", "open", items, MenuItem::label, MenuItem::target ) ) .build(); } @OnWidgetAction(widget = "home_menu", action = "open") public ScreenAction open(String payload) { return ScreenAction.push(payload); }}
Это не обязательная часть для простого бота.
Но если бот постепенно превращается в небольшой интерфейс с навигацией, widgets помогают не копировать одинаковые блоки между экранами.
Screen state storage
У экранов есть отдельное состояние.
Это важно, потому что FSM и состояние экрана — не одно и то же.
FSM отвечает за диалоговый сценарий: например, пользователь сейчас вводит код или название.
Screen state отвечает за состояние UI: выбранная вкладка, данные экрана, targetData, временные параметры отображения.
По умолчанию starter использует in-memory storage:
telegram: bot: screen-state: storage: memory
При необходимости можно включить Redis:
telegram: bot: screen-state: storage: redis redis: key-prefix: telegram:screen ttl-seconds: 86400
Для реального сервиса это полезно по той же причине, что и Redis для FSM: после рестарта приложения пользователь не должен внезапно потерять весь контекст.
Прямой вызов Telegram Bot API
Если routing-слой не нужен, можно работать через TelegramApiClient напрямую.
Пример:
import ru.tardyon.botframework.telegram.api.DefaultTelegramApiClient;import ru.tardyon.botframework.telegram.api.TelegramApiClient;import ru.tardyon.botframework.telegram.api.method.SendMessageRequest;public class DirectApiExample { public static void main(String[] args) { TelegramApiClient client = new DefaultTelegramApiClient(System.getenv("BOT_TOKEN")); client.sendMessage(new SendMessageRequest( 123456789L, "Привет", null )); }}
В библиотеке реализованы разные группы Telegram Bot API:
-
базовые сообщения;
-
callback и inline API;
-
invoices;
-
shipping/pre-checkout;
-
web app query;
-
media group;
-
загрузка файлов;
-
chat/admin/member operations;
-
forum topics;
-
gifts;
-
Stars;
-
paid media;
-
business connection;
-
stories;
-
checklist.
В T2M Bridge прямой client API нужен для операций, которые не укладываются в простую обработку входящего update’а: например, отправка сообщений и медиа при кросспостинге.
Загрузка файлов
Для методов, которые принимают InputFile, доступны разные варианты:
-
InputFile.fileId(...); -
InputFile.url(...); -
InputFile.path(...); -
InputFile.bytes(...); -
InputFile.stream(...).
Это удобно для кросспостинга.
В одном сценарии файл уже может существовать в Telegram как file_id.
В другом — его нужно скачать, переложить, отправить как stream или bytes.
Для текстового бота это второстепенно. Для бота, который переносит посты между каналами, работа с медиа становится одной из центральных частей.
Testkit и demo-модули
Когда библиотека начинает отвечать не только за отправку сообщений, но и за routing, FSM, screens и middleware, ручного тестирования уже мало.
В репозитории есть telegram-bot-framework-testkit.
Он нужен, чтобы тестировать сценарии без реального Telegram API.
Также есть demo-модули:
-
telegram-bot-framework-demo; -
telegram-bot-framework-screen-demo.
Первый показывает базовые сценарии, второй — screen API, push/back навигацию, screen state, widgets и работу с targetData.
Для библиотеки это важнее, чем может показаться.
README и unit-тесты могут быть зелёными, но только demo показывает, как API ощущается в реальном использовании.
Чем telegalib отличается от maxlib по процессу разработки
maxlib я писал в режиме «быстро получить рабочую основу для MAX».
Там Codex сделал очень много начального кода, а я потом достаточно долго вычищал галлюцинации: выдуманные поля, неверные объекты, лишние структуры и расхождения с документацией.
С telegalib подход был осторожнее.
Во-первых, я уже понимал, какие runtime-части мне нужны:
-
client;
-
dispatcher;
-
router;
-
filters;
-
middleware;
-
FSM;
-
screens;
-
Spring Boot starter;
-
testkit.
Во-вторых, я лучше разделял задачи для Codex.
Не:
Сделай библиотеку для Telegram Bot API.
А более конкретно:
Сделай обработчик для такого update-события.
Или:
Подготовь Spring Boot auto-configuration для уже существующих компонентов.
Или:
Напиши README для этого модуля по текущему коду.
Или:
Сгенерируй тесты для этого routing-сценария.
В-третьих, в Telegram-части я уже меньше доверял «правдоподобному» коду.
Если модель выглядит логично, это ещё не значит, что она соответствует Bot API.
Поэтому работа стала больше похожа на 50/50:
-
я задаю архитектуру;
-
Codex ускоряет однотипные участки;
-
я проверяю API и публичные контракты;
-
Codex помогает с тестами и документацией;
-
я принимаю финальное решение, что остаётся в библиотеке, а что удаляется.
Где ИИ помог
Codex хорошо справлялся с задачами, где контекст уже задан.
Например:
-
есть существующий интерфейс — нужно добавить реализацию;
-
есть runtime-компоненты — нужно собрать auto-configuration;
-
есть handler — нужно написать похожий handler;
-
есть тестовый сценарий — нужно добавить соседние cases;
-
есть код — нужно подготовить README;
-
есть screen API — нужно сделать demo-экран.
Такой режим работы был полезным.
ИИ не проектировал библиотеку вместо меня, но сокращал время на повторяющиеся операции.
Где ИИ всё равно мешал
Даже с Telegram API, где больше известных примеров, ИИ всё равно иногда пытался достроить недостающие детали сам.
Типичные проблемы:
-
добавляет поле, потому что «так обычно бывает»;
-
выбирает имя свойства по аналогии, а не по документации;
-
путает похожие update-типы;
-
смешивает старую и новую версии API;
-
пишет тест, который проверяет happy path, но не ловит ошибку контракта;
-
предлагает слишком обобщённую абстракцию там, где достаточно простого класса.
Поэтому правило осталось тем же:
Для внешнего API документация важнее сгенерированного кода.
Но по сравнению с maxlib я уже меньше разгребал последствия, потому что изначально давал Codex более узкие задачи.
Как telegalib используется в T2M Bridge
В T2M Bridge telegalib отвечает за Telegram-часть.
С её помощью бот:
-
принимает update’ы из Telegram;
-
обрабатывает команды и callback’и;
-
показывает пользователю экраны;
-
хранит состояние сценариев;
-
работает с каналами;
-
проверяет пользовательские действия;
-
отправляет сообщения и медиа;
-
участвует в связке Telegram-канала с MAX-каналом.
Например, когда пользователь открывает Telegram-бота, связывает профиль, добавляет бота в канал и выбирает пару каналов, значительная часть интерфейса проходит через Telegram screens и callback handlers.
Но сам кросспостинг я в этой статье подробно не разбираю.
Это тема третьей части цикла.
Там уже будет интереснее с точки зрения бизнес-логики:
-
как связать пользователя между двумя платформами;
-
как сопоставить каналы;
-
как хранить соответствия между публикациями;
-
как переносить медиа;
-
что делать с replies;
-
как обрабатывать редактирование и удаление;
-
где заканчивается библиотека и начинается логика продукта.
Что получилось удачно
Первое — единый runtime-подход.
Telegram-часть и MAX-часть T2M Bridge стали ближе по модели разработки. Это снижает когнитивную нагрузку: не нужно держать две полностью разные парадигмы для двух мессенджеров.
Второе — Spring Boot starter.
Для прикладного сервиса удобнее описывать обработчики и экраны, а не вручную собирать client, dispatcher, polling runner и storage.
Третье — screen API.
Для бота с настройками и навигацией это важнее, чем кажется. Команды подходят для developer-first интерфейса, но для владельца канала кнопки и экраны понятнее.
Четвёртое — testkit и demo.
Они помогают проверять не только отдельные методы, но и то, как библиотека используется в реальных сценариях.
Пятое — более дисциплинированная работа с Codex.
После maxlib я стал меньше просить ИИ «сделать всё» и чаще давать ему узкие задачи с понятными границами.
Что я бы сделал иначе
Если бы я начинал telegalib заново, я бы ещё раньше формализовал границу между тремя слоями:
-
Bot API models and methods.
-
Runtime: dispatcher, router, middleware, FSM.
-
UI layer: screens, widgets, navigation.
Когда эти слои начинают смешиваться, библиотека быстро становится неудобной.
Ещё я бы раньше усилил contract-тесты для Bot API моделей.
Компилятор не скажет, что поле не соответствует реальному API. Он скажет только, что Java-код корректен.
Для библиотек вокруг внешних API этого мало.
Нужны тесты, которые защищают не только от синтаксических ошибок, но и от расхождения с контрактом.
Итоги
telegalib появилась не потому, что в Java-мире совсем нечем писать Telegram-ботов.
Она появилась потому, что для T2M Bridge мне нужен был единый подход к двум платформам: Telegram и MAX.
В текущем виде библиотека закрывает несколько задач:
-
прямую работу с Telegram Bot API через
TelegramApiClient; -
long polling и webhook;
-
routing update-событий;
-
filters;
-
middleware;
-
FSM;
-
screen API;
-
widgets;
-
Spring Boot auto-configuration;
-
Redis storage для state и screen state;
-
testkit и demo-модули.
Главное отличие от первой библиотеки — другой процесс разработки.
Если maxlib во многом начиналась как «быстро сгенерировать каркас и потом исправлять», то telegalib я писал уже осторожнее: примерно 50/50 между мной и Codex.
ИИ всё ещё помогал и экономил время, но архитектурные решения, публичный API и соответствие Telegram Bot API оставались под ручным контролем.
Основной вывод после двух библиотек такой:
Codex хорошо ускоряет разработку инфраструктурного кода, но чем ближе код к внешнему API, тем меньше ему можно доверять без проверки.
В следующей статье расскажу уже про сам T2M Bridge: как я связывал Telegram- и MAX-профили, как сопоставлял каналы, как переносил публикации и где начались проблемы, которые не видны на уровне отдельных библиотек.
Репозиторий telegalib:
https://github.com/tardyon-soft/telegalib
Живой MVP и документация T2M Bridge:
Новости разработки и roadmap:
ссылка на оригинал статьи https://habr.com/ru/articles/1049572/