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:

if os.Args[1] == "--health" {
// HTTP GET to /health, exit with status code
os.Exit(0)
}
defer func() {
if r := recover(); r != nil {
log.Errorf("[Main] Panic recovered: %v", r)
os.Exit(1)
}
}()
localeManager := i18n.GetManager()
localeManager.Initialize(&Locales, "locales", i18n.DefaultManagerConfig())
if config.AppConfig.EnableSentry {
sentry.Init(sentry.ClientOptions{
Dsn: config.AppConfig.SentryDSN,
Environment: config.AppConfig.SentryEnvironment,
// ...
})
}
httpTransport := &http.Transport{
MaxIdleConns: config.AppConfig.HTTPMaxIdleConns,
MaxIdleConnsPerHost: config.AppConfig.HTTPMaxIdleConnsPerHost,
IdleConnTimeout: 120 * time.Second,
ForceAttemptHTTP2: true,
}
b, err := gotgbot.NewBot(config.AppConfig.BotToken, &gotgbot.BotOpts{
BotClient: &gotgbot.BaseBotClient{
Client: http.Client{Transport: transport, Timeout: 30 * time.Second},
},
})
go func() {
for i := 0; i < 3; i++ {
b.GetMe(nil) // Establish connection pool
time.Sleep(100 * time.Millisecond)
}
}()
alita.InitialChecks(b) // Validates config, initializes cache
if config.AppConfig.EnableAsyncProcessing {
async.InitializeAsyncProcessor()
}
dispatcher := ext.NewDispatcher(&ext.DispatcherOpts{
Error: errorHandler,
MaxRoutines: config.AppConfig.DispatcherMaxRoutines,
})
statsCollector = monitoring.NewBackgroundStatsCollector()
autoRemediation = monitoring.NewAutoRemediationManager(statsCollector)
activityMonitor = monitoring.NewActivityMonitor()
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.ShouldSuppressFromSentry(err) {
log.WithFields(logFields).Warn("Expected error (suppressed)")
return ext.DispatcherActionNoop
}
// 4. Log and report to Sentry
log.WithFields(logFields).Error("Handler error")
sentry.CaptureException(err)
// 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