chat admin
build moderation bots for groups, supergroups, channels, join requests, permissions, and forum topics.
guard admin commands
don't hand-roll "is this user an admin" per command — @yaebal/guards ships isAdmin/isOwner/hasPermission as live getChatMember lookups, membership() to cache them, and guardOr to answer a denial instead of silently dropping the command.
import { createBot } from "yaebal";
import { guardOr, isAdmin, membership } from "@yaebal/guards";
export const bot = createBot(process.env.BOT_TOKEN!)
// caches the getChatMember lookup isAdmin/isOwner/hasPermission need, with
// event-driven invalidation — without it, every guard check is a live api call.
.install(membership())
.use(guardOr(isAdmin, (ctx) => ctx.reply("admins only")));ban from a reply
a simple admin command usually targets the user in the replied-to message. ctx.ban(userId) fills chat_id from the current chat; pass the target id explicitly, since it
otherwise defaults to whoever sent the command.
import { createBot } from "yaebal";
import { guardOr, isAdmin, membership } from "@yaebal/guards";
const bot = createBot(process.env.BOT_TOKEN!)
.install(membership())
.use(guardOr(isAdmin, (ctx) => ctx.reply("admins only")))
.command("ban", async (ctx) => {
const target = ctx.message?.reply_to_message?.from;
if (!target) return ctx.reply("reply to a user first");
// chat_id comes from ctx; userId defaults to the sender of *this* message
// (i.e. the admin) when omitted — always pass it explicitly here.
await ctx.ban(target.id);
});approve or decline join requests
join requests are opt-in updates. include chat_join_request in allowedUpdates when using long polling.
import { createBot } from "yaebal";
const isAllowed = async (userId: number) => true;
const bot = createBot(process.env.BOT_TOKEN!);
bot.on("chat_join_request", async (ctx) => {
if (await isAllowed(ctx.from.id)) {
await ctx.approve();
} else {
await ctx.decline();
}
});track member changes
chat_member updates tell you when users join, leave, get promoted, or are restricted.
they are useful for audit logs and permission caches.
import { createBot } from "yaebal";
const bot = createBot(process.env.BOT_TOKEN!);
bot.on("chat_member", async (ctx) => {
const update = ctx.update.chat_member!;
console.log(update.from.id, update.old_chat_member.status, "->", update.new_chat_member.status);
});temporary restrictions
ctx.mute(seconds, params) is sugar over restrictChatMember — telegram
lifts the restriction itself once the duration elapses, no cron job needed.
import { createBot } from "yaebal";
const bot = createBot(process.env.BOT_TOKEN!);
bot.command("mute", async (ctx) => {
const target = ctx.message?.reply_to_message?.from;
if (!target) return ctx.reply("reply to a user first");
// restricts (no messages) for 10 minutes, then telegram lifts it automatically.
await ctx.mute(600, { user_id: target.id });
});admin checklist
- ask botfather for the permissions your bot actually needs.
- check
botIsAdmin/botHasPermission(also from@yaebal/guards) before promising a moderation feature — the bot needs the right permission, not just the caller. - handle supergroup migrations if your bot stores old group ids.
- keep audit logs for destructive actions — see observability.
- rate-limit public admin commands to avoid accidental spam.