Skip to content

Request Flow

This document details how Telegram updates flow through Alita Robot, from reception to response.

+-------------+ +----------------+ +------------+ +----------+
| Telegram | --> | HTTP/Polling | --> | Dispatcher | --> | Handlers |
| Bot API | | Receiver | | (Router) | | (Modules)|
+-------------+ +----------------+ +------------+ +----------+
| |
| v
| +------+------+
| | Permission |
| | Checks |
| +------+------+
| |
| v
| +------+------+
| | Database |
| | / Cache |
| +------+------+
| |
v v
+----------+ +----------+
| Error | | Response |
| Handler | | to User |
+----------+ +----------+

When the bot starts (main.go), it performs these steps in order:

  1. Health Check Mode Optional

    If the --health flag is passed, the process performs an HTTP GET to /health and exits with the status code. This is used by Docker health checks.

    if os.Args[1] == "--health" {
    // HTTP GET to /health, exit with status code
    os.Exit(0)
    }
  2. Panic Recovery Setup Safety

    A top-level deferred recovery ensures the process logs the panic and exits cleanly rather than crashing silently.

    defer func() {
    if r := recover(); r != nil {
    log.Errorf("[Main] Panic recovered: %v", r)
    os.Exit(1)
    }
    }()
  3. Locale Manager Initialization i18n

    The singleton LocaleManager loads embedded YAML translation files for all supported languages.

    localeManager := i18n.GetManager()
    localeManager.Initialize(&Locales, "locales", i18n.DefaultManagerConfig())
  4. OpenTelemetry Tracing Initialization Observability

    Sets up distributed tracing with OTLP or console exporters based on environment configuration (OTEL_EXPORTER_OTLP_ENDPOINT, OTEL_SERVICE_NAME, OTEL_TRACES_SAMPLE_RATE).

    tracing.InitTracing()
  5. HTTP Transport Configuration Networking

    Connection pooling is configured for outbound HTTP requests to the Telegram API.

    httpTransport := &http.Transport{
    MaxIdleConns: config.AppConfig.HTTPMaxIdleConns,
    MaxIdleConnsPerHost: config.AppConfig.HTTPMaxIdleConnsPerHost,
    IdleConnTimeout: 120 * time.Second,
    ForceAttemptHTTP2: true,
    }
  6. Bot Client Creation Core

    The gotgbot client is initialized with the configured HTTP transport and a 30-second timeout.

    b, err := gotgbot.NewBot(config.AppConfig.BotToken, &gotgbot.BotOpts{
    BotClient: &gotgbot.BaseBotClient{
    Client: http.Client{Transport: transport, Timeout: 30 * time.Second},
    },
    })
  7. Connection Pre-warming Performance

    Three GetMe calls run in a background goroutine to establish the HTTP connection pool before real traffic arrives.

    go func() {
    for i := 0; i < 3; i++ {
    b.GetMe(nil) // Establish connection pool
    time.Sleep(100 * time.Millisecond)
    }
    }()
  8. Initial Checks Validation

    Validates configuration, initializes cache, and performs startup health checks.

    alita.InitialChecks(b) // Validates config, initializes cache
  9. Async Processor Optional

    If EnableAsyncProcessing is configured, the async processor is initialized for non-critical background operations.

    if config.AppConfig.EnableAsyncProcessing {
    async.InitializeAsyncProcessor()
    }
  10. Dispatcher Creation Core

    The dispatcher routes updates to handlers with bounded concurrency. It uses a TracingProcessor wrapper for trace context propagation in polling mode.

    dispatcher := ext.NewDispatcher(&ext.DispatcherOpts{
    Error: errorHandler,
    MaxRoutines: config.AppConfig.DispatcherMaxRoutines,
    })
  11. Monitoring Systems Observability

    Three monitoring subsystems start: background stats collection (every 30s), auto-remediation (GC triggers on memory thresholds), and activity monitoring (per-chat and per-user tracking).

    statsCollector = monitoring.NewBackgroundStatsCollector()
    autoRemediation = monitoring.NewAutoRemediationManager(statsCollector)
    activityMonitor = monitoring.NewActivityMonitor()
  12. HTTP Server & Mode Selection Core

    The unified HTTP server registers health, metrics, and optionally pprof endpoints. Then either webhook or polling mode is activated.

    httpServer := httpserver.New(config.AppConfig.HTTPPort)
    httpServer.RegisterHealth()
    httpServer.RegisterMetrics()
    if config.AppConfig.UseWebhooks {
    httpServer.RegisterWebhook(b, dispatcher, secret, domain)
    } else {
    updater.StartPolling(b, pollingOpts)
    }

