Skip to content

Bot API 9.5, дебют Rate Limiter и суперсилы Composer

23 февраля – 2 марта 2026

Насыщенная неделя для экосистемы GramIO. Telegram Bot API 9.5 полностью поддерживается — теги участников, сущности date_time и право can_manage_tags. Появляется новый плагин @gramio/rate-limit с макросным троттлингом на уровне обработчика. @gramio/format учится парсить HTML прямо в Telegram-сущности. @gramio/composer получает EventContextOf, ContextOf, defineComposerMethods и полноценную систему макросов в духе Elysia. Плюс поддержка sticker/voice/video_note в @gramio/views, генерация CLAUDE.md в create-gramio, кросс-цепочечная дедупликация в @gramio/scenes.

Bot API 9.5 — Теги участников и сущности date_time

Telegram Bot API 9.5 теперь полностью поддерживается во всей экосистеме.

Теги участников: устанавливаем, сбрасываем, проверяем

Боты теперь могут назначать текстовые теги (до 16 символов, без эмодзи) участникам групп и супергрупп. Для этого требуется право can_manage_tags. GramIO предоставляет доступ через новый метод setChatMemberTag и контекстное сокращение:

ts
// Устанавливаем тег через сокращение контекста
bot.command("tag", async (ctx) => {
    if (!ctx.replyToMessage) return ctx.send("Ответьте на сообщение пользователя");
    await ctx.replyToMessage.setMemberTag("VIP");
    await ctx.reply("Тег установлен!");
});

// Удаляем тег, передав undefined (или пустую строку)
await ctx.setMemberTag(undefined);

Новые поля в типе ChatMember:

ПолеТипДоступно в
tagstringChatMemberMember, ChatMemberRestricted
canEditTagbooleanChatMemberRestricted, ChatPermissions
canManageTagsbooleanChatAdministratorRights, ChatMemberAdministrator
senderTagstring | undefinedMessage

Чтобы разрешить другим администраторам устанавливать теги, передайте can_manage_tags: true в promoteChatMember.

Новый тип сущности date_time

Telegram теперь помечает временные метки в сообщениях сущностью date_time. GramIO предоставляет доступ к новым полям MessageEntity:

ts
bot.on("message", (ctx) => {
    const dateEntities = ctx.entities?.filter((e) => e.type === "date_time");
    for (const entity of dateEntities ?? []) {
        console.log(entity.unixTime);       // Unix timestamp
        console.log(entity.dateTimeFormat); // строка формата Telegram
    }
});

Обновлённые пакеты: @gramio/types v9.5.0, gramio v0.7.0, @gramio/contexts v0.5.0, @gramio/keyboards v1.3.1

@gramio/rate-limit v0.0.1 — Троттлинг через макросы

Новый официальный плагин для ограничения частоты запросов

@gramio/rate-limit — новый официальный плагин защиты обработчиков от спама через скользящее окно. Ключевое архитектурное решение: вместо императивных проверок if (!await ctx.rateLimit(...)) return используется система макросов GramIO для декларативных опций на уровне обработчика.

ts
import { Bot } from "gramio";
import { rateLimit } from "@gramio/rate-limit";

const bot = new Bot(process.env.BOT_TOKEN!)
    .extend(
        rateLimit({
            // Опционально: подключите Redis, SQLite, Cloudflare KV…
            // storage: redisStorage(redis),
            onLimitExceeded: async (ctx) => {
                if (ctx.is("message")) await ctx.reply("Слишком много запросов, подождите!");
            },
        }),
    );

// Троттлинг на уровне обработчика — никаких if-проверок в теле
bot.command("pay", (ctx) => {
    // обработка платежа
}, { rateLimit: { limit: 3, window: 60 } });

bot.command("help", (ctx) => ctx.reply("Справка"), {
    rateLimit: {
        id: "help",
        limit: 20,
        window: 60,
        onLimitExceeded: (ctx) => ctx.reply("Слишком много запросов /help!"),
    },
});

