ext

package
v0.2.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jul 19, 2026 License: MIT Imports: 15 Imported by: 0

README

ext package quick start

ext provides application-layer routing and bounded update dispatch on top of tgbot.Bot:

  • ordered and nested handler groups;
  • global and group middleware;
  • filters, priorities, first-match routing, and propagation control;
  • a fixed-size worker pool with a bounded queue;
  • synchronous and enqueue-first webhook acknowledgement;
  • long polling with bounded backpressure.

Minimal setup

package main

import (
    "context"
    "net/http"
    "time"

    "github.com/cloudapp3/tgbot"
    "github.com/cloudapp3/tgbot/ext"
)

func main() {
    bot, _ := tgbot.NewBot("<BOT_TOKEN>")
    app, _ := ext.NewApplication(bot)

    app.AddHandler(ext.NewCommandHandler("start", func(ctx context.Context, c *ext.Context) error {
        msg := c.EffectiveMessage()
        if msg == nil || msg.Chat == nil {
            return nil
        }
        _, err := c.Bot.SendMessage(ctx, &tgbot.SendMessageParams{
            ChatID: msg.Chat.ID,
            Text:   "hello",
        })
        return err
    }))

    mux := http.NewServeMux()
    mux.Handle("/telegram/webhook", app.WebhookHandler("<SECRET_TOKEN>"))
    server := &http.Server{
        Addr:              ":8080",
        Handler:           mux,
        ReadHeaderTimeout: 5 * time.Second,
        ReadTimeout:       15 * time.Second,
        WriteTimeout:      45 * time.Second,
        IdleTimeout:       60 * time.Second,
    }
    _ = server.ListenAndServe()
}

WebhookHandler remains synchronous: it returns 200 only after all matching handlers complete successfully.

Groups and middleware

Explicit groups run by ascending priority. At most the first matching handler in each explicit group runs. Application-level AddHandler registrations keep the legacy behavior where every matching registration runs.

app.Use(func(next ext.HandlerFunc) ext.HandlerFunc {
    return func(ctx context.Context, update *ext.Context) error {
        return next(ctx, update)
    }
})

commands := app.GroupWithPriority(-10, ext.CommandFilter())
commands.AddHandler(ext.NewCommandHandler("start", startHandler))
commands.AddHandler(ext.NewCommandHandler("help", helpHandler))

admin := commands.Group(adminFilter)
admin.Use(auditMiddleware)
handle := admin.AddHandler(ext.NewCommandHandler("status", statusHandler))
defer handle.Remove()

Child groups dynamically inherit parent filters and middleware. A handler can call Context.StopPropagation() to prevent later groups from running.

Bounded webhook dispatch

Create and own the dispatcher explicitly when a webhook should acknowledge an update after admission rather than after processing:

app, _ := ext.NewApplication(
    bot,
    ext.WithMaxConcurrentUpdates(8),
    ext.WithUpdateQueueSize(128),
    ext.WithHandlerTimeout(30*time.Second),
    ext.WithPanicHandler(func(ctx context.Context, update *ext.Context, err *ext.PanicError) {
        log.Printf("update panic: %v", err)
    }),
)

dispatcher, _ := ext.NewDispatcher(app)
defer func() {
    shutdownCtx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
    defer cancel()
    _ = dispatcher.Shutdown(shutdownCtx)
}()

handler := app.WebhookHandlerWithOptions(
    "<SECRET_TOKEN>",
    ext.AckAfterEnqueue,
    ext.WithWebhookDispatcher(dispatcher),
)

AckAfterEnqueue returns 503 when the queue is full or the dispatcher is closed, so Telegram can retry. AckAfterProcess can also use a dispatcher while retaining synchronous acknowledgement. The dispatcher must be created from the same Application; a mismatched dispatcher is rejected with 503.

Enqueue waits for queue capacity. TryEnqueue returns ErrUpdateQueueFull immediately. Shutdown stops admission and drains accepted updates until its context expires. Handler timeouts cancel the handler context; handlers must observe cancellation to stop promptly.

Long polling

