Request Flow
How Telegram updates are processed through the Alita Robot pipeline.
This document details how Telegram updates flow through Alita Robot, from reception to response.
Update Processing Pipeline
+-------------+ +----------------+ +------------+ +----------+
| Telegram | --> | HTTP/Polling | --> | Dispatcher | --> | Handlers |
| Bot API | | Receiver | | (Router) | | (Modules)|
+-------------+ +----------------+ +------------+ +----------+
| |
| v
| +------+------+
| | Permission |
| | Checks |
| +------+------+
| |
| v
| +------+------+
| | Database |
| | / Cache |
| +------+------+
| |
v v
+----------+ +----------+
| Error | | Response |
| Handler | | to User |
+----------+ +----------+
Initialization Sequence
When the bot starts (main.go), it performs these steps in order:
Health Check Mode
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 len(os.Args) > 1 && (os.Args[1] == "--health" || os.Args[1] == "-health") {
// HTTP GET to /health, exit with status code
os.Exit(0)
}Version Check
If the --version, -version, or -v flag is passed, the process prints the bot version and exits immediately without initializing any services.
if len(os.Args) > 1 && (os.Args[1] == "--version" || os.Args[1] == "-version" || os.Args[1] == "-v") {
fmt.Println(config.AppConfig.BotVersion)
os.Exit(0)
}Panic Recovery Setup
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)
}
}()Cache Initialization
The Redis cache is initialized with connection retry logic (exponential backoff). Startup fails if Redis is unavailable.
if err := cache.InitCache(); err != nil {
log.Fatalf("Failed to initialize cache: %v", err)
}Locale Manager Initialization
The singleton LocaleManager loads embedded YAML translation files for all supported languages.
localeManager := i18n.GetManager()
localeManager.Initialize(&Locales, "locales", i18n.DefaultManagerConfig())OpenTelemetry Tracing Initialization
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()HTTP Transport Configuration
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,
}Bot Client Creation
The gotgbot client is initialized with the configured HTTP transport, a 30-second timeout, and the optional custom Bot API URL.
b, err := gotgbot.NewBot(config.AppConfig.BotToken, &gotgbot.BotOpts{
BotClient: &gotgbot.BaseBotClient{
Client: http.Client{Transport: transport, Timeout: 30 * time.Second},
DefaultRequestOpts: &gotgbot.RequestOpts{
APIURL: resolveBotAPIURL(config.AppConfig.ApiServer),
},
},
})Initial Checks
Ensures the bot user exists in the database before modules load, providing the foreign-key anchor used by downstream records. Configuration has already been loaded and validated by the config package.
if err := alita.InitialChecks(b); err != nil {
log.Fatalf("Initial checks failed: %v", err)
}Dispatcher Creation
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,
Processor: tracing.TracingProcessor{},
})Monitoring Systems
Monitoring systems are started based on configuration.
if config.AppConfig.EnableBackgroundStats {
statsCollector = monitoring.NewBackgroundStatsCollector()
}
if config.AppConfig.EnablePerformanceMonitoring {
autoRemediation = monitoring.NewAutoRemediationManager(statsCollector)
}
activityMonitor = monitoring.NewActivityMonitor() // Always startsHTTP Server & Mode Selection
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, appStartTime)
httpServer.RegisterHealth()
httpServer.RegisterMetrics()
httpServer.RegisterDBMetrics()
if config.AppConfig.EnablePPROF {
httpServer.RegisterPPROF()
}
if config.AppConfig.UseWebhooks {
httpServer.RegisterWebhook(b, dispatcher, secret, domain)
} else {
updater.StartPolling(b, pollingOpts)
}Module Loading Order
After dispatcher creation and before Telegram updates are accepted, postInit
loads modules and starts the persisted captcha lifecycle:
func LoadModules(dispatcher *ext.Dispatcher) {
// Initialize help system first
modules.DefaultHelpRegistry().AbleMap = make(map[string]bool)
// Load help LAST (deferred) to collect all commands
defer modules.LoadHelp(dispatcher)
// Loads all registered modules in priority order
modules.LoadAllModules(dispatcher)
}
alita.LoadModules(dispatcher)
if err := modules.StartCaptchaLifecycle(bot); err != nil {
log.Fatalf("[Captcha] Failed to start lifecycle: %v", err)
}
Captcha startup recovery releases incomplete attempts, finalizes expired attempts, and reschedules still-valid attempts. It then starts the periodic attempt-cleanup and scheduled-unmute workers. A recovery failure aborts startup instead of accepting updates with an unknown captcha state.
Registered modules include: BotUpdates, Antispam, Languages, Admin, Approvals, Pins, Misc, Bans, Mutes, Purges, Users, Reports, Dev, Locks, Filters, Antiflood, Notes, Connections, Disabling, Rules, Warns, Greetings, Captcha, AntiRaid, Blacklists, Reactions, Formatting, Backup.
Handler Registration Pattern
Each module registers handlers using gotgbot’s handler system:
func LoadBans(dispatcher *ext.Dispatcher) {
// Register module in help system
DefaultHelpRegistry().AbleMap[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,
))
}
Handler Types
| Type | Registration | Trigger |
|---|---|---|
| Command | handlers.NewCommand("cmd", fn) |
/cmd messages |
| Callback | handlers.NewCallback(filter, fn) |
Button presses |
| Message | handlers.NewMessage(filter, fn) |
Text messages |
| ChatMember | handlers.NewChatMemberUpdated(filter, fn) |
Member updates |
Handler Groups
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)
Permission Check Flow
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) {
return ext.EndGroups
}
// 2. Require user to be admin
if !chat_status.RequireUserAdmin(b, ctx, nil, user.Id) {
return ext.EndGroups
}
// 3. Require bot to be admin
if !chat_status.RequireBotAdmin(b, ctx, nil) {
return ext.EndGroups
}
// 4. Check specific permission (restrict members)
if !chat_status.CanUserRestrict(b, ctx, nil, user.Id) {
return ext.EndGroups
}
// 5. Check bot has same permission
if !chat_status.CanBotRestrict(b, ctx, nil) {
return ext.EndGroups
}
// Proceed with ban logic...
}
Permission Functions Reference
| Function | Purpose | When to Use |
|---|---|---|
RequireGroup |
Ensures chat is group/supergroup | Group-only commands |
RequirePrivate |
Ensures chat is private | PM-only commands |
RequireUserAdmin |
User must be admin | Admin commands |
RequireBotAdmin |
Bot must be admin | Commands needing bot admin |
RequireUserOwner |
User must be creator | Owner-only commands |
CanUserRestrict |
User can ban/mute | Ban/mute commands |
CanBotRestrict |
Bot can ban/mute | Ban/mute commands |
CanUserDelete |
User can delete messages | Purge commands |
CanBotDelete |
Bot can delete messages | Purge commands |
CanUserPin |
User can pin messages | Pin commands |
CanBotPin |
Bot can pin messages | Pin commands |
CanUserPromote |
User can promote/demote | Admin management |
CanBotPromote |
Bot can promote/demote | Admin management |
IsUserAdmin |
Check if user is admin | Conditional logic |
IsUserInChat |
Check if user is member | User validation |
IsUserBanProtected |
Check if user is protected | Before ban/kick |
Response Patterns
Handler Return Values
// Stop processing, no more handlers run
return ext.EndGroups
// Continue to subsequent handler groups
return ext.ContinueGroups
// Error propagates to dispatcher error handler
return err
Response Actions
// 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, &gotgbot.EditMessageTextOpts{Text: "New text"})
// Delete message
msg.Delete(b, nil)
// Answer callback query
query.Answer(b, &gotgbot.AnswerCallbackQueryOpts{
Text: "Notification text",
})
Async Processing
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 Handling in Request Flow
Dispatcher Error Handler
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
}
Common Error Patterns
// 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
Next Steps
- Module Pattern - Creating new feature modules
- Caching - Redis cache integration
- Project Structure - File organization