
Это вторая статья из цикла о разработке 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; @BotController public 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: 86400 spring: 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; @ScreenController public 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; @WidgetController public 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:
