Module

x/grammy/composer.ts

The Telegram Bot Framework.
Go to Latest
class Composer
implements MiddlewareObj<C>
import { Composer } from "https://deno.land/x/grammy@v1.10.1/composer.ts";

The composer is the heart of the middleware system in grammY. It is also the superclass of Bot. Whenever you call use or on or some of the other methods on your bot, you are in fact using the underlying composer instance to register your middleware.

If you're just getting started, you do not need to worry about what middleware is, or about how to use a composer.

On the other hand, if you want to dig deeper into how grammY implements middleware, check out the documentation on the website.

Constructors

new
Composer(...middleware: Array<Middleware<C>>)

Constructs a new composer based on the provided middleware. If no middleware is given, the composer instance will simply make all context objects pass through without touching them.

Properties

private
handler: MiddlewareFn<C>

Methods

branch(
predicate: (ctx: C) => MaybePromise<boolean>,
trueMiddleware: MaybeArray<Middleware<C>>,
falseMiddleware: MaybeArray<Middleware<C>>,
)

This is an advanced method of grammY.

Allows you to branch between two cases for a given context object.

This method takes a predicate function that is tested once per context object. If it returns true, the first supplied middleware is executed. If it returns false, the second supplied middleware is executed. Note that the predicate may be asyncronous, i.e. it can return a Promise of a boolean.

callbackQuery(trigger: MaybeArray<string | RegExp>, ...middleware: Array<CallbackQueryMiddleware<C>>): Composer<CallbackQueryContext<C>>

Registers some middleware for callback queries, i.e. the updates that Telegram delivers to your bot when a user clicks an inline button (that is a button under a message).

This method is essentially the same as calling

bot.on('callback_query:data', ctx => { ... })

but it also allows you match the query data agains a given text or regular expression.

// Create an inline keyboard
const keyboard = new InlineKeyboard().text('Go!', 'button-payload')
// Send a message with the keyboard
await bot.api.sendMessage(chat_id, 'Press a button!', {
  reply_markup: keyboard
})
// Listen to users pressing buttons with that specific payload
bot.callbackQuery('button-payload', ctx => { ... })

// Listen to users pressing any button your bot ever sent
bot.on('callback_query:data', ctx => { ... })

Always remember to call answerCallbackQuery—even if you don't perform any action: https://core.telegram.org/bots/api#answercallbackquery

bot.on('callback_query:data', async ctx => {
  await ctx.answerCallbackQuery()
})

You can pass an array of triggers. Your middleware will be executed if at least one of them matches.

chatType<T extends Chat["type"]>(chatType: MaybeArray<T>, ...middleware: Array<Middleware<ChatTypeContext<C, T>>>): Composer<ChatTypeContext<C, T>>

Registers some middleware for certain chat types only. For example, you can use this method to only receive updates from private chats. The four chat types are "channel", "supergroup", "group", and "private". This is especially useful when combined with other filtering logic. For example, this is how can you respond to /start commands only from private chats:

bot.chatType("private").command("start", ctx => { ... })

Naturally, you can also use this method on its own.

// Private chats only
bot.chatType("private", ctx => { ... });
// Channels only
bot.chatType("channel", ctx => { ... });

You can pass an array of chat types if you want your middleware to run for any of several provided chat types.

// Groups and supergroups only
bot.chatType(["group", "supergroup"], ctx => { ... });

Remember also that you can access the chat type via ctx.chat.type.

command<S extends string>(command: MaybeArray<StringWithSuggestions<
| S
| "start"
| "help"
| "settings"
>>
, ...middleware: Array<CommandMiddleware<C>>
): Composer<CommandContext<C>>

Registers some middleware that will only be executed when a certain command is found.

// Reacts to /start commands
bot.command('start', ctx => { ... })
// Reacts to /help commands
bot.command('help', ctx => { ... })

The rest of the message (excluding the command, and trimmed) is provided via ctx.match.