await bot.start();

Плагин поставляется с хранилищем в памяти из коробки. Подключите Redis, SQLite или Cloudflare KV через опцию storage из @gramio/storages.

Важно: функция плагина переименована из rateLimitPlugin в rateLimit. Используйте rateLimit.

@gramio/format v0.5.0 — Парсинг HTML в Telegram-сущности

htmlToFormattable(): отправляем HTML без parse_mode

@gramio/format v0.5.0 добавляет htmlToFormattable() — новый субмодуль, конвертирующий HTML-разметку в FormattableString GramIO. Идеально дополняет markdownToFormattable() когда источник контента выдаёт HTML (CMS, TipTap, ProseMirror, HTML от LLM и т.д.).

Подход тот же, что и для Markdown: парсим локально в сущности, отправляем без parse_mode. Невалидный HTML деградирует до обычного текста вместо ошибки.

Установите peer-зависимость:

bash
npm install node-html-parser
bash
yarn add node-html-parser
bash
pnpm add node-html-parser
bash
bun add node-html-parser

Импортируйте из субпути @gramio/format/html:

ts
import { htmlToFormattable } from "@gramio/format/html";
import { Bot } from "gramio";

const bot = new Bot(process.env.BOT_TOKEN!);

bot.command("start", (ctx) => {
    const content = `<h1>Привет!</h1><p><strong>Жирный</strong> и <em>курсив</em></p>
<ul><li>пункт один</li><li>пункт два</li></ul>
<p>Посетите <a href="https://gramio.dev">gramio.dev</a></p>`;

    ctx.send(htmlToFormattable(content));
});

await bot.start();

Поддерживаемые HTML-теги:

HTMLTelegram-сущность
<b>, <strong>bold
<i>, <em>italic
<u>underline
<s>, <del>, <strike>strikethrough
<code>code
<pre><code class="language-js">pre (с языком)
<blockquote>blockquote
<a href="...">text_link
<h1><h6>bold
<ul>, <ol>, <li>обычный текст с маркером/номером
<br>перенос строки

Внимание: API может измениться по мере стабилизации.

join() теперь принимает массивы FormattableString напрямую

У хелпера join() появилась новая перегрузка: можно передавать массив FormattableString напрямую без функции маппинга:

ts
import { join, bold, italic } from "@gramio/format";

const items: FormattableString[] = [bold("раз"), italic("два"), "три"];

// Новое: передаём массив напрямую
const result = join(items, "\n");
// Раньше нужно было: join(items, (x) => x, "\n")

@gramio/composer v0.3.3 — EventContextOf, ContextOf, система макросов

EventContextOf<T, E>: видимость per-event derive в кастомных методах

EventContextOf<TComposer, TEvent> — новый утилитарный тип, извлекающий полный контекст для конкретного события из инстанса composer, включая и глобальные, и per-event derive:

ts
import type { EventContextOf } from "@gramio/composer";

// Per-event derive: виден только в обработчиках 'message'
composer.derive(['message'], () => ({ messageData: "..." }));

// EventContextOf извлекает и глобальные, и per-event derive для 'message'
type MessageCtx = EventContextOf<typeof composer, 'message'>;
// MessageCtx включает 'messageData'

ContextOf<T> и defineComposerMethods(): типобезопасные кастомные методы с derive

ContextOf<T> — извлекает TOut (полный накопленный тип контекста) из инстанса composer:

ts
import type { ContextOf } from "@gramio/composer";

type Ctx = ContextOf<typeof myComposer>; // накопленный контекст

defineComposerMethods() — необходим, когда кастомные методы имеют generic-сигнатуры (TypeScript не может вывести дженерики через вложенные типы):

ts
import { defineComposerMethods, createComposer } from "@gramio/composer";
import type { ComposerLike, ContextOf, Middleware } from "@gramio/composer";

