@yaebal/filters
composable, type-narrowing update filters (the mtcute idea) for the core composer.filter(...) method — combine with and / or / not, and some attach typed data to the context.
install
pnpm add @yaebal/filtersthe filter() method
filter() lives in core. It runs its handlers only when the filter matches, and because
a filter is a type guard, the handler context is narrowed to C & Add — including
any data the filter attached.
// composer.filter(filter, ...handlers) — runs handlers only when the filter matches.
// the filter is a TYPE GUARD, so handlers see C & Add:
bot.filter(command("add"), (ctx) => {
ctx.command; // string
ctx.args; // string[] ← added by the filter, fully typed
});usage
import { and, or, not, command, regex, isPrivate, fromUser, mediaType } from "@yaebal/filters";
bot.filter(and(isPrivate, command("buy")), (ctx) => ctx.args); // ctx.args: string[]
bot.filter(regex(/^\d+$/), (ctx) => ctx.match[0]); // ctx.match: RegExpMatchArray
bot.filter(or(mediaType("photo"), mediaType("video")), handler);
bot.filter(not(fromUser(BANNED_ID)), handler);built-in filters
| filter | matches | attaches |
|---|---|---|
text | non-empty message text | ctx.text: string |
regex(re) | text matches re | ctx.match: RegExpMatchArray |
command(name?) | a /command | ctx.command, ctx.args |
chatType(...t) | chat type in t | — |
isPrivate / isGroup | shorthand chat types | — |
fromUser(...ids) | sender id in ids | — |
chatId(...ids) | chat id in ids | — |
mediaType(...k) | message has media kind k | — |
media | any media | — |
hasEntity(type) | message has an entity of type | — |
combinators
and(a, b, …)— all must match; the attached additions intersect.or(a, b, …)— any matches; no additions (the matched branch is unknown).not(a)— inverts; no additions.
All of them are also under one namespace, mtcute-style:
import { filters } from "@yaebal/filters" then filters.command(...), filters.and(...).custom filters
A filter is just an object with a type-guard test. Return true after
(optionally) attaching data — exactly how regex exposes ctx.match.
import type { Filter, Context } from "@yaebal/core";
// a filter is just a type guard that may attach data:
const fromAdmin: Filter<Context, { isAdmin: true }> = {
test(ctx): ctx is Context & { isAdmin: true } {
if (!ADMINS.has(ctx.from?.id ?? 0)) return false;
Object.assign(ctx, { isAdmin: true });
return true;
},
};
bot.filter(fromAdmin, (ctx) => ctx.isAdmin);Filter queries (
on("message:text")) still exist and are great for the common case — filter() adds composition, custom predicates, and data attachment on top.