Did you know? You can use deep linking (https://core.telegram.org/bots#deep-linking) to let users start your bot with a custom payload. As an example, send someone the link https://t.me/name-of-your-bot?start=custom-payload and register a start command handler on your bot with grammY. As soon as the user starts your bot, you will receive custom-payload in the ctx.match property!

bot.command('start', ctx => {
  const payload = ctx.match // will be 'custom-payload'
})

Note that commands are not matched in captions or in the middle of the text.

bot.command('start', ctx => { ... })
// ... does not match:
// A message saying: “some text /start some more text”
// A photo message with the caption “/start”

By default, commands are detected in channel posts, too. This means that ctx.message is potentially undefined, so you should use ctx.msg instead to grab both messages and channel posts. Alternatively, if you want to limit your bot to finding commands only in private and group chats, you can use bot.on('message').command('start', ctx => { ... }), or even store a message-only version of your bot in a variable like so:

const m = bot.on('message')

m.command('start', ctx => { ... })
m.command('help', ctx => { ... })
// etc

If you need more freedom matching your commands, check out the command-filter plugin.

drop(predicate: (ctx: C) => MaybePromise<boolean>, ...middleware: Array<Middleware<C>>)

This is an advanced method of grammY.

Registers middleware behind a custom filter function that operates on the context object and decides whether or not to execute the middleware. In other words, the middleware will only be executed if the given predicate returns false for the given context object. Otherwise, it will be skipped and the next middleware will be executed. Note that the predicate may be asyncronous, i.e. it can return a Promise of a boolean.

This method is the same using filter (normal usage) with a negated predicate.

errorBoundary(errorHandler: (error: BotError<C>, next: NextFunction) => MaybePromise<unknown>, ...middleware: Array<Middleware<C>>)

This is an advanced function of grammY.

Installs an error boundary that catches errors that happen only inside the given middleware. This allows you to install custom error handlers that protect some parts of your bot. Errors will not be able to bubble out of this part of your middleware system, unless the supplied error handler rethrows them, in which case the next surrounding error boundary will catch the error.

Example usage:

function errHandler(err: BotError) {
  console.error('Error boundary caught error!', err)
}

const safe =
  // All passed middleware will be protected by the error boundary.
  bot.errorBoundary(errHandler, middleware0, middleware1, middleware2)

// Those will also be protected!
safe.on('message', middleware3)

// No error from `middleware4` will reach the `errHandler` from above,
// as errors are suppressed.

// do nothing on error (suppress error), and run outside middleware
const suppress = (_err: BotError, next: NextFunction) => { return next() }
safe.errorBoundary(suppress).on('edited_message', middleware4)

Check out the documentation on the website to learn more about error boundaries.

filter<D extends C>(predicate: (ctx: C) => ctx is D, ...middleware: Array<Middleware<D>>): Composer<D>

This is an advanced method of grammY.

Registers middleware behind a custom filter function that operates on the context object and decides whether or not to execute the middleware. In other words, the middleware will only be executed if the given predicate returns true for the given context object. Otherwise, it will be skipped and the next middleware will be executed.

This method has two signatures. The first one is straightforward, it is the one described above. Note that the predicate may be asyncronous, i.e. it can return a Promise of a boolean.

Alternatively, you can pass a function that has a type predicate as return type. This will allow you to narrow down the context object. The installed middleware is then able to operate on this constrained context object.

// NORMAL USAGE
// Only process every second update
bot.filter(ctx => ctx.update.update_id % 2 === 0, ctx => { ... })

// TYPE PREDICATE USAGE
function predicate(ctx): ctx is Context & { message: undefined } {
  return ctx.message === undefined
}
// Only process updates where `message` is `undefined`
bot.filter(predicate, ctx => {
  const m = ctx.message // inferred as always undefined!
  const m2 = ctx.update.message // also inferred as always undefined!
})
filter(predicate: (ctx: C) => MaybePromise<boolean>, ...middleware: Array<Middleware<C>>): Composer<C>
filter(predicate: (ctx: C) => MaybePromise<boolean>, ...middleware: Array<Middleware<C>>)
fork(...middleware: Array<Middleware<C>>)

This is an advanced method of grammY.

Registers some middleware that runs concurrently to the executing middleware stack.

bot.use( ... ) // will run first
bot.fork( ... ) // will be started second, but run concurrently
bot.use( ... ) // will also be run second

In the first middleware, as soon as next's Promise resolves, both forks have completed.

Both the fork and the downstream middleware are awaited with Promise.all, so you will only be to catch up to one error (the one that is thrown first).

In opposite to the other middleware methods on composer, fork does not return simply return the composer connected to the main middleware stack. Instead, it returns the created composer of the fork connected to the middleware stack. This allows for the following pattern.

// Middleware will be run concurrently!
bot.fork().on('message', ctx => { ... })
gameQuery(trigger: MaybeArray<string | RegExp>, ...middleware: Array<GameQueryMiddleware<C>>): Composer<GameQueryContext<C>>

Registers some middleware for game queries, i.e. the updates that Telegram delivers to your bot when a user clicks an inline button for the HTML5 games platform on Telegram.

This method is essentially the same as calling

bot.on('callback_query:game_short_name', ctx => { ... })

but it also allows you match the query data agains a given text or regular expression.

You can pass an array of triggers. Your middleware will be executed if at least one of them matches.

hears(trigger: MaybeArray<string | RegExp>, ...middleware: Array<HearsMiddleware<C>>): Composer<HearsContext<C>>

Registers some middleware that will only be executed when the message contains some text. Is it possible to pass a regular expression to match:

// Match some text (exact match)
bot.hears('I love grammY', ctx => ctx.reply('And grammY loves you! <3'))
// Match a regular expression
bot.hears(/\/echo (.+)/, ctx => ctx.reply(ctx.match[1]))

Note how ctx.match will contain the result of the regular expression. Here it is a RegExpMatchArray object, so ctx.match[1] refers to the part of the regex that was matched by (.+), i.e. the text that comes after “/echo”.

You can pass an array of triggers. Your middleware will be executed if at least one of them matches.

Both text and captions of the received messages will be scanned. For example, when a photo is sent to the chat and its caption matches the trigger, your middleware will be executed.

If you only want to match text messages and not captions, you can do this:

// Only matches text messages (and channel posts) for the regex
bot.on(':text').hears(/\/echo (.+)/, ctx => { ... })
inlineQuery(trigger: MaybeArray<string | RegExp>, ...middleware: Array<InlineQueryMiddleware<C>>): Composer<InlineQueryContext<C>>

Registers middleware for inline queries. Telegram sends an inline query to your bot whenever a user types “@your_bot_name ...” into a text field in Telegram. You bot will then receive the entered search query and can respond with a number of results (text, images, etc) that the user can pick from to send a message via your bot to the respective chat. Check out https://core.telegram.org/bots/inline to read more about inline bots.

Note that you have to enable inline mode for you bot by contacting @BotFather first.

// Listen for users typing “@your_bot_name query”
bot.inlineQuery('query', async ctx => {
  // Answer the inline query, confer https://core.telegram.org/bots/api#answerinlinequery
  await ctx.answerInlineQuery( ... )
})
lazy(middlewareFactory: (ctx: C) => MaybePromise<MaybeArray<Middleware<C>>>): Composer<C>

This is an advanced method of grammY.

Executes some middleware that can be generated on the fly for each context. Pass a factory function that creates some middleware (or a middleware array even). The factory function will be called once per context, and its result will be executed with the context object.

// The middleware returned by `createMyMiddleware` will be used only once
bot.lazy(ctx => createMyMiddleware(ctx))

You may generate this middleware in an async fashion.

You can decide to return an empty array ([]) if you don't want to run any middleware for a given context object. This is equivalent to returning an empty instance of Composer.

middleware()
on<Q extends FilterQuery>(filter: Q | Q[], ...middleware: Array<Middleware<Filter<C, Q>>>): Composer<Filter<C, Q>>

Registers some middleware that will only be executed for some specific updates, namely those matching the provided filter query. Filter queries are a concise way to specify which updates you are interested in.

Here are some examples of valid filter queries:

// All kinds of message updates
bot.on('message', ctx => { ... })

// Only text messages
bot.on('message:text', ctx => { ... })

// Only text messages with URL
bot.on('message:entities:url', ctx => { ... })

// Text messages and text channel posts
bot.on(':text', ctx => { ... })

// Messages with URL in text or caption (i.e. entities or caption entities)
bot.on('message::url', ctx => { ... })

// Messages or channel posts with URL in text or caption
bot.on('::url', ctx => { ... })

You can use autocomplete in VSCode to see all available filter queries. Check out the documentation on the website to learn more about filter queries in grammY.

It is possible to pass multiple filter queries in an array, i.e.

// Matches all text messages and edited text messages that contain a URL
bot.on(['message:entities:url', 'edited_message:entities:url'], ctx => { ... })

Your middleware will be executed if any of the provided filter queries matches (logical OR).

If you instead want to match all of the provided filter queries (logical AND), you can chain the .on calls:

// Matches all messages and channel posts that both a) contain a URL and b) are forwards
bot.on('::url').on(':forward_date', ctx => { ... })
route<R extends Record<string, Middleware<C>>>(
router: (ctx: C) => MaybePromise<undefined | keyof R>,
routeHandlers: R,
fallback?: Middleware<C>,
): Composer<C>

This is an advanced method of grammY.

Not to be confused with the router plugin.

This method is an alternative to the router plugin. It allows you to branch between different middleware per context object. You can pass two things to it:

  1. A routing function
  2. Different middleware identified by key

The routing function decides based on the context object which middleware to run. Each middleware is identified by a key, so the routing function simply returns the key of that middleware.

// Define different route handlers
const routeHandlers = {
  evenUpdates: (ctx: Context) => { ... }
  oddUpdates: (ctx: Context) => { ... }
}
// Decide for a context object which one to pick
const router = (ctx: Context) => ctx.update.update_id % 2 === 0
  ? 'evenUpdates'
  : 'oddUpdates'
// Route it!
bot.route(router, routeHandlers)

Optionally, you can pass a third option that is used as fallback middleware if your route function returns undefined, or if the key returned by your router has no middleware associated with it.

This method may need less setup than first instanciating a Router, but for more complex setups, having a Router may be more readable.

use(...middleware: Array<Middleware<C>>)

Registers some middleware that receives all updates. It is installed by concatenating it to the end of all previously installed middleware.

Often, this method is used to install middleware that behaves like a plugin, for example session middleware.

bot.use(session())

This method returns a new instance of composer. The returned instance can be further extended, and all changes will be regarded here. Confer the documentation on the website if you want to know more about how the middleware system in grammY works, especially when it comes to chaining the method calls (use( ... ).use( ... ).use( ... )).