grammy-callback-data
v3.0.0
Published
Type-safe callback data for grammY bots
Maintainers
Readme
grammy-callback-data
Type-safe utility for packing, unpacking, and filtering Telegram callback_data in grammY bots.
Works with both ESM and CommonJS. Requires Node.js 18+.
Installation
pnpm add grammy-callback-data
# or
npm install grammy-callback-data
# or
bun add grammy-callback-dataQuick Start
import { Bot, InlineKeyboard } from "grammy";
import { CallbackData, t } from "grammy-callback-data";
// 1. Define your callback data schema
const PostCD = new CallbackData("post", {
action: t.string(),
postId: t.number(),
isConfirmed: t.boolean(),
});
// 2. Create keyboard with packed callback data
const keyboard = new InlineKeyboard()
.text("👍 Like", PostCD.pack({ action: "like", postId: 123, isConfirmed: false }))
.text("❌ Delete", PostCD.pack({ action: "delete", postId: 123, isConfirmed: false }));
// 3. Use keyboard in your bot
bot.command("post", async (ctx) => {
await ctx.reply("Post actions:", {
reply_markup: keyboard,
});
});
// 4. Handle specific actions using filter
bot.callbackQuery(PostCD.filter({ action: "like" }), async (ctx) => {
const { postId } = PostCD.unpack(ctx.callbackQuery.data);
await ctx.reply(`Post ${postId} liked!`);
});
// Handle delete with confirmation
bot.callbackQuery(PostCD.filter({ action: "delete" }), async (ctx) => {
const { postId, isConfirmed } = PostCD.unpack(ctx.callbackQuery.data);
if (!isConfirmed) {
const confirmKeyboard = new InlineKeyboard().text(
"Confirm Delete",
PostCD.pack({ action: "delete", postId, isConfirmed: true }),
);
await ctx.reply("Are you sure?", {
reply_markup: confirmKeyboard,
});
return;
}
await ctx.reply(`Post ${postId} deleted!`);
});Features
- Type-safe callback data handling with TypeScript
- Compact
prefix:value:...format for Telegram's 64-byte limit - Built-in serializers for
string,number, andboolean - RegExp filters for
bot.callbackQuery(...) - Dual ESM / CJS publish with source maps
API
Creating Callback Data
const callback = new CallbackData("prefix", {
field1: t.string(),
field2: t.number(),
field3: t.boolean(),
});Packing Data
const data = callback.pack({
field1: "value",
field2: 123,
field3: true,
});
// => "prefix:value:123:1"Unpacking Data
const { field1, field2, field3 } = callback.unpack(ctx.callbackQuery.data);Filtering Callbacks
// Match the prefix and schema field count
bot.callbackQuery(callback.filter(), handler);
// Filter by specific fields
bot.callbackQuery(callback.filter({ field1: "value" }), handler);Format and Validation
- The format is
prefix:value:..., with fields in schema key order. An empty schema packs to the prefix alone. - Prefixes must be non-empty. Neither prefixes nor serialized field values may contain
:; values are not escaped or encoded. - Strings must be strings; empty strings are supported by packing, unpacking, and filtering.
- Numbers must be finite. Parsing uses
Number()but rejects empty or whitespace-only strings,NaN, and infinity. Negative numbers, fractions, and exponential notation are supported. - Booleans must be booleans and are encoded as
0or1; other representations are rejected when unpacking. pack()enforces Telegram's 64-byte UTF-8 limit, including the prefix and separators. Unicode characters may occupy multiple bytes.unpack()requires an exact prefix and field count.filter()matches the whole payload, treating supplied values literally; it checks structure and conditions, whileunpack()validates field values.
Invalid inputs throw an Error describing the cause. Custom serializers follow the same parse(value: string) / serialize(value) contract; their serialized values must also be colon-free and fit within the total payload limit.
Development
pnpm install
pnpm buildLicense
MIT
