Handlers
Dispatcher can process a lot of different update types, and for each of them you can register as many handlers as you want.
For each of the handler types, there are 2 ways you can add a handler - either using a specialized method, or with addUpdateHandler
method, as described below
See also: Reference
WARNING
Do not add or remove handlers inside of another handler, this may lead to undefined behaviour.
New message
Whenever a new message is received by the bot, or a message is sent by another client (mostly used for users), new_message
handlers are dispatched:
dp.onNewMessage(async (upd) => {
await upd.answerText('Hey!')
})
dp.onNewMessage(async (upd) => {
await upd.answerText('Hey!')
})
Edit message
Whenever a message is edited (and client receives an update about that*), edit_message
handlers are dispatched:
dp.onEditMessage(async (upd) => {
await upd.replyText('Yes.')
})
dp.onEditMessage(async (upd) => {
await upd.replyText('Yes.')
})
* Telegram might decide not to send these updates in case this message is old enough.
Message group
When message grouping is enabled (see here), message_group
handlers are dispatched when a media group (aka album) is received:
dp.onMessageGroup(async (upd) => {
await upd.replyText('Thanks for the media!')
})
dp.onMessageGroup(async (upd) => {
await upd.replyText('Thanks for the media!')
})
Delete message
Whenever a message is deleted (and client receives an update about that*), delete_message
handlers are dispatched. Note that these updates only contain message ID(s), and not its contents, and it is up to you to check what that ID corresponds to.
dp.onDeleteMessage(async (upd) => {
if (upd.messageIds.includes(42)) {
console.log('Magic message deleted :c')
}
})
dp.onDeleteMessage(async (upd) => {
if (upd.messageIds.includes(42)) {
console.log('Magic message deleted :c')
}
})
Note that for private chats, this does not include user's ID, so you may want to implement some caching if you need that info.
* Telegram might decide not to send these updates in case this message is old enough.
Chat member
Whenever chat member status is changed in a channel/supergroup where the bot is an administrator, chat_member
handlers are dispatched.
dp.onChatMemberUpdate(async (upd) => {
console.log(`${upd.user.displayName} ${upd.type} by ${upd.actor.displayName}`)
})
dp.onChatMemberUpdate(async (upd) => {
console.log(`${upd.user.displayName} ${upd.type} by ${upd.actor.displayName}`)
})
TIP
You can filter by update type using filters.chatMember
:
dp.onChatMemberUpdate(
filters.chatMember('joined'),
async (upd) => {
await upd.client.sendText(upd.chat, `${upd.user.mention()}, welcome to the chat!`)
}
)
dp.onChatMemberUpdate(
filters.chatMember('joined'),
async (upd) => {
await upd.client.sendText(upd.chat, `${upd.user.mention()}, welcome to the chat!`)
}
)
Inline query
Whenever an inline query is sent by user to your bot, inline_query
handlers are dispatched:
dp.onInlineQuery(async (upd) => {
await upd.answer([], {
switchPm: {
text: 'Hello!',
parameter: 'inline_hello'
}
})
})
dp.onInlineQuery(async (upd) => {
await upd.answer([], {
switchPm: {
text: 'Hello!',
parameter: 'inline_hello'
}
})
})
You can learn more about inline queries in Inline Mode section.
Chosen inline result
When a user selects an inline result, and assuming that you have inline feedback feature enabled, chosen_inline_result
handlers are dispatched:
dp.onChosenInlineResult(async (upd) => {
await upd.editMessage({
text: `${result.user.displayName}, thanks for using inline!`
})
})
dp.onChosenInlineResult(async (upd) => {
await upd.editMessage({
text: `${result.user.displayName}, thanks for using inline!`
})
})
As mentioned, these updates are only sent by Telegram when you have enabled inline feedback feature. You can enable it in @BotFather.
It is however noted by Telegram that this should only be used for statistical purposes, and even if probability setting is 100%, not all chosen inline results may be reported (source).
Callback query
Whenever user clicks on a callback button, callback_query
or inline_callback_query
handlers are dispatched, based on the origin of the message:
dp.onCallbackQuery(async (upd) => {
await upd.answer({ text: '🌸' })
})
dp.onInlineCallbackQuery(async (upd) => {
await upd.answer({ text: '🌸' })
})
dp.onCallbackQuery(async (upd) => {
await upd.answer({ text: '🌸' })
})
dp.onInlineCallbackQuery(async (upd) => {
await upd.answer({ text: '🌸' })
})
For messages sent normally by the bot (e.g. using sendText
), callback_query
handlers are dispatched. For messages sent from an inline query (e.g. inside onInlineQuery
), inline_callback_query
handlers are dispatched.
Poll update
Whenever a poll state is updated (stopped, anonymous user has voted, etc.), poll
handlers are dispatched:
dp.onPollUpdate(async (upd) => {
// do something
})
dp.onPollUpdate(async (upd) => {
// do something
})
Bots only receive updates about polls that they have sent.
Note that due to Telegram limitation, sometimes the update does not include the poll itself, and mtcute creates a "stub" poll, that is missing most of the information (including question text, answers text, missing flags like quiz
, etc.). Number of votes per-answer is always there, though.
If you need that missing information, you will need to implement caching by yourself. Bot API (strictly speaking, TDLib) does it internally and thus is able to provide all the needed information autonomously. This is not implemented in mtcute yet.
Poll vote
When a user votes in a public poll, poll_vote
handlers are dispatched:
dp.onPollVote(async (upd) => {
upd.user.sendText('Thanks for voting!')
})
dp.onPollVote(async (upd) => {
upd.user.sendText('Thanks for voting!')
})
Bots only receive updates about polls that they have sent.
This update currently doesn't contain information about the poll, only the poll ID, so if you need that info, you'll have to implement caching yourself.
User status
When a user's online status changes (e.g. user goes offline), and client receives an update about that*, user_status
handlers are dispatched:
dp.onUserStatusUpdate(async (upd) => {
console.log(`User ${upd.userId} is now ${upd.status}`)
})
dp.onUserStatusUpdate(async (upd) => {
console.log(`User ${upd.userId} is now ${upd.status}`)
})
* Telegram might decide not to send these updates in many cases, for example: you don't have an active PM with this user, this user is from a large chat that you aren't currently chatting in, etc.
User typing
When a user's typing status changes, and client receives an update about that*, user_status
handlers are dispatched:
dp.onUserTyping(async (upd) => {
console.log(`${upd.userId} is ${upd.status} in ${upd.chatId}`)
})
dp.onUserTyping(async (upd) => {
console.log(`${upd.userId} is ${upd.status} in ${upd.chatId}`)
})
* Telegram might decide not to send these updates in many cases, for example: you haven't talked to this user for some time, that user/chat is archived, etc.
History read
When history is read in a chat (either by the other party or by you from another client), and client receives an update about that, history_read
handlers are dispatched:
dp.onHistoryRead(async (upd) => {
console.log(`History read in ${upd.chatId} up to ${upd.maxReadId}`)
})
dp.onHistoryRead(async (upd) => {
console.log(`History read in ${upd.chatId} up to ${upd.maxReadId}`)
})
Bot stopped
When a user clicks "Stop bot" button in the bot's profile, bot_stopped
handlers are dispatched:
dp.onBotStopped(async (upd) => {
console.log(`Bot stopped by ${upd.user.id}`)
})
dp.onBotStopped(async (upd) => {
console.log(`Bot stopped by ${upd.user.id}`)
})
Join requests
These updates differ depending on whether the currently logged in user is a bot or not.
Bot
When a user requests to join a group/channel where the current bot is an admin, bot_chat_join_request
handlers are dispatched:
dp.onBotChatJoinRequest(async (upd) => {
console.log(`User ${upd.user.id} wants to join ${upd.chat.id}`)
if (upd.user.id === DUROV) {
await upd.decline()
}
})
dp.onBotChatJoinRequest(async (upd) => {
console.log(`User ${upd.user.id} wants to join ${upd.chat.id}`)
if (upd.user.id === DUROV) {
await upd.decline()
}
})
User
When a user requests to join a group/channel where the current user is an admin, chat_join_request
handlers are dispatched:
dp.onChatJoinRequest(async (upd) => {
console.log(`User ${upd.recentRequesters[0].id} wants to join ${upd.chatId}`)
})
dp.onChatJoinRequest(async (upd) => {
console.log(`User ${upd.recentRequesters[0].id} wants to join ${upd.chatId}`)
})
These updates contain less information than bot join requests, and additional info should be fetched manually if needed
Pre-checkout query
When a user clicks "Pay" button, pre_checkout_query
handlers are dispatched:
dp.onPreCheckoutQuery(async (upd) => {
await upd.approve()
})
dp.onPreCheckoutQuery(async (upd) => {
await upd.approve()
})
Story updates
When a story is posted or modified, story
handlers are dispatched:
dp.onStoryUpdate(async (upd) => {
console.log(`${upd.peer.id} posted or modified a story!`)
})
dp.onStoryUpdate(async (upd) => {
console.log(`${upd.peer.id} posted or modified a story!`)
})
Delete story updates
When a story is deleted, delete_story
handlers are dispatched:
dp.onDeleteStory(async (upd) => {
console.log(`${upd.peer.id} deleted story ${upd.storyId}!`)
})
dp.onDeleteStory(async (upd) => {
console.log(`${upd.peer.id} deleted story ${upd.storyId}!`)
})
Raw updates
Dispatcher only implements the most commonly used updates, but you can still add a handler for a custom MTProto update:
dp.onRawUpdate(
(cl, upd) => upd._ === 'updateUserName',
async (
client: TelegramClient,
update_: tl.TypeUpdate,
peers: PeersIndex,
) => {
const update = update_ as tl.RawUpdateUserName
// ...
}
)
dp.onRawUpdate(
(cl, upd) => upd._ === 'updateUserName',
async (
client: TelegramClient,
update_: tl.TypeUpdate,
peers: PeersIndex,
) => {
const update = update_ as tl.RawUpdateUserName
// ...
}
)
Note that the signature is slightly different: since the update is not parsed by the library, client, raw update and entities are provided as-is.
Raw update handlers are dispatched independently of parsed update handlers (learn more).
addUpdateHandler
Another way to register a handler is to use addUpdateHandler
method.
It supports all the updates that have a specialized methods, and this method is actually used by on*
under the hood. It accepts an object, containing update type, its handler and optionally a check
function, which checks if the update should be handled by this handler (basically a Filter):
dp.addUpdateHandler({
type: 'new_message',
callback: async (msg) => {
...
},
check: filters.media
})
dp.addUpdateHandler({
type: 'new_message',
callback: async (msg) => {
...
},
check: filters.media
})
This may be useful in case you are loading your handlers dynamically.
Removing handlers
It might be useful to remove a previously added handler.
Removing a single handler
To remove a single handler, it must have been added using addUpdateHandler
, and then that object should be passed to removeUpdateHandler
:
const handler = { ... }
dp.addUpdateHandler(handler)
// later
dp.removeUpdateHandler(handler)
const handler = { ... }
dp.addUpdateHandler(handler)
// later
dp.removeUpdateHandler(handler)
Handlers are matched by the object that wraps them, because the same function may be used for multiple different handlers.
Removing handlers by type
To remove all handlers that have the given type, pass this type to removeUpdateHandler
:
dp.removeUpdateHandler('new_message')
dp.removeUpdateHandler('new_message')
Removing all handlers
Pass all
to removeUpdateHandler
:
dp.removeUpdateHandler('all')
dp.removeUpdateHandler('all')