const methods = defineComposerMethods({
    command<TThis extends ComposerLike<TThis>>(
        this: TThis,
        name: string,
        handler: Middleware<MessageCtx & ContextOf<TThis>>,
    ): TThis {
        return this.on("message", (ctx, next) => {
            if (ctx.text === `/${name}`) return handler(ctx, next);
            return next();
        });
    },
});

const { Composer } = createComposer<BaseCtx, EventMap, typeof methods>({
    discriminator: (ctx) => ctx.updateType,
    methods,
});

// Derive автоматически попадает в обработчик — без аннотаций:
new Composer()
    .derive(() => ({ user: { id: 1 } }))
    .command("start", (ctx) => {
        ctx.user.id; // ✅ типизировано
    });

Система макросов в духе Elysia

macro() позволяет регистрировать переиспользуемые поведения, которые обработчики активируют декларативно через объект опций:

ts
bot.macro("adminOnly", {
    preHandler: async (ctx, next) => {
        if (ctx.from?.id !== ADMIN_ID) return ctx.reply("Только для админов");
        return next();
    },
});

// Активируем в любом обработчике через опции:
bot.command("ban", handler, { adminOnly: true });
bot.command("kick", handler, { adminOnly: true });

Исправление изоляции WeakMap-свойств

Критический баг в group() и extend() исправлен. Раньше использовался Object.create(ctx) для изоляции — что ломало WeakMap-backed приватные поля (ленивые геттеры контекста GramIO: ctx.text, ctx.from), поскольку WeakMap не работает через прототипную цепочку.

Исправление: стратегия snapshot/restore вместо Object.create(). Свойства контекста GramIO внутри групп изоляции теперь работают корректно.

@gramio/views v0.1.1 — Поддержка стикеров, голосовых и кружков

Views теперь отправляют стикеры, голосовые и видеокружки

@gramio/views v0.1.1 расширяет поддерживаемые типы медиа: sticker, voice, video_note. У каждого своё поведение при редактировании (Telegram не позволяет менять эти файлы после отправки):

Тип медиаОтправкаПоведение при редактировании
stickerТолько editReplyMarkup (обновление клавиатуры)
voiceeditCaption + клавиатура
video_noteТолько editReplyMarkup (обновление клавиатуры)

Методы рендеринга теперь возвращают правильные типы (RenderSendResult, RenderResult) вместо void.

@gramio/scenes — Кросс-цепочечная дедупликация и extend с EventComposer

Плагины, подключённые на уровне бота, больше не применяются в сценах повторно

Раньше при входе в сцену плагины, уже подключённые на уровне бота, применялись повторно. Теперь движок сцен проверяет основной composer бота и пропускает уже применённые плагины:

ts
const authPlugin = new Plugin("auth").derive(() => ({ user: getUser() }));

const bot = new Bot(token)
    .extend(authPlugin)   // ← применяется здесь
    .extend(scenes);      // сцены пропускают authPlugin внутри себя

Сцены теперь поддерживают extend() с EventComposer

scene.extend() теперь принимает не только Plugin, но и инстансы EventComposer:

ts
const echoScene = new Scene("echo")
    .extend(myEventComposer) // ← новое
    .on("message", (ctx) => ctx.send(ctx.text!));

create-gramio v2.0.0–v2.0.3 — ИИ-инструменты, Broadcast, CLI-пресеты

Генерация CLAUDE.md для ИИ-агентов

npm create gramio@latest теперь генерирует файл CLAUDE.md в корне проекта — контекстный файл для ИИ-агентов (Claude Code, Cursor и т.д.), описывающий технологический стек, архитектуру, команды и включённые плагины.

AI Skills для глубокого знания GramIO

Новые проекты могут подключить GramIO AI Skills (bunx skills add) прямо во время генерации — включено по умолчанию в пресетах recommended и full.

Поддержка плагина Broadcast

Генератор теперь включает @gramio/broadcast как опциональный выбор с автоматической настройкой Redis и graceful shutdown.

CLI-аргументы и пресеты

