message extras

telegram messages have many optional controls: replies, link previews, notifications, protection, reactions, captions, entities, keyboards, and effects.

reply parameters

ctx.reply() fills reply parameters for the current message. use raw params when you need custom threading behavior.

reply.ts
import { createBot } from "yaebal";

const bot = createBot(process.env.BOT_TOKEN!);

bot.on("message:text", async (ctx) => {
  await ctx.reply("threaded reply");

  await ctx.send("manual reply", {
    reply_parameters: { message_id: ctx.message!.message_id },
  });
});

ctx.quote() is sugar for the common "reply quoting part of the message" case:

quote.ts
import { createBot } from "yaebal";

const bot = createBot(process.env.BOT_TOKEN!);

// quote() is sugar for reply_parameters.quote — replies pinned to a specific
// substring of the original message instead of the whole thing.
bot.on("message:text", (ctx) => ctx.quote(ctx.text.slice(0, 20), "about that part —"));

link previews

preview.ts
import { createBot } from "yaebal";

const bot = createBot(process.env.BOT_TOKEN!);

bot.command("share", (ctx) =>
  ctx.reply("https://yaebal.mom", {
    link_preview_options: {
      is_disabled: false,
      prefer_large_media: true,
    },
  }),
);

reactions

generated contexts expose convenience shortcuts like ctx.react() when you use createBot() from the meta package — it accepts a bare emoji, an emoji plus a custom emoji id, or no arguments at all to clear the bot's own reaction.

reaction.ts
import { createBot } from "yaebal";

const bot = createBot(process.env.BOT_TOKEN!);

bot.on("message:text", async (ctx) => {
  await ctx.react("🔥");          // one emoji
  await ctx.react("🔥", "12345"); // emoji + a custom emoji id
  await ctx.react();              // no args — clears this bot's reaction
});

other send options

extras.ts
import { createBot, InlineKeyboard } from "yaebal";

const bot = createBot(process.env.BOT_TOKEN!);

bot.command("pick", (ctx) =>
  ctx.reply("pick", {
    protect_content: true,
    disable_notification: true,
    reply_markup: new InlineKeyboard().text("ok", "ok"),
  }),
);

captions and entities

a caption takes the same FormatResult a message body does — @yaebal/fmt's html/md or core's entity builders split into caption/caption_entities automatically, the same splitting ctx.send() does for plain text.

caption.ts
import { createBot, html, media } from "yaebal";

const bot = createBot(process.env.BOT_TOKEN!);

bot.command("pic", (ctx) =>
  // caption accepts the same FormatResult as text — html/md/format results are
  // split into caption + caption_entities automatically, same as ctx.send().
  ctx.sendPhoto(media.url("https://example.com/cat.jpg"), {
    caption: html`<b>cute cat</b>, no filter`,
  }),
);

message effects

message_effect_id plays a full-screen animation on private chats (🎉, 🔥, ❤️, 👍, 👎, 💩 as of this writing) — send-only, private chats only, and only from a real params object (not available on every context shortcut's positional-string sugar).

effect.ts
import { createBot } from "yaebal";

const bot = createBot(process.env.BOT_TOKEN!);

// one of Telegram's small fixed set of animated effect ids (🔥 shown by clients
// as a full-screen animation) — there's no yaebal-side enum, it's a raw id
// telegram assigns; capture the ones you use as named constants in your own code.
const FIRE_EFFECT_ID = "5104841245755180586";

bot.command("celebrate", (ctx) => ctx.reply("🎉", { message_effect_id: FIRE_EFFECT_ID }));

formatting

prefer entity-based formatting through @yaebal/fmt or core builders. it avoids broken markdown/html escaping and makes user interpolation safe.