import { Composer } from "https://dotland.deno.dev/x/grammy@v1.20.2/mod.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
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.
Type Parameters
Properties
Methods
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 asynchronous, i.e. it can return a Promise of a
boolean.
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 to match the query data against 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.
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
.
Registers middleware for the ChosenInlineResult by the given id or ids. ChosenInlineResult represents a result of an inline query that was chosen by the user and sent to their chat partner. Check out https://core.telegram.org/bots/api#choseninlineresult to read more about chosen inline results.
bot.chosenInlineResult('id', async ctx => {
const id = ctx.result_id;
// Your code
})
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/features#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 thectx.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 commands
plugin.
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 asynchronous, i.e. it can return a Promise of a boolean.
This method is the same using filter
(normal usage) with a negated
predicate.
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.
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 asynchronous, 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!
})
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 => { ... })
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 to match the query data against 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.
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 => { ... })
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( ... )
})
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
.
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 VS Code 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_origin', ctx => { ... })
Registers some middleware that will only be added when a new reaction of the given type is added to a message.
// Reacts to new '👍' reactions
bot.reaction('👍', ctx => { ... })
// Reacts to new '👍' or '👎' reactions
bot.reaction(['👍', '👎'], ctx => { ... })
Note that you have to enable
message_reaction
updates inallowed_updates
if you want your bot to receive updates about message reactions.
bot.reaction
will trigger if:
- a new emoji reaction is added to a message
- a new custom emoji reaction is added a message
bot.reaction
will not trigger if:
- a reaction is removed
- an anonymous reaction count is updated, such as on channel posts
message_reaction
updates are not enabled for your bot
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:
- A routing function
- 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 instantiating a Router
, but
for more complex setups, having a Router
may be more readable.
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( ... )
).