Contributing
How to contribute to Alita Robot development.
Thank you for your interest in contributing to Alita Robot! This guide will help you get started with development.
Development Setup
Prerequisites
- Go 1.26+
- PostgreSQL 16+
- Redis 7+
- Make (for running commands)
Clone and Setup
Clone the repository
git clone https://github.com/divkix/Alita_Robot.git
cd Alita_RobotConfigure environment
cp sample.env .env
# Edit .env with your configurationVerify the build
make lint
make test
make runEssential Commands
make run # Run the bot locally
make build # Build release artifacts using goreleaser
make lint # Run golangci-lint for code quality checks
make test # Run test suite
make tidy # Clean up and download go.mod dependencies
Database Commands
make psql-migrate # Apply all pending PostgreSQL migrations
make psql-status # Check current migration status
make psql-reset # Reset database (DANGEROUS: drops all tables)
Useful Environment Variables
| Variable | Purpose |
|---|---|
DEBUG=true |
Enable verbose logging and stack traces |
AUTO_MIGRATE=true |
Apply database migrations on startup |
ENABLE_DB_MONITORING=true |
Log database pool stats every minute |
ENABLE_PPROF=true |
Expose pprof profiling endpoints (dev only) |
Project Structure
Alita_Robot/
├── alita/
│ ├── config/ # Configuration and environment parsing
│ ├── db/ # Database operations and repositories
│ ├── i18n/ # Internationalization
│ ├── modules/ # Command handlers (one file per module)
│ └── utils/ # Utility functions and decorators
├── locales/ # Translation files (YAML)
├── migrations/ # SQL migration files
└── main.go # Entry point
Adding a New Module
Create database operations
Add them in alita/db/{module}/repository.go (following the domain-package pattern).
Implement command handlers
Put them in alita/modules/{module}.go.
Register commands
Register commands in a LoadXxx(dispatcher) function.
Add translations
Add translations to locales/en.yml (and other locale files).
Self-register in init()
Use RegisterLegacyModule("MyModule", priority, LoadMyModule) — modules auto-load via LoadAllModules() in alita/main.go.
Module Template
package modules
import (
"github.com/PaulSonOfLars/gotgbot/v2"
"github.com/PaulSonOfLars/gotgbot/v2/ext"
"github.com/PaulSonOfLars/gotgbot/v2/ext/handlers"
)
var myModule = moduleStruct{moduleName: "MyModule"}
func (m moduleStruct) myCommand(b *gotgbot.Bot, ctx *ext.Context) error {
// Implementation
return ext.EndGroups
}
func LoadMyModule(d *ext.Dispatcher) {
d.AddHandler(handlers.NewCommand("mycommand", myModule.myCommand))
}
func init() {
RegisterLegacyModule("MyModule", 100, LoadMyModule)
}
Code Style
The repository uses pre-commit hooks that run automatically on git commit:
- golangci-lint — Code quality and lint checks
- gofmt — Code formatting enforcement
- go mod tidy — Dependency cleanup
Install hooks:
pip install pre-commit && pre-commit install
- Run
make lintbefore committing - Run
make testbefore committing - Follow Go conventions and idioms
- Add proper error handling with panic recovery
- Use decorators for common middleware (permissions, error handling)
Security Best Practices
HTML Escaping
Always escape user-controlled input before rendering in HTML-formatted messages:
import "github.com/divkix/Alita_Robot/alita/utils/formatting"
// Wrong - vulnerable to HTML injection
text := fmt.Sprintf("Welcome to %s!", chat.Title)
// Correct - escaped
text := fmt.Sprintf("Welcome to %s!", formatting.HtmlEscape(chat.Title))
When to escape:
- Chat titles and descriptions
- Usernames (when displaying as text, not as @mentions)
- Any user-supplied text in HTML-formatted messages
Safe alternatives:
formatting.MentionHtml(userId, name)- Already escapes the nameformatting.MentionUrl(url, name)- Already escapes the name
Goroutine Error Handling
When running database operations in goroutines, always:
- Capture variables for closure safety
- Add panic recovery
- Handle and log errors
// Correct pattern
chatId := chat.Id // Capture variable
go func() {
defer error_handling.RecoverFromPanic("SetAnonAdminMode", "admin")
if err := db.SetAnonAdminMode(chatId, true); err != nil {
log.Errorf("[Admin] Failed to set anon admin mode: %v", err)
}
}()
User Input Validation
- Never trust usernames from user input for security-critical operations
- Validate user IDs against Telegram API when necessary
- Use
extraction.ExtractUserAndText()for consistent user resolution
Translation Guidelines
Add help messages to locales/en.yml:
mymodule_help_msg: |
Help text for my module.
*Commands:*
× /mycommand: Description of command.
Testing
Automated regression tests are required for all contributions:
- Run
make test(orgo test ./...) and make sure all tests pass - Run
make lintand fix any lint findings - Manually verify behavior with a test bot/group for user-facing changes
Submitting Changes
Fork the repository
Create a feature branch
git checkout -b feature/my-featureMake your changes
Run quality checks
make test && make lintCommit with a descriptive message
Push to your fork
Open a Pull Request
Common Pitfalls
These are common bugs to avoid when developing modules:
Nil Pointer on User Extraction
Problem: ctx.EffectiveSender.User can be nil for channel posts.
// Wrong - will panic on channel posts
user := ctx.EffectiveSender.User
userId := user.Id
// Correct - use the safe helper
user := chat_status.RequireUser(b, ctx, false)
if user == nil {
return ext.EndGroups
}
Shadow Variables in Conditionals
Problem: Using := inside conditionals creates a new variable that shadows the outer one.
// Wrong - shadows outer userId
if condition {
userId := someValue // New variable!
}
// userId here is still the original value
// Correct - reassigns the outer variable
if condition {
userId = someValue // Reassigns existing variable
}
Empty Slice Access
Problem: Accessing slice elements without bounds checking.
// Wrong - panics if args is empty
args := strings.Fields(input)
firstArg := args[0]
// Correct - check length first
args := strings.Fields(input)
if len(args) == 0 {
// Handle empty case
return
}
firstArg := args[0]
Callback Data Validation
Problem: Not validating callback query data before parsing.
// Wrong - panics on malformed data
args := strings.Split(query.Data, ".")
action := args[1]
userId, _ := strconv.Atoi(args[2])
// Correct - validate first
args := strings.Split(query.Data, ".")
if len(args) < 3 {
log.Error("Malformed callback data")
return ext.EndGroups
}
action := args[1]
userId, err := strconv.Atoi(args[2])
if err != nil {
log.Error("Invalid userId in callback")
return ext.EndGroups
}
Goroutine Variable Capture
Problem: Goroutines capturing loop variables or mutable state.
// Risky - captures variables by reference
go func() {
doSomething(userId) // userId might change
}()
// Safer - pass as parameters
go func(uid int64) {
doSomething(uid)
}(userId)
Markdown/HTML Parse Mode Mismatch
Problem: Locale strings use Markdown formatting (*bold*, `code`) but the bot sends messages with HTML parse mode, causing raw asterisks to appear instead of formatted text.
// Wrong - locale uses Markdown, but sending with HTML parse mode
helpMsg, _ := tr.GetString("module_help_msg") // Contains *bold*
b.SendMessage(chatId, helpMsg, &gotgbot.SendMessageOpts{
ParseMode: helpers.HTML, // Markdown won't render!
})
// Correct - convert Markdown to HTML before sending
helpMsg, _ := tr.GetString("module_help_msg")
htmlMsg := tgmd2html.MD2HTMLV2(helpMsg) // Converts *bold* to <b>bold</b>
b.SendMessage(chatId, htmlMsg, &gotgbot.SendMessageOpts{
ParseMode: helpers.HTML,
})
The tgmd2html library provides conversion functions:
tgmd2html.MD2HTMLV2(text)- Converts Markdown formatting to HTMLtgmd2html.MD2HTMLButtonsV2(text)- Converts and extracts inline buttons
Getting Help
- Support Group: t.me/DivideSupport
- GitHub Issues: Report bugs or request features