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 и контекстное сокращение:
// Устанавливаем тег через сокращение контекста
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:
| Поле | Тип | Доступно в |
|---|---|---|
tag | string | ChatMemberMember, ChatMemberRestricted |
canEditTag | boolean | ChatMemberRestricted, ChatPermissions |
canManageTags | boolean | ChatAdministratorRights, ChatMemberAdministrator |
senderTag | string | undefined | Message |
Чтобы разрешить другим администраторам устанавливать теги, передайте can_manage_tags: true в promoteChatMember.
Новый тип сущности date_time
Telegram теперь помечает временные метки в сообщениях сущностью date_time. GramIO предоставляет доступ к новым полям MessageEntity:
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 для декларативных опций на уровне обработчика.
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-зависимость:
npm install node-html-parseryarn add node-html-parserpnpm add node-html-parserbun add node-html-parserИмпортируйте из субпути @gramio/format/html:
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-теги:
| HTML | Telegram-сущность |
|---|---|
<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 напрямую без функции маппинга:
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:
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:
import type { ContextOf } from "@gramio/composer";
type Ctx = ContextOf<typeof myComposer>; // накопленный контекстdefineComposerMethods() — необходим, когда кастомные методы имеют generic-сигнатуры (TypeScript не может вывести дженерики через вложенные типы):
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() позволяет регистрировать переиспользуемые поведения, которые обработчики активируют декларативно через объект опций:
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 (обновление клавиатуры) |
voice | ✅ | editCaption + клавиатура |
video_note | ✅ | Только editReplyMarkup (обновление клавиатуры) |
Методы рендеринга теперь возвращают правильные типы (RenderSendResult, RenderResult) вместо void.
@gramio/scenes — Кросс-цепочечная дедупликация и extend с EventComposer
Плагины, подключённые на уровне бота, больше не применяются в сценах повторно
Раньше при входе в сцену плагины, уже подключённые на уровне бота, применялись повторно. Теперь движок сцен проверяет основной composer бота и пропускает уже применённые плагины:
const authPlugin = new Plugin("auth").derive(() => ({ user: getUser() }));
const bot = new Bot(token)
.extend(authPlugin) // ← применяется здесь
.extend(scenes); // сцены пропускают authPlugin внутри себяСцены теперь поддерживают extend() с EventComposer
scene.extend() теперь принимает не только Plugin, но и инстансы EventComposer:
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, клавиатуры, сессии
- С Telegraf —
Telegraf→Bot,.action()→.callbackQuery(),ctx.telegram→ctx.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 для динамических результатов и во время разработки.