Полный разбор CLI-аргументов: npm create gramio@latest ./bot --preset=recommended --orm=drizzle --linter=biome — без интерактивных подсказок для CI и скриптов. Три пресета: minimal, recommended, full.

@gramio/files v0.3.2 — Исправление Buffer → Uint8Array

Объекты Buffer теперь корректно конвертируются в Uint8Array перед передачей в конструктор File, что исправляет несовместимость в окружениях без наследования Uint8Array от Buffer. Значения undefined в form data теперь пропускаются.

Сайт документации — масштабное обновление контента

Этот цикл принёс огромное обновление сайта документации — новые страницы, переработки и гайды по всем разделам.

Переработка главной страницы: фокус на типобезопасность с живыми вкладками кода

Главная страница получила полностью новую структуру. Hero-сообщение сместилось с «создавай ботов с удобством» на «строй ботов правильно» с типобезопасностью в центре. Новый раздел «Посмотри в деле» добавляет пять интерактивных вкладок кода: команды и форматирование, клавиатуры и callback-и, i18n, сцены и Composer — каждая показывает реальные паттерны GramIO с derive(), CallbackData, format и guard().

Новая страница «Введение» со сравнением фреймворков

Новая страница /introduction объясняет философию проектирования GramIO на конкретных примерах: распространение типов, форматирование и компоновка плагинов. Включает таблицу сравнения с grammY и Telegraf по типобезопасности, подходу к форматированию, мультирантайм-поддержке, утилитам тестирования и сценам.

Переработка гайда «Начало работы»

Гайд по старту полностью переписан: упрощённый онбординг, исчерпывающие примеры кода (команды, форматирование, клавиатуры, derive, middleware) и раздел с production-паттернами, объясняющий архитектуру на основе общего Composer-плагина.

Новый гайд по Composer: модульная архитектура бота

Новая страница /guides/composer документирует структурирование многофайловых ботов через Composer как систему модулей — паттерн «один файл — один модуль», шаринг контекста через derive()/decorate() с .as("scoped"), типизация ContextOf для вынесенных обработчиков, статические зависимости и рекомендуемая структура файлов. Плюс таблица сравнения Composer vs Plugin.

Добавлены четыре гайда по миграции

Пошаговые гайды с примерами «было/стало» для разработчиков, переходящих с других фреймворков:

  • С grammY — сокращения контекста, middleware, клавиатуры, сессии
  • С TelegrafTelegrafBot, .action().callbackQuery(), ctx.telegramctx.api
  • С puregram — стиль обработчиков, регистрация команд, методы ответа
  • С node-telegram-bot-api — callback-стиль → async/await, полноценный TypeScript

Расширение шпаргалки: derive, guards, сцены, i18n, тестирование

Шпаргалка получила полное расширение: per-request и per-update-type derive(), decorate() для статических значений, примеры guard-ов adminOnly/textOnly с сужением типов, многошаговые сцены с ask(), плюрализация i18n и паттерны тестирования через TelegramTestEnvironment. Добавлены быстрые навигационные якоря ко всем разделам.

Гайд по фильтрам + страница экосистемы @gramio/schema-parser

Новый гайд по фильтрам охватывает фильтрующий .on() с автодискавери через CompatibleEvents, инлайн-фильтры и самостоятельные предикаты (reply, isBot, isPremium, forwardOrigin, senderChat). Отдельная страница документирует @gramio/schema-parser — нативный TypeScript-парсер схемы Telegram API.

allow_paid_broadcast задокументирован в гайде по rate limits

Гайд по ограничениям запросов получил новый раздел о allow_paid_broadcast: до 1 000 сообщений/сек по 0.1 Stars за сообщение, с предупреждением о проверке баланса бота через @BotFather перед массовыми рассылками.

Совет по cache_time добавлен в гайд по инлайн-запросам

Гайд по инлайн-запросам теперь документирует, что context.answer() принимает все параметры answerInlineQuery вторым аргументом, с cache_time (по умолчанию 300 с) как самым полезным. Рекомендуется cache_time: 0 для динамических результатов и во время разработки.