RunPolling owns an internal bounded dispatcher configured by the application. The default error policy processes updates concurrently up to the configured worker count while applying queue backpressure. WithContinueOnError(false) uses strict serial processing so it can stop at the first handler error.

ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM)
defer stop()

app, _ := ext.NewApplication(
    bot,
    ext.WithMaxConcurrentUpdates(4),
    ext.WithUpdateQueueSize(64),
)

_ = app.RunPolling(ctx,
    ext.WithPollingAllowedUpdates(
        ext.UpdateTypeMessage,
        ext.UpdateTypeCallbackQuery,
    ),
)

WithPollingNonBlockingDispatch explicitly permits the root poller to drop local updates when downstream delivery is full. Leave it disabled when every update must receive backpressure instead of being dropped.

Useful helpers

Handlers:

  • NewAnyHandler
  • NewTypeHandler
  • NewMessageHandler
  • NewCommandHandler
  • NewCallbackQueryHandler

Filters:

  • TextFilter
  • CommandFilter
  • RegexFilter
  • UpdateTypeFilter
  • And, Or, and Not

Documentation

Index

Constants

View Source
const (
	UpdateTypeUnknown                 = tg.UpdateTypeUnknown
	UpdateTypeMessage                 = tg.UpdateTypeMessage
	UpdateTypeEditedMessage           = tg.UpdateTypeEditedMessage
	UpdateTypeChannelPost             = tg.UpdateTypeChannelPost
	UpdateTypeEditedChannelPost       = tg.UpdateTypeEditedChannelPost
	UpdateTypeBusinessConnection      = tg.UpdateTypeBusinessConnection
	UpdateTypeBusinessMessage         = tg.UpdateTypeBusinessMessage
	UpdateTypeEditedBusinessMessage   = tg.UpdateTypeEditedBusinessMessage
	UpdateTypeDeletedBusinessMessages = tg.UpdateTypeDeletedBusinessMessages
	UpdateTypeGuestMessage            = tg.UpdateTypeGuestMessage
	UpdateTypeMessageReaction         = tg.UpdateTypeMessageReaction
	UpdateTypeMessageReactionCount    = tg.UpdateTypeMessageReactionCount
	UpdateTypeInlineQuery             = tg.UpdateTypeInlineQuery
	UpdateTypeChosenInlineResult      = tg.UpdateTypeChosenInlineResult
	UpdateTypeCallbackQuery           = tg.UpdateTypeCallbackQuery
	UpdateTypeShippingQuery           = tg.UpdateTypeShippingQuery
	UpdateTypePreCheckoutQuery        = tg.UpdateTypePreCheckoutQuery
	UpdateTypePurchasedPaidMedia      = tg.UpdateTypePurchasedPaidMedia
	UpdateTypePoll                    = tg.UpdateTypePoll
	UpdateTypePollAnswer              = tg.UpdateTypePollAnswer
	UpdateTypeMyChatMember            = tg.UpdateTypeMyChatMember
	UpdateTypeChatMember              = tg.UpdateTypeChatMember
	UpdateTypeChatJoinRequest         = tg.UpdateTypeChatJoinRequest
	UpdateTypeChatBoost               = tg.UpdateTypeChatBoost
	UpdateTypeRemovedChatBoost        = tg.UpdateTypeRemovedChatBoost
	UpdateTypeManagedBot              = tg.UpdateTypeManagedBot
	UpdateTypeSubscription            = tg.UpdateTypeSubscription
)

Variables

View Source
var (
	// ErrUpdateQueueFull indicates that TryEnqueue could not admit an update.
	ErrUpdateQueueFull = errors.New("telegram update queue is full")
	// ErrDispatcherClosed indicates that a dispatcher no longer accepts updates.
	ErrDispatcherClosed = errors.New("telegram update dispatcher is closed")
)
View Source
var AllUpdateTypes = append([]UpdateType(nil), tg.AllUpdateTypes...)

AllUpdateTypes lists every update type supported by the root SDK.

Functions

This section is empty.

Types

type Application

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

Application provides PTB-like update routing on top of tg.Bot.

func NewApplication

func NewApplication(bot *tg.Bot, opts ...Option) (*Application, error)

NewApplication creates an update dispatcher bound to a bot instance.

func (*Application) AddHandler

