Skip to content

Bot API 9.4, система Views, OpenTelemetry и streamMessage

8–15 февраля 2026

Невероятно насыщенная неделя. GramIO догоняет Bot API 9.4 — 8 новых контекстов. Появилась система шаблонов Views для переиспользуемых сообщений. Вышли плагины OpenTelemetry и Sentry. Добавлен хук onApiCall для инструментирования API-вызовов, streamMessage для стриминга черновиков с живой печатью, стилизация кнопок в клавиатурах, Node.js-поддержка SQLite и нативный Redis для Bun. Разбираем.

gramio v0.4.14 — Хук onApiCall и человечные стектрейсы

b54d121 54ece76 7e48c24

Новый хук: onApiCall

Седьмой хук в коллекции. onApiCall оборачивает весь жизненный цикл API-вызова — трейсинг, логирование, метрики, что угодно. Работает как middleware с next():

ts
bot.onApiCall(async (context, next) => {
    console.log(`→ ${context.method}`);
    const start = Date.now();
    const result = await next();
    console.log(`← ${context.method} (${Date.now() - start}ms)`);
    return result;
});

Можно привязать к конкретным методам:

ts
bot.onApiCall("sendMessage", async (context, next) => {
    // срабатывает только для sendMessage
    return next();
});

Несколько хуков составляются как middleware — первый зарегистрированный оборачивает всё. Работает и в Bot, и в Plugin. Именно на этом построен новый плагин OpenTelemetry.

Стектрейсы ошибок теперь ведут на ваш код

TelegramError теперь запоминает место, откуда вы вызвали API. Когда bot.api.sendMessage(...) падает — стектрейс указывает на вашу строку кода, а не на внутренности фреймворка. Настройка не нужна — работает из коробки.

Зависимости

  • @gramio/contexts обновлён до ^0.4.0
  • @gramio/keyboards обновлён до ^1.3.0
  • @gramio/types обновлён до ^9.4.0

@gramio/contexts v0.4.0 — Bot API 9.2 / 9.3 / 9.4

ea3049f aa3ff6d 3df81eb 524ba8b 13564e6

streamMessage — стриминг черновиков с живой печатью

Отправляйте текст чанками в чат с живым предпросмотром печати. Каждый чанк обновляет черновик через sendMessageDraft, а финальное сообщение отправляется через sendMessage при заполнении сегмента в 4096 символов. Идеально для стриминга ответов AI/LLM:

ts
bot.command("stream", async (context) => {
    const chunks = generateTextChunks(); // Iterable или AsyncIterable
    const messages = await context.streamMessage(chunks);
});

Принимает Iterable<MessageDraftPiece> или AsyncIterable<MessageDraftPiece>, где каждый элемент — строка или { text, entities?, draft_id? }. Поддерживает AbortSignal для отмены.

Bot API 9.2 — Предложенные посты и личные сообщения

8 новых контекстов и структур для жизненного цикла предложенных постов:

  • SuggestedPostApprovedContext, SuggestedPostApprovalFailedContext, SuggestedPostDeclinedContext, SuggestedPostPaidContext, SuggestedPostRefundedContext
  • Структура DirectMessagesTopic
  • Chat.isDirectMessages, ChatFullInfo.parentChat, ChatAdministratorRights.canManageDirectMessages
  • Gift.publisherChat, UniqueGift.publisherChat
  • Message.suggestedPostInfo, Message.directMessagesTopic, Message.isPaidPost

Bot API 9.3 — Апгрейд подарков, новые структуры

  • GiftUpgradeSentContext — сервисное сообщение об апгрейде подарка
  • Метод sendMessageDraft() в SendMixin
  • Новые структуры: GiftBackground, UniqueGiftColors, UserRating
  • Расширен Gift: isPremium, background, uniqueGiftVariantCount
  • Расширен ChatFullInfo: rating, uniqueGiftColors, paidMessageStarCount

Bot API 9.4 — Смена владельца чата, качество видео, аудио профиля

  • ChatOwnerLeftContext, ChatOwnerChangedContext — отслеживание смены владельца чата
  • Структура VideoQuality с полем codec ("h265" | "av01")
  • Структура UserProfileAudios
  • В VideoAttachment добавлен геттер qualities
  • В UniqueGiftModel добавлено поле rarity ("uncommon" | "rare" | "epic" | "legendary")
  • В UniqueGift добавлен isBurned
  • В User добавлен allowsUsersToCreateTopics()

@gramio/views — Система шаблонов для переиспользуемых сообщений (НОВОЕ)

4de789b cd3b934 944a5fb c7521ae b2f917b

Свежий пакет для создания переиспользуемых шаблонов сообщений с автоматическим выбором стратегии отправки/редактирования. Определяете вьюшку один раз — библиотека сама решает, отправить новое сообщение или отредактировать текущее, в зависимости от типа контекста.

Программные вьюшки

ts
import { initViewsBuilder } from "@gramio/views";
import { defineAdapter } from "@gramio/views/define";

const adapter = defineAdapter({
    welcome(name: string) {
        return this.response
            .text(`Привет, ${name}!`)
            .keyboard([[{ text: "Начать", callback_data: "start" }]]);
    },
});

const defineView = initViewsBuilder().from(adapter);

bot.derive(["message", "callback_query"], (context) => ({
    render: defineView.buildRender(context, {}),
}));

bot.command("start", (context) => context.render("welcome", "Alice"));

Вьюшки из JSON

Определяйте вьюшки как JSON с интерполяцией в тексте, клавиатурах и медиа:

ts
import { createJsonAdapter } from "@gramio/views/json";

const adapter = createJsonAdapter({
    views: {
        welcome: {
            text: "Привет, {{name}}!",
            reply_markup: {
                inline_keyboard: [
                    [{ text: "Профиль {{name}}", callback_data: "profile_{{id}}" }],
                ],
            },
        },
    },
});

Загрузка из файловой системы

ts
import { loadJsonViewsDir } from "@gramio/views/fs";

// views/messages.json → "messages.welcome", "messages.goodbye"
// views/goods/products.json → "goods.products.list"
const views = await loadJsonViewsDir("./views");

Поддержка i18n

Два подхода: фабрика адаптера для JSON-файлов по локалям, или кастомный resolve для ключей перевода:

ts
// Выбор адаптера по локали
const defineView = initViewsBuilder<{ locale: string }>()
    .from((globals) => adapters[globals.locale]);

// Или кастомный resolve для ключей перевода
const adapter = createJsonAdapter({
    views: { greet: { text: "{{t:hello}}, {{name}}!" } },
    resolve: (key, globals) => {
        if (key.startsWith("t:")) return globals.t(key.slice(2));
    },
});

Поддерживает все типы клавиатур (inline, reply, remove, force reply), одиночные и групповые медиа с интерполяцией URL, а также доступ к глобалам через синтаксис .

@gramio/opentelemetry — Распределённый трейсинг (НОВОЕ)

5fe9928 56789d9 1ba7880

Вендор-нейтральный трейсинг для GramIO через OpenTelemetry API. Каждый update становится корневым спаном, каждый API-вызов — дочерним. Работает с любым OTEL-бэкендом (Jaeger, Grafana, Axiom и т.д.).

ts
import { opentelemetryPlugin } from "@gramio/opentelemetry";

bot.extend(opentelemetryPlugin({
    recordApiParams: true, // записывать параметры API как атрибуты спана
}));

Иерархия трейсов:

gramio.update.message (CONSUMER)
  ├── telegram.api/sendMessage (CLIENT)
  ├── telegram.api/deleteMessage (CLIENT)
  └── кастомные спаны через record()

Экспортируемые утилиты: record(name, fn) для кастомных дочерних спанов, getCurrentSpan(), setAttributes(). Бесшовно интегрируется с вебхуками Elysia — спаны GramIO автоматически вкладываются в HTTP-спаны запроса.

