T2M Bridge, часть 2: как я написал Java‑библиотеку для Telegram Bot API вместе с Codex — уже примерно 50/50

от автора

Это вторая статья из цикла о разработке T2M Bridge — бота для кросспостинга между Telegram-каналами и MAX-каналами.

В цикле планируются четыре части:

  1. T2M Bridge, часть 1: как я написал Java-библиотеку для MAX Bot API с помощью Codex

  2. T2M Bridge, часть 2: как я написал Java-библиотеку для Telegram Bot API вместе с Codex — уже примерно 50/50

  3. T2M Bridge, часть 3: как я своими силами собрал бота для кросспостинга между Telegram и MAX

  4. 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 — это не одно сообщение.

Пользователь может:

  1. открыть профиль;

  2. нажать «Связать профили»;

  3. запросить код;

  4. открыть второго бота;

  5. выбрать ввод кода;

  6. отправить код текстом.

Такой сценарий требует состояния.

В 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 заново, я бы ещё раньше формализовал границу между тремя слоями:

  1. Bot API models and methods.

  2. Runtime: dispatcher, router, middleware, FSM.

  3. 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:

https://docs.t2m-bridge.ru

Новости разработки и roadmap:

https://t.me/telega2max

ссылка на оригинал статьи https://habr.com/ru/articles/1049572/