Contributing
Contributing to Alita Robot
Section titled “Contributing to Alita Robot”Thank you for your interest in contributing to Alita Robot! This guide will help you get started with development.
Development Setup
Section titled “Development Setup”Prerequisites
Section titled “Prerequisites”- Go 1.25+
- PostgreSQL 14+
- Redis 6+
- Make (for running commands)
Clone and Setup
Section titled “Clone and Setup”git clone https://github.com/divkix/Alita_Robot.gitcd Alita_Robotcp sample.env .env# Edit .env with your configurationEssential Commands
Section titled “Essential Commands”make run # Run the bot locallymake build # Build release artifacts using goreleasermake lint # Run golangci-lint for code quality checksmake tidy # Clean up and download go.mod dependenciesDatabase Commands
Section titled “Database Commands”make psql-migrate # Apply all pending PostgreSQL migrationsmake psql-status # Check current migration statusmake psql-reset # Reset database (DANGEROUS: drops all tables)Project Structure
Section titled “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 pointAdding a New Module
Section titled “Adding a New Module”- Create database operations in
alita/db/{module}_db.go - Implement command handlers in
alita/modules/{module}.go - Register commands in the module’s
init()function - Add translations to
locales/en.yml(and other locale files)
Module Template
Section titled “Module Template”package modules
import ( "github.com/divkix/Alita_Robot/alita/utils/helpers" "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 init() { helpers.RegisterModule(myModule.moduleName, func(d *ext.Dispatcher) { d.AddHandler(handlers.NewCommand("mycommand", myModule.myCommand)) })}Code Style
Section titled “Code Style”- Run
make lintbefore committing - Follow Go conventions and idioms
- Use the repository pattern for data access
- Add proper error handling with panic recovery
- Use decorators for common middleware (permissions, error handling)
Security Best Practices
Section titled “Security Best Practices”HTML Escaping
Section titled “HTML Escaping”Always escape user-controlled input before rendering in HTML-formatted messages:
import "github.com/divkix/Alita_Robot/alita/utils/helpers"
// Wrong - vulnerable to HTML injectiontext := fmt.Sprintf("Welcome to %s!", chat.Title)
// Correct - escapedtext := fmt.Sprintf("Welcome to %s!", helpers.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:
helpers.MentionHtml(userId, name)- Already escapes the namehelpers.MentionUrl(url, name)- Already escapes the name
Goroutine Error Handling
Section titled “Goroutine Error Handling”When running database operations in goroutines, always:
- Capture variables for closure safety
- Add panic recovery
- Handle and log errors
// Correct patternchatId := chat.Id // Capture variablego 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
Section titled “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
Section titled “Translation Guidelines”Add help messages to locales/en.yml:
mymodule_help_msg: | Help text for my module.
*Commands:* × /mycommand: Description of command.Testing
Section titled “Testing”The project uses golangci-lint for code quality. Manual testing is done with a test bot and group:
- Create a test bot via @BotFather
- Create a test group
- Configure your
.envwith the test bot token - Run
make runand test your changes
Submitting Changes
Section titled “Submitting Changes”- Fork the repository
- Create a feature branch (
git checkout -b feature/my-feature) - Make your changes
- Run
make lintto check for issues - Commit with a descriptive message
- Push to your fork
- Open a Pull Request
Common Pitfalls
Section titled “Common Pitfalls”These are common bugs to avoid when developing modules:
Nil Pointer on User Extraction
Section titled “Nil Pointer on User Extraction”Problem: ctx.EffectiveSender.User can be nil for channel posts.
// Wrong - will panic on channel postsuser := ctx.EffectiveSender.UseruserId := user.Id
// Correct - use the safe helperuser := chat_status.RequireUser(b, ctx, false)if user == nil { return ext.EndGroups}Shadow Variables in Conditionals
Section titled “Shadow Variables in Conditionals”Problem: Using := inside conditionals creates a new variable that shadows the outer one.
// Wrong - shadows outer userIdif condition { userId := someValue // New variable!}// userId here is still the original value
// Correct - reassigns the outer variableif condition { userId = someValue // Reassigns existing variable}Empty Slice Access
Section titled “Empty Slice Access”Problem: Accessing slice elements without bounds checking.
// Wrong - panics if args is emptyargs := strings.Fields(input)firstArg := args[0]
// Correct - check length firstargs := strings.Fields(input)if len(args) == 0 { // Handle empty case return}firstArg := args[0]Callback Data Validation
Section titled “Callback Data Validation”Problem: Not validating callback query data before parsing.
// Wrong - panics on malformed dataargs := strings.Split(query.Data, ".")action := args[1]userId, _ := strconv.Atoi(args[2])
// Correct - validate firstargs := 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
Section titled “Goroutine Variable Capture”Problem: Goroutines capturing loop variables or mutable state.
// Risky - captures variables by referencego func() { doSomething(userId) // userId might change}()
// Safer - pass as parametersgo func(uid int64) { doSomething(uid)}(userId)Markdown/HTML Parse Mode Mismatch
Section titled “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 modehelpMsg, _ := 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 sendinghelpMsg, _ := 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
Section titled “Getting Help”- Support Group: t.me/DivideSupport
- GitHub Issues: Report bugs or request features