@gramio/sentry — Отслеживание ошибок (НОВОЕ)

47948dd fb1e8c8

Интеграция с Sentry: автоматический перехват ошибок, идентификация пользователей, breadcrumbs и опциональный трейсинг:

ts
import { sentryPlugin } from "@gramio/sentry";

bot.extend(sentryPlugin({
    setUser: true,      // автоматическая привязка пользователя из context.from
    breadcrumbs: true,  // breadcrumb на каждый update и API-вызов
    tracing: false,     // isolation scopes + спаны на каждый update
}));

// Методы из контекста:
bot.command("test", (context) => {
    context.sentry.captureMessage("Что-то произошло");
    context.sentry.setTag("custom", "value");
});

Использует @sentry/core для поддержки разных рантаймов (Bun и Node.js).

@gramio/keyboards v1.3.0 — Стилизация кнопок

b97e34e

Все методы кнопок теперь принимают необязательный параметр options для визуальной стилизации:

ts
new InlineKeyboard()
    .text("Удалить", "delete", { style: "danger" })
    .text("Подтвердить", "confirm", {
        style: "success",
        icon_custom_emoji_id: "5368324170671202286",
    });

Три стиля: "danger" (красный), "primary" (синий), "success" (зелёный). Плюс icon_custom_emoji_id для кастомных эмодзи рядом с текстом кнопки. Работает и в InlineKeyboard, и в Keyboard.

@gramio/types v9.4.0

177ce3d 27149c1

Новые типы для Bot API 9.4: VideoQuality, UserProfileAudios, ChatOwnerLeft, ChatOwnerChanged, UniqueGiftModelRarity. Типы стилизации кнопок (KeyboardButtonStyle, InlineKeyboardButtonStyle) теперь официальные. Новые методы API: getUserProfileAudios, setMyProfilePhoto, removeMyProfilePhoto.

@gramio/storage-sqlite v1.0.0 — Теперь работает на Node.js!

84ea1b1

Адаптер SQLite больше не только для Bun! Теперь с двойными экспортами под каждый рантайм:

  • Bun: использует bun:sqlite (без изменений)
  • Node.js: использует node:sqlite с DatabaseSync

Нужная реализация подставляется автоматически по рантайму — менять код не нужно.

@gramio/storage-redis — Нативный Redis для Bun

8190612 98749d1

Адаптер Redis теперь поддерживает встроенный RedisClient из Bun наряду с ioredis:

ts
// Автоматически выбирается на Bun — ioredis не нужен
import { redisStorage } from "@gramio/storage-redis";
const storage = redisStorage({ url: "redis://localhost:6379" });

Двойные экспорты: @gramio/storage-redis (автоопределение рантайма), @gramio/storage-redis/ioredis (явно), @gramio/storage-redis/bun (явно). Peer-зависимость ioredis теперь опциональна.

@gramio/test — Мокирование API и симуляция чатов

f9b670d 480cde3

onApi / offApi для подмены ответов API

ts
import { apiError } from "@gramio/test";

// Статичный ответ
env.onApi("sendMessage", { message_id: 1, chat: { id: 1 }, ... });

// Динамический обработчик
env.onApi("getChat", (params) => {
    if (params.chat_id === 123) return chatData;
    return apiError(400, "Chat not found");
});

// Симуляция ошибки с retry_after
env.onApi("sendMessage", apiError(429, "Too Many Requests", { retry_after: 30 }));

env.offApi("sendMessage"); // сбросить один
env.offApi();              // сбросить все

Объекты чатов и действия пользователей

ts
const chat = env.createChat();
const user = env.createUser({ first_name: "Alice" });

await user.join(chat);           // эмитит chat_member + new_chat_members
await user.sendMessage(chat, "Привет!"); // эмитит message в чате
await user.click("button_data"); // эмитит callback_query
await user.leave(chat);          // эмитит chat_member + left_chat_member

Все API-вызовы записываются в env.apiCalls для проверки в тестах.