func (app *Application) AddHandler(handler Handler)

AddHandler appends a handler to the routing chain.

func (*Application) AddHandlerHandle added in v0.2.0

func (app *Application) AddHandlerHandle(handler Handler) *HandlerHandle

AddHandlerHandle appends a handler and returns a removable registration handle. Each application-level registration is placed in its own implicit group so all matching handlers continue to run in registration order.

func (*Application) Bot

func (app *Application) Bot() *tg.Bot

Bot returns the bound bot instance.

func (*Application) Group added in v0.2.0

func (app *Application) Group(filters ...Filter) *HandlerGroup

Group creates an explicit handler group at the default priority. All non-nil filters must match before the group is considered.

func (*Application) GroupWithPriority added in v0.2.0

func (app *Application) GroupWithPriority(priority int, filters ...Filter) *HandlerGroup

GroupWithPriority creates an explicit handler group. Lower priorities run first; groups with equal priority keep registration order.

func (*Application) ProcessUpdate

func (app *Application) ProcessUpdate(ctx context.Context, update *Update) error

ProcessUpdate routes an update through all matching handlers.

func (*Application) RunPolling

func (app *Application) RunPolling(ctx context.Context, opts ...PollingOption) error

RunPolling starts long polling in the background and routes updates through the application.

func (*Application) SetErrorHandler

func (app *Application) SetErrorHandler(handler ErrorHandler)

SetErrorHandler updates the global handler error callback.

func (*Application) Use added in v0.2.0

func (app *Application) Use(middleware ...Middleware)

Use appends global middleware in execution order.

func (*Application) WebhookHandler

func (app *Application) WebhookHandler(secretToken string) http.Handler

WebhookHandler returns a synchronous http.Handler for Telegram webhook callbacks. If secretToken is non-empty, requests must pass X-Telegram-Bot-Api-Secret-Token.

func (*Application) WebhookHandlerWithOptions added in v0.2.0

func (app *Application) WebhookHandlerWithOptions(secretToken string, opts ...WebhookOption) http.Handler

WebhookHandlerWithOptions returns a configurable http.Handler for Telegram webhook callbacks. Processing is synchronous and completes before ACK unless AckAfterEnqueue is selected with a dispatcher.

type Context

type Context struct {
	Bot    *tg.Bot
	Update *Update
	// contains filtered or unexported fields
}

Context holds routing state for handlers.

func (*Context) Command

func (ctx *Context) Command() (string, string, bool)

Command extracts command and args from the effective message text.

func (*Context) EffectiveMessage

func (ctx *Context) EffectiveMessage() *tg.Message

EffectiveMessage returns the first message-like payload.

func (*Context) PropagationStopped added in v0.2.0

func (ctx *Context) PropagationStopped() bool

PropagationStopped reports whether later handler groups should be skipped.

func (*Context) StopPropagation added in v0.2.0

func (ctx *Context) StopPropagation()

StopPropagation prevents later handler groups from processing this update. The currently running handler and its middleware are allowed to finish.

func (*Context) UpdateType

func (ctx *Context) UpdateType() UpdateType

UpdateType returns the concrete update type.

type Dispatcher added in v0.2.0

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

Dispatcher owns a bounded update queue and a fixed worker pool.

func NewDispatcher added in v0.2.0

func NewDispatcher(app *Application) (*Dispatcher, error)

NewDispatcher creates a dispatcher and starts its fixed worker pool.

func (*Dispatcher) Dispatch added in v0.2.0

func (dispatcher *Dispatcher) Dispatch(ctx context.Context, update *Update) error

Dispatch admits an update with backpressure and waits for its result.

func (*Dispatcher) Done added in v0.2.0

func (dispatcher *Dispatcher) Done() <-chan struct{}

Done is closed after Shutdown has drained the queue and all workers exit.

func (*Dispatcher) Enqueue added in v0.2.0

func (dispatcher *Dispatcher) Enqueue(ctx context.Context, update *Update) error

Enqueue admits an update with backpressure and returns before processing. Request cancellation is detached after the update has been admitted.

func (*Dispatcher) Shutdown added in v0.2.0

func (dispatcher *Dispatcher) Shutdown(ctx context.Context) error

