hooks
three extension points on the Api — before, after, onError — wrap every request, and the error hook drives the retry loop.
the request lifecycle
every call goes through before hooks, the request itself, then after hooks. if the request throws, onError hooks decide whether to retry. plugins hang off
these same three points.
import { createApi } from "@yaebal/core";
const api = createApi(process.env.BOT_TOKEN!)
.before((m, p) => p)
.after((m, r) => r)
.onError((m, e) => undefined);
// each registrar returns the Api, so registration chainsbefore
a before hook receives the method name and params and may return replacement params.
returning undefined leaves them as-is. hooks run in registration order, each seeing
the previous one's output.
// before — inspect or rewrite params; return new params to replace them
bot.api.before((method, params) => {
if (method === "sendMessage") {
return { parse_mode: "HTML", ...params };
}
// return undefined → params unchanged
});after
an after hook receives the method name and the successful result, and may return a
replacement value. returning undefined leaves the result unchanged.
// after — inspect or rewrite the result; return a value to replace it
bot.api.after((method, result) => {
console.log(method, "ok");
// return undefined → result unchanged
});onError and the retry loop
when a request throws, each onError hook is called with the method, the error, and
the 1-based attempt that just failed. the first hook to return { retry: true
} triggers a re-run; an optional delayMs waits before retrying. if no hook
requests a retry, the error is rethrown.
// onError — runs when a request throws; ask for a retry by returning an action
bot.api.onError((method, error, attempt) => {
if (error instanceof TelegramError && error.code === 429 && attempt < 5) {
return { retry: true, delayMs: 1000 };
}
// return undefined (or { retry: false }) → the error is rethrown
});attempt (or use a plugin like
auto-retry) so it terminates.errors
when Telegram replies with ok: false, the call throws a TelegramError carrying the method and numeric code.
import { TelegramError } from "@yaebal/core";
try {
await bot.api.sendMessage({ chat_id: 1, text: "" });
} catch (e) {
if (e instanceof TelegramError) {
e.method; // "sendMessage"
e.code; // error_code from Telegram
e.message; // "[sendMessage] 400: message text is empty"
}
}encodeRequest: JSON vs multipart
the body encoding is chosen per request. plain params (and url/fileId media) serialize to JSON; the moment a path/buffer upload is present the
request becomes multipart with attach:// references. see media for the full encoding rules.
fileUrl
fileUrl builds the download URL for a file_path returned by getFile.
const file = await bot.api.call("getFile", { file_id: id });
const url = bot.api.fileUrl(file.file_path);
// https://api.telegram.org/file/bot<token>/<file_path>
// ^^^^^^^ contains the bot token — never log it