yaebal meta
the batteries-included entry point — the core engine, the auto-generated
per-update contexts, and the most-used plugins behind a single import. media.path() works on node, bun and deno, and the same bot can run behind long polling
or a fetch webhook. for a minimal build, use @yaebal/core directly — the
meta package adds the generated context layer, the formatting/keyboard/callback-data helpers and
a handful of the most-used plugins on top; nothing in core changes underneath it.
install
pnpm add yaebalquick start
import { createBot, html } from "yaebal";
const bot = createBot(process.env.BOT_TOKEN!)
.command("start", (ctx) => ctx.send("hi"))
.on("message:text", (ctx) => ctx.reply(html`you said: <b>${ctx.text}</b>`));
await bot.start();use createBot() for normal app code: it wires runtime rich contexts, so generated
shortcut methods like ctx.react() are both typed and actually present when handlers run.
import { createBot } from "yaebal";
const bot = createBot(process.env.BOT_TOKEN!);
bot.command("start", (ctx) => ctx.reply("hello from yaebal"));
bot.on("message:text", (ctx) => {
console.log("got message:", ctx.text);
ctx.reply(`you said: ${ctx.text}`);
});
bot.onStart((me) => console.log("bot ready"));
bot.start();Bot vs createBot
import { Bot, createBot, richContext } from "yaebal";
const token = process.env.BOT_TOKEN!;
// recommended for app code: rich generated contexts at runtime.
const bot = createBot(token);
// advanced: same Bot class, but you choose the context factory yourself.
const custom = new Bot(token, { contextFactory: richContext });
// bare new Bot(token) still has auto readFile and typed router overloads,
// but does not graft generated context methods at runtime unless you pass
// contextFactory or use createBot().new Bot(token) from yaebal adds the meta
package conveniences such as auto file reading and richer router typings, but it does not install richContext automatically. use createBot(token), or pass { contextFactory: richContext } yourself, when you want generated context methods at
runtime.rich, typed contexts
createBot() grafts the auto-generated shortcut methods onto every update — ctx.react, ctx.editText, ctx.pin, … — typed to the matching
update and backed by the generated context classes.
import { createBot } from "yaebal";
const bot = createBot(process.env.BOT_TOKEN!);
bot.on("message:text", (ctx) => ctx.react("🔥")); // MessageContext
bot.on("callback_query:data", (ctx) => ctx.answer("ok")); // CallbackQueryContextexports
| from | exports | what |
|---|---|---|
| core | Bot, Composer, Context, media, format, createApi, TelegramError, types | the engine, base context, media, entity formatting and API client |
| contexts | all generated context classes and contextFor | per-update context classes and shortcuts |
| yaebal | createBot, richContext | meta-package runtime wiring for generated contexts |
| fmt | html, md, htmlToEntities, mdToEntities | HTML/Markdown parsing into Telegram entities |
| filters | filters, and, or, not | composable, type-narrowing filters |
| keyboard | InlineKeyboard, Keyboard | fluent keyboard builders |
| callback-data | callbackData | typed callback_data pack / unpack |
| session | session | per-chat state, pluggable storage |
| i18n | i18n | per-chat locale, ctx.t |
| sklad | MemoryStorage, redisStorage, sqliteStorage, kvStorage, StorageAdapter | the pluggable storage interface session (and other stateful plugins) build on, plus its adapters |
| again | autoRetry, decideRetry | retries 429 flood-waits and transient 5xx |
| auto-answer | autoAnswer | clears the callback-query spinner, alerts still win the race |
| hydrate | hydrate, hydrateApi, hydrateMessage | a sent message carries editText/delete/pin/react |
| typing | typing | ctx.typing(fn) holds the indicator for as long as fn runs |
| files | files, createFiles, resolveFileId, FileDownload | ctx.files: inspect, stream and download telegram files |
| file-id | FileId, FileUniqueId, fileUniqueIdFromFileId | decode a file_id locally — no api call |
| split | splitter, splitText, splitCaption, MAX_MESSAGE_LENGTH | long text as several messages, entity-aware |
| inline-results | InlineQueryResult, InputMessageContent | typed answerInlineQuery payloads |
| web | webhook, serve, setWebhook, deleteWebhook, getWebhookInfo, dedupe, expressAdapter, fastifyAdapter, elysiaAdapter, awsLambdaAdapter, azureAdapter, gcfAdapter, cloudflareAdapter | fetch/webhook helpers plus adapters for the most common node http frameworks and serverless platforms |
@yaebal/scenes, @yaebal/conversation, @yaebal/prompt, @yaebal/morda, @yaebal/router and others — stay outside the meta
package on purpose. import them directly; see the plugin catalog.a quick tour
keyboards + typed callback data:
import { createBot, InlineKeyboard, callbackData } from "yaebal";
const vote = callbackData("vote", { id: Number });
const bot = createBot(process.env.BOT_TOKEN!);
bot.command("poll", (ctx) =>
ctx.send("pick one", {
reply_markup: new InlineKeyboard()
.text("👍", vote.pack({ id: 1 }))
.text("👎", vote.pack({ id: 2 }))
.build(),
}),
);
bot.callbackQuery(vote.pattern, (ctx) => {
const data = vote.unpack(ctx.callbackQuery.data!);
if (data) return ctx.answer("voted " + data.id);
});per-chat sessions and i18n:
import { createBot, session, i18n } from "yaebal";
const bot = createBot(process.env.BOT_TOKEN!)
.install(session({ initial: () => ({ count: 0 }) }))
.install(i18n({
defaultLocale: "en",
locales: { en: { hi: "hello" }, ru: { hi: "привет" } },
}))
.command("count", (ctx) => ctx.reply("#" + ++ctx.session.count))
.command("start", (ctx) => ctx.reply(ctx.t("hi")));media — no platform package to pick:
import { createBot, media } from "yaebal";
const bot = createBot(process.env.BOT_TOKEN!);
bot.command("pic", (ctx) => ctx.sendPhoto(media.path("./cat.jpg"))); // node/bun/deno
// on edge, send media.url(...) / media.buffer(...) insteadrun on the edge over webhooks:
import { createBot, webhook } from "yaebal";
export default {
fetch(request: Request, env: { BOT_TOKEN: string; SECRET: string }) {
const bot = createBot(env.BOT_TOKEN);
bot.command("start", (ctx) => ctx.reply("running on the edge"));
return webhook(bot, { secretToken: env.SECRET })(request);
},
};Bot exposes a contextFactory hook; richContext builds the base Context and grafts the matching generated context's shortcut methods,
accessors and payload fields onto it. core stays decoupled from @yaebal/contexts — the meta-package does the wiring.