After dispatcher creation, modules are loaded via alita/main.go:

func LoadModules(dispatcher *ext.Dispatcher) {
// Initialize help system first
modules.HelpModule.AbleMap.Init()
// Load help LAST (deferred) to collect all commands
defer modules.LoadHelp(dispatcher)
// Core modules (order matters for handler priority)
modules.LoadBotUpdates(dispatcher) // Bot status tracking
modules.LoadAntispam(dispatcher) // Spam protection
modules.LoadLanguage(dispatcher) // Language settings
modules.LoadAdmin(dispatcher) // Admin commands
modules.LoadPin(dispatcher) // Pin management
modules.LoadMisc(dispatcher) // Misc utilities
modules.LoadBans(dispatcher) // Ban/kick commands
modules.LoadMutes(dispatcher) // Mute commands
modules.LoadPurges(dispatcher) // Message purging
modules.LoadUsers(dispatcher) // User tracking
modules.LoadReports(dispatcher) // Report system
modules.LoadDev(dispatcher) // Developer tools
modules.LoadLocks(dispatcher) // Chat locks
modules.LoadFilters(dispatcher) // Message filters
modules.LoadAntiflood(dispatcher) // Flood protection
modules.LoadNotes(dispatcher) // Notes system
modules.LoadConnections(dispatcher) // Chat connections
modules.LoadDisabling(dispatcher) // Command disabling
modules.LoadRules(dispatcher) // Rules management
modules.LoadWarns(dispatcher) // Warning system
modules.LoadGreetings(dispatcher) // Welcome messages
modules.LoadCaptcha(dispatcher) // CAPTCHA verification
modules.LoadBlacklists(dispatcher) // Blacklist system
modules.LoadMkdCmd(dispatcher) // Markdown commands
}

Each module registers handlers using gotgbot’s handler system:

func LoadBans(dispatcher *ext.Dispatcher) {
// Register module in help system
HelpModule.AbleMap.Store(bansModule.moduleName, true)
// Command handlers
dispatcher.AddHandler(handlers.NewCommand("ban", bansModule.ban))
dispatcher.AddHandler(handlers.NewCommand("kick", bansModule.kick))
dispatcher.AddHandler(handlers.NewCommand("unban", bansModule.unban))
// Callback query handlers
dispatcher.AddHandler(handlers.NewCallback(
callbackquery.Prefix("restrict."),
bansModule.restrictButtonHandler,
))
}
TypeRegistrationTrigger
Commandhandlers.NewCommand("cmd", fn)/cmd messages
Callbackhandlers.NewCallback(filter, fn)Button presses
Messagehandlers.NewMessage(filter, fn)Text messages
ChatMemberhandlers.NewChatMemberUpdated(filter, fn)Member updates

Handlers can be assigned to groups for priority control:

// Negative group = higher priority (runs first)
dispatcher.AddHandlerToGroup(handler, -10)
// Group 0 = default
dispatcher.AddHandler(handler) // Same as group 0
// Positive group = lower priority
dispatcher.AddHandlerToGroup(handler, 10)

Most admin commands follow this permission checking pattern:

func (m moduleStruct) ban(b *gotgbot.Bot, ctx *ext.Context) error {
chat := ctx.EffectiveChat
user := ctx.EffectiveSender.User
msg := ctx.EffectiveMessage
// 1. Require group chat (not private)
if !chat_status.RequireGroup(b, ctx, nil, false) {
return ext.EndGroups
}
// 2. Require user to be admin
if !chat_status.RequireUserAdmin(b, ctx, nil, user.Id, false) {
return ext.EndGroups
}
// 3. Require bot to be admin
if !chat_status.RequireBotAdmin(b, ctx, nil, false) {
return ext.EndGroups
}
// 4. Check specific permission (restrict members)
if !chat_status.CanUserRestrict(b, ctx, nil, user.Id, false) {
return ext.EndGroups
}
// 5. Check bot has same permission
if !chat_status.CanBotRestrict(b, ctx, nil, false) {
return ext.EndGroups
}
// Proceed with ban logic...
}
FunctionPurposeWhen to Use
RequireGroupEnsures chat is group/supergroupGroup-only commands
RequirePrivateEnsures chat is privatePM-only commands
RequireUserAdminUser must be adminAdmin commands
RequireBotAdminBot must be adminCommands needing bot admin
RequireUserOwnerUser must be creatorOwner-only commands
CanUserRestrictUser can ban/muteBan/mute commands
CanBotRestrictBot can ban/muteBan/mute commands
CanUserDeleteUser can delete messagesPurge commands
CanBotDeleteBot can delete messagesPurge commands
CanUserPinUser can pin messagesPin commands
CanBotPinBot can pin messagesPin commands
CanUserPromoteUser can promote/demoteAdmin management
CanBotPromoteBot can promote/demoteAdmin management
IsUserAdminCheck if user is adminConditional logic
IsUserInChatCheck if user is memberUser validation
IsUserBanProtectedCheck if user is protectedBefore ban/kick
// Stop processing, no more handlers run
return ext.EndGroups
// Continue to next handler in same group
return ext.ContinueGroups
// Error propagates to dispatcher error handler
return err
// Reply to the triggering message
msg.Reply(b, "Response text", &gotgbot.SendMessageOpts{
ParseMode: helpers.HTML,
})
// Send new message to chat
b.SendMessage(chat.Id, "Message text", nil)
// Edit existing message
msg.EditText(b, "New text", nil)
// Delete message
msg.Delete(b, nil)
// Answer callback query
query.Answer(b, &gotgbot.AnswerCallbackQueryOpts{
Text: "Notification text",
})

Non-critical operations can be processed asynchronously:

// Fire and forget (with panic recovery)
go func() {
defer func() {
if r := recover(); r != nil {
log.Error("Panic in async operation")
}
}()
// Async work here
}()
// With timeout protection
go func() {
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
select {
case <-time.After(2 * time.Second):
// Do delayed work
case <-ctx.Done():
log.Warn("Operation timed out")
}
}()
Error: func(b *gotgbot.Bot, ctx *ext.Context, err error) ext.DispatcherAction {
// 1. Recover from panics
defer error_handling.RecoverFromPanic("DispatcherErrorHandler", "Main")
// 2. Extract context for logging
logFields := log.Fields{
"update_id": ctx.UpdateId,
"error_type": fmt.Sprintf("%T", err),
}
// 3. Check for expected/suppressible errors
if helpers.IsExpectedTelegramError(err) {
log.WithFields(logFields).Warn("Expected Telegram API error")
return ext.DispatcherActionNoop
}
// 4. Log the error
log.WithFields(logFields).Error("Handler error")
// 5. Continue processing other updates
return ext.DispatcherActionNoop
}
// Log and return error (propagates to dispatcher)
if err != nil {
log.Error(err)
return err
}
// Log but continue (non-fatal)
if err != nil {
log.Warn("Non-fatal error:", err)
}
// Silent failure for expected cases
_, _ = msg.Delete(b, nil) // Ignore delete errors