Shutdown stops admission, drains accepted updates, and waits for workers. If ctx expires, active and queued handler contexts are canceled before returning.

func (*Dispatcher) TryEnqueue added in v0.2.0

func (dispatcher *Dispatcher) TryEnqueue(ctx context.Context, update *Update) error

TryEnqueue admits an update without waiting for queue capacity. Request cancellation is detached after the update has been admitted.

type ErrorHandler

type ErrorHandler func(context.Context, *Context, error)

ErrorHandler handles errors produced by routed handlers.

type Filter

type Filter interface {
	Match(*Context) bool
}

Filter decides whether a message-like update should be handled.

func And

func And(filters ...Filter) Filter

And matches when all provided filters match.

func AnyFilter

func AnyFilter() Filter

AnyFilter matches every context.

func CommandFilter

func CommandFilter() Filter

CommandFilter matches updates whose effective message contains a command.

func Not

func Not(filter Filter) Filter

Not negates a filter.

func Or

func Or(filters ...Filter) Filter

Or matches when any provided filter matches.

func RegexFilter

func RegexFilter(pattern *regexp.Regexp) Filter

RegexFilter matches updates whose effective message text matches the regexp.

func TextFilter

func TextFilter() Filter

TextFilter matches updates whose effective message has non-empty text.

func UpdateTypeFilter

func UpdateTypeFilter(updateTypes ...UpdateType) Filter

UpdateTypeFilter matches the provided update types.

type FilterFunc

type FilterFunc func(*Context) bool

FilterFunc is an adapter for inline filter callbacks.

func (FilterFunc) Match

func (fn FilterFunc) Match(ctx *Context) bool

Match evaluates the filter.

type Handler

type Handler interface {
	Match(*Context) bool
	Handle(context.Context, *Context) error
}

Handler is the routing contract used by Application.

func NewAnyHandler

func NewAnyHandler(fn HandlerFunc) Handler

NewAnyHandler registers a handler that receives all updates.

func NewCallbackQueryHandler

func NewCallbackQueryHandler(pattern *regexp.Regexp, fn HandlerFunc) Handler

NewCallbackQueryHandler registers a callback query handler. If pattern is nil, every callback query will match.

func NewCommandHandler

func NewCommandHandler(command string, fn HandlerFunc) Handler

NewCommandHandler registers a handler for a specific command name.

func NewMessageHandler

func NewMessageHandler(filter Filter, fn HandlerFunc) Handler

NewMessageHandler registers a handler for message-like updates.

func NewTypeHandler

func NewTypeHandler(updateType UpdateType, fn HandlerFunc) Handler

NewTypeHandler registers a handler for a specific update type.

type HandlerFunc

type HandlerFunc func(context.Context, *Context) error

HandlerFunc is an adapter for handler callbacks.

type HandlerGroup added in v0.2.0

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

HandlerGroup is an ordered group of handlers with shared filters and middleware. At most the first matching handler in a group runs for each update.

func (*HandlerGroup) AddHandler added in v0.2.0

func (group *HandlerGroup) AddHandler(handler Handler) *HandlerHandle

AddHandler appends a handler to the group.

func (*HandlerGroup) Group added in v0.2.0

func (group *HandlerGroup) Group(filters ...Filter) *HandlerGroup

Group creates a child group at the default priority. Child groups dynamically inherit filters and middleware from every ancestor.

func (*HandlerGroup) GroupWithPriority added in v0.2.0

func (group *HandlerGroup) GroupWithPriority(priority int, filters ...Filter) *HandlerGroup

GroupWithPriority creates a child group at the provided priority. Lower priorities run first; groups with equal priority keep registration order.

func (*HandlerGroup) SetPriority added in v0.2.0

func (group *HandlerGroup) SetPriority(priority int) *HandlerGroup

SetPriority changes the group priority and returns the group for chaining. Lower priorities run first; equal priorities retain registration order.

func (*HandlerGroup) Use added in v0.2.0

func (group *HandlerGroup) Use(middleware ...Middleware)

Use appends middleware shared by every handler in the group.

type HandlerHandle added in v0.2.0

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

HandlerHandle identifies a registered handler. Remove is safe to call concurrently and more than once.

func (*HandlerHandle) Remove added in v0.2.0

func (handle *HandlerHandle) Remove() bool

Remove unregisters the handler. It reports whether this call removed it.

type Middleware added in v0.2.0

type Middleware func(HandlerFunc) HandlerFunc

Middleware wraps a handler callback.

type Option

type Option func(*Application)

Option configures Application.

func WithContinueOnError

func WithContinueOnError(enabled bool) Option

WithContinueOnError controls whether dispatcher continues after a handler error.

func WithErrorHandler

func WithErrorHandler(handler ErrorHandler) Option

WithErrorHandler sets a global error callback.

func WithHandlerTimeout added in v0.2.0

func WithHandlerTimeout(timeout time.Duration) Option

WithHandlerTimeout limits each update's handler execution time. Handlers must observe context cancellation for timely shutdown.

func WithMaxConcurrentUpdates added in v0.2.0

func WithMaxConcurrentUpdates(count int) Option

WithMaxConcurrentUpdates sets the fixed number of dispatcher workers.

func WithPanicHandler added in v0.2.0

func WithPanicHandler(handler PanicHandler) Option

WithPanicHandler sets the callback for recovered dispatcher panics.

func WithUpdateQueueSize added in v0.2.0

func WithUpdateQueueSize(size int) Option

WithUpdateQueueSize sets the bounded dispatcher queue capacity. A size of zero creates an unbuffered queue.

func WithWebhookBodyLimit

func WithWebhookBodyLimit(limit int64) Option

WithWebhookBodyLimit overrides max webhook body size.

type PanicError added in v0.2.0

type PanicError struct {
	Value any
	Stack []byte
}

PanicError describes a panic recovered by Dispatcher.

func (*PanicError) Error added in v0.2.0

func (err *PanicError) Error() string

Error implements error.

func (*PanicError) Unwrap added in v0.2.0

func (err *PanicError) Unwrap() error

Unwrap exposes a recovered error value when possible.

type PanicHandler added in v0.2.0

type PanicHandler func(context.Context, *Context, *PanicError)

PanicHandler observes a panic recovered while processing an update.

type PollingOption

type PollingOption = tg.UpdatePollerOption

PollingOption configures Application.RunPolling.

func WithPollingAllowedUpdates

func WithPollingAllowedUpdates(updateTypes ...UpdateType) PollingOption

WithPollingAllowedUpdates converts ext update types into poller allowed_updates values.

func WithPollingNonBlockingDispatch

func WithPollingNonBlockingDispatch() PollingOption

WithPollingNonBlockingDispatch allows the root poller to drop local updates instead of waiting when its downstream channel is full.

type Update

type Update = tg.Update

Update aliases the root SDK envelope so ext cannot drift from generated fields.

func DecodeUpdate

func DecodeUpdate(data []byte) (*Update, error)

DecodeUpdate parses a Telegram update payload.

func DecodeUpdateFromReader

func DecodeUpdateFromReader(reader io.Reader) (*Update, error)

DecodeUpdateFromReader parses a Telegram update payload from a reader.

func WrapUpdate

func WrapUpdate(update tg.Update) *Update

WrapUpdate preserves the compatibility entry point for callers using ext.

type UpdateType

type UpdateType = tg.UpdateType

UpdateType is Telegram's update discriminator used by the application dispatcher.

type WebhookAckMode added in v0.2.0

type WebhookAckMode uint8

WebhookAckMode controls when a webhook request is acknowledged.

const (
	// AckAfterProcess acknowledges only after update processing completes.
	AckAfterProcess WebhookAckMode = iota
	// AckAfterEnqueue acknowledges after a dispatcher accepts the update.
	AckAfterEnqueue
)

type WebhookOption added in v0.2.0

type WebhookOption interface {
	// contains filtered or unexported methods
}

WebhookOption configures Application.WebhookHandler.

func WithWebhookDispatcher added in v0.2.0

func WithWebhookDispatcher(dispatcher *Dispatcher) WebhookOption

WithWebhookDispatcher routes webhook updates through dispatcher. The dispatcher must belong to the Application serving the webhook.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL