import { type Context } from "./context.js"; import type { Animation, Audio, CallbackQuery, Contact, Dice, Document, Game, Invoice, Location, Message, MessageEntity, PhotoSize, Poll, Sticker, SuccessfulPayment, Update, UpdateName, Venue, Video, VideoNote, Voice, WebAppData } from "./telegram-types.js"; export type NextFn = () => Promise; export type Middleware = (ctx: C, next: NextFn) => unknown | Promise; /** * a plugin enriches the context. dependencies are expressed by the type it * requires (`In`), so installing a plugin before its dependency is a compile * error — not a runtime surprise (core invariant #4). * * @example * const session: Plugin; * const auth: Plugin; * bot.install(auth); // ❌ no `session` on the context * bot.install(session).install(auth); // ✅ */ export type Plugin> = (composer: Composer) => Composer; /** * a composable filter (the mtcute idea, made two-phase). a filter is a predicate * over the context — sync or async — that may *stage* extra fields in `bag` * (e.g. `regex` stages `ctx.match`). nothing touches the context until the whole * filter tree matches: `composer.filter()` commits the bag onto the context only * on success, so a failing branch can never leak or corrupt data. `Add` describes * what the handler gains — staged bag fields and/or purely type-level narrowing * (e.g. `chatType("private")` narrows `ctx.chat` without staging anything). * combine with `and` / `or` / `not` from `@yaebal/filters`; any bare * `(ctx) => boolean` predicate is already a valid `Filter`. */ export type Filter> = { (ctx: C, bag: Record): boolean | Promise; /** type-level carrier for `Add` — never present at runtime. */ readonly "~adds"?: Add; }; /** * the structural shape of a `@yaebal/callback-data` namespace that `callbackQuery` * can route on — matched by value and decoded to `T`, exposed to handlers as * `ctx.queryData`. kept structural so core takes no dependency on the plugin. */ export interface CallbackDataMatcher { readonly pattern: RegExp; unpack(raw: string): T | undefined; } /** * filter query mini-language (the grammY idea), e.g. * `"message:text"`, `"callback_query:data"`, `":photo"`. */ export type FilterQuery = UpdateName | `${UpdateName}:${string}` | `:${string}`; /** `message:` content fields whose presence `on()` can narrow (L2 of the FilterQuery grammar). */ export interface MediaField { photo: PhotoSize[]; video: Video; sticker: Sticker; audio: Audio; voice: Voice; document: Document; animation: Animation; contact: Contact; location: Location; poll: Poll; dice: Dice; venue: Venue; video_note: VideoNote; game: Game; invoice: Invoice; successful_payment: SuccessfulPayment; web_app_data: WebAppData; } /** * the L1 update types whose payload is a `Message` — exactly what `messageOf` * unwraps, so `on()` can narrow `ctx.message` to non-optional * (`ctx.updateType === Q` ⇒ that key is present ⇒ `messageOf` returns it). * keep in lockstep with `messageOf` in context.ts. */ export type MessageUpdate = "message" | "edited_message" | "channel_post" | "edited_channel_post" | "business_message" | "edited_business_message"; /** * narrows the handler context type for the query grammar the runtime (`matchQuery` * / `checkField`) actually gates on — so a narrowed field is one the runtime has * verified. resolution order matches the runtime's field precedence: * * 1. `…:text` / `…:caption` → `ctx.text: string` (checkField: non-empty string) * 2. `…:data` / `callback_query` → `ctx.callbackQuery: CallbackQuery` * 3. `…:entities…` → `ctx.entities: MessageEntity[]` (covers `…:entities` and the * `…:entities:` L3 form; the runtime only gates on `entities` presence — * the L3 entity *subtype* is not applied at runtime, so we narrow no further) * 4. `…:` where `` is any `Message` key → `ctx.message` gains that * field non-optional (checkField default: `Boolean(msg[field])`). subsumes the * media fields and every other message content field (`new_chat_members`, * `pinned_message`, `reply_to_message`, `caption_entities`, …). * 5. bare `MessageUpdate` → `ctx.message: Message` (the update carries a message) * 6. bare `UpdateName` → that update key on `ctx.update` becomes non-optional * * anything the runtime can't verify (an unknown ``, the `::` shortcut * whose empty L1 makes checkField test `msg[""]`, an entity subtype at L3) falls * through to `C` unchanged — matched-or-not at runtime, never an unsound narrow. */ export type Filtered = Q extends `${string}:text` | `${string}:caption` ? C & { text: string; } : Q extends `${string}:data` | "callback_query" ? C & { callbackQuery: CallbackQuery; } : Q extends `${string}:entities${string}` ? C & { entities: MessageEntity[]; } : Q extends `${string}:${infer Field}` ? Field extends keyof Message ? C & { message: Message & Required>; } : C : Q extends MessageUpdate ? C & { message: Message; } : Q extends UpdateName ? C & { update: Required>; } : C; /** koa-style middleware composer with single-`next()` protection. */ export declare function compose(middlewares: Middleware[]): (ctx: C, next?: NextFn) => Promise; /** update types whose text can carry a fresh (non-edited) command. */ export declare const COMMAND_UPDATES: ReadonlySet; /** * `String.match` with a `g`/`y` regex is stateful across calls (`lastIndex` * persists on the shared RegExp), so a trigger could silently skip every other * update. reset before matching — each update matches from the start. */ export declare function matchOf(text: string, re: RegExp): RegExpMatchArray | null; export declare function matchQuery(ctx: Context, query: string): boolean; /** * the chainable middleware pipeline. every context-enriching method returns a * composer whose context type carries the new properties — types flow through * the whole chain (the GramIO idea). `Composer` is also usable standalone, so * feature files can be plain composers with no `Bot` and no token. */ export declare class Composer { protected middlewares: Middleware[]; protected decorations: object[]; /** raw middleware. call `next()` to continue the chain. */ use(...middleware: Middleware[]): this; /** handle a specific update, optionally narrowed by a filter query. */ on(query: Q, ...handlers: Middleware>[]): this; /** * handle `/` commands. matches the text of fresh messages only (an edited * `/cmd` doesn't re-fire), case-insensitively (`/Start` hits `command("start")`), * strips a trailing `@botname` — and when the bot's username is known (`ctx.me`, * filled by long polling) a mismatching mention (`/cmd@other_bot`) is skipped. * exposes `ctx.command`, whitespace-split `ctx.args`, and the raw trimmed * remainder as `ctx.payload` (deep-link parameters arrive intact). */ command(name: string, ...handlers: Middleware[]): this; /** match message text/caption against a string or regex; exposes `ctx.match`. */ hears(trigger: string | RegExp, ...handlers: Middleware[]): this; /** * route callback-query data. pass a `@yaebal/callback-data` namespace to validate + * decode the payload and expose it, typed, as `ctx.queryData` — handlers run only on a * clean unpack, so there's no `filter`-then-`unpack` gap. */ callbackQuery(data: CallbackDataMatcher, ...handlers: Middleware[]): this; /** match callback-query data against a string or regex; exposes `ctx.match`. */ callbackQuery(trigger: string | RegExp, ...handlers: Middleware[]): this; /** * narrowing form: a type-guard predicate types everything registered after it * as `C2` — the type flows down the chain, like `derive`/`filter`. */ guard(predicate: (ctx: C) => ctx is C2): Composer; /** continue only if the predicate holds. */ guard(predicate: (ctx: C) => boolean | Promise): this; /** * run `handlers` only when `filter` matches; handlers see `C & Add`. the * filter may be sync or async, and any bare `(ctx) => boolean` predicate * works. fields the filter staged in its bag are committed onto the context * only here, after the whole filter tree matched — a rejected filter leaves * the context untouched. compose filters with `and` / `or` / `not` from * `@yaebal/filters`. */ filter>(filter: Filter, ...handlers: Middleware[]): this; /** apply a plugin. its required context (`In`) is checked at compile time. */ install(plugin: (composer: Composer) => Composer): Composer; /** async, per-request context enrichment. adds `D` to the context type. */ derive(fn: (ctx: C) => D | Promise): Composer; /** * scoped enrichment (the GramIO idea): `fn` runs only for the listed update * types, so irrelevant updates pay nothing. the fields are typed as optional * (`Partial`) since they are absent on other update types. */ derive(updates: UpdateName | UpdateName[], fn: (ctx: C) => D | Promise): Composer>; /** static context enrichment. adds `D` to the context type without adding middleware hops. */ decorate(value: D): Composer; /** merge another composer in, inheriting its full context type. */ extend(other: Composer): Composer; /** collapse this composer into a single middleware (used by `extend` and `Bot`). */ toMiddleware(): Middleware; } //# sourceMappingURL=composer.d.ts.map