Filters
Filters is a powerful concept that allows handlers to only process some of the updates and not every single one.
mtcute comes with a lot of filters for different cases, and you can write your own as well.
Common filters
For the full reference, see filters
namespace in the API reference
For many updates, filters.userId
is supported which filters by user ID(s) that issued the update (i.e. message sender, poll voter, etc.):
dp.onNewMessage(
filters.userId(12345678),
async (msg) => {
// ...
}
)
dp.onNewMessage(
filters.userId(12345678),
async (msg) => {
// ...
}
)
There's also filters.chatId
that checks for the chat ID instead:
dp.onNewMessage(
filters.chatId(-100123456789),
async (msg) => {
// ...
}
)
dp.onNewMessage(
filters.chatId(-100123456789),
async (msg) => {
// ...
}
)
filters.chat
allows you to filter by chat type(s):
dp.onNewMessage(
filters.chat('private'),
async (msg) => {
// ...
}
)
dp.onNewMessage(
filters.chat('private'),
async (msg) => {
// ...
}
)
For every media type, there's a filter that will only match that media type: filters.photo
, filters.video
, etc:
dp.onNewMessage(
filters.photo,
async (msg) => {
await msg.replyText('Great photo though')
}
)
dp.onNewMessage(
filters.photo,
async (msg) => {
await msg.replyText('Great photo though')
}
)
For service messages, there's filters.action
that allows filtering by service message action type(s):
dp.onNewMessage(
filters.action('chat_created'),
async (msg) => {
await msg.answerText(`${msg.user.mention()} created ${msg.action.title}`)
}
)
dp.onNewMessage(
filters.action('chat_created'),
async (msg) => {
await msg.answerText(`${msg.user.mention()} created ${msg.action.title}`)
}
)
There are also filters.command
and filters.regex
that add information about the match into the update object:
dp.onNewMessage(
filters.command('start'),
async (msg) => {
if (msg.command[1] === 'from_inline') {
await msg.answerText('Thanks for using inline mode!')
}
}
)
dp.onNewMessage(
filters.regex(/^I'?m (\S+)/i),
async (msg) => {
await msg.replyText(`Hi ${msg.match[1]}, I'm Dad!`)
}
)
dp.onNewMessage(
filters.command('start'),
async (msg) => {
if (msg.command[1] === 'from_inline') {
await msg.answerText('Thanks for using inline mode!')
}
}
)
dp.onNewMessage(
filters.regex(/^I'?m (\S+)/i),
async (msg) => {
await msg.replyText(`Hi ${msg.match[1]}, I'm Dad!`)
}
)
For ChatMember updates, you can use filters.chatMember
to filter by change type:
dp.onChatMemberUpdate(
filters.chatMember('joined'),
async (upd: ChatMemberUpdate) => {
await upd.chat.sendText(`${upd.user.mention()}, welcome to the chat!`)
}
)
dp.onChatMemberUpdate(
filters.chatMember('joined'),
async (upd: ChatMemberUpdate) => {
await upd.chat.sendText(`${upd.user.mention()}, welcome to the chat!`)
}
)
You can also use filters.chatMemberSelf
to filter for actions that were issued by the current user:
dp.onChatMemberUpdate(
filters.and(
filters.chatMemberSelf,
filters.chatMember('joined'),
),
async (upd: ChatMemberUpdate) => {
await addChatToDatabase(upd.chat)
}
)
dp.onChatMemberUpdate(
filters.and(
filters.chatMemberSelf,
filters.chatMember('joined'),
),
async (upd: ChatMemberUpdate) => {
await addChatToDatabase(upd.chat)
}
)
Type modification
Some filters like filters.photo
only match in case msg.media
is a Photo
, and it makes sense to make the handler aware of that and avoid redundant checks in your code.
This is true for most of the built-in filters:
dp.onNewMessage(
async (msg) => {
// msg.media is Photo | Video | ... | null
}
)
dp.onNewMessage(
filters.media,
async (msg) => {
// msg.media is Photo | Video | ...
}
)
dp.onNewMessage(
filters.photo,
async (msg) => {
// msg.media is Photo
}
)
dp.onNewMessage(
async (msg) => {
// msg.media is Photo | Video | ... | null
}
)
dp.onNewMessage(
filters.media,
async (msg) => {
// msg.media is Photo | Video | ...
}
)
dp.onNewMessage(
filters.photo,
async (msg) => {
// msg.media is Photo
}
)
Combining filters
Filters on their own are already pretty powerful, but you can also combine and negate them.
This also modifies the type modification accordingly.
Negating
To negate a filter, use filters.not
:
dp.onNewMessage(
filters.photo,
async (msg) => {
// msg.media is Photo
}
)
dp.onNewMessage(
filters.not(filters.photo),
async (msg) => {
// msg.media is Exclude<MessageMedia, Photo>
}
)
dp.onNewMessage(
filters.photo,
async (msg) => {
// msg.media is Photo
}
)
dp.onNewMessage(
filters.not(filters.photo),
async (msg) => {
// msg.media is Exclude<MessageMedia, Photo>
}
)
Logical addition
Logical addition (i.e. OR operator) is supported with filters.or
dp.onNewMessage(
filters.or(filters.video, filters.photo),
async (msg) => {
// msg.media is Photo | Video
}
)
dp.onNewMessage(
filters.or(filters.video, filters.photo),
async (msg) => {
// msg.media is Photo | Video
}
)
Logical multiplication
Logical multiplication (i.e. AND operator) is supported with filters.and
.
dp.onNewMessage(
filters.and(filters.chat('private'), filters.photo),
async (msg) => {
// msg.media is Photo
}
)
dp.onNewMessage(
filters.and(filters.chat('private'), filters.photo),
async (msg) => {
// msg.media is Photo
}
)
Custom filters
Sometimes pre-existing filters are just not enough. Then, you can write a custom filter.
Simple custom filter
Under the hood, filters are simply functions, so you can do the following:
dp.onNewMessage(
(msg) => msg.sender.isVerified,
async (msg) => {
// ...
}
)
dp.onNewMessage(
(msg) => msg.sender.isVerified,
async (msg) => {
// ...
}
)
It can even be asynchronous:
dp.onNewMessage(
async (msg) => await shouldProcessMessage(msg.id),
async (msg) => {
// ...
}
)
dp.onNewMessage(
async (msg) => await shouldProcessMessage(msg.id),
async (msg) => {
// ...
}
)
Parameters
To accept parameters in your custom filter, simply create a function that returns a filter:
const usernameRegex = (regex: RegExp): UpdateFilter<Message> =>
(msg) => {
const m = msg.sender.username?.match(regex)
return !!m
}
dp.onNewMessage(
usernameRegex(/some_regex/),
async (msg) => {
// ...
}
)
const usernameRegex = (regex: RegExp): UpdateFilter<Message> =>
(msg) => {
const m = msg.sender.username?.match(regex)
return !!m
}
dp.onNewMessage(
usernameRegex(/some_regex/),
async (msg) => {
// ...
}
)
Adding type modification
You can also add type modification to your custom filter:
const fromChat: UpdateFilter<Message, { sender: Chat }> =
(msg) => msg.sender.type === 'chat'
dp.onNewMessage(
fromChat,
async (msg) => {
// msg.sender is Chat
}
)
const fromChat: UpdateFilter<Message, { sender: Chat }> =
(msg) => msg.sender.type === 'chat'
dp.onNewMessage(
fromChat,
async (msg) => {
// msg.sender is Chat
}
)
WARNING
This is used as-is, it is not checked if the filter actually checks for the given modification due to TypeScript limitations.
Additional fields
You can add additional fields to the update using type modifications. This may be useful in multiple cases, for example, to add the result of the parametrized filter:
const usernameRegex = (regex: RegExp): UpdateFilter<
Message,
{ usernameMatch: RegExpMatchArray }
> => (msg) => {
const m = msg.sender.username?.match(regex)
if (m) {
;(obj as any).usernameMatch = m
return true
}
return false
}
dp.onNewMessage(
usernameRegex(/some_regex/),
async (msg) => {
// msg.usernameMatch is RegExpMatchArray
}
)
const usernameRegex = (regex: RegExp): UpdateFilter<
Message,
{ usernameMatch: RegExpMatchArray }
> => (msg) => {
const m = msg.sender.username?.match(regex)
if (m) {
;(obj as any).usernameMatch = m
return true
}
return false
}
dp.onNewMessage(
usernameRegex(/some_regex/),
async (msg) => {
// msg.usernameMatch is RegExpMatchArray
}
)
Or, you can add some additional fields similar to a middleware in other frameworks:
const loadSenderFromDb: UpdateFilter<Message, { senderDb: UserModel }> =
async (msg) => {
;(msg as any).senderDb = await db.loadUser(msg.sender.id)
return true
}
dp.onNewMessage(
filters.and(filters.chat('private'), loadSenderFromDb),
async (msg) => {
// msg.senderDb is UserModel
}
)
const loadSenderFromDb: UpdateFilter<Message, { senderDb: UserModel }> =
async (msg) => {
;(msg as any).senderDb = await db.loadUser(msg.sender.id)
return true
}
dp.onNewMessage(
filters.and(filters.chat('private'), loadSenderFromDb),
async (msg) => {
// msg.senderDb is UserModel
}
)
WARNING
While this is possible, this is strongly not recommended.
Instead, do this right inside the handler code:
dp.onNewMessage(
filters.chat('private'),
async (msg) => {
const senderDb = await db.loadUser(msg.sender.id)
}
)
dp.onNewMessage(
filters.chat('private'),
async (msg) => {
const senderDb = await db.loadUser(msg.sender.id)
}
)