Updates

Filters

Narrow what a handler matches by passing one or more Filter values to On. The pattern string handles the body match; filters handle everything else.

The idea

gogram's On accepts an arbitrary number of Filter values after the pattern and handler. A handler fires only when every filter returns true. The type is a tiny interface: Check(*NewMessage) bool + CheckCallback(*CallbackQuery) bool. Built-in filters and your own implementations are interchangeable.

Built-in filters

The library ships a set of Filter* values that match the common predicates. The big ones:

FilterPrivate / FilterGroup / FilterChannel
Match by chat kind. Each also has a CheckCallback implementation so the same filter works on inline-keyboard buttons.
FilterCommand
True when the message looks like a bot command (starts with / or !).
FilterReply / FilterForward / FilterEdited
True for replies, forwarded messages, and messages that have been edited at least once.
FilterIncoming / FilterOutgoing
Direction of the message relative to the current session.
FilterFromBot
Sender is a bot. Useful when a userbot wants to react only to messages from other bots.
FilterMention
The current user is mentioned in the message.
HasMedia / HasPhoto / HasVideo / HasDocument / HasAudio / HasSticker / HasAnimation / HasVoice / HasVideoNote / HasContact / HasLocation / HasVenue / HasPoll
Match by media kind.
IsText
The message has non-empty text. Pairs well with a HasPhoto filter when you want photos that came with a caption.

Passing filters to On

client.On("message:*", onlyPrivate, telegram.FilterPrivate)
client.On("message:*", onlyPhotos,  telegram.FilterPhoto)
client.On("message:*", onlyEdited,  telegram.FilterEdited)
client.On("message:*", onlyPrivate, telegram.FilterPrivate)
client.On("message:*", onlyPhotos,  telegram.FilterPhoto)
client.On("message:*", onlyEdited,  telegram.FilterEdited)

Stack multiple filters to require all of them:

client.On("message:*", onMatch,
	telegram.FilterGroup,
	telegram.FilterPhoto,
)
client.On("message:*", onMatch,
	telegram.FilterGroup,
	telegram.FilterPhoto,
)

The handler runs only when the message is from a group and contains a photo.

Pattern strings

The string in "message:body" handles the body match. Three shapes:

  • * matches anything.
  • A literal string matches by substring.
  • A regex pattern prefixed with r: — e.g. the example below pulls an order number out of a command.
client.On("message:r:^/order\\s+(\\d+)$", onOrder)
client.On("message:r:^/order\\s+(\\d+)$", onOrder)

Custom filters

Anything that satisfies the Filter interface works. The library provides telegram.Func as a small helper that wraps a function into a filter:

mine := telegram.Func(func(m *telegram.NewMessage) bool {
	return m.SenderID() == myAdminID
})

client.On("message:*", onAdminMessage, mine)
mine := telegram.Func(func(m *telegram.NewMessage) bool {
	return m.SenderID() == myAdminID
})

client.On("message:*", onAdminMessage, mine)

Use this for the long tail — checking caller permissions, looking up flags in your database, gating on feature flags — without writing a struct.

Global middleware

For cross-cutting concerns that should run on every update regardless of the handler — timing, recovery, structured logging — use client.Use instead of filters:

client.Use(func(handler telegram.Handler) telegram.Handler {
	return func(ctx telegram.HandlerCtx) error {
		start := time.Now()
		err  := handler(ctx)
		log.Printf("[%v] %T finished in %s", err == nil, ctx.Update, time.Since(start))
		return err
	}
})
client.Use(func(handler telegram.Handler) telegram.Handler {
	return func(ctx telegram.HandlerCtx) error {
		start := time.Now()
		err  := handler(ctx)
		log.Printf("[%v] %T finished in %s", err == nil, ctx.Update, time.Since(start))
		return err
	}
})

Middleware wraps the dispatcher pipeline; filters narrow individual handlers. Use the right tool: middleware for cross-cutting, filters for routing.