Files
go-telegram/docs/reference/dispatch.md
T
lukaszraczylo f899cc2663 fix(api): generate CallRaw + per-element decode for []<union> returns
GetChatAdministrators returns []ChatMember, where ChatMember is a
sealed-interface union. The codegen template emitted the generic
client.Call[..., []ChatMember] for it — encoding/json cannot unmarshal
a slice of an interface (no discriminator-aware path), so every real
response from Telegram failed at the parse step:

  telegram: parse: json: cannot unmarshal api.ChatMember into
  Go struct field Result[[]ChatMember].Result of type api.ChatMember

Fix is in cmd/genapi/methods.tmpl: add a third branch alongside the
existing single-union branch. When a method returns []<union>,
emit CallRaw + json.Unmarshal into []json.RawMessage + per-element
Unmarshal<Union>(e). Mirrors what GetChatMember (single-element)
already does, applied uniformly so any future slice-of-union method
Telegram introduces inherits the right shape.

Survey of v1.1.1 across all 23 sealed-interface unions confirms
GetChatAdministrators was the only broken site; the fix regenerates
just that one method body. New regression tests in
api/getchatadministrators_test.go cover the typical
admin+owner response and the empty-array case.
2026-05-09 18:57:54 +01:00

27 KiB

dispatch

import "github.com/lukaszraczylo/go-telegram/dispatch"

Package dispatch provides a typed router for Telegram updates. It consumes any transport.Updater and dispatches updates to handlers registered by command, regex, or update-payload kind.

Index

Variables

ErrContinueGroups signals that this group's handler should be treated as not-matching when returned by a handler: dispatch moves on to the next handler in the same group, then to subsequent groups.

Without ErrContinueGroups, a non-error return from a matched handler stops dispatch (default first-match-wins semantics).

var ErrContinueGroups = errors.New("dispatch: continue groups")

ErrEndGroups stops dispatch from running any further handlers in any group for this update when returned by a handler. Use it to indicate the update has been definitively handled.

errors.Is(err, ErrEndGroups) is the canonical check, though dispatch itself recognises it by exact identity.

var ErrEndGroups = errors.New("dispatch: end groups")

type Context

Context bundles the per-update state every handler receives.

Ctx is the request context propagated from Router.Run; cancelling the run cancels every handler.

Bot is the API client. Handlers reply by calling api.SendMessage(c.Ctx, c.Bot, ...) etc.

Update is the raw update; payload-typed handlers also receive a narrowed pointer to one of its sub-fields.

Values is a per-update bag matchers populate. Conventional keys:

"command":      string, the matched bot command (e.g. "/start")
"command_args": string, everything after the command
"regex_match":  []string, regex sub-matches when OnText matches
type Context struct {
    Ctx    context.Context
    Bot    *client.Bot
    Update *api.Update
    Values map[string]any
}

func NewContext

func NewContext(ctx context.Context, b *client.Bot, u *api.Update) *Context

NewContext constructs a Context. Used by Router internally; exposed for custom test harnesses.

type Filter

Filter is a predicate over a typed payload (e.g. *api.Message). Filters compose via And/Or/Not for multi-condition matching.

Example:

f := message.HasPhoto().And(message.InChat(-100123456789))
type Filter[T any] func(payload T) bool

func All

func All[T any](filters ...Filter[T]) Filter[T]

All combines filters with AND. Returns a Filter that matches when all match. Returns a filter that always matches when filters is empty.

func Any

func Any[T any](filters ...Filter[T]) Filter[T]

Any combines filters with OR. Returns a Filter that matches when at least one matches. Returns a filter that never matches when filters is empty.

func (Filter[T]) And

func (f Filter[T]) And(others ...Filter[T]) Filter[T]

And returns a Filter that matches iff f and every one of others matches.

func (Filter[T]) Not

func (f Filter[T]) Not() Filter[T]

Not returns a Filter that inverts f.

func (Filter[T]) Or

func (f Filter[T]) Or(others ...Filter[T]) Filter[T]

Or returns a Filter that matches iff f matches OR any of others matches.

type Handler

Handler is a generic handler over update payload type T. T is typically *api.Message, *api.CallbackQuery, *api.InlineQuery, or *api.Update for global middleware.

type Handler[T any] func(ctx *Context, payload T) error

type Middleware

Middleware wraps a Handler[T] with cross-cutting behaviour (logging, recovery, auth). Middleware composition is left-to-right: Use(a,b,c) runs as a(b(c(handler))).

type Middleware[T any] func(Handler[T]) Handler[T]

func Chain

func Chain[T any](mws ...Middleware[T]) Middleware[T]

Chain composes a slice of middleware into a single Middleware[T].

func Recovery

func Recovery() Middleware[*api.Update]

Recovery returns middleware that recovers from panics in downstream handlers, converting them into a returned error and logging via the bot's configured logger. Registered automatically by NewRouter.

type NamedHandlers

NamedHandlers manages handlers by string name, allowing runtime registration, replacement, and removal. This complements the Router's registration methods: each registration via Named*() also gets a name for later lookup.

Use case: a plugin system that loads/unloads command handlers without restarting the bot.

type NamedHandlers[T any] struct {
    // contains filtered or unexported fields
}

func NewNamedHandlers

func NewNamedHandlers[T any]() *NamedHandlers[T]

NewNamedHandlers returns a new, empty NamedHandlers[T].

func (*NamedHandlers[T]) Handler

func (n *NamedHandlers[T]) Handler() Handler[T]

Handler returns a single Handler[T] that runs each registered handler in registration order, first non-nil error stops the chain. Use this to wire NamedHandlers into a Router.OnXxx call:

names := dispatch.NewNamedHandlers[*api.Message]()
names.Set("logger", loggingHandler)
names.Set("audit", auditHandler)
router.OnCommand("/admin", names.Handler())

Subsequent Set/Remove calls take effect on the next dispatch.

func (*NamedHandlers[T]) Has

func (n *NamedHandlers[T]) Has(name string) bool

Has reports whether name is registered.

func (*NamedHandlers[T]) Names

func (n *NamedHandlers[T]) Names() []string

Names returns the registered names in registration order.

func (*NamedHandlers[T]) Remove

func (n *NamedHandlers[T]) Remove(name string) bool

Remove unregisters the handler under name. Returns true if it existed.

func (*NamedHandlers[T]) Set

func (n *NamedHandlers[T]) Set(name string, h Handler[T])

Set registers or replaces the handler under name. If name is new, it is appended to the end of the registration order.

type Router

Router dispatches updates from any Updater to typed handlers.

Matchers run in registration order; first match wins. A panic-recovery middleware is attached automatically and runs around every dispatch.

type Router struct {
    // contains filtered or unexported fields
}

func New

func New(b *client.Bot, opts ...RouterOption) *Router

New constructs a Router. Recovery middleware is added by default; users can disable it by passing WithoutRecovery (not implemented here, but the hook is in place via Use).

func (*Router) Group

func (r *Router) Group(group int) *RouterScope

Group returns a RouterScope that registers handlers in the given group. Group 0 (the default) runs first, then group 1, etc. Within a group, handlers run in registration order; the first non-skipped match terminates dispatch unless the handler returns ErrContinueGroups.

func (*Router) OnBusinessConnection

func (r *Router) OnBusinessConnection(h Handler[*api.BusinessConnection])

OnBusinessConnection registers a handler for business connection updates.

func (*Router) OnCallback

func (r *Router) OnCallback(pattern string, h Handler[*api.CallbackQuery])

OnCallback registers a handler for callback queries whose Data matches the regex.

Panics at registration time if pattern is not a valid regular expression.

func (*Router) OnCallbackFilter

func (r *Router) OnCallbackFilter(f Filter[*api.CallbackQuery], h Handler[*api.CallbackQuery])

OnCallbackFilter registers a typed callback-query handler gated by filter f. Filter routes are checked after pattern-based OnCallback routes; first match wins.

func (*Router) OnChannelPost

func (r *Router) OnChannelPost(h Handler[*api.Message])

OnChannelPost registers a handler for channel post updates.

func (*Router) OnChatBoost

func (r *Router) OnChatBoost(h Handler[*api.ChatBoostUpdated])

OnChatBoost registers a handler for chat boost updates.

func (*Router) OnChatJoinRequest

func (r *Router) OnChatJoinRequest(h Handler[*api.ChatJoinRequest])

OnChatJoinRequest registers a handler for chat join requests.

func (*Router) OnChatJoinRequestFilter

func (r *Router) OnChatJoinRequestFilter(f Filter[*api.ChatJoinRequest], h Handler[*api.ChatJoinRequest])

OnChatJoinRequestFilter registers a filtered handler for chat join requests.

func (*Router) OnChatMember

func (r *Router) OnChatMember(h Handler[*api.ChatMemberUpdated])

OnChatMember registers a handler for chat member status changes.

func (*Router) OnChatMemberFilter

func (r *Router) OnChatMemberFilter(f Filter[*api.ChatMemberUpdated], h Handler[*api.ChatMemberUpdated])

OnChatMemberFilter registers a filtered handler for chat member status changes.

func (*Router) OnChosenInlineResult

func (r *Router) OnChosenInlineResult(h Handler[*api.ChosenInlineResult])

OnChosenInlineResult registers a handler for chosen inline results.

func (*Router) OnCommand

func (r *Router) OnCommand(cmd string, h Handler[*api.Message])

OnCommand registers a handler for a slash command. The command string includes the leading slash (e.g. "/start"). Matching strips an optional "@BotName" suffix.

func (*Router) OnEditedChannelPost

func (r *Router) OnEditedChannelPost(h Handler[*api.Message])

OnEditedChannelPost registers a handler for edited channel post updates.

func (*Router) OnEditedMessage

func (r *Router) OnEditedMessage(h Handler[*api.Message])

OnEditedMessage registers a handler for edited message updates.

func (*Router) OnInlineQuery

func (r *Router) OnInlineQuery(h Handler[*api.InlineQuery])

OnInlineQuery registers a handler for inline queries (one matcher only; inline queries are not partitioned by content here).

func (*Router) OnInlineQueryFilter

func (r *Router) OnInlineQueryFilter(f Filter[*api.InlineQuery], h Handler[*api.InlineQuery])

OnInlineQueryFilter registers an inline-query handler gated by filter f. Filter routes are checked after bare OnInlineQuery handlers; first match wins.

func (*Router) OnMessageFilter

func (r *Router) OnMessageFilter(f Filter[*api.Message], h Handler[*api.Message])

OnMessageFilter registers a typed message handler gated by filter f. Filter routes are checked after command and text routes; first match wins.

func (*Router) OnMessageReaction

func (r *Router) OnMessageReaction(h Handler[*api.MessageReactionUpdated])

OnMessageReaction registers a handler for message reaction updates.

func (*Router) OnMessageReactionCount

func (r *Router) OnMessageReactionCount(h Handler[*api.MessageReactionCountUpdated])

OnMessageReactionCount registers a handler for anonymous message reaction count updates.

func (*Router) OnMyChatMember

func (r *Router) OnMyChatMember(h Handler[*api.ChatMemberUpdated])

OnMyChatMember registers a handler for bot's own chat member status changes.

func (*Router) OnMyChatMemberFilter

func (r *Router) OnMyChatMemberFilter(f Filter[*api.ChatMemberUpdated], h Handler[*api.ChatMemberUpdated])

OnMyChatMemberFilter registers a filtered handler for bot's own chat member status changes.

func (*Router) OnPoll

func (r *Router) OnPoll(h Handler[*api.Poll])

OnPoll registers a handler for poll state updates.

func (*Router) OnPollAnswer

func (r *Router) OnPollAnswer(h Handler[*api.PollAnswer])

OnPollAnswer registers a handler for poll answer updates.

func (*Router) OnPreCheckoutQuery

func (r *Router) OnPreCheckoutQuery(h Handler[*api.PreCheckoutQuery])

OnPreCheckoutQuery registers a handler for pre-checkout queries.

func (*Router) OnPreCheckoutQueryFilter

func (r *Router) OnPreCheckoutQueryFilter(f Filter[*api.PreCheckoutQuery], h Handler[*api.PreCheckoutQuery])

OnPreCheckoutQueryFilter registers a filtered handler for pre-checkout queries.

func (*Router) OnPurchasedPaidMedia

func (r *Router) OnPurchasedPaidMedia(h Handler[*api.PaidMediaPurchased])

OnPurchasedPaidMedia registers a handler for purchased paid media updates.

func (*Router) OnRemovedChatBoost

func (r *Router) OnRemovedChatBoost(h Handler[*api.ChatBoostRemoved])

OnRemovedChatBoost registers a handler for removed chat boost updates.

func (*Router) OnShippingQuery

func (r *Router) OnShippingQuery(h Handler[*api.ShippingQuery])

OnShippingQuery registers a handler for shipping queries.

func (*Router) OnText

func (r *Router) OnText(pattern string, h Handler[*api.Message])

OnText registers a handler for messages whose Text matches the regex.

Panics at registration time if pattern is not a valid regular expression.

func (*Router) Run

func (r *Router) Run(ctx context.Context, u transport.Updater) error

Run consumes the Updater and dispatches each update. It blocks until the Updater's channel is closed or ctx is cancelled.

By default updates are processed concurrently (up to WithMaxConcurrency(50) goroutines). Handlers for different updates may therefore run simultaneously; shared state must be protected. Pass WithMaxConcurrency(0) to New to restore serial (legacy) behaviour.

Run waits for all in-flight handlers to finish before returning.

func (*Router) Use

func (r *Router) Use(mw Middleware[*api.Update])

Use registers a global middleware applied to every Update dispatch.

type RouterOption

RouterOption configures a Router at construction time.

type RouterOption func(*Router)

func WithMaxConcurrency

func WithMaxConcurrency(n int) RouterOption

WithMaxConcurrency sets the maximum number of updates processed in parallel. Default is 50. Pass 0 to dispatch serially (one update at a time, in the calling goroutine — the legacy behaviour before v1.1.0).

Note: concurrent dispatch means handlers for different updates may run simultaneously. Handlers that mutate shared state must be safe for concurrent access.

type RouterScope

RouterScope registers handlers into a specific priority group on its parent Router. Group 0 runs first, then group 1, etc. Within a group, handlers run in registration order; the first non-skipped match terminates dispatch unless the handler returns ErrContinueGroups.

type RouterScope struct {
    // contains filtered or unexported fields
}

func (*RouterScope) OnCommand

func (s *RouterScope) OnCommand(cmd string, h Handler[*api.Message])

OnCommand registers a command handler in this group.

func (*RouterScope) OnMessageFilter

func (s *RouterScope) OnMessageFilter(f Filter[*api.Message], h Handler[*api.Message])

OnMessageFilter registers a filter-based message handler in this group.

func (*RouterScope) OnText

func (s *RouterScope) OnText(pattern string, h Handler[*api.Message])

OnText registers a regex text handler in this group. Panics at registration time if pattern is not a valid regular expression.

Generated